package judge import ( "slices" "strings" "testing" "time" "git.novox.be/novox/mesh-sdk/go/delivery" ) // ADR 0567 §3, §4 (rows moved from the assigner's check of a pull request's placement, ADR 0559 §5): what a proposal // changes by node, and the verdict of the answers: differences shown and failing nothing, a refusal and a silent node // failing, a later answer judging again, the seed failing on any difference. func TestJudge(t *testing.T) { current := map[string]delivery.Placement{"anchor": {Node: "anchor", Modules: []delivery.PlacedModule{{Module: "mail"}}}, "server": {Node: "server"}} laid := map[string]*delivery.Placement{ "anchor": {Node: "anchor", Modules: []delivery.PlacedModule{{Module: "mail", Ports: map[string]int{"25": 2525}}, {Module: "web"}}}, "laptop": {Node: "laptop", Modules: []delivery.PlacedModule{{Module: "web"}}}} got, must, _ := Intended(nil, nil, current, laid, []string{"anchor", "laptop", "server"}, nil) if !got["anchor"]["mail"] || !got["anchor"]["web"] || len(got["laptop"]) != 0 || !slices.Equal(must, []string{"anchor", "laptop"}) { t.Errorf("intended = %v, must %v: want anchor's mail and web, nothing on the laptop that has no key, server untouched", got, must) } at := time.Date(2026, 10, 27, 2, 0, 0, 0, time.UTC) p := delivery.PlacementProposal{ID: "p-one", By: "op", Why: "move web", Written: at, Base: map[string]uint64{"anchor": 3, "laptop": 0}, Must: []string{"anchor", "laptop"}} revisions := map[string]uint64{"anchor": 7, "laptop": 8} intended := map[string]map[string]bool{"anchor": {"web": true}} same := func(rev uint64) *delivery.CandidateAnswer { return &delivery.CandidateAnswer{Proposal: "p-one", Revision: rev, Verdict: delivery.CandidateSame, Same: []string{"mail"}} } differs := func(rev uint64, module string) *delivery.CandidateAnswer { return &delivery.CandidateAnswer{Proposal: "p-one", Revision: rev, Verdict: delivery.CandidateDiffers, Differs: []delivery.ModuleDiffers{{Module: module, Why: "the port differs"}}} } for _, row := range []struct { name string seed bool answers map[string]*delivery.CandidateAnswer pulsing map[string]bool state string words string marked string }{ {"all the same", false, map[string]*delivery.CandidateAnswer{"anchor": same(7), "laptop": same(8)}, nil, delivery.VerdictPass, "", ""}, {"a module added where intended", false, map[string]*delivery.CandidateAnswer{"anchor": {Proposal: "p-one", Revision: 7, Verdict: delivery.CandidateDiffers, Added: []delivery.ModuleAdded{{Module: "web", Unissued: []string{"its own secret"}}}}, "laptop": same(8)}, nil, delivery.VerdictPass, "", ""}, {"a difference not computed passes, marked", false, map[string]*delivery.CandidateAnswer{"anchor": differs(7, "mail"), "laptop": same(8)}, nil, delivery.VerdictPass, "", "mail"}, {"the seed carries a difference, named", true, map[string]*delivery.CandidateAnswer{"anchor": same(7), "laptop": {Proposal: "p-one", Revision: 8, Verdict: delivery.CandidateDiffers, Differs: []delivery.ModuleDiffers{{Module: "node-tools", Resource: "node-tools.runtime", Why: "the resource differs", Bodies: []delivery.ResourceBodies{{ID: "node-tools.runtime", Paths: []string{"env.MESH_OWNED_PATHS./etc/resolv.conf", "env.MESH_TOOL_MODULES"}, Running: `{"env":{"MESH_OWNED_PATHS":"{\"/etc/hosts\":\"hostname\",\"/etc/resolv.conf\":\"networkmanager (node-uplink)\"}"}}`, Composed: `{"env":{"MESH_OWNED_PATHS":"{\"/etc/hosts\":\"hostname\",\"/etc/resolv.conf\":\"systemd-resolved (node-resolver)\"}","MESH_TOOL_MODULES":"… (cut at 8192 bytes)`}}}}}}, nil, delivery.VerdictPass, "", ""}, {"a refusal", false, map[string]*delivery.CandidateAnswer{"anchor": same(7), "laptop": {Proposal: "p-one", Revision: 8, Verdict: delivery.CandidateRefused, Refusals: []delivery.ModuleRefused{{Module: "web", Why: "no own secret"}}}}, nil, delivery.VerdictFail, "laptop refuses", ""}, {"a node not answering, within its bound", false, map[string]*delivery.CandidateAnswer{"anchor": same(7)}, map[string]bool{"laptop": true}, delivery.VerdictWaits, "laptop (offline)", ""}, {"an answer of an earlier revision, past its bound", false, map[string]*delivery.CandidateAnswer{"anchor": same(6), "laptop": same(8)}, nil, delivery.VerdictFail, "anchor did not answer", ""}, {"a module held as today passes, shown in the brief", false, map[string]*delivery.CandidateAnswer{"anchor": same(7), "laptop": {Proposal: "p-one", Revision: 8, Verdict: delivery.CandidateSame, Held: []delivery.ModuleRefused{{Module: "spiegel", Why: "a required value is unset"}}}}, nil, delivery.VerdictPass, "", ""}, {"its later answer", false, map[string]*delivery.CandidateAnswer{"anchor": same(7), "laptop": same(8)}, nil, delivery.VerdictPass, "", ""}, } { q := p q.Seed = row.seed v := JudgeAnswers(q, intended, row.answers, revisions, map[string]bool{"anchor": true}, row.pulsing, at) all := strings.Join(append(append([]string{}, v.Fails...), v.Notes...), "; ") if v.State != row.state || !strings.Contains(all, row.words) { t.Errorf("%s: %s, %q", row.name, v.State, all) } if row.seed && row.state == delivery.VerdictPass && !strings.Contains(Brief(q, v), "the seed carries: node-tools: MESH_OWNED_PATHS[/etc/resolv.conf] networkmanager (node-uplink) → (value cut) (and 1 more fields)") { t.Errorf("%s: the brief does not name it: %s", row.name, Brief(q, v)) } if row.seed && row.state == delivery.VerdictPass { whole := row.answers["laptop"].Differs[0] whole.Bodies = []delivery.ResourceBodies{{ID: "r", Paths: []string{"env.MESH_OWNED_PATHS./etc/resolv.conf"}, Running: `{"env":{"MESH_OWNED_PATHS":"{\"/etc/resolv.conf\":\"networkmanager (node-uplink)\"}"}}`, Composed: `{"env":{"MESH_OWNED_PATHS":"{\"/etc/resolv.conf\":\"systemd-resolved (node-resolver)\"}"}}`}} if got := differenceWords(whole); got != "node-tools: MESH_OWNED_PATHS[/etc/resolv.conf] networkmanager (node-uplink) → systemd-resolved (node-resolver)" { t.Errorf("the difference is named %q", got) } } if strings.HasPrefix(row.name, "a module held as today") && !strings.Contains(Brief(q, v), "stays as today: spiegel (held)") { t.Errorf("%s: the brief does not show it: %s", row.name, Brief(q, v)) } if row.marked != "" && (!slices.Contains(v.Unintended["anchor"], row.marked) || !strings.Contains(Brief(q, v), "not computed: "+row.marked)) { t.Errorf("%s: the difference is not marked: %+v, %s", row.name, v.Unintended, Brief(q, v)) } } }