ADR 0573 §2 has every module build against the latest published version of each mesh package it uses, chosen by the planner and written by the build, never pinned by hand (issue 1585, S4). The ask, the fetch request and the merge preview now carry the packages and their versions, and the new-form fingerprint takes each version, so a package's trunk merge changes every dependent's fingerprint. A module using no package keeps its fingerprint. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
199 lines
9.0 KiB
Go
199 lines
9.0 KiB
Go
package delivery
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const oddManifest = "{\n\t\"module\": \"a\",\n \"z\": 1, \"a\": [ \"x\",\"y\" ],\n \"why\": \"é — <b> & \\n\"\n}\n\n"
|
|
|
|
func mergedWithInputs() Forge {
|
|
return Forge{Kind: ForgeMerged, Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa", Merge: "cccc", Root: "t0",
|
|
Trees: map[string]string{"a": "t1", "b": "t2"},
|
|
Manifests: map[string]string{"a": oddManifest},
|
|
Locks: map[string]map[string]string{"a": {"go.sum": strings.Repeat("ab", 32)}}}
|
|
}
|
|
|
|
// 1442/AC1
|
|
func TestForgeMergedCarriesManifestsAndLocks(t *testing.T) {
|
|
f := mergedWithInputs()
|
|
data, err := EncodeForge(f)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, frag := range []string{`"manifests":{"a":`, `"locks":{"a":{"go.sum":`} {
|
|
if !strings.Contains(string(data), frag) {
|
|
t.Errorf("the encoding lacks %s: %s", frag, data)
|
|
}
|
|
}
|
|
got, err := DecodeForge(data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Manifests["a"] != oddManifest || !SameForge(got, withV(f)) {
|
|
t.Errorf("the message changed on the way: %q", got.Manifests["a"])
|
|
}
|
|
// ADR 0565 §2 (issue 1953 D1): a merged message carries the predefined list exact, or says the merge holds none; a
|
|
// pull message carries neither.
|
|
f.Predefined = []byte(`{"domain":{"type":"string"}}`)
|
|
if data, err := EncodeForge(f); err != nil {
|
|
t.Fatal(err)
|
|
} else if got, err := DecodeForge(data); err != nil || string(got.Predefined) != `{"domain":{"type":"string"}}` {
|
|
t.Errorf("the predefined list read back as %s, %v", got.Predefined, err)
|
|
}
|
|
f.Predefined, f.PredefinedRead = nil, true
|
|
if _, err := EncodeForge(f); err != nil {
|
|
t.Errorf("a merge holding no predefined list: %v", err)
|
|
}
|
|
pull := Forge{Kind: ForgePull, Repository: "novox/mesh-sdk", Number: 1, Head: "h", Action: PullOpened, PredefinedRead: true}
|
|
if _, err := EncodeForge(pull); err == nil || !strings.Contains(err.Error(), "only a merged message") {
|
|
t.Errorf("a pull message carrying the predefined list: %v", err)
|
|
}
|
|
}
|
|
|
|
func withV(f Forge) Forge { f.V = Version; return f }
|
|
|
|
// 1442/AC2
|
|
func TestForgeMergedRefusesBadInputs(t *testing.T) {
|
|
for name, tc := range map[string]struct {
|
|
mut func(*Forge)
|
|
want string
|
|
}{
|
|
"manifest outside trees": {func(f *Forge) { f.Manifests["zz"] = "{}" }, `"zz"`},
|
|
"locks outside trees": {func(f *Forge) { f.Locks["yy"] = map[string]string{"go.sum": "h"} }, `"yy"`},
|
|
"empty manifest": {func(f *Forge) { f.Manifests["b"] = "" }, `"b"`},
|
|
"empty lock hash": {func(f *Forge) { f.Locks["a"]["go.sum"] = "" }, `"a"`},
|
|
// No repository holds placement (ADR 0567 §11): a message carrying placement files is refused, on a merge or a pull.
|
|
"placement files": {func(f *Forge) { f.RetiredPlacements = []byte(`{"anchor":"{}"}`) }, "mesh-placement"},
|
|
"placements read": {func(f *Forge) { f.RetiredRead = true }, "mesh-placement"},
|
|
// ADR 0565 §2 (issue 1953 D1): the predefined list is one JSON object, said present or absent, never both.
|
|
"predefined not an object": {func(f *Forge) { f.Predefined = []byte(`["domain"]`) }, "one JSON object"},
|
|
"predefined both": {func(f *Forge) { f.Predefined = []byte(`{}`); f.PredefinedRead = true }, "says the merge holds none"},
|
|
} {
|
|
f := mergedWithInputs()
|
|
tc.mut(&f)
|
|
if _, err := EncodeForge(f); err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Errorf("%s: %v, want a refusal naming %s", name, err, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1442/AC3
|
|
func TestForgePullCarriesManifestsAndLocks(t *testing.T) {
|
|
pull := func(action string) Forge {
|
|
return Forge{Kind: ForgePull, Repository: "novox/mesh-sdk", Number: 1, Head: "h", Action: action,
|
|
Dirs: []string{"a"}, DirsKnown: true, Manifests: map[string]string{"a": oddManifest},
|
|
Locks: map[string]map[string]string{"a": {"go.sum": strings.Repeat("0", 64)}}}
|
|
}
|
|
for _, action := range []string{PullOpened, PullUpdated} {
|
|
data, err := EncodeForge(pull(action))
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", action, err)
|
|
}
|
|
if got, err := DecodeForge(data); err != nil || got.Manifests["a"] != oddManifest {
|
|
t.Errorf("%s: %v", action, err)
|
|
}
|
|
}
|
|
f := pull(PullOpened)
|
|
f.Manifests["b"] = "{}"
|
|
if _, err := EncodeForge(f); err == nil || !strings.Contains(err.Error(), `"b"`) {
|
|
t.Errorf("a manifest outside Dirs: %v", err)
|
|
}
|
|
f = pull(PullOpened)
|
|
f.Locks["c"] = map[string]string{"go.sum": "h"}
|
|
if _, err := EncodeForge(f); err == nil || !strings.Contains(err.Error(), `"c"`) {
|
|
t.Errorf("locks outside Dirs: %v", err)
|
|
}
|
|
for name, f := range map[string]Forge{
|
|
"refused manifests": {Kind: ForgeRefused, Repository: "a/b", Act: "x", Words: "no", Manifests: map[string]string{"a": "{}"}},
|
|
"refused locks": {Kind: ForgeRefused, Repository: "a/b", Act: "x", Words: "no", Locks: map[string]map[string]string{"a": {"go.sum": "h"}}},
|
|
"open manifests": {Kind: ForgeOpen, Manifests: map[string]string{"a": "{}"}},
|
|
"open locks": {Kind: ForgeOpen, Locks: map[string]map[string]string{"a": {"go.sum": "h"}}},
|
|
} {
|
|
if _, err := EncodeForge(f); err == nil {
|
|
t.Errorf("%s was encoded", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1442/AC4
|
|
func TestForgeFromAnOlderBridge(t *testing.T) {
|
|
old := `{"v":1,"kind":"merged","repository":"novox/mesh-sdk","number":36,"head":"aaaa","merge":"cccc","trees":{"a":"t1"},"root":"t0"}`
|
|
f, err := DecodeForge([]byte(old))
|
|
if err != nil || f.Manifests != nil || f.Locks != nil {
|
|
t.Fatalf("%+v, %v", f, err)
|
|
}
|
|
data, err := EncodeForge(f)
|
|
// the older message is written with the struct's key order, so the bytes compare as they are
|
|
if err != nil || string(data) != old {
|
|
t.Errorf("%s, %v, want exactly %s", data, err, old)
|
|
}
|
|
}
|
|
|
|
// 1442/AC5
|
|
func TestModuleKeepsInputs2(t *testing.T) {
|
|
kept := KeptInputs2{Tree: "t1", Bases: []string{"src2:" + strings.Repeat("1", 64), "src2:" + strings.Repeat("2", 64)},
|
|
Builder: "src2:" + strings.Repeat("3", 64), Contexts: map[string]string{"web": "t9"},
|
|
Locks: map[string]string{"web": strings.Repeat("cd", 32)}}
|
|
m := Module{FP: "src1:x", Manifest: strings.Repeat("ef", 32), Inputs2: &kept, Repository: "novox/m", Commit: "c"}
|
|
m.FP = SourcePrefix + "x"
|
|
data, err := EncodeModule(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(string(data), `"inputs2":{"tree":"t1"`) {
|
|
t.Errorf("the encoding lacks inputs2: %s", data)
|
|
}
|
|
got, err := DecodeModule(data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.V = Version
|
|
if !SameModule(got, m) {
|
|
t.Errorf("Inputs2 did not come back equal: %+v", got.Inputs2)
|
|
}
|
|
tc := map[string]Toolchain2{"web": {Language: "typescript", Version: "5.9", Recipe: "2"}}
|
|
want, none := SourceFingerprint2(SourceInputs2{Module: "m", Tree: kept.Tree, Bases: kept.Bases, Builder: kept.Builder,
|
|
Contexts: kept.Contexts, Locks: kept.Locks, Toolchains: tc})
|
|
have, none2 := SourceFingerprint2(got.Inputs2.With("m", tc))
|
|
if want == "" || none != "" || none2 != "" || have != want {
|
|
t.Errorf("fingerprints differ: %q %q (%q %q)", have, want, none, none2)
|
|
}
|
|
m.Manifest = ""
|
|
if _, err := EncodeModule(m); err == nil {
|
|
t.Error("Inputs2 without a manifest digest was encoded")
|
|
}
|
|
|
|
// Issue 1585 (ADR 0573 §2): the mesh packages a go.sum names, those of other repositories, and the latest published
|
|
// version of each: the trunk commit of the deepest module of its repository whose directory holds it.
|
|
sum := "git.novox.be/novox/mesh-sdk/go v0.1.7 h1:a=\ngit.novox.be/novox/mesh-sdk/go v0.1.7/go.mod h1:b=\n" +
|
|
"git.novox.be/novox/mesh-controller/modules/route-proxy/contract v0.1.0 h1:c=\n" +
|
|
"github.com/nats-io/nats.go v1.0.0 h1:d=\ngit.novox.be/novox/mesh-host v0.0.0-1 h1:e=\n"
|
|
pkgs := MeshPackagesOf([]byte(sum))
|
|
if want := []string{"git.novox.be/novox/mesh-controller/modules/route-proxy/contract", "git.novox.be/novox/mesh-host", "git.novox.be/novox/mesh-sdk/go"}; !reflect.DeepEqual(pkgs, want) {
|
|
t.Errorf("the packages of a go.sum: %v, want %v", pkgs, want)
|
|
}
|
|
if got := OtherPackages(pkgs, "novox/mesh-controller"); len(got) != 2 || got[0] != "git.novox.be/novox/mesh-host" {
|
|
t.Errorf("the packages of other repositories: %v", got)
|
|
}
|
|
held := map[string]Module{
|
|
"mesh-sdk": {Repository: "novox/mesh-sdk", Path: RootPath, Commit: "c-sdk"},
|
|
"route-proxy": {Repository: "novox/mesh-controller", Path: "modules/route-proxy", Commit: "c-rp"},
|
|
"controller": {Repository: "novox/mesh-controller", Path: RootPath, Commit: "c-ctl"},
|
|
"route-proxy2": {Repository: "novox/mesh-controller", Path: "modules/route", Commit: "c-wrong"},
|
|
"no-path": {Repository: "novox/mesh-host", Commit: "c-host"},
|
|
}
|
|
versions, missing := PackageVersionsOf(pkgs, held)
|
|
if want := (PackageVersions{"git.novox.be/novox/mesh-sdk/go": "c-sdk", "git.novox.be/novox/mesh-controller/modules/route-proxy/contract": "c-rp"}); !reflect.DeepEqual(versions, want) {
|
|
t.Errorf("versions %v, want %v", versions, want)
|
|
}
|
|
if len(missing) != 1 || missing[0] != "git.novox.be/novox/mesh-host" {
|
|
t.Errorf("missing %v: a module that names no directory holds no package", missing)
|
|
}
|
|
kept.Packages = []string{"git.novox.be/novox/mesh-sdk/go", "git.novox.be/novox/mesh-host"}
|
|
if fp, none := SourceFingerprint2(kept.WithPackages("m", tc, versions)); fp != "" || !strings.HasPrefix(none, NoPackageVersion) {
|
|
t.Errorf("a package with no version: %q %q", fp, none)
|
|
}
|
|
}
|