StepEnded.ReadOutput takes the inline `output`, else asks the seat's verb output on the node that ran the ask for the object `output-object` names (the runtime hands the chunks as the member `file`, A-249); a step-ended with both is refused. Builds: design 47 G A-254, A-240, A-247 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
64 lines
2.5 KiB
Go
64 lines
2.5 KiB
Go
package buildstep
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Design 47 G A-254: one reader of a run's answer from a step-ended, inline or through the seat's verb output on the node that ran it.
|
|
func TestStepEndedReadOutput(t *testing.T) {
|
|
const ask = "m2kx9a1b3c4d5f"
|
|
var key string
|
|
var body map[string]any
|
|
verb := func(answer string, err error) AskVerb {
|
|
return func(k string, b any) (json.RawMessage, error) {
|
|
key = k
|
|
body, _ = b.(map[string]any)
|
|
return json.RawMessage(answer), err
|
|
}
|
|
}
|
|
for _, c := range []struct {
|
|
name string
|
|
end StepEnded
|
|
ask AskVerb
|
|
want string
|
|
wantKey string
|
|
fail string
|
|
}{
|
|
{name: "inline", end: StepEnded{Ask: ask, Node: "ace", Output: `{"a":1}`}, want: `{"a":1}`},
|
|
{name: "an object is read through the verb on its node", end: StepEnded{Ask: ask, Node: "ace", OutputObject: OutputObjectName(ask)},
|
|
ask: verb(`{"ask":"`+ask+`","file":"eyJhIjoxfQ=="}`, nil), want: `{"a":1}`, wantKey: "seat:node-build-go.output@ace"},
|
|
{name: "no answer kept", end: StepEnded{Ask: ask, Node: "ace", OutputObject: "o"}, ask: verb(`{"ask":"`+ask+`","chunks":0}`, nil), fail: "keeps no answer"},
|
|
{name: "another ask's answer", end: StepEnded{Ask: ask, Node: "ace", OutputObject: "o"}, ask: verb(`{"ask":"x","file":"e30="}`, nil), fail: "answered the ask"},
|
|
{name: "the verb fails", end: StepEnded{Ask: ask, Node: "ace", OutputObject: "o"}, ask: verb(``, errors.New("no seat")), fail: "no seat"},
|
|
{name: "no verb", end: StepEnded{Ask: ask, Node: "ace", OutputObject: "o"}, fail: "no verb"},
|
|
} {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
key, body = "", nil
|
|
got, err := c.end.ReadOutput("go", c.ask)
|
|
if c.fail != "" {
|
|
if err == nil || !strings.Contains(err.Error(), c.fail) {
|
|
t.Fatalf("err %v, want %q", err, c.fail)
|
|
}
|
|
return
|
|
}
|
|
if err != nil || string(got) != c.want || key != c.wantKey {
|
|
t.Fatalf("got %q, %v on %q, want %q on %q", got, err, key, c.want, c.wantKey)
|
|
}
|
|
if c.wantKey != "" && (body["ask"] != ask || len(body["call"].(string)) != 32) {
|
|
t.Fatalf("asked with %v, want the ask and 128 random bits as call", body)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A step-ended names its answer inline or as an object, never both.
|
|
func TestStepEndedRefusesAnswerTwice(t *testing.T) {
|
|
e := StepEnded{ID: "m2kx9a1b3c4d5f", Ask: "m2kx9a1b3c4d5f", Asker: "a", Node: "ace", Outcome: OutcomePassed, Output: "1", OutputObject: "o"}
|
|
if _, err := EncodeStepEnded(e); err == nil || !strings.Contains(err.Error(), "inline and as an object") {
|
|
t.Fatalf("err %v", err)
|
|
}
|
|
}
|