Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/modules_test.go
T

224 lines
12 KiB
Go

package index
import (
"context"
"encoding/json"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
const merge = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
// record is a build record as the controller writes it (mesh-sdk go/delivery/testdata/build-record-build-agent.json is
// the captured shape): SourceInputs under its Go field names, no json keys. inputs is that object's JSON.
func record(t *testing.T, id, module, repo, path, ref, state, at, inputs string) (string, []byte) {
t.Helper()
// an outcome has no state key (mesh-controller internal/chain/records.go:57-59); state asked is a request
raw := `{"id":"` + id + `",`
if state != "" {
raw += `"state":"` + state + `",`
}
raw += `"repository":"` + repo + `","ref":"` + ref + `","module":"` + module + `","commit":"7a74cee83f88e6585a5511c3cc53bdf0f5303d20","path":"` + path + `","at":"` + at + `"`
if inputs != "" {
raw += `,"source-fingerprint":"src1:ignored","source-inputs":` + inputs
}
raw += `}`
var check delivery.BuildRecord
if err := json.Unmarshal([]byte(raw), &check); err != nil {
t.Fatal(err)
}
return delivery.BuildKey(module, id), []byte(raw)
}
func modulesIndexer(t *testing.T, recs ...[2]any) (*Indexer, *memStore) {
t.Helper()
mods := newMem()
ix := &Indexer{Store: newMem(), Modules: mods, Builds: NewBuilds()}
for _, r := range recs {
ix.Builds.Put(r[0].(string), r[1].([]byte))
}
return ix, mods
}
func rec(t *testing.T, id, module, repo, path, ref, state, at, inputs string) [2]any {
k, v := record(t, id, module, repo, path, ref, state, at, inputs)
return [2]any{k, v}
}
func mergedMsg(repo string, trees map[string]string) delivery.Forge {
return delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: repo, Branch: "feat/x", Number: 12, Head: h1, Merge: merge, Root: "r0", Trees: trees}
}
func moduleOf(t *testing.T, st *memStore, key string) (delivery.Module, bool) {
t.Helper()
v, ok := st.vals[key]
if !ok {
return delivery.Module{}, false
}
m, err := delivery.DecodeModule(v)
if err != nil {
t.Fatal(err)
}
return m, true
}
const aInputs = `{"Module":"a","Tree":"oldtree","Bases":["registry.example/base@sha256:aaa"],"Contexts":null,"Toolchains":{"code":"go sha256:ccc 0123456789abcdef"},"Unpinned":""}`
func TestModuleFingerprintFromTheMergeTreeAndTheRecordedInputs(t *testing.T) { // 1239/AC1
ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs))
if err := ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree", "modules/b": "other"})); err != nil {
t.Fatal(err)
}
want, none := delivery.SourceFingerprint(delivery.SourceInputs{Module: "a", Tree: "newtree", Bases: []string{"registry.example/base@sha256:aaa"},
Toolchains: map[string]string{"code": "go sha256:ccc 0123456789abcdef"}})
if none != "" {
t.Fatal(none)
}
m, ok := moduleOf(t, mods, "a")
if !ok || m.FP != want || m.None != "" || m.Repository != "novox/mesh-delivery" || m.Commit != merge || m.Src != 0 {
t.Errorf("module = %+v (%v), want fp %s", m, ok, want)
}
if _, ok := mods.vals["b"]; ok {
t.Error("a key was written for a directory no build names")
}
}
func TestMergeOfTheRecordedTreeReproducesTheRecordedFingerprint(t *testing.T) { // 1239/AC2
// mailu, build-1792391134620131370: mesh-catalog at b4527d9091e972f28cb365623d9280045e7741a3, dir modules/mailu. Its
// inputs and recorded fingerprint are the live ones mesh-sdk go/delivery/fingerprint_test.go TestFingerprintLiveBuilds
// captured (its tree is `git rev-parse <commit>:modules/mailu`, its bases from the build's [bases] log line).
const tree = "cfc6fa8ff5fc1e220132e51ade653b9d3f387b3e"
const recorded = "src1:539f2e64000d436c572ef53b40e524012455945f8c5a1c6bb173ee0707e88ef4"
inputs := `{"Module":"mailu","Tree":"` + tree + `","Bases":["sha256:25f3cfeaceca14921366af4d1240b56457ef46273bdb508c7b0e8f469f6fd228"],"Contexts":null,` +
`"Toolchains":{"code":"typescript sha256:4b81682df5665c1f461d3f720a3a0fd7593f4aa71e4fc272dbfdc4bbd235b489 20125bd590041690"},"Unpinned":""}`
ix, mods := modulesIndexer(t, rec(t, "build-1792391134620131370", "mailu", "novox/mesh-catalog", "modules/mailu", "main", "", "2026-10-19T09:00:00Z", inputs))
if err := ix.Apply(context.Background(), mergedMsg("novox/mesh-catalog", map[string]string{"modules/mailu": tree})); err != nil {
t.Fatal(err)
}
if m, ok := moduleOf(t, mods, "mailu"); !ok || m.FP != recorded {
t.Errorf("module = %+v (%v), want fp %s", m, ok, recorded)
}
}
func TestANarrowContextHasNoFingerprint(t *testing.T) { // 1239/AC3, 1209/AC1
// A Go program's build source as the builder records it (build-agent's record, mesh-sdk go/delivery/testdata):
// the context "server" is "narrow:" and a hash.
inputs := `{"Module":"build-agent","Tree":"11c0b4f90cae0d6e4ef9cc48cbfdf9f6dbe52bca","Bases":["sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc"],"Contexts":{"server":"narrow:d51ca744fd11af59d616c858f76c27b3e58e16e11fbdc0c720eb9d7d19d55fac"},"Toolchains":null,"Unpinned":""}`
ix, mods := modulesIndexer(t, rec(t, "build-1", "build-agent", "novox/mesh-controller", "modules/build-agent", "main", "", "2026-10-19T09:00:00Z", inputs))
if err := ix.Apply(context.Background(), mergedMsg("novox/mesh-controller", map[string]string{"modules/build-agent": "newtree"})); err != nil {
t.Fatal(err)
}
m, ok := moduleOf(t, mods, "build-agent")
if !ok || m.FP != "" || !strings.Contains(m.None, "Go program") || !strings.Contains(m.None, "release 4") {
t.Errorf("module = %+v (%v), want none naming the Go program's build source and release 4", m, ok)
}
}
func TestUnpinnedBuildHasNoFingerprint(t *testing.T) { // 1239/AC4
inputs := `{"Module":"a","Tree":"t","Bases":null,"Contexts":null,"Toolchains":null,"Unpinned":"the build resolves packages when built"}`
ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", inputs))
_ = ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"}))
if m, ok := moduleOf(t, mods, "a"); !ok || m.FP != "" || m.None != "the build resolves packages when built" {
t.Errorf("module = %+v (%v)", m, ok)
}
}
func TestARootTreeContextHasNoFingerprint(t *testing.T) { // 1239/AC5
inputs := `{"Module":"a","Tree":"t","Bases":null,"Contexts":{"web":"f00dcafef00dcafef00dcafef00dcafef00dcafe"},"Toolchains":null,"Unpinned":""}`
ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", inputs))
_ = ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"}))
m, ok := moduleOf(t, mods, "a")
if !ok || m.FP != "" || !strings.Contains(m.None, "root tree") || !strings.Contains(m.None, "release 4") {
t.Errorf("module = %+v (%v)", m, ok)
}
}
func TestDirectoryGoneFromTheMergeHasNoTree(t *testing.T) { // 1239/AC6
ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs))
_ = ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/other": "t"}))
if m, ok := moduleOf(t, mods, "a"); !ok || m.FP != "" || m.None != "no tree" {
t.Errorf("module = %+v (%v)", m, ok)
}
}
func TestAModuleWithNoSuccessfulTrunkBuildHasNone(t *testing.T) { // 1239/AC7
ix, mods := modulesIndexer(t,
rec(t, "build-1", "failed-only", "novox/mesh-delivery", "modules/f", "main", "failed", "2026-10-19T09:00:00Z", aInputs),
rec(t, "build-2", "branch-only", "novox/mesh-delivery", "modules/g", "feat/x", "", "2026-10-19T09:00:00Z", aInputs),
rec(t, "build-3", "no-inputs", "novox/mesh-delivery", "modules/h", "main", "", "2026-10-19T09:00:00Z", ""),
)
_ = ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/f": "t", "modules/g": "t", "modules/h": "t"}))
for _, name := range []string{"failed-only", "branch-only", "no-inputs"} {
if m, ok := moduleOf(t, mods, name); !ok || m.FP != "" || !strings.Contains(m.None, "no successful trunk build") {
t.Errorf("%s: module = %+v (%v)", name, m, ok)
}
}
}
func TestTheNewestSuccessfulTrunkBuildCounts(t *testing.T) { // 1239 (the newest key is only a hint)
older := strings.Replace(aInputs, `"Bases":["registry.example/base@sha256:aaa"]`, `"Bases":["registry.example/base@sha256:old"]`, 1)
ix, mods := modulesIndexer(t,
rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", older),
rec(t, "build-2", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T10:00:00Z", aInputs),
rec(t, "build-3", "a", "novox/mesh-delivery", "modules/a", "main", "failed", "2026-10-19T11:00:00Z", older),
rec(t, "build-4", "a", "novox/mesh-delivery", "modules/a", "feat/x", "", "2026-10-19T12:00:00Z", older),
)
_ = ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"}))
want, _ := delivery.SourceFingerprint(delivery.SourceInputs{Module: "a", Tree: "newtree", Bases: []string{"registry.example/base@sha256:aaa"},
Toolchains: map[string]string{"code": "go sha256:ccc 0123456789abcdef"}})
if m, _ := moduleOf(t, mods, "a"); m.FP != want {
t.Errorf("fp = %s, want the 10:00 trunk build's %s", m.FP, want)
}
}
func TestModulesOfOtherRepositoriesAreNotWritten(t *testing.T) { // 1239/AC8
ix, mods := modulesIndexer(t,
rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs),
rec(t, "build-2", "c", "novox/mesh-catalog", "modules/c", "main", "", "2026-10-19T09:00:00Z", strings.Replace(aInputs, `"Module":"a"`, `"Module":"c"`, 1)))
_ = ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "t", "modules/c": "t"}))
if _, ok := mods.vals["c"]; ok {
t.Error("the key of a module of another repository was written")
}
if _, ok := mods.vals["a"]; !ok {
t.Error("the key of the merged repository's module was not written")
}
}
func TestAnUnchangedModuleWritesNothing(t *testing.T) { // 1239 (unchanged value writes nothing)
ix, mods := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs))
m := mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"})
_ = ix.Apply(context.Background(), m)
rev, writes := mods.revs["a"], mods.writes
_ = ix.Apply(context.Background(), m)
if mods.revs["a"] != rev || mods.writes != writes {
t.Errorf("the same merge wrote again (%d writes, was %d)", mods.writes, writes)
}
}
func TestMergedMessageRemovesThePullFromItsGroupToo(t *testing.T) { // 1238/AC5 with 1239 (both effects of one message)
ix, _ := modulesIndexer(t, rec(t, "build-1", "a", "novox/mesh-delivery", "modules/a", "main", "", "2026-10-19T09:00:00Z", aInputs))
cands := ix.Store.(*memStore)
_ = ix.Apply(context.Background(), pull("novox/mesh-delivery", 12, "feat/x", h1, "modules/a"))
_ = ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"}))
if _, ok := cands.vals["feat/x"]; ok {
t.Error("the merged pull request is still a candidate")
}
}
func TestARecordAsTheControllerWritesItGivesAFingerprint(t *testing.T) { // 1281/AC8
// The layout of mesh-controller internal/chain/records.go Build (commit 1f708e2d8d72): an outcome has no state key, a build
// asked by a plan after a merge no ref, and the repository is the clone URL.
raw := `{"id":"build-1","repository":"http://novox.internal:20000/novox/mesh-delivery.git","module":"a","commit":"7a74cee83f88e6585a5511c3cc53bdf0f5303d20",` +
`"path":"modules/a","source-fingerprint":"src1:ignored","source-inputs":` + aInputs + `,"at":"2026-10-19T09:00:00Z"}`
ix, mods := modulesIndexer(t, [2]any{delivery.BuildKey("a", "build-1"), []byte(raw)})
if err := ix.Apply(context.Background(), mergedMsg("novox/mesh-delivery", map[string]string{"modules/a": "newtree"})); err != nil {
t.Fatal(err)
}
m, ok := moduleOf(t, mods, "a")
if !ok || m.FP == "" || m.None != "" {
t.Errorf("module = %+v (%v), want a fingerprint, not none", m, ok)
}
}