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; a want naming a fingerprint without a digest, 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}, } { 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'), 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, 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` 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"}} { if _, err := EncodeNodeWant(bad); err == nil { t.Errorf("EncodeNodeWant(%+v) accepted", bad) } } 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) } } }