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
3// claim_invariant_probes_read_test.go is the probe table for every read: each
4// is a valid request for a resource that exists (staged where the plain world
5// has none), accepted only on the exact success status with the resource named
6// in the answer. A read that comes back 404 because the id was made up, 401
7// because the bearer was missing, or a redirect to sign-in is not coverage.
8
9import (
10 "bytes"
11 "context"
12 "encoding/json"
13 "net/http"
14 "testing"
15 "time"
16
17 "github.com/carlosframework/platform/internal/geo"
18 "github.com/carlosframework/platform/internal/geoip"
19 "github.com/carlosframework/platform/internal/release"
20 "github.com/carlosframework/platform/internal/reqrec"
21)
22
23// claimPageRead is a browser GET of a page, the owner signed in.
24func claimPageRead(setup func(*testing.T, *claimCase), path string, want ...string) claimProbe {
25 return claimProbe{setup: setup, do: claimPageReply(path), body: want}
26}
27
28// claimAPIRead is the GET the CLI sends, as the owner's bearer.
29func claimAPIRead(setup func(*testing.T, *claimCase), path string, want ...string) claimProbe {
30 return claimProbe{setup: setup, do: claimGetAPI(path), body: want}
31}
32
33// claimRedirectRead is a GET whose answer is the one named redirect.
34func claimRedirectRead(path string, status int, loc string) claimProbe {
35 return claimProbe{do: claimPageReply(path), status: status, loc: loc}
36}
37
38// ---- the worlds reads need -------------------------------------------------------
39
40func claimWorldInstance(t *testing.T, cx *claimCase) {
41 t.Helper()
42 cx.instanceWorld(t)
43 cx.instance(t, `{"host":"acme.go.tito.io","region":"ie"}`)
44}
45
46func claimWorldFleet(t *testing.T, cx *claimCase) { t.Helper(); cx.fleet(t) }
47
48func claimWorldMove(t *testing.T, cx *claimCase) { t.Helper(); cx.movableInstance(t) }
49
50func claimWorldAlias(t *testing.T, cx *claimCase) {
51 t.Helper()
52 cx.claimedAlias(t, "tickets.example.com")
53}
54
55func claimWorldDoorKey(t *testing.T, cx *claimCase) {
56 t.Helper()
57 cx.mustAPI(t, http.MethodPost, "/api/cli/apps/bab/titogo/door-keys", `{"label":"router"}`, http.StatusCreated)
58}
59
60func claimWorldLedger(t *testing.T, cx *claimCase) {
61 t.Helper()
62 cx.mustAPI(t, http.MethodPost, "/api/cli/apps/bab/titogo/ledgers/carbon/append", `{"payload":{"kg_central":1}}`, http.StatusOK)
63}
64
65func claimWorldSchedule(t *testing.T, cx *claimCase) {
66 t.Helper()
67 cx.mustAPI(t, http.MethodPost, "/api/cli/apps/bab/titogo/schedules", `{"name":"sync","every":"6h","path":"/jobs/sync"}`, http.StatusOK)
68}
69
70func claimWorldTag(t *testing.T, cx *claimCase) {
71 t.Helper()
72 body, err := json.Marshal(release.Tag{Schema: 1, Version: "0.1.0", Release: "v1", TaggedAt: time.Now().UTC(), Actor: claimFxOwner, Changelog: "census"})
73 if err != nil {
74 t.Fatal(err)
75 }
76 if _, err := cx.rel.Put(context.Background(), release.TagKey("bab", "kass", "0.1.0"), body, ""); err != nil {
77 t.Fatalf("stage a tag: %v", err)
78 }
79}
80
81func claimWorldApply(t *testing.T, cx *claimCase) {
82 t.Helper()
83 cx.mustAPI(t, http.MethodPost, "/api/cli/accounts/bab/store/apply", `{"note":"census"}`, http.StatusOK)
84}
85
86func claimWorldGeoip(t *testing.T, cx *claimCase) {
87 t.Helper()
88 claimMustRunAlone(t, "claimWorldGeoip")
89 withFixtureProbes(t)
90 if _, err := cx.srv.geoip.Publish(context.Background(), bytes.NewReader(fixture(t, "GeoIP2-City-Test.mmdb")), geoipPublishOpts{Origin: "manual"}); err != nil {
91 t.Fatalf("stage a published geoip database: %v", err)
92 }
93 if !cx.bucketHas(geoip.PointerKey) {
94 t.Fatal("stage a published geoip database: no pointer")
95 }
96}
97
98func claimInvitePageProbe() claimProbe {
99 return claimProbe{
100 setup: claimInviteWorld,
101 do: func(t *testing.T, cx *claimCase) claimReply {
102 // The invitation is kim's: its page is kim's own signed-in browser.
103 cx.setUser("kim@keymail.dev")
104 c := newBrowser(t)
105 signUp(t, cx.consoleFixture, c, "code-claim-kim-page")
106 return cx.pageReplyAs(t, c, "/invite/<invite>")
107 },
108 body: []string{"<h1>Join "},
109 }
110}
111
112var claimReadProbes = map[string]claimProbe{
113 // ---- methodless ----
114 "route /api/economics/backhaul": claimAPIRead(nil, "/api/economics/backhaul", `"period"`, `"accounts"`),
115 "route /fleet/architecture": claimPageRead(nil, "/fleet/architecture", "<h1>Architecture</h1>"),
116
117 // ---- browser pages ----
118 "route GET /a/{sqid}/apps/new": claimPageRead(nil, "/a/bab/apps/new", `action="/a/bab/apps/new"`),
119 "route GET /a/{sqid}/apps/{app}": claimPageRead(nil, "/a/bab/apps/kass", "kass"),
120 "route GET /a/{sqid}/apps/{app}/account-review": claimPageRead(nil, "/a/bab/apps/kass/account-review", "kass"),
121 "route GET /a/{sqid}/apps/{app}/activity": claimPageRead(nil, "/a/bab/apps/kass/activity", "kass"),
122 "route GET /a/{sqid}/apps/{app}/analytics": claimPageRead(nil, "/a/bab/apps/titogo/analytics", "titogo"),
123 "route GET /a/{sqid}/apps/{app}/availability": claimPageRead(nil, "/a/bab/apps/titogo/availability", "titogo"),
124 "route GET /a/{sqid}/apps/{app}/deployments": claimPageRead(nil, "/a/bab/apps/kass/deployments", "v1"),
125 "route GET /a/{sqid}/apps/{app}/errors": claimPageRead(nil, "/a/bab/apps/titogo/errors", "titogo"),
126 "route GET /a/{sqid}/apps/{app}/instances": claimPageRead(nil, "/a/bab/apps/kass/instances", "kass.carlosapp.dev"),
127 "route GET /a/{sqid}/apps/{app}/instances/{host}": claimPageRead(nil, "/a/bab/apps/kass/instances/kass.carlosapp.dev", "kass.carlosapp.dev"),
128 "route GET /a/{sqid}/apps/{app}/logs": claimPageRead(nil, "/a/bab/apps/titogo/logs", "titogo"),
129 "route GET /a/{sqid}/apps/{app}/logs/data": claimPageRead(nil, "/a/bab/apps/titogo/logs/data", `"lines"`),
130 "route GET /a/{sqid}/apps/{app}/pipeline": claimPageRead(nil, "/a/bab/apps/titogo/pipeline", "titogo"),
131 "route GET /a/{sqid}/apps/{app}/releases": claimPageRead(nil, "/a/bab/apps/kass/releases", "v1"),
132 "route GET /a/{sqid}/apps/{app}/settings": claimPageRead(nil, "/a/bab/apps/titogo/settings", "titogo"),
133 "route GET /a/{sqid}/apps/{app}/settings/{section}": claimPageRead(nil, "/a/bab/apps/titogo/settings/advanced",
134 "/apps/titogo/control-plane/set"),
135 "route GET /a/{sqid}/fleet": claimPageRead(claimWorldFleet, "/a/bab/fleet", "pi"),
136 "route GET /a/{sqid}/fleet/byo": claimPageRead(nil, "/a/bab/fleet/byo", "<h1>Bring your own fleet</h1>"),
137 "route GET /a/{sqid}/fleets/{fleet}": claimPageRead(claimWorldFleet, "/a/bab/fleets/pi", "<h1>pi</h1>"),
138 "route GET /a/{sqid}/settings": claimPageRead(nil, "/a/bab/settings", "<h1>Account settings</h1>"),
139 "route GET /a/{sqid}/team": claimPageRead(nil, "/a/bab/team", "<h1>Team</h1>", claimFxOwner),
140 "route GET /a/{sqid}/trash": claimPageRead(func(t *testing.T, cx *claimCase) { cx.trash(t, "titogo") },
141 "/a/bab/trash", "<h1>Trash</h1>", "titogo"),
142 "route GET /a/{sqid}/{$}": claimPageRead(nil, "/a/bab/", "kass"),
143 "route GET /accounts/new": claimPageRead(nil, "/accounts/new", "<h1>New account</h1>"),
144 "route GET /settings": claimPageRead(nil, "/settings", "<h1>Your settings</h1>"),
145 "route GET /auth/cli": claimPageRead(nil, "/auth/cli", "<h1>Approve a terminal</h1>"),
146 "route GET /invite/{token}": claimInvitePageProbe(),
147 "route GET /fleet": claimPageRead(nil, "/fleet", "<h1>Fleet status</h1>"),
148 "route GET /fleet/economics": claimPageRead(nil, "/fleet/economics", "<h1>Fleet economics</h1>"),
149 "route GET /fleet/economics/accounts": claimPageRead(nil, "/fleet/economics/accounts", "<h1>Fleet economics: accounts</h1>"),
150 "route GET /fleet/metrics": claimPageRead(nil, "/fleet/metrics", "<h1>Fleet metrics</h1>"),
151 "route GET /fleet/store": claimPageRead(claimWorldApply, "/fleet/store", "<h1>Object storage applications</h1>"),
152 "route GET /healthz": claimPageRead(nil, "/healthz", "ok"),
153
154 // ---- redirects ----
155 "route GET /apps": claimRedirectRead("/apps", http.StatusFound, "/a/"),
156 "route GET /apps/{app}": claimRedirectRead("/apps/kass", http.StatusFound, "/a/"),
157 "route GET /auth/cli/tokens": claimRedirectRead("/auth/cli/tokens", http.StatusSeeOther, "/settings"),
158 "route GET /{$}": claimRedirectRead("/", http.StatusFound, "/a/"),
159
160 // ---- the CLI's reads ----
161 "route GET /api/cli/apps": claimAPIRead(nil, "/api/cli/apps", `"sqid":"bab"`, `"kass"`),
162 "route GET /api/cli/apps/{account}/list": claimAPIRead(nil, "/api/cli/apps/bab/list", `"kass"`),
163 "route GET /api/cli/apps/{account}/{app}/analytics": claimAPIRead(nil, "/api/cli/apps/bab/titogo/analytics", `"pv"`),
164 "route GET /api/cli/apps/{account}/{app}/availability": claimAPIRead(nil, "/api/cli/apps/bab/titogo/availability", `"groups"`),
165 "route GET /api/cli/apps/{account}/{app}/channels": claimAPIRead(nil, "/api/cli/apps/bab/kass/channels", `"stable"`),
166 "route GET /api/cli/apps/{account}/{app}/config": claimAPIRead(nil, "/api/cli/apps/bab/titogo/config", `"bundle"`),
167 "route GET /api/cli/apps/{account}/{app}/db/{host}/ltx/{level}": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.dbWorld(t) },
168 "/api/cli/apps/bab/titogo/db/"+claimDBHost+"/ltx/0", `"files"`, `"handle"`, `"min_txid":1`),
169 "route GET /api/cli/apps/{account}/{app}/domains": claimAPIRead(claimWorldAlias, "/api/cli/apps/bab/titogo/domains", "tickets.example.com"),
170 "route GET /api/cli/apps/{account}/{app}/domains/{host}": claimAPIRead(claimWorldAlias, "/api/cli/apps/bab/titogo/domains/tickets.example.com", "tickets.example.com"),
171 "route GET /api/cli/apps/{account}/{app}/door-keys": claimAPIRead(claimWorldDoorKey, "/api/cli/apps/bab/titogo/door-keys", `"label":"router"`),
172 "route GET /api/cli/apps/{account}/{app}/email": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.emailDeclared(t) },
173 "/api/cli/apps/bab/titogo/email", claimMailDomain),
174 "route GET /api/cli/apps/{account}/{app}/email/events": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.emailProvisioned(t) },
175 "/api/cli/apps/bab/titogo/email/events", `"events"`),
176 "route GET /api/cli/apps/{account}/{app}/email/events/push": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.emailPush(t) },
177 "/api/cli/apps/bab/titogo/email/events/push", "/mail/events"),
178 // An unsealed code needs no mesh key to decode, so the probe answers 200
179 // without seeding one; the record is simply not found.
180 "route GET /api/cli/apps/{account}/{app}/edge-refs/{code}": claimAPIRead(nil,
181 "/api/cli/apps/bab/titogo/edge-refs/"+reqrec.NewRef(time.Now(), reqrec.TierProbe, "probe-paths", "box1", nil), `"tier":"probe"`),
182 "route GET /api/cli/apps/{account}/{app}/errors": claimAPIRead(nil, "/api/cli/apps/bab/titogo/errors", `"log_level"`),
183 "route GET /api/cli/apps/{account}/{app}/features": claimAPIRead(nil, "/api/cli/apps/bab/titogo/features", `"features"`),
184 "route GET /api/cli/apps/{account}/{app}/geo": claimAPIRead(func(t *testing.T, cx *claimCase) {
185 db, err := geo.OpenMMDB("../geo/testdata/GeoIP2-City-Test.mmdb")
186 if err != nil {
187 t.Fatal(err)
188 }
189 sw := geo.NewSwappable()
190 sw.Swap(db)
191 t.Cleanup(func() { sw.Swap(nil) })
192 cx.srv.cfg.GeoIPReader = sw
193 }, "/api/cli/apps/bab/titogo/geo?ip=81.2.69.142", `"found":true`, `"country":"GB"`),
194 "route GET /api/cli/apps/{account}/{app}/instances": claimAPIRead(claimWorldInstance, "/api/cli/apps/bab/titogo/instances", "acme.go.tito.io"),
195 "route GET /api/cli/apps/{account}/{app}/instances/{host}": claimAPIRead(claimWorldInstance,
196 "/api/cli/apps/bab/titogo/instances/acme.go.tito.io", "acme.go.tito.io"),
197 "route GET /api/cli/apps/{account}/{app}/instances/{host}/move": claimAPIRead(claimWorldMove,
198 "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/move", `"state":"none"`),
199 "route GET /api/cli/apps/{account}/{app}/instances/{host}/updates": claimAPIRead(claimWorldInstance,
200 "/api/cli/apps/bab/titogo/instances/acme.go.tito.io/updates", `"host":"acme.go.tito.io"`, `"updates":"auto"`),
201 "route GET /api/cli/apps/{account}/{app}/ledgers": claimAPIRead(claimWorldLedger, "/api/cli/apps/bab/titogo/ledgers", `"carbon"`),
202 "route GET /api/cli/apps/{account}/{app}/ledgers/{ledger}/head": claimAPIRead(claimWorldLedger,
203 "/api/cli/apps/bab/titogo/ledgers/carbon/head", `"head_sha256"`),
204 "route GET /api/cli/apps/{account}/{app}/logs": claimAPIRead(nil, "/api/cli/apps/bab/titogo/logs", `"records"`),
205 "route GET /api/cli/apps/{account}/{app}/pending": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.proposal(t) },
206 "/api/cli/apps/bab/titogo/pending", "<pending>"),
207 "route GET /api/cli/apps/{account}/{app}/pipeline": claimAPIRead(nil, "/api/cli/apps/bab/titogo/pipeline", `"pipeline"`),
208 "route GET /api/cli/apps/{account}/{app}/regions": claimAPIRead(nil, "/api/cli/apps/bab/titogo/regions", `"regions"`),
209 "route GET /api/cli/apps/{account}/{app}/relay": claimSerialProbe("relayEnabled stages the region's relay master through t.Setenv: process environment",
210 claimAPIRead(func(t *testing.T, cx *claimCase) { cx.relayEnabled(t) },
211 "/api/cli/apps/bab/titogo/relay", `"app":"titogo"`, `"enabled":true`)),
212 "route GET /api/cli/apps/{account}/{app}/releases": claimAPIRead(nil, "/api/cli/apps/bab/kass/releases", `"version":"v1"`),
213 "route GET /api/cli/apps/{account}/{app}/releases/{version}/authorize": claimAPIRead(nil,
214 "/api/cli/apps/bab/kass/releases/v1/authorize", `"version":"v1"`, `"manifest_digest":"`),
215 "route GET /api/cli/apps/{account}/{app}/retention": claimAPIRead(nil, "/api/cli/apps/bab/titogo/retention", `"keep"`),
216 "route GET /api/cli/apps/{account}/{app}/routes": claimAPIRead(nil, "/api/cli/apps/bab/kass/routes", `"kass.carlosapp.dev"`),
217 "route GET /api/cli/apps/{account}/{app}/schedules": claimAPIRead(claimWorldSchedule, "/api/cli/apps/bab/titogo/schedules", `"sync"`),
218 "route GET /api/cli/apps/{account}/{app}/sidecars": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.sidecar(t) },
219 "/api/cli/apps/bab/titogo/sidecars", `"bot"`),
220 "route GET /api/cli/apps/{account}/{app}/sidecars/{name}": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.sidecar(t) },
221 "/api/cli/apps/bab/titogo/sidecars/bot", `"bot"`),
222 "route GET /api/cli/apps/{account}/{app}/store": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.storeGranted(t) },
223 "/api/cli/apps/bab/titogo/store", `"app":"titogo"`, `"entitlement"`),
224 "route GET /api/cli/apps/{account}/{app}/store/scan": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.scanGranted(t) },
225 "/api/cli/apps/bab/titogo/store/scan", `"app":"titogo"`, `"scanning"`),
226 "route GET /api/cli/apps/{account}/{app}/store/scan/object": claimAPIRead(func(t *testing.T, cx *claimCase) {
227 cx.scanObjectStaged(t)
228 cx.mustAPI(t, http.MethodPost, "/api/cli/apps/bab/titogo/store/scan/sweep", `{}`, http.StatusOK)
229 }, "/api/cli/apps/bab/titogo/store/scan/object?key=claim-fixture.txt", `"state":"clean"`),
230 "route GET /api/cli/apps/{account}/{app}/tags": claimAPIRead(claimWorldTag, "/api/cli/apps/bab/kass/tags", `"version":"0.1.0"`),
231 "route GET /api/cli/apps/{account}/{app}/tags/{version}": claimAPIRead(claimWorldTag, "/api/cli/apps/bab/kass/tags/0.1.0", `"version":"0.1.0"`, `"changelog":"census"`),
232 "route GET /api/cli/apps/{account}/{app}/version-target": claimAPIRead(nil, "/api/cli/apps/bab/titogo/version-target", `"target"`),
233 "route GET /api/cli/fleets/{account}": claimAPIRead(claimWorldFleet, "/api/cli/fleets/bab", `"pi"`, `"pi-fleet-data"`),
234 "route GET /api/cli/services/{account}": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.service(t) },
235 "/api/cli/services/bab", `"svc-a"`),
236 "route GET /api/cli/status/instances": claimAPIRead(nil, "/api/cli/status/instances", `"instances"`),
237 "route GET /api/cli/status/layout": claimAPIRead(nil, "/api/cli/status/layout", `"boxes"`),
238 "route GET /api/cli/store/approvals": claimAPIRead(claimWorldApply, "/api/cli/store/approvals", `"applications"`, claimFxOwner),
239 "route GET /api/cli/accounts/{account}/plan": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.srv.cfg.PlanCatalog = examplePlanCatalog(t) },
240 "/api/cli/accounts/bab/plan", `"plan":"free"`),
241 "route GET /api/cli/system/antivirus": claimAPIRead(nil, "/api/cli/system/antivirus", `"wired":true`),
242 "route GET /api/cli/system/geoip": func() claimProbe {
243 p := claimAPIRead(claimWorldGeoip, "/api/cli/system/geoip", `"pointer"`, "GeoIP2-City")
244 p.serial = "claimWorldGeoip swaps the package variable geoipProbeAddrs (withFixtureProbes)"
245 return p
246 }(),
247 "route GET /api/cli/system/jobs/{id}": {
248 setup: func(t *testing.T, cx *claimCase) { cx.vars["job"] = cx.systemRelease(t, "canary", "66587fa") },
249 do: claimGetAPI("/api/cli/system/jobs/<job>"),
250 body: []string{`"steps"`, `"done":true`},
251 },
252 "route GET /api/cli/system/layout": claimAPIRead(nil, "/api/cli/system/layout", `"mode":"none"`),
253 "route GET /api/cli/system/pointer-digests": claimAPIRead(nil, "/api/cli/system/pointer-digests",
254 `"account":"bab"`, `"app":"kass","channel":"stable"`),
255 "route GET /api/cli/system/status": claimAPIRead(func(t *testing.T, cx *claimCase) { cx.systemRelease(t, "canary", "66587fa") },
256 "/api/cli/system/status", `"pointers"`, `"version":"66587fa"`),
257 "route GET /auth/cli/whoami": claimAPIRead(nil, "/auth/cli/whoami", `"address":"`+claimFxOwner+`"`),
258
259 // ---- POSTs that only read ----
260 "route POST /a/{sqid}/apps/new/region": {
261 do: claimFormReply("/a/bab/apps/new/region", "tz", "Europe/Dublin"),
262 body: []string{`"code"`, `"source"`},
263 },
264 "route POST /api/cli/apps/{account}/{app}/regions/guess": {
265 do: claimAPIReply(http.MethodPost, "/api/cli/apps/bab/titogo/regions/guess", `{"tz":"Europe/Dublin"}`),
266 body: []string{`"guess"`},
267 },
268 "route POST /api/cli/ops/hosts/{host}/resolve/inspect": {
269 setup: func(t *testing.T, cx *claimCase) { cx.hostLossWorld(t) },
270 do: claimAPIReply(http.MethodPost, "/api/cli/ops/hosts/"+claimLossHost+"/resolve/inspect",
271 `{"action":"declare-lost","box":"`+claimLossBox+`"}`),
272 body: []string{`"challenge"`, `"details"`, `"` + claimLossBox + `"`},
273 },
274}
275