Files
mesh-sdk/go/delivery/want_composed_test.go
T
jschoubben 4d5f430224
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: ace: novox/mesh-catalog#505: checks/placement/go.mod names mesh-sdk f1d7c7377dc4, which is neither on mesh-sdk's trunk nor the grou…
A want the node composes itself says so (Self) and names the build alone; every other want names its composition as before, so no caller of the old form changes (issue 1126, ADR 0547 §2)
2026-10-26 19:13:56 +01:00

190 lines
8.1 KiB
Go

package delivery
import (
"strings"
"testing"
)
func hashOf(c byte) string { return "sha256:" + digestOf(c) }
// 1943/AC3: a want naming its fingerprint and the digest of the composition it allows round-trips, and so does one
// carrying the answer to a failed backup, and from ADR 0547's second milestone one naming the build alone or the yes
// a person gave; a digest not of the sha256
// form, a removed want naming a digest, an unknown backup answer and a rule past the seventh are refused.
func TestAWantNamesItsComposition(t *testing.T) {
for _, w := range []Want{
{FP: fp2of('a'), Composed: hashOf('1')},
{FP: fp2of('a'), Composed: hashOf('1'), Backup: BackupAgain},
{FP: fp2of('a'), Composed: hashOf('1'), Backup: BackupSkip, Words: "the operator: go on without a backup"},
{FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: RuleComposed, Words: "no composition of b for ace yet"},
{Removed: true},
// ADR 0547 §2, §3 (issue 1126): from the second milestone a want names the build alone, and the yes a person gave.
{FP: fp2of('a'), Self: true},
{FP: fp2of('a'), Self: true, Yes: hashOf('3')},
} {
raw, err := EncodeWant(w)
if err != nil {
t.Fatalf("EncodeWant(%+v): %v", w, err)
}
back, err := DecodeWant(raw)
w.V = Version
if err != nil || !SameWant(back, w) {
t.Errorf("want %+v came back %+v, %v", w, back, err)
}
}
refusals := []struct {
w Want
why string
}{
{Want{FP: fp2of('a')}, "names no composition"},
{Want{FP: fp2of('a'), Self: true, Composed: hashOf('1')}, "names a composition"},
{Want{FP: fp2of('a'), Self: true, Yes: "sha256:12"}, "a want's yes allows"},
{Want{Removed: true, Self: true}, "removed want names no"},
{Want{FP: fp2of('a'), Composed: "sha256:12"}, "not sha256"},
{Want{Removed: true, Composed: hashOf('1')}, "removed want names no"},
{Want{FP: fp2of('a'), Composed: hashOf('1'), Backup: "later"}, `backup answer "later"`},
{Want{Removed: true, Backup: BackupAgain}, "removed want names no"},
{Want{FP: fp2of('a'), Composed: hashOf('1'), Newest: fp2of('b'), Rule: 8}, "rule 8"},
}
for _, r := range refusals {
if _, err := EncodeWant(r.w); err == nil || !strings.Contains(err.Error(), r.why) {
t.Errorf("EncodeWant(%+v) = %v, want a refusal saying %q", r.w, err, r.why)
}
}
if _, err := DecodeWant([]byte(`{"v":1,"fp":"` + fp2of('a') + `"}`)); err == nil || !strings.Contains(err.Error(), "names no composition") {
t.Errorf("a want of release 5, naming no composition and not the node's own, was read: %v", err)
}
if RuleComposed != 7 {
t.Errorf("RuleComposed = %d, want the seventh rule", RuleComposed)
}
}
// 1943/AC3: the node's own want is `want.<node>.node` and round-trips through its parser, while a module named node is
// still refused a want key; its value names the digest of the node's own composition and no fingerprint.
func TestTheNodesOwnWant(t *testing.T) {
key, err := WantNodeKey("ace")
if err != nil || key != "want.ace.node" {
t.Fatalf("WantNodeKey = %q, %v", key, err)
}
if node, err := ParseWantNodeKey(key); err != nil || node != "ace" {
t.Errorf("ParseWantNodeKey(%q) = %q, %v", key, node, err)
}
if _, err := WantKey("ace", "node"); err == nil {
t.Errorf(`WantKey(ace, "node") accepted`)
}
if _, _, err := ParseWantKey(key); err == nil {
t.Errorf("ParseWantKey read the node's own want as a module's")
}
for _, bad := range []string{"want.ace.mesh-mail", "want.node", "ace.node", "want.a.b.node", ""} {
if _, err := ParseWantNodeKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") {
t.Errorf("ParseWantNodeKey(%q) = %v, want a refusal naming it", bad, err)
}
}
if _, err := WantNodeKey(""); err == nil {
t.Errorf("WantNodeKey(\"\") accepted")
}
w := NodeWant{Composed: hashOf('2'), Words: "the node's own composition"}
raw, err := EncodeNodeWant(w)
if err != nil {
t.Fatal(err)
}
back, err := DecodeNodeWant(raw)
w.V = Version
if err != nil || back != w {
t.Errorf("node want came back %+v, %v", back, err)
}
for _, bad := range []NodeWant{{}, {Composed: "sha256:1"}, {Self: true, Composed: hashOf('2')}} {
if _, err := EncodeNodeWant(bad); err == nil {
t.Errorf("EncodeNodeWant(%+v) accepted", bad)
}
}
// ADR 0547 §2 (issue 1126): from the second milestone the node composes its own composition, and its want names none.
if raw, err := EncodeNodeWant(NodeWant{Self: true, Words: "the node composes its own"}); err != nil {
t.Errorf("a node want naming no composition: %v", err)
} else if back, err := DecodeNodeWant(raw); err != nil || back.Composed != "" {
t.Errorf("a node want naming no composition came back %+v, %v", back, err)
}
if _, err := DecodeNodeWant([]byte(`{"v":1,"composed":"` + hashOf('2') + `","fp":"` + fp2of('a') + `"}`)); err == nil {
t.Errorf("a node want naming a fingerprint was read")
}
}
// 1943/AC3: a have held after a failed backup, naming the composition it runs, round-trips; so does the node's own have
// naming its composition; a have's digest not of the sha256 form is refused.
func TestAHaveNamesItsComposition(t *testing.T) {
for _, h := range []Have{
{FP: fp2of('a'), Composed: hashOf('3'), Phase: PhaseHeld, Health: HealthOK, Detail: "the backup failed: no space left"},
{FP: fp2of('a'), Composed: hashOf('3'), Phase: PhaseRunning, Health: HealthOK},
} {
raw, err := EncodeHave(h)
if err != nil {
t.Fatalf("EncodeHave(%+v): %v", h, err)
}
back, err := DecodeHave(raw)
h.V = Version
if err != nil || !SameHave(back, h) {
t.Errorf("have %+v came back %+v, %v", h, back, err)
}
}
if _, err := EncodeHave(Have{Composed: "sha256:x", Phase: PhaseRunning, Health: HealthOK}); err == nil || !strings.Contains(err.Error(), "not sha256") {
t.Errorf("a have naming a bad digest = %v", err)
}
n := HaveNode{Composed: hashOf('4')}
raw, err := EncodeHaveNode(n)
if err != nil {
t.Fatal(err)
}
back, err := DecodeHaveNode(raw)
n.V = Version
if err != nil || !SameHaveNode(back, n) {
t.Errorf("node have came back %+v, %v", back, err)
}
if SameHaveNode(n, HaveNode{V: Version, Composed: hashOf('5')}) {
t.Errorf("node haves naming two compositions are the same")
}
}
// 1943/AC3: the assigner's backup ask (back up again, or go on without one, ADR 0539 clause 16) and its record ask (a
// move a person must allow, clause 14) key by node, module and fingerprint or composition, round-trip through
// ParseAnyAskKey, and the view may answer both.
func TestTheBackupAndRecordAsks(t *testing.T) {
fp, digest := fp2of('a'), hashOf('6')
backup, err := BackupAskKey("ace", "mesh-mail", fp)
if err != nil || backup != "backup.ace.mesh-mail."+strings.Repeat("a", 64) {
t.Fatalf("BackupAskKey = %q, %v", backup, err)
}
record, err := RecordAskKey("ace", "mesh-mail", digest)
if err != nil || record != "record.ace.mesh-mail."+strings.Repeat("6", 64) {
t.Fatalf("RecordAskKey = %q, %v", record, err)
}
if got, err := ParseAnyAskKey(backup); err != nil || got != (AskKey{Kind: AskKindBackup, Node: "ace", Module: "mesh-mail", FP: fp}) {
t.Errorf("ParseAnyAskKey(%q) = %+v, %v", backup, got, err)
}
if got, err := ParseAnyAskKey(record); err != nil || got != (AskKey{Kind: AskKindRecord, Node: "ace", Module: "mesh-mail", Composed: digest}) {
t.Errorf("ParseAnyAskKey(%q) = %+v, %v", record, got, err)
}
for _, kind := range []string{AskKindBackup, AskKindRecord} {
if proof, err := AskNeedsProof(kind); err != nil || proof {
t.Errorf("AskNeedsProof(%s) = %v, %v; the view may answer it", kind, proof, err)
}
}
for _, k := range []string{backup, record} {
if _, err := ViewAnswerKey(k); err != nil {
t.Errorf("the view may not answer %s: %v", k, err)
}
}
for _, bad := range [][3]string{{"", "mesh-mail", fp}, {"ace", "node", fp}, {"ace", "mesh-mail", "src2:1"}} {
if _, err := BackupAskKey(bad[0], bad[1], bad[2]); err == nil {
t.Errorf("BackupAskKey(%v) accepted", bad)
}
}
if _, err := RecordAskKey("ace", "mesh-mail", "sha256:1"); err == nil {
t.Errorf("a record ask naming a bad digest was keyed")
}
for _, bad := range []string{"backup.ace.mesh-mail", "record.ace.mesh-mail.12", "record.ace." + strings.Repeat("6", 64)} {
if _, err := ParseAnyAskKey(bad); err == nil {
t.Errorf("ParseAnyAskKey(%q) accepted", bad)
}
}
}