package delivery import ( "errors" "strings" "testing" ) // 1943/AC1: a node's composition bucket is named for the node, and a node with no name, or one a bucket refuses, // is refused, naming why. func TestComposedBucketFor(t *testing.T) { b, err := ComposedBucketFor("ace") if err != nil || b != "composed-ace" { t.Fatalf("ComposedBucketFor(ace) = %q, %v", b, err) } for _, bad := range []string{"", "a.b", "a*", "a b", "a>"} { if _, err := ComposedBucketFor(bad); err == nil || !strings.Contains(err.Error(), "node") { t.Errorf("ComposedBucketFor(%q) = %v, want a refusal naming the node", bad, err) } } } // 1943/AC1: a module's composition key and the node's own key round-trip through one parser; an empty node, a // module named node, a bad fingerprint and a key of another shape are refused, naming why. func TestComposedKeys(t *testing.T) { fp := fp2of('c') key, err := ComposedKey("ace", "mesh-mail", fp) if err != nil || key != "ace.mesh-mail."+strings.Repeat("c", 64) { t.Fatalf("ComposedKey = %q, %v", key, err) } node, module, back, isNode, err := ParseComposedKey(key) if err != nil || node != "ace" || module != "mesh-mail" || back != fp || isNode { t.Errorf("ParseComposedKey(%q) = %q, %q, %q, %v, %v", key, node, module, back, isNode, err) } own, err := ComposedNodeKey("ace") if err != nil || own != "ace.node" { t.Fatalf("ComposedNodeKey = %q, %v", own, err) } node, module, back, isNode, err = ParseComposedKey(own) if err != nil || node != "ace" || module != "" || back != "" || !isNode { t.Errorf("ParseComposedKey(%q) = %q, %q, %q, %v, %v", own, node, module, back, isNode, err) } refusals := []struct { node, module, fp, why string }{ {"", "mesh-mail", fp, "node name is empty"}, {"ace", "node", fp, `"node" is refused`}, {"ace", "", fp, "module name is empty"}, {"ace", "mesh-mail", "src2:abc", "not a fingerprint of the new form"}, {"ace", "mesh-mail", "src1:" + strings.Repeat("c", 64), "not a fingerprint of the new form"}, {"a.b", "mesh-mail", fp, "node name"}, } for _, r := range refusals { if _, err := ComposedKey(r.node, r.module, r.fp); err == nil || !strings.Contains(err.Error(), r.why) { t.Errorf("ComposedKey(%q, %q, %q) = %v, want a refusal saying %q", r.node, r.module, r.fp, err, r.why) } } if _, err := ComposedNodeKey(""); err == nil { t.Errorf("ComposedNodeKey(\"\") accepted") } for _, bad := range []string{"", "ace", "ace.mesh-mail", "ace.node.x", "ace.mesh-mail.abc", ".mesh-mail." + strings.Repeat("c", 64), "ace.node." + strings.Repeat("c", 64), "ace.mesh-mail." + strings.Repeat("c", 64) + ".x", "want.ace.mesh-mail"} { if _, _, _, _, err := ParseComposedKey(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("ParseComposedKey(%q) = %v, want a refusal naming it", bad, err) } } } // 1943/AC1: the controller's value beside a composition, its digest and the judgement of the move, round-trips; an // unknown judgement, a digest not of the sha256 form, a move judged against nothing, a cannot-judge without its // reason, and an upgrade policy or source outside the catalogue's words are refused, naming why; an unknown version is // refused as one. The module's upgrade policy and its source ride beside the judgement, for the assigner's hold. func TestComposedDigestValue(t *testing.T) { digest := "sha256:" + strings.Repeat("d", 64) against := "sha256:" + strings.Repeat("e", 64) for _, v := range []ComposedDigest{ {Digest: digest, Judgement: JudgedNeither}, {Digest: digest, Judgement: JudgedNeither, Against: against}, {Digest: digest, Judgement: JudgedRecreates, Against: against, Words: "the container's image changes"}, {Digest: digest, Judgement: JudgedMovesData, Against: against}, {Digest: digest, Judgement: JudgedCannot, Words: "the composition the node runs is gone"}, {Digest: digest, Judgement: JudgedUnchanged, Words: "equal to the declaration the node last applied"}, {Digest: digest, Judgement: JudgedNeither, Policy: "record", PolicyFrom: "person", PolicyWhy: "the operator: its data"}, {Digest: digest, Judgement: JudgedNeither, Policy: "roll", PolicyFrom: "default"}, {Digest: digest, Judgement: JudgedNeither, Against: against, UserListOnly: true, Words: "only the bus's user list changes"}, } { raw, err := EncodeComposedDigest(v) if err != nil { t.Fatalf("EncodeComposedDigest(%+v): %v", v, err) } back, err := DecodeComposedDigest(raw) v.V = Version if err != nil || !SameComposedDigest(back, v) { t.Errorf("round trip of %+v = %+v, %v", v, back, err) } } refusals := []struct { v ComposedDigest why string }{ {ComposedDigest{Digest: digest, Judgement: "maybe"}, `judgement "maybe"`}, {ComposedDigest{Digest: digest}, `judgement ""`}, {ComposedDigest{Digest: "sha256:abc", Judgement: JudgedNeither}, "not sha256"}, {ComposedDigest{Judgement: JudgedNeither}, "not sha256"}, {ComposedDigest{Digest: digest, Judgement: JudgedRecreates}, "against"}, {ComposedDigest{Digest: digest, Judgement: JudgedMovesData}, "against"}, {ComposedDigest{Digest: digest, Judgement: JudgedCannot}, "why"}, {ComposedDigest{Digest: digest, Judgement: JudgedNeither, Against: "sha256:zz"}, "not sha256"}, {ComposedDigest{Digest: digest, Judgement: JudgedNeither, Policy: "sometimes", PolicyFrom: "person"}, `policy "sometimes"`}, {ComposedDigest{Digest: digest, Judgement: JudgedNeither, Policy: "record"}, `source ""`}, {ComposedDigest{Digest: digest, Judgement: JudgedNeither, PolicyFrom: "person"}, `policy ""`}, } for _, r := range refusals { if _, err := EncodeComposedDigest(r.v); err == nil || !strings.Contains(err.Error(), r.why) { t.Errorf("EncodeComposedDigest(%+v) = %v, want a refusal saying %q", r.v, err, r.why) } } if _, err := DecodeComposedDigest([]byte(`{"v":2,"digest":"` + digest + `","judgement":"neither"}`)); !errors.Is(err, ErrUnknownVersion) { t.Errorf("version 2 = %v, want ErrUnknownVersion", err) } if _, err := DecodeComposedDigest([]byte(`{"v":1,"digest":"` + digest + `","judgement":"perhaps"}`)); err == nil { t.Errorf("an unknown judgement was read") } }