carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package plans
2
3import (
4 "errors"
5 "os"
6 "strings"
7 "testing"
8)
9
10const examplePath = "testdata/example-catalog.json"
11
12func exampleCatalog(t *testing.T) *Catalog {
13 t.Helper()
14 c, err := LoadCatalog(examplePath)
15 if err != nil {
16 t.Fatalf("load example catalog: %v", err)
17 }
18 return c
19}
20
21func exampleBytes(t *testing.T) string {
22 t.Helper()
23 b, err := os.ReadFile(examplePath)
24 if err != nil {
25 t.Fatal(err)
26 }
27 return string(b)
28}
29
30func TestParseExampleCatalog(t *testing.T) {
31 c := exampleCatalog(t)
32 if got := c.Plans["free"].Limits.RunningThings; got != 3 {
33 t.Fatalf("free running_things = %d, want 3", got)
34 }
35 if c.DefaultPlan != "free" || c.FreeAccountsPerPerson != 1 {
36 t.Fatalf("default_plan %q, free_accounts_per_person %d", c.DefaultPlan, c.FreeAccountsPerPerson)
37 }
38}
39
40func TestParseCatalogRefuses(t *testing.T) {
41 base := exampleBytes(t)
42 cases := map[string]string{
43 "empty version": strings.Replace(base, `"version": "2026-10-20-v1"`, `"version": ""`, 1),
44 "default plan missing": strings.Replace(base, `"default_plan": "free"`, `"default_plan": "gold"`, 1),
45 "pack offered not defined": strings.Replace(base, `["apps", "storage", "email", "bandwidth"]`, `["apps", "seats"]`, 1),
46 "pack adds idle": strings.Replace(base, `{"running_things": 5}`, `{"running_things": 5, "idle_seconds": 60}`, 1),
47 "pack adds memory tier": strings.Replace(base, `{"running_things": 5}`, `{"memory_tier": "default"}`, 1),
48 "pack adds a bool": strings.Replace(base, `{"running_things": 5}`, `{"custom_domains": true}`, 1),
49 "max quantity zero": strings.Replace(base, `{"running_things": 5}, "max_quantity": 20`, `{"running_things": 5}, "max_quantity": 0`, 1),
50 "unknown memory tier": strings.Replace(base, `"memory_tier": "small"`, `"memory_tier": "tiny"`, 1),
51 "unknown field": strings.Replace(base, `"default_plan": "free",`, `"default_plan": "free", "surprise": 1,`, 1),
52 "no plans": `{"version": "v", "default_plan": "free", "plans": {}}`,
53 "negative running things": strings.Replace(base, `"running_things": 3,`, `"running_things": -3,`, 1),
54 "pack adds negative amount": strings.Replace(base, `{"email_per_day": 1000}`, `{"email_per_day": -1000}`, 1),
55 }
56 for name, body := range cases {
57 if body == base {
58 t.Fatalf("%s: replacement did not apply", name)
59 }
60 if _, err := ParseCatalog([]byte(body)); err == nil {
61 t.Errorf("%s: ParseCatalog accepted it", name)
62 }
63 }
64}
65
66func TestCatalogIDChangesWithContent(t *testing.T) {
67 base := exampleBytes(t)
68 a, err := ParseCatalog([]byte(base))
69 if err != nil {
70 t.Fatal(err)
71 }
72 again, err := ParseCatalog([]byte(base))
73 if err != nil {
74 t.Fatal(err)
75 }
76 if a.ID() != again.ID() {
77 t.Fatalf("same bytes, different IDs: %s vs %s", a.ID(), again.ID())
78 }
79 if !strings.HasPrefix(a.ID(), "2026-10-20-v1@") || len(a.ID()) != len("2026-10-20-v1@")+12 {
80 t.Fatalf("ID shape: %q", a.ID())
81 }
82 changed, err := ParseCatalog([]byte(strings.Replace(base, `"running_things": 3,`, `"running_things": 4,`, 1)))
83 if err != nil {
84 t.Fatal(err)
85 }
86 if changed.ID() == a.ID() {
87 t.Fatal("content changed without a version bump but the ID did not move")
88 }
89 reformatted := strings.Join(strings.Fields(base), " ")
90 reformatted = strings.Replace(reformatted, `"free_accounts_per_person": 1, "plans"`, `"plans"`, 1)
91 reformatted = strings.Replace(reformatted, `"version": "2026-10-20-v1",`, `"free_accounts_per_person": 1, "version": "2026-10-20-v1",`, 1)
92 r, err := ParseCatalog([]byte(reformatted))
93 if err != nil {
94 t.Fatal(err)
95 }
96 if r.ID() != a.ID() {
97 t.Fatalf("whitespace and key order changed the ID: %s vs %s", r.ID(), a.ID())
98 }
99}
100
101func TestEffectivePlanOnly(t *testing.T) {
102 c := exampleCatalog(t)
103 got, err := c.Effective("free", nil, nil)
104 if err != nil {
105 t.Fatal(err)
106 }
107 if got != c.Plans["free"].Limits {
108 t.Fatalf("got %+v, want %+v", got, c.Plans["free"].Limits)
109 }
110}
111
112func TestEffectiveAddsPacks(t *testing.T) {
113 c := exampleCatalog(t)
114 got, err := c.Effective("personal", map[string]int{"apps": 2, "email": 1}, nil)
115 if err != nil {
116 t.Fatal(err)
117 }
118 if got.RunningThings != 35 || got.EmailPerDay != 2000 {
119 t.Fatalf("running_things %d (want 35), email_per_day %d (want 2000)", got.RunningThings, got.EmailPerDay)
120 }
121}
122
123func TestEffectiveClampsToCeilings(t *testing.T) {
124 c := exampleCatalog(t)
125 got, err := c.Effective("personal", map[string]int{"apps": 20, "email": 20}, nil)
126 if err != nil {
127 t.Fatal(err)
128 }
129 if got.RunningThings != 125 {
130 t.Fatalf("running_things %d, want 125", got.RunningThings)
131 }
132 if got.EmailPerDay != 10000 {
133 t.Fatalf("email_per_day %d, want the 10000 ceiling", got.EmailPerDay)
134 }
135 c.Ceilings.RunningThings = 100
136 got, err = c.Effective("personal", map[string]int{"apps": 20}, nil)
137 if err != nil {
138 t.Fatal(err)
139 }
140 if got.RunningThings != 100 {
141 t.Fatalf("running_things %d, want the 100 ceiling", got.RunningThings)
142 }
143}
144
145func TestEffectiveRefuses(t *testing.T) {
146 c := exampleCatalog(t)
147 cases := []struct {
148 name string
149 plan string
150 packs map[string]int
151 want error
152 }{
153 {"unknown plan", "gold", nil, ErrUnknownPlan},
154 {"unknown pack", "personal", map[string]int{"seats": 1}, ErrUnknownPack},
155 {"pack not offered", "free", map[string]int{"apps": 1}, ErrPackNotOffered},
156 {"above max", "personal", map[string]int{"apps": 21}, ErrPackQuantity},
157 {"negative", "personal", map[string]int{"apps": -1}, ErrPackQuantity},
158 {"zero", "personal", map[string]int{"apps": 0}, ErrPackQuantity},
159 }
160 for _, tc := range cases {
161 if _, err := c.Effective(tc.plan, tc.packs, nil); !errors.Is(err, tc.want) {
162 t.Errorf("%s: err %v, want %v", tc.name, err, tc.want)
163 }
164 }
165}
166
167func TestEffectiveSponsoredTakesTheMoreGenerous(t *testing.T) {
168 c := exampleCatalog(t)
169 got, err := c.Effective("personal", nil, &Limits{RunningThings: 300, EmailPerDay: 20})
170 if err != nil {
171 t.Fatal(err)
172 }
173 if got.RunningThings != 300 {
174 t.Fatalf("running_things %d, want 300 (sponsored is not clamped)", got.RunningThings)
175 }
176 if got.EmailPerDay != 1000 {
177 t.Fatalf("email_per_day %d, want the plan's 1000", got.EmailPerDay)
178 }
179 got, err = c.Effective("free", nil, &Limits{CustomDomains: true, MemoryTier: "default"})
180 if err != nil {
181 t.Fatal(err)
182 }
183 if !got.CustomDomains || got.MemoryTier != "default" {
184 t.Fatalf("custom_domains %v, memory_tier %q", got.CustomDomains, got.MemoryTier)
185 }
186}
187
188func TestEffectiveSponsoredZeroIsUnset(t *testing.T) {
189 c := exampleCatalog(t)
190 got, err := c.Effective("free", nil, &Limits{RunningThings: 40})
191 if err != nil {
192 t.Fatal(err)
193 }
194 if got.MinScheduleSeconds != 900 || got.RunningThings != 40 {
195 t.Fatalf("min_schedule_seconds %d (want 900), running_things %d (want 40)", got.MinScheduleSeconds, got.RunningThings)
196 }
197 got, err = c.Effective("free", nil, &Limits{MinScheduleSeconds: 60})
198 if err != nil {
199 t.Fatal(err)
200 }
201 if got.MinScheduleSeconds != 60 {
202 t.Fatalf("min_schedule_seconds %d, want 60", got.MinScheduleSeconds)
203 }
204}
205
206// Astra plans-1 finding 5: an accepted catalog with a huge pack increment
207// must never wrap to a negative limit that the ceiling cannot correct.
208func TestEffectiveRefusesOverflow(t *testing.T) {
209 c := exampleCatalog(t)
210 pk := c.Packs["storage"]
211 pk.Adds.StorageBytes = 1 << 62
212 c.Packs["storage"] = pk
213 c.Ceilings.StorageBytes = 100
214 got, err := c.Effective("personal", map[string]int{"storage": 2}, nil)
215 if !errors.Is(err, ErrPackQuantity) {
216 t.Fatalf("err %v (limits %+v), want ErrPackQuantity", err, got)
217 }
218}
219