|
| 1 | +package handlers |
| 2 | + |
| 3 | +// admin_allowed_tiers_registry_test.go — rule-18 drift guard. |
| 4 | +// |
| 5 | +// adminAllowedTiers (admin_customers.go) is a hand-maintained closed set of |
| 6 | +// the tiers an admin may set on a team. The capabilities-contract regression |
| 7 | +// (a hardcoded slice that silently dropped hobby_plus for 3h) is the bug class |
| 8 | +// this guards: a NEW tier added to plans.yaml must be consciously filed as |
| 9 | +// either admin-settable OR explicitly excluded — never silently forgotten. |
| 10 | +// |
| 11 | +// Per rule 18 the test iterates the LIVE plans registry rather than a |
| 12 | +// hand-typed tier list, so adding a tier to plans.yaml reds CI until someone |
| 13 | +// makes the decision. |
| 14 | + |
| 15 | +import ( |
| 16 | + "testing" |
| 17 | + |
| 18 | + "instant.dev/internal/plans" |
| 19 | +) |
| 20 | + |
| 21 | +func TestAdminAllowedTiers_StaysInSyncWithPlanRegistry(t *testing.T) { |
| 22 | + // Tiers that are deliberately NOT admin-settable, each with the reason. |
| 23 | + // Adding a tier here is the conscious "this is not an admin headline tier" |
| 24 | + // decision the guard forces. |
| 25 | + adminExcluded := map[string]string{ |
| 26 | + "anonymous": "unclaimed default; not a settable account tier", |
| 27 | + "growth": "upsell-only API tier; admin UI exposes headline tiers only", |
| 28 | + "hobby_plus": "upsell-only API tier; admin UI exposes headline tiers only", |
| 29 | + "hobby_plus_yearly": "yearly variant of an upsell-only tier", |
| 30 | + "hobby_yearly": "yearly billing variant; admin sets the monthly headline tier", |
| 31 | + "pro_yearly": "yearly billing variant; admin sets the monthly headline tier", |
| 32 | + "team_yearly": "yearly billing variant; admin sets the monthly headline tier", |
| 33 | + } |
| 34 | + |
| 35 | + all := plans.Default().All() |
| 36 | + if len(all) == 0 { |
| 37 | + t.Fatal("plans registry is empty — cannot validate adminAllowedTiers drift") |
| 38 | + } |
| 39 | + |
| 40 | + for tier := range all { |
| 41 | + settable := adminAllowedTiers[tier] |
| 42 | + _, excluded := adminExcluded[tier] |
| 43 | + switch { |
| 44 | + case settable && excluded: |
| 45 | + t.Errorf("tier %q is BOTH admin-settable and in the exclude set — contradiction; "+ |
| 46 | + "remove it from one (admin_customers.go / this test)", tier) |
| 47 | + case !settable && !excluded: |
| 48 | + t.Errorf("tier %q exists in plans.yaml but is neither in adminAllowedTiers nor the "+ |
| 49 | + "documented exclude set — file it on one side (rule 18)", tier) |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + // Belt: no stale entry in adminAllowedTiers that no longer exists in the |
| 54 | + // registry (a removed/renamed tier left behind). |
| 55 | + for tier := range adminAllowedTiers { |
| 56 | + if _, ok := all[tier]; !ok { |
| 57 | + t.Errorf("adminAllowedTiers contains %q which is not present in plans.yaml", tier) |
| 58 | + } |
| 59 | + } |
| 60 | + // Belt: no stale exclude entry either. |
| 61 | + for tier := range adminExcluded { |
| 62 | + if _, ok := all[tier]; !ok { |
| 63 | + t.Errorf("adminExcluded test set contains %q which is not present in plans.yaml — "+ |
| 64 | + "drop the stale entry", tier) |
| 65 | + } |
| 66 | + } |
| 67 | +} |
0 commit comments