test: Prozessgrenzen-Tests für CORS-Tür und MCP-Handshake
CORS: Preflight 204+Header von gelisteter Origin, kein ACAO für ungeliste/ leere Liste, Wildcard, nur-/speak, cors-Feld in /status. MCP: kompletter Handshake als literal JSON-RPC über HTTP, tools/call enqueue sichtbar am Fake-Provider, muted/rate_limit/quiet_hours als isError:false-Ergebnisse, Fehlerpfade (-32700/-32600/-32601/-32602), GET /mcp 405.
This commit is contained in:
1 parent
a577597714
commit
150c374bdc
3 files changed
+565
-2
No files matched your search
+70
-2
@@ -13,6 +13,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -401,8 +402,10 @@ type statusBody struct {
|
||||
Text string `json:"text"`
|
||||
Priority string `json:"priority"`
|
||||
} `json:"current"`
|
||||
Muted bool `json:"muted"`
|
||||
Provider string `json:"provider"`
|
||||
Muted bool `json:"muted"`
|
||||
Provider string `json:"provider"`
|
||||
MCP bool `json:"mcp"`
|
||||
Cors []string `json:"cors"`
|
||||
}
|
||||
|
||||
// getStatus ruft GET /status auf (Deadline: eine Sekunde — der Daemon ist
|
||||
@@ -471,3 +474,68 @@ func postPath(t *testing.T, d *daemonProc, path string) (int, map[string]any) {
|
||||
}
|
||||
return resp.StatusCode, m
|
||||
}
|
||||
|
||||
// rawResponse ist eine vollständig gelesene HTTP-Antwort (Status, Header,
|
||||
// Body als String) — Beobachtungsform der rohen CORS-/MCP-Tests.
|
||||
type rawResponse struct {
|
||||
status int
|
||||
header http.Header
|
||||
body string
|
||||
}
|
||||
|
||||
// doRequest macht einen rohen HTTP-Request mit beliebigen Headern und
|
||||
// optionalem Body und liest die Antwort komplett (CORS-Preflights und
|
||||
// MCP-JSON-RPC-Payloads sind literal, kein Client-SDK dazwischen).
|
||||
func doRequest(t *testing.T, method, url string, headers map[string]string, body string) rawResponse {
|
||||
t.Helper()
|
||||
var rd io.Reader
|
||||
if body != "" {
|
||||
rd = strings.NewReader(body)
|
||||
}
|
||||
req, err := http.NewRequest(method, url, rd)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s bauen: %v", method, url, err)
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s: %v", method, url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
raw, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s Antwort lesen: %v", method, url, err)
|
||||
}
|
||||
return rawResponse{status: resp.StatusCode, header: resp.Header, body: string(raw)}
|
||||
}
|
||||
|
||||
// preflight macht einen OPTIONS-Preflight gegen /speak (so wie ein Browser
|
||||
// es vor einem echten POST tut).
|
||||
func preflight(t *testing.T, d *daemonProc, origin string) rawResponse {
|
||||
t.Helper()
|
||||
return doRequest(t, http.MethodOptions, d.baseURL+"/speak", map[string]string{
|
||||
"Origin": origin,
|
||||
"Access-Control-Request-Method": http.MethodPost,
|
||||
}, "")
|
||||
}
|
||||
|
||||
// mcpPost POSTet einen literalen JSON-RPC-Payload an /mcp.
|
||||
func mcpPost(t *testing.T, d *daemonProc, payload string) rawResponse {
|
||||
t.Helper()
|
||||
return doRequest(t, http.MethodPost, d.baseURL+"/mcp", map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
}, payload)
|
||||
}
|
||||
|
||||
// mcpDecode dekodiert eine JSON-RPC-Antwort in eine generische Map (für
|
||||
// Feld-Zugriffe der literalen Payload-Tests).
|
||||
func mcpDecode(t *testing.T, raw rawResponse) map[string]any {
|
||||
t.Helper()
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal([]byte(raw.body), &m); err != nil {
|
||||
t.Fatalf("JSON-RPC-Antwort dekodieren: %v — Body: %s", err, raw.body)
|
||||
}
|
||||
return m
|
||||
}
|
||||
Reference in new issue
Block a user