package console // claim_invariant_probes_nonapp_test.go is the probe table for every entry // that mutates deployment, account or person state (never app state): each has // a valid request, the world it needs, and an effect witness read from the // world. See claim_invariant_probes_test.go for what a probe has to prove. import ( "context" "encoding/json" "errors" "github.com/carlosframework/platform/internal/hostactors" "net/http" "net/url" "strings" "testing" "time" "github.com/carlosframework/platform/internal/economics" "github.com/carlosframework/platform/internal/geoip" "github.com/carlosframework/platform/internal/infraview" "github.com/carlosframework/platform/internal/plans" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/seeds" "github.com/carlosframework/platform/internal/telemetry" ) // claimProbes is every read, nonApp and worker probe, merged. A key in two // tables is a mistake the first run reports. var claimProbes = func() map[string]claimProbe { out := map[string]claimProbe{} for _, table := range []map[string]claimProbe{claimNonAppProbes, claimReadProbes, claimWorkerProbes} { for key, p := range table { if _, dup := out[key]; dup { panic("duplicate claim probe: " + key) } out[key] = p } } return out }() const claimBillMonth = "2026-08" // claimInviteWorld is a pending invitation to kim@keymail.dev into bab, its // token kept in vars. func claimInviteWorld(t *testing.T, cx *claimCase) { t.Helper() tok, err := cx.st.CreateInvite(OpsAccountID, "kim@keymail.dev", claimFxOwner, time.Now()) if err != nil { t.Fatal(err) } cx.vars["invite"] = tok } // claimMemberWorld is kim@keymail.dev as a member of bab. func claimMemberWorld(t *testing.T, cx *claimCase) { t.Helper() if err := cx.st.AddMember(OpsAccountID, "kim@keymail.dev", RoleMember, time.Now()); err != nil { t.Fatal(err) } } // claimInventorySeam gives the infra pass an inventory to take, the seam // production fills with the AWS provider (resolveInventory). func claimInventorySeam(t *testing.T, cx *claimCase) { t.Helper() cx.srv.inventory = func(context.Context) (infraview.Inventory, error) { return infraview.Inventory{Provider: "aws", Account: "123456789012", TakenAt: time.Now().UTC(), Resources: []infraview.Resource{}}, nil } } // claimDeviceLogin is one device-code login waiting for a human, its codes in // vars. func claimDeviceLogin(t *testing.T, cx *claimCase) { t.Helper() userCode, deviceCode, err := cx.st.NewCLIAuth("census terminal", "192.0.2.1", time.Now()) if err != nil { t.Fatal(err) } cx.vars["usercode"], cx.vars["devicecode"] = userCode, deviceCode } func claimSigninProbe() claimProbe { var c *http.Client return claimProbe{ do: func(t *testing.T, cx *claimCase) claimReply { c = newBrowser(t) return cx.pageReplyAs(t, c, "/auth/signin") }, status: http.StatusSeeOther, loc: claimKeymailLoc + "/oauth/authorize", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if cx.cookieOf(c, authCookie) == "" { return "the sign-in flow's state cookie was not set" } return "" }, } } func claimCallbackProbe() claimProbe { var c *http.Client return claimProbe{ do: func(t *testing.T, cx *claimCase) claimReply { c = newBrowser(t) return cx.signInReply(t, c, "code-claim-callback-probe") }, status: http.StatusSeeOther, loc: "/a/", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if !cx.signedInAs(c, claimFxOwner) { return "no live session for " + claimFxOwner + " came out of the callback" } return "" }, } } func claimConfirmProbe() claimProbe { // Retired (spec 11): the old Apps-inbox link only explains itself now, // even for a token the old table still holds. var c *http.Client return claimProbe{ setup: func(t *testing.T, cx *claimCase) { raw, err := cx.st.CreateConfirmation(claimFxOwner, time.Now()) if err != nil { t.Fatal(err) } cx.vars["confirm"] = raw }, do: func(t *testing.T, cx *claimCase) claimReply { c = newBrowser(t) return cx.pageReplyAs(t, c, "/auth/confirm?token=") }, body: []string{esc(copyText("link.expired"))}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if cx.signedInAs(c, claimFxOwner) { return "the retired confirmation link opened a session" } return "" }, } } // claimEmailChallengeWorld is a sign-in challenge for the case's owner in a // browser's sign-in flow, the flow token, link token and code kept in vars. func claimEmailChallengeWorld(t *testing.T, cx *claimCase) { t.Helper() flow, err := randomToken() if err != nil { t.Fatal(err) } link, code, err := cx.st.CreateEmailChallenge(EmailChallenge{Purpose: ChallengeSignIn, Address: claimFxOwner, FlowHash: hashToken(flow)}, time.Now()) if err != nil { t.Fatal(err) } cx.vars["flow"], cx.vars["link"], cx.vars["code"] = flow, link, code } // claimBrowserWithCookie is a fresh browser already holding one console // cookie. func claimBrowserWithCookie(t *testing.T, cx *claimCase, name, value string) *http.Client { t.Helper() c := newBrowser(t) u, err := url.Parse(cx.ts.URL) if err != nil { t.Fatal(err) } c.Jar.SetCookies(u, []*http.Cookie{{Name: name, Value: value, Path: "/"}}) return c } func claimEmailLinkPageProbe() claimProbe { var c *http.Client return claimProbe{ setup: claimEmailChallengeWorld, do: func(t *testing.T, cx *claimCase) claimReply { c = newBrowser(t) return cx.pageReplyAs(t, c, "/auth/email/link?t=") }, body: []string{esc(copyText("link.title", "address", claimFxOwner)), `name="t" value=""`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { nonce := cx.cookieOf(c, linkConfirmCookie) if nonce == "" || !strings.Contains(r.Body, `name="nonce" value="`+nonce+`"`) { return "the confirm page set no nonce cookie matching its form" } if _, ok := cx.st.PeekEmailLink(cx.vars["link"], time.Now()); !ok { return "the GET spent the link" } return "" }, } } func claimEmailCodeProbe() claimProbe { var c *http.Client return claimProbe{ setup: claimEmailChallengeWorld, do: func(t *testing.T, cx *claimCase) claimReply { c = claimBrowserWithCookie(t, cx, flowCookie, cx.vars["flow"]) status, loc, body := postForm(t, c, cx.ts.URL+"/auth/email/code", url.Values{"code": {cx.vars["code"]}}) return cx.browserReply(c, status, loc, body) }, status: http.StatusSeeOther, loc: "/a/", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if !cx.signedInAs(c, claimFxOwner) { return "the emailed code opened no session" } if _, ok := cx.st.LiveEmailChallenge(ChallengeSignIn, hashToken(cx.vars["flow"]), time.Now()); ok { return "the challenge is still standing after its code was used" } return "" }, } } func claimContinueProbe() claimProbe { // An address Keymail's lookup does not know: the email door, which // records a challenge and answers with the check page. const addr = "probe@example.com" return claimProbe{ setup: func(t *testing.T, cx *claimCase) { cx.srv.consoleMail = &fakeConsoleMail{ready: true} }, do: func(t *testing.T, cx *claimCase) claimReply { status, loc, body := postForm(t, newBrowser(t), cx.ts.URL+"/auth/continue", url.Values{"address": {addr}}) return claimReply{Status: status, Loc: loc, Body: body} }, body: []string{esc(copyText("check.sent", "address", addr))}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if cx.sqlCount(t, `SELECT COUNT(*) FROM email_challenges WHERE address = ?`, addr) == 0 { return "no sign-in challenge was recorded for " + addr } return "" }, } } func claimAcceptInviteProbe() claimProbe { return claimProbe{ setup: claimInviteWorld, do: func(t *testing.T, cx *claimCase) claimReply { // The invitation is kim's: the request is kim's own signed-in // browser accepting it. cx.setUser("kim@keymail.dev") c := newBrowser(t) signUp(t, cx.consoleFixture, c, "code-claim-kim-invite") return cx.formReplyAs(t, c, sessionToken(t, cx.consoleFixture, c), "/invite/") }, status: http.StatusSeeOther, loc: "/a/bab/", effect: func(t *testing.T, cx *claimCase, r claimReply) string { role, err := cx.st.Role(OpsAccountID, "kim@keymail.dev") if err != nil { return err.Error() } if role != RoleMember { return "kim@keymail.dev holds role " + `"` + role + `"` + " in bab, want a member" } return "" }, } } var claimNonAppProbes = map[string]claimProbe{ // ---- operator economics ---- "route /api/economics/bill": { do: claimAPIReply(http.MethodPost, "/api/economics/bill", `{"month":"`+claimBillMonth+`","total_usd":123.45,"services":{"AmazonEC2":55}}`), body: []string{`"month":"` + claimBillMonth + `"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { var b economics.Bill if !cx.bucketJSON(economics.BillKey(claimBillMonth), &b) { return "no bill object for " + claimBillMonth } if b.TotalUSD != 123.45 || b.Source != economics.SourceManual || b.Services["AmazonEC2"] != 55 { return "the stored bill is not the one sent" } return "" }, }, "route /fleet/economics/bill": { do: claimFormReply("/fleet/economics/bill", "month", claimBillMonth, "total", "123.45", "lines", "AmazonEC2=55"), status: http.StatusSeeOther, loc: "/fleet/economics", effect: func(t *testing.T, cx *claimCase, r claimReply) string { var b economics.Bill if !cx.bucketJSON(economics.BillKey(claimBillMonth), &b) || b.TotalUSD != 123.45 || b.Services["AmazonEC2"] != 55 { return "no manual bill for " + claimBillMonth + " with the submitted lines" } return "" }, }, "route /fleet/economics/scenarios": { do: func(t *testing.T, cx *claimCase) claimReply { raw, err := json.Marshal(economics.DefaultScenario()) if err != nil { t.Fatal(err) } return cx.formReply(t, "/fleet/economics/scenarios", "name", "census", "scenario", string(raw), "activate", "1") }, status: http.StatusSeeOther, loc: "/fleet/economics", effect: func(t *testing.T, cx *claimCase, r claimReply) string { var sc economics.Scenario if !cx.bucketJSON(economics.ScenarioKey("census"), &sc) || sc.Name != "census" { return "no scenario named census was saved" } var active economics.Active if !cx.bucketJSON(economics.ActiveKey, &active) || active.Name != "census" { return "the saved scenario was not made active" } return "" }, }, "route /fleet/economics/tier": { do: claimFormReply("/fleet/economics/tier", "account", "bab", "tier", "pool"), status: http.StatusSeeOther, loc: "/fleet/economics/accounts", effect: func(t *testing.T, cx *claimCase, r claimReply) string { tiers, err := cx.st.AccountTiers() if err != nil { return err.Error() } if tiers["bab"] != "pool" { return `bab's tier is "` + tiers["bab"] + `", want pool` } return "" }, }, "route /fleet/architecture/refresh": { setup: claimInventorySeam, do: claimFormReply("/fleet/architecture/refresh"), status: http.StatusSeeOther, loc: "/fleet/architecture", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if r.Flash != "ok" { return "the refresh did not say it refreshed: " + r.Note } var inv infraview.Inventory if !cx.bucketJSON(infraview.InventoryKey, &inv) || inv.Provider != "aws" { return "no inventory snapshot was stored" } return "" }, }, // ---- sign-in and session ---- "route GET /auth/signin": claimSigninProbe(), "route GET /auth/callback": claimCallbackProbe(), "route GET /auth/confirm": claimConfirmProbe(), "route POST /auth/continue": claimContinueProbe(), "route GET /auth/email/link": claimEmailLinkPageProbe(), "route POST /auth/email/code": claimEmailCodeProbe(), "route POST /auth/signout": { do: claimFormReply("/auth/signout"), status: http.StatusSeeOther, loc: "/", effect: func(t *testing.T, cx *claimCase, r claimReply) string { // "/" is also where a signed-out request is bounced, so the proof is // the session itself: it was live and is gone. if _, ok := cx.st.SessionAddress(cx.csrf, time.Now()); ok { return "the session is still live after sign-out" } return "" }, }, "route POST /accounts/new": { do: claimFormReply("/accounts/new", "name", "census-acct"), status: http.StatusSeeOther, loc: "/a/", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if !cx.accountNamed(t, claimFxOwner, "census-acct") { return "the owner has no account named census-acct" } return "" }, }, "route POST /invite/{token}": claimAcceptInviteProbe(), // ---- the CLI's own login ---- "route POST /auth/cli/start": { do: func(t *testing.T, cx *claimCase) claimReply { return cx.apiReplyAs(t, "", http.MethodPost, "/auth/cli/start", `{"label":"census terminal"}`) }, body: []string{`"user_code"`, `"device_code"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { var out struct { UserCode string `json:"user_code"` } if json.Unmarshal([]byte(r.Body), &out) != nil || out.UserCode == "" { return "the answer carries no user code" } if req, err := cx.st.CLIAuthByUserCode(out.UserCode, time.Now()); err != nil || req == nil { return "no login is waiting on the code it answered" } return "" }, }, "route POST /auth/cli/approve": { setup: claimDeviceLogin, do: claimFormReply("/auth/cli/approve", "code", "", "action", "approve"), body: []string{"

Approved

"}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { status, token, addr, err := cx.st.PollCLIAuth(cx.vars["devicecode"], time.Now()) if err != nil { return err.Error() } if status != CLIAuthApproved || token == "" || addr != claimFxOwner { return "the login was not approved for " + claimFxOwner + " (" + status + ")" } return "" }, }, "route POST /auth/cli/poll": { setup: func(t *testing.T, cx *claimCase) { claimDeviceLogin(t, cx) if err := cx.st.ApproveCLIAuth(cx.vars["usercode"], claimFxOwner, time.Now()); err != nil { t.Fatal(err) } }, do: func(t *testing.T, cx *claimCase) claimReply { return cx.apiReplyAs(t, "", http.MethodPost, "/auth/cli/poll", `{"device_code":""}`) }, body: []string{`"status":"approved"`, `"token"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { var out struct { Token string `json:"token"` } if json.Unmarshal([]byte(r.Body), &out) != nil || out.Token == "" { return "the answer carries no token" } if addr, ok := cx.st.CLITokenAddress(out.Token, time.Now()); !ok || addr != claimFxOwner { return "the token the poll handed out does not act as " + claimFxOwner } return "" }, }, "route POST /auth/cli/revoke": { setup: func(t *testing.T, cx *claimCase) { // A second terminal of the owner's: the request revokes ITSELF. cx.vars["terminal"] = mintUserToken(t, cx.st, claimFxOwner, time.Now()) }, do: func(t *testing.T, cx *claimCase) claimReply { return cx.apiReplyAs(t, cx.vars["terminal"], http.MethodPost, "/auth/cli/revoke", `{}`) }, body: []string{`"revoked":true`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if _, ok := cx.st.CLITokenAddress(cx.vars["terminal"], time.Now()); ok { return "the terminal's token still acts after its own revoke" } if _, ok := cx.st.CLITokenAddress(cx.bearer, time.Now()); !ok { return "the revoke took the owner's other terminal with it" } return "" }, }, "route POST /auth/cli/accounts": { do: claimAPIReply(http.MethodPost, "/auth/cli/accounts", `{"name":"census-cli-acct"}`), body: []string{`"sqid"`, `"census-cli-acct"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if !cx.accountNamed(t, claimFxOwner, "census-cli-acct") { return "the owner has no account named census-cli-acct" } return "" }, }, // ---- account settings and membership ---- "route POST /a/{sqid}/analytics/setting": { do: claimFormReply("/a/bab/analytics/setting", "count", "edge", "door", "on"), status: http.StatusSeeOther, loc: "/a/bab/settings", effect: func(t *testing.T, cx *claimCase, r claimReply) string { st, _, err := telemetry.Read(context.Background(), cx.rel, telemetry.AccountKey("bab")) if err != nil { return err.Error() } if st.Analytics == nil || st.Analytics.Count != "edge" || st.Analytics.Door != "on" { return "the account's analytics default was not set to edge and on" } return "" }, }, "route POST /a/{sqid}/errors/level": { do: claimFormReply("/a/bab/errors/level", "level", "warn"), status: http.StatusSeeOther, loc: "/a/bab/settings", effect: func(t *testing.T, cx *claimCase, r claimReply) string { st, _, err := telemetry.Read(context.Background(), cx.rel, telemetry.AccountKey("bab")) if err != nil { return err.Error() } if st.Errors == nil || st.Errors.LogLevel != "warn" { return "the account's error log level was not set to warn" } return "" }, }, "route POST /a/{sqid}/invite": { do: claimFormReply("/a/bab/invite", "address", "kim@keymail.dev"), status: http.StatusSeeOther, loc: "/a/bab/team", effect: func(t *testing.T, cx *claimCase, r claimReply) string { pending, err := cx.st.PendingInvites(OpsAccountID) if err != nil { return err.Error() } for _, inv := range pending { if inv.Address == "kim@keymail.dev" { return "" } } return "no invitation to kim@keymail.dev is pending" }, }, "route POST /a/{sqid}/invite/revoke": { setup: claimInviteWorld, do: claimFormReply("/a/bab/invite/revoke", "token", ""), status: http.StatusSeeOther, loc: "/a/bab/team", effect: func(t *testing.T, cx *claimCase, r claimReply) string { pending, err := cx.st.PendingInvites(OpsAccountID) if err != nil { return err.Error() } for _, inv := range pending { if inv.Token == cx.vars["invite"] { return "the invitation is still pending" } } if r.Flash != "ok" { return "the revoke did not say it revoked: " + r.Note } return "" }, }, "route POST /a/{sqid}/members/remove": { setup: claimMemberWorld, do: claimFormReply("/a/bab/members/remove", "address", "kim@keymail.dev"), status: http.StatusSeeOther, loc: "/a/bab/team", effect: func(t *testing.T, cx *claimCase, r claimReply) string { role, err := cx.st.Role(OpsAccountID, "kim@keymail.dev") if err != nil { return err.Error() } if role != "" { return `kim@keymail.dev still holds role "` + role + `"` } return "" }, }, "route POST /a/{sqid}/members/transfer": { setup: claimMemberWorld, do: claimFormReply("/a/bab/members/transfer", "address", "kim@keymail.dev"), status: http.StatusSeeOther, loc: "/a/bab/team", effect: func(t *testing.T, cx *claimCase, r claimReply) string { kim, err := cx.st.Role(OpsAccountID, "kim@keymail.dev") if err != nil { return err.Error() } me, err := cx.st.Role(OpsAccountID, claimFxOwner) if err != nil { return err.Error() } if kim != RoleOwner || me != RoleMember { return `ownership did not move: kim is "` + kim + `", the previous owner "` + me + `"` } return "" }, }, "route POST /a/{sqid}/delete": { setup: func(t *testing.T, cx *claimCase) { cx.vars["doomed"] = cx.mintedAccount(t, "census-doomed") }, do: claimFormReply("/a//delete", "confirm", "census-doomed"), status: http.StatusSeeOther, loc: "/a/", effect: func(t *testing.T, cx *claimCase, r claimReply) string { acct, err := cx.st.AccountBySqid(cx.vars["doomed"]) if err != nil { return err.Error() } if acct != nil { return "the account is still live" } return "" }, }, "route POST /a/{sqid}/store/apply": { do: claimFormReply("/a/bab/store/apply", "note", "census"), status: http.StatusSeeOther, loc: "/a/bab/settings", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if e := cx.entitlement(t); e == nil || e.Applied == nil || e.Applied.By != claimFxOwner { return "no storage application by the owner was recorded" } return "" }, }, // ---- fleets (registry) ---- "route POST /a/{sqid}/fleets/{fleet}/detach": { setup: func(t *testing.T, cx *claimCase) { cx.fleetBoxWorld(t) }, do: claimFormReply("/a/bab/fleets/pi/detach", "label", "pi-1"), status: http.StatusSeeOther, loc: "/a/bab/fleets/pi", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if b, ok := cx.fleetBox(t, "pi", "pi-1"); !ok || b.Status != "detached" { return "the box is not detached in the registry" } return "" }, }, "route POST /a/{sqid}/fleets/{fleet}/rotate": { setup: func(t *testing.T, cx *claimCase) { cx.fleetBoxWorld(t) }, do: claimFormReply("/a/bab/fleets/pi/rotate", "label", "pi-1"), body: []string{"pi-1"}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if b, ok := cx.fleetBox(t, "pi", "pi-1"); !ok || b.TokenHash == "" || b.TokenHash == cx.vars["boxhash"] { return "the box's token hash did not change" } return "" }, }, "route POST /a/{sqid}/fleets/{fleet}/tags": { setup: func(t *testing.T, cx *claimCase) { cx.fleetBoxWorld(t) }, do: claimFormReply("/a/bab/fleets/pi/tags", "label", "pi-1", "tags", "office,free"), status: http.StatusSeeOther, loc: "/a/bab/fleets/pi", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if b, ok := cx.fleetBox(t, "pi", "pi-1"); !ok || b.Tags != "office,free" { return "the box is not tagged office,free" } return "" }, }, "route POST /api/cli/fleets/{account}/new": { do: claimAPIReply(http.MethodPost, "/api/cli/fleets/bab/new", `{"name":"pi","bucket":"pi-fleet-data"}`), body: []string{`"fleet"`, `"pi-fleet-data"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if !cx.fleetNamed(t, "pi") { return "the registry has no fleet pi" } return "" }, }, "route POST /api/cli/fleets/{account}/{fleet}/boxes": { setup: func(t *testing.T, cx *claimCase) { cx.fleet(t) }, do: claimAPIReply(http.MethodPost, "/api/cli/fleets/bab/pi/boxes", `{"label":"pi-1"}`), body: []string{`"token"`, `"pi-1"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if b, ok := cx.fleetBox(t, "pi", "pi-1"); !ok || b.Status != "active" { return "the registry has no active box pi-1 in fleet pi" } return "" }, }, "route POST /api/cli/fleets/{account}/{fleet}/boxes/{label}/detach": { setup: func(t *testing.T, cx *claimCase) { cx.fleetBoxWorld(t) }, do: claimAPIReply(http.MethodPost, "/api/cli/fleets/bab/pi/boxes/pi-1/detach", `{}`), effect: func(t *testing.T, cx *claimCase, r claimReply) string { if b, ok := cx.fleetBox(t, "pi", "pi-1"); !ok || b.Status != "detached" { return "the box is not detached in the registry" } return "" }, }, "route POST /api/cli/fleets/{account}/{fleet}/boxes/{label}/rotate": { setup: func(t *testing.T, cx *claimCase) { cx.fleetBoxWorld(t) }, do: claimAPIReply(http.MethodPost, "/api/cli/fleets/bab/pi/boxes/pi-1/rotate", `{}`), body: []string{`"token"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if b, ok := cx.fleetBox(t, "pi", "pi-1"); !ok || b.TokenHash == "" || b.TokenHash == cx.vars["boxhash"] { return "the box's token hash did not change" } return "" }, }, "route POST /api/cli/fleets/{account}/{fleet}/boxes/{label}/tags": { setup: func(t *testing.T, cx *claimCase) { cx.fleetBoxWorld(t) }, do: claimAPIReply(http.MethodPost, "/api/cli/fleets/bab/pi/boxes/pi-1/tags", `{"tags":"office,free"}`), effect: func(t *testing.T, cx *claimCase, r claimReply) string { if b, ok := cx.fleetBox(t, "pi", "pi-1"); !ok || b.Tags != "office,free" { return "the box is not tagged office,free" } return "" }, }, // ---- object-storage entitlements ---- "route POST /api/cli/accounts/{account}/store/apply": { do: claimAPIReply(http.MethodPost, "/api/cli/accounts/bab/store/apply", `{"note":"census"}`), body: []string{`"entitlement"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if e := cx.entitlement(t); e == nil || e.Applied == nil || e.Applied.Note != "census" { return "no storage application with the note was recorded" } return "" }, }, "route POST /api/cli/accounts/{account}/plan": { setup: func(t *testing.T, cx *claimCase) { cx.srv.cfg.PlanCatalog = examplePlanCatalog(t) }, do: claimAPIReply(http.MethodPost, "/api/cli/accounts/bab/plan", `{"plan":"personal","reason":"census"}`), body: []string{`"plan":"personal"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { var rec plans.Record if !cx.bucketJSON(plans.LimitsKey("bab"), &rec) || rec.Plan != "personal" { return "the personal plan was not published for bab" } return "" }, }, "route POST /api/cli/accounts/{account}/store/approve": { setup: func(t *testing.T, cx *claimCase) { cx.mustAPI(t, http.MethodPost, "/api/cli/accounts/bab/store/apply", `{"note":"census"}`, http.StatusOK) }, do: claimAPIReply(http.MethodPost, "/api/cli/accounts/bab/store/approve", `{}`), body: []string{`"entitlement"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if e := cx.entitlement(t); e == nil || !e.Approved() { return "the storage entitlement is not approved" } return "" }, }, "route POST /api/cli/accounts/{account}/store/decline": { setup: func(t *testing.T, cx *claimCase) { cx.mustAPI(t, http.MethodPost, "/api/cli/accounts/bab/store/apply", `{"note":"census"}`, http.StatusOK) }, do: claimAPIReply(http.MethodPost, "/api/cli/accounts/bab/store/decline", `{"reason":"census decline"}`), body: []string{`"entitlement"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if e := cx.entitlement(t); e == nil || e.Decision == nil || e.Decision.Approved || e.Decision.Reason != "census decline" { return "the storage entitlement is not declined with the reason" } return "" }, }, "route POST /fleet/store/{account}/{decision}": { setup: func(t *testing.T, cx *claimCase) { cx.mustAPI(t, http.MethodPost, "/api/cli/accounts/bab/store/apply", `{"note":"census"}`, http.StatusOK) }, do: claimFormReply("/fleet/store/bab/decline", "reason", "census decline"), status: http.StatusSeeOther, loc: "/fleet/store", effect: func(t *testing.T, cx *claimCase, r claimReply) string { if e := cx.entitlement(t); e == nil || e.Decision == nil || e.Decision.Approved { return "the storage entitlement is not declined" } return "" }, }, // ---- the platform system doors ---- "route POST /api/cli/system/antivirus": { do: claimAPIReply(http.MethodPost, "/api/cli/system/antivirus", `{"enabled":true}`), body: []string{`"enabled":true`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { av, _, err := release.GetAntivirus(context.Background(), cx.rel) if err != nil || av == nil || !av.Enabled || av.By != claimFxOwner { return "the deployment's scanning switch is not on, set by the owner" } return "" }, }, "route POST /api/cli/system/layout": { do: claimAPIReply(http.MethodPost, "/api/cli/system/layout", `{"mode":"flat"}`), body: []string{`"mode":"flat"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { log, found, err := release.LoadLayoutIntents(context.Background(), cx.rel) if err != nil || !found || len(log.Requests) == 0 || log.Requests[len(log.Requests)-1].Mode != release.LayoutModeFlat { return "no flat layout intent was recorded" } return "" }, }, "route POST /api/cli/system/pointer-digests": { do: claimAPIReply(http.MethodPost, "/api/cli/system/pointer-digests", `{"on":true,"accounts":["bab"]}`), body: []string{`"account":"bab","on":true`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { m, err := release.ReadDigestMarker(context.Background(), cx.rel, cx.pub, "bab") if err != nil || m == nil || m.By != claimFxOwner { return "bab's pointer digest marker is not on, written by the owner" } return "" }, }, "route POST /api/cli/system/seeds-floor": { do: claimAPIReply(http.MethodPost, "/api/cli/system/seeds-floor", `{"min_release":"v0.27.0"}`), body: []string{`"min_release":"v0.27.0"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { f, err := seeds.ReadFloor(context.Background(), cx.rel) if err != nil || f == nil || f.MinRelease != "v0.27.0" { return "no seeds floor at v0.27.0 was published" } return "" }, }, "route PUT /api/cli/system/upload": { do: func(t *testing.T, cx *claimCase) claimReply { return cx.systemUploadReply(t, "66587fa") }, body: []string{`"upload"`, `"size"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { handle := claimReplyField(t, r.Body, "upload") cx.srv.systemMu.Lock() up, ok := cx.srv.systemUploads[handle] cx.srv.systemMu.Unlock() if !ok || up.version != "66587fa" || up.platform != "linux-arm64" { return "the console holds no parked build under the handle it answered" } return "" }, }, "route POST /api/cli/system/update": { setup: func(t *testing.T, cx *claimCase) { cx.stageSystemCandidate(t, "66587fa") up := cx.systemUploadReply(t, "66587fa") if up.Status != http.StatusOK { t.Fatalf("stage upload: %s", up) } cx.vars["upload"] = claimReplyField(t, up.Body, "upload") }, do: claimAPIReply(http.MethodPost, "/api/cli/system/update", `{"channel":"canary","upload":""}`), body: []string{`"job"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { job := cx.systemJob(t, claimReplyField(t, r.Body, "job")) if job["error"] != "" { return "the update job failed" } if got := cx.systemVersion("canary"); got != "66587fa" { return `the canary pointer names "` + got + `", want 66587fa` } return "" }, }, "route POST /api/cli/system/activation/cancel": { setup: func(t *testing.T, cx *claimCase) { ctx := context.Background() cx.systemRelease(t, "canary", "66587fa") current, _, err := release.GetSystemPointer(ctx, cx.rel, cx.pub, "router", "canary") if err != nil { t.Fatal(err) } prepared, err := release.PrepareSystemPromotion(ctx, cx.rel, release.RootSigner(cx.priv), "router", "canary", current.Current, release.SystemPromoteOptions{}, "claim-cancel-probe") if err != nil { t.Fatal(err) } d := cx.claimFixtureDirectory() o, err := d.Read(ctx) if err != nil { t.Fatal(err) } h := hostactors.Reservation{ID: prepared.ID, Publication: &prepared, RequestSHA256: strings.Repeat("a", 64), UpdateMode: "promote", FloorSHA256: o.Roster.FloorSHA256, Actors: map[string]string{}, Releases: map[string]hostactors.Report{}} for ref, box := range o.Roster.Boxes { h.Actors["box:"+ref] = box.Enrollment h.Releases["box:"+ref] = hostactors.Report{Enrollment: box.Enrollment} break } next := hostactors.Clone(o.Roster) next.Activations[h.ID] = h raw, err := release.SignRootEnvelope(release.RootSigner(cx.priv), next) if err != nil { t.Fatal(err) } if _, err = cx.rel.Put(ctx, hostactors.Key, raw, o.ETag); err != nil { t.Fatal(err) } raw, err = json.Marshal(prepared) if err != nil { t.Fatal(err) } cx.vars["cancel-publication"] = string(raw) }, do: claimAPIReply(http.MethodPost, "/api/cli/system/activation/cancel", `{"channel":"canary","activation":"claim-cancel-probe"}`), body: []string{`"activation":"claim-cancel-probe"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { ctx := context.Background() o, err := cx.claimFixtureDirectory().Read(ctx) if err != nil { return "cannot read roster after cancellation" } if _, ok := o.Roster.Activations["claim-cancel-probe"]; ok { return "activation remains held" } var p release.SystemPublication if err = json.Unmarshal([]byte(cx.vars["cancel-publication"]), &p); err != nil { t.Fatal(err) } if _, err = release.PublishSystemPublication(ctx, cx.rel, cx.pub, p); !errors.Is(err, release.ErrCASConflict) { return "cancelled publication was not fenced" } if cx.systemVersion("canary") != "66587fa" { return "cancellation changed the running version" } return "" }, }, "route POST /api/cli/system/rollback": { setup: func(t *testing.T, cx *claimCase) { cx.systemRelease(t, "canary", "66587fa") cx.systemRelease(t, "canary", "77698ab") if got := cx.systemVersion("canary"); got != "77698ab" { t.Fatalf("stage rollback: canary names %q, want 77698ab", got) } }, do: claimAPIReply(http.MethodPost, "/api/cli/system/rollback", `{"channel":"canary"}`), body: []string{`"version":"66587fa"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if got := cx.systemVersion("canary"); got != "66587fa" { return `the canary pointer names "` + got + `", want the first build 66587fa` } return "" }, }, "route PUT /api/cli/system/geoip": { serial: "withFixtureProbes swaps the package variable geoipProbeAddrs", setup: func(t *testing.T, cx *claimCase) { claimMustRunAlone(t, "geoip probe"); withFixtureProbes(t) }, do: func(t *testing.T, cx *claimCase) claimReply { return cx.apiReply(t, http.MethodPut, "/api/cli/system/geoip", string(fixture(t, "GeoIP2-City-Test.mmdb"))) }, status: http.StatusAccepted, body: []string{`"job"`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { // The publish is a job: the 202 only says it was accepted. for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { raw, _, err := cx.rel.Get(context.Background(), geoip.PointerKey) if err != nil { continue } if ptr, err := geoip.ParsePointer(raw); err == nil && ptr.DatabaseType == "GeoIP2-City" && cx.bucketHas(ptr.Key) { return "" } } return "the geoip pointer never named the uploaded database" }, }, "route DELETE /api/cli/system/geoip": { serial: "withFixtureProbes swaps the package variable geoipProbeAddrs", setup: func(t *testing.T, cx *claimCase) { claimMustRunAlone(t, "geoip probe") withFixtureProbes(t) if _, err := cx.srv.geoip.Publish(context.Background(), strings.NewReader(string(fixture(t, "GeoIP2-City-Test.mmdb"))), geoipPublishOpts{Origin: "manual"}); err != nil { t.Fatalf("stage a published geoip database: %v", err) } if !cx.bucketHas(geoip.PointerKey) { t.Fatal("stage a published geoip database: no pointer") } }, do: claimAPIReply(http.MethodDelete, "/api/cli/system/geoip", ""), body: []string{`"off":true`}, effect: func(t *testing.T, cx *claimCase, r claimReply) string { if cx.bucketHas(geoip.PointerKey) { return "the geoip pointer is still published" } keys, err := cx.rel.List(context.Background(), geoip.Prefix) if err != nil { return err.Error() } for _, k := range keys { if strings.HasSuffix(k, ".mmdb") { return "the blob " + k + " survived turning geoip off" } } return "" }, }, }