ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
115 lines
4.6 KiB
Go
115 lines
4.6 KiB
Go
package resolution
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
const oldBackupText = "# lines\n${contribution:node-backup:backup}"
|
|
|
|
const oldDataText = "# data\n${contribution:node-backup:data}"
|
|
|
|
const placedBackupTxt = "# lines\n${backup:lines}"
|
|
|
|
const placedDataTxt = "# data\n${backup:data}"
|
|
|
|
const backupHolder = `{"module":"restic","version":"1","moved":true,"claims":[{"name":"node-backup","scope":"node"}],
|
|
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
|
|
"resources":[{"id":"declared","type":"file","path":"/etc/backups.conf","template":"files/backups"},
|
|
{"id":"data-declared","type":"file","path":"/etc/data.conf","template":"files/data"}]}`
|
|
|
|
func dataModule(name string) string {
|
|
return `{"module":"` + name + `","version":"1","resources":[{"id":"d","type":"directory","mode":"0700"}],
|
|
"data":{"own":[{"id":"d","path":"${dir:d}","class":"irreplaceable","why":"the ` + name + ` data"}]}}`
|
|
}
|
|
|
|
// backupsOn is a machine of a holder whose files say these texts and the modules that declare data.
|
|
func backupsOn(t *testing.T, lines, data string, modules ...string) mcat.Resolution {
|
|
t.Helper()
|
|
all := []mcat.Manifest{held(t, backupHolder, builtWith("restic", map[string]string{"backups": lines, "data": data}))}
|
|
names := []string{"restic"}
|
|
for _, name := range modules {
|
|
all = append(all, mustParse(t, dataModule(name)))
|
|
names = append(names, name)
|
|
}
|
|
r, err := Resolve(shelf(all...), names, workstation(), mcat.World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r.Account = "op"
|
|
return r
|
|
}
|
|
|
|
var backupModules = []string{"alpha", "beta", "gamma"}
|
|
|
|
// gammaLeftOut is a machine whose gamma does not place: its lines are kept, best effort.
|
|
func gammaLeftOut(with mcat.Rendering) mcat.Rendering {
|
|
with.NotPlaced = map[string]string{"gamma": "its placement setting does not read"}
|
|
return with
|
|
}
|
|
|
|
func TestTheBackupPlacementIsTheTextTheOldPlaceholdersRender(t *testing.T) {
|
|
// The old form is an unmoved holder: its placeholders are the controller's alone.
|
|
oldHolder := strings.Replace(backupHolder, `"moved":true,`, "", 1)
|
|
all := []mcat.Manifest{held(t, oldHolder, builtWith("restic", map[string]string{"backups": oldBackupText, "data": oldDataText}))}
|
|
for _, name := range backupModules {
|
|
all = append(all, mustParse(t, dataModule(name)))
|
|
}
|
|
old, err := Resolve(shelf(all...), append([]string{"restic"}, backupModules...), workstation(), mcat.World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
old.Account = "op"
|
|
wantLines, wantData := fileOf(t, old, gammaLeftOut(mcat.Rendering{}), "restic.declared"), fileOf(t, old, gammaLeftOut(mcat.Rendering{}), "restic.data-declared")
|
|
if !strings.Contains(wantLines, "# alpha\npath ") || !strings.Contains(wantLines, "# gamma\n") || !strings.Contains(wantData, "item d") {
|
|
t.Fatalf("the old form renders\n%s\n%s", wantLines, wantData)
|
|
}
|
|
moved := backupsOn(t, placedBackupTxt, placedDataTxt, backupModules...)
|
|
// Not handed: the controller fills the new placeholders with the old text.
|
|
if got := fileOf(t, moved, gammaLeftOut(mcat.Rendering{}), "restic.declared"); got != wantLines {
|
|
t.Errorf("not handed, the lines are\n%s\nwant\n%s", got, wantLines)
|
|
}
|
|
if got := fileOf(t, moved, gammaLeftOut(mcat.Rendering{}), "restic.data-declared"); got != wantData {
|
|
t.Errorf("not handed, the data is\n%s\nwant\n%s", got, wantData)
|
|
}
|
|
// Handed: the plan carries the same text as placement, and the files are the client's.
|
|
c, _ := composeWith(t, moved, gammaLeftOut(handingTo("store:5000")))
|
|
p := c.Plans["restic"].Placement
|
|
if got := "# lines\n" + p["backup:lines"]; got != wantLines {
|
|
t.Errorf("handed, the plan's lines are\n%s\nwant\n%s", got, wantLines)
|
|
}
|
|
if got := "# data\n" + p["backup:data"]; got != wantData {
|
|
t.Errorf("handed, the plan's data is\n%s\nwant\n%s", got, wantData)
|
|
}
|
|
if len(p) != 2 {
|
|
t.Errorf("the plan's placement is %v", p)
|
|
}
|
|
}
|
|
|
|
func TestAnUnknownBackupKeyIsRefused(t *testing.T) {
|
|
r := backupsOn(t, "${backup:rows}", placedDataTxt, backupModules...)
|
|
if _, err := Compose(r, handingTo("store:5000")); err == nil || !strings.Contains(err.Error(), "backup:rows") {
|
|
t.Errorf("handed, an unknown key is %v", err)
|
|
}
|
|
if _, err := Declaration(r, mcat.Rendering{}); err == nil || !strings.Contains(err.Error(), "backup:rows") {
|
|
t.Errorf("not handed, an unknown key is %v", err)
|
|
}
|
|
}
|
|
|
|
func fileOf(t *testing.T, r mcat.Resolution, with mcat.Rendering, id string) string {
|
|
t.Helper()
|
|
out, err := Declaration(r, with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, res := range out {
|
|
if res["id"] == id {
|
|
return res["content"].(string)
|
|
}
|
|
}
|
|
t.Fatalf("%s was not composed", id)
|
|
return ""
|
|
}
|