package status_test import ( "bytes" "context" "encoding/json" "errors" "fmt" "reflect" "strings" "testing" "time" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/status" ) // flakyPutStore wraps a real release.Store and makes its first N Put calls // return release.ErrCASConflict before delegating — a stand-in for another // writer racing in between WriteBox's Get-etag and Put. Every other method // (Get, List) is the embedded Store's real behavior. type flakyPutStore struct { release.Store failCount int // Put calls left to fail before delegating putCalls int } func (f *flakyPutStore) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) { f.putCalls++ if f.putCalls <= f.failCount { return "", release.ErrCASConflict } return f.Store.Put(ctx, key, body, ifMatch) } func TestBandBoundaries(t *testing.T) { f := func(v float64) *float64 { return &v } cases := []struct { frac *float64 want string }{ {f(0.69), "plenty"}, {f(0.70), "getting-full"}, {f(0.90), "getting-full"}, {f(0.91), "at-capacity"}, } for _, c := range cases { got := status.CollectBox(nil, status.Probes{MachineID: "m", DiskFrac: c.frac}, time.Now()).Capacity if got == nil || *got != c.want { t.Errorf("frac %v: got %v want %s", *c.frac, got, c.want) } } if b := status.CollectBox(nil, status.Probes{MachineID: "m"}, time.Now()); b.Capacity != nil || b.Load != nil { t.Error("unknown fractions must band to null, not a word") } } func TestAwakeAsleepNullWhenUnreadable(t *testing.T) { routes := []registry.Route{{Host: "a", Kind: "instance", Hibernate: true, Socket: "/x.sock"}} b := status.CollectBox(routes, status.Probes{MachineID: "m"}, time.Now()) // nil Listening if b.Routes.Awake != nil || b.Routes.Asleep != nil { t.Error("nil socket probe must publish null, never 0") } } // TestCollectBoxSkipsPlatformMintedRoutes: a platform domain is a second // NAME for a route already counted, not a second instance. Counting both // would roughly double every fleet-wide instance number the moment // PLATFORM_APPS_DOMAIN is set — the public status page overstating the // fleet, which is exactly the kind of quiet dishonesty the page's whole // design exists to prevent. func TestCollectBoxSkipsPlatformMintedRoutes(t *testing.T) { listening := func(string) *bool { v := true; return &v } routes := []registry.Route{ {Host: "kass.training", Kind: "instance", Status: "ready"}, {Host: "kass.bab.oncarlos.test", Kind: "instance", Status: "ready", Platform: true}, {Host: "site.custom", Kind: "static"}, {Host: "site.bab.oncarlos.test", Kind: "static", Platform: true}, {Host: "sleepy.custom", Kind: "instance", Hibernate: true, Socket: "/s.sock"}, {Host: "sleepy.bab.oncarlos.test", Kind: "instance", Hibernate: true, Socket: "/s.sock", Platform: true}, } b := status.CollectBox(routes, status.Probes{MachineID: "m", Listening: listening}, time.Now()) if b.Routes.Resident != 1 || b.Routes.Static != 1 || b.Routes.Hibernating != 1 { t.Errorf("counts = %+v, want exactly one of each: aliases must not inflate the fleet", b.Routes) } if b.Routes.Awake == nil || *b.Routes.Awake != 1 { t.Errorf("awake = %v, want 1 — the alias must not be counted awake twice", b.Routes.Awake) } } // TestCollectBoxSidecarNotResident: a sidecar (Kind "sidecar") is not the // public fleet page's business — its existence is account-visible, not // public. Before an explicit branch, it fell into "default: Resident++", // silently inflating the public resident-app-instance count. It must land // in neither Resident, Static nor Hibernating (even when it happens to be // hibernating — a sidecar's Hibernate comes from the app manifest, so it may // legitimately be either), and Awake/Asleep must not count it — its Socket // is empty by construction, so probing it would be meaningless. func TestCollectBoxSidecarNotResident(t *testing.T) { listening := func(string) *bool { v := true; return &v } routes := []registry.Route{ {Host: "bot.eleven.bdm.sidecar.internal", Kind: "sidecar", Status: "ready"}, {Host: "bot2.eleven.bdm.sidecar.internal", Kind: "sidecar", Status: "ready", Hibernate: true}, {Host: "real.custom", Kind: "instance", Status: "ready"}, } b := status.CollectBox(routes, status.Probes{MachineID: "m", Listening: listening}, time.Now()) if b.Routes.Resident != 1 { t.Errorf("Resident = %d, want 1 — a sidecar must not be counted as a public resident app instance", b.Routes.Resident) } if b.Routes.Hibernating != 0 { t.Errorf("Hibernating = %d, want 0 — a hibernating sidecar still belongs in its own bucket, not this one", b.Routes.Hibernating) } if b.Sidecars != 2 { t.Errorf("Sidecars = %d, want 2 — both sidecars, hibernating or not", b.Sidecars) } } // TestSidecarsNeverReachFleetJSON is the dryrun.go pattern applied to this // one field: BuildFleet's per-box copy names Box's public fields one at a // time (fleet.go), and Sidecars must never be one of them — a sidecar's // existence is account-visible, not public. Proven by round-tripping through // the real writer/merger, not just by the FleetBox struct's shape, so a // future field added to FleetBox that DOES copy it would be caught here too. func TestSidecarsNeverReachFleetJSON(t *testing.T) { s := teststore(t) now := time.Now() routes := []registry.Route{{Host: "bot.x.sidecar.internal", Kind: "sidecar", Status: "ready"}} b := status.CollectBox(routes, status.Probes{MachineID: "leaky"}, now) if b.Sidecars != 1 { t.Fatalf("Sidecars = %d, want 1 in the private snapshot itself", b.Sidecars) } if err := status.WriteBox(context.Background(), s, b); err != nil { t.Fatal(err) } f, err := status.BuildFleet(context.Background(), s, nil, now, 5*time.Minute) if err != nil { t.Fatal(err) } j, err := json.Marshal(f) if err != nil { t.Fatal(err) } if strings.Contains(strings.ToLower(string(j)), "sidecar") { t.Errorf("fleet.json mentions sidecars: %s", j) } } func TestLabelStableAndOpaque(t *testing.T) { a, b := status.Label("machine-1"), status.Label("machine-1") if a != b || a == "" || strings.Contains(a, "machine") { t.Errorf("label %q must be stable and never contain the machine id", a) } } func TestSnapshotJSONCarriesNoHostnames(t *testing.T) { routes := []registry.Route{{Host: "secret.example.com", Kind: "instance", Status: "ready"}} b := status.CollectBox(routes, status.Probes{MachineID: "ip-10-0-0-7"}, time.Now()) j, _ := json.Marshal(b) for _, leak := range []string{"secret", "example.com", "ip-10-0-0-7"} { if strings.Contains(string(j), leak) { t.Errorf("snapshot leaked %q: %s", leak, j) } } } // TestRouteCountsJSONKeysAreSnakeCase locks RouteCounts' wire shape to the // design spec's fleet.json example (docs/superpowers/specs/2026-08-01-fleet- // status-page-design.md §2: {"static":2,"resident":3,"hibernating":4, // "awake":1,"asleep":3}) — lowercase snake_case, not Go's default // capitalized field names. func TestRouteCountsJSONKeysAreSnakeCase(t *testing.T) { routes := []registry.Route{ {Host: "a", Kind: "static"}, {Host: "b", Kind: "instance"}, {Host: "c", Kind: "instance", Hibernate: true, Socket: "/c.sock"}, } listening := func(string) *bool { v := true; return &v } b := status.CollectBox(routes, status.Probes{MachineID: "m", Listening: listening}, time.Now()) j, err := json.Marshal(b.Routes) if err != nil { t.Fatalf("marshal RouteCounts: %v", err) } for _, key := range []string{`"static"`, `"resident"`, `"hibernating"`, `"awake"`, `"asleep"`} { if !strings.Contains(string(j), key) { t.Errorf("RouteCounts JSON %s missing key %s", j, key) } } for _, key := range []string{`"Static"`, `"Resident"`, `"Hibernating"`, `"Awake"`, `"Asleep"`} { if strings.Contains(string(j), key) { t.Errorf("RouteCounts JSON %s carries capitalized Go field name %s", j, key) } } } // TestWriteBoxPutsUnderLabeledKey isn't in the plan's Step-1 listing, but // WriteBox is part of the exported contract Task 3 relies on — worth locking // down that it actually lands the box at the key CollectBox's label implies. func TestWriteBoxPutsUnderLabeledKey(t *testing.T) { dir := t.TempDir() store := release.NewDirStore(dir) b := status.CollectBox(nil, status.Probes{MachineID: "m"}, time.Now()) if err := status.WriteBox(context.Background(), store, b); err != nil { t.Fatalf("WriteBox: %v", err) } key := "status/boxes/" + b.Label + ".json" got, _, err := store.Get(context.Background(), key) if err != nil { t.Fatalf("Get %s: %v", key, err) } var round status.Box if err := json.Unmarshal(got, &round); err != nil { t.Fatalf("unmarshal written box: %v", err) } if round.Label != b.Label { t.Errorf("round-tripped label %q, want %q", round.Label, b.Label) } } // TestWriteBoxOverwritesOnSecondTick proves WriteBox is last-writer-wins for // a given label: boxes publish every 5 minutes to the same key, so a // create-only Put (which fails once the object exists) would break on the // second tick. Both calls must succeed, and the second call's content must // be what a reader sees afterward. func TestWriteBoxOverwritesOnSecondTick(t *testing.T) { dir := t.TempDir() store := release.NewDirStore(dir) ctx := context.Background() first := status.CollectBox(nil, status.Probes{MachineID: "m"}, time.Unix(1000, 0)) if err := status.WriteBox(ctx, store, first); err != nil { t.Fatalf("first WriteBox: %v", err) } second := status.CollectBox(nil, status.Probes{MachineID: "m"}, time.Unix(2000, 0)) if err := status.WriteBox(ctx, store, second); err != nil { t.Fatalf("second WriteBox (must overwrite, not conflict): %v", err) } key := "status/boxes/" + second.Label + ".json" got, _, err := store.Get(ctx, key) if err != nil { t.Fatalf("Get %s: %v", key, err) } var round status.Box if err := json.Unmarshal(got, &round); err != nil { t.Fatalf("unmarshal written box: %v", err) } if !round.GeneratedAt.Equal(second.GeneratedAt) { t.Errorf("stored box has GeneratedAt %v, want the second write's %v (second write must win)", round.GeneratedAt, second.GeneratedAt) } } // TestWriteBoxRetriesOnceOnCASConflict exercises the retry branch directly: // the first Put loses the CAS race (something else wrote between WriteBox's // Get and its Put), so WriteBox must re-read the etag and retry exactly // once — and that retry must succeed. func TestWriteBoxRetriesOnceOnCASConflict(t *testing.T) { dir := t.TempDir() real := release.NewDirStore(dir) flaky := &flakyPutStore{Store: real, failCount: 1} b := status.CollectBox(nil, status.Probes{MachineID: "m"}, time.Now()) if err := status.WriteBox(context.Background(), flaky, b); err != nil { t.Fatalf("WriteBox with one transient conflict: %v", err) } if flaky.putCalls != 2 { t.Errorf("Put called %d times, want exactly 2 (fail once, retry once)", flaky.putCalls) } key := "status/boxes/" + b.Label + ".json" if _, _, err := real.Get(context.Background(), key); err != nil { t.Fatalf("Get %s after retry: %v", key, err) } } // ---- Task 5: raw Metrics block (private to the bucket) -------------------- // TestCollectBoxCarriesRawMetrics proves CollectBox attaches an unbanded // Metrics block straight through from Probes when the six fields are known — // the one deliberate exception to this package's band-everything rule (see // snapshot.go's package doc). func TestCollectBoxCarriesRawMetrics(t *testing.T) { mt, ma, df := int64(4056547328), int64(2028273664), int64(9999999) l1 := 0.42 b := status.CollectBox(nil, status.Probes{ MachineID: "m", MemTotalBytes: &mt, MemAvailBytes: &ma, Load1: &l1, DiskFreeBytes: &df, Release: "abc1234", Tunnel: "connected", }, time.Now()) m := b.Metrics if m == nil || *m.MemTotalBytes != mt || *m.Load1 != l1 || m.Release != "abc1234" || m.Tunnel != "connected" { t.Fatalf("metrics block wrong: %+v", m) } } // TestCollectBoxOmitsMetricsWhenAllUnknown proves the all-unknown case // really omits the block rather than attaching an empty-but-present // BoxMetrics{} — the same "unknown stays absent" rule every other Box field // follows, and what makes an old parser's absent-key shape and a // can't-answer box's shape identical on the wire. func TestCollectBoxOmitsMetricsWhenAllUnknown(t *testing.T) { b := status.CollectBox(nil, status.Probes{MachineID: "m"}, time.Now()) if b.Metrics != nil { t.Fatalf("metrics block should be nil when nothing is known, got %+v", b.Metrics) } } // TestBoxBackCompatParse: yesterday's Box shape (no metrics key) must parse // today, and a FUTURE box's unknown keys (inside or alongside metrics) must // not break today's parse either — additive wire shape, per AGENTS.md's // "consumer = everything that PARSES the shape" rule. func TestBoxBackCompatParse(t *testing.T) { yesterday := []byte(`{"generated_at":"2026-08-03T10:00:00Z","label":"ada-stargazing", "routes":{"static":1,"resident":2,"hibernating":3,"awake":null,"asleep":null}, "load":"comfortable","capacity":"plenty", "replication":{"healthy":true,"lag_seconds":5}, "adoption_running":true,"expired_canaries":0}`) var b status.Box if err := json.Unmarshal(yesterday, &b); err != nil { t.Fatalf("yesterday's shape failed to parse: %v", err) } if b.Metrics != nil { t.Fatal("absent metrics must decode as nil, not zero-value") } tomorrow := []byte(`{"generated_at":"2026-08-05T10:00:00Z","label":"ada-stargazing", "routes":{"static":0,"resident":0,"hibernating":0,"awake":null,"asleep":null}, "load":null,"capacity":null,"replication":{"healthy":null,"lag_seconds":null}, "adoption_running":null,"expired_canaries":null, "metrics":{"mem_total_bytes":1,"some_future_field":true}}`) if err := json.Unmarshal(tomorrow, &b); err != nil { t.Fatalf("tomorrow's shape failed to parse: %v", err) } } // ---- Task 8: staged_version (route versions, private) ---------------------- // TestCollectBoxCarriesRouteVersions proves CollectBox attaches a host -> // Version map onto the raw Metrics block for every non-platform route that // carries a Version (registry.Route.Version, stamped by the adopt pass), and // that WriteBox lands it in status/boxes/