package console // The fleets API (fleets.go): account-owned Fleet/FleetBox rows and app // placement. The properties that matter, in the order they'd hurt if lost: // a fleet name owned by another account resolves the SAME byte-identical // 404 an unknown one does (fleet names are one flat, cross-account // namespace — see fleets.go's package doc), a minted or rotated token's // plaintext appears exactly once and its stored hash is really its sha256, // detach flips status without touching the token, and `place` stamps BOTH // an app's routes and its already-provisioned instance records (the // tripwire the brief calls out by name) while refusing a target fleet the // caller does not own. import ( "context" "crypto/sha256" "encoding/hex" "errors" "io" "net/http" "net/url" "os" "strings" "testing" "time" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/status" ) // fleetFixture is a console with paul (the fixture's default ops-account // owner, sqid fx.sqid) holding a live CLI token — owner-level, which is // exactly the gate every fleet door grants (there is no member/owner split // in this API; the account-scoped 404 is the only gate). func fleetFixture(t *testing.T, code string) (*consoleFixture, string) { t.Helper() fx := newConsoleFixture(t) return fx, cliToken(t, fx, "paul@keymail.dev", code) } func fleetsURL(fx *consoleFixture, acct string) string { return fx.ts.URL + "/api/cli/fleets/" + acct } // openTestReg opens a second, independent handle on the fixture's registry // file to seed rows the console API itself has no door for (routes) — // called before the console's own handlers ever touch this path, so there // is no cross-connection WAL-staleness risk (registry.go's List/Fingerprint // doc comment): the console's cached handle (openRegistry, view.go) does // not exist yet, and every write below is committed before that first call. func openTestReg(t *testing.T, fx *consoleFixture) *registry.Registry { t.Helper() reg, err := registry.Open(fx.regPath) if err != nil { t.Fatal(err) } return reg } // TestFleetCreateListRoundTrip: create, then list, and the created fleet // comes back with an empty (never null) boxes array. func TestFleetCreateListRoundTrip(t *testing.T) { fx, token := fleetFixture(t, "code-fleet-roundtrip") status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "acme", "bucket": "acme-data"}, token) if status != http.StatusOK { t.Fatalf("create = %d %v, want 200", status, body) } f, ok := body["fleet"].(map[string]any) if !ok { t.Fatalf("create body = %v, want a \"fleet\" object", body) } if f["name"] != "acme" || f["bucket"] != "acme-data" { t.Fatalf("fleet = %v, want name=acme bucket=acme-data", f) } boxes, ok := f["boxes"].([]any) if !ok || len(boxes) != 0 { t.Fatalf("fleet.boxes = %v, want an empty array", f["boxes"]) } status, body = getJSON(t, fleetsURL(fx, fx.sqid), token) if status != http.StatusOK { t.Fatalf("list = %d %v, want 200", status, body) } list, ok := body["fleets"].([]any) if !ok || len(list) != 1 { t.Fatalf("list = %v, want exactly the one fleet", body) } got := list[0].(map[string]any) if got["name"] != "acme" || got["bucket"] != "acme-data" { t.Fatalf("listed fleet = %v, want name=acme bucket=acme-data", got) } } // TestFleetCreateDuplicateName409: the exact "that name is taken" wording // handleAPICreateApp already uses for the same conflict on apps. func TestFleetCreateDuplicateName409(t *testing.T) { fx, token := fleetFixture(t, "code-fleet-dup") u := fleetsURL(fx, fx.sqid) + "/new" if status, body := postJSON(t, u, map[string]string{"name": "acme", "bucket": "bkt-1"}, token); status != http.StatusOK { t.Fatalf("first create = %d %v, want 200", status, body) } status, body := postJSON(t, u, map[string]string{"name": "acme", "bucket": "bkt-2"}, token) if status != http.StatusConflict { t.Fatalf("duplicate create = %d %v, want 409", status, body) } if body["error"] != "that name is taken" { t.Fatalf("duplicate error = %q, want the exact convention", body["error"]) } } // TestFleetAddBoxTokenHash: the minted token is 64 hex characters, its // sha256 hex is exactly what landed in the registry row, and it never // appears anywhere in the create-fleet or list responses. func TestFleetAddBoxTokenHash(t *testing.T) { fx, token := fleetFixture(t, "code-fleet-addbox") if status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "acme", "bucket": "acme-data"}, token); status != http.StatusOK { t.Fatalf("create fleet = %d %v", status, body) } status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/acme/boxes", map[string]string{"label": "pi-1"}, token) if status != http.StatusOK { t.Fatalf("add-box = %d %v, want 200", status, body) } plaintext, _ := body["token"].(string) if len(plaintext) != 64 { t.Fatalf("token = %q, want 64 hex characters", plaintext) } if body["label"] != "pi-1" { t.Fatalf("label = %v, want pi-1", body["label"]) } sum := sha256.Sum256([]byte(plaintext)) wantHash := hex.EncodeToString(sum[:]) reg := openTestReg(t, fx) boxes, err := reg.ListFleetBoxes("acme") if err != nil { t.Fatal(err) } if len(boxes) != 1 || boxes[0].Label != "pi-1" || boxes[0].Status != "active" { t.Fatalf("registry boxes = %+v", boxes) } if boxes[0].TokenHash != wantHash { t.Fatalf("stored hash = %s, want sha256(token) = %s", boxes[0].TokenHash, wantHash) } } // TestFleetRotateBoxChangesHash: a fresh token comes back, distinct from the // mint's, and the stored hash moves to match it. func TestFleetRotateBoxChangesHash(t *testing.T) { fx, token := fleetFixture(t, "code-fleet-rotate") if status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "acme", "bucket": "acme-data"}, token); status != http.StatusOK { t.Fatalf("create fleet = %d %v", status, body) } _, addBody := postJSON(t, fleetsURL(fx, fx.sqid)+"/acme/boxes", map[string]string{"label": "pi-1"}, token) first, _ := addBody["token"].(string) status, rotBody := postJSON(t, fleetsURL(fx, fx.sqid)+"/acme/boxes/pi-1/rotate", map[string]string{}, token) if status != http.StatusOK { t.Fatalf("rotate = %d %v, want 200", status, rotBody) } second, _ := rotBody["token"].(string) if second == "" || second == first { t.Fatalf("rotated token = %q, want a fresh 64-hex value distinct from %q", second, first) } reg := openTestReg(t, fx) boxes, err := reg.ListFleetBoxes("acme") if err != nil { t.Fatal(err) } sum := sha256.Sum256([]byte(second)) wantHash := hex.EncodeToString(sum[:]) if len(boxes) != 1 || boxes[0].TokenHash != wantHash { t.Fatalf("registry boxes = %+v, want hash %s", boxes, wantHash) } // The OLD hash no longer matches anything stored. oldSum := sha256.Sum256([]byte(first)) if boxes[0].TokenHash == hex.EncodeToString(oldSum[:]) { t.Fatalf("registry still carries the pre-rotation hash") } } // TestFleetDetachBoxFlipsStatus: detach flips status to "detached" and // leaves the token hash untouched — rotation and detachment are different // axes (fleets.go's rotate doc comment). func TestFleetDetachBoxFlipsStatus(t *testing.T) { fx, token := fleetFixture(t, "code-fleet-detach") if status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "acme", "bucket": "acme-data"}, token); status != http.StatusOK { t.Fatalf("create fleet = %d %v", status, body) } postJSON(t, fleetsURL(fx, fx.sqid)+"/acme/boxes", map[string]string{"label": "pi-1"}, token) status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/acme/boxes/pi-1/detach", map[string]string{}, token) if status != http.StatusOK { t.Fatalf("detach = %d %v, want 200", status, body) } reg := openTestReg(t, fx) boxes, err := reg.ListFleetBoxes("acme") if err != nil { t.Fatal(err) } if len(boxes) != 1 || boxes[0].Status != "detached" { t.Fatalf("registry boxes = %+v, want status detached", boxes) } // The list door reflects it too. _, listBody := getJSON(t, fleetsURL(fx, fx.sqid), token) fleets := listBody["fleets"].([]any) f := fleets[0].(map[string]any) bx := f["boxes"].([]any)[0].(map[string]any) if bx["status"] != "detached" { t.Fatalf("listed box status = %v, want detached", bx["status"]) } } // TestAPIFleetTagBox: a full-replace tag write lands in the registry and // shows up on the list door's "tags" field; bad grammar 400s; an unknown // label 404s byte-identical to rotate's own unknown-label 404 (both are // jsonError's "no such box", not the plain apiFleet 404); a foreign-owned // fleet 404s the ordinary apiFleet way (byte-identical to canonicalNotFound, // same as every other door in TestFleetCrossAccountAccess404). func TestAPIFleetTagBox(t *testing.T) { fx, token := fleetFixture(t, "code-fleet-tagbox") if status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "pi", "bucket": "pi-data"}, token); status != http.StatusOK { t.Fatalf("create fleet = %d %v", status, body) } if status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/pi/boxes", map[string]string{"label": "box-a"}, token); status != http.StatusOK { t.Fatalf("add-box = %d %v", status, body) } status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/pi/boxes/box-a/tags", map[string]string{"tags": "office,free"}, token) if status != http.StatusOK { t.Fatalf("tag-box = %d %v, want 200", status, body) } reg := openTestReg(t, fx) boxes, err := reg.ListFleetBoxes("pi") if err != nil { t.Fatal(err) } if len(boxes) != 1 || boxes[0].Tags != "office,free" { t.Fatalf("registry boxes = %+v, want tags office,free", boxes) } _, listBody := getJSON(t, fleetsURL(fx, fx.sqid), token) fleets := listBody["fleets"].([]any) f := fleets[0].(map[string]any) bx := f["boxes"].([]any)[0].(map[string]any) if bx["tags"] != "office,free" { t.Fatalf("listed box tags = %v, want office,free", bx["tags"]) } // Bad grammar (uppercase) is a 400, and must not have touched the row. status, body = postJSON(t, fleetsURL(fx, fx.sqid)+"/pi/boxes/box-a/tags", map[string]string{"tags": "Office"}, token) if status != http.StatusBadRequest { t.Fatalf("bad-grammar tag-box = %d %v, want 400", status, body) } boxes, err = reg.ListFleetBoxes("pi") if err != nil { t.Fatal(err) } if boxes[0].Tags != "office,free" { t.Fatalf("registry tags after rejected write = %q, want unchanged office,free", boxes[0].Tags) } // Unknown label: byte-identical to rotate's own unknown-label 404. rotStatus, rotHeader, rotBody := rawReq(t, http.MethodPost, fleetsURL(fx, fx.sqid)+"/pi/boxes/ghost/rotate", token, `{}`) tagStatus, tagHeader, tagBody := rawReq(t, http.MethodPost, fleetsURL(fx, fx.sqid)+"/pi/boxes/ghost/tags", token, `{"tags":"x"}`) if tagStatus != http.StatusNotFound { t.Fatalf("tag-box unknown label = %d, want 404", tagStatus) } if tagStatus != rotStatus || tagBody != rotBody || tagHeader.Get("Content-Type") != rotHeader.Get("Content-Type") { t.Fatalf("tag-box unknown-label 404 = %d %q (%s), want byte-identical to rotate's %d %q (%s)", tagStatus, tagBody, tagHeader.Get("Content-Type"), rotStatus, rotBody, rotHeader.Get("Content-Type")) } // Foreign-owned fleet: the ordinary apiFleet 404, same as every other // door in TestFleetCrossAccountAccess404. now := time.Now() if err := fx.st.CreateAccount(42, "cdef", "widgets", "bob@keymail.dev", now); err != nil { t.Fatal(err) } bobToken := cliToken(t, fx, "bob@keymail.dev", "code-fleet-tagbox-bob") want := canonicalNotFound(t) fStatus, fHeader, fOut := rawReq(t, http.MethodPost, fleetsURL(fx, "cdef")+"/pi/boxes/box-a/tags", bobToken, `{"tags":"x"}`) assertNotFound(t, "tag-box on a foreign-owned fleet", want, fStatus, fHeader, fOut) } // TestFleetCrossAccountAccess404: a fleet named under the caller's OWN // account segment but owned by a DIFFERENT account resolves the exact same // bytes an outright nonexistent fleet name does — fleet names are one flat // namespace (fleets.go's package doc), so this is the probe surface that // matters here, distinct from (and in addition to) apiAccount's ordinary // unknown/foreign-account 404. func TestFleetCrossAccountAccess404(t *testing.T) { fx, ownerToken := fleetFixture(t, "code-fleet-cross-owner") if status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "acme", "bucket": "acme-data"}, ownerToken); status != http.StatusOK { t.Fatalf("create fleet = %d %v", status, body) } now := time.Now() if err := fx.st.CreateAccount(42, "cdef", "widgets", "bob@keymail.dev", now); err != nil { t.Fatal(err) } bobToken := cliToken(t, fx, "bob@keymail.dev", "code-fleet-cross-bob") want := canonicalNotFound(t) cases := []struct { what, method, url, body string }{ {"list boxes via add-box on a foreign-owned fleet", http.MethodPost, fleetsURL(fx, "cdef") + "/acme/boxes", `{"label":"x"}`}, {"add-box on a genuinely nonexistent fleet", http.MethodPost, fleetsURL(fx, "cdef") + "/ghost/boxes", `{"label":"x"}`}, {"rotate on a foreign-owned fleet", http.MethodPost, fleetsURL(fx, "cdef") + "/acme/boxes/pi-1/rotate", `{}`}, {"detach on a foreign-owned fleet", http.MethodPost, fleetsURL(fx, "cdef") + "/acme/boxes/pi-1/detach", `{}`}, } for _, c := range cases { status, h, out := rawReq(t, c.method, c.url, bobToken, c.body) assertNotFound(t, c.what, want, status, h, out) } // Bob's own account is genuinely untouched: an honest fleet of his own // still works, proving the refusal above is about OWNERSHIP, not a // broken door. if status, body := postJSON(t, fleetsURL(fx, "cdef")+"/new", map[string]string{"name": "widget-fleet", "bucket": "w-data"}, bobToken); status != http.StatusOK { t.Fatalf("bob's own fleet create = %d %v, want 200", status, body) } } // TestFleetMissingBearerIs401: every fleet door, and place, answer the same // 401 with no credential at all. func TestFleetMissingBearerIs401(t *testing.T) { fx, token := fleetFixture(t, "code-fleet-401") postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "acme", "bucket": "acme-data"}, token) postJSON(t, fleetsURL(fx, fx.sqid)+"/acme/boxes", map[string]string{"label": "pi-1"}, token) for _, c := range []struct{ method, url, body string }{ {http.MethodPost, fleetsURL(fx, fx.sqid) + "/new", `{"name":"x","bucket":"x-data"}`}, {http.MethodGet, fleetsURL(fx, fx.sqid), ""}, {http.MethodPost, fleetsURL(fx, fx.sqid) + "/acme/boxes", `{"label":"x"}`}, {http.MethodPost, fleetsURL(fx, fx.sqid) + "/acme/boxes/pi-1/rotate", `{}`}, {http.MethodPost, fleetsURL(fx, fx.sqid) + "/acme/boxes/pi-1/detach", `{}`}, {http.MethodPost, fx.ts.URL + "/api/cli/apps/" + fx.sqid + "/kass/place", `{"target":"fleet/acme"}`}, } { if status, _, out := rawReq(t, c.method, c.url, "", c.body); status != http.StatusUnauthorized { t.Errorf("%s %s with no token = %d %s, want 401", c.method, c.url, status, out) } } } // placeURL is the place door for one app under the fixture's own account. func placeURL(fx *consoleFixture, app string) string { return fx.ts.URL + "/api/cli/apps/" + fx.sqid + "/" + app + "/place" } // TestAppPlacementFallsBackToRouteWhenStoreEmpty: an app placed by an OLDER // build — before the store's placement column existed (2026-08-06 ruling) — // has its placement fact only in its routes, stamped directly rather than // through handleAPIPlace (which now also writes the store). appPlacement // must still find it: Store.AppPlacement answers "" for this app, so the // derivation falls back to walking routes, exactly as it always did before // the store became the primary source. func TestAppPlacementFallsBackToRouteWhenStoreEmpty(t *testing.T) { fx := newConsoleFixture(t) now := time.Now() if _, err := fx.st.CreateAppIn(OpsAccountID, "titogo", "paul@keymail.dev", now); err != nil { t.Fatal(err) } reg := openTestReg(t, fx) if err := reg.Upsert(registry.Route{ Host: "acme.go.tito.io", Kind: "instance", App: "titogo", Account: fx.sqid, Region: "fleet/pi", }); err != nil { t.Fatal(err) } // Sanity: the store genuinely has nothing recorded for this app. if p, err := fx.st.AppPlacement(OpsAccountID, "titogo"); err != nil || p != "" { t.Fatalf("store placement before appPlacement runs = %q %v, want \"\" (unset)", p, err) } acct := &Account{ID: OpsAccountID, Sqid: fx.sqid} got, err := fx.srv.appPlacement(reg, acct, "titogo") if err != nil { t.Fatal(err) } if got != "fleet/pi" { t.Fatalf("appPlacement = %q, want the route-derived fleet/pi", got) } } // TestPlaceStampsRoutesAndInstances is the tripwire test the brief calls out // by name: placing an app stamps BOTH its routes' Region (walked from the // registry, SetPlacement per host) AND its already-provisioned instance // records' Region (provision.SetRecordRegion) — the second half is what // makes placement real for instances that already exist, not only future // ones. Clearing (target "") repoints both back to "". func TestPlaceStampsRoutesAndInstances(t *testing.T) { fx, token := fleetFixture(t, "code-place-tripwire") now := time.Now() if _, err := fx.st.CreateAppIn(OpsAccountID, "titogo", "paul@keymail.dev", now); err != nil { t.Fatal(err) } // Seed two routes for the app, owned by the fixture account, BEFORE any // console fleets/registry call — see openTestReg's doc comment for why // that ordering matters. reg := openTestReg(t, fx) for _, host := range []string{"acme.go.tito.io", "beta.go.tito.io"} { if err := reg.Upsert(registry.Route{Host: host, Kind: "instance", App: "titogo", Account: fx.sqid}); err != nil { t.Fatal(err) } } // A route for a DIFFERENT app, same account — must be left alone. if err := reg.Upsert(registry.Route{Host: "other.go.tito.io", Kind: "instance", App: "otherapp", Account: fx.sqid}); err != nil { t.Fatal(err) } // The fleet target must exist and be owned by the caller. if status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "pi", "bucket": "pi-fleet-data"}, token); status != http.StatusOK { t.Fatalf("create fleet = %d %v", status, body) } // One already-provisioned instance record for the app. ctx := context.Background() rec := provision.Record{Schema: 1, Host: "acme.go.tito.io", Slug: "acme", App: "titogo", Account: fx.sqid, Region: "ie", Channel: "stable", CreatedAt: now.UTC()} if err := provision.CreateRecord(ctx, fx.rel, rec); err != nil { t.Fatal(err) } status, body := postJSON(t, placeURL(fx, "titogo"), map[string]string{"target": "fleet/pi"}, token) if status != http.StatusOK { t.Fatalf("place = %d %v, want 200", status, body) } for _, host := range []string{"acme.go.tito.io", "beta.go.tito.io"} { rt, err := reg.Get(host) if err != nil || rt == nil { t.Fatalf("route %s: %v", host, err) } if rt.Region != "fleet/pi" { t.Errorf("route %s Region = %q, want fleet/pi", host, rt.Region) } } other, err := reg.Get("other.go.tito.io") if err != nil || other == nil { t.Fatalf("other-app route: %v", err) } if other.Region != "" { t.Fatalf("a route belonging to a DIFFERENT app was stamped: Region = %q", other.Region) } got, err := provision.GetRecord(ctx, fx.rel, fx.sqid, "titogo", "acme.go.tito.io") if err != nil { t.Fatal(err) } if got.Region != "fleet/pi" { t.Fatalf("instance record Region = %q, want fleet/pi — the tripwire: routes-only stamping is not enough", got.Region) } // Nothing else about the record moved. if got.Channel != "stable" || got.Slug != "acme" { t.Fatalf("place touched more than Region: %+v", got) } // Clearing repoints both back to "". if status, body := postJSON(t, placeURL(fx, "titogo"), map[string]string{"target": ""}, token); status != http.StatusOK { t.Fatalf("clear place = %d %v, want 200", status, body) } rt, err := reg.Get("acme.go.tito.io") if err != nil || rt == nil || rt.Region != "" { t.Fatalf("route after clearing: %+v, %v, want Region \"\"", rt, err) } got, err = provision.GetRecord(ctx, fx.rel, fx.sqid, "titogo", "acme.go.tito.io") if err != nil || got.Region != "" { t.Fatalf("instance record after clearing: %+v, %v, want Region \"\"", got, err) } } // vanishOnGetStore wraps a release.Store and deletes ONE key out from under // itself the first time that key is Get after skip Gets — a stand-in for I4's race: an // instance record retired (DeleteRecord) in the gap between handleAPIPlace's // List of a prefix and its per-key SetRecordRegion write reaching that same // key. Every other key, and every other method, is the embedded Store's real // behavior untouched. type vanishOnGetStore struct { release.Store vanish string skip int // Gets of vanish to let through first deleted bool } func (v *vanishOnGetStore) Get(ctx context.Context, key string) ([]byte, string, error) { if key == v.vanish && !v.deleted && v.skip > 0 { v.skip-- } else if key == v.vanish && !v.deleted { v.deleted = true if err := v.Store.Delete(ctx, key); err != nil { return nil, "", err } } return v.Store.Get(ctx, key) } // TestPlaceSkipsVanishedRecordMidWalk is I4 (final customer-fleets review): // handleAPIPlace's instance-record walk lists a prefix, then writes each key // it found. If one of those records is deleted in the gap between the list // and its own write — SetRecordRegion returns an os.ErrNotExist-wrapped // error — that must be a logged skip, not a 502 that fails the whole // placement (and not silently swallowing some OTHER error, which still must // fail the call). The surviving record must still get stamped. func TestPlaceSkipsVanishedRecordMidWalk(t *testing.T) { fx, token := fleetFixture(t, "code-place-vanish") now := time.Now() if _, err := fx.st.CreateAppIn(OpsAccountID, "titogo", "paul@keymail.dev", now); err != nil { t.Fatal(err) } reg := openTestReg(t, fx) for _, host := range []string{"acme.go.tito.io", "ghost.go.tito.io"} { if err := reg.Upsert(registry.Route{Host: host, Kind: "instance", App: "titogo", Account: fx.sqid}); err != nil { t.Fatal(err) } } if status, body := postJSON(t, fleetsURL(fx, fx.sqid)+"/new", map[string]string{"name": "pi", "bucket": "pi-fleet-data"}, token); status != http.StatusOK { t.Fatalf("create fleet = %d %v", status, body) } ctx := context.Background() for _, host := range []string{"acme.go.tito.io", "ghost.go.tito.io"} { rec := provision.Record{Schema: 1, Host: host, Slug: strings.SplitN(host, ".", 2)[0], App: "titogo", Account: fx.sqid, Channel: "stable", CreatedAt: now.UTC()} if err := provision.CreateRecord(ctx, fx.rel, rec); err != nil { t.Fatal(err) } } // Swap the server's store for one that deletes ghost's record the // instant handleAPIPlace's walk tries to read it — after the List that // found it, exactly the race SetRecordRegion's os.ErrNotExist-wrapped // return exists to name. ghostKey := provision.RecordKey(fx.sqid, "titogo", "ghost.go.tito.io") // skip 1: the place's strict read (seedmove.go) reads ghost first. fx.srv.cfg.Releases = &vanishOnGetStore{Store: fx.rel, vanish: ghostKey, skip: 1} status, body := postJSON(t, placeURL(fx, "titogo"), map[string]string{"target": "fleet/pi"}, token) if status != http.StatusOK { t.Fatalf("place = %d %v, want 200 (a vanished record mid-walk must not 502 the call)", status, body) } got, err := provision.GetRecord(ctx, fx.rel, fx.sqid, "titogo", "acme.go.tito.io") if err != nil { t.Fatal(err) } if got.Region != "fleet/pi" { t.Fatalf("surviving record Region = %q, want fleet/pi — a vanished sibling must not stop the rest of the walk", got.Region) } if _, err := provision.GetRecord(ctx, fx.rel, fx.sqid, "titogo", "ghost.go.tito.io"); !errors.Is(err, os.ErrNotExist) { t.Fatalf("ghost record: %v, want it to stay gone", err) } } // TestPlaceRefusesForeignFleet: placing onto a fleet owned by a DIFFERENT // account is refused (the same byte-identical 404 apiFleet gives elsewhere), // and nothing about the app's routes or instances is touched. func TestPlaceRefusesForeignFleet(t *testing.T) { fx, token := fleetFixture(t, "code-place-foreign") now := time.Now() if _, err := fx.st.CreateAppIn(OpsAccountID, "titogo", "paul@keymail.dev", now); err != nil { t.Fatal(err) } reg := openTestReg(t, fx) if err := reg.Upsert(registry.Route{Host: "acme.go.tito.io", Kind: "instance", App: "titogo", Account: fx.sqid}); err != nil { t.Fatal(err) } ctx := context.Background() rec := provision.Record{Schema: 1, Host: "acme.go.tito.io", Slug: "acme", App: "titogo", Account: fx.sqid, Channel: "stable", CreatedAt: now.UTC()} if err := provision.CreateRecord(ctx, fx.rel, rec); err != nil { t.Fatal(err) } // A second account with its OWN fleet, "widgets". if err := fx.st.CreateAccount(42, "cdef", "widgets", "bob@keymail.dev", now); err != nil { t.Fatal(err) } bobToken := cliToken(t, fx, "bob@keymail.dev", "code-place-foreign-bob") if status, body := postJSON(t, fleetsURL(fx, "cdef")+"/new", map[string]string{"name": "acme", "bucket": "bob-data"}, bobToken); status != http.StatusOK { t.Fatalf("bob's fleet create = %d %v", status, body) } want := canonicalNotFound(t) status, h, out := rawReq(t, http.MethodPost, placeURL(fx, "titogo"), token, `{"target":"fleet/acme"}`) assertNotFound(t, "place onto a foreign-owned fleet", want, status, h, out) // Nothing moved: not the route, not the record. rt, err := reg.Get("acme.go.tito.io") if err != nil || rt == nil || rt.Region != "" { t.Fatalf("route after a refused place: %+v, %v, want untouched (Region \"\")", rt, err) } got, err := provision.GetRecord(ctx, fx.rel, fx.sqid, "titogo", "acme.go.tito.io") if err != nil || got.Region != "" { t.Fatalf("instance record after a refused place: %+v, %v, want untouched (Region \"\")", got, err) } } // TestPlaceInvalidTargetIs400: a target that doesn't parse as "" or // "fleet/" is a plain 400, not a 404 — it never reaches the // fleet-ownership question at all. func TestPlaceInvalidTargetIs400(t *testing.T) { fx, token := fleetFixture(t, "code-place-badtarget") now := time.Now() if _, err := fx.st.CreateAppIn(OpsAccountID, "titogo", "paul@keymail.dev", now); err != nil { t.Fatal(err) } status, body := postJSON(t, placeURL(fx, "titogo"), map[string]string{"target": "eu"}, token) if status != http.StatusBadRequest { t.Fatalf("place with a bare region code = %d %v, want 400", status, body) } } // ---- Task 9: the console fleet page (owner view) ------------------------- // // The properties that matter here, same ordering rule as the API section // above: a non-member gets the byte-identical 404 an unrouted URL gets, a // member who isn't the owner sees the same data with no mutation forms, a // stale heartbeat renders "unreachable" and never its last happy numbers, // an unjoined registry row (Task 6's KNOWN DEGRADATION carry) renders safely // rather than crashing or borrowing another row's numbers, and a rotated // token appears in the response body exactly once, beside the // detach-not-rotate warning. // writeFleetHeartbeat lands one box snapshot at // fleets//status/boxes/