carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "errors"
6 "testing"
7
8 "github.com/carlosframework/platform/internal/plans"
9 "github.com/carlosframework/platform/internal/provision"
10)
11
12func capsApp(t *testing.T, fx *consoleFixture, acct *Account, app string) {
13 t.Helper()
14 if _, err := fx.st.CreateAppIn(acct.ID, app, "owner@example.com", planNow); err != nil {
15 t.Fatal(err)
16 }
17}
18
19func capsInstance(t *testing.T, fx *consoleFixture, acct *Account, app, host string) {
20 t.Helper()
21 if _, err := fx.rel.Put(context.Background(), provision.RecordKey(acct.Sqid, app, host), []byte(`{}`), ""); err != nil {
22 t.Fatal(err)
23 }
24}
25
26func capsSidecar(t *testing.T, fx *consoleFixture, acct *Account, app, name string) {
27 t.Helper()
28 if _, err := fx.rel.Put(context.Background(), provision.SidecarRecordKey(acct.Sqid, app, name), []byte(`{}`), ""); err != nil {
29 t.Fatal(err)
30 }
31}
32
33func isCap(err error) bool {
34 var c *planCapError
35 return errors.As(err, &c)
36}
37
38func TestPlanChecksPassWithoutACatalog(t *testing.T) {
39 fx := newConsoleFixture(t)
40 acct := planAccount(t, fx, 501)
41 for i := range 5 {
42 capsApp(t, fx, acct, "app"+string(rune('a'+i)))
43 }
44 capsSidecar(t, fx, acct, "appa", "w1")
45 capsSidecar(t, fx, acct, "appa", "w2")
46 ctx := context.Background()
47 for name, err := range map[string]error{
48 "running things": fx.srv.checkRunningThings(ctx, acct, 1),
49 "sidecars": fx.srv.checkSidecars(ctx, acct, "appa"),
50 "free account": fx.srv.checkNewFreeAccount("owner" + acct.Sqid + "@example.com"),
51 "fleets": fx.srv.checkFleets(acct),
52 "custom domains": fx.srv.checkCustomDomains(acct),
53 } {
54 if err != nil {
55 t.Errorf("%s with no catalog: %v", name, err)
56 }
57 }
58}
59
60func TestLiveAppCountCountsRestoringNotTrashed(t *testing.T) {
61 fx := newConsoleFixture(t)
62 acct := planAccount(t, fx, 501)
63 for _, app := range []string{"live", "gone", "coming-back"} {
64 capsApp(t, fx, acct, app)
65 }
66 if _, err := fx.st.db.Exec(`UPDATE apps SET deleted_at = ? WHERE account_id = ? AND app IN ('gone', 'coming-back')`, planNow.Unix(), acct.ID); err != nil {
67 t.Fatal(err)
68 }
69 if _, err := fx.st.db.Exec(`UPDATE apps SET restoring = 1 WHERE account_id = ? AND app = 'coming-back'`, acct.ID); err != nil {
70 t.Fatal(err)
71 }
72 if n, err := fx.st.LiveAppCount(acct.ID); err != nil || n != 2 {
73 t.Fatalf("LiveAppCount = %d, %v; want 2 (live + restoring)", n, err)
74 }
75}
76
77func TestRunningThingsCountsAppsAndInstances(t *testing.T) {
78 fx := newConsoleFixture(t)
79 acct := planAccount(t, fx, 501)
80 capsApp(t, fx, acct, "shop")
81 capsApp(t, fx, acct, "blog")
82 capsApp(t, fx, acct, "old")
83 capsInstance(t, fx, acct, "shop", "a.shop.example.com")
84 capsInstance(t, fx, acct, "shop", "b.shop.example.com")
85 capsInstance(t, fx, acct, "blog", "c.blog.example.com")
86 capsInstance(t, fx, acct, "old", "d.old.example.com")
87 if err := fx.st.TrashAppRow(acct.ID, "old", planNow); err != nil {
88 t.Fatal(err)
89 }
90 if n, err := fx.srv.runningThings(context.Background(), acct); err != nil || n != 5 {
91 t.Fatalf("runningThings = %d, %v; want 5 (2 apps + 3 instances; the trashed app and its instance do not count)", n, err)
92 }
93}
94
95func TestRunningThingsCapAtAndBelow(t *testing.T) {
96 fx := newConsoleFixture(t)
97 fx.srv.cfg.PlanCatalog = examplePlanCatalog(t)
98 acct := planAccount(t, fx, 501)
99 capsApp(t, fx, acct, "one")
100 capsApp(t, fx, acct, "two")
101 if err := fx.srv.checkRunningThings(context.Background(), acct, 1); err != nil {
102 t.Fatalf("at 2 of 3: %v", err)
103 }
104 capsApp(t, fx, acct, "three")
105 if err := fx.srv.checkRunningThings(context.Background(), acct, 1); !isCap(err) {
106 t.Fatalf("at 3 of 3: %v, want a plan cap", err)
107 }
108}
109
110func TestRunningThingsCapUsesEffectiveLimits(t *testing.T) {
111 fx := newConsoleFixture(t)
112 fx.srv.cfg.PlanCatalog = examplePlanCatalog(t)
113 acct := planAccount(t, fx, 501)
114 if _, err := fx.st.SetAccountPlan(acct.ID, "free", nil, PlanSourceSponsored, &plans.Limits{RunningThings: 300}, "existing account", "ops@example.com", planNow); err != nil {
115 t.Fatal(err)
116 }
117 for i := range 4 {
118 capsApp(t, fx, acct, "app"+string(rune('a'+i)))
119 }
120 if err := fx.srv.checkRunningThings(context.Background(), acct, 1); err != nil {
121 t.Fatalf("sponsored 300, running 4: %v", err)
122 }
123}
124
125func TestSidecarCap(t *testing.T) {
126 fx := newConsoleFixture(t)
127 fx.srv.cfg.PlanCatalog = examplePlanCatalog(t)
128 acct := planAccount(t, fx, 501)
129 capsApp(t, fx, acct, "shop")
130 if err := fx.srv.checkSidecars(context.Background(), acct, "shop"); err != nil {
131 t.Fatalf("free, no sidecars: %v", err)
132 }
133 capsSidecar(t, fx, acct, "shop", "w1")
134 if err := fx.srv.checkSidecars(context.Background(), acct, "shop"); !isCap(err) {
135 t.Fatalf("free, one sidecar: %v, want a plan cap", err)
136 }
137 if _, err := fx.st.SetAccountPlan(acct.ID, "personal", nil, PlanSourceOperator, nil, "r", "ops@example.com", planNow); err != nil {
138 t.Fatal(err)
139 }
140 for _, n := range []string{"w2", "w3", "w4"} {
141 capsSidecar(t, fx, acct, "shop", n)
142 }
143 if err := fx.srv.checkSidecars(context.Background(), acct, "shop"); err != nil {
144 t.Fatalf("personal, four sidecars: %v", err)
145 }
146 capsSidecar(t, fx, acct, "shop", "w5")
147 if err := fx.srv.checkSidecars(context.Background(), acct, "shop"); !isCap(err) {
148 t.Fatalf("personal, five sidecars: %v, want a plan cap", err)
149 }
150}
151
152func TestNewFreeAccountCap(t *testing.T) {
153 fx := newConsoleFixture(t)
154 fx.srv.cfg.PlanCatalog = examplePlanCatalog(t)
155 acct := planAccount(t, fx, 501)
156 owner := "owner" + acct.Sqid + "@example.com"
157 if err := fx.srv.checkNewFreeAccount(owner); !isCap(err) {
158 t.Fatalf("owner of one free account: %v, want a plan cap", err)
159 }
160 if _, err := fx.st.SetAccountPlan(acct.ID, "personal", nil, PlanSourceOperator, nil, "r", "ops@example.com", planNow); err != nil {
161 t.Fatal(err)
162 }
163 if err := fx.srv.checkNewFreeAccount(owner); err != nil {
164 t.Fatalf("owner of one paid account: %v", err)
165 }
166 member := "member@example.com"
167 other := planAccount(t, fx, 502)
168 if err := fx.st.AddMember(other.ID, member, RoleMember, planNow); err != nil {
169 t.Fatal(err)
170 }
171 if err := fx.srv.checkNewFreeAccount(member); err != nil {
172 t.Fatalf("member, not owner, of a free account: %v", err)
173 }
174}
175
176func TestFleetsAndCustomDomainsFollowThePlan(t *testing.T) {
177 fx := newConsoleFixture(t)
178 fx.srv.cfg.PlanCatalog = examplePlanCatalog(t)
179 acct := planAccount(t, fx, 501)
180 if !isCap(fx.srv.checkFleets(acct)) || !isCap(fx.srv.checkCustomDomains(acct)) {
181 t.Fatal("free plan allowed fleets or custom domains")
182 }
183 if _, err := fx.st.SetAccountPlan(acct.ID, "personal", nil, PlanSourceOperator, nil, "r", "ops@example.com", planNow); err != nil {
184 t.Fatal(err)
185 }
186 if fx.srv.checkFleets(acct) != nil || fx.srv.checkCustomDomains(acct) != nil {
187 t.Fatal("personal plan refused fleets or custom domains")
188 }
189}
190
191// Astra plans-3a finding 1: restoring an app brings back its retained
192// instance records too, so the check must count the whole footprint.
193func TestRestoreCountsTheAppsInstances(t *testing.T) {
194 fx := newConsoleFixture(t)
195 fx.srv.cfg.PlanCatalog = examplePlanCatalog(t)
196 acct := planAccount(t, fx, 501)
197 capsApp(t, fx, acct, "live")
198 capsApp(t, fx, acct, "old")
199 capsInstance(t, fx, acct, "old", "a.old.example.com")
200 capsInstance(t, fx, acct, "old", "b.old.example.com")
201 if err := fx.st.TrashAppRow(acct.ID, "old", planNow); err != nil {
202 t.Fatal(err)
203 }
204 // One running thing of three: restoring "old" would make four.
205 if err := fx.srv.checkRestore(context.Background(), acct, "old"); !isCap(err) {
206 t.Fatalf("restore bringing back an app and two instances at 1 of 3: %v, want a plan cap", err)
207 }
208}
209
210// Astra plans-3a finding 3: adopting an app whose objects already exist in
211// the bucket brings its instance records with it.
212func TestCreateCountsAnAdoptedAppsInstances(t *testing.T) {
213 fx := newConsoleFixture(t)
214 fx.srv.cfg.PlanCatalog = examplePlanCatalog(t)
215 acct := planAccount(t, fx, 501)
216 capsApp(t, fx, acct, "live")
217 capsInstance(t, fx, acct, "legacy", "a.legacy.example.com")
218 capsInstance(t, fx, acct, "legacy", "b.legacy.example.com")
219 if err := fx.srv.checkCreateApp(context.Background(), acct, "legacy"); !isCap(err) {
220 t.Fatalf("adopting an app with two instances at 1 of 3: %v, want a plan cap", err)
221 }
222 if err := fx.srv.checkCreateApp(context.Background(), acct, "fresh"); err != nil {
223 t.Fatalf("a new app at 1 of 3: %v", err)
224 }
225}
226