Files
mesh-contracts/go/seats/nodeengine/contract_test.go
T

83 lines
10 KiB
Go

// Code generated by seatgen from the seat definitions; DO NOT EDIT.
package nodeengine
import (
"encoding/json"
"reflect"
"regexp"
"testing"
)
// TestFixtures reads every fixture of seats/node-engine/fixtures: each decodes into its type, a field the type does not
// know is dropped (ADR 0608 §6), and it reads back as its wire says; each subject named is the contract's.
func TestFixtures(t *testing.T) {
cases := []struct {
name, subject, given, wire string
decode func([]byte) (any, error)
subjectIs *regexp.Regexp
}{
{name: "asked on the node's subject", subject: "mesh.seat.node-engine.event.asked.ace", given: "{\"act\":\"hand-over\",\"ask\":\"hand-over-1\",\"caller\":\"mesh-tools\",\"node\":\"ace\",\"state\":\"asked\",\"subject\":\"/srv/media\"}", wire: "{\"act\":\"hand-over\",\"ask\":\"hand-over-1\",\"caller\":\"mesh-tools\",\"node\":\"ace\",\"state\":\"asked\",\"subject\":\"/srv/media\"}",
decode: func(b []byte) (any, error) { var v AskedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.asked\\.[A-Za-z0-9_-]+$")},
{name: "done on the node's subject", subject: "mesh.seat.node-engine.event.done.ace", given: "{\"act\":\"hand-over\",\"ask\":\"hand-over-1\",\"caller\":\"mesh-tools\",\"node\":\"ace\",\"said\":\"handed over: owner media, group media, mode 2775\",\"state\":\"done\",\"subject\":\"/srv/media\"}", wire: "{\"act\":\"hand-over\",\"ask\":\"hand-over-1\",\"caller\":\"mesh-tools\",\"node\":\"ace\",\"said\":\"handed over: owner media, group media, mode 2775\",\"state\":\"done\",\"subject\":\"/srv/media\"}",
decode: func(b []byte) (any, error) { var v DoneEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.done\\.[A-Za-z0-9_-]+$")},
{name: "refused on the node's subject", subject: "mesh.seat.node-engine.event.refused.ace", given: "{\"act\":\"access-act\",\"ask\":\"access-act-1\",\"caller\":\"mesh-tools\",\"node\":\"ace\",\"said\":\"the operator said no\",\"state\":\"refused\",\"subject\":\"library:music\"}", wire: "{\"act\":\"access-act\",\"ask\":\"access-act-1\",\"caller\":\"mesh-tools\",\"node\":\"ace\",\"said\":\"the operator said no\",\"state\":\"refused\",\"subject\":\"library:music\"}",
decode: func(b []byte) (any, error) { var v RefusedEvent; err := json.Unmarshal(b, &v); return v, err }, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.refused\\.[A-Za-z0-9_-]+$")},
{name: "manifest-reads-changed when the engine started", subject: "mesh.seat.node-engine.event.manifest-reads-changed.ace", given: "{\"at\":\"2026-11-04T10:00:00Z\",\"manifest-reads\":[\"form:seat-reference\",\"word:series\"],\"node\":\"ace\",\"v\":1,\"why\":\"started\"}", wire: "{\"at\":\"2026-11-04T10:00:00Z\",\"manifest-reads\":[\"form:seat-reference\",\"word:series\"],\"node\":\"ace\",\"v\":1,\"why\":\"started\"}",
decode: func(b []byte) (any, error) {
var v ManifestReadsChangedEvent
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.manifest-reads-changed\\.[A-Za-z0-9_-]+$")},
{name: "manifest-reads-changed when the node returned, from an engine that states none", subject: "mesh.seat.node-engine.event.manifest-reads-changed.g14", given: "{\"at\":\"2026-11-04T10:05:00Z\",\"node\":\"g14\",\"v\":1,\"why\":\"returned\"}", wire: "{\"at\":\"2026-11-04T10:05:00Z\",\"node\":\"g14\",\"v\":1,\"why\":\"returned\"}",
decode: func(b []byte) (any, error) {
var v ManifestReadsChangedEvent
err := json.Unmarshal(b, &v)
return v, err
}, subjectIs: regexp.MustCompile("^mesh\\.seat\\.node-engine\\.event\\.manifest-reads-changed\\.[A-Za-z0-9_-]+$")},
{name: "the finding of a seat list unread, as the engine writes it", subject: "", given: "{\"at\":\"2026-11-05T14:00:00Z\",\"key\":\"machine.ace.seat-list-unread\",\"kind\":\"seat-list-unread\",\"observer\":\"mesh-host\",\"severity\":\"warning\",\"source\":\"node-engine.seat-list\",\"subject\":{\"id\":\"ace\",\"machine\":\"ace\",\"scope\":\"machine\"},\"summary\":\"the seat list has not been read for five minutes\",\"v\":1}", wire: "{\"at\":\"2026-11-05T14:00:00Z\",\"key\":\"machine.ace.seat-list-unread\",\"kind\":\"seat-list-unread\",\"observer\":\"mesh-host\",\"severity\":\"warning\",\"source\":\"node-engine.seat-list\",\"subject\":{\"id\":\"ace\",\"machine\":\"ace\",\"scope\":\"machine\"},\"summary\":\"the seat list has not been read for five minutes\",\"v\":1}",
decode: func(b []byte) (any, error) { var v FindingsByKeyValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "a node's packages, present and absent (captured from ace, shortened)", subject: "", given: "{\"node\":\"ace\",\"packages\":[{\"absent\":false,\"modules\":[\"avahi\"],\"package\":\"avahi\"},{\"absent\":false,\"modules\":[\"mesh-build-go\",\"mesh-build-node\"],\"package\":\"bubblewrap\"},{\"absent\":true,\"modules\":[\"time-sync\"],\"package\":\"ntp\"}]}", wire: "{\"node\":\"ace\",\"packages\":[{\"absent\":false,\"modules\":[\"avahi\"],\"package\":\"avahi\"},{\"absent\":false,\"modules\":[\"mesh-build-go\",\"mesh-build-node\"],\"package\":\"bubblewrap\"},{\"absent\":true,\"modules\":[\"time-sync\"],\"package\":\"ntp\"}]}",
decode: func(b []byte) (any, error) { var v PackagesByNodeValue; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "manifest-reads answers what the engine reads and when it stated it", subject: "", given: "{\"manifest-reads\":[\"form:seat-reference\",\"word:series\"],\"node\":\"ace\",\"stated-at\":\"2026-11-04T10:00:00Z\",\"v\":1}", wire: "{\"manifest-reads\":[\"form:seat-reference\",\"word:series\"],\"node\":\"ace\",\"stated-at\":\"2026-11-04T10:00:00Z\",\"v\":1}",
decode: func(b []byte) (any, error) { var v ManifestReadsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "manifest-reads of an engine built before it said them is the baseline", subject: "", given: "{\"node\":\"g14\",\"stated-at\":\"2026-11-04T10:05:00Z\",\"v\":1}", wire: "{\"node\":\"g14\",\"stated-at\":\"2026-11-04T10:05:00Z\",\"v\":1}",
decode: func(b []byte) (any, error) { var v ManifestReadsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "compose-candidate asks the candidate and, as a boolean, its composition input", subject: "", given: "{\"candidate\":\"mesh-host.0123abcd\",\"input\":true}", wire: "{\"candidate\":\"mesh-host.0123abcd\",\"input\":true}",
decode: func(b []byte) (any, error) { var v ComposeCandidateInput; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "compose-candidate answers per module and the node's own", subject: "", given: "{\"candidate\":\"mesh-host.0123abcd\",\"modules\":[{\"changed\":true,\"digest\":\"sha256:aa\",\"fp\":\"fp-1\",\"module\":\"plex\",\"state\":\"composed\"}],\"node\":\"ace\",\"own\":{\"digest\":\"sha256:bb\",\"module\":\"node\",\"state\":\"composed\"},\"took\":1200,\"v\":1,\"verdict\":\"composed\"}", wire: "{\"candidate\":\"mesh-host.0123abcd\",\"modules\":[{\"changed\":true,\"digest\":\"sha256:aa\",\"fp\":\"fp-1\",\"module\":\"plex\",\"state\":\"composed\"}],\"node\":\"ace\",\"own\":{\"digest\":\"sha256:bb\",\"module\":\"node\",\"state\":\"composed\"},\"took\":1200,\"v\":1,\"verdict\":\"composed\"}",
decode: func(b []byte) (any, error) { var v ComposeCandidateAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "acts answers each act kept", subject: "", given: "{\"acts\":[{\"act\":\"hand-over\",\"ask\":\"hand-over-1\",\"at\":\"2026-11-04T09:00:00Z\",\"caller\":\"mesh-tools\",\"expires\":\"2026-11-05T09:00:00Z\",\"id\":\"hand-over-1\",\"state\":\"asked\",\"subject\":\"/srv/media\"}],\"node\":\"ace\"}", wire: "{\"acts\":[{\"act\":\"hand-over\",\"ask\":\"hand-over-1\",\"at\":\"2026-11-04T09:00:00Z\",\"caller\":\"mesh-tools\",\"expires\":\"2026-11-05T09:00:00Z\",\"id\":\"hand-over-1\",\"state\":\"asked\",\"subject\":\"/srv/media\"}],\"node\":\"ace\"}",
decode: func(b []byte) (any, error) { var v ActsAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "seat-list answers what the engine holds and what it waits for", subject: "", given: "{\"health\":\"the seat list is held from the bootstrap and nothing new is acted on\",\"status\":{\"heard\":false,\"held\":[{\"seat\":\"node-engine\",\"source\":\"bootstrap\"},{\"digest\":\"3f2a9c\",\"seat\":\"node-files\",\"source\":\"trunk\"}],\"last-error\":\"no answer\",\"waiting\":[\"node-engine\"]}}", wire: "{\"health\":\"the seat list is held from the bootstrap and nothing new is acted on\",\"status\":{\"heard\":false,\"held\":[{\"seat\":\"node-engine\",\"source\":\"bootstrap\"},{\"digest\":\"3f2a9c\",\"seat\":\"node-files\",\"source\":\"trunk\"}],\"last-error\":\"no answer\",\"waiting\":[\"node-engine\"]}}",
decode: func(b []byte) (any, error) { var v SeatListAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
{name: "seat-list-again answers the error when the read failed", subject: "", given: "{\"error\":\"seatlist: no answer\",\"health\":\"the seat list is held from the bootstrap\",\"status\":{\"heard\":false,\"held\":[{\"seat\":\"node-engine\",\"source\":\"bootstrap\"}],\"waiting\":[\"node-engine\"]}}", wire: "{\"error\":\"seatlist: no answer\",\"health\":\"the seat list is held from the bootstrap\",\"status\":{\"heard\":false,\"held\":[{\"seat\":\"node-engine\",\"source\":\"bootstrap\"}],\"waiting\":[\"node-engine\"]}}",
decode: func(b []byte) (any, error) { var v SeatListAgainAnswer; err := json.Unmarshal(b, &v); return v, err }, subjectIs: nil},
}
for _, c := range cases {
v, err := c.decode([]byte(c.given))
if err != nil {
t.Errorf("%s: does not decode: %v", c.name, err)
continue
}
back, err := json.Marshal(v)
if err != nil {
t.Errorf("%s: does not encode: %v", c.name, err)
continue
}
var got, want any
if err := json.Unmarshal(back, &got); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal([]byte(c.wire), &want); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, want) {
t.Errorf("%s: reads back as %s, want %s", c.name, back, c.wire)
}
if c.subjectIs != nil && !c.subjectIs.MatchString(c.subject) {
t.Errorf("%s: subject %q is not the contract's %s", c.name, c.subject, c.subjectIs)
}
}
}