Files
mesh-delivery/modules/mesh-delivery-sdk/steprun/pull_events_test.go
T
jschoubben 00b52e3c1f BG4, BN4, BI4: the puller says ask-taken, step-progress and the end of any ask
A holder says ask-taken when an ask starts, step-progress every minute while it runs (phase fetch, build or run) and
step-ended for an ask outside a pipeline, each on the seat's subject of its node (SayOn); the asker is the last token of
the subject the ask came on (Pulled.Subject).

Builds: design 47 G §496, §602, §603, §481

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

94 lines
2.9 KiB
Go

package steprun
import (
"context"
"encoding/json"
"errors"
"sync"
"testing"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
)
type said struct {
event, id string
body map[string]any
}
type sayer struct {
mu sync.Mutex
got []said
}
func (s *sayer) say(event string, body any, id string) error {
raw, _ := json.Marshal(body)
var m map[string]any
_ = json.Unmarshal(raw, &m)
s.mu.Lock()
s.got = append(s.got, said{event, id, m})
s.mu.Unlock()
return nil
}
func (s *sayer) of(event string) []said {
s.mu.Lock()
defer s.mu.Unlock()
var out []said
for _, g := range s.got {
if g.event == event {
out = append(out, g)
}
}
return out
}
// Design 47 G §496, §602 (BG4, BN4, BI4): a holder says ask-taken when an ask starts, step-progress while it runs and
// step-ended when an ask outside a pipeline ends, whatever its asker.
func TestPullSaysAskTakenProgressAndEnd(t *testing.T) {
for _, c := range []struct {
name string
runErr error
outcome string
}{{"passed", nil, sdkstep.OutcomePassed}, {"failed", errors.New("the build broke"), sdkstep.OutcomeFailed}} {
t.Run(c.name, func(t *testing.T) {
src, st, sy := NewFakeAsks(), newSteps(), &sayer{}
a := testAsk(sdkstep.AskDelivery)
_ = src.PublishAsk(a)
release, entered := make(chan struct{}), make(chan struct{})
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Heartbeat: time.Hour, Say: sy.say, ProgressEvery: 5 * time.Millisecond,
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) {
Phase(ctx, "build m")
close(entered)
<-release
return nil, c.runErr
}}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
<-entered
waitFor(t, "a step-progress in the build", func() bool {
pr := sy.of("step-progress")
return len(pr) > 0 && pr[len(pr)-1].body["phase"] == "build"
})
close(release)
waitFor(t, "step-ended", func() bool { return len(sy.of("step-ended")) == 1 })
taken := sy.of("ask-taken")
if len(taken) != 1 || taken[0].body["ask"] != a.ID || taken[0].body["node"] != "shanks" || taken[0].id != "ask-taken."+a.ID {
t.Fatalf("ask-taken = %+v, want one for %s on shanks", taken, a.ID)
}
if pr := sy.of("step-progress")[len(sy.of("step-progress"))-1].body; pr["ask"] != a.ID || pr["phase"] != "build" || pr["asker"] != a.Asker {
t.Fatalf("step-progress = %v, want the ask, its asker and the phase build", pr)
}
end := sy.of("step-ended")[0].body
if end["outcome"] != c.outcome || end["ask"] != a.ID || end["asker"] != a.Asker || end["node"] != "shanks" || end["at"] == nil {
t.Fatalf("step-ended = %v, want outcome %s of ask %s", end, c.outcome, a.ID)
}
if c.runErr != nil && end["said"] != "the build broke" {
t.Fatalf("step-ended said %v, want why", end["said"])
}
})
}
}