carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/registry"
12 "github.com/carlosframework/platform/internal/release"
13 "github.com/carlosframework/platform/internal/status"
14)
15
16// seedLayoutBox writes a box snapshot carrying a layout report at the given
17// store and label — the console-side fixture for a door that reads
18// status.LoadBoxes AND the customer-fleet boxes apisystemupdate.go's
19// systemBoxes already knows how to find (amendment R15, R21).
20func seedLayoutBox(t *testing.T, store release.Store, label string, at time.Time, report *status.LayoutReport) {
21 t.Helper()
22 b := status.CollectBox(nil, status.Probes{Release: "v1", Layout: report}, at)
23 b.Label = label
24 if err := status.WriteBox(context.Background(), store, b); err != nil {
25 t.Fatal(err)
26 }
27}
28
29// TestAPIStatusLayoutGate: operator only, the identical posture
30// handleAPIStatusInstances has (apistatusinstances.go:91-104) — a
31// non-operator gets the byte-identical 404 an unknown route gives, so the
32// refusal names neither the rows nor whether the caller holds the bit.
33func TestAPIStatusLayoutGate(t *testing.T) {
34 fx := newConsoleFixture(t)
35 token := cliToken(t, fx, "paul@keymail.dev", "code-apistatuslayout")
36 ghostStatus, ghostBody := getBearer(t, fx.ts.URL+"/this-api-route-does-not-exist", token)
37
38 if status, _ := getBearer(t, fx.ts.URL+"/api/cli/status/layout", ""); status != http.StatusUnauthorized {
39 t.Fatalf("unauthenticated = %d; want the CLI door's 401", status)
40 }
41 if status, body := getBearer(t, fx.ts.URL+"/api/cli/status/layout", token); status != ghostStatus || string(body) != string(ghostBody) {
42 t.Fatalf("non-operator = %d %q; want the ghost 404 (%d %q)", status, body, ghostStatus, ghostBody)
43 }
44
45 if err := fx.st.SetOperator("paul@keymail.dev", true); err != nil {
46 t.Fatal(err)
47 }
48 status, body := getBearer(t, fx.ts.URL+"/api/cli/status/layout", token)
49 if status != http.StatusOK {
50 t.Fatalf("operator = %d %q", status, body)
51 }
52 var out struct {
53 Boxes []apiStatusLayoutBox `json:"boxes"`
54 }
55 if err := json.Unmarshal(body, &out); err != nil {
56 t.Fatalf("unreadable: %v: %s", err, body)
57 }
58 if out.Boxes == nil {
59 t.Error("boxes decoded as null; want an empty array")
60 }
61}
62
63// TestAPIStatusLayoutListsBoxesRowsAndStale pins the door's shape: one
64// entry per box that has reported a layout, its rows, and Stale computed
65// from status.BoxStale the same way every other private reader gets it —
66// plus a box registered to a customer fleet, read from
67// fleets/<name>/status/boxes/ the way apisystemupdate.go's systemBoxes
68// already does (R15, R21).
69func TestAPIStatusLayoutListsBoxesRowsAndStale(t *testing.T) {
70 fx := newConsoleFixture(t)
71 token := cliToken(t, fx, "paul@keymail.dev", "code-apistatuslayoutrows")
72 if err := fx.st.SetOperator("paul@keymail.dev", true); err != nil {
73 t.Fatal(err)
74 }
75 now := time.Now()
76
77 seedLayoutBox(t, fx.rel, "fresh-box", now, &status.LayoutReport{
78 Mode: "nested", Default: true,
79 Rows: map[string]status.RowLayout{
80 "a.example.com": {Layout: "nested"},
81 "b.example.com": {Layout: "foreign", Reason: "db and socket are neither default"},
82 },
83 })
84 // Stale: past staleFactor(3)*SnapshotPeriod(5m) = 15 minutes dark.
85 seedLayoutBox(t, fx.rel, "stale-box", now.Add(-20*time.Minute), &status.LayoutReport{
86 Mode: "flat", DefaultBlocked: "isolation not ready: probe fixture absent",
87 Rows: map[string]status.RowLayout{"c.example.com": {Layout: "flat"}},
88 })
89
90 // A customer-fleet box: registered to the registry's fleet table, and
91 // its own snapshot written under the fleet's prefix — the shape
92 // systemBoxes reads, distinct from status/boxes/ (finding S14).
93 // A private handle, not openRegistry's shared one: since #208 the
94 // console's handle is process-owned and must not be closed by a caller.
95 reg, err := registry.Open(fx.regPath)
96 if err != nil {
97 t.Fatal(err)
98 }
99 t.Cleanup(func() { reg.Close() })
100 if err := reg.UpsertFleet(registry.Fleet{Name: "pi", Account: fx.sqid, Bucket: "pi-bucket"}); err != nil {
101 t.Fatal(err)
102 }
103 fleetStore := release.NewPrefixStore(fx.rel, "fleets/pi/")
104 seedLayoutBox(t, fleetStore, "fleet-box", now, &status.LayoutReport{
105 Mode: "flat",
106 Rows: map[string]status.RowLayout{"d.example.com": {Layout: "flat"}},
107 })
108
109 status, body := getBearer(t, fx.ts.URL+"/api/cli/status/layout", token)
110 if status != http.StatusOK {
111 t.Fatalf("GET /api/cli/status/layout = %d %q", status, body)
112 }
113 var out struct {
114 Boxes []apiStatusLayoutBox `json:"boxes"`
115 }
116 if err := json.Unmarshal(body, &out); err != nil {
117 t.Fatalf("unreadable: %v: %s", err, body)
118 }
119 if len(out.Boxes) != 3 {
120 t.Fatalf("boxes = %+v, want fresh-box, stale-box and fleet-box", out.Boxes)
121 }
122 byLabel := map[string]apiStatusLayoutBox{}
123 for _, b := range out.Boxes {
124 byLabel[b.Box] = b
125 }
126
127 fresh := byLabel["fresh-box"]
128 if !fresh.Default || fresh.DefaultBlocked != "" {
129 t.Errorf("fresh-box = %+v, want Default carried through from the snapshot", fresh)
130 }
131 if stale := byLabel["stale-box"]; stale.Default || stale.DefaultBlocked != "isolation not ready: probe fixture absent" {
132 t.Errorf("stale-box = %+v, want DefaultBlocked carried through", stale)
133 }
134 if !fresh.Mixed {
135 t.Errorf("fresh-box has a nested row beside a foreign one (uid carlos, no namespace) and is not marked mixed: %+v", fresh)
136 }
137 if fresh.Mode != "nested" || fresh.Stale {
138 t.Errorf("fresh-box = %+v", fresh)
139 }
140 if len(fresh.Rows) != 2 {
141 t.Fatalf("fresh-box rows = %+v", fresh.Rows)
142 }
143 byHost := map[string]apiStatusLayoutRow{}
144 for _, r := range fresh.Rows {
145 byHost[r.Host] = r
146 }
147 if byHost["a.example.com"].Layout != "nested" || byHost["a.example.com"].Reason != "" {
148 t.Errorf("a.example.com = %+v", byHost["a.example.com"])
149 }
150 if byHost["b.example.com"].Layout != "foreign" || byHost["b.example.com"].Reason == "" {
151 t.Errorf("b.example.com = %+v", byHost["b.example.com"])
152 }
153
154 stale := byLabel["stale-box"]
155 if stale.Mode != "flat" || !stale.Stale {
156 t.Errorf("stale-box = %+v, want Stale true", stale)
157 }
158
159 fleetBox := byLabel["fleet-box"]
160 if fleetBox.Mode != "flat" || len(fleetBox.Rows) != 1 || fleetBox.Rows[0].Host != "d.example.com" {
161 t.Errorf("fleet-box = %+v, want the fleet-prefixed snapshot to surface here", fleetBox)
162 }
163}
164
165// layoutMixed is the warning's rule: a nested row beside one that runs as
166// uid carlos with no namespace (flat, foreign or external). A box that is
167// all nested, or has no nested row, is not mixed.
168func TestLayoutMixedNeedsANestedAndAFlatRow(t *testing.T) {
169 rows := func(layouts ...string) []apiStatusLayoutRow {
170 var out []apiStatusLayoutRow
171 for i, l := range layouts {
172 out = append(out, apiStatusLayoutRow{Host: fmt.Sprintf("h%d.example.com", i), Layout: l})
173 }
174 return out
175 }
176 for _, tc := range []struct {
177 layouts []string
178 want bool
179 }{
180 {[]string{"nested", "flat"}, true},
181 {[]string{"flat", "fenced", "nested"}, true},
182 {[]string{"nested", "nested"}, false},
183 {[]string{"flat", "flat"}, false},
184 {[]string{"nested", "foreign"}, true},
185 {[]string{"nested", "external"}, true},
186 {[]string{"nested", "fenced", "conflict"}, false},
187 {[]string{"foreign", "external", "flat"}, false},
188 {nil, false},
189 } {
190 if got := layoutMixed("flat", rows(tc.layouts...)); got != tc.want {
191 t.Errorf("layoutMixed(flat, %v) = %v, want %v", tc.layouts, got, tc.want)
192 }
193 }
194 // Under a nested intent an unisolated row is mixed with or without a
195 // nested sibling; an all-nested or empty box is not.
196 for _, tc := range []struct {
197 layouts []string
198 want bool
199 }{
200 {[]string{"foreign", "external"}, true},
201 {[]string{"flat"}, true},
202 {[]string{"nested", "nested"}, false},
203 {[]string{"fenced"}, false},
204 {nil, false},
205 } {
206 if got := layoutMixed("nested", rows(tc.layouts...)); got != tc.want {
207 t.Errorf("layoutMixed(nested, %v) = %v, want %v", tc.layouts, got, tc.want)
208 }
209 }
210}
211