package delivery import ( "errors" "strings" "testing" "time" ) // 1297/B1a: a want key round-trips through its parser; a held key, a have key and a node's own key are refused, // naming the key. func TestWantKey(t *testing.T) { key, err := WantKey("ace", "mesh-mail") if err != nil || key != "want.ace.mesh-mail" { t.Fatalf("WantKey = %q, %v", key, err) } node, module, err := ParseWantKey(key) if err != nil || node != "ace" || module != "mesh-mail" { t.Errorf("ParseWantKey = %q, %q, %v", node, module, err) } for _, n := range [][2]string{{"ace", "node"}, {"a.b", "m"}, {"ace", ""}, {"", "m"}, {"ace", "m*"}} { if _, err := WantKey(n[0], n[1]); err == nil { t.Errorf("WantKey(%q, %q) accepted", n[0], n[1]) } } held, _ := HeldKey("mesh-mail", fp2of('a')) for _, bad := range []string{"ace.mesh-mail", "want.ace.node", "want.ace", "want.ace.m.x", held, "wants.ace.m", ""} { if _, _, err := ParseWantKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("ParseWantKey(%q) = %v, want a refusal naming it", bad, err) } } } // 1297/B1a: a held key round-trips; a want key, a builder build key and a fingerprint not of the new form are refused. func TestHeldKey(t *testing.T) { fp := fp2of('b') key, err := HeldKey("mesh-mail", fp) if err != nil || key != "held.mesh-mail."+strings.Repeat("b", 64) { t.Fatalf("HeldKey = %q, %v", key, err) } module, back, err := ParseHeldKey(key) if err != nil || module != "mesh-mail" || back != fp { t.Errorf("ParseHeldKey = %q, %q, %v", module, back, err) } src1, _ := SourceFingerprint(SourceInputs{Module: "m", Tree: "t"}) if _, err := HeldKey("mesh-mail", src1); err == nil { t.Errorf("a src1 fingerprint was held") } build, _ := BuilderBuildKey("mesh-mail", fp) for _, bad := range []string{build, "want.ace.mesh-mail", "held.mesh-mail.abc", "held." + strings.Repeat("b", 64), ""} { if _, _, err := ParseHeldKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("ParseHeldKey(%q) = %v, want a refusal naming it", bad, err) } } } // 1297/B1a: a difference key round-trips; a want or held key and a node's own key are refused. func TestDifferenceKey(t *testing.T) { key, err := DifferenceKey("g14", "mesh-mail") if err != nil || key != "g14.mesh-mail" { t.Fatalf("DifferenceKey = %q, %v", key, err) } node, module, err := ParseDifferenceKey(key) if err != nil || node != "g14" || module != "mesh-mail" { t.Errorf("ParseDifferenceKey = %q, %q, %v", node, module, err) } held, _ := HeldKey("mesh-mail", fp2of('a')) for _, bad := range []string{"want.g14.mesh-mail", held, "g14.node", "g14", ""} { if _, _, err := ParseDifferenceKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("ParseDifferenceKey(%q) = %v, want a refusal naming it", bad, err) } } } // 1297/B1a: wants, holds and differences round-trip through their encoders unchanged, at Version. func TestAssignerValuesRoundTrip(t *testing.T) { wants := []Want{ {FP: fp2of('a'), Composed: hashOf('1')}, {FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: RuleCanary, Words: "the canary ace does not run it healthy yet"}, {Removed: true, Words: "the module left g14"}, {FP: fp2of('a'), Composed: hashOf('1'), Put: true, Hold: true, Words: "put by the operator: the bus's user list"}, } for _, w := range wants { data, err := EncodeWant(w) if err != nil { t.Fatalf("EncodeWant(%+v): %v", w, err) } got, err := DecodeWant(data) w.V = Version if err != nil || !SameWant(got, w) { t.Errorf("want %+v came back %+v, %v", w, got, err) } } at := time.Date(2026, 10, 25, 21, 0, 0, 0, time.UTC) p := Put{FP: fp2of('a'), Hold: true, Why: "the bus's user list", By: "shanks.node-tools", At: at}.Said(at, PutAsked, "put asked") p = p.Said(at, PutAsked, "put asked").Said(at.Add(time.Second), PutWanted, "want written") p.Composed = hashOf('1') if len(p.Steps) != 2 || p.Step != PutWanted || PutEnded(p.Step) || !PutEnded(PutReleased) { t.Errorf("a put's steps are %+v at %s", p.Steps, p.Step) } pkey, _ := PutKey("novox", "nats") if n, m, err := ParsePutKey(pkey); err != nil || n != "novox" || m != "nats" { t.Errorf("the put key %s reads %s %s, %v", pkey, n, m, err) } pdata, err := EncodePut(p) if err != nil { t.Fatal(err) } if gotP, err := DecodePut(pdata); err != nil || gotP.FP != p.FP || gotP.Step != PutWanted || len(gotP.Steps) != 2 || !gotP.Hold { t.Errorf("the put came back %+v, %v", gotP, err) } h := Held{Node: "ace", Words: "its health check failed: connection refused"} data, err := EncodeHeld(h) if err != nil { t.Fatal(err) } gotH, err := DecodeHeld(data) h.V = Version if err != nil || !SameHeld(gotH, h) { t.Errorf("held came back %+v, %v", gotH, err) } diffs := []Difference{ {Want: fp2of('a'), Sent: fp2of('b'), Rule: RuleCanary}, {Sent: fp2of('b'), Rule: RuleBuildDone}, {WantRemoved: true, Sent: fp2of('b'), Unexplained: true}, {SentBuild: "build-17", Rule: RuleGrant}, } for _, d := range diffs { data, err := EncodeDifference(d) if err != nil { t.Fatalf("EncodeDifference(%+v): %v", d, err) } got, err := DecodeDifference(data) d.V = Version if err != nil || !SameDifference(got, d) { t.Errorf("difference %+v came back %+v, %v", d, got, err) } } } // 1297/B1a: what a want, a hold and a difference may not say is refused on encode and on decode. func TestAssignerValuesRefused(t *testing.T) { src1, _ := SourceFingerprint(SourceInputs{Module: "m", Tree: "t"}) for _, w := range []Want{ {}, {FP: src1}, {Removed: true, FP: fp2of('a')}, {FP: fp2of('a'), Newest: fp2of('b')}, {FP: fp2of('a'), Newest: fp2of('a'), Rule: 3}, {FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: 8}, {FP: fp2of('a'), Composed: hashOf('1'), Hold: true}, {FP: fp2of('a'), Composed: hashOf('1'), Put: true, Newest: fp2of('b'), Rule: 3}, {Removed: true, Put: true}, } { if _, err := EncodeWant(w); err == nil { t.Errorf("EncodeWant(%+v) accepted", w) } } if _, err := EncodeHeld(Held{Node: "a.b"}); err == nil { t.Errorf("a held naming a dotted node was accepted") } for _, d := range []Difference{ {Want: fp2of('a'), Sent: fp2of('b')}, {Want: fp2of('a'), Sent: fp2of('b'), Rule: 3, Unexplained: true}, {Want: fp2of('a'), Sent: fp2of('a'), Rule: 3}, {}, {Want: fp2of('a'), Sent: fp2of('b'), Rule: 9}, {WantRemoved: true, Want: fp2of('a'), Rule: 1}, } { if _, err := EncodeDifference(d); err == nil { t.Errorf("EncodeDifference(%+v) accepted", d) } } for _, p := range []Put{ {FP: fp2of('a'), Why: "w", Step: "walking"}, {FP: fp2of('a'), Step: PutAsked}, {FP: "src2:abc", Why: "w", Step: PutAsked}, {FP: fp2of('a'), Why: "w", Step: PutWanted, Composed: "sha256:x"}, } { if _, err := EncodePut(p); err == nil { t.Errorf("EncodePut(%+v) accepted", p) } } if _, _, err := ParsePutKey("ace.node"); err == nil { t.Errorf("a node's own key was read as a put") } if _, err := DecodeWant([]byte(`{"v":2,"fp":"` + fp2of('a') + `"}`)); !errors.Is(err, ErrUnknownVersion) { t.Errorf("a want of version 2 was read: %v", err) } if _, err := DecodeDifference([]byte(`{"v":1,"want":"` + fp2of('a') + `","sent":"` + fp2of('b') + `"}`)); err == nil { t.Errorf("a difference naming no rule was read") } } // 1297/B1b: a composition key is the checker's verdict key of the same check, and round-trips; a want, held or // difference key is refused, naming it. func TestCompositionKey(t *testing.T) { key, err := CompositionKey("feat/r4g", "0123456789ab") verdict, _ := CheckerVerdictKey("feat/r4g", "0123456789ab") if err != nil || key != verdict { t.Fatalf("CompositionKey = %q, %v; the checker's key is %q", key, err, verdict) } group, heads, err := ParseCompositionKey(key) if err != nil || group != "feat/r4g" || heads != "0123456789ab" { t.Errorf("ParseCompositionKey = %q, %q, %v", group, heads, err) } held, _ := HeldKey("mesh-mail", fp2of('a')) for _, bad := range []string{"want.ace.mesh-mail", held, "ace.mesh-mail", "g.0123456789AB", ""} { if _, _, err := ParseCompositionKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("ParseCompositionKey(%q) = %v, want a refusal naming it", bad, err) } } } // 1297/B1b: a composition in each state round-trips; the checker's composition of a pass or fail carries its state, // words, mesh-sdk commit and machines, and a wait gives none. func TestCompositionVerdictRoundTrip(t *testing.T) { for _, c := range []CompositionVerdict{ {Group: "g", Heads: "0123456789ab", State: CompositionPass, SDK: "7aacbc1", Machines: []string{"ace"}, Took: 120}, {Group: "g", Heads: "0123456789ab", State: CompositionFail, SDK: "7aacbc1", Machines: []string{"ace", "g14"}, Machine: "g14", Words: "g14 runs mesh-mail, which the change removes from its source", Warnings: []string{"the merge rebuilds 13 modules"}}, {Group: "g", Heads: "0123456789ab", State: CompositionWaits, Words: "the indexer holds no candidates for the group yet"}, } { data, err := EncodeCompositionVerdict(c) if err != nil { t.Fatalf("EncodeCompositionVerdict(%+v): %v", c, err) } got, err := DecodeCompositionVerdict(data) c.V = Version if err != nil || !SameCompositionVerdict(got, c) { t.Errorf("composition %+v came back %+v, %v", c, got, err) } comp := got.Composition() if c.State == CompositionWaits { if comp != nil { t.Errorf("a wait gave the checker a composition %+v", comp) } continue } if comp == nil || comp.State != c.State || comp.SDK != c.SDK || comp.Words != c.Words || len(comp.Machines) != len(c.Machines) { t.Errorf("the checker's composition of %+v is %+v", c, comp) } if err := comp.check(); err != nil { t.Errorf("the checker's composition of %+v is refused by the verdict: %v", c, err) } } } // 1297/B1b: what a composition may not say is refused. func TestCompositionVerdictRefused(t *testing.T) { for _, c := range []CompositionVerdict{ {Heads: "0123456789ab", State: CompositionPass, SDK: "x"}, {Group: "g", Heads: "0123", State: CompositionPass, SDK: "x"}, {Group: "g", Heads: "0123456789ab", State: CompositionPass}, {Group: "g", Heads: "0123456789ab", State: CompositionPass, SDK: "x", Machine: "ace"}, {Group: "g", Heads: "0123456789ab", State: CompositionFail, SDK: "x"}, {Group: "g", Heads: "0123456789ab", State: CompositionFail, Words: "w"}, {Group: "g", Heads: "0123456789ab", State: CompositionWaits}, {Group: "g", Heads: "0123456789ab", State: CompositionWaits, Words: "w", Machines: []string{"ace"}}, {Group: "g", Heads: "0123456789ab", State: CompositionNotRun}, {Group: "g", Heads: "0123456789ab", State: CompositionPass, SDK: "x", Took: -1}, } { if _, err := EncodeCompositionVerdict(c); err == nil { t.Errorf("EncodeCompositionVerdict(%+v) accepted", c) } } } // 1394/B1b: a grant key round-trips; a want or held key is refused, naming it; a grant round-trips and one naming an // empty account is refused. func TestControllerGrant(t *testing.T) { fp := fp2of('c') key, err := ControllerGrantKey("mesh-delivery-assigner", fp) if err != nil || key != "mesh-delivery-assigner."+strings.Repeat("c", 64) { t.Fatalf("ControllerGrantKey = %q, %v", key, err) } module, back, err := ParseControllerGrantKey(key) if err != nil || module != "mesh-delivery-assigner" || back != fp { t.Errorf("ParseControllerGrantKey = %q, %q, %v", module, back, err) } held, _ := HeldKey("mesh-mail", fp) for _, bad := range []string{"want.ace.mesh-mail", held, "ace.mesh-mail", ""} { if _, _, err := ParseControllerGrantKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("ParseControllerGrantKey(%q) = %v, want a refusal naming it", bad, err) } } for _, g := range []ControllerGrant{{}, {Accounts: []string{"UABC", "UDEF"}}} { data, err := EncodeControllerGrant(g) if err != nil { t.Fatal(err) } got, err := DecodeControllerGrant(data) g.V = Version if err != nil || !SameControllerGrant(got, g) { t.Errorf("grant %+v came back %+v, %v", g, got, err) } } if _, err := EncodeControllerGrant(ControllerGrant{Accounts: []string{""}}); err == nil { t.Errorf("a grant naming an empty account was accepted") } g := ControllerGrant{Accounts: []string{"ace.mesh-build-go", "shanks.mesh-mail.reader"}} for _, c := range []struct { g ControllerGrant node, module string names bool }{ {g, "ace", "mesh-build-go", true}, {g, "shanks", "mesh-mail", true}, {g, "g14", "mesh-build-go", false}, {g, "ace", "mesh-build", false}, {ControllerGrant{}, "novox", "nats", true}, } { if got := c.g.NamesNode(c.node, c.module); got != c.names { t.Errorf("%v names %s.%s: %v, want %v", c.g.Accounts, c.node, c.module, got, c.names) } } }