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 TestAnArtifactWhoseLockFileTheMergeDidNotCarryHasNoLocksEntry(t *testing.T) { // 1459/AC1, AC3 // A Go bundle without a go.sum (a dependency-free or vendored module): no entry, so a fingerprint is possible (ADR 0469 clause 5). in := inputsOf("avahi", catalogueManifest(t, "avahi"), "avahitree", nil, noBases) if in.Kept == nil || len(in.Kept.Locks) != 0 { t.Fatalf("kept = %+v, want no Locks entry", in.Kept) } if !reflect.DeepEqual(in.Languages, map[string]string{"tools": "go"}) { t.Errorf("languages = %v, want tools: go (the toolchain still applies)", in.Languages) } // With the toolchain known, mesh-sdk gives such inputs a src2: value, never 'no lock'. if fp, none := delivery.SourceFingerprint2(in.Kept.With("avahi", map[string]delivery.Toolchain2{"tools": {Language: "go", Version: "1.25.0", Recipe: "1"}})); fp == "" || none != "" { t.Errorf("fingerprint = %q none %q, want a value", fp, none) } // A TypeScript bundle without package-lock.json (go.sum of another language does not count). ts := inputsOf("audit-logger", catalogueManifest(t, "audit-logger"), "t", map[string]string{"go.sum": "wrong"}, noBases) if ts.Kept == nil || len(ts.Kept.Locks) != 0 || !reflect.DeepEqual(ts.Languages, map[string]string{"code": "typescript"}) { t.Errorf("typescript: kept %+v languages %v", ts.Kept, ts.Languages) } } func TestAnArtifactWhoseLockFileTheMergeCarriedHasItsHash(t *testing.T) { // 1459/AC2, AC3 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}) { t.Errorf("typescript: kept %+v", ts.Kept) } // netcheck has a Go and a TypeScript bundle: each takes its own language's lock file, and one without it has none. 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) } goOnly := inputsOf("netcheck", catalogueManifest(t, "netcheck"), "t", map[string]string{"go.sum": "g"}, noBases) if goOnly.Kept == nil || !reflect.DeepEqual(goOnly.Kept.Locks, map[string]string{"tools-go": "g"}) { t.Errorf("netcheck without package-lock.json: kept %+v", goOnly.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: "novox/mesh-tools", // a base held of another repository stays when the catalogue merges (issue 1900) 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. // Its claim of the retired seat node-build-agent is read as a claim of a build seat: the parser refuses a retired seat // since R4g (mesh-sdk A9a), and the claim is not what this test is about. raw := strings.ReplaceAll(fixture(t, "controller-f2d7bc94-build-agent"), `"node-build-agent"`, `"node-build-go"`) in := inputsOf("build-agent", raw, "t", nil, noBases) if in.Kept == nil || in.None2 != None2Context { t.Errorf("kept %+v none2 %q, want none2 %q", in.Kept, in.None2, None2Context) } }