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.
244 lines
7.7 KiB
Go
244 lines
7.7 KiB
Go
package agentcapabilities
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestScanSSERecordsParsesCanonicalRecords(t *testing.T) {
|
|
input := strings.Join([]string{
|
|
": connected comment",
|
|
"event: stream.connected",
|
|
"data: {}",
|
|
"",
|
|
"event: finding.created",
|
|
"data: {\"findingId\":\"f1\"}",
|
|
"data: {\"severity\":\"critical\"}",
|
|
"",
|
|
}, "\r\n")
|
|
|
|
var got []SSERecord
|
|
if err := ScanSSERecords(strings.NewReader(input), func(record SSERecord) bool {
|
|
got = append(got, record)
|
|
return true
|
|
}); err != nil {
|
|
t.Fatalf("ScanSSERecords: %v", err)
|
|
}
|
|
|
|
if len(got) != 2 {
|
|
t.Fatalf("records len = %d, want 2: %+v", len(got), got)
|
|
}
|
|
if got[0] != (SSERecord{Event: "stream.connected", Data: "{}"}) {
|
|
t.Fatalf("first record = %+v", got[0])
|
|
}
|
|
wantData := "{\"findingId\":\"f1\"}\n{\"severity\":\"critical\"}"
|
|
if got[1] != (SSERecord{Event: "finding.created", Data: wantData}) {
|
|
t.Fatalf("second record = %+v, want data %q", got[1], wantData)
|
|
}
|
|
}
|
|
|
|
func TestScanSSERecordsStopsWhenHandlerReturnsFalse(t *testing.T) {
|
|
input := "event: finding.created\ndata: {\"findingId\":\"f1\"}\n\nevent: action.completed\ndata: {\"actionId\":\"a1\"}\n\n"
|
|
var got []SSERecord
|
|
if err := ScanSSERecords(strings.NewReader(input), func(record SSERecord) bool {
|
|
got = append(got, record)
|
|
return false
|
|
}); err != nil {
|
|
t.Fatalf("ScanSSERecords: %v", err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatalf("records len = %d, want 1: %+v", len(got), got)
|
|
}
|
|
if got[0].Event != string(EventKindFindingCreated) {
|
|
t.Fatalf("first event = %q, want %q", got[0].Event, EventKindFindingCreated)
|
|
}
|
|
}
|
|
|
|
func TestScanSSERecordsFlushesFinalRecordAtEOF(t *testing.T) {
|
|
input := "event: action.completed\ndata: {\"actionId\":\"a1\"}"
|
|
var got []SSERecord
|
|
if err := ScanSSERecords(strings.NewReader(input), func(record SSERecord) bool {
|
|
got = append(got, record)
|
|
return true
|
|
}); err != nil {
|
|
t.Fatalf("ScanSSERecords: %v", err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatalf("records len = %d, want 1: %+v", len(got), got)
|
|
}
|
|
if got[0] != (SSERecord{Event: string(EventKindActionCompleted), Data: "{\"actionId\":\"a1\"}"}) {
|
|
t.Fatalf("record = %+v", got[0])
|
|
}
|
|
}
|
|
|
|
func TestStreamAgentSSERecordsBuildsCanonicalSubscriptionRequest(t *testing.T) {
|
|
stream := strings.Join([]string{
|
|
"event: " + string(EventKindFindingCreated),
|
|
"data: {\"findingId\":\"f1\"}",
|
|
"",
|
|
"event: " + string(EventKindActionCompleted),
|
|
"data: {\"actionId\":\"a1\"}",
|
|
"",
|
|
}, "\n")
|
|
|
|
requests := 0
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
requests++
|
|
if r.Method != http.MethodGet {
|
|
t.Errorf("method = %s, want GET", r.Method)
|
|
}
|
|
if r.URL.Path != AgentEventsPath {
|
|
t.Errorf("path = %s, want %s", r.URL.Path, AgentEventsPath)
|
|
}
|
|
if r.Header.Get(AgentAPITokenHeader) != "test-token" {
|
|
t.Errorf("%s header = %q", AgentAPITokenHeader, r.Header.Get(AgentAPITokenHeader))
|
|
}
|
|
if r.Header.Get("Accept") != AgentSSEAccept {
|
|
t.Errorf("Accept header = %q, want %q", r.Header.Get("Accept"), AgentSSEAccept)
|
|
}
|
|
w.Header().Set("Content-Type", AgentSSEAccept)
|
|
_, _ = w.Write([]byte(stream))
|
|
}))
|
|
defer server.Close()
|
|
|
|
var got []SSERecord
|
|
err := StreamAgentSSERecords(context.Background(), server.Client(), server.URL, AgentEventsPath, "test-token", func(record SSERecord) bool {
|
|
got = append(got, record)
|
|
return false
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("StreamAgentSSERecords: %v", err)
|
|
}
|
|
if requests != 1 {
|
|
t.Fatalf("requests = %d, want 1", requests)
|
|
}
|
|
if len(got) != 1 || got[0].Event != string(EventKindFindingCreated) {
|
|
t.Fatalf("got records = %+v, want first finding.created only", got)
|
|
}
|
|
}
|
|
|
|
func TestStreamAgentSSERecordsReturnsStatusError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
}))
|
|
defer server.Close()
|
|
|
|
err := StreamAgentSSERecords(context.Background(), server.Client(), server.URL, AgentEventsPath, "bad-token", nil)
|
|
if err == nil || !strings.Contains(err.Error(), "subscribe: status 401") {
|
|
t.Fatalf("error = %v, want subscribe status error", err)
|
|
}
|
|
}
|
|
|
|
func TestStreamAgentActionableSSERecordsFiltersTransportRecords(t *testing.T) {
|
|
stream := strings.Join([]string{
|
|
": connected comment",
|
|
"event: " + string(EventKindStreamConnected),
|
|
"data: {}",
|
|
"",
|
|
"event: " + string(EventKindHeartbeat),
|
|
"",
|
|
"data: unnamed payload",
|
|
"",
|
|
"event: " + string(EventKindFindingCreated),
|
|
"data: {\"findingId\":\"f1\"}",
|
|
"",
|
|
}, "\n")
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", AgentSSEAccept)
|
|
_, _ = w.Write([]byte(stream))
|
|
}))
|
|
defer server.Close()
|
|
|
|
var got []SSERecord
|
|
err := StreamAgentActionableSSERecords(context.Background(), server.Client(), server.URL, AgentEventsPath, "test-token", func(record SSERecord) bool {
|
|
got = append(got, record)
|
|
return true
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("StreamAgentActionableSSERecords: %v", err)
|
|
}
|
|
if len(got) != 1 {
|
|
t.Fatalf("actionable records len = %d, want 1: %+v", len(got), got)
|
|
}
|
|
if got[0] != (SSERecord{Event: string(EventKindFindingCreated), Data: "{\"findingId\":\"f1\"}"}) {
|
|
t.Fatalf("actionable record = %+v", got[0])
|
|
}
|
|
}
|
|
|
|
func TestStreamMCPEventNotificationsProjectsActionableEvents(t *testing.T) {
|
|
stream := strings.Join([]string{
|
|
": connected comment",
|
|
"event: " + string(EventKindStreamConnected),
|
|
"data: {}",
|
|
"",
|
|
"event: " + string(EventKindFindingCreated),
|
|
"data: {\"findingId\":\"f1\"}",
|
|
"",
|
|
"event: " + string(EventKindHeartbeat),
|
|
"",
|
|
"event: " + string(EventKindActionCompleted),
|
|
"data: {\"actionId\":\"a1\"}",
|
|
"",
|
|
}, "\n")
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get(AgentAPITokenHeader) != "test-token" {
|
|
t.Errorf("%s header = %q", AgentAPITokenHeader, r.Header.Get(AgentAPITokenHeader))
|
|
}
|
|
if r.Header.Get("Accept") != AgentSSEAccept {
|
|
t.Errorf("Accept header = %q, want %q", r.Header.Get("Accept"), AgentSSEAccept)
|
|
}
|
|
w.Header().Set("Content-Type", AgentSSEAccept)
|
|
_, _ = w.Write([]byte(stream))
|
|
}))
|
|
defer server.Close()
|
|
|
|
var got []JSONRPCRequest
|
|
err := StreamMCPEventNotifications(context.Background(), server.Client(), server.URL, AgentEventsPath, "test-token", func(notification JSONRPCRequest) error {
|
|
got = append(got, notification)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("StreamMCPEventNotifications: %v", err)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Fatalf("notifications len = %d, want finding.created and action.completed: %+v", len(got), got)
|
|
}
|
|
if got[0].Method != MCPNotificationMethod(string(EventKindFindingCreated)) {
|
|
t.Fatalf("first notification method = %q", got[0].Method)
|
|
}
|
|
if got[1].Method != MCPNotificationMethod(string(EventKindActionCompleted)) {
|
|
t.Fatalf("second notification method = %q", got[1].Method)
|
|
}
|
|
if string(got[0].Params) != `{"findingId":"f1"}` {
|
|
t.Fatalf("first notification params = %s", got[0].Params)
|
|
}
|
|
if len(got[0].ID) != 0 || len(got[1].ID) != 0 {
|
|
t.Fatalf("notifications must not carry ids: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestStreamMCPEventNotificationsReturnsWriterError(t *testing.T) {
|
|
stream := strings.Join([]string{
|
|
"event: " + string(EventKindFindingCreated),
|
|
"data: {\"findingId\":\"f1\"}",
|
|
"",
|
|
}, "\n")
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", AgentSSEAccept)
|
|
_, _ = w.Write([]byte(stream))
|
|
}))
|
|
defer server.Close()
|
|
|
|
sentinel := errors.New("write notification")
|
|
err := StreamMCPEventNotifications(context.Background(), server.Client(), server.URL, AgentEventsPath, "test-token", func(notification JSONRPCRequest) error {
|
|
return sentinel
|
|
})
|
|
if !errors.Is(err, sentinel) {
|
|
t.Fatalf("error = %v, want writer error", err)
|
|
}
|
|
}
|