Files
mesh-delivery/modules/mesh-delivery-indexer/manifest_test.go
T
jschoubben 1592abbf4f DLV-I5: the indexer keeps each package's series, its newest numbered and newest published trunk version
A user takes the newest of the series it accepts (ADR 0609 §1), so the
indexer learns versions from the builder's numbered and the registry seats'
published, one key <package>.<series> beside the per-module digests.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 13:42:33 +01:00

294 lines
13 KiB
Go

package manifest
import (
"bytes"
"encoding/json"
"io/fs"
"os"
"path/filepath"
"reflect"
"regexp"
"slices"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-delivery/sdk/graph"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-sdk/go/views"
"mesh-delivery-indexer/internal/index"
)
// The controller's manifest reader is mesh-controller internal/catalogue (Manifest, manifest.go: Consumes :521,
// Invokes :590, Streams :613, Reads :659; StreamDeclaration, streams.go), an internal package of another repository.
// These tests decode the manifest strictly into the fields those types name (DisallowUnknownFields) and hold the
// rules of issue 1238 (ADR 0437 §4). The merge gate's module check proves the controller takes the manifest.
type state struct {
Name string `json:"name"`
History int `json:"history,omitempty"`
}
type artifact struct {
Name string `json:"name"`
Kind string `json:"kind"`
Language string `json:"language"`
System string `json:"system"`
From string `json:"from"`
Binary string `json:"binary"`
Loads []string `json:"loads"`
Env map[string]string `json:"env"`
}
type stream struct {
Name string `json:"name"`
Events []string `json:"events"`
MaxAgeDays int `json:"max-age-days"`
MaxMiB int `json:"max-mib"`
}
type manifest struct {
Module string `json:"module"`
Version string `json:"version"`
Slug string `json:"slug"`
RunsAs string `json:"runs-as"`
Streams []stream `json:"streams"`
Claims []struct {
Name string `json:"name"`
Scope string `json:"scope"`
Serves []string `json:"serves"`
} `json:"claims"`
Tools []string `json:"tools"`
Emits []string `json:"emits"`
Consumes []string `json:"consumes"`
State []state `json:"state"`
Reads []string `json:"reads"`
Invokes []string `json:"invokes"`
OwnSecrets map[string]string `json:"own-secrets"`
SecretsOwner string `json:"secrets-owner"`
Resources []map[string]any `json:"resources"`
Build struct {
Artifacts []artifact `json:"artifacts"`
} `json:"build"`
}
func load(t *testing.T) (manifest, map[string]json.RawMessage) {
t.Helper()
raw, err := os.ReadFile("module.json")
if err != nil {
t.Fatal(err)
}
var m manifest
dec := json.NewDecoder(bytes.NewReader(raw))
dec.DisallowUnknownFields()
if err := dec.Decode(&m); err != nil {
t.Fatalf("the controller's reader refuses a key this manifest has: %v", err)
}
var top map[string]json.RawMessage
if err := json.Unmarshal(raw, &top); err != nil {
t.Fatal(err)
}
return m, top
}
func sorted(s []string) []string {
s = slices.Clone(s)
slices.Sort(s)
return s
}
func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build modules' toolchains)
m, top := load(t)
if m.Module != "mesh-delivery-indexer" {
t.Errorf("module = %q", m.Module)
}
// 1126: placement is the seat mesh-placement's (ADR 0567): the indexer holds no placement state. ADR 0578 §1: it says
// a refused manifest as manifest-refused, and (issue 1966 D3, 1966/AC10) a newly accepted one asking for bus state as
// manifest-accepted.
if !reflect.DeepEqual(m.Emits, []string{"manifest-refused", "manifest-accepted"}) {
t.Errorf("emits = %v, want manifest-refused and manifest-accepted", m.Emits)
}
// 1126 note 76: heard keeps the last 64 FORGE messages the indexer handled, for indexer_heard.
if want := []state{{Name: "candidates", History: 10}, {Name: "heard", History: 64}, {Name: "manifests", History: 1}, {Name: "modules", History: 20}, {Name: "predefined", History: 5}, {Name: "config-candidates", History: 5}, {Name: "taken", History: 1}, {Name: "takeins", History: 1}, {Name: "verdicts", History: 5}, {Name: "oldsections", History: 1}, {Name: delivery.PackagesState, History: delivery.PackagesHistory},
{Name: graph.State, History: graph.History}, {Name: views.MarksState, History: 1}, {Name: health.FindingsState, History: 5}, {Name: views.StatusState, History: 1},
{Name: index.RequirementsState, History: 5}}; !reflect.DeepEqual(m.State, want) { // DLV-I7
t.Errorf("state = %+v, want %+v", m.State, want)
}
// DLV-F1r (ADR 0615 §1): the assigner's placement is read through its first view and views family, never its state.
if want := []string{"mesh-build-go.toolchains", "mesh-build-node.toolchains", "mesh-delivery-builder.build-log", "mesh-delivery-checker.previews", "mesh-delivery-gate.source"}; !reflect.DeepEqual(sorted(m.Reads), want) {
t.Errorf("reads = %v, want %v", m.Reads, want)
}
// "Reads FORGE" is a consumes of the indexer's stream: the broker grants only buckets by reads (mesh-control
// internal/broker/state.go:62-93).
// The registry seats' published and the builder's numbered fill each package's series (DLV-I5, design 51 *The versions*).
if want := []string{"go-package-registry.published", "mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-delivery-assigner.views.placement",
"mesh-delivery-builder.version.numbered", "mesh-delivery-checker.preview.written", "mesh-delivery-forge-bridge.forge.**", "npm-package-registry.published"}; !reflect.DeepEqual(sorted(m.Consumes), want) {
t.Errorf("consumes = %v, want %v", m.Consumes, want)
}
if b, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 || b != "${dir:state}/broker" {
t.Errorf("own-secrets = %v, want exactly broker", m.OwnSecrets)
}
if m.SecretsOwner != "mesh-delivery-indexer" {
t.Errorf("secrets-owner = %q", m.SecretsOwner)
}
// Three invokes: the file of a registered module's manifest and lock files (registered.go), the forge's trees (the named paths' objects, since the indexer is the one reader of a manifest's paths:
// ADR 0499 clauses 2 and 3, ADR 0495 clause 1; and the module directories of the trunk, issue 1900) and whether a
// commit is in another (issue 1900).
if want := []string{"mesh-delivery-assigner.placement", "gitea.gitea_commit_trees", "gitea.gitea_contains", "gitea.gitea_get_file", "gitea.gitea_open_pulls", "seat:node-engine.composition"}; !reflect.DeepEqual(m.Invokes, want) {
t.Errorf("invokes = %v, want %v", m.Invokes, want)
}
// 1945: indexer_take_in, served by its tools bundle through the takeins state; the process serves none. The same bundle
// reads the verdicts state (indexer_verdicts) and asks the indexer to judge again through the takeins state
// (indexer_judge; issue 774, clause 3). 1966/AC11: indexer_old_sections reads the count of the old sections (state oldsections).
// ADR 0621 §1, ADR 0622 §1: the first-view tools manifests, modules and verdicts, and design 25 § 12.2's nine read
// tools, all of the tools bundle, which runs in a runtime of its own on the indexer's account (runs-as).
want := []string{"indexer_take_in", "indexer_old_sections", "indexer_verdicts", "indexer_judge"}
for _, v := range append(append([]index.ViewedTool{}, index.FirstViewTools...), index.ReadTools...) {
want = append(want, v.Tool)
if _, ok := index.ViewedStates[v.State]; !ok {
t.Errorf("%s answers the state %s, which the views family does not say", v.Tool, v.State)
}
}
want = append(want, "indexer_first_views", "indexer_first_view_again", index.ConfigReadersTool, index.BusStateTool, index.RequirementsTool) // DLV-F3r, DLV-I10, DLV-I11, DLV-I7
if !reflect.DeepEqual(m.Tools, want) || len(want) != 22 {
t.Errorf("tools = %v, want %v", m.Tools, want)
}
if m.RunsAs != m.Module || m.SecretsOwner != m.RunsAs {
t.Errorf("runs-as = %q, secrets-owner %q: the tools bundle runs on the indexer's own account (ADR 0621 §1)", m.RunsAs, m.SecretsOwner)
}
// ADR 0621 §2, ADR 0622 §1: each first-view tool answers exactly its readers; the indexer is on none.
callers := map[string]string{"manifests": "mesh-grants,node-tools,mesh-delivery-assigner", "modules": "mesh-grants,node-tools,mesh-delivery-assigner", "verdicts": "mesh-grants"}
for _, a := range m.Build.Artifacts {
if a.Name != "tools" {
continue
}
if len(a.Env) != len(callers) {
t.Errorf("tools env = %v, want only the three callers lists", a.Env)
}
for tool, list := range callers {
if got := a.Env[views.CallersEnv(tool)]; got != list {
t.Errorf("%s callers = %q, want %q", tool, got, list)
}
if views.ParseCallers(a.Env[views.CallersEnv(tool)]).Allows("n1." + m.Module) {
t.Errorf("%s answers the indexer itself", tool)
}
}
}
// ADR 0615 §5, ADR 0619 §2: its views family on its old streams section, 7 days and 256 MiB, dropping its oldest.
if want := []stream{{Name: index.ViewsStream, Events: []string{views.EventPattern}, MaxAgeDays: views.StreamDays, MaxMiB: views.StreamMiB}}; !reflect.DeepEqual(m.Streams, want) {
t.Errorf("streams = %+v, want %+v", m.Streams, want)
}
// ADR 0604 §2: it holds the seat mesh-catalog, serving its four verbs from its tools bundle (which stays without a
// request subject).
if len(m.Claims) != 1 || m.Claims[0].Name != catalogue.CatalogSeat || m.Claims[0].Scope != catalogue.ScopeMesh ||
!slices.Equal(m.Claims[0].Serves, []string{"modules", "module", "provides", "dependents"}) {
t.Errorf("claims = %+v, want mesh-catalog with its four verbs", m.Claims)
}
// No bus, no other seat (ADR 0437 §4).
for _, k := range []string{"logs", "provides", "requires", "uses", "defines-seats", "seat", "seats", "bus"} {
if _, ok := top[k]; ok {
t.Errorf("the manifest declares %q: the indexer has none", k)
}
}
}
func TestManifestProcess(t *testing.T) { // 1238/AC1
m, _ := load(t)
var procs []map[string]any
for _, r := range m.Resources {
if r["type"] == "process" {
procs = append(procs, r)
}
if r["type"] == "tool" || r["type"] == "seat" {
t.Errorf("resource %v: the indexer serves no tool and holds no seat", r)
}
}
if len(procs) != 1 {
t.Fatalf("%d process resources, want one", len(procs))
}
p := procs[0]
if p["artifact"] != "program" || !reflect.DeepEqual(p["run"], []any{"./mesh-delivery-indexer"}) {
t.Errorf("process = %v", p)
}
env, _ := p["env"].(map[string]any)
if len(env) != 1 || env["MESH_BROKER_FILE"] != "${dir:state}/broker" {
t.Errorf("env = %v", p["env"])
}
if _, bus := p["bus"]; bus {
t.Errorf("the process declares a bus: it runs on its own broker credential")
}
if len(m.Build.Artifacts) != 2 {
t.Fatalf("artifacts = %+v, want the program and the tools", m.Build.Artifacts)
}
if a := m.Build.Artifacts[1]; a.Name != "tools" || a.Kind != "bundle" || a.Binary != "mesh-delivery-indexer-tools" || !reflect.DeepEqual(a.Loads, []string{"mesh-delivery-indexer-tools"}) {
t.Errorf("tools artifact = %+v", a) // 1945
} else if st, err := os.Stat(a.From); err != nil || !st.IsDir() {
t.Errorf("the tools' source %s: %v", a.From, err)
}
a := m.Build.Artifacts[0]
if st, err := os.Stat(a.From); err != nil || !st.IsDir() || a.Binary != "mesh-delivery-indexer" || !strings.HasSuffix(a.From, a.Binary) {
t.Errorf("artifact = %+v", a)
}
}
func TestServesNoRequestSubjectAndHoldsNoTicker(t *testing.T) { // 1238/AC9 (ADR 0437 §8)
forbidden := []string{"Subscribe(", "QueueSubscribe", "ChanSubscribe", "time.NewTicker", "time.Tick", "time.NewTimer", "time.AfterFunc", "time.After(", "time.Sleep"}
var files int
for _, root := range []string{"cmd", "internal"} {
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return err
}
files++
src, err := os.ReadFile(path)
if err != nil {
return err
}
for _, f := range forbidden {
if strings.Contains(string(src), f) {
t.Errorf("%s holds %s: the indexer serves no request subject and holds no timer", path, f)
}
}
return nil
})
if err != nil {
t.Fatal(err)
}
}
if files < 2 {
t.Fatalf("read %d source files, want the program's", files)
}
}
// Every forge tool the program calls is granted in invokes, or the bus refuses the call (issue 1900).
func TestEveryGiteaToolTheCodeCallsIsInInvokes(t *testing.T) {
m, _ := load(t)
files, err := filepath.Glob("cmd/mesh-delivery-indexer/*.go")
if err != nil || len(files) == 0 {
t.Fatalf("no program files: %v", err)
}
re := regexp.MustCompile(`gitea\.gitea_[a-z_]+`)
seen := 0
for _, f := range files {
if strings.HasSuffix(f, "_test.go") {
continue
}
src, err := os.ReadFile(f)
if err != nil {
t.Fatal(err)
}
for _, tool := range re.FindAllString(string(src), -1) {
seen++
if !slices.Contains(m.Invokes, tool) {
t.Errorf("%s calls %s, which invokes does not grant", f, tool)
}
}
}
if seen < 2 {
t.Errorf("found %d tool names in the program, want at least the two it calls", seen)
}
}