package console // The instance API (apiinstances.go): the console's provisioning verbs and // the app-held token that may speak to them. The properties that matter, in // the order they'd hurt if lost: every refusal before the principal is // proven is the family's byte-identical 404 (a probe learns nothing — not // whether an account exists, not whether an app does, not whether an app // token's binding is close to right), an app token is contained to its own // account+app and can mint nothing, no host reaches a bucket key without // passing the label+suffix check, and the rate limiter is consulted only // AFTER the gate so a 429 can never confirm what a 404 hides. import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "maps" "net/http" "net/http/httptest" "net/url" "os" "slices" "sort" "strings" "testing" "time" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/metrics" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/status" ) // rawReq is the tests' one-shot request: any method, an optional bearer and // an optional JSON body, and the response handed back as status + headers + // bytes so a refusal can be compared to http.NotFound's own output field by // field rather than merely "looks like a 404". func rawReq(t *testing.T, method, u, bearer, body string) (int, http.Header, string) { t.Helper() var rdr io.Reader if body != "" { rdr = strings.NewReader(body) } req, err := http.NewRequest(method, u, rdr) if err != nil { t.Fatal(err) } if body != "" { req.Header.Set("Content-Type", "application/json") } if bearer != "" { req.Header.Set("Authorization", "Bearer "+bearer) } res, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer res.Body.Close() out, err := io.ReadAll(res.Body) if err != nil { t.Fatal(err) } return res.StatusCode, res.Header, string(out) } // notFound404 is one refusal reduced to the parts a prober could read: the // status, the two headers http.Error sets, and the body. type notFound404 struct { Status int Type string NoSniff string Body string } // canonicalNotFound captures what the STDLIB writes, rather than typing the // expected bytes out here: the property under test is "identical to // http.NotFound", so http.NotFound is what the tests must compare against. func canonicalNotFound(t *testing.T) notFound404 { t.Helper() rec := httptest.NewRecorder() http.NotFound(rec, httptest.NewRequest(http.MethodGet, "/", nil)) return notFound404{rec.Code, rec.Header().Get("Content-Type"), rec.Header().Get("X-Content-Type-Options"), rec.Body.String()} } func assertNotFound(t *testing.T, what string, want notFound404, status int, h http.Header, body string) { t.Helper() got := notFound404{status, h.Get("Content-Type"), h.Get("X-Content-Type-Options"), body} if got != want { t.Errorf("%s\n got: %+v\nwant: the byte-identical %+v", what, got, want) } } // instanceFixture is a console with titogo claimed in the ops account and a // MEMBER (not owner) token for it — member-level is exactly the gate these // verbs grant, the same one promote and the ledger API use. func instanceFixture(t *testing.T, code string) (*consoleFixture, string) { t.Helper() fx := newConsoleFixture(t) now := time.Now() if _, err := fx.st.CreateAppIn(OpsAccountID, "titogo", "paul@keymail.dev", now); err != nil { t.Fatal(err) } if err := fx.st.AddMember(OpsAccountID, "alice@keymail.dev", RoleMember, now); err != nil { t.Fatal(err) } return fx, cliToken(t, fx, "alice@keymail.dev", code) } // enableInstances writes the app's manifest — the operator act (`carlos // instances enable`) whose ABSENCE is what makes the feature not exist. func enableInstances(t *testing.T, fx *consoleFixture, app string, m provision.Manifest) { t.Helper() if err := provision.PutManifest(context.Background(), fx.rel, release.OpsAccount, app, m); err != nil { t.Fatal(err) } } func titoManifest() provision.Manifest { return provision.Manifest{Schema: 1, App: "titogo", Domain: "go.tito.io", Pool: "tito", Channel: "stable", Regions: []string{"ie"}, Seed: []string{"seed"}} } func instancesURL(fx *consoleFixture, acct, app string) string { return fx.ts.URL + "/api/cli/apps/" + acct + "/" + app + "/instances" } // TestAPIInstanceRefusalMatrix: every way of not being allowed here — a // stranger's token, an app token pointed at a neighbouring account or a // neighbouring app, an account that does not exist, an app that was never // claimed — answers with the SAME bytes http.NotFound writes. Asserted // against the stdlib's own output, on every verb. func TestAPIInstanceRefusalMatrix(t *testing.T) { fx, member := instanceFixture(t, "code-inst-refusal") enableInstances(t, fx, "titogo", titoManifest()) want := canonicalNotFound(t) // A second account, so the app token's cross-account probe names one // that genuinely exists: the refusal must not depend on that. now := time.Now() if err := fx.st.CreateAccount(42, "cdef", "widgets", "bob@keymail.dev", now); err != nil { t.Fatal(err) } if _, err := fx.st.CreateAppIn(42, "titogo", "bob@keymail.dev", now); err != nil { t.Fatal(err) } // An app token bound to (bab, titogo) — the honest binding. appTok, err := fx.st.NewAppToken(OpsAccountID, "titogo", "alice@keymail.dev", "signup", now) if err != nil { t.Fatal(err) } // A member of nothing at all. eve := cliToken(t, fx, "eve@keymail.dev", "code-inst-eve") body := `{"host":"acme.go.tito.io","region":"ie"}` cases := []struct { what string method string url string token string body string }{ {"non-member list", http.MethodGet, instancesURL(fx, "bab", "titogo"), eve, ""}, {"non-member create", http.MethodPost, instancesURL(fx, "bab", "titogo"), eve, body}, {"non-member get", http.MethodGet, instancesURL(fx, "bab", "titogo") + "/acme.go.tito.io", eve, ""}, {"non-member delete", http.MethodDelete, instancesURL(fx, "bab", "titogo") + "/acme.go.tito.io", eve, ""}, {"app token, foreign account", http.MethodGet, instancesURL(fx, "cdef", "titogo"), appTok, ""}, {"app token, foreign account create", http.MethodPost, instancesURL(fx, "cdef", "titogo"), appTok, body}, {"app token, foreign app", http.MethodGet, instancesURL(fx, "bab", "other"), appTok, ""}, {"app token, foreign app create", http.MethodPost, instancesURL(fx, "bab", "other"), appTok, body}, // The account segment resolves by NAME for a user token; an app // token holds no memberships to resolve one against, so its own // account's name is not a way in either. {"app token, own account by name", http.MethodGet, instancesURL(fx, "ops", "titogo"), appTok, ""}, {"unknown account", http.MethodGet, instancesURL(fx, "zzzz", "titogo"), member, ""}, {"unclaimed app", http.MethodGet, instancesURL(fx, "bab", "ghost-app"), member, ""}, {"unclaimed app create", http.MethodPost, instancesURL(fx, "bab", "ghost-app"), member, body}, } for _, c := range cases { status, h, out := rawReq(t, c.method, c.url, c.token, c.body) assertNotFound(t, c.what, want, status, h, out) } // None of the refused calls wrote anything. if _, _, err := fx.rel.Get(context.Background(), provision.RecordKey("bab", "titogo", "acme.go.tito.io")); err == nil { t.Fatalf("a record exists after only refused creates") } if _, _, err := fx.rel.Get(context.Background(), provision.RecordKey("cdef", "titogo", "acme.go.tito.io")); err == nil { t.Fatalf("a record exists in the neighbouring account after only refused creates") } } // TestAPIInstanceNoToken: no credential at all is the 401 every CLI door // gives, on all five routes. func TestAPIInstanceNoToken(t *testing.T) { fx, _ := instanceFixture(t, "code-inst-notoken") for _, c := range []struct{ method, url, body string }{ {http.MethodGet, instancesURL(fx, "bab", "titogo"), ""}, {http.MethodPost, instancesURL(fx, "bab", "titogo"), `{"host":"a.go.tito.io"}`}, {http.MethodGet, instancesURL(fx, "bab", "titogo") + "/a.go.tito.io", ""}, {http.MethodDelete, instancesURL(fx, "bab", "titogo") + "/a.go.tito.io", ""}, {http.MethodPost, fx.ts.URL + "/api/cli/apps/bab/titogo/instance-token", `{}`}, } { 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) } } } // TestAPIInstanceAppTokenContainment: the app token is contained in both // directions. It cannot speak to the verbs a person's token speaks to // (promote), it cannot mint another token, and it CAN do exactly the four // instance verbs on its own app. func TestAPIInstanceAppTokenContainment(t *testing.T) { fx, _ := instanceFixture(t, "code-inst-contain") enableInstances(t, fx, "titogo", titoManifest()) appTok, err := fx.st.NewAppToken(OpsAccountID, "titogo", "alice@keymail.dev", "signup", time.Now()) if err != nil { t.Fatal(err) } // Promote: the containment is unchanged, the ANSWER moved from 401 to // 403 when the role gate landed (service-principals spec §3b, §8). An // app token is a service principal holding role `instance`, and promote // names `publish` — so cliRoute refuses it out at the wrapper, before // the handler and before the account segment is read. 403 discloses // nothing the holder does not already know: its own role, and the roles // this door wants. It is the same constant answer for every account // segment, which is what keeps it from being a membership oracle // (TestRoleRefusalIsConstantAcrossAccounts). if status, _, out := rawReq(t, http.MethodPost, fx.ts.URL+"/api/cli/apps/bab/titogo/promote", appTok, `{"version":"1.0.0","channel":"canary/a"}`); status != http.StatusForbidden { t.Fatalf("app token on promote = %d %s, want 403", status, out) } // Minting is a human act: the mint door is registered with the zero // Set, so no role reaches it and admin is no escape hatch either // (TestNoRoleReachesAMintFleetOrPolicyDoor). Behind it, apiPerson still // refuses a non-person outright — the gate is the outer of two walls, // not a replacement for the inner one. (apiAccountApp used to be that // inner wall by accident, because it resolved through cliUser; since it // became principal-aware the refusal is stated rather than inherited.) if status, _, out := rawReq(t, http.MethodPost, fx.ts.URL+"/api/cli/apps/bab/titogo/instance-token", appTok, `{}`); status != http.StatusForbidden { t.Fatalf("app token minting a token = %d %s, want 403", status, out) } // Its own app's verbs work. status, _, out := rawReq(t, http.MethodPost, instancesURL(fx, "bab", "titogo"), appTok, `{"host":"acme.go.tito.io","region":"ie"}`) if status != http.StatusCreated { t.Fatalf("app token create on its own app = %d %s, want 201", status, out) } var rec provision.Record if err := json.Unmarshal([]byte(out), &rec); err != nil { t.Fatalf("create body %s: %v", out, err) } if rec.CreatedBy != "app:bab/titogo" { t.Fatalf("created_by = %q, want the app principal app:bab/titogo", rec.CreatedBy) } if status, _, out := rawReq(t, http.MethodGet, instancesURL(fx, "bab", "titogo"), appTok, ""); status != http.StatusOK { t.Fatalf("app token list = %d %s, want 200", status, out) } if status, _, out := rawReq(t, http.MethodDelete, instancesURL(fx, "bab", "titogo")+"/acme.go.tito.io", appTok, ""); status != http.StatusOK { t.Fatalf("app token delete = %d %s, want 200", status, out) } } // TestAPIInstanceCreateRefusalOrder walks spec §7's order with a member // token: membership is already proven, so from here on refusals may name // their cause — and each one must be the RIGHT cause, in the right order. func TestAPIInstanceCreateRefusalOrder(t *testing.T) { fx, token := instanceFixture(t, "code-inst-create") u := instancesURL(fx, "bab", "titogo") // No manifest at all: the feature does not exist for this app. status, _, out := rawReq(t, http.MethodPost, u, token, `{"host":"acme.go.tito.io","region":"ie"}`) if status != http.StatusConflict { t.Fatalf("create with no manifest = %d %s, want 409", status, out) } if !strings.Contains(out, "carlos instances enable") { t.Fatalf("no-manifest refusal = %s, want it to name `carlos instances enable`", out) } enableInstances(t, fx, "titogo", titoManifest()) for _, c := range []struct{ what, body string }{ {"bad host shape", `{"host":"-nope-.go.tito.io","region":"ie"}`}, {"traversal host", `{"host":"../../etc/passwd","region":"ie"}`}, {"wrong suffix", `{"host":"acme.evil.example","region":"ie"}`}, {"bare domain, no label", `{"host":"go.tito.io","region":"ie"}`}, {"region outside the allow-list", `{"host":"acme.go.tito.io","region":"us"}`}, {"malformed region", `{"host":"acme.go.tito.io","region":"IE/../x"}`}, {"bad channel", `{"host":"acme.go.tito.io","region":"ie","channel":"../etc"}`}, } { status, _, out := rawReq(t, http.MethodPost, u, token, c.body) if status != http.StatusBadRequest { t.Errorf("%s = %d %s, want 400", c.what, status, out) } } // Nothing above reached the bucket. keys, err := fx.rel.List(context.Background(), provision.RecordsPrefix("bab", "titogo")) if err != nil { t.Fatal(err) } if len(keys) != 0 { t.Fatalf("refused creates left %v in the bucket", keys) } // The happy path: channel defaults from the manifest, pool is stamped // from it (never from the request), the record comes back whole. status, _, out = rawReq(t, http.MethodPost, u, token, `{"host":"acme.go.tito.io","region":"ie","pool":"attacker"}`) if status != http.StatusCreated { t.Fatalf("create = %d %s, want 201", status, out) } var rec provision.Record if err := json.Unmarshal([]byte(out), &rec); err != nil { t.Fatalf("create body %s: %v", out, err) } if rec.Schema != 1 || rec.Host != "acme.go.tito.io" || rec.Slug != "acme" || rec.App != "titogo" || rec.Account != "bab" || rec.Pool != "tito" || rec.Region != "ie" || rec.Channel != "stable" { t.Fatalf("record = %+v", rec) } if rec.CreatedBy != "alice@keymail.dev" { t.Fatalf("created_by = %q, want the member's address", rec.CreatedBy) } // It is in the bucket at exactly the key the reconciler will look for. raw, _, err := fx.rel.Get(context.Background(), provision.RecordKey("bab", "titogo", "acme.go.tito.io")) if err != nil { t.Fatalf("stored record: %v", err) } var stored provision.Record if err := json.Unmarshal(raw, &stored); err != nil { t.Fatal(err) } if stored.CreatedBy != "alice@keymail.dev" || stored.Host != "acme.go.tito.io" { t.Fatalf("stored record = %+v", stored) } if stored.CreatedAt.IsZero() { t.Fatalf("stored record has no created_at: %+v", stored) } // The same host twice is a lost race, not a silent overwrite. status, _, out = rawReq(t, http.MethodPost, u, token, `{"host":"acme.go.tito.io","region":"ie"}`) if status != http.StatusConflict { t.Fatalf("duplicate create = %d %s, want 409", status, out) } if !strings.Contains(out, "already exists") { t.Fatalf("duplicate refusal = %s, want it to say the instance already exists", out) } // An explicit channel is honoured over the manifest's default. status, _, out = rawReq(t, http.MethodPost, u, token, `{"host":"beta.go.tito.io","region":"ie","channel":"canary/beta"}`) if status != http.StatusCreated { t.Fatalf("create with channel = %d %s, want 201", status, out) } if err := json.Unmarshal([]byte(out), &rec); err != nil { t.Fatal(err) } if rec.Channel != "canary/beta" { t.Fatalf("channel = %q, want the requested canary/beta", rec.Channel) } } // TestAPIInstanceCreateInheritsFleetPlacement: a fleet-placed app's // instances inherit the app's placement (customer-fleets plan, spec's "one // flag on the app, all its instances follow") — the request body's region // is ignored entirely, and even a region the manifest's allow-list would // otherwise refuse succeeds, because it is never consulted once the app is // placed. // // A route must already exist for "titogo" before place is called: // appPlacement (fleets.go) derives an app's placement from its ALREADY // STAMPED routes, so an app placed before it has any route/instance at all // has nothing yet to derive from — a known limitation of the routes-derived // approach, not exercised by this test (see fleets.go's appPlacement doc // comment). Seeding one here is the realistic shape: an app already // running (e.g. via `carlos add`) before being placed onto a fleet. func TestAPIInstanceCreateInheritsFleetPlacement(t *testing.T) { fx, token := instanceFixture(t, "code-inst-placed") enableInstances(t, fx, "titogo", titoManifest()) // Regions: []string{"ie"} reg, err := registry.Open(fx.regPath) if err != nil { t.Fatal(err) } if err := reg.Upsert(registry.Route{Host: "www.go.tito.io", Kind: "instance", App: "titogo", Account: "bab"}); err != nil { t.Fatal(err) } if status, body := postJSON(t, fx.ts.URL+"/api/cli/fleets/bab/new", map[string]string{"name": "pi", "bucket": "pi-fleet-data"}, token); status != http.StatusOK { t.Fatalf("create fleet = %d %v, want 200", status, body) } if status, body := postJSON(t, fx.ts.URL+"/api/cli/apps/bab/titogo/place", map[string]string{"target": "fleet/pi"}, token); status != http.StatusOK { t.Fatalf("place = %d %v, want 200", status, body) } u := instancesURL(fx, "bab", "titogo") // "us" is outside titoManifest's ["ie"] allow-list — refused on an // unplaced app (TestAPIInstanceCreateUnplacedHonorsBodyRegion below), // but accepted (and overridden) here. status, _, out := rawReq(t, http.MethodPost, u, token, `{"host":"acme.go.tito.io","region":"us"}`) if status != http.StatusCreated { t.Fatalf("create on a placed app = %d %s, want 201", status, out) } var rec provision.Record if err := json.Unmarshal([]byte(out), &rec); err != nil { t.Fatalf("create body %s: %v", out, err) } if rec.Region != "fleet/pi" { t.Fatalf("record Region = %q, want the inherited fleet/pi (body region ignored)", rec.Region) } // The stored record agrees. stored, err := provision.GetRecord(context.Background(), fx.rel, "bab", "titogo", "acme.go.tito.io") if err != nil { t.Fatal(err) } if stored.Region != "fleet/pi" { t.Fatalf("stored record Region = %q, want fleet/pi", stored.Region) } } // TestAPIInstanceCreateInheritsRouteLessPlacedApp is the fix for the // limitation TestAPIInstanceCreateInheritsFleetPlacement's doc comment used // to describe: an app placed at CREATE TIME (`carlos apps create -place // fleet/`, 2026-08-06 ruling) has ZERO routes at the instant of // placement — nothing for the old route-derived appPlacement to find — yet // its first instance must still inherit the target, because the store's // placement column (not routes) is now the source of truth. Unlike // TestAPIInstanceCreateInheritsFleetPlacement, no route is ever seeded here. func TestAPIInstanceCreateInheritsRouteLessPlacedApp(t *testing.T) { fx := newConsoleFixture(t) token := cliToken(t, fx, "paul@keymail.dev", "code-inst-routeless-placed") if status, body := postJSON(t, fx.ts.URL+"/api/cli/fleets/"+fx.sqid+"/new", map[string]string{"name": "pi", "bucket": "pi-fleet-data"}, token); status != http.StatusOK { t.Fatalf("create fleet = %d %v, want 200", status, body) } // The app itself is created THROUGH the API, carrying place in the same // call — the exact CLI surface this test exists for, and the only way an // app can reach instance-create with a placement and genuinely zero // routes (a pre-seeded fixture app would not prove the store path). if status, body := postJSON(t, fx.ts.URL+"/api/cli/apps/"+fx.sqid+"/new", map[string]string{"name": "titogo", "place": "fleet/pi"}, token); status != http.StatusOK { t.Fatalf("create app with place = %d %v, want 200", status, body) } enableInstances(t, fx, "titogo", titoManifest()) // Regions: []string{"ie"} // Sanity: genuinely zero routes for this app — the case route-derivation // alone cannot answer. reg, err := registry.Open(fx.regPath) if err != nil { t.Fatal(err) } routes, err := reg.List() if err != nil { t.Fatal(err) } for _, rt := range routes { if rt.Account == fx.sqid && rt.App == "titogo" { t.Fatalf("titogo already has a route (%s) — this test needs zero to prove the store path", rt.Host) } } u := instancesURL(fx, fx.sqid, "titogo") // "us" is outside titoManifest's ["ie"] allow-list — accepted (and // overridden) here only because the store's placement inherits. status, _, out := rawReq(t, http.MethodPost, u, token, `{"host":"acme.go.tito.io","region":"us"}`) if status != http.StatusCreated { t.Fatalf("create on a route-less placed app = %d %s, want 201", status, out) } var rec provision.Record if err := json.Unmarshal([]byte(out), &rec); err != nil { t.Fatalf("create body %s: %v", out, err) } if rec.Region != "fleet/pi" { t.Fatalf("record Region = %q, want the inherited fleet/pi (body region ignored)", rec.Region) } } // TestAPIInstanceCreateUnplacedHonorsBodyRegion is the regression guard for // the inheritance check above: without a placement, the existing contract // is unchanged — the body's region is validated against the manifest's // allow-list and stored exactly as before. func TestAPIInstanceCreateUnplacedHonorsBodyRegion(t *testing.T) { fx, token := instanceFixture(t, "code-inst-unplaced") enableInstances(t, fx, "titogo", titoManifest()) u := instancesURL(fx, "bab", "titogo") status, _, out := rawReq(t, http.MethodPost, u, token, `{"host":"acme.go.tito.io","region":"ie"}`) if status != http.StatusCreated { t.Fatalf("create = %d %s, want 201", status, out) } var rec provision.Record if err := json.Unmarshal([]byte(out), &rec); err != nil { t.Fatal(err) } if rec.Region != "ie" { t.Fatalf("Region = %q, want the body's ie (no placement in play)", rec.Region) } // A region outside the manifest's allow-list is still refused, exactly // as it was before placement inheritance existed. status, _, out = rawReq(t, http.MethodPost, u, token, `{"host":"beta.go.tito.io","region":"us"}`) if status != http.StatusBadRequest { t.Fatalf("create outside the allow-list on an unplaced app = %d %s, want 400", status, out) } } // TestAPIInstanceCreateEmptyRegionAllowList: an empty allow-list means any // shape-valid region, per §3.1 — including the unconstrained empty one. func TestAPIInstanceCreateEmptyRegionAllowList(t *testing.T) { fx, token := instanceFixture(t, "code-inst-anyregion") m := titoManifest() m.Regions = nil m.Channel = "" enableInstances(t, fx, "titogo", m) u := instancesURL(fx, "bab", "titogo") status, _, out := rawReq(t, http.MethodPost, u, token, `{"host":"acme.go.tito.io","region":"us-east"}`) if status != http.StatusCreated { t.Fatalf("create with an open allow-list = %d %s, want 201", status, out) } var rec provision.Record if err := json.Unmarshal([]byte(out), &rec); err != nil { t.Fatal(err) } // No manifest channel either, and this app has no pipeline, so the // record follows the entry channel every app's releases land on. // apiinstanceschannel_test.go is where that resolution is pinned; here // it is only asserted so an open allow-list cannot quietly change it. if rec.Channel != "edge" { t.Fatalf("channel = %q, want the entry-channel default", rec.Channel) } if status, _, out := rawReq(t, http.MethodPost, u, token, `{"host":"beta.go.tito.io"}`); status != http.StatusCreated { t.Fatalf("create with no region = %d %s, want 201", status, out) } } // TestAPIInstanceListAndGet: what was created lists and reads back, and a // host that isn't there is the byte-identical 404 — a record's existence is // not something to probe for either. func TestAPIInstanceListAndGet(t *testing.T) { fx, token := instanceFixture(t, "code-inst-list") enableInstances(t, fx, "titogo", titoManifest()) u := instancesURL(fx, "bab", "titogo") for _, h := range []string{"acme.go.tito.io", "beta.go.tito.io"} { if status, _, out := rawReq(t, http.MethodPost, u, token, fmt.Sprintf(`{"host":%q,"region":"ie"}`, h)); status != http.StatusCreated { t.Fatalf("create %s = %d %s", h, status, out) } } status, _, out := rawReq(t, http.MethodGet, u, token, "") if status != http.StatusOK { t.Fatalf("list = %d %s", status, out) } var list struct { Instances []provision.Record `json:"instances"` Truncated bool `json:"truncated"` } if err := json.Unmarshal([]byte(out), &list); err != nil { t.Fatalf("list body %s: %v", out, err) } if len(list.Instances) != 2 || list.Truncated { t.Fatalf("list = %+v, want both records and truncated false", list) } if list.Instances[0].Host != "acme.go.tito.io" || list.Instances[1].Host != "beta.go.tito.io" { t.Fatalf("list order = %+v, want host order", list.Instances) } status, _, out = rawReq(t, http.MethodGet, u+"/acme.go.tito.io", token, "") if status != http.StatusOK { t.Fatalf("get = %d %s", status, out) } var rec provision.Record if err := json.Unmarshal([]byte(out), &rec); err != nil { t.Fatal(err) } if rec.Host != "acme.go.tito.io" || rec.Slug != "acme" { t.Fatalf("get = %+v", rec) } want := canonicalNotFound(t) status, h, out := rawReq(t, http.MethodGet, u+"/missing.go.tito.io", token, "") assertNotFound(t, "get of a host with no record", want, status, h, out) // A hostile host never reaches the key builder: same 404, nothing read. status, h, out = rawReq(t, http.MethodGet, u+"/..%2f..%2fchannels", token, "") assertNotFound(t, "get of a traversal host", want, status, h, out) } // TestAPIInstanceListStagedVersion (issue #78): the list join. A record // whose host was named in a FRESH box's private status/boxes/