Only gated sends said which containers a move recreates; a push moved mail to a new build and recreated a container with a new image without a word. The push now lists, per machine, every module it moves and what that recreates, with the same Recreates the gated send uses.
85 lines
3.0 KiB
Go
85 lines
3.0 KiB
Go
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())
|
|
}
|
|
}
|