Files
mesh-sdk/go/catalogue/data_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

119 lines
6.5 KiB
Go

package catalogue
import (
"slices"
"testing"
)
// A store as a module now declares it (novox/hq ADR 0233): its own data by directory with a class and a
// dump, and what it keeps for its consumers.
const declaredStore = `{"module":"pg","version":"1",
"provides":[{"name":"postgres-database","scope":"mesh"}],
"grants":{"postgres-database":"${dir:grants}"},
"data":{
"own":[
{"id":"store","path":"${dir:store}","class":"irreplaceable","backup":{"dump":"dump-all > ${dir:dumps}/all","into":"dumps"},"active":"1d"},
{"id":"dumps","path":"${dir:dumps}","class":"rebuildable","backup":"none"}],
"consumers":{"postgres-database":{"class":"irreplaceable","in":"store"}}},
"resources":[
{"id":"grants","type":"directory","mode":"0700"},
{"id":"store","type":"directory","path":"/srv/store","mode":"0700"},
{"id":"dumps","type":"directory","path":"${dir:store}/dumps","mode":"0700"},
{"id":"server","type":"container","image":"x@sha256:aa","volumes":["${dir:store}:/var/lib/postgresql/data"]}]}`
func mustParse(t *testing.T, raw string) Manifest {
t.Helper()
m, err := decodeForTest([]byte(raw))
if err != nil {
t.Fatalf("refused: %v", err)
}
return m
}
// What a binding is sticky by follows the class (ADR 0232, generalised by 0233): a cache's consumers
// move freely, a store's do not; an older definition saying nothing still follows its grant.
func TestKeepingConsumerDataFollowsTheClass(t *testing.T) {
for class, keeps := range map[string]bool{"irreplaceable": true, "valuable": true, "rebuildable": true, "cache": false, "none": false} {
in := `,"in":"d"`
own := `"own":[{"id":"d","path":"${dir:d}","class":"irreplaceable"}],`
if !keeps {
in, own = "", ""
}
m := mustParse(t, `{"module":"p","version":"1","provides":[{"name":"q","scope":"mesh"}],"grants":{"q":"${dir:d}"},
"resources":[{"id":"d","type":"directory","mode":"0700"}],
"data":{`+own+`"consumers":{"q":{"class":"`+class+`"`+in+`}}}}`)
if m.KeepsConsumerData("q") != keeps {
t.Errorf("class %s keeps consumer data: %v, want %v", class, !keeps, keeps)
}
shelf := map[string]Manifest{"p": m}
if KeepsConsumerData(shelf, "q") != keeps {
t.Errorf("class %s: the provision by name keeps: %v", class, !keeps)
}
}
older := mustParse(t, `{"module":"p","version":"1","provides":[{"name":"q","scope":"mesh"}],"grants":{"q":"${dir:d}"},
"resources":[{"id":"d","type":"directory","mode":"0700"}]}`)
if !older.KeepsConsumerData("q") {
t.Fatal("an older definition that grants no longer keeps its consumers' data")
}
}
// The backup holder's lines are derived: the dump runs and the directory it writes into is kept,
// anything but a cache is copied by default — the standard nightly plan — an item on redundancy is not
// copied, and every item is listed with its class and protection for the holder to measure and watch:
// directories filled, a home directory filled from the machine, an operator's path from where the
// assignment placed it. A backup line still written by hand beside a data section is not placed.
func TestTheBackupHoldersLinesAreDerivedFromTheData(t *testing.T) {
pg := mustParse(t, declaredStore)
pg.Contributions = append(pg.Contributions, SeatContribution{Seat: BackupSeat, Kind: "backup", Content: "path /stale"})
agent := mustParse(t, `{"module":"agent","version":"1",
"data":{"own":[{"id":"home","path":"${dir:home}","class":"valuable"},{"id":"tmp","path":"${dir:home}/tmp","class":"cache"}]},
"resources":[{"id":"home","type":"directory","path":"${machine:account-home}/.agent","mode":"0700"}]}`)
media := mustParse(t, `{"module":"media","version":"1",
"accesses":[{"id":"films","mode":"read"}],
"data":{"own":[{"id":"films","path":"${access:films}","class":"irreplaceable","redundancy":"on a redundant array; no room for a copy","measure":"dataset"},
{"id":"meta","path":"${dir:meta}","class":"rebuildable"}]},
"resources":[{"id":"meta","type":"directory","mode":"0700"}]}`)
facts := map[string]string{"account-home": "/home/op"}
with := Rendering{Settings: SettingsBy{"media": {LayerOf("node", map[string]any{AccessesSetting: map[string]any{"films": "/tank/films"}})}}}
backup, _, err := seatContributions([]Manifest{pg, agent, media}, Manifest{}, BackupSeat+":"+BackupKindBackup, with, facts, nil)
if err != nil {
t.Fatal(err)
}
want := "# agent\npath /home/op/.agent\n# media\npath /var/lib/media/meta\n# pg\nrun dump-all > /srv/store/dumps/all\npath /srv/store/dumps\n"
if backup != want {
t.Fatalf("backup lines:\n%s\nwant:\n%s", backup, want)
}
data, _, err := seatContributions([]Manifest{pg, agent, media}, Manifest{}, BackupSeat+":"+BackupKindData, with, facts, nil)
if err != nil {
t.Fatal(err)
}
want = "# agent\nitem home valuable /home/op/.agent /home/op/.agent backup walk\nitem tmp cache /home/op/.agent/tmp - none walk\n" +
"# media\nitem films irreplaceable /tank/films - redundancy dataset\nitem meta rebuildable /var/lib/media/meta /var/lib/media/meta backup walk\n" +
"# pg\nitem store irreplaceable /srv/store /srv/store/dumps backup walk\nitem dumps rebuildable /srv/store/dumps - none walk\n"
if data != want {
t.Fatalf("data lines:\n%s\nwant:\n%s", data, want)
}
// ADR 0571 clause 3: the same text, read into items, as disk-load and the backup holder read it; a path a shell
// would split is refused.
items, err := ParseDataItems(data)
if err != nil || len(items) != 3 || items["media"][0] != (PlacedDataItem{Module: "media", ID: "films", Class: "irreplaceable",
Path: "/tank/films", Protection: "redundancy", Measure: MeasureDataset}) || items["pg"][0].CoveredBy != "/srv/store/dumps" {
t.Fatalf("the data lines read as %+v: %v", items, err)
}
if _, err := ParseDataItems("# a\nitem x cache /a b - none walk\n"); err == nil {
t.Fatal("a path with a space was read")
}
if _, _, err := seatContributions([]Manifest{agent}, Manifest{}, BackupSeat+":"+BackupKindData, Rendering{}, nil, nil); err == nil {
t.Fatal("a home directory with no account on the machine reached the holder as a placeholder")
}
// What keeps something irreplaceable depends on the machine's backup holder — it backs it up or
// watches its array; valuable data alone is backed up where a holder is, and refused nowhere.
if !slices.Contains(DependsOn(nil, media), BackupSeat) || !slices.Contains(DependsOn(nil, pg), BackupSeat) ||
slices.Contains(DependsOn(nil, agent), BackupSeat) {
t.Fatalf("depends: media %v, pg %v, agent %v", DependsOn(nil, media), DependsOn(nil, pg), DependsOn(nil, agent))
}
if it, _ := media.DataItem("films"); it.OwnedByModule() || it.Protection() != "redundancy" {
t.Fatalf("an operator's path reads %+v", it)
}
}