Files
mesh-catalog/modules/mesh-build-node/cmd/mesh-build-node/read_test.go
T
mesh-tools c33b83dce7
mesh/merge-gate head c33b83dce7d5 merged into trunk 940594b07478 — pass: builds mesh-build-go, mesh-build-node → g14, novox, shanks; no bus step; every mac…
mesh/repo-check head c33b83dce7d5 merged into trunk 940594b07478 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-catalog@e023f69989ab (merged as b5dbfbb7 into main, walk plan-17925769516…
Read a build module's steps and toolchains states with its own tools, so a step's result and the reported toolchain can be checked live (issue 1562)
2026-10-21 09:57:16 +00:00

148 lines
5.2 KiB
Go

package main
import (
"context"
"encoding/json"
"slices"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// fakeState is a state as stored: key to raw value.
type fakeState map[string]json.RawMessage
func (f fakeState) read() StateRead {
return StateRead{
Keys: func() ([]string, error) {
var k []string
for key := range f {
k = append(k, key)
}
return k, nil
},
Get: func(key string) (json.RawMessage, error) { return f[key], nil },
}
}
func putStep(t *testing.T, f fakeState, s delivery.Step) {
t.Helper()
b, err := delivery.EncodeStep(s)
if err != nil {
t.Fatal(err)
}
key, _ := delivery.StepKey(s.Node, s.ID)
f[key] = b
}
func readModule(steps, toolchains fakeState) *Module {
m := moduleWith(newStates(), "v22.1.0")
m.ReadSteps, m.ReadToolchainsState = steps.read(), toolchains.read()
return m
}
func tool(t *testing.T, m *Module, name string) func(map[string]any) (any, error) {
t.Helper()
for _, tl := range m.Tools(m.StepTools()) {
if tl.Name == name {
return tl.Run
}
}
t.Fatalf("no tool %s", name)
return nil
}
// 1562/AC1: build_node_step answers the stored value of a step: state, reason and result (a fetch's workspace id).
func TestStepToolAnswersTheStoredStep(t *testing.T) {
steps := fakeState{}
t0 := time.Date(2026, 10, 21, 9, 0, 0, 0, time.UTC)
putStep(t, steps, delivery.Step{ID: "abc1", Tool: "build_node_fetch", Node: "ace", State: delivery.StepPassed, Started: t0, Ended: t0.Add(time.Second),
Result: json.RawMessage(`{"workspace":"w42"}`)})
putStep(t, steps, delivery.Step{ID: "abc2", Tool: "build_node_test", Node: "ace", State: delivery.StepRefused, Started: t0, Reason: "node cannot be run"})
m := readModule(steps, fakeState{})
for _, id := range []string{"abc1", "ace.abc1"} {
out, err := tool(t, m, StepReadTool)(map[string]any{"id": id})
if err != nil {
t.Fatal(err)
}
st := out.(delivery.Step)
if st.State != delivery.StepPassed || string(st.Result) != `{"workspace":"w42"}` || st.Tool != "build_node_fetch" {
t.Errorf("id %s: step = %+v", id, st)
}
}
out, _ := tool(t, m, StepReadTool)(map[string]any{"id": "abc2"})
if st := out.(delivery.Step); st.State != delivery.StepRefused || st.Reason != "node cannot be run" {
t.Errorf("refused step = %+v", st)
}
}
func TestStepToolRefusesAnUnknownOrForeignOrBadID(t *testing.T) {
m := readModule(fakeState{}, fakeState{})
for id, want := range map[string]string{"nothere": "no step ace.nothere", "g14.abc1": "of node", "Bad Id": "lower-case"} {
_, err := tool(t, m, StepReadTool)(map[string]any{"id": id})
if err == nil || !strings.Contains(err.Error(), want) {
t.Errorf("id %q: err = %v, want it to say %q", id, err, want)
}
}
}
// 1562: build_node_steps lists this node's steps newest first without results, at most limit.
func TestStepsToolListsRecentStepsOfThisNode(t *testing.T) {
steps := fakeState{}
t0 := time.Date(2026, 10, 21, 9, 0, 0, 0, time.UTC)
putStep(t, steps, delivery.Step{ID: "a", Tool: "build_node_fetch", Node: "ace", State: delivery.StepPassed, Started: t0, Ended: t0, Result: json.RawMessage(`1`)})
putStep(t, steps, delivery.Step{ID: "b", Tool: "build_node_test", Node: "ace", State: delivery.StepRunning, Started: t0.Add(time.Minute)})
putStep(t, steps, delivery.Step{ID: "c", Tool: "build_node_build", Node: "ace", State: delivery.StepFailed, Started: t0.Add(2 * time.Minute), Reason: "boom"})
putStep(t, steps, delivery.Step{ID: "d", Tool: "build_node_build", Node: "g14", State: delivery.StepPassed, Started: t0.Add(time.Hour)})
m := readModule(steps, fakeState{})
out, err := tool(t, m, StepsReadTool)(map[string]any{})
if err != nil {
t.Fatal(err)
}
lines := out.(map[string]any)["steps"].([]StepLine)
var ids []string
for _, l := range lines {
ids = append(ids, l.ID)
}
if !slices.Equal(ids, []string{"c", "b", "a"}) {
t.Errorf("ids = %v, want c b a", ids)
}
if lines[0].Reason != "boom" || lines[0].State != delivery.StepFailed {
t.Errorf("first = %+v", lines[0])
}
out, _ = tool(t, m, StepsReadTool)(map[string]any{"limit": float64(1)})
if n := len(out.(map[string]any)["steps"].([]StepLine)); n != 1 {
t.Errorf("limit 1 answered %d steps", n)
}
}
// 1562 (1483/AC5): build_node_toolchains answers each toolchains key as stored.
func TestToolchainsToolAnswersTheStoredKeys(t *testing.T) {
tcs := fakeState{}
b, err := delivery.EncodeToolchain(delivery.Toolchain{Language: "typescript", Version: "v22.1.0", Recipe: "1", At: time.Date(2026, 10, 21, 9, 0, 0, 0, time.UTC)})
if err != nil {
t.Fatal(err)
}
tcs["ace.typescript"] = b
m := readModule(fakeState{}, tcs)
out, err := tool(t, m, ToolchainsReadTool)(map[string]any{})
if err != nil {
t.Fatal(err)
}
got := out.(map[string]any)["toolchains"].(map[string]delivery.Toolchain)
if got["ace.typescript"].Version != "v22.1.0" || got["ace.typescript"].Recipe != "1" {
t.Errorf("toolchains = %+v", got)
}
}
func TestReadToolsAreInTheManifestAndNeedNoToolchain(t *testing.T) {
m := readModule(fakeState{}, fakeState{})
m.Probe = func(context.Context) (string, error) { return "", context.DeadlineExceeded }
// a node whose go cannot be run can still be asked what its steps said
if _, err := tool(t, m, StepsReadTool)(map[string]any{}); err != nil {
t.Errorf("the read refused: %v", err)
}
}