package billing import "testing" func TestIsCustomPackage(t *testing.T) { cases := []struct { name string isCustom bool want bool }{ {"Free", false, false}, {"Starter", false, false}, {"Growth", false, false}, {"Business", false, false}, {"Enterprise", true, true}, {"Enterprise", false, false}, // public ladder without is_custom flag {"A1", false, false}, // legacy name without is_custom → limited matrix {"A1", true, true}, // dump-faithful A1 PAYG is_custom → custom profile (Stores/AI still gated) {"Legacy", true, false}, // exact Legacy package never enable-all {"Merkur trial", false, true}, {" growth ", false, false}, {"", false, true}, // empty name is not a public plan name } for _, tc := range cases { got := IsCustomPackage(tc.name, tc.isCustom) if got != tc.want { t.Fatalf("IsCustomPackage(%q, %v)=%v want %v", tc.name, tc.isCustom, got, tc.want) } } } func TestAllRegistryFeatures(t *testing.T) { on := AllRegistryFeatures(true) off := AllRegistryFeatures(false) if len(on) != len(FeatureCatalogKeys) || len(off) != len(FeatureCatalogKeys) { t.Fatalf("len on=%d off=%d catalog=%d", len(on), len(off), len(FeatureCatalogKeys)) } for _, k := range FeatureCatalogKeys { if !on[k] { t.Fatalf("expected %s enabled", k) } if off[k] { t.Fatalf("expected %s disabled", k) } } } func TestPrepareCustomPackageCreateFeatures(t *testing.T) { t.Run("custom create without features enables all", func(t *testing.T) { p := Plan{Name: "ClientCo Deal", IsCustom: true} prepareCustomPackageCreateFeatures(&p, true, false) if p.Features == nil || len(p.Features) != len(FeatureCatalogKeys) { t.Fatalf("expected full enable-all features, got %#v", p.Features) } for _, k := range FeatureCatalogKeys { if !p.Features[k] { t.Fatalf("key %s not enabled", k) } } }) t.Run("non-public name forces is_custom", func(t *testing.T) { p := Plan{Name: "Merkur", IsCustom: false} prepareCustomPackageCreateFeatures(&p, true, false) if !p.IsCustom { t.Fatal("expected is_custom forced true for client deal") } if len(p.Features) != len(FeatureCatalogKeys) { t.Fatalf("expected enable-all after force custom, got %d keys", len(p.Features)) } }) t.Run("public free create leaves features nil", func(t *testing.T) { p := Plan{Name: "Free", IsCustom: false} prepareCustomPackageCreateFeatures(&p, true, false) if p.Features != nil { t.Fatalf("standard Free must not materialize features: %#v", p.Features) } }) t.Run("explicit features respected", func(t *testing.T) { p := Plan{Name: "A1", IsCustom: true, Features: map[string]bool{"catalog.products": false}} prepareCustomPackageCreateFeatures(&p, true, true) if p.Features["catalog.products"] != false || len(p.Features) != 1 { t.Fatalf("explicit features overwritten: %#v", p.Features) } }) t.Run("update does not rewrite", func(t *testing.T) { p := Plan{Name: "A1", IsCustom: true, ID: 9} prepareCustomPackageCreateFeatures(&p, false, false) if p.Features != nil { t.Fatalf("update must not inject features: %#v", p.Features) } }) t.Run("enterprise is_custom create enables all", func(t *testing.T) { p := Plan{Name: "Enterprise", IsCustom: true} prepareCustomPackageCreateFeatures(&p, true, false) if len(p.Features) != len(FeatureCatalogKeys) { t.Fatalf("enterprise custom create should enable all, got %d", len(p.Features)) } }) t.Run("A1 custom create uses PAYG matrix without Stores/Marketing/Integrations", func(t *testing.T) { p := Plan{Name: "A1", IsCustom: true} prepareCustomPackageCreateFeatures(&p, true, false) if p.IsLegacy { t.Fatal("A1 PAYG create must clear is_legacy") } if !p.IsCustom { t.Fatal("A1 remains a client deal (is_custom)") } if !p.Features["processing.monitor"] { t.Fatal("A1 PAYG must enable processing.monitor") } if !p.Features["capability.eprel"] { t.Fatal("A1 PAYG must enable capability.eprel") } if p.Features["stores.hub"] || p.Features["marketing.campaigns"] || p.Features["integrations.ai"] || p.Features["integrations.email"] { t.Fatal("A1 PAYG must deny stores, marketing, and integrations") } if len(p.Features) != len(FeatureCatalogKeys) { t.Fatalf("expected full tailored matrix, got %d keys", len(p.Features)) } }) t.Run("Legacy create seeds legacy sparse", func(t *testing.T) { p := Plan{Name: "Legacy", IsCustom: false, IsLegacy: true} prepareCustomPackageCreateFeatures(&p, true, false) if !p.IsLegacy { t.Fatal("Legacy create must set is_legacy") } if p.Features["processing.monitor"] { t.Fatal("Legacy must not enable processing.monitor") } }) } func TestDefaultPlanFeaturesCustomUsesIsCustomPackage(t *testing.T) { // Non-legacy client deal without is_custom flag still all-ON via name. m := DefaultPlanFeatures("ClientCo Deal", false) for _, k := range FeatureCatalogKeys { if !m[k] { t.Fatalf("client deal default missing %s", k) } } // A1 without is_custom stays legacy — limited matrix. a1 := DefaultPlanFeatures("A1", false) if a1["processing.monitor"] { t.Fatal("legacy A1 (is_custom=false) must keep processing.monitor off") } a1Payg := DefaultPlanFeatures("A1", true) if !a1Payg["processing.monitor"] || !a1Payg["capability.eprel"] { t.Fatal("A1 PAYG is_custom must enable core PAYG features") } if a1Payg["stores.hub"] || a1Payg["stores.shopify"] || a1Payg["marketing.campaigns"] || a1Payg["integrations.ai"] || a1Payg["integrations.ai.byok"] || a1Payg["integrations.email"] { t.Fatal("A1 PAYG must keep Stores, Marketing, and Integrations off") } free := DefaultPlanFeatures("Free", false) if free["capability.ai_processing"] { t.Fatal("Free should keep AI processing off by default") } } func TestPlanAllowsFeatureCustomByName(t *testing.T) { if !PlanAllowsFeature("Merkur trial", false, nil, "capability.byok") { t.Fatal("non-public package should allow all keys when overrides empty") } if PlanAllowsFeature("Free", false, nil, "capability.byok") { t.Fatal("Free should deny byok by default") } } func TestResolveEffectiveFeaturesSectionGate(t *testing.T) { gates := emptyGatesView() gates.Sections["marketing"] = false features, sections, disabled := ResolveEffectiveFeatures("Merkur", true, nil, gates) if sections["marketing"] { t.Fatal("marketing section should be off") } if features["marketing.campaigns"] { t.Fatal("marketing.campaigns should be effective-false when section off") } found := false for _, d := range disabled { if d == "marketing.campaigns" { found = true break } } if !found { t.Fatal("marketing.campaigns should appear in disabled list") } }