package steprun import ( "context" "testing" "time" sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep" ) // 1887/A9a, ADR 0525 ยง10: a build seat's verbs are .current, .pause and .resume; current names the ask running, // pause stops the puller taking asks, resume lets it take them again. func TestSeatToolsCurrentPauseResume(t *testing.T) { src, st := NewFakeAsks(), newSteps() release := make(chan struct{}) p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 }, PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { <-release; return nil, nil }} tools := map[string]func(map[string]any) (any, error){} for _, tool := range SeatTools("node-build-go", "Go", p) { tools[tool.Name] = tool.Run } for _, name := range []string{"node-build-go.current", "node-build-go.pause", "node-build-go.resume"} { if tools[name] == nil { t.Fatalf("no %s", name) } } a := testAsk(sdkstep.AskDelivery) _ = src.PublishAsk(a) ctx, cancel := context.WithCancel(context.Background()) defer cancel() go p.Pull(ctx) waitFor(t, "the ask running", func() bool { return p.InHand() == 1 }) out, _ := tools["node-build-go.current"](nil) cur := out.(map[string]any) running := cur["running"].([]RunningAsk) if len(running) != 1 || running[0].ID != a.ID || running[0].Module != "m" || cur["capacity"] != 1 || cur["paused"] != false { t.Fatalf("current = %+v", cur) } if out, err := tools["node-build-go.pause"](nil); err != nil || out.(map[string]any)["paused"] != true { t.Fatalf("pause = %v, %v", out, err) } close(release) _ = src.PublishAsk(testAsk(sdkstep.AskDelivery)) time.Sleep(50 * time.Millisecond) if src.Waiting(sdkstep.AskDelivery) != 1 { t.Fatal("a paused node took an ask") } if _, err := tools["node-build-go.resume"](nil); err != nil { t.Fatal(err) } waitFor(t, "the ask taken after the resume", func() bool { return src.Waiting(sdkstep.AskDelivery) == 0 }) } // Design 47 G A-254: `.output` answers a run's answer by its ask as the whole file (base64), which the tool runner sends in // chunks (A-257); an ask this node keeps no answer of answers no chunks. func TestOutputToolAnswersTheKeptAnswer(t *testing.T) { kept := map[string][]byte{sdkstep.OutputObjectName("a1"): []byte(`{"verdict":"x"}`)} tool := OutputTool("node-build-go", func(object string) ([]byte, error) { return kept[object], nil }) if tool.Name != "node-build-go.output" { t.Fatalf("name %q", tool.Name) } got, err := tool.Run(map[string]any{"ask": "a1", "call": "c"}) if err != nil { t.Fatal(err) } if m := got.(map[string]any); m["ask"] != "a1" || m["file"] != "eyJ2ZXJkaWN0IjoieCJ9" { t.Fatalf("answer %v, want the ask and its answer's bytes as the file", got) } got, _ = tool.Run(map[string]any{"ask": "none", "call": "c"}) if m := got.(map[string]any); m["ask"] != "none" || m["chunks"] != 0 || m["file"] != nil { t.Fatalf("answer %v, want no chunks and no file", got) } if _, err := tool.Run(map[string]any{"call": "c"}); err == nil { t.Fatal("a call with no ask was answered") } }