Files
mesh-delivery/modules/mesh-delivery-view/manifest_test.go
T
jschoubben d309bb6cef
mesh/merge-gate head d309bb6cef45 merged into trunk 253f464a8155 — pass: builds mesh-delivery-view → shanks; no bus step; every machine composes with the c…
mesh/repo-check head d309bb6cef45 merged into trunk 253f464a8155 — pass: its merge-check.sh passed
mesh/delivery delivered
Put the view's socket in /var/lib/mesh-delivery-view-cli: the node-engine never places a module's directory below /run (issue 1134)
2026-10-19 01:53:11 +02:00

249 lines
8.7 KiB
Go

package manifest
import (
"bytes"
"encoding/json"
"os"
"reflect"
"slices"
"strings"
"testing"
)
// The controller's manifest reader is mesh-controller internal/catalogue (ParseManifest), an internal package of another
// repository, which this module cannot import. These tests therefore decode the manifest strictly into the fields the
// controller's Manifest, Build and Artifact types name (internal/catalogue/manifest.go:475, :602, :659, :667, :737,
// :809, :821, :892, :1010, :1065-:1090), as its parseManifest does (DisallowUnknownFields, manifest.go:1744), and check
// the rules the issue names, and two rules of the controller's module check (a module with templates packs a files
// archive; a module contributing an item is moved). **The merge gate's module check is what proves the controller
// takes this manifest (1134/AC7); these tests hold the rules so a unit run catches a refusal early.**
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"`
Context *struct {
Repository string `json:"repository"`
} `json:"context"`
}
type item struct {
Collection string `json:"collection"`
Template string `json:"template"`
}
type manifest struct {
Module string `json:"module"`
Moved bool `json:"moved"`
Version string `json:"version"`
Slug string `json:"slug"`
Reads []string `json:"reads"`
OwnSecrets map[string]string `json:"own-secrets"`
SecretsOwner string `json:"secrets-owner"`
Resources []map[string]any `json:"resources"`
Items []item `json:"items"`
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 TestManifestIsTakenAndReadsOnly(t *testing.T) { // 1134/AC7
m, top := load(t)
if m.Module != "mesh-delivery-view" {
t.Errorf("module = %q", m.Module)
}
want := []string{"mesh-controller.assignments", "mesh-controller.builds", "mesh-delivery-watchman.presence", "mesh-delivery-watchman.stuck", "node-engine.have"} // 1168/AC1
got := slices.Clone(m.Reads)
slices.Sort(got)
if !reflect.DeepEqual(got, want) {
t.Errorf("reads = %v, want exactly %v", got, want)
}
for _, k := range []string{"state", "emits", "consumes", "provides", "claims"} {
if _, ok := top[k]; ok {
t.Errorf("the manifest declares %q: the view writes nothing", k)
}
}
if _, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 {
t.Errorf("own-secrets = %v, want exactly broker (or no credential is issued)", m.OwnSecrets)
}
if m.SecretsOwner != "mesh-delivery-view" {
t.Errorf("secrets-owner = %q", m.SecretsOwner)
}
}
func TestManifestIsWholeInItsRepository(t *testing.T) { // 1134/AC7
m, _ := load(t)
if len(m.Build.Artifacts) != 3 {
t.Fatalf("artifacts = %+v", m.Build.Artifacts)
}
for _, a := range m.Build.Artifacts {
if a.Name == "files" {
continue
}
if a.Context != nil {
t.Errorf("artifact %s names a context of another repository", a.Name)
}
if a.Kind != "bundle" || a.Language != "go" || a.System != "arch" {
t.Errorf("artifact %s = %+v, want a go bundle for arch", a.Name, a)
}
if st, err := os.Stat(a.From); err != nil || !st.IsDir() {
t.Errorf("artifact %s builds from %q, which is not a directory of this module", a.Name, a.From)
}
if a.Binary == "" || !strings.HasSuffix(a.From, a.Binary) {
t.Errorf("artifact %s binary %q does not match %q", a.Name, a.Binary, a.From)
}
}
names := map[string]string{}
for _, a := range m.Build.Artifacts {
names[a.Name] = a.Binary
}
if names["program"] != "mesh-delivery-view" || names["cli"] != "mesh-delivery-cli" {
t.Errorf("artifacts = %v", names)
}
}
func TestManifestResources(t *testing.T) { // 1134/AC7
m, _ := load(t)
byType := map[string][]map[string]any{}
for _, r := range m.Resources {
byType[r["type"].(string)] = append(byType[r["type"].(string)], r)
}
for _, ty := range []string{"user", "directory", "process", "archive"} {
if len(byType[ty]) == 0 {
t.Fatalf("no %s resource", ty)
}
}
user := byType["user"][0]["name"]
if user != "mesh-delivery-view" {
t.Errorf("user = %v", user)
}
p := byType["process"][0]
if p["user"] != user || p["artifact"] != "program" {
t.Errorf("process = %v", p)
}
env, _ := p["env"].(map[string]any)
if env["MESH_BROKER_FILE"] != "${dir:state}/broker" || m.OwnSecrets["broker"] != "${dir:state}/broker" {
t.Errorf("the process reads %v, the own-secret is written at %v", env["MESH_BROKER_FILE"], m.OwnSecrets["broker"])
}
if h, _ := p["health"].(map[string]any); h["kind"] != "unit" {
t.Errorf("health = %v", p["health"])
}
var state, run bool
for _, d := range byType["directory"] {
if d["id"] == "state" && d["mode"] == "0700" && d["owner"] == user {
state = true
}
if d["path"] == "/var/lib/mesh-delivery-view-cli" && d["mode"] == "0755" && d["owner"] == user {
run = true
}
}
if !state || !run {
t.Errorf("state directory 0700 owned by the user: %v; run directory for the socket 0755: %v", state, run)
}
a := byType["archive"][0]
if a["artifact"] != "cli" || a["path"] != "/usr/local/lib/mesh-delivery-view" {
t.Errorf("archive = %v, want the cli unpacked into its own directory", a)
}
}
func TestModuleCheckRules(t *testing.T) { // 1134/AC7
m, _ := load(t)
if len(m.Items) > 0 && !m.Moved {
t.Error("a module contributing an item is marked moved: true")
}
var files bool
for _, a := range m.Build.Artifacts {
files = files || (a.Name == "files" && a.Kind == "archive" && a.From == "files")
}
if len(m.Items) > 0 && !files {
t.Error("a module naming templates packs a files archive from files/")
}
}
func TestAliasItem(t *testing.T) { // 1137/AC1
m, _ := load(t)
if !reflect.DeepEqual(m.Items, []item{{Collection: "shell:zsh:normal", Template: "files/zsh-normal"}}) {
t.Fatalf("items = %+v", m.Items)
}
raw, err := os.ReadFile("files/zsh-normal")
if err != nil {
t.Fatal(err)
}
if string(raw) != "alias nox-delivery=mesh-delivery-cli\n" {
t.Fatalf("files/zsh-normal = %q, want the one line", raw)
}
}
// The controller refuses two modules declaring one path (internal/catalogue/resolve.go:1160-1168). mesh-cli (mesh-catalog
// modules/mesh-cli/module.json) declares /usr/local/bin whole, so this module may place only a file below a shared
// directory, never the directory itself.
func TestNoSharedDirectoryIsClaimed(t *testing.T) { // 1134/AC7
m, _ := load(t)
shared := map[string]bool{"/usr/local/bin": true, "/usr/local/lib": true, "/usr/bin": true, "/usr/local": true, "/usr/lib": true, "/etc": true, "/opt": true}
var binary bool
for _, r := range m.Resources {
p, _ := r["path"].(string)
if shared[strings.TrimRight(p, "/")] {
t.Errorf("resource %v declares the shared directory %s, which mesh-cli and others declare too", r["id"], p)
}
if p == "/usr/local/bin/mesh-delivery-cli" && r["type"] == "file" && r["template"] == "files/mesh-delivery-cli" {
binary = true
}
}
if !binary {
t.Error("the command is placed as the file /usr/local/bin/mesh-delivery-cli")
}
raw, err := os.ReadFile("files/mesh-delivery-cli")
if err != nil || !strings.Contains(string(raw), "exec /usr/local/lib/mesh-delivery-view/mesh-delivery-cli") {
t.Errorf("files/mesh-delivery-cli = %q, %v", raw, err)
}
}
// The node-engine never places a module-owned directory below /etc, /usr, /run or /var/run: they are root's
// (mesh-host internal/apply/placement_guard.go:80, rootsOnly; novox/hq issue 339).
func TestNoDirectoryBelowRootsTrees(t *testing.T) { // 1134/AC10
m, _ := load(t)
for _, r := range m.Resources {
if r["type"] != "directory" {
continue
}
p, _ := r["path"].(string)
for _, root := range []string{"/etc", "/usr", "/run", "/var/run"} {
if p == root || strings.HasPrefix(p, root+"/") {
t.Errorf("directory %v at %s is below %s, which is root's", r["id"], p, root)
}
}
}
var socketDir bool
for _, r := range m.Resources {
if r["path"] == "/var/lib/mesh-delivery-view-cli" && r["type"] == "directory" && r["mode"] == "0755" && r["owner"] == "mesh-delivery-view" {
socketDir = true
}
}
if !socketDir {
t.Error("the socket's directory is /var/lib/mesh-delivery-view-cli, 0755, owned by mesh-delivery-view")
}
}