package mcpsrv
import (
"context"
"testing"
"sourcecraft.dev/bigbes/sr-ht-spec/authn"
"sourcecraft.dev/bigbes/sr-ht-spec/core"
"sourcecraft.dev/bigbes/sr-ht-spec/service"
)
// fakeWriter captures the request the tool builds and returns a canned result,
// so the handler's mapping — arguments in, principal from context, result out —
// is checked without a service, a repository or a database.
type fakeWriter struct {
got service.ProposeRequest
res service.ProposeResult
err error
}
func (f *fakeWriter) Propose(_ context.Context, req service.ProposeRequest) (service.ProposeResult, error) {
f.got = req
return f.res, f.err
}
// TestProposeHandlerForwardsPrincipalAndArgs proves the tool passes the acting
// agent (resolved onto the request context by the /mcp middleware) and every
// argument through to service.Propose, and maps the result — id, url, merged —
// back out.
func TestProposeHandlerForwardsPrincipalAndArgs(t *testing.T) {
principal := authn.Principal{
Kind: authn.KindAgent,
Owner: "bigbes",
Agent: "claude-code/spec-writer",
Session: "sess-1",
}
ctx := authn.WithPrincipal(context.Background(), principal)
w := &fakeWriter{res: service.ProposeResult{
Proposal: service.Proposal{
ID: 7,
Branch: "proposals/7",
BaseRev: "1f0c1d1a1e2b3c4d5e6f708192a3b4c5d6e7f809",
State: core.StateOpen,
},
URL: "https://spec.srht.bigb.es/~bigbes/rfcs/p/7",
Merged: false,
}}
out, err := proposeHandler(ctx, w, proposeInput{
Space: "~bigbes/rfcs",
IfMatch: "1f0c1d1a1e2b3c4d5e6f708192a3b4c5d6e7f809",
Title: "Add a note",
Rationale: "because",
Message: "write it",
Documents: []proposeDoc{{Path: "notes/a.md", Content: "---\nid: N-1\n---\n"}},
})
if err != nil {
t.Fatalf("proposeHandler: %v", err)
}
// The request the service saw. Principal is no longer comparable with == —
// it carries a grant set — so its rendering stands in.
if w.got.Principal.String() != principal.String() {
t.Errorf("principal = %+v, want the one on the context %+v", w.got.Principal, principal)
}
if w.got.Space != (core.SpaceRef{Owner: "bigbes", Name: "rfcs"}) {
t.Errorf("space = %v, want ~bigbes/rfcs", w.got.Space)
}
if w.got.IfMatch != "1f0c1d1a1e2b3c4d5e6f708192a3b4c5d6e7f809" || w.got.Title != "Add a note" {
t.Errorf("request args not forwarded: %+v", w.got)
}
if len(w.got.Writes) != 1 || w.got.Writes[0].Path != "notes/a.md" {
t.Errorf("writes = %+v, want the one document", w.got.Writes)
}
// The result mapped out.
if out.Proposal != 7 || out.URL != "https://spec.srht.bigb.es/~bigbes/rfcs/p/7" || out.Merged {
t.Errorf("output = %+v, want id 7, the url, merged=false", out)
}
if out.State != "open" || out.Branch != "proposals/7" {
t.Errorf("output state/branch = %q/%q, want open/proposals/7", out.State, out.Branch)
}
}
// TestProposeHandlerRejectsEmptyDocuments refuses a call with no documents
// before it reaches the service.
func TestProposeHandlerRejectsEmptyDocuments(t *testing.T) {
w := &fakeWriter{}
if _, err := proposeHandler(context.Background(), w, proposeInput{Space: "~bigbes/rfcs"}); err == nil {
t.Fatal("proposeHandler with no documents = nil, want error")
}
if w.got.Writes != nil || w.got.Space != (core.SpaceRef{}) {
t.Fatal("service was called despite empty documents")
}
}