package delivery import ( "bytes" "compress/gzip" "encoding/json" "errors" "fmt" "io" "os" "sort" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/declaration" ) // capturedDeclaration is a node's declaration as the controller's `plan` verb answered it on 2026-10-24 at 16:19 // (testdata/declaration-.json.gz; the resources carrying a sealed value or a secret taken out, and the filled // files that named them), never typed by hand. type capturedDeclaration struct { Builds map[string]string `json:"builds"` Resources []json.RawMessage `json:"resources"` Filled []string `json:"filled_files"` Provisioner string `json:"provisioner_account"` } func readCaptured(t *testing.T, node string) capturedDeclaration { t.Helper() f, err := os.Open("testdata/declaration-" + node + ".json.gz") if err != nil { t.Fatal(err) } defer f.Close() z, err := gzip.NewReader(f) if err != nil { t.Fatal(err) } raw, err := io.ReadAll(z) if err != nil { t.Fatal(err) } var c capturedDeclaration if err := json.Unmarshal(raw, &c); err != nil { t.Fatal(err) } return c } // fpOfBuild stands for each module's fingerprint in a captured declaration, which names builds, not fingerprints. func fpOfBuild(build string) string { var b strings.Builder for b.Len() < 64 { b.WriteString(fmt.Sprintf("%x", []byte(build))) } return SourcePrefix2 + b.String()[:64] } func splitCaptured(t *testing.T, node string, c capturedDeclaration) map[string]Composed { t.Helper() fps := map[string]string{} for m, b := range c.Builds { fps[m] = fpOfBuild(b) } // mesh-wireguard's resources are composed with no build of it named; its fingerprint is the indexer's, as every // module's is, and stood in for here by its name. for _, r := range c.Resources { var head struct { Module string `json:"module"` } _ = json.Unmarshal(r, &head) if _, ok := fps[head.Module]; head.Module != "" && !ok { fps[head.Module] = fpOfBuild(head.Module) } } split, err := SplitComposed(node, c.Resources, fps, c.Filled, NodeOwn{Provisioner: c.Provisioner}) if err != nil { t.Fatalf("SplitComposed(%s): %v", node, err) } return split } func canonical(t *testing.T, raw json.RawMessage) string { t.Helper() var v any d := json.NewDecoder(bytes.NewReader(raw)) d.UseNumber() if err := d.Decode(&v); err != nil { t.Fatal(err) } out, _ := json.Marshal(v) return string(out) } // 1943/AC2: each module's slice of a captured declaration of each node, and the node's own resources, round-trip // through the composition's encoding with the same digest; together they are the declaration's resources, each once, // each in the composition of the module it says. func TestCompositionsOfACapturedDeclaration(t *testing.T) { t.Parallel() for _, node := range []string{"ace", "g14"} { t.Run(node, func(t *testing.T) { t.Parallel() oneCapturedDeclaration(t, node) }) } } func oneCapturedDeclaration(t *testing.T, node string) { { c := readCaptured(t, node) split := splitCaptured(t, node, c) modules := map[string]bool{} for m := range c.Builds { modules[m] = true } var union []string for key, comp := range split { n, module, fp, isNode, err := ParseComposedKey(key) if err != nil || n != node || comp.Node != node || comp.Module != module || comp.FP != fp { t.Fatalf("%s: key %q holds %s/%s/%s (%v)", node, key, comp.Node, comp.Module, comp.FP, err) } if !isNode && !modules[module] && module != "mesh-wireguard" { t.Errorf("%s: a composition of %s, which the declaration names no build of", node, module) } raw, err := EncodeComposed(comp) if err != nil { t.Fatalf("%s: EncodeComposed(%s): %v", node, key, err) } back, err := DecodeComposed(raw) if err != nil { t.Fatalf("%s: DecodeComposed(%s): %v", node, key, err) } if !SameComposed(back, comp) { t.Errorf("%s: %s did not round-trip", node, key) } // SameComposed encodes both, so the digest of what was read back, a hash of its encoding, is the one written. if d, err := ComposedDigestOf(back); err != nil || !digestForm.MatchString(d) { t.Errorf("%s: %s digests %q (%v)", node, key, d, err) } for _, r := range back.Resources { var head struct { Module string `json:"module"` } _ = json.Unmarshal(r, &head) if head.Module != module { t.Errorf("%s: %s holds a resource of %q", node, key, head.Module) } union = append(union, canonical(t, r)) } } var want []string for _, r := range c.Resources { want = append(want, canonical(t, r)) } sort.Strings(union) sort.Strings(want) if strings.Join(union, "\n") != strings.Join(want, "\n") { t.Errorf("%s: the compositions hold %d resources, the declaration %d, or they differ", node, len(union), len(want)) } } } // 1943/AC2: one changed byte of a resource is another digest; the same composition written with its keys in another // order is the same digest. func TestComposedDigestFollowsTheContent(t *testing.T) { c := readCaptured(t, "g14") split := splitCaptured(t, "g14", c) var comp Composed for _, v := range split { if v.Module != "" && len(v.Resources) > 1 { comp = v break } } d1, _ := ComposedDigestOf(comp) var m map[string]json.RawMessage _ = json.Unmarshal(comp.Resources[0], &m) var keys []string for k := range m { keys = append(keys, k) } sort.Sort(sort.Reverse(sort.StringSlice(keys))) var parts []string for _, k := range keys { parts = append(parts, fmt.Sprintf("%q : %s", k, m[k])) } reordered := json.RawMessage("{ " + strings.Join(parts, ",\n ") + " }") shuffled := comp shuffled.Resources = append([]json.RawMessage{reordered}, comp.Resources[1:]...) if d2, _ := ComposedDigestOf(shuffled); d2 != d1 { t.Errorf("the same resources in another order and spacing digest %s and %s", d1, d2) } changed := comp changed.Resources = append([]json.RawMessage{json.RawMessage(strings.Replace(canonical(t, comp.Resources[0]), `"id":"`, `"id":"x`, 1))}, comp.Resources[1:]...) if d3, _ := ComposedDigestOf(changed); d3 == d1 { t.Errorf("a changed resource kept its digest") } revised := comp revised.Data = !comp.Data if d4, _ := ComposedDigestOf(revised); d4 == d1 { t.Errorf("a changed data flag kept its digest") } } // 1943/AC2: a file over 64 KiB is carried by digest and size, its content handed back to be put in the node's object // bucket; a composition that carries such a file whole is refused. func TestAComposedFileOver64KiBIsCarriedByDigest(t *testing.T) { c := readCaptured(t, "g14") split := splitCaptured(t, "g14", c) var comp Composed var at int for _, v := range split { for i, r := range v.Resources { var f map[string]any _ = json.Unmarshal(r, &f) if f["type"] == "file" && v.Module != "" && f["bytes"] == nil && f["into"] == nil { comp, at = v, i break } } if comp.Module != "" { break } } var f map[string]any _ = json.Unmarshal(comp.Resources[at], &f) // Grown past the threshold from a captured file: no captured file of this node is over 64 KiB. body := strings.Repeat("x", declaration.FileReferenceThreshold+1) f["content"] = body big, _ := json.Marshal(f) comp.Resources = append([]json.RawMessage{}, comp.Resources...) comp.Resources[at] = big if _, err := EncodeComposed(comp); err == nil || !strings.Contains(err.Error(), "by digest") { t.Fatalf("a file over 64 KiB carried whole = %v, want refused", err) } byDigest, objects, err := comp.ByDigest() if err != nil { t.Fatal(err) } var ref map[string]any _ = json.Unmarshal(byDigest.Resources[at], &ref) digest := declaration.DigestOf([]byte(body)) if ref["digest"] != digest || ref["size"] != float64(len(body)) || ref["content"] != nil { t.Errorf("the file became %v, want digest %s and size %d", ref, digest, len(body)) } if string(objects[digest]) != body || len(objects) != 1 { t.Errorf("the objects handed back are %d, want the file's content under its digest", len(objects)) } if _, err := EncodeComposed(byDigest); err != nil { t.Errorf("the composition by digest is refused: %v", err) } } // 1943/AC2: an unknown version is refused as one; a module's composition carrying the node's own parts, a resource of // another module, a resource its kind refuses, or no fingerprint, is refused, naming why. func TestComposedRefusals(t *testing.T) { fp := fp2of('a') file := json.RawMessage(`{"id":"m.f","type":"file","module":"m","path":"/etc/m","content":"x"}`) ok := Composed{Node: "ace", Module: "m", FP: fp, Resources: []json.RawMessage{file}} raw, err := EncodeComposed(ok) if err != nil { t.Fatal(err) } if _, err := DecodeComposed(bytes.Replace(raw, []byte(`"v":1`), []byte(`"v":2`), 1)); !errors.Is(err, ErrUnknownVersion) { t.Errorf("version 2 = %v, want ErrUnknownVersion", err) } refusals := []struct { c Composed why string }{ {Composed{Node: "ace", Module: "m", Resources: []json.RawMessage{file}}, "fingerprint"}, {Composed{Node: "", Module: "m", FP: fp, Resources: []json.RawMessage{file}}, "node name is empty"}, {Composed{Node: "ace", Module: "other", FP: fp, Resources: []json.RawMessage{file}}, `of "m"`}, {Composed{Node: "ace", Module: "m", FP: fp, Resources: []json.RawMessage{file}, Own: NodeOwn{Provisioner: "p"}}, "node's own"}, {Composed{Node: "ace", FP: fp, Resources: []json.RawMessage{}}, "names no fingerprint"}, {Composed{Node: "ace", Resources: []json.RawMessage{file}}, `of "m"`}, {Composed{Node: "ace", Module: "m", FP: fp, Resources: []json.RawMessage{json.RawMessage(`{"id":"m.f","type":"file","module":"m","path":"/etc/m","content":"x","image":"y"}`)}}, `"image"`}, {Composed{Node: "ace", Module: "m", FP: fp, Resources: []json.RawMessage{json.RawMessage(`{"id":"m.a","type":"action","module":"m"}`)}}, "action"}, } for _, r := range refusals { if _, err := EncodeComposed(r.c); err == nil || !strings.Contains(err.Error(), r.why) { t.Errorf("EncodeComposed(%s/%s) = %v, want a refusal saying %q", r.c.Node, r.c.Module, err, r.why) } } } // 1943/AC2 (issue 1150): a module whose name holds a dot keys its have, its want and its composition escaped, and the // parsers give its name back; a name with a wildcard, whitespace or an escape of its own is still refused. func TestADottedModuleKeysEscaped(t *testing.T) { fp := fp2of('f') have, err := HaveKey("ace", "novox.be") if err != nil || have != "ace.novox=2ebe" { t.Fatalf("HaveKey = %q, %v", have, err) } if n, m, isNode, err := ParseHaveKey(have); err != nil || n != "ace" || m != "novox.be" || isNode { t.Errorf("ParseHaveKey(%q) = %q, %q, %v, %v", have, n, m, isNode, err) } want, err := WantKey("ace", "novox.be") if err != nil || want != "want.ace.novox=2ebe" { t.Fatalf("WantKey = %q, %v", want, err) } if n, m, err := ParseWantKey(want); err != nil || n != "ace" || m != "novox.be" { t.Errorf("ParseWantKey(%q) = %q, %q, %v", want, n, m, err) } comp, err := ComposedKey("ace", "novox.be", fp) if err != nil || comp != "ace.novox=2ebe."+strings.Repeat("f", 64) { t.Fatalf("ComposedKey = %q, %v", comp, err) } if n, m, back, _, err := ParseComposedKey(comp); err != nil || n != "ace" || m != "novox.be" || back != fp { t.Errorf("ParseComposedKey(%q) = %q, %q, %q, %v", comp, n, m, back, err) } for _, bad := range []string{"m*", "m x", "m=2e", "m>", ".m", "m.", "a..b"} { if _, err := HaveKey("ace", bad); err == nil { t.Errorf("HaveKey(ace, %q) accepted", bad) } } for _, bad := range []string{"ace.m=2", "ace.m=2E", "ace.m=41", "ace.=6eode"} { if _, _, _, err := ParseHaveKey(bad); err == nil { t.Errorf("ParseHaveKey(%q) accepted", bad) } } }