package catalogue // A directory the mesh places (novox/hq ADR 0112). These tests pin the contract: a pathless // directory resolves under the node's root, ${dir:…} names it from every field a host path can // live in, a stated path is the adopted-data placement and wins, an unknown reference refuses at // the manifest, and filling for one node never leaks into the next composition. import ( "strings" "testing" ) func placedModule() Manifest { return Manifest{ Module: "photos", Resources: []map[string]any{ {"id": "data", "type": "directory", "mode": "0700"}, {"id": "server-env", "type": "file", "path": "${dir:data}/server.env", "content": "STORE=${dir:data}/objects\n"}, {"id": "server", "type": "container", "name": "photos-server", "volumes": []any{"${dir:data}:/data"}, "env": map[string]any{"DATA": "${dir:data}/objects"}, "env-file": []any{"${dir:data}/server.env"}}, }, } } func TestAPathlessDirectoryResolvesUnderTheNodesRoot(t *testing.T) { m := placedModule() dirs := dirsFor(m, Rendering{}) if dirs["data"] != "/var/lib/photos/data" { t.Fatalf("the default root is /var/lib and the shape is //; got %q", dirs["data"]) } dirs = dirsFor(m, Rendering{DataRoot: "/tank/nox/"}) if dirs["data"] != "/tank/nox/photos/data" { t.Fatalf("a node's own root is honoured, trailing slash and all; got %q", dirs["data"]) } } func TestDirReferencesBecomeThePlaceInEveryField(t *testing.T) { m := placedModule() dirs := dirsFor(m, Rendering{}) directory := shallowCopy(m.Resources[0]) if err := dirInto(directory, dirs, m.Module); err != nil { t.Fatal(err) } if directory["path"] != "/var/lib/photos/data" { t.Fatalf("a pathless directory receives its resolved path; got %v", directory["path"]) } file := shallowCopy(m.Resources[1]) if err := dirInto(file, dirs, m.Module); err != nil { t.Fatal(err) } if file["path"] != "/var/lib/photos/data/server.env" { t.Fatalf("a file's path names the place; got %v", file["path"]) } if file["content"] != "STORE=/var/lib/photos/data/objects\n" { t.Fatalf("a file's content names the place; got %v", file["content"]) } container := shallowCopy(m.Resources[2]) if err := dirInto(container, dirs, m.Module); err != nil { t.Fatal(err) } if container["volumes"].([]any)[0] != "/var/lib/photos/data:/data" { t.Fatalf("a mount names the place; got %v", container["volumes"]) } if container["env"].(map[string]any)["DATA"] != "/var/lib/photos/data/objects" { t.Fatalf("an environment value names the place; got %v", container["env"]) } if container["env-file"].([]any)[0] != "/var/lib/photos/data/server.env" { t.Fatalf("an env-file names the place; got %v", container["env-file"]) } } func TestAStatedPathIsThePlacementAndStillAnswersByName(t *testing.T) { m := placedModule() // The adopted-machine case: data that must sit where the predecessor already put it. m.Resources[0]["path"] = "/services/mssql/data/" dirs := dirsFor(m, Rendering{}) if dirs["data"] != "/services/mssql/data" { t.Fatalf("a stated path wins over the root, trimmed; got %q", dirs["data"]) } container := shallowCopy(m.Resources[2]) if err := dirInto(container, dirs, m.Module); err != nil { t.Fatal(err) } if container["volumes"].([]any)[0] != "/services/mssql/data:/data" { t.Fatalf("references follow the placement; got %v", container["volumes"]) } } func TestFillingForOneNodeLeaksIntoNoOther(t *testing.T) { m := placedModule() first := shallowCopy(m.Resources[2]) if err := dirInto(first, dirsFor(m, Rendering{DataRoot: "/first"}), m.Module); err != nil { t.Fatal(err) } second := shallowCopy(m.Resources[2]) if err := dirInto(second, dirsFor(m, Rendering{DataRoot: "/second"}), m.Module); err != nil { t.Fatal(err) } if got := second["volumes"].([]any)[0]; got != "/second/photos/data:/data" { t.Fatalf("the second composition must see the manifest, not the first fill; got %v", got) } if m.Resources[2]["volumes"].([]any)[0] != "${dir:data}:/data" { t.Fatalf("the manifest itself stays a template; got %v", m.Resources[2]["volumes"]) } } func TestAReferenceToNoDirectoryRefusesAtTheManifest(t *testing.T) { m := placedModule() m.Resources[2]["volumes"] = []any{"${dir:date}:/data"} // a typo, the likely shape problems := m.unknownDirRefs() if len(problems) != 1 || !strings.Contains(problems[0], `${dir:date}`) { t.Fatalf("a reference naming no directory is a manifest problem; got %v", problems) } if got := placedModule().unknownDirRefs(); len(got) != 0 { t.Fatalf("a correct definition has none; got %v", got) } } func TestAReferenceToNoDirectoryRefusesAtCompositionToo(t *testing.T) { m := placedModule() container := shallowCopy(m.Resources[2]) container["env"] = map[string]any{"DATA": "${dir:date}"} err := dirInto(container, dirsFor(m, Rendering{}), m.Module) if err == nil || !strings.Contains(err.Error(), `"date"`) || !strings.Contains(err.Error(), `"data"`) { t.Fatalf("the refusal names the mistake and what exists; got %v", err) } } func shallowCopy(resource map[string]any) map[string]any { copied := map[string]any{} for k, v := range resource { copied[k] = v } return copied }