Files
tutti/services/tuttid/agent_runtime_activity_event_bridge_test.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

208 lines
6.4 KiB
Go

package main
import (
"context"
"encoding/json"
"testing"
"time"
"github.com/tutti-os/tutti/packages/agent/daemon/liveprotocol"
agentruntime "github.com/tutti-os/tutti/packages/agent/daemon/runtime"
eventstreamservice "github.com/tutti-os/tutti/services/tuttid/service/eventstream"
)
func TestAgentRuntimeActivityEventBridgePublishesMessageDeltaToBusinessWebSocket(t *testing.T) {
events := eventstreamservice.NewService(eventstreamservice.DefaultCatalog(), nil)
session := events.OpenSession()
defer events.CloseSession(session)
if err := events.Subscribe(
session,
[]string{eventstreamservice.TopicAgentActivityUpdated},
eventstreamservice.EventScope{WorkspaceID: "workspace-1"},
); err != nil {
t.Fatal(err)
}
content, err := json.Marshal("Hel")
if err != nil {
t.Fatal(err)
}
delta, err := liveprotocol.NewMessageDeltaEvent(liveprotocol.MessageDeltaData{
WorkspaceID: "workspace-1",
AgentSessionID: "session-1",
MessageID: "message-1",
TurnID: "turn-1",
Role: "assistant",
Kind: "text",
OccurredAtUnixMS: 100,
Content: &liveprotocol.MessageContentOperation{
Operation: "set",
Value: content,
},
})
if err != nil {
t.Fatal(err)
}
bridge := agentRuntimeActivityEventBridge{
publisher: eventstreamservice.AgentActivityPublisher{Service: events},
}
if err := bridge.ObserveRuntimeStreamEvents(
context.Background(),
"workspace-1",
"session-1",
[]agentruntime.StreamEvent{{
EventType: agentruntime.StreamEventMessageDelta,
Data: delta,
}},
); err != nil {
t.Fatal(err)
}
select {
case published := <-events.Events(session):
var payload struct {
WorkspaceID string `json:"workspaceId"`
AgentSessionID string `json:"agentSessionId"`
EventType string `json:"eventType"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(published.Payload, &payload); err != nil {
t.Fatal(err)
}
if payload.WorkspaceID != "workspace-1" ||
payload.AgentSessionID != "session-1" ||
payload.EventType != "message_delta" {
t.Fatalf("payload = %#v", payload)
}
var got liveprotocol.MessageDeltaData
if err := json.Unmarshal(payload.Data, &got); err != nil {
t.Fatal(err)
}
if got.MessageID != "message-1" || got.Content == nil || string(got.Content.Value) != `"Hel"` {
t.Fatalf("delta data = %#v", got)
}
case <-time.After(time.Second):
t.Fatal("timed out waiting for message_delta")
}
}
func TestAgentRuntimeActivityEventBridgeReconcilesMismatchedMessageDelta(t *testing.T) {
events := eventstreamservice.NewService(eventstreamservice.DefaultCatalog(), nil)
session := events.OpenSession()
defer events.CloseSession(session)
if err := events.Subscribe(
session,
[]string{eventstreamservice.TopicAgentActivityUpdated},
eventstreamservice.EventScope{WorkspaceID: "workspace-1"},
); err != nil {
t.Fatal(err)
}
content, err := json.Marshal("Hel")
if err != nil {
t.Fatal(err)
}
delta, err := liveprotocol.NewMessageDeltaEvent(liveprotocol.MessageDeltaData{
WorkspaceID: "workspace-2",
AgentSessionID: "session-2",
MessageID: "message-1",
TurnID: "turn-1",
Role: "assistant",
Kind: "text",
OccurredAtUnixMS: 100,
Content: &liveprotocol.MessageContentOperation{
Operation: "set",
Value: content,
},
})
if err != nil {
t.Fatal(err)
}
bridge := agentRuntimeActivityEventBridge{
publisher: eventstreamservice.AgentActivityPublisher{Service: events},
}
if filtered := bridge.FilterRuntimeStreamEvents("workspace-1", "session-1", []agentruntime.StreamEvent{{
EventType: agentruntime.StreamEventMessageDelta,
Data: delta,
}}); len(filtered) != 0 {
t.Fatalf("filtered events = %#v, want mismatched delta withheld", filtered)
}
if filtered := bridge.FilterRuntimeStreamEvents("workspace-1", "session-1", []agentruntime.StreamEvent{{
EventType: agentruntime.StreamEventStatePatch,
Data: delta,
}}); len(filtered) != 0 {
t.Fatalf("filtered relabeled delta = %#v, want malformed delta withheld", filtered)
}
if err := bridge.ObserveRuntimeStreamEvents(
context.Background(),
"workspace-1",
"session-1",
[]agentruntime.StreamEvent{{
EventType: agentruntime.StreamEventStatePatch,
Data: delta,
}},
); err == nil {
t.Fatal("ObserveRuntimeStreamEvents() error = nil for relabeled delta, want identity mismatch")
}
if err := bridge.ObserveRuntimeStreamEvents(
context.Background(),
"workspace-1",
"session-1",
[]agentruntime.StreamEvent{{
EventType: agentruntime.StreamEventMessageDelta,
Data: delta,
}},
); err == nil {
t.Fatal("ObserveRuntimeStreamEvents() error = nil, want identity mismatch")
}
select {
case published := <-events.Events(session):
var payload struct {
WorkspaceID string `json:"workspaceId"`
AgentSessionID string `json:"agentSessionId"`
EventType string `json:"eventType"`
Data json.RawMessage `json:"data"`
}
if err := json.Unmarshal(published.Payload, &payload); err != nil {
t.Fatal(err)
}
if payload.WorkspaceID != "workspace-1" ||
payload.AgentSessionID != "session-1" ||
payload.EventType != "session_reconcile_required" {
t.Fatalf("payload = %#v, want expected session reconcile event", payload)
}
var data struct {
WorkspaceID string `json:"workspaceId"`
AgentSessionID string `json:"agentSessionId"`
EventType string `json:"eventType"`
LastEvent int64 `json:"lastEventUnixMs"`
}
if err := json.Unmarshal(payload.Data, &data); err != nil {
t.Fatal(err)
}
if data.WorkspaceID != "workspace-1" || data.AgentSessionID != "session-1" ||
data.EventType != "session_reconcile_required" || data.LastEvent <= 0 {
t.Fatalf("reconcile data = %#v", data)
}
case <-time.After(time.Second):
t.Fatal("timed out waiting for session_reconcile_required")
}
for index := 0; index < 64; index++ {
if err := bridge.ObserveRuntimeStreamEvents(
context.Background(),
"workspace-1",
"session-1",
[]agentruntime.StreamEvent{{
EventType: agentruntime.StreamEventMessageDelta,
Data: delta,
}},
); err == nil {
t.Fatal("ObserveRuntimeStreamEvents() error = nil for repeated mismatched delta")
}
}
select {
case extra := <-events.Events(session):
t.Fatalf("repeated mismatches published an extra event: %#v", extra)
case <-time.After(100 * time.Millisecond):
}
}