From f69a561a1693abf5e2b71eaf1905dbbd02535e54 Mon Sep 17 00:00:00 2001 From: mesh-tools Date: Tue, 20 Oct 2026 18:55:54 +0000 Subject: [PATCH] Test how a manifest and lock hashes become a module's new-form inputs (issue 1450, design 47 R4c-2) --- .../internal/index/inputs_test.go | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 modules/mesh-delivery-indexer/internal/index/inputs_test.go diff --git a/modules/mesh-delivery-indexer/internal/index/inputs_test.go b/modules/mesh-delivery-indexer/internal/index/inputs_test.go new file mode 100644 index 0000000..faee51b --- /dev/null +++ b/modules/mesh-delivery-indexer/internal/index/inputs_test.go @@ -0,0 +1,146 @@ +package index + +import ( + "context" + "reflect" + "strings" + "testing" + + "git.novox.be/novox/mesh-sdk/go/delivery" +) + +// aBase is the new-form fingerprint of a base module, the one SourceFingerprint2 gives for the inputs of avahi at a tree. +func aBase(t *testing.T) string { + t.Helper() + fp, none := delivery.SourceFingerprint2(delivery.SourceInputs2{Module: "mesh-tools", Tree: "basetree"}) + if none != "" { + t.Fatal(none) + } + return fp +} + +func noBases(string) (string, string) { return "", "none are held in this test" } + +func TestAGoModuleKeepsItsTreeAndTheHashOfItsLockFile(t *testing.T) { // 1450/AC1 + in := inputsOf("avahi", catalogueManifest(t, "avahi"), "avahitree", map[string]string{"go.sum": avahiGoSum}, noBases) + want := &delivery.KeptInputs2{Tree: "avahitree", Locks: map[string]string{"tools": avahiGoSum}} + if !reflect.DeepEqual(in.Kept, want) { + t.Errorf("kept = %+v, want %+v", in.Kept, want) + } + if !reflect.DeepEqual(in.Languages, map[string]string{"tools": "go"}) { + t.Errorf("languages = %v, want tools: go", in.Languages) + } + // Through the indexer: the merge's tree of the module's directory and the lock hashes of that directory, kept in modules.avahi. + ix, mods, _ := manifestsIndexer(t, rec(t, "build-1", "avahi", catalogueRepo, "modules/avahi", "main", "", "2026-10-19T09:00:00Z", aInputs)) + f := withManifests(mergedMsg(catalogueRepo, map[string]string{"modules/avahi": "avahitree", "modules/other": "x"}), + map[string]string{"modules/avahi": catalogueManifest(t, "avahi")}, map[string]map[string]string{"modules/avahi": {"go.sum": avahiGoSum}}) + if err := ix.Apply(context.Background(), f); err != nil { + t.Fatal(err) + } + m, ok := moduleOf(t, mods, "avahi") + if !ok || !reflect.DeepEqual(m.Inputs2, want) || m.Manifest != delivery.ManifestDigest([]byte(catalogueManifest(t, "avahi"))) { + t.Errorf("module = %+v (%v), want inputs %+v and the manifest digest", m, ok, want) + } +} + +func TestAnArtifactThatResolvesPackagesAndHasNoLockFileHasAnEmptyHash(t *testing.T) { // 1450/AC2 + in := inputsOf("avahi", catalogueManifest(t, "avahi"), "avahitree", nil, noBases) + if in.Kept == nil || !reflect.DeepEqual(in.Kept.Locks, map[string]string{"tools": ""}) { + t.Fatalf("kept = %+v, want Locks {tools: \"\"}", in.Kept) + } + // The fingerprint of such inputs is none, "no lock" (mesh-sdk SourceFingerprint2). + if fp, none := delivery.SourceFingerprint2(in.Kept.With("avahi", nil)); fp != "" || none != "no lock" { + t.Errorf("fingerprint = %q none %q, want none 'no lock'", fp, none) + } + // A TypeScript bundle's lock file is package-lock.json, not go.sum. + ts := inputsOf("audit-logger", catalogueManifest(t, "audit-logger"), "t", map[string]string{"go.sum": "wrong", "package-lock.json": avahiGoSum}, noBases) + if ts.Kept == nil || !reflect.DeepEqual(ts.Kept.Locks, map[string]string{"code": avahiGoSum}) || !reflect.DeepEqual(ts.Languages, map[string]string{"code": "typescript"}) { + t.Errorf("typescript: kept %+v languages %v", ts.Kept, ts.Languages) + } + // netcheck has a Go and a TypeScript bundle: each takes its own language's lock file. + both := inputsOf("netcheck", catalogueManifest(t, "netcheck"), "t", map[string]string{"go.sum": "g", "package-lock.json": "p"}, noBases) + if both.Kept == nil || !reflect.DeepEqual(both.Kept.Locks, map[string]string{"tools-go": "g", "tools-typescript": "p"}) { + t.Errorf("netcheck: kept %+v", both.Kept) + } +} + +// aModuleWithABase is avahi's manifest (mesh-catalog a67f0196) with the one build.on entry that names a module as the +// controller's own test has it (mesh-controller internal/builder/standing_on_test.go:22): no manifest of the sources names +// a module as a base, only vendors' images. +func aModuleWithABase(t *testing.T) string { + t.Helper() + raw := catalogueManifest(t, "avahi") + with := strings.Replace(raw, `"build": {`, `"build": { + "on": [{"arg": "RUNTIME_BASE", "module": "mesh-tools", "artifact": "runtime"}],`, 1) + if with == raw { + t.Fatal("avahi's manifest has no build") + } + return with +} + +func TestABaseEntersByTheFingerprintOfItsModuleOrNamesItsAbsence(t *testing.T) { // 1450/AC3 + fp := aBase(t) + has := func(module string) (string, string) { + if module != "mesh-tools" { + t.Errorf("asked for the base %q", module) + } + return fp, "" + } + in := inputsOf("avahi", aModuleWithABase(t), "t", map[string]string{"go.sum": avahiGoSum}, has) + if in.Kept == nil || !reflect.DeepEqual(in.Kept.Bases, []string{fp}) { + t.Errorf("bases = %+v, want [%s]", in.Kept, fp) + } + none := inputsOf("avahi", aModuleWithABase(t), "t", map[string]string{"go.sum": avahiGoSum}, noBases) + if none.Kept == nil || len(none.Kept.Bases) != 0 || !strings.Contains(none.None2, "mesh-tools") || !strings.Contains(none.None2, "base") { + t.Errorf("kept %+v none2 %q, want no bases and none2 naming the base mesh-tools", none.Kept, none.None2) + } + // Through the indexer: modules.mesh-tools holds a fingerprint of the new form (a record written by a later release). + ix, mods, _ := manifestsIndexer(t, rec(t, "build-1", "avahi", catalogueRepo, "modules/avahi", "main", "", "2026-10-19T09:00:00Z", aInputs)) + mods.put("mesh-tools", mustEncode(t, delivery.Module{Repository: catalogueRepo, Commit: merge, None: "x", FP2: fp, Manifest: strings.Repeat("a", 64)})) + f := withManifests(mergedMsg(catalogueRepo, map[string]string{"modules/avahi": "t"}), map[string]string{"modules/avahi": aModuleWithABase(t)}, nil) + if err := ix.Apply(context.Background(), f); err != nil { + t.Fatal(err) + } + if m, _ := moduleOf(t, mods, "avahi"); m.Inputs2 == nil || !reflect.DeepEqual(m.Inputs2.Bases, []string{fp}) { + t.Errorf("module = %+v, want the base's fingerprint", m) + } +} + +func mustEncode(t *testing.T, m delivery.Module) []byte { + t.Helper() + b, err := delivery.EncodeModule(m) + if err != nil { + t.Fatal(err) + } + return b +} + +func TestAnArtifactOfNoToolchainHasNoLanguageAndNoLock(t *testing.T) { // 1450/AC4 + in := inputsOf("ca-trust", catalogueManifest(t, "ca-trust"), "t", map[string]string{"go.sum": avahiGoSum}, noBases) + if in.Kept == nil || len(in.Kept.Locks) != 0 || len(in.Languages) != 0 { + t.Errorf("kept %+v languages %v, want neither locks nor languages for an archive", in.Kept, in.Languages) + } +} + +func TestAManifestTheParserRefusesLeavesNoInputsAndSaysWhy(t *testing.T) { // 1450/AC5 + for name, raw := range map[string]string{ + "not json": "not json", + "unknown field": strings.Replace(catalogueManifest(t, "avahi"), `"version": "1",`, `"version": "1", "scope": "everything",`, 1), + } { + in := inputsOf("avahi", raw, "t", nil, noBases) + if in.Kept != nil || !strings.HasPrefix(in.None2, "the manifest cannot be read: ") || len(in.None2) <= len("the manifest cannot be read: ") { + t.Errorf("%s: kept %+v none2 %q, want nil and 'the manifest cannot be read: '", name, in.Kept, in.None2) + } + } + if in := inputsOf("someone-else", catalogueManifest(t, "avahi"), "t", nil, noBases); in.Kept != nil || !strings.Contains(in.None2, "avahi") { + t.Errorf("a manifest of another module: kept %+v none2 %q", in.Kept, in.None2) + } +} + +func TestAContextIsNotPinned(t *testing.T) { // 1450 (the rule 1444 keeps: context not pinned, as today's form) + // build-agent's manifest from mesh-controller at f2d7bc94 has an image artifact built from a context in another repository. + in := inputsOf("build-agent", fixture(t, "controller-f2d7bc94-build-agent"), "t", nil, noBases) + if in.Kept == nil || in.None2 != None2Context { + t.Errorf("kept %+v none2 %q, want none2 %q", in.Kept, in.None2, None2Context) + } +}