Files
mesh-delivery/modules/mesh-delivery-indexer/internal/index/catalog_test.go
T
2026-11-03 23:11:21 +01:00

141 lines
5.2 KiB
Go

package index
import (
"context"
"encoding/json"
"os"
"path/filepath"
"reflect"
"slices"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/sdk/graph"
)
// capturedKeys are the field names of the first object at path (a list's first element when the value is a list) in the
// old catalog module's answer captured with mesh_call on 2026-11-03 (testdata/catalog; trimmed to a few modules, and the
// manifest of catalog_module to three of its keys).
func capturedKeys(t *testing.T, file string, path ...string) []string {
t.Helper()
raw, err := os.ReadFile(filepath.Join("testdata", "catalog", file))
if err != nil {
t.Fatal(err)
}
return keysAt(t, raw, path...)
}
func keysAt(t *testing.T, raw []byte, path ...string) []string {
t.Helper()
var v any
if err := json.Unmarshal(raw, &v); err != nil {
t.Fatal(err)
}
for _, p := range path {
v = v.(map[string]any)[p]
if l, ok := v.([]any); ok {
v = l[0]
}
}
var out []string
for k := range v.(map[string]any) {
out = append(out, k)
}
slices.Sort(out)
return out
}
// catalogVerbsAnswerFromTheStates: the seat mesh-catalog's four verbs answer from the indexer's modules, manifests and
// graph, with the field names of the old catalog module's answers (ADR 0604 §1, §2).
func catalogVerbsAnswerFromTheStates(t *testing.T) {
withAcme := strings.Replace(renamed(t, "audit-logger", "acme"), `"module": "acme",`,
`"module": "acme", "provides": [{"name": "internal-acme-ca", "scope": "mesh", "answered-by": "helper"}],`, 1)
ix, mods := newFormIndexer(t, mod{"mesh-tools", renamed(t, "avahi", "mesh-tools")},
mod{"avahi", standingOn(t, catalogueManifest(t, "avahi"), "mesh-tools")},
mod{"ca-trust", catalogueManifest(t, "ca-trust")}, mod{"acme", withAcme})
reportToolchain(t, ix, "mesh-build-go", "novox", "go", "go1.27.1")
c := CatalogRead{
Modules: func() (map[string]delivery.Module, error) {
out := map[string]delivery.Module{}
for k := range mods.vals {
m, _ := moduleOf(t, mods, k)
out[k] = m
}
return out, nil
},
Manifest: func(digest string) (string, error) {
raw, _, err := ix.manifestRaw(context.Background(), digest)
return raw, err
},
Graph: func() (graph.Graph, error) {
g := graph.Graph{}
for k := range ix.Graph.(*memStore).vals {
g[k] = graphOf(t, ix, k)
}
return g, nil
},
}
answer := func(v any, err error) []byte {
t.Helper()
if err != nil {
t.Fatal(err)
}
raw, _ := json.Marshal(v)
return raw
}
contains := func(got, want []string) bool {
for _, w := range want {
if !slices.Contains(got, w) {
return false
}
}
return true
}
// The tools bundle serves the four verbs as mesh-catalog.<verb>, answering from the same states.
var names []string
for _, tool := range CatalogTools(c) {
names = append(names, tool.Name)
if tool.Name == "mesh-catalog.modules" {
if got, err := tool.Run(nil); err != nil || !strings.Contains(string(answer(got, nil)), `"module":"avahi"`) {
t.Errorf("mesh-catalog.modules answered %v, %v", got, err)
}
}
}
if !slices.Equal(names, []string{"mesh-catalog.modules", "mesh-catalog.module", "mesh-catalog.provides", "mesh-catalog.dependents"}) {
t.Errorf("the tools bundle serves %v", names)
}
raw := answer(c.CatalogModulesAnswer())
if got, want := keysAt(t, raw, "modules"), capturedKeys(t, "catalog_modules.json", "modules"); !contains(got, want) {
t.Errorf("modules' fields %v, want the old catalog's %v", got, want)
}
if !strings.Contains(string(raw), `"module":"avahi"`) || !strings.Contains(string(raw), `"fp":"src2:`) {
t.Errorf("modules = %s", raw)
}
raw = answer(c.CatalogModuleAnswer("ca-trust"))
want := slices.DeleteFunc(capturedKeys(t, "catalog_module.json", "module"), func(k string) bool { return k == "ref" }) // the trunk's, no ref
if got := keysAt(t, raw, "module"); !contains(got, want) || !strings.Contains(string(raw), `"internal-acme-ca"`) {
t.Errorf("module ca-trust = %s, want the fields %v and its manifest", raw, want)
}
if _, err := c.CatalogModuleAnswer("gone"); err == nil || !strings.Contains(err.Error(), "no module gone") {
t.Errorf("a module the trunk does not hold: %v", err)
}
raw = answer(c.CatalogProvidesAnswer("internal-acme-ca"))
if got, want := keysAt(t, raw, "provides"), capturedKeys(t, "catalog_provides.json", "provides"); !reflect.DeepEqual(got, want) ||
!reflect.DeepEqual(keysAt(t, raw, "requires"), capturedKeys(t, "catalog_provides.json", "requires")) ||
!strings.Contains(string(raw), `"provides":[{"module":"acme","commit":"`+merge+`","scope":"mesh"}]`) ||
!strings.Contains(string(raw), `"requires":[{"module":"ca-trust"`) {
t.Errorf("provides internal-acme-ca = %s", raw)
}
raw = answer(c.CatalogDependentsAnswer("mesh-tools"))
if got, want := keysAt(t, raw), capturedKeys(t, "catalog_dependents.json"); !reflect.DeepEqual(got, want) ||
!strings.Contains(string(raw), `"dependents":[{"module":"avahi"`) {
t.Errorf("dependents of mesh-tools = %s", raw)
}
raw = answer(c.CatalogDependentsAnswer("mesh-build-go")) // the build module reaches every Go module it builds
if !strings.Contains(string(raw), `"avahi"`) || !strings.Contains(string(raw), `"mesh-tools"`) {
t.Errorf("dependents of mesh-build-go = %s", raw)
}
}