Files
mesh-delivery/modules/mesh-delivery-sdk/steprun/askers_test.go
T
jschoubben 7033d5e80d BG2, BG3: the puller refuses a kind its asker may not ask and pulls the named worker
An ask's asker is the last token of its accept subject; the holder checks the kind against the callers the build seat's
definition lists (Puller.Callers, CallersFromDefinitions), refuses an ask of another kind answering error naming the kind
and the asker and ending it failed, takes only a build and a run on main's subject, and gives back an ask the seat list
does not judge yet. A second worker per queue (AskNamedWorker, filtered to accept.<queue>.*) is pulled after main's; the
holder says `named worker not bound` while its membership lists none.

Builds: design 47 G §543, §544, §481, A-163, A-170

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 05:44:38 +01:00

87 lines
4.1 KiB
Go

package steprun
import (
"context"
"strings"
"testing"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/seatdef"
)
func callersFor(table map[string][]string) func(string) ([]string, bool) {
return func(kind string) ([]string, bool) { c, ok := table[kind]; return c, ok }
}
// Design 47 G §543 (BG2, BG3, A-163, A-170): an ask whose kind its asker, the last token of its subject, is no caller of is
// answered error naming the kind and the asker and ended failed; a step on main's subject is refused as well; one the
// seat list does not judge yet is given back.
func TestPullRefusesAKindItsAskerMayNotAsk(t *testing.T) {
step := testAsk(sdkstep.AskStep)
step.Check = nil
step.Step = &sdkstep.StepRun{Pipeline: "p", Step: sdkstep.StepUnit, Module: "m", Artifact: "m:a", Commit: "0123456789abcdef0123456789abcdef01234567",
Fingerprint: "fp", Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: "0123456789abcdef0123456789abcdef01234567"}}
build := testAsk(sdkstep.AskDelivery)
table := map[string][]string{"step": {"mesh-pipeline-runner"}, "delivery": {"mesh-builds"}}
for _, c := range []struct {
name, subject string
ask sdkstep.StepAsk
callers func(string) ([]string, bool)
want string // "" runs it, "refused" refuses it, "back" gives it back
}{
{"the indexer's step", "mesh.seat.node-build-go.accept.check.mesh-catalog", step, callersFor(table), "refused"},
{"the runner's step", "mesh.seat.node-build-go.accept.check.mesh-pipeline-runner", step, callersFor(table), ""},
{"a step on main's subject", "mesh.seat.node-build-go.accept.check", step, callersFor(table), "refused"},
{"a build on main's subject", "mesh.seat.node-build-go.accept.delivery", build, callersFor(table), ""},
{"a kind the seat list does not hold", "mesh.seat.node-build-go.accept.check.mesh-pipeline-runner", step, callersFor(nil), "back"},
} {
t.Run(c.name, func(t *testing.T) {
src, st, sy := NewFakeAsks(), newSteps(), &sayer{}
raw, _ := sdkstep.EncodeStepAsk(c.ask)
src.PublishWithSubject(c.ask.Kind, raw, c.subject)
ran := make(chan struct{}, 1)
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 }, Callers: c.callers,
PullWait: 5 * time.Millisecond, Heartbeat: time.Hour, Say: sy.say, ProgressEvery: time.Hour,
Run: func(context.Context, sdkstep.StepAsk) (any, error) { ran <- struct{}{}; return nil, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
switch c.want {
case "":
<-ran
case "refused":
waitFor(t, "the ask ended", func() bool { _, termed, _ := src.Fates(); return len(termed) == 1 })
key, _ := sdkstep.AskStepKey("shanks", c.ask.ID)
s, _ := st.get(key)
asker := c.subject[strings.LastIndex(c.subject, ".")+1:]
if s.State != sdkstep.StepFailed || !strings.Contains(s.Reason, KindOf(c.ask)) || (strings.Contains(c.subject, "mesh-catalog") && !strings.Contains(s.Reason, asker)) {
t.Fatalf("the step = %+v, want failed naming the kind %s and the asker", s, KindOf(c.ask))
}
if end := sy.of("step-ended"); len(end) != 1 || end[0].body["outcome"] != sdkstep.OutcomeFailed {
t.Fatalf("step-ended = %+v, want failed", end)
}
select {
case <-ran:
t.Fatal("a refused ask ran")
default:
}
case "back":
waitFor(t, "the ask given back", func() bool { src.mu.Lock(); defer src.mu.Unlock(); return len(src.Delayed) > 0 })
}
})
}
}
func TestCallersFromDefinitionsReadsTheSeatsCallers(t *testing.T) {
def := seatdef.Definition{Seat: "node-build-go", Accepts: []seatdef.Message{
{Name: "step", Callers: &seatdef.Callers{List: []string{"seat:mesh-pipeline-runner"}}}}}
callers := CallersFromDefinitions(sdkstep.AskGo, func() map[string]seatdef.Definition { return map[string]seatdef.Definition{"node-build-go": def} })
if got, ok := callers("step"); !ok || len(got) != 1 || got[0] != "mesh-pipeline-runner" {
t.Fatalf("callers of step = %v, %v", got, ok)
}
if _, ok := callers("run"); ok {
t.Fatal("a kind the definition does not list is found")
}
}