Files
mesh-sdk/go/catalogue/templates_dir_test.go
T
jschoubben 68aaf235d4 Take the judge out of mesh-sdk: it is mesh-delivery's now (ADR 0578 §4, §7)
Issue 1585, S19: the strict parser, every manifest problem function and the judge's tables (192 declarations,
about 5,200 lines) and their 244 tests leave; composition keeps its own refusals and the helpers it uses. The
catalogue's tests decode the manifests they build. ParseManifest, ParseManifestWith, ParseManifestForInputs,
CatalogueProblems and the rest are gone; mesh-delivery-indexer/judge holds them.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
2026-11-01 02:36:24 +01:00

120 lines
5.1 KiB
Go

package catalogue
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
)
// A module may name the directory its templates are kept in (`"templates": "<dir>"`, default files/; issue 731): a
// module whose files/ is already an archive unpacked onto the machine keeps its templates in a directory of their
// own, packed into an archive no resource names, so its unpacked files/ is exactly what it was.
// The two forms of one module whose files/ is unpacked onto the machine: its text inline, and in templates/.
const (
unpackingInline = `{"module":"tidy","version":"1",
"build":{"artifacts":[{"name":"scripts","kind":"archive","from":"files"}]},
"resources":[
{"id":"scripts","type":"archive","path":"/usr/local/lib/tidy","artifact":"scripts"},
{"id":"conf","type":"directory","path":"${dir:conf}"},
{"id":"main","type":"file","path":"/etc/tidy.conf","mode":"0644","content":"` + mainText + `"},
{"id":"extra","type":"file","path":"/etc/extra.conf","content":"` + extraText + `"}]}`
unpackingTemplated = `{"module":"tidy","version":"1",
"templates":"templates",
"build":{"artifacts":[{"name":"scripts","kind":"archive","from":"files"},
{"name":"templates","kind":"archive","from":"templates"}]},
"resources":[
{"id":"scripts","type":"archive","path":"/usr/local/lib/tidy","artifact":"scripts"},
{"id":"conf","type":"directory","path":"${dir:conf}"},
{"id":"main","type":"file","path":"/etc/tidy.conf","mode":"0644","template":"templates/tidy.conf"},
{"id":"extra","type":"file","path":"/etc/extra.conf","template":"templates/deeper/extra.conf"}]}`
)
func builtUnpacking(templates map[string]string) []Built {
built := []Built{{Name: "scripts", Kind: ArtifactArchive,
Reference: "https://store.invalid/scripts.tar.gz",
Digest: "sha256:" + strings.Repeat("a", 64)}}
if templates != nil {
built = append(built, Built{Name: "templates", Kind: ArtifactArchive,
Reference: ArtifactStoreScheme + "tidy/templates/blobs/sha256:" + strings.Repeat("b", 64),
Digest: "sha256:" + strings.Repeat("b", 64), Texts: templates})
}
return built
}
func TestAModuleWithItsOwnTemplatesDirectoryComposesByteIdenticallyToItsInlineTwin(t *testing.T) {
inline := composed(t, held(t, unpackingInline, builtUnpacking(nil)))
templated := composed(t, held(t, unpackingTemplated, builtUnpacking(templateTexts(t))))
if inline != templated {
t.Fatalf("the two forms compose differently:\ninline: %s\ntemplated: %s", inline, templated)
}
// The unpacked archive is the files/ one, by its digest; the templates archive reaches no machine.
if !strings.Contains(templated, strings.Repeat("a", 64)) || strings.Contains(templated, strings.Repeat("b", 64)) {
t.Fatalf("the composition does not unpack files/ alone: %s", templated)
}
}
func TestTheManifestTheMeshHoldsNamesNoTemplatesDirectory(t *testing.T) {
m := held(t, unpackingTemplated, builtUnpacking(templateTexts(t)))
raw, err := json.Marshal(m)
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(raw), `"templates"`) {
t.Fatalf("the held manifest still names its templates directory: %s", raw)
}
}
func TestAModuleWithItsOwnTemplatesDirectoryIsReadFromItsTree(t *testing.T) {
dir := t.TempDir()
for name, text := range templateTexts(t) {
full := filepath.Join(dir, "templates", filepath.FromSlash(name))
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(full, []byte(text), 0o644); err != nil {
t.Fatal(err)
}
}
// files/ holds what the machine unpacks, a symlink among it judged by nobody here: it is not the templates.
if err := os.MkdirAll(filepath.Join(dir, "files", "bin"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "files", "bin", "tidy"), []byte("#!/bin/sh\n"), 0o755); err != nil {
t.Fatal(err)
}
m, err := decodeForTest([]byte(unpackingTemplated))
if err != nil {
t.Fatal(err)
}
filled, err := WithTemplatesFrom(m, dir)
if err != nil {
t.Fatal(err)
}
resolved, err := filled.Resolve(builtUnpacking(map[string]string{}))
if err != nil {
t.Fatal(err)
}
if got, want := composed(t, resolved), composed(t, held(t, unpackingInline, builtUnpacking(nil))); got != want {
t.Fatalf("read from the tree, the module composes differently:\ninline: %s\ntemplated: %s", want, got)
}
// A symlink under the templates directory is refused by its path, as under files/.
if err := os.Symlink("/etc/hostname", filepath.Join(dir, "templates", "elsewhere")); err != nil {
t.Fatal(err)
}
if _, err := WithTemplatesFrom(m, dir); err == nil || !strings.Contains(err.Error(), "templates/elsewhere is a symlink") {
t.Fatalf("a symlink under templates/ was not refused by its path: %v", err)
}
}
func TestATemplatesDirectoryNamedFilesIsTheDefault(t *testing.T) {
raw := strings.Replace(templatedTwin, `"version":"1",`, `"version":"1","templates":"files",`, 1)
if got, want := composed(t, held(t, raw, builtFiles(templateTexts(t)))),
composed(t, held(t, inlineTwin, builtFiles(nil))); got != want {
t.Fatalf("naming files/ as the templates directory composes differently:\n%s\n%s", got, want)
}
}