package main import ( "bytes" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/link" ) // The live case of 2026-10-07, 20:27 UTC: a person's `push` of one machine moved mail to a new build whose // automx container had a new image, and recreated it; the push's answer said nothing of it. The push now // says, per machine, every module it moves and what that recreates — before it is sent. A module whose // build did not change, and one new to the machine, are not moves. func TestAPushSaysWhatItRecreates(t *testing.T) { open := aMesh(t) ctx := t.Context() inv := open.inventory start := time.Now().Add(-time.Hour) image := func(c string) string { return "registry.invalid:5000/mailu/x@sha256:" + strings.Repeat(c, 64) } for _, b := range []link.BuildResult{ aContainerBuild(t, "mailu", "c74f407a", catalogue.PolicyRecord, start, map[string][2]string{"smtp": {image("a"), ""}, "automx": {image("c"), ""}}), aContainerBuild(t, "postgres", "c1111111", "", start.Add(time.Second), map[string][2]string{"server": {image("e"), ""}}), } { if _, _, err := takeIn(ctx, inv, b); err != nil { t.Fatal(err) } } for _, m := range []string{"mailu", "postgres"} { if _, err := inv.Assign(ctx, "anchor", m); err != nil { t.Fatal(err) } } if err := inv.RecordSent(ctx, nodeID(t, open, "anchor"), "d-anchor", map[string]string{"mailu": "c74f407a", "postgres": "c1111111"}); err != nil { t.Fatal(err) } // Mail's new build: automx's image changes, smtp's does not. if _, _, err := takeIn(ctx, inv, aContainerBuild(t, "mailu", "1ab84e69", catalogue.PolicyRecord, start.Add(time.Minute), map[string][2]string{"smtp": {image("a"), ""}, "automx": {image("d"), ""}})); err != nil { t.Fatal(err) } // Composed as a person's push composes it. plan, settings, err := planFor(ctx, open, "anchor") if err != nil { t.Fatal(err) } gens, err := generators(ctx, open) if err != nil { t.Fatal(err) } declared, err := declarationWith(ctx, open, "anchor", plan, settings, gens, Allocating) if err != nil { t.Fatal(err) } if declared.Builds["mailu"] != "1ab84e69" { t.Fatalf("a person's push carries mail at %q", declared.Builds["mailu"]) } var said bytes.Buffer sayWhatAPushRecreates(ctx, open, []readyNode{{node: "anchor", declared: declared}}, &said) out := said.String() for _, want := range []string{"anchor moves 1 module(s)", "mailu", "c74f407a → 1ab84e69", "recreates 1 of mailu's 2 container(s) (with a new image: automx)"} { if !strings.Contains(out, want) { t.Fatalf("the push says %q; it does not say %q", out, want) } } if strings.Contains(out, "postgres") { t.Fatalf("a module whose build did not change is said to move: %q", out) } // A machine never sent anything before has no moves to say. said.Reset() sayWhatAPushRecreates(ctx, open, []readyNode{{node: "laptop", declared: declared}}, &said) if said.Len() != 0 { t.Fatalf("a machine with no last send is said to move: %q", said.String()) } }