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) queue := c.ask.Kind if queue == sdkstep.AskStep { queue = sdkstep.AskCheck // a step rides the queue check (A-170, A-184) } src.PublishWithSubject(queue, raw, c.subject) _, _, asker, _ := sdkstep.ParseAskSubjectBy(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 // §481: the step is keyed under the asker its subject names, main's under its node alone. key, _ := sdkstep.AskStepKey(asker, "shanks", c.ask.ID) waitFor(t, "the step keyed "+key, func() bool { _, ok := st.get(key); return ok }) case "refused": waitFor(t, "the ask ended", func() bool { _, termed, _ := src.Fates(); return len(termed) == 1 }) key, _ := sdkstep.AskStepKey(asker, "shanks", c.ask.ID) s, _ := st.get(key) 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") } }