mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-10 00:14:38 +00:00
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.
174 lines
4.7 KiB
Go
174 lines
4.7 KiB
Go
package agentcapabilities
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewToolBlockedErrorBuildsSharedEnvelope(t *testing.T) {
|
|
resp := NewToolBlockedError(ErrCodeStrictResolution, "discover first", map[string]any{
|
|
"resource_id": "vm:101",
|
|
})
|
|
|
|
if resp.OK {
|
|
t.Fatal("blocked tool response must set ok=false")
|
|
}
|
|
if resp.Error == nil {
|
|
t.Fatal("blocked tool response must include error")
|
|
}
|
|
if resp.Error.Code != ErrCodeStrictResolution {
|
|
t.Fatalf("error code = %q, want %q", resp.Error.Code, ErrCodeStrictResolution)
|
|
}
|
|
if !resp.Error.Blocked || resp.Error.Failed || resp.Error.Retryable {
|
|
t.Fatalf("unexpected error posture: %+v", resp.Error)
|
|
}
|
|
|
|
body, err := json.Marshal(resp)
|
|
if err != nil {
|
|
t.Fatalf("marshal response: %v", err)
|
|
}
|
|
text := string(body)
|
|
for _, want := range []string{
|
|
`"ok":false`,
|
|
`"code":"STRICT_RESOLUTION"`,
|
|
`"blocked":true`,
|
|
`"resource_id":"vm:101"`,
|
|
} {
|
|
if !strings.Contains(text, want) {
|
|
t.Fatalf("tool response missing %s: %s", want, text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestToolResponseResultPreservesErrorState(t *testing.T) {
|
|
blocked := NewToolBlockedError(ErrCodeActionNotAllowed, "not available here", nil)
|
|
result := NewToolResponseResult(blocked)
|
|
if !result.IsError {
|
|
t.Fatal("blocked tool response must become isError=true")
|
|
}
|
|
if len(result.Content) != 1 || !strings.Contains(result.Content[0].Text, `"code":"ACTION_NOT_ALLOWED"`) {
|
|
t.Fatalf("blocked response not encoded in tool content: %+v", result)
|
|
}
|
|
|
|
ok := NewToolResponseResult(ToolResponse{OK: true, Data: map[string]any{"status": "ok"}})
|
|
if ok.IsError {
|
|
t.Fatal("ok tool response must become isError=false")
|
|
}
|
|
if len(ok.Content) != 1 || !strings.Contains(ok.Content[0].Text, `"status":"ok"`) {
|
|
t.Fatalf("ok response not encoded in tool content: %+v", ok)
|
|
}
|
|
|
|
}
|
|
|
|
func TestToolResultHasVerificationOK(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
text string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "top level verification ok",
|
|
text: `{"success":true,"verification":{"ok":true,"method":"exit_code"}}`,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "leading text before json",
|
|
text: `Action complete: {"verification":{"ok":true}}`,
|
|
want: true,
|
|
},
|
|
{
|
|
name: "verification false",
|
|
text: `{"verification":{"ok":false}}`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "missing verification",
|
|
text: `{"success":true}`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "malformed json",
|
|
text: `Action complete: {"verification":{"ok":true}`,
|
|
want: false,
|
|
},
|
|
{
|
|
name: "non boolean ok",
|
|
text: `{"verification":{"ok":"true"}}`,
|
|
want: false,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := ToolResultHasVerificationOK(tc.text); got != tc.want {
|
|
t.Fatalf("ToolResultHasVerificationOK(%q) = %v, want %v", tc.text, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestToolResultErrorCode(t *testing.T) {
|
|
blocked := NewToolResponseResult(NewToolBlockedError(ErrCodeStrictResolution, "discover first", nil))
|
|
blockedText := ToolResultText(blocked)
|
|
fsmBlocked := NewToolResponseResult(NewToolBlockedError(ErrCodeFSMBlocked, "verify before writing again", nil))
|
|
fsmBlockedText := ToolResultText(fsmBlocked)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
text string
|
|
wantCode string
|
|
wantOK bool
|
|
}{
|
|
{
|
|
name: "tool response error code",
|
|
text: blockedText,
|
|
wantCode: ErrCodeStrictResolution,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "fsm blocked error code",
|
|
text: fsmBlockedText,
|
|
wantCode: ErrCodeFSMBlocked,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "leading text before json",
|
|
text: `Blocked: {"ok":false,"error":{"code":"ROUTING_MISMATCH","message":"choose child"}}`,
|
|
wantCode: ErrCodeRoutingMismatch,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "legacy top level error code",
|
|
text: `{"error_code":"EXECUTION_CONTEXT_UNAVAILABLE"}`,
|
|
wantCode: ErrCodeExecutionContextUnavailable,
|
|
wantOK: true,
|
|
},
|
|
{
|
|
name: "missing error",
|
|
text: `{"ok":true}`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "malformed json",
|
|
text: `Blocked: {"error":{"code":"STRICT_RESOLUTION"}`,
|
|
wantOK: false,
|
|
},
|
|
{
|
|
name: "non string code",
|
|
text: `{"error":{"code":42}}`,
|
|
wantOK: false,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
gotCode, gotOK := ToolResultErrorCode(tc.text)
|
|
if gotCode != tc.wantCode || gotOK != tc.wantOK {
|
|
t.Fatalf("ToolResultErrorCode(%q) = %q, %v; want %q, %v", tc.text, gotCode, gotOK, tc.wantCode, tc.wantOK)
|
|
}
|
|
if gotOK && !ToolResultHasErrorCode(tc.text, tc.wantCode) {
|
|
t.Fatalf("ToolResultHasErrorCode(%q, %q) = false, want true", tc.text, tc.wantCode)
|
|
}
|
|
if ToolResultHasErrorCode(tc.text, ErrCodeNoAgent) {
|
|
t.Fatalf("ToolResultHasErrorCode(%q, %q) = true, want false", tc.text, ErrCodeNoAgent)
|
|
}
|
|
})
|
|
}
|
|
}
|