103 lines
4.0 KiB
Go
103 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"strings"
|
|
"testing"
|
|
|
|
"encoding/json"
|
|
|
|
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
|
|
|
sdkdecl "git.novox.be/novox/mesh-sdk/go/declaration"
|
|
)
|
|
|
|
// A composition's file over 64 KiB reaches the node by its digest, and its object is in the node's bucket before the
|
|
// composition is written (ADR 0539 §4; issue 1945, live on 2026-10-25 at 01:22: novox's node-engine could not fetch
|
|
// nats.bus-users, because the body the compositions were split from already named the file by digest, so the writer
|
|
// found no content to put, and nothing else put it once sends were gone).
|
|
func TestALargeFileOfACompositionHasItsObjectInTheBucket(t *testing.T) {
|
|
big := strings.Repeat("user = x\n", sdkdecl.FileReferenceThreshold/8)
|
|
declared := sendable{fileReferences: true, Resources: []map[string]any{{"id": "nats.bus-users", "type": "file",
|
|
"path": "/var/lib/nats-module/conf/accounts.conf", "mode": "0644", "content": big}}}
|
|
body, err := compositionBody(declared)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
comps, _, err := compositionsOf("novox", body, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
at, _, _, _ := aPlace(t, "novox")
|
|
if _, err := writeNodeCompositions(context.Background(), at, comps); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sum := sha256.Sum256([]byte(big))
|
|
if _, ok := at.objects.(fakeObjects)[hex.EncodeToString(sum[:])]; !ok {
|
|
t.Fatalf("the object of nats.bus-users is not in the bucket: %d objects", len(at.objects.(fakeObjects)))
|
|
}
|
|
}
|
|
|
|
// A composition that names a file by a digest whose object the bucket does not hold is refused, never written: a node
|
|
// that read it could not apply it.
|
|
func TestACompositionNamingAnObjectTheBucketLacksIsRefused(t *testing.T) {
|
|
big := strings.Repeat("user = x\n", sdkdecl.FileReferenceThreshold/8)
|
|
declared := sendable{fileReferences: true, Resources: []map[string]any{{"id": "nats.bus-users", "type": "file",
|
|
"path": "/var/lib/nats-module/conf/accounts.conf", "mode": "0644", "content": big}}}
|
|
body, err := declared.Body() // the body as a send named it: by digest, the content gone
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
comps, _, err := compositionsOf("novox", body, nil, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
at, _, _, _ := aPlace(t, "novox")
|
|
if _, err := writeNodeCompositions(context.Background(), at, comps); err == nil ||
|
|
!strings.Contains(err.Error(), "does not hold") {
|
|
t.Fatalf("a composition naming a missing object was written: %v", err)
|
|
}
|
|
}
|
|
|
|
// Every file the provisioner fills stays filled in the compositions, a moved module's too: the node's own composition
|
|
// carries the path, so the node-engine leaves its content to the provisioner (issue 1945, live on 2026-10-25 at 01:22:
|
|
// ace's node-engine wrote claude-code's, node-env's and power's filled files empty, because the compositions dropped the
|
|
// filled paths of every module moved into the node's own, and their resources say no content).
|
|
func TestEveryFilledFileStaysFilledInTheCompositions(t *testing.T) {
|
|
for _, node := range []string{"ace", "g14"} {
|
|
body, fps := capturedBody(t, node)
|
|
var d struct {
|
|
Filled []string `json:"filled_files"`
|
|
}
|
|
if err := json.Unmarshal(body, &d); err != nil || len(d.Filled) == 0 {
|
|
t.Fatalf("%s: the captured declaration fills nothing (%v)", node, err)
|
|
}
|
|
// power, claude-code and node-env have no fingerprint of the new form, so they move into the node's own.
|
|
for _, m := range []string{"power", "claude-code", "node-env"} {
|
|
delete(fps, m)
|
|
}
|
|
comps, _, err := compositionsOf(node, body, fps, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", node, err)
|
|
}
|
|
filled := map[string]bool{}
|
|
for _, c := range comps {
|
|
for _, p := range c.Filled {
|
|
filled[p] = true
|
|
}
|
|
}
|
|
for _, p := range d.Filled {
|
|
if !filled[p] {
|
|
t.Errorf("%s: %s is filled by the provisioner, and no composition says so", node, p)
|
|
}
|
|
}
|
|
for key, c := range comps {
|
|
if _, err := sdkdelivery.EncodeComposed(c); err != nil {
|
|
t.Errorf("%s: %s: %v", node, key, err)
|
|
}
|
|
}
|
|
}
|
|
}
|