package manifest import ( "bytes" "encoding/json" "io/fs" "os" "path/filepath" "reflect" "slices" "strings" "testing" ) // 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 1607 (ADR 0453 §4, ADR 0478). 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"` } type manifest struct { Module string `json:"module"` Version string `json:"version"` Slug string `json:"slug"` Consumes []string `json:"consumes"` State []state `json:"state"` Reads []string `json:"reads"` Uses []string `json:"uses"` Invokes []string `json:"invokes"` Config map[string]struct { Type string `json:"type"` Description string `json:"description"` Pattern string `json:"pattern"` Default string `json:"default"` } `json:"config"` 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) { // 1607/AC1 m, top := load(t) if m.Module != "mesh-delivery-builder" { t.Errorf("module = %q", m.Module) } if want := []state{{Name: "builds", History: 5}, {Name: "instances", History: 1}}; !reflect.DeepEqual(m.State, want) { // delivery.BuilderBuildsHistory t.Errorf("state = %+v, want %+v", m.State, want) } // 1887/A4c: the builder chooses no node, so it reads neither presence nor have, invokes no build module's tool and // holds no capacity; it puts asks on the three build seats and reads their steps (ADR 0525 §1, §4, §5). if want := []string{"mesh-build-go.steps", "mesh-build-go.toolchains", "mesh-build-image.steps", "mesh-build-node.steps", "mesh-build-node.toolchains", "mesh-controller.assignments", "mesh-delivery-indexer.candidates", "mesh-delivery-indexer.manifests", "mesh-delivery-indexer.modules"}; !reflect.DeepEqual(sorted(m.Reads), want) { t.Errorf("reads = %v, want %v", m.Reads, want) } if want := []string{"node-build-go", "node-build-image", "node-build-node"}; !reflect.DeepEqual(sorted(m.Uses), want) { t.Errorf("uses = %v, want the three build seats", m.Uses) } // 1937/AC3: the builder asks the controller whether the artifact store still holds a done key's blobs (held), and no // other tool; it holds no capacity. if want := []string{"seat:mesh-controller.held"}; !reflect.DeepEqual(m.Invokes, want) || len(m.Config) != 0 { t.Errorf("invokes = %v, config = %v, want %v and no config", m.Invokes, m.Config, want) } // The builder is woken by a build module's report of a toolchain, as the indexer is: the bucket may not exist when it // starts; and by the controller's artifacts-gone, a take-in refused for a gone blob (1937/AC4). if want := []string{"mesh-build-go.toolchain.reported", "mesh-build-node.toolchain.reported", "mesh-controller.artifacts-gone"}; !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-builder" { t.Errorf("secrets-owner = %q", m.SecretsOwner) } for _, k := range []string{"streams", "logs", "emits", "claims", "provides", "requires", "defines-seats", "tools", "seat", "seats", "bus", "config"} { if _, ok := top[k]; ok { t.Errorf("the manifest declares %q: the builder has none", k) } } } func TestManifestProcess(t *testing.T) { // 1607/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 builder 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-builder"}) { 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, want the broker file alone", p["env"]) } for _, r := range m.Resources { if r["id"] == "settings" { t.Errorf("a settings resource %v: the builder holds no setting since the capacity is the build modules' (ADR 0525 §5)", r) } } if _, bus := p["bus"]; bus { t.Errorf("the process declares a bus: it runs on its own broker credential") } if len(m.Build.Artifacts) != 1 { t.Fatalf("artifacts = %+v, want the program alone", m.Build.Artifacts) } a := m.Build.Artifacts[0] if st, err := os.Stat(a.From); err != nil || !st.IsDir() || a.Binary != "mesh-delivery-builder" || !strings.HasSuffix(a.From, a.Binary) || a.Kind != "bundle" || a.Language != "go" { t.Errorf("artifact = %+v", a) } } func TestServesNoRequestSubjectAndHoldsNoTicker(t *testing.T) { // 1607/AC6 (ADR 0437 §8: no timer, a wait is a context's bound) 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 builder serves no request subject and holds no timer", path, f) } } return nil }) if err != nil { t.Fatal(err) } } if files < 1 { t.Fatalf("read %d source files, want the program's", files) } }