- jz.bm.mcp-links on the token twins and the management API: under MCP, url and *_url strings starting with a single / get the app base - material answers under MCP leave the body out unless include_body, marking it with body_omitted; write answers never echo it - jz.bm.manage keys a dispatched call by user - bm_materials and bm_material student tools Plain HTTP answers are unchanged.
168 lines
5.4 KiB
Go
168 lines
5.4 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/bouncer"
|
|
)
|
|
|
|
// mcpRequest is a request as pier dispatches it in-process: the api_token
|
|
// marker with a principal on its context.
|
|
func mcpRequest(method, target string) *http.Request {
|
|
r := httptest.NewRequest(method, target, nil)
|
|
return r.WithContext(bouncer.WithDispatch(r.Context(), &bouncer.Dispatch{
|
|
Guards: []string{"api_token"},
|
|
Principal: &bouncer.Principal{ID: 7},
|
|
}))
|
|
}
|
|
|
|
// keysOf marshals v and returns its top-level JSON object.
|
|
func keysOf(t *testing.T, v any) map[string]any {
|
|
t.Helper()
|
|
raw, err := json.Marshal(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var out map[string]any
|
|
if err := json.Unmarshal(raw, &out); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestViaMCP(t *testing.T) {
|
|
plain := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
if viaMCP(plain) {
|
|
t.Error("a plain request is an MCP call")
|
|
}
|
|
if !viaMCP(mcpRequest(http.MethodGet, "/")) {
|
|
t.Error("a dispatched api_token request is not an MCP call")
|
|
}
|
|
backend := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
backend = backend.WithContext(bouncer.WithDispatch(backend.Context(), &bouncer.Dispatch{
|
|
Guards: []string{"backend"},
|
|
Principal: &bouncer.Principal{ID: 7},
|
|
}))
|
|
if viaMCP(backend) {
|
|
t.Error("a backend dispatch is an MCP call of jz.bm")
|
|
}
|
|
for target, want := range map[string]bool{
|
|
"/": false,
|
|
"/?include_body=false": false,
|
|
"/?include_body=0": false,
|
|
"/?include_body=true": true,
|
|
"/?include_body=1": true,
|
|
} {
|
|
if got := includeBody(httptest.NewRequest(http.MethodGet, target, nil)); got != want {
|
|
t.Errorf("includeBody(%s) = %v, want %v", target, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMCPMaterialView(t *testing.T) {
|
|
body := "transcript text"
|
|
url := "https://x.test/a"
|
|
detail := materialDetailJSON{
|
|
materialListJSON: materialListJSON{ID: 3, Title: "T", Type: "notatka", URL: &url, Tags: []tagJSON{}},
|
|
Body: &body,
|
|
Files: []fileJSON{{ID: 9, URL: "/storage/f.pdf", Name: "f.pdf", Size: 10, Mime: "application/pdf"}},
|
|
}
|
|
|
|
t.Run("plain request unchanged", func(t *testing.T) {
|
|
got := keysOf(t, materialView(detail, viaMCP(httptest.NewRequest(http.MethodGet, "/", nil)), false))
|
|
want := keysOf(t, detail)
|
|
gotRaw, _ := json.Marshal(got)
|
|
wantRaw, _ := json.Marshal(want)
|
|
if string(gotRaw) != string(wantRaw) {
|
|
t.Errorf("plain view = %s, want %s", gotRaw, wantRaw)
|
|
}
|
|
})
|
|
|
|
t.Run("MCP without include_body", func(t *testing.T) {
|
|
got := keysOf(t, materialView(detail, viaMCP(mcpRequest(http.MethodGet, "/")), includeBody(mcpRequest(http.MethodGet, "/"))))
|
|
if _, ok := got["body"]; ok {
|
|
t.Errorf("body present: %v", got)
|
|
}
|
|
if got["body_omitted"] != true {
|
|
t.Errorf("body_omitted = %v, want true", got["body_omitted"])
|
|
}
|
|
files, _ := got["files"].([]any)
|
|
if len(files) != 1 {
|
|
t.Fatalf("files = %v", got["files"])
|
|
}
|
|
f := files[0].(map[string]any)
|
|
for _, k := range []string{"id", "name", "size", "mime", "url"} {
|
|
if _, ok := f[k]; !ok {
|
|
t.Errorf("file lacks %s: %v", k, f)
|
|
}
|
|
}
|
|
if got["title"] != "T" || got["url"] != url {
|
|
t.Errorf("list fields lost: %v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("MCP without include_body, empty body", func(t *testing.T) {
|
|
empty := detail
|
|
empty.Body = nil
|
|
got := keysOf(t, materialView(empty, true, false))
|
|
if _, ok := got["body"]; ok {
|
|
t.Errorf("body present: %v", got)
|
|
}
|
|
if _, ok := got["body_omitted"]; ok {
|
|
t.Errorf("body_omitted set for an empty body: %v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("MCP with include_body", func(t *testing.T) {
|
|
got := keysOf(t, materialView(detail, true, includeBody(mcpRequest(http.MethodGet, "/?include_body=true"))))
|
|
if got["body"] != body {
|
|
t.Errorf("body = %v, want %q", got["body"], body)
|
|
}
|
|
if _, ok := got["body_omitted"]; ok {
|
|
t.Errorf("body_omitted set with include_body: %v", got)
|
|
}
|
|
})
|
|
|
|
managed := managedMaterialJSON{ID: 3, Title: "T", Type: "notatka", Body: &body, Tags: []tagJSON{}}
|
|
|
|
t.Run("managed plain unchanged", func(t *testing.T) {
|
|
got := keysOf(t, managedMaterialView(managed, false, false))
|
|
if got["body"] != body {
|
|
t.Errorf("plain managed body = %v", got["body"])
|
|
}
|
|
if _, ok := got["body_omitted"]; ok {
|
|
t.Errorf("plain managed has body_omitted: %v", got)
|
|
}
|
|
})
|
|
|
|
t.Run("managed MCP read", func(t *testing.T) {
|
|
got := keysOf(t, managedMaterialView(managed, true, false))
|
|
if _, ok := got["body"]; ok || got["body_omitted"] != true {
|
|
t.Errorf("managed MCP read = %v, want no body and body_omitted", got)
|
|
}
|
|
got = keysOf(t, managedMaterialView(managed, true, includeBody(mcpRequest(http.MethodGet, "/?include_body=1"))))
|
|
if got["body"] != body {
|
|
t.Errorf("managed MCP read with include_body = %v", got)
|
|
}
|
|
})
|
|
|
|
// A write answer passes withBody false whatever the request asks.
|
|
t.Run("managed MCP write never echoes the body", func(t *testing.T) {
|
|
got := keysOf(t, managedMaterialView(managed, true, false))
|
|
if _, ok := got["body"]; ok || got["body_omitted"] != true {
|
|
t.Errorf("managed MCP write = %v, want no body and body_omitted", got)
|
|
}
|
|
d := managedMaterialDetailJSON{managedMaterialJSON: managed, Files: []fileJSON{}}
|
|
got = keysOf(t, managedMaterialDetailView(d, true, false))
|
|
if _, ok := got["body"]; ok || got["body_omitted"] != true {
|
|
t.Errorf("managed MCP write detail = %v, want no body and body_omitted", got)
|
|
}
|
|
if _, ok := got["files"]; !ok {
|
|
t.Errorf("managed MCP write detail lost files: %v", got)
|
|
}
|
|
})
|
|
}
|