Reviewed the phase-2 implementation (parts 2–5) end to end. The suite hung
for 600s and several handlers were never exercised because there were no
tests for the new mutation/event/MCP surface. Fixes:
- CRITICAL: sseListener ran on context.Background() and held a pooled
connection forever, so pool.Close() deadlocked (600s test timeout).
NewHandler now takes a ctx that governs the listener; ListenAndServe and
the test helper cancel it before closing the pool.
- CRITICAL: MCP AddTool panicked ("missing input schema") at construction
under go-sdk v1.6.1 — so NewHandler (and every API handler) panicked.
Added object input schemas to all 8 tools via an objSchema helper.
- HIGH: PatchEntity parsed lifecycle transitions as map[string][]string but
the shape is {from:{to:{requires:[]}}}, so every state-change PATCH 500'd.
Parse the nested shape; allow same-state no-ops.
- HIGH: CreateEntity bound SQL NULL for attributes when omitted, violating
the NOT NULL column (the default only applies when omitted). Default to
'{}'.
- MED: serveSSEWriter ignored the request ctx (per-client goroutine leak on
disconnect) and set an invalid Content-Length: -1. Thread ctx through;
omit the header. writeSSE now nil-checks the flusher (io.Pipe path passed
nil → would have panicked on first event).
- MED: SSE `data:` leaked raw sqlcgen.Event (PascalCase, base64 JSONB).
Emit canonical gen.Event so SSE matches GET /events. Verified live.
- LOW: CreateEntity uses uuid.NewV7 (ADR-0005) + real actor from context in
audit; removed dead bearerAuth; fixed vet unkeyed-field warnings.
Tests (would have caught all of the above): entity create/patch with
If-Match 409/400, valid+invalid lifecycle transitions, idempotency replay,
duplicate-slug 409, abstract-type 422, event+audit side effects, MCP tool
registration. Live smoke test confirmed NOTIFY→listener→SSE delivery.
Also adds the missing Phase 2 deliverable: Gitea Actions CI (vet,
golangci-lint, govulncheck, generated-code drift guard, race tests against
TimescaleDB, docker build) and wires sqlc into `make generate`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
41 lines
1.3 KiB
Go
41 lines
1.3 KiB
Go
package mcp
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// TestNewServerRegistersTools verifies every tool registers with a valid
|
|
// input schema. The MCP SDK panics at AddTool if a tool omits its object
|
|
// input schema, so merely constructing the server exercises that contract —
|
|
// this test would have caught the "missing input schema" panic.
|
|
func TestNewServerRegistersTools(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Fatalf("newServer panicked (tool schema bug?): %v", r)
|
|
}
|
|
}()
|
|
// pool is only used inside tool handlers (invoked per-call), not at
|
|
// registration time, so a nil pool is safe for this construction test.
|
|
s := newServer(nil)
|
|
if s == nil {
|
|
t.Fatal("newServer returned nil")
|
|
}
|
|
}
|
|
|
|
func TestObjSchema(t *testing.T) {
|
|
s := objSchema(prop{"foo", "string", "a foo"}, prop{"n", "integer", "a number"})
|
|
if s.Type != "object" {
|
|
t.Errorf("schema type = %q, want object", s.Type)
|
|
}
|
|
if len(s.Properties) != 2 {
|
|
t.Fatalf("got %d properties, want 2", len(s.Properties))
|
|
}
|
|
if s.Properties["foo"].Type != "string" || s.Properties["n"].Type != "integer" {
|
|
t.Errorf("property types wrong: %+v", s.Properties)
|
|
}
|
|
// empty schema still valid (object with no properties)
|
|
if objSchema().Type != "object" {
|
|
t.Error("empty objSchema not an object")
|
|
}
|
|
}
|