Files
mesh-sdk/go/delivery/assigner_test.go
T

318 lines
12 KiB
Go

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)
}
}
}