package main import ( "strings" "testing" "time" "github.com/novox/mesh-controller/internal/chain" "github.com/novox/mesh-controller/internal/inventory" ) // **A rollout lists the assignments it changed, each with the build before and after** (ADR 0408 §1, issue 1095): one // change for a machine and a module however often the send is noted, the build before kept from the first, the builds the // rollout carried once each, and one step for a machine and a tier. func TestARolloutListsTheAssignmentsItChangedWithTheBuildBeforeAndAfter(t *testing.T) { at := time.Date(2026, 10, 18, 3, 0, 0, 0, time.UTC) p := inventory.Plan{ID: "plan-1", Tier: 1, Modules: map[string]*inventory.PlanModule{ "notes": {Commit: "c2", Build: "b2"}, "wiki": {Commit: "c2w", Build: "b2w"}, "nothing": nil}} was := map[string]chain.Ref{"notes": {Build: "b1", Commit: "c1"}, "wiki": {Commit: "c1w"}} noteChanges(&p, "anchor", []string{"notes", "wiki", "nothing"}, was, at) noteChanges(&p, "laptop", []string{"notes"}, nil, at) // The send noted again, after what was sent had become what the machine runs: the first before stands. noteChanges(&p, "anchor", []string{"notes"}, map[string]chain.Ref{"notes": {Build: "b2", Commit: "c2"}}, at.Add(time.Minute)) want := []chain.Change{ {Node: "anchor", Module: "notes", Before: chain.Ref{Build: "b1", Commit: "c1"}, After: chain.Ref{Build: "b2", Commit: "c2"}}, {Node: "anchor", Module: "wiki", Before: chain.Ref{Commit: "c1w"}, After: chain.Ref{Build: "b2w", Commit: "c2w"}}, {Node: "laptop", Module: "notes", After: chain.Ref{Build: "b2", Commit: "c2"}}, } if len(p.Changed) != len(want) { t.Fatalf("changed %+v", p.Changed) } for k := range want { if p.Changed[k] != want[k] { t.Errorf("change %d is %+v, want %+v", k, p.Changed[k], want[k]) } } if len(p.Builds) != 2 || p.Builds[0] != "b2" || p.Builds[1] != "b2w" { t.Errorf("builds %v", p.Builds) } if len(p.Steps) != 2 || p.Steps[0].Node != "anchor" || p.Steps[0].Tier != 1 || p.Steps[0].Sent == nil || p.Steps[1].Node != "laptop" { t.Errorf("steps %+v", p.Steps) } lines := changedLines(p) if len(lines) != 4 || !strings.Contains(lines[1], "anchor/notes b1 (c1) → b2 (c2)") || !strings.Contains(lines[3], "laptop/notes nothing → b2 (c2)") { t.Errorf("changed lines %q", lines) } if got := changedLines(inventory.Plan{}); got != nil { t.Errorf("a plan that changed nothing says %q", got) } } func TestWhatAMachineRunsIsSaidAsItsAssignmentNamesIt(t *testing.T) { at := time.Date(2026, 10, 18, 3, 0, 0, 0, time.UTC) lines := runLines([]chain.Assignment{ {Node: "anchor", Module: "notes", Build: "b2", Commit: "c2c2c2c2c2c2", Rollout: "plan-1", Since: at}, {Node: "anchor", Module: "web", Commit: "c3"}, {Node: "anchor", Module: "never-sent"}, }) if len(lines) != 3 || !strings.Contains(lines[1], "notes") || !strings.Contains(lines[1], "b2 (c2c2c2c2)") || !strings.Contains(lines[1], "sent by plan-1") || !strings.Contains(lines[2], "web") || !strings.Contains(lines[2], "commit c3") { t.Fatalf("lines %q", lines) } if runLines([]chain.Assignment{{Node: "a", Module: "m"}}) != nil { t.Fatal("a module never sent is said to run something") } }