Files
mesh-sdk/go/delivery/stepasks_compose_test.go
T
jschoubben 1b073c23c2
mesh/delivery merged: source.main moved to novox/mesh-sdk@54aff0a3d920
mesh/delivery-group group feat/b0-checking-program merged: every member is merged
mesh/check being carried to 1b073c23c2a5 on 54aff0a3d920 | from c1b50e99a639, hq 885099eebbbd, mesh-catalog 980ade933853, mesh-delivery 2d201f781ced
A run's input is asked of a verb of the asker's seat, never read from its state (issue 1921, B0 review round 1)
The operator's answer: a build module reading the checker's state broke ADR 0596 §2. RunInput names the seat verb it is
served on (seat:<seat>.<verb>), RunInput.Read asks it and takes only the bytes named, and the seat
mesh-delivery-checker defines the verb input (RunInputAsk, RunInputAnswer).

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
2026-11-06 23:33:13 +01:00

183 lines
9.2 KiB
Go

package delivery
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"time"
)
func composeAsk() StepAsk {
return StepAsk{ID: "mh3k2x9qa1b2c5", Kind: AskCheck, Language: AskGo, Asker: "mesh-delivery-assigner",
At: time.Date(2026, 10, 24, 3, 0, 0, 0, time.UTC),
Compose: &ComposeJob{Fetch: map[string]any{"repository": "novox/mesh-sdk", "commit": askCommit},
Dir: "go", Package: "git.novox.be/novox/mesh-sdk/go/cmd/mesh-compose",
Request: json.RawMessage(`{"v":1,"cases":[{"name":"change","manifests":{"demo":{"digest":"d1"}}}]}`), Reader: "mesh-sdk head"}}
}
// runAsk is the checker's run of a checking program built at a mesh-delivery head, over two heads (issue 1921, B0).
func runAsk() StepAsk {
in, err := RunInputOf("seat:mesh-delivery-checker."+CheckerInputVerb, []byte(`{"v":1}`))
if err != nil {
panic(err)
}
return StepAsk{ID: "mh3k2x9qa1b2c6", Kind: AskCheck, Language: AskGo, Asker: "mesh-delivery-checker",
At: time.Date(2026, 11, 6, 22, 0, 0, 0, time.UTC),
Run: &RunJob{Program: RunProgram{Repository: "novox/mesh-delivery", Commit: askCommit, Dir: "modules/mesh-delivery-checker",
Package: "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-checker/cmd/mesh-checking-program"},
Heads: []RunHead{{Repository: "novox/mesh-delivery", Commit: askCommit}, {Repository: "novox/mesh-sdk", Commit: askCommit}},
Input: in, Args: []string{"-verdict"}, Reader: "checking program of mesh-delivery head"},
Packages: PackageVersions{"git.novox.be/novox/mesh-sdk/go": askCommit}}
}
// 1622: a compose ask encodes and decodes with every field; 1921 (B0): a run ask too.
func TestAComposeAskRoundTrips(t *testing.T) {
for _, a := range []StepAsk{composeAsk(), runAsk()} {
raw, err := EncodeStepAsk(a)
if err != nil {
t.Fatal(err)
}
got, err := DecodeStepAsk(raw)
if err != nil {
t.Fatal(err)
}
a.V = StepAskVersion
if !reflect.DeepEqual(got, a) {
t.Fatalf("round trip:\n got %+v\nwant %+v", got, a)
}
}
}
// 1622: a compose ask goes only on the Go seat's check stream, holds no other job beside it, fetches a full commit,
// builds a package in a clean directory of the repository and carries a request object.
func TestAComposeAskIsRefusedWhereItDoesNotBelong(t *testing.T) {
for name, change := range map[string]func(*StepAsk){
"on the delivery stream": func(a *StepAsk) { a.Kind = AskDelivery },
"on the recheck stream": func(a *StepAsk) { a.Kind = AskRecheck },
"on the Node seat": func(a *StepAsk) { a.Language = AskNode },
"beside a check": func(a *StepAsk) { a.Check = checkAsk().Check },
"with a short commit": func(a *StepAsk) { a.Compose.Fetch["commit"] = "0123456" },
"in no directory": func(a *StepAsk) { a.Compose.Dir = "" },
"outside the repository": func(a *StepAsk) { a.Compose.Dir = "../x" },
"with no package": func(a *StepAsk) { a.Compose.Package = "" },
"with no request": func(a *StepAsk) { a.Compose.Request = nil },
"with a request not JSON": func(a *StepAsk) { a.Compose.Request = json.RawMessage(`[1]`) },
} {
a := composeAsk()
change(&a)
if _, err := EncodeStepAsk(a); err == nil {
t.Errorf("a compose ask %s is encoded", name)
}
}
// 1921 (B0): a run ask likewise goes only on the Go seat's check stream, alone, builds a clean package at a full
// commit, reads each named head once at a full commit, and names an input kept by digest within its bound.
for name, change := range map[string]func(*StepAsk){
"on the delivery stream": func(a *StepAsk) { a.Kind = AskDelivery },
"on the Node seat": func(a *StepAsk) { a.Language = AskNode },
"beside a compose": func(a *StepAsk) { a.Compose = composeAsk().Compose },
"with a short commit": func(a *StepAsk) { a.Run.Program.Commit = "0123456" },
"outside the repository": func(a *StepAsk) { a.Run.Program.Dir = "../x" },
"with no package": func(a *StepAsk) { a.Run.Program.Package = "" },
"with no head": func(a *StepAsk) { a.Run.Heads = nil },
"with a head twice": func(a *StepAsk) { a.Run.Heads = append(a.Run.Heads, a.Run.Heads[0]) },
"with a head at a branch": func(a *StepAsk) { a.Run.Heads[1].Commit = "main" },
"with an input on a state": func(a *StepAsk) { a.Run.Input.Verb = "mesh-delivery-checker.inputs" },
"with an input on one machine": func(a *StepAsk) { a.Run.Input.Verb = "seat:mesh-delivery-checker.input@ace" },
"with an input by no digest": func(a *StepAsk) { a.Run.Input.Digest = "d1" },
"with an input over its bound": func(a *StepAsk) { a.Run.Input.Size = RunInputBound + 1 },
"with an argument of the shell": func(a *StepAsk) { a.Run.Args = []string{"-x; rm"} },
} {
a := runAsk()
change(&a)
if _, err := EncodeStepAsk(a); err == nil {
t.Errorf("a run ask %s is encoded", name)
}
}
// The asker refuses an input over its bound before it asks, naming its size; the build module refuses bytes that
// are not the input named.
if _, err := RunInputOf("seat:mesh-delivery-checker.input", make([]byte, RunInputBound+1)); err == nil || !strings.Contains(err.Error(), "262145 bytes") {
t.Errorf("an input over its bound: %v", err)
}
in := runAsk().Run.Input
if err := in.Verify([]byte(`{"v":1}`)); err != nil {
t.Errorf("the input named: %v", err)
}
if err := in.Verify([]byte(`{"v":2}`)); err == nil {
t.Error("another input read as the one named")
}
// The build module asks the verb the input names for its digest and takes only the bytes named (the operator's answer
// on B0: the input travels by a verb of the asker's seat, never a read of its state).
for name, c := range map[string]struct {
answer string
err error
want string
}{
"the bytes named": {answer: `{"digest":"` + in.Digest + `","input":"eyJ2IjoxfQ=="}`},
"other bytes": {answer: `{"digest":"` + in.Digest + `","input":"eyJ2IjoyfQ=="}`, want: "is not the one the ask names"},
"another digest": {answer: `{"digest":"` + strings.Repeat("0", 64) + `","input":"eyJ2IjoxfQ=="}`, want: "answered the input"},
"no input": {answer: `[1]`, want: "answered no input"},
"an error of the callee": {err: errors.New("keeps no input of that digest"), want: "keeps no input of that digest"},
} {
var asked string
var body any
got, err := in.Read(func(key string, b any) (json.RawMessage, error) {
asked, body = key, b
return json.RawMessage(c.answer), c.err
})
if asked != "seat:mesh-delivery-checker.input" || body != (RunInputAsk{Digest: in.Digest}) {
t.Errorf("%s: asked %s with %+v", name, asked, body)
}
switch {
case c.want == "" && (err != nil || string(got) != `{"v":1}`):
t.Errorf("%s: %q, %v", name, got, err)
case c.want != "" && (err == nil || !strings.Contains(err.Error(), c.want)):
t.Errorf("%s: %v, want %q", name, err, c.want)
}
}
}
// 1622/AC3, 1921: a composer is kept under one key per repository, commit, directory, package, toolchain and asked versions, so two asks of
// one head build it once on a node, and another head or toolchain builds its own.
func TestAComposerIsKeptByWhatItWasBuiltFrom(t *testing.T) {
tc := ToolchainUsed{Version: "go1.26.0", Recipe: "3"}
k := ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, nil)
if k != ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, nil) || !strings.HasPrefix(k, "composer-") || len(k) != len("composer-")+16 {
t.Fatalf("key = %q", k)
}
for _, other := range []string{
ComposerKey("novox/mesh-sdk", strings.Repeat("1", 40), "go", "p", tc, nil),
ComposerKey("novox/mesh-controller", askCommit, ".", "p", tc, nil),
ComposerKey("novox/mesh-sdk", askCommit, "go", "p", ToolchainUsed{Version: "go1.26.1", Recipe: "3"}, nil),
ComposerKey("novox/mesh-sdk", askCommit, "go", "p", tc, PackageVersions{"git.novox.be/novox/mesh-sdk/go": strings.Repeat("2", 40)}),
} {
if other == k {
t.Errorf("two composers built from different inputs share the key %s", k)
}
}
// 1921: the asked versions are in the key, in any order of naming; none, or an empty ask, gives the key without them.
a, b := strings.Repeat("a", 40), strings.Repeat("b", 40)
k1 := ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{"m/x": a, "m/y": b})
if k1 != ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{"m/y": b, "m/x": a}) {
t.Error("the key depends on the order of the versions")
}
if k1 == ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{"m/x": b, "m/y": a}) || k1 == ComposerKey("r", askCommit, "go", "p", tc, nil) {
t.Error("other versions, or none, share the key")
}
if ComposerKey("r", askCommit, "go", "p", tc, nil) != ComposerKey("r", askCommit, "go", "p", tc, PackageVersions{}) {
t.Error("an empty ask changed the key")
}
// 1921 (B0): a run's program is kept the same way, under a key of its own kind.
p := RunProgram{Repository: "novox/mesh-delivery", Commit: askCommit, Dir: "modules/mesh-delivery-checker", Package: "p"}
rk := RunKey(p, tc, nil)
if !strings.HasPrefix(rk, "program-") || len(rk) != len("program-")+16 || rk != RunKey(p, tc, nil) {
t.Errorf("run key = %q", rk)
}
q := p
q.Commit = strings.Repeat("1", 40)
if RunKey(q, tc, nil) == rk || RunKey(p, tc, PackageVersions{"m/x": a}) == rk {
t.Error("two programs built from different inputs share the key")
}
}