Files
mesh-catalog/modules/mesh-issues/cmd/mesh-issues/caller_test.go
T
jschoubben 66068febb0 mesh-issues: record who acted as the caller the runtime proves (hq issue 365)
The tool runner now takes a call's caller from the subject it arrived on and
hands it to the module through the SDK (stdio.CallerOf); the tracker records
that bus user on what each verb writes instead of unproven. Never from the
arguments, which still refuse any caller, and unproven where the runtime proves
none. Needs mesh-sdk's go/caller.go (pinned at its branch head until merged).
2026-10-11 11:22:41 +02:00

141 lines
5.0 KiB
Go

package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"strings"
"testing"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// served is the tracker's verbs served as the runtime launches them, over the SDK's stdio loop, with the caller
// read from the runtime (stdio.CallerOf): what reaches the module is what a tools/call carries.
type served struct {
in *io.PipeWriter
lines *bufio.Scanner
next int
}
func serve(t *testing.T, h *harness) *served {
t.Helper()
h.CallerOf = stdio.CallerOf
inR, inW := io.Pipe()
outR, outW := io.Pipe()
go func() { _ = stdio.ServeOn("mesh-issues", Tools(h.Tracker), inR, outW) }()
t.Cleanup(func() { _ = inW.Close() })
return &served{in: inW, lines: bufio.NewScanner(outR)}
}
// call sends one tools/call, with the runtime's `_meta` naming caller when one is given, and answers the
// verb's result or its refusal.
func (s *served) call(t *testing.T, verb, caller string, args map[string]any) (map[string]any, string) {
t.Helper()
s.next++
params := map[string]any{"name": Seat + "." + verb, "arguments": args}
if caller != "" {
params["_meta"] = map[string]any{stdio.MetaCaller: caller}
}
b, _ := json.Marshal(map[string]any{"jsonrpc": "2.0", "id": s.next, "method": "tools/call", "params": params})
_, _ = s.in.Write(append(b, '\n'))
done := make(chan []byte, 1)
go func() {
if s.lines.Scan() {
done <- append([]byte(nil), s.lines.Bytes()...)
}
}()
var line []byte
select {
case line = <-done:
case <-time.After(5 * time.Second):
t.Fatalf("%s was not answered", verb)
}
var m struct {
Result struct {
Content []struct {
Text string `json:"text"`
} `json:"content"`
IsError bool `json:"isError"`
} `json:"result"`
}
_ = json.Unmarshal(line, &m)
if len(m.Result.Content) == 0 {
t.Fatalf("%s was answered %s", verb, line)
}
text := m.Result.Content[0].Text
if m.Result.IsError {
return nil, text
}
var out map[string]any
_ = json.Unmarshal([]byte(text), &out)
return out, ""
}
// **Who acted is the caller the runtime proved** (novox/hq issue 365, ADR 0275 §5): a call whose subject named
// its caller is recorded as that bus user — on the issue it opened, the note, the link, the move and its event,
// the value set, and the number reserved.
func TestWhoActedIsTheCallerTheRuntimeProved(t *testing.T) {
h, _ := reserveHarness(t)
s := serve(t, h)
const who = "shanks.node-tools"
opened, refused := s.call(t, "open", who, map[string]any{"title": "a title", "report": "a report"})
if refused != "" || opened["by"] != who {
t.Fatalf("open answered %v %q, want by %s", opened, refused, who)
}
n := uint64(opened["number"].(float64))
other, _ := s.call(t, "open", who, map[string]any{"title": "another", "report": "another report"})
is, _ := h.Get(n)
if is.OpenedBy != who {
t.Errorf("the issue was opened by %q", is.OpenedBy)
}
if noted, refused := s.call(t, "note", who, map[string]any{"number": n, "text": "seen again"}); refused != "" || noted["by"] != who {
t.Errorf("note answered %v %q", noted, refused)
}
if linked, refused := s.call(t, "link", who, map[string]any{"number": n, "kind": "relates-to", "target": fmt.Sprint(other["number"])}); refused != "" || linked["by"] != who {
t.Errorf("link answered %v %q", linked, refused)
}
if _, refused := s.call(t, "set", who, map[string]any{"number": n, "severity": "minor"}); refused != "" {
t.Errorf("set refused: %s", refused)
}
if _, refused := s.call(t, "move", who, map[string]any{"number": n, "status": "fixing", "by": "fix-1@g14"}); refused != "" {
t.Errorf("move refused: %s", refused)
}
if reserved, refused := s.call(t, "reserve", who, map[string]any{"kind": "decision", "for": "a decision"}); refused != "" || reserved["by"] != who {
t.Errorf("reserve answered %v %q", reserved, refused)
}
is, _ = h.Get(n)
for _, note := range is.Notes {
if note.By != who {
t.Errorf("a note is by %q", note.By)
}
}
for _, e := range h.said() {
if e.Event == EventMoved && e.Body["caller"] != who {
t.Errorf("the move was said as by caller %v", e.Body["caller"])
}
}
}
// A caller named in the arguments is refused, whatever it names — an argument is the caller's to write — and a
// call the runtime proved no caller for is recorded Unproven, as it always was.
func TestACallerInTheArgumentsIsRefusedAndAnUnprovenCallSaysSo(t *testing.T) {
h := newHarness(t)
s := serve(t, h)
for _, args := range []map[string]any{
{"title": "t", "report": "r", "caller": "novox.node-tools"},
{"title": "t", "report": "r", "_meta": map[string]any{stdio.MetaCaller: "novox.node-tools"}},
{"title": "t", "report": "r", "by": "novox.node-tools"},
} {
if out, refused := s.call(t, "open", "shanks.node-tools", args); refused == "" || !strings.Contains(refused, "does not take") {
t.Errorf("an open naming a caller in its arguments %v was answered %v", args, out)
}
}
opened, refused := s.call(t, "open", "", map[string]any{"title": "t", "report": "r"})
if refused != "" || opened["by"] != Unproven {
t.Fatalf("an open the runtime named no caller for answered %v %q, want by %s", opened, refused, Unproven)
}
}