Files
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

215 lines
7.4 KiB
Go

package agentsessionstore
import (
"context"
"errors"
"sync"
"testing"
)
func TestSessionActivityReporterAdapterReportsStateAndMessages(t *testing.T) {
reporter := &fakeSessionActivityReporter{}
store := NewFileAgentSyncStateStore(t.TempDir())
adapter := NewSessionActivityReporterAdapter(reporter, WithReporterSyncStateStore(store))
err := adapter.Report(context.Background(), ReportActivityInput{
WorkspaceID: "room-1",
Source: EventSource{
Provider: "codex",
AgentID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
},
StatePatches: []WorkspaceAgentStatePatch{{
AgentSessionID: "session-1",
LifecycleStatus: "active",
CurrentPhase: "working",
}},
MessageUpdates: []WorkspaceAgentMessageUpdate{
{AgentSessionID: "session-1", MessageID: "message-1", TurnID: "turn-1", Role: "assistant", Kind: "text"},
{AgentSessionID: "session-1", MessageID: "message-2", TurnID: "turn-1", Role: "assistant", Kind: "text"},
},
})
if err != nil {
t.Fatal(err)
}
if len(reporter.stateInputs) != 1 || reporter.stateInputs[0].AgentSessionID != "session-1" {
t.Fatalf("state inputs = %#v, want one for session-1", reporter.stateInputs)
}
if reporter.stateInputs[0].State.LifecycleStatus != "active" {
t.Fatalf("state update = %#v", reporter.stateInputs[0].State)
}
if len(reporter.messageInputs) != 1 || len(reporter.messageInputs[0].Updates) != 2 {
t.Fatalf("message inputs = %#v, want one group with two updates", reporter.messageInputs)
}
syncStates := adapter.RoomSyncStates("room-1")
syncState, ok := syncStates["session-1"]
if !ok {
t.Fatalf("sync states = %#v, want session-1", syncStates)
}
if syncState.Status != WorkspaceAgentSyncStatusSynced {
t.Fatalf("sync status = %q, want synced", syncState.Status)
}
if syncState.PendingStatePatchCount != 0 || syncState.PendingMessageUpdateCount != 0 {
t.Fatalf("pending counts = %#v, want zero", syncState)
}
if syncState.LastSyncedAtUnixMS <= 0 {
t.Fatalf("last synced = %d, want > 0", syncState.LastSyncedAtUnixMS)
}
persisted, err := store.LoadRoomSyncStates(context.Background(), "room-1")
if err != nil {
t.Fatal(err)
}
if persisted["session-1"].Status != WorkspaceAgentSyncStatusSynced {
t.Fatalf("persisted sync state = %#v, want synced", persisted["session-1"])
}
}
func TestSessionActivityReporterAdapterMarksFailedAndPersistsLastError(t *testing.T) {
reporter := &fakeSessionActivityReporter{stateErr: errors.New("controlplane unavailable")}
store := NewFileAgentSyncStateStore(t.TempDir())
adapter := NewSessionActivityReporterAdapter(reporter, WithReporterSyncStateStore(store))
err := adapter.Report(context.Background(), ReportActivityInput{
WorkspaceID: "room-1",
Source: EventSource{
Provider: "codex",
AgentID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
},
StatePatches: []WorkspaceAgentStatePatch{{
AgentSessionID: "session-1",
LifecycleStatus: "active",
}},
})
if err == nil {
t.Fatal("expected report error")
}
syncState := adapter.RoomSyncStates("room-1")["session-1"]
if syncState.Status != WorkspaceAgentSyncStatusFailed {
t.Fatalf("sync status = %q, want failed", syncState.Status)
}
if syncState.LastError != "controlplane unavailable" {
t.Fatalf("last error = %q", syncState.LastError)
}
if syncState.FailedReportCount != 1 {
t.Fatalf("failed report count = %d, want 1", syncState.FailedReportCount)
}
persisted, err := store.LoadRoomSyncStates(context.Background(), "room-1")
if err != nil {
t.Fatal(err)
}
if persisted["session-1"].Status != WorkspaceAgentSyncStatusFailed || persisted["session-1"].LastError == "" {
t.Fatalf("persisted sync state = %#v, want failed with last error", persisted["session-1"])
}
}
func TestSessionActivityReporterAdapterSeedsFromPersistedStates(t *testing.T) {
store := NewFileAgentSyncStateStore(t.TempDir())
if err := store.SaveAgentSyncState(context.Background(), "room-1", WorkspaceAgentSyncState{
AgentSessionID: "session-1",
Status: WorkspaceAgentSyncStatusFailed,
FailedReportCount: 3,
LastError: "boom",
}); err != nil {
t.Fatal(err)
}
adapter := NewSessionActivityReporterAdapter(&fakeSessionActivityReporter{}, WithReporterSyncStateStore(store))
syncState, ok := adapter.RoomSyncStates("room-1")["session-1"]
if !ok || syncState.Status != WorkspaceAgentSyncStatusFailed || syncState.FailedReportCount != 3 {
t.Fatalf("seeded sync state = %#v, ok=%v", syncState, ok)
}
}
func TestSessionActivityReporterAdapterRejectsMessageUpdateWithoutTurnID(t *testing.T) {
reporter := &fakeSessionActivityReporter{}
adapter := NewSessionActivityReporterAdapter(reporter)
err := adapter.Report(context.Background(), ReportActivityInput{
WorkspaceID: "room-1",
Source: EventSource{
AgentID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
},
MessageUpdates: []WorkspaceAgentMessageUpdate{{
AgentSessionID: "session-1",
MessageID: "message-1",
Role: "assistant",
Kind: "text",
}},
})
if err == nil {
t.Fatal("expected missing turnId error")
}
if len(reporter.stateInputs) != 0 || len(reporter.messageInputs) != 0 {
t.Fatalf("reporter received inputs despite conversion error: %#v %#v", reporter.stateInputs, reporter.messageInputs)
}
}
func TestSessionActivityReporterAdapterReportsFirstClassSessionAudit(t *testing.T) {
reporter := &fakeSessionActivityReporter{}
adapter := NewSessionActivityReporterAdapter(reporter)
err := adapter.Report(context.Background(), ReportActivityInput{
WorkspaceID: "room-1",
Source: EventSource{AgentID: "session-1", SessionOrigin: WorkspaceAgentSessionOriginRuntime},
SessionAudits: []WorkspaceAgentSessionAuditUpdate{{
AuditID: "goal-control:op-1", Role: "user", Content: "/goal clear", OccurredAtUnixMS: 10,
}},
})
if err != nil {
t.Fatalf("Report() error = %v", err)
}
if len(reporter.messageInputs) != 1 || len(reporter.messageInputs[0].Updates) != 1 {
t.Fatalf("message inputs = %#v", reporter.messageInputs)
}
update := reporter.messageInputs[0].Updates[0]
if update.Kind != "session_audit" || update.TurnID != "" {
t.Fatalf("audit update = %#v", update)
}
}
func TestSessionActivityReporterAdapterRequiresWorkspaceID(t *testing.T) {
adapter := NewSessionActivityReporterAdapter(&fakeSessionActivityReporter{})
if err := adapter.Report(context.Background(), ReportActivityInput{}); err == nil {
t.Fatal("expected workspace id error")
}
}
type fakeSessionActivityReporter struct {
mu sync.Mutex
stateInputs []ReportSessionStateInput
messageInputs []ReportSessionMessagesInput
stateErr error
messagesErr error
}
func (f *fakeSessionActivityReporter) ReportSessionState(
_ context.Context,
input ReportSessionStateInput,
) (ReportSessionStateReply, error) {
f.mu.Lock()
defer f.mu.Unlock()
if f.stateErr != nil {
return ReportSessionStateReply{}, f.stateErr
}
f.stateInputs = append(f.stateInputs, input)
return ReportSessionStateReply{Accepted: true, StateApplied: true}, nil
}
func (f *fakeSessionActivityReporter) ReportSessionMessages(
_ context.Context,
input ReportSessionMessagesInput,
) (ReportSessionMessagesReply, error) {
f.mu.Lock()
defer f.mu.Unlock()
if f.messagesErr != nil {
return ReportSessionMessagesReply{}, f.messagesErr
}
f.messageInputs = append(f.messageInputs, input)
return ReportSessionMessagesReply{AcceptedCount: len(input.Updates)}, nil
}