Files
tutti/packages/agent/daemon/activity/client_device_metadata_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

363 lines
12 KiB
Go

package agentsessionstore
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
)
func TestReportSessionStateIncludesOptionalAgentTargetAndDeviceMetadata(t *testing.T) {
var got map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
t.Fatal(err)
}
_, _ = w.Write([]byte(`{"accepted":true}`))
}))
defer server.Close()
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
_, err := client.ReportSessionState(context.Background(), ReportSessionStateInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
AgentTargetID: "target-1",
DeviceID: "device-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
Source: EventSource{
Provider: "codex",
AgentID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
},
State: WorkspaceAgentSessionStateUpdate{LifecycleStatus: "active"},
})
if err != nil {
t.Fatal(err)
}
if got["agentTargetId"] != "target-1" || got["deviceId"] != "device-1" {
t.Fatalf("request metadata = %#v, want top-level agentTargetId/deviceId", got)
}
source, ok := got["source"].(map[string]any)
if !ok || source["agentTargetId"] != "target-1" || source["deviceId"] != "device-1" {
t.Fatalf("source = %#v, want propagated metadata", got["source"])
}
state, ok := got["state"].(map[string]any)
if !ok || state["agentTargetId"] != "target-1" || state["deviceId"] != "device-1" {
t.Fatalf("state = %#v, want propagated metadata", got["state"])
}
}
func TestReportSessionStateOmitsMetadataKeysWhenUnset(t *testing.T) {
var raw map[string]json.RawMessage
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
t.Fatal(err)
}
_, _ = w.Write([]byte(`{"accepted":true}`))
}))
defer server.Close()
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
_, err := client.ReportSessionState(context.Background(), ReportSessionStateInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
Source: EventSource{
Provider: "codex",
AgentID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
},
State: WorkspaceAgentSessionStateUpdate{LifecycleStatus: "active"},
})
if err != nil {
t.Fatal(err)
}
for _, key := range []string{"agentTargetId", "deviceId"} {
if _, present := raw[key]; present {
t.Fatalf("request unexpectedly contains %q: %s", key, raw[key])
}
}
var source map[string]json.RawMessage
if err := json.Unmarshal(raw["source"], &source); err != nil {
t.Fatal(err)
}
if _, present := source["deviceId"]; present {
t.Fatalf("source unexpectedly contains deviceId: %s", raw["source"])
}
}
func TestReportSessionStateDerivesTopLevelMetadataFromSourceAndState(t *testing.T) {
var got map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
t.Fatal(err)
}
_, _ = w.Write([]byte(`{"accepted":true}`))
}))
defer server.Close()
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
// The adapter path carries metadata only inside Source/State; the
// explicit input fields stay empty. Top-level request metadata must still
// be populated so controlplanes keying off it get scoped activity.
_, err := client.ReportSessionState(context.Background(), ReportSessionStateInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
Source: EventSource{
Provider: "codex",
AgentTargetID: "target-1",
DeviceID: "device-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
},
State: WorkspaceAgentSessionStateUpdate{LifecycleStatus: "active"},
})
if err != nil {
t.Fatal(err)
}
if got["agentTargetId"] != "target-1" || got["deviceId"] != "device-1" {
t.Fatalf("request metadata = %#v, want top-level metadata derived from source", got)
}
state, ok := got["state"].(map[string]any)
if !ok || state["agentTargetId"] != "target-1" || state["deviceId"] != "device-1" {
t.Fatalf("state = %#v, want metadata filled from source", got["state"])
}
}
func TestReportSessionMessagesDerivesTopLevelMetadataFromSource(t *testing.T) {
var got map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
t.Fatal(err)
}
_, _ = w.Write([]byte(`{"acceptedCount":1}`))
}))
defer server.Close()
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
_, err := client.ReportSessionMessages(context.Background(), ReportSessionMessagesInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
Source: EventSource{
Provider: "codex",
AgentTargetID: "target-1",
DeviceID: "device-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
},
Updates: []WorkspaceAgentSessionMessageUpdate{{
MessageID: "message-1",
TurnID: "turn-1",
Role: "assistant",
Kind: "text",
}},
})
if err != nil {
t.Fatal(err)
}
if got["agentTargetId"] != "target-1" || got["deviceId"] != "device-1" {
t.Fatalf("request metadata = %#v, want top-level metadata derived from source", got)
}
}
func TestReportSessionStateRequestBodyIsByteIdenticalWhenMetadataUnset(t *testing.T) {
var raw []byte
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
t.Fatal(err)
}
raw = body
_, _ = w.Write([]byte(`{"accepted":true}`))
}))
defer server.Close()
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
_, err := client.ReportSessionState(context.Background(), ReportSessionStateInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
Source: EventSource{Provider: "codex"},
State: WorkspaceAgentSessionStateUpdate{LifecycleStatus: "active"},
})
if err != nil {
t.Fatal(err)
}
// Golden body: exactly what this request serialized to before the
// optional AgentTargetID/DeviceID inputs existed. Guards the hard
// constraint that unset metadata leaves the wire byte-for-byte unchanged.
want := `{"roomId":"ws-1","agentSessionId":"session-1",` +
`"sessionOrigin":"WORKSPACE_AGENT_SESSION_ORIGIN_RUNTIME",` +
`"source":{"provider":"codex"},"state":{"lifecycleStatus":"active"}}`
if string(raw) != want {
t.Fatalf("request body = %s, want %s", raw, want)
}
}
func TestReportSessionMessagesSplitBatchesPreserveMetadata(t *testing.T) {
rawText := strings.Repeat("x", maxUpstreamToolPayloadStringBytes+512)
var requestCount int
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var got reportSessionMessagesRequest
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
t.Fatal(err)
}
requestCount++
if got.AgentTargetID != "target-1" || got.DeviceID != "device-1" {
t.Fatalf("batch %d metadata = %q/%q, want target-1/device-1", requestCount, got.AgentTargetID, got.DeviceID)
}
_, _ = w.Write([]byte(`{"acceptedCount":` + strconv.Itoa(len(got.Updates)) + `}`))
}))
defer server.Close()
updates := make([]WorkspaceAgentSessionMessageUpdate, 0, 80)
for i := 0; i < 80; i++ {
updates = append(updates, WorkspaceAgentSessionMessageUpdate{
MessageID: "message-" + strconv.Itoa(i),
TurnID: "turn-1",
Role: "assistant",
Kind: "tool_call",
Payload: map[string]any{
"input": map[string]any{"stdout": rawText},
"output": map[string]any{"stdout": rawText},
},
})
}
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
if _, err := client.ReportSessionMessages(context.Background(), ReportSessionMessagesInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
AgentTargetID: "target-1",
DeviceID: "device-1",
Updates: updates,
}); err != nil {
t.Fatal(err)
}
if requestCount < 2 {
t.Fatalf("requestCount = %d, want oversized request split into multiple uploads", requestCount)
}
}
func TestReportSessionMessagesIncludesOptionalMetadata(t *testing.T) {
var got map[string]any
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if err := json.NewDecoder(r.Body).Decode(&got); err != nil {
t.Fatal(err)
}
_, _ = w.Write([]byte(`{"acceptedCount":1}`))
}))
defer server.Close()
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
_, err := client.ReportSessionMessages(context.Background(), ReportSessionMessagesInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
AgentTargetID: "target-1",
DeviceID: "device-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
Source: EventSource{
Provider: "codex",
AgentID: "session-1",
SessionOrigin: WorkspaceAgentSessionOriginRuntime,
},
Updates: []WorkspaceAgentSessionMessageUpdate{{
MessageID: "message-1",
TurnID: "turn-1",
Role: "assistant",
Kind: "text",
}},
})
if err != nil {
t.Fatal(err)
}
if got["agentTargetId"] != "target-1" || got["deviceId"] != "device-1" {
t.Fatalf("request metadata = %#v, want top-level agentTargetId/deviceId", got)
}
source, ok := got["source"].(map[string]any)
if !ok || source["deviceId"] != "device-1" {
t.Fatalf("source = %#v, want propagated deviceId", got["source"])
}
}
func TestListSessionMessagesSendsDeviceIDQueryOnlyWhenSet(t *testing.T) {
var queries []string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
queries = append(queries, r.URL.Query().Get("device_id"))
_, _ = w.Write([]byte(`{"messages":[]}`))
}))
defer server.Close()
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
if _, err := client.ListSessionMessages(context.Background(), ListSessionMessagesInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
DeviceID: "device-1",
}); err != nil {
t.Fatal(err)
}
if _, err := client.ListSessionMessages(context.Background(), ListSessionMessagesInput{
WorkspaceID: "ws-1",
AgentSessionID: "session-1",
}); err != nil {
t.Fatal(err)
}
if len(queries) != 2 || queries[0] != "device-1" || queries[1] != "" {
t.Fatalf("device_id queries = %#v, want [device-1 \"\"]", queries)
}
}
func TestListAgentsWithFilterSendsDeviceIDQuery(t *testing.T) {
var deviceID string
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
deviceID = r.URL.Query().Get("device_id")
_, _ = w.Write([]byte(`{"presences":[],"sessions":[]}`))
}))
defer server.Close()
client := NewClient(Config{BaseURL: server.URL, HTTPClient: server.Client()})
if _, err := client.ListAgentsWithFilter(context.Background(), ListAgentsInput{
WorkspaceID: "ws-1",
DeviceID: "device-1",
}); err != nil {
t.Fatal(err)
}
if deviceID != "device-1" {
t.Fatalf("device_id query = %q, want device-1", deviceID)
}
}
func TestWorkspaceAgentSessionUnmarshalsDeviceIDLeniently(t *testing.T) {
var camel ProviderActivitySessionProjection
if err := json.Unmarshal([]byte(`{"agentSessionId":"session-1","deviceId":"device-1"}`), &camel); err != nil {
t.Fatal(err)
}
if camel.DeviceID != "device-1" {
t.Fatalf("camelCase deviceId = %q, want device-1", camel.DeviceID)
}
var snake ProviderActivitySessionProjection
if err := json.Unmarshal([]byte(`{"agent_session_id":"session-1","device_id":"device-2"}`), &snake); err != nil {
t.Fatal(err)
}
if snake.DeviceID != "device-2" {
t.Fatalf("snake_case device_id = %q, want device-2", snake.DeviceID)
}
roundtrip, err := json.Marshal(ProviderActivitySessionProjection{AgentSessionID: "session-1", DeviceID: "device-1"})
if err != nil {
t.Fatal(err)
}
var decoded map[string]any
if err := json.Unmarshal(roundtrip, &decoded); err != nil {
t.Fatal(err)
}
if decoded["deviceId"] != "device-1" {
t.Fatalf("marshal output = %s, want deviceId", roundtrip)
}
}