package main import ( "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" ) // What the forge's take compares, as a machine would report it. func aForgeComparison() comparison { return comparison{reported: inventory.Adoption{ Firewall: "ufw", Held: []inventory.Held{ {ID: "forge.server", Module: "forge", Kind: "container", Target: "forge", Facts: map[string]any{ "image": "forge:1.27.3", "image_created": "2026-09-17T10:00:00Z", "declared_image": "forge:1.22.6", "declared_image_created": "2026-08-20T10:00:00Z", "downgrade": true, "networks": map[string]any{"predecessor_default": []any{"office", "db"}}, "ports": []any{"3000/tcp>0.0.0.0:3000"}, "declared_ports": []any{"3000:3000"}, }}, {ID: "forge.config", Module: "forge", Kind: "file", Target: "/etc/forge/app.ini", Kept: "/var/lib/mesh/kept/app.ini", Facts: map[string]any{"differs": true, "difference": []any{"- private scope: local", "+ upstream: public"}}}, {ID: "other.server", Module: "other", Kind: "container", Target: "other"}, }, Reachable: []inventory.Reach{ {Protocol: "tcp", Address: "0.0.0.0", Port: 3000, By: "forge", Published: true, ContainerPort: 3000}, {Protocol: "tcp", Address: "0.0.0.0", Port: 22, By: "sshd"}, }, }} } // A take is a comparison (novox/hq ADR 0163): the preview puts what runs beside what the module // declares, and an older image or a differing file refuses unless named. func TestATakePreviewsTheComparisonAndRefusesWhatIsNotNamed(t *testing.T) { c := aForgeComparison() preview, refusals, saw := comparisonOf("forge", c, takeOptions{}) for _, want := range []string{"runs forge:1.27.3 (made 2026-09-17)", "declares forge:1.22.6 (made 2026-08-20)", "DOWNGRADE", "on the network predecessor_default with office, db", "will not once it moves to the module's own network", "publishes 3000/tcp>0.0.0.0:3000; the module declares 3000:3000", // How far the port reaches now, as the machine reported it (rule 1). "reachable now at 0.0.0.0:3000 (tcp, container port 3000), behind the found firewall (ufw)", "- private scope: local", "original kept at /var/lib/mesh/kept/app.ini"} { if !strings.Contains(preview, want) { t.Errorf("the preview lacks %q:\n%s", want, preview) } } if strings.Contains(preview, "other") { t.Errorf("another module's held things are in the preview:\n%s", preview) } if len(refusals) != 2 || !strings.Contains(refusals[0], "--downgrade") || !strings.Contains(refusals[1], "--replace /etc/forge/app.ini") { t.Fatalf("the downgrade and the differing file refuse, each naming its override: %v", refusals) } if len(saw) != 12 { t.Fatalf("the preview's digest is %q", saw) } // Named, they pass. if _, refusals, _ := comparisonOf("forge", c, takeOptions{Downgrade: true, Replace: map[string]bool{"/etc/forge/app.ini": true}}); len(refusals) != 0 { t.Fatalf("named differences still refused: %v", refusals) } if _, refusals, _ := comparisonOf("forge", c, takeOptions{Downgrade: true, Replace: map[string]bool{"*": true}}); len(refusals) != 0 { t.Fatalf("replace * did not cover the file: %v", refusals) } // A held thing with no facts yet — a host older than this — refuses nothing and says what it can. if preview, refusals, _ := comparisonOf("other", c, takeOptions{}); len(refusals) != 0 || !strings.Contains(preview, "container other") { t.Fatalf("a factless hold: %q %v", preview, refusals) } // The digest is of what the preview says: a fact changing changes it. c.reported.Held[0].Facts["image"] = "forge:1.27.4" if _, _, again := comparisonOf("forge", c, takeOptions{}); again == saw { t.Fatal("the found image changed and the digest did not") } } // A secret the mesh minted for a service whose data was found refuses: the running service already // has a value (rule 2). Accepted, it is carried in; `--mint` says the service shall take the new one. func TestAMintedSecretForFoundDataRefusesUnlessAcceptedOrMinted(t *testing.T) { c := aForgeComparison() c.secrets = []inventory.SecretState{ {Name: "admin", Origin: inventory.OriginMade}, {Name: "postgres-database", Origin: inventory.OriginMade, Provider: "anchor"}, {Name: "broker", Origin: inventory.OriginAccepted}, } preview, refusals, _ := comparisonOf("forge", c, takeOptions{Downgrade: true, Replace: map[string]bool{"*": true}}) for _, want := range []string{ "own secret admin: MINTED by the mesh and not accepted", "secret from anchor postgres-database: MINTED by the mesh and not accepted", "own secret broker: accepted from a person, carried in as it is", } { if !strings.Contains(preview, want) { t.Errorf("the preview lacks %q:\n%s", want, preview) } } if len(refusals) != 2 { t.Fatalf("two minted secrets refuse: %v", refusals) } if !strings.Contains(refusals[0], "`secret accept forge admin`") || !strings.Contains(refusals[0], "`--mint admin`") { t.Errorf("the own secret's refusal names accepting it and minting it: %s", refusals[0]) } if !strings.Contains(refusals[1], "`secret accept forge postgres-database --provider anchor`") { t.Errorf("the required secret's refusal names its provider: %s", refusals[1]) } preview, refusals, _ = comparisonOf("forge", c, takeOptions{Downgrade: true, Replace: map[string]bool{"*": true}, Mint: map[string]bool{"admin": true, "postgres-database": true}}) if len(refusals) != 0 || !strings.Contains(preview, "admin: minted by the mesh; the service takes the new value, as --mint said") { t.Fatalf("--mint did not pass the minted secrets: %v\n%s", refusals, preview) } // With no found data — only a file held — the service has no value of its own, and a minted // secret is simply said. c.reported.Held = c.reported.Held[1:2] if _, refusals, _ := comparisonOf("forge", c, takeOptions{Replace: map[string]bool{"*": true}}); len(refusals) != 0 { t.Fatalf("a minted secret refused with no data found: %v", refusals) } } // A found network a per-machine setting keeps is named in the preview (rule 4), and the module's // settings are said with where each came from, composed or not (rules 1 and 6). func TestTheKeptNetworkAndTheSettingsAreInThePreview(t *testing.T) { c := aForgeComparison() c.keeps = map[string][]string{"forge.server": {"predecessor_default"}} c.layers = []catalogue.Layer{ {From: catalogue.MeshWideLayer, Values: map[string]any{"site": "x"}}, {From: "anchor", Values: map[string]any{catalogue.NetworksSetting: map[string]any{"server": []any{"predecessor_default"}}}}, } preview, _, _ := comparisonOf("forge", c, takeOptions{Downgrade: true, Replace: map[string]bool{"*": true}}) for _, want := range []string{ "on the network predecessor_default with office, db — kept by this machine's setting, so they still reach it by name once taken", "settings from the mesh: site", "settings from anchor: networks", } { if !strings.Contains(preview, want) { t.Errorf("the preview lacks %q:\n%s", want, preview) } } if strings.Contains(preview, "will not once it moves") { t.Errorf("a kept network is still said to be lost:\n%s", preview) } c.settingsRefused = "forge: ports is a { port: machine-port } map" preview, _, _ = comparisonOf("forge", c, takeOptions{Downgrade: true, Replace: map[string]bool{"*": true}}) if !strings.Contains(preview, "SETTINGS DO NOT COMPOSE with the module's definition, so the push leaves it out: forge: ports") { t.Errorf("settings that cannot compose are not said:\n%s", preview) } }