318 lines
12 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|