package billing import ( "errors" "fmt" "testing" ) func TestDefaultPlanFeaturesFreeDeniesAI(t *testing.T) { m := DefaultPlanFeatures("Free", false) for _, key := range []string{ "capability.ai_processing", "catalog.products.process_ai_titles", "marketing.campaigns.generate_ai", "settings.api_keys", "capability.api_access", "capability.email_live_send", } { if m[key] { t.Fatalf("Free should deny %s", key) } } if !m["catalog.products"] || !m["capability.normalize_specs_fill"] { t.Fatal("Free should allow catalog + normalize") } } func TestDefaultPlanFeaturesCustomEnableAll(t *testing.T) { m := DefaultPlanFeatures("Acme Deal", true) for _, k := range FeatureCatalogKeys { if !m[k] { t.Fatalf("custom should enable all; missing %s", k) } } m2 := DefaultPlanFeatures("Enterprise", true) for _, k := range FeatureCatalogKeys { if !m2[k] { t.Fatalf("Enterprise (is_custom) should enable all; missing %s", k) } } } func TestResolveEffectiveFeaturesGlobalSectionDisableAll(t *testing.T) { gates := emptyGatesView() gates.Sections["marketing"] = false features, sections, disabled := ResolveEffectiveFeatures("Growth", false, nil, gates) if sections["marketing"] { t.Fatal("marketing section should be off") } if features["marketing.campaigns"] || features["marketing.campaigns.generate_ai"] { t.Fatal("marketing keys must be false when section disabled") } found := false for _, d := range disabled { if d == "marketing.campaigns.generate_ai" { found = true break } } if !found { t.Fatal("disabled_features should list marketing.campaigns.generate_ai") } if !features["catalog.products"] { t.Fatal("catalog should remain on") } } func TestResolveEffectiveFeaturesCustomOverrideFalse(t *testing.T) { gates := emptyGatesView() overrides := map[string]bool{"settings.api_keys": false} features, _, _ := ResolveEffectiveFeatures("Client Deal", true, overrides, gates) if features["settings.api_keys"] { t.Fatal("override false must win on custom") } if !features["capability.ai_processing"] { t.Fatal("other keys stay on for custom") } } func TestPlanAllowsFeatureCapabilityResolution(t *testing.T) { if PlanAllowsFeature("Free", false, nil, "capability.ai_processing") { t.Fatal("Free deny AI capability") } if !PlanAllowsFeature("Starter", false, nil, "capability.ai_processing") { t.Fatal("Starter allow AI capability") } if PlanAllowsFeature("Starter", false, nil, "capability.byok") { t.Fatal("Starter deny BYOK") } if !PlanAllowsFeature("Growth", false, nil, "capability.byok") { t.Fatal("Growth allow BYOK") } } func TestFeatureKeyFromError(t *testing.T) { err := fmt.Errorf("%w: %s", ErrFeatureDisabled, "marketing.campaigns.generate_ai") if got := FeatureKeyFromError(err); got != "marketing.campaigns.generate_ai" { t.Fatalf("got %q", got) } if FeatureKeyFromError(errors.New("other")) != "" { t.Fatal("non-feature error should yield empty") } } func TestFeatureKeysForProcessingType(t *testing.T) { keys := FeatureKeysForProcessingType("title") if len(keys) != 2 || keys[0] != "capability.ai_processing" { t.Fatalf("title keys: %v", keys) } keys = FeatureKeysForProcessingType("normalize") if len(keys) != 1 || keys[0] != "capability.normalize_specs_fill" { t.Fatalf("normalize keys: %v", keys) } } func TestCloneGatesViewIndependent(t *testing.T) { src := emptyGatesView() src.Sections["marketing"] = false src.Features["capability.ai_processing"] = false dst := cloneGatesView(src) dst.Sections["marketing"] = true dst.Features["capability.ai_processing"] = true if src.Sections["marketing"] { t.Fatal("clone must not share sections map") } if src.Features["capability.ai_processing"] { t.Fatal("clone must not share features map") } }