| index f8f397952..2e5bb2f46 100644 |
| --- a/internal/host/agent_layout_test.go |
| +++ b/internal/host/agent_layout_test.go |
| @@ -454,3 +454,97 @@ func TestSyncExecSerializedSkipsAHostWithALayoutJournal(t *testing.T) { |
| t.Fatalf("sibling bundle = %q, %v", b, err) |
| } |
| } |
| + |
| +// TestLayoutStatusReportWithNoPassArmedIsNil pins the snapshot writer's |
| +// omission rule (R15): with no activator, and so no layout pass, there is |
| +// nothing to move and nothing to report — the status snapshot must omit the |
| +// block entirely rather than guess at a mode. |
| +func TestLayoutStatusReportWithNoPassArmedIsNil(t *testing.T) { |
| + resetLayoutGlobals(t) |
| + if got := layoutStatusReport(nil); got != nil { |
| + t.Fatalf("report = %+v, want nil with no pass armed", got) |
| + } |
| +} |
| + |
| +// TestLayoutStatusReportModeFollowsTheLastIntentSeen pins the three-way |
| +// Mode mapping the amendment (R15) puts on the report, distinct from the |
| +// brief's original two-word doc: "none" before any intent is recorded, the |
| +// mode itself once one is, and "unreadable" when the last read of it |
| +// failed — never silently downgraded to "none", which would read as "this |
| +// box wants flat" when the box in fact could not tell. |
| +func TestLayoutStatusReportModeFollowsTheLastIntentSeen(t *testing.T) { |
| + resetLayoutGlobals(t) |
| + dd, sd := t.TempDir(), t.TempDir() |
| + armedLayoutPass.Store(&layout.Pass{DataDir: dd, SocketDir: sd}) |
| + |
| + if got := layoutStatusReport(nil); got == nil || got.Mode != "none" { |
| + t.Fatalf("mode with no intent seen = %+v, want none", got) |
| + } |
| + noteLayoutIntent(release.LayoutModeFlat) |
| + if got := layoutStatusReport(nil); got == nil || got.Mode != "flat" { |
| + t.Fatalf("mode after a flat intent = %+v, want flat", got) |
| + } |
| + noteLayoutIntent(release.LayoutModeNested) |
| + if got := layoutStatusReport(nil); got == nil || got.Mode != "nested" { |
| + t.Fatalf("mode after a nested intent = %+v, want nested", got) |
| + } |
| + noteLayoutIntent(string(layout.IntentUnreadable)) |
| + if got := layoutStatusReport(nil); got == nil || got.Mode != "unreadable" { |
| + t.Fatalf("mode after an unreadable read = %+v, want unreadable", got) |
| + } |
| +} |
| + |
| +// TestLayoutStatusReportRowsCoverOnlyInstanceKindRoutes pins R15's row |
| +// selection: an ActivatorManaged row goes through the pass's own Classify |
| +// (proven here by a row shaped to classify as foreign, so the reason comes |
| +// through too), an unflagged socket instance route reports "external", and |
| +// everything else — a sidecar, a static route, a service route, and an |
| +// instance route with no socket at all — carries no row, because the |
| +// report is not the place any of those are decided. |
| +func TestLayoutStatusReportRowsCoverOnlyInstanceKindRoutes(t *testing.T) { |
| + resetLayoutGlobals(t) |
| + dd, sd := t.TempDir(), t.TempDir() |
| + armedLayoutPass.Store(&layout.Pass{DataDir: dd, SocketDir: sd}) |
| + noteLayoutIntent(release.LayoutModeNested) |
| + |
| + routes := []registry.Route{ |
| + // ActivatorManaged, but its paths match neither default — Classify |
| + // must be the thing deciding this, not a guess made here. |
| + {Host: "managed.example.com", Kind: "instance", Hibernate: true, |
| + Socket: "/elsewhere/managed.sock", DBPath: "/elsewhere/managed.db", Status: "ready"}, |
| + // Hibernate=false with a socket: externally supervised. |
| + {Host: "external.example.com", Kind: "instance", Hibernate: false, Socket: "/elsewhere/external.sock"}, |
| + // A sidecar, explicitly out of scope (R15, finding S6). |
| + {Host: "side.example.com", Kind: "sidecar"}, |
| + // Not instance-kind at all. |
| + {Host: "static.example.com", Kind: "static"}, |
| + {Host: "svc.example.com", Kind: "service"}, |
| + // Instance-kind but no socket and not hibernate-flagged — neither |
| + // managed nor the "unflagged socket" shape. |
| + {Host: "nosock.example.com", Kind: "instance"}, |
| + } |
| + |
| + report := layoutStatusReport(routes) |
| + if report == nil { |
| + t.Fatal("report = nil, want one with a pass armed") |
| + } |
| + if report.Mode != "nested" { |
| + t.Errorf("mode = %q, want nested", report.Mode) |
| + } |
| + if len(report.Rows) != 2 { |
| + t.Fatalf("rows = %+v, want exactly managed and external", report.Rows) |
| + } |
| + managed := report.Rows["managed.example.com"] |
| + if managed.Layout != "foreign" || managed.Reason == "" { |
| + t.Errorf("managed row = %+v, want foreign with a reason from Classify", managed) |
| + } |
| + external := report.Rows["external.example.com"] |
| + if external.Layout != "external" || external.Reason != "" { |
| + t.Errorf("external row = %+v, want external with no reason", external) |
| + } |
| + for _, host := range []string{"side.example.com", "static.example.com", "svc.example.com", "nosock.example.com"} { |
| + if _, ok := report.Rows[host]; ok { |
| + t.Errorf("%s has a row, want none", host) |
| + } |
| + } |
| +} |