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

242 lines
9.1 KiB
Go

package storesqlite
import (
"context"
"fmt"
"strings"
"testing"
)
func TestListWorkspaceGeneratedFileTurnsScopesAndOrdersSettledTurns(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{
"/workspace/project-a",
"/workspace/project-b",
}}))
ctx := context.Background()
const workspaceID = "workspace-generated-files"
seedGeneratedFileSession(t, ctx, store, workspaceID, "older", testTargetIDCodex, "/workspace/project-a/apps/web")
seedGeneratedFileSession(t, ctx, store, workspaceID, "newer", testTargetIDClaude, "/workspace/project-a")
seedGeneratedFileSession(t, ctx, store, workspaceID, "other-project", testTargetIDCodex, "/workspace/project-b")
recordGeneratedFileTurn(t, ctx, store, workspaceID, "older", "turn-older", 100, []any{
map[string]any{"path": "src/report.md", "change": "added"},
})
recordGeneratedFileTurn(t, ctx, store, workspaceID, "newer", "turn-newer", 200, []any{
map[string]any{"path": "report.md", "change": "deleted"},
})
recordGeneratedFileTurn(t, ctx, store, workspaceID, "other-project", "turn-other", 300, []any{
map[string]any{"path": "other.md", "change": "added"},
})
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{
WorkspaceID: workspaceID, AgentSessionID: "newer", TurnID: "turn-active",
Phase: TurnPhaseRunning, Origin: TurnOriginLegacyUnknown, OccurredAtUnixMS: 400,
}); err != nil || !accepted {
t.Fatalf("RecordTurnTransition(active) accepted=%v error=%v", accepted, err)
}
result, ok, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
WorkspaceID: workspaceID,
SectionKey: RailSectionKeyForProject("/workspace/project-a"),
})
if err != nil || !ok {
t.Fatalf("ListWorkspaceGeneratedFileTurns() ok=%v error=%v", ok, err)
}
if len(result.Turns) != 2 {
t.Fatalf("turns = %#v, want two settled project-a turns", result.Turns)
}
if result.Turns[0].TurnID != "turn-newer" || result.Turns[1].TurnID != "turn-older" {
t.Fatalf("turn order = %q, %q", result.Turns[0].TurnID, result.Turns[1].TurnID)
}
if result.Turns[0].AgentTargetID != testTargetIDClaude || len(result.Turns[0].Changes) != 1 || result.Turns[0].Changes[0].Change != "deleted" {
t.Fatalf("newer turn = %#v", result.Turns[0])
}
}
func TestListWorkspaceGeneratedFileTurnsKeepsRetractedTurnSideEffects(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{"/workspace/project"}}))
ctx := context.Background()
const workspaceID = "workspace-retracted-generated-files"
const sessionID = "session-1"
const turnID = "turn-retracted"
seedGeneratedFileSession(t, ctx, store, workspaceID, sessionID, testTargetIDCodex, "/workspace/project")
recordGeneratedFileTurn(t, ctx, store, workspaceID, sessionID, turnID, 100, []any{
map[string]any{"path": "still-modified.md", "change": "added"},
})
if _, err := store.db.ExecContext(ctx, `
UPDATE workspace_agent_turn_history
SET history_state = 'retracted', retracted_by_operation_id = 'operation-1'
WHERE workspace_id = ? AND agent_session_id = ? AND turn_id = ?
`, workspaceID, sessionID, turnID); err != nil {
t.Fatal(err)
}
result, ok, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
WorkspaceID: workspaceID,
SectionKey: RailSectionKeyForProject("/workspace/project"),
})
if err != nil || !ok {
t.Fatalf("ListWorkspaceGeneratedFileTurns() ok=%v error=%v", ok, err)
}
if len(result.Turns) != 1 ||
result.Turns[0].TurnID != turnID ||
len(result.Turns[0].Changes) != 1 ||
result.Turns[0].Changes[0].Path != "still-modified.md" {
t.Fatalf("retracted turn side effects = %#v", result.Turns)
}
}
func TestListWorkspaceGeneratedFileTurnsCapsSectionAtOneHundred(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{"/workspace/project"}}))
ctx := context.Background()
const workspaceID = "workspace-generated-file-limit"
const sessionID = "session-1"
seedGeneratedFileSession(t, ctx, store, workspaceID, sessionID, testTargetIDCodex, "/workspace/project")
for index := 0; index < sectionGeneratedFileTurnLimit+1; index++ {
recordGeneratedFileTurn(t, ctx, store, workspaceID, sessionID, fmt.Sprintf("turn-%03d", index), int64(100+index), []any{
map[string]any{"path": fmt.Sprintf("file-%03d.md", index), "change": "added"},
})
}
result, ok, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
WorkspaceID: workspaceID,
SectionKey: RailSectionKeyForProject("/workspace/project"),
})
if err != nil || !ok {
t.Fatalf("ListWorkspaceGeneratedFileTurns() ok=%v error=%v", ok, err)
}
if len(result.Turns) != sectionGeneratedFileTurnLimit || result.Turns[0].TurnID != "turn-100" || result.Turns[len(result.Turns)-1].TurnID != "turn-001" {
t.Fatalf("bounded turns = %#v", result.Turns)
}
}
func TestListWorkspaceGeneratedFileTurnsAllowsSparseQuietSections(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{paths: []string{
"/workspace/quiet",
"/workspace/noisy",
}}))
ctx := context.Background()
const workspaceID = "workspace-generated-file-sparse"
seedGeneratedFileSession(t, ctx, store, workspaceID, "quiet", testTargetIDCodex, "/workspace/quiet")
seedGeneratedFileSession(t, ctx, store, workspaceID, "noisy", testTargetIDCodex, "/workspace/noisy")
recordGeneratedFileTurn(t, ctx, store, workspaceID, "quiet", "turn-quiet", 100, []any{
map[string]any{"path": "quiet.md", "change": "added"},
})
for index := 0; index < workspaceGeneratedFileTurnCandidateLimit; index++ {
recordGeneratedFileTurn(t, ctx, store, workspaceID, "noisy", fmt.Sprintf("turn-noisy-%04d", index), int64(1000+index), nil)
}
result, ok, err := store.ListWorkspaceGeneratedFileTurns(ctx, ListWorkspaceGeneratedFileTurnsInput{
WorkspaceID: workspaceID,
SectionKey: RailSectionKeyForProject("/workspace/quiet"),
})
if err != nil || !ok {
t.Fatalf("ListWorkspaceGeneratedFileTurns() ok=%v error=%v", ok, err)
}
if len(result.Turns) != 0 {
t.Fatalf("quiet section turns = %#v, want sparse empty result outside candidate window", result.Turns)
}
}
func TestListWorkspaceGeneratedFileTurnsPlanUsesRecentSettledIndex(t *testing.T) {
t.Parallel()
store := openTestStore(t, testOptions(&staticProjectPaths{}))
rows, err := store.db.Query(`
EXPLAIN QUERY PLAN
WITH recent_workspace_turns AS MATERIALIZED (
SELECT agent_session_id, turn_id, settled_at_unix_ms
FROM workspace_agent_turns INDEXED BY idx_workspace_agent_turns_workspace_settled_recent
WHERE workspace_id = ? AND phase = 'settled' AND settled_at_unix_ms IS NOT NULL
ORDER BY settled_at_unix_ms DESC, agent_session_id DESC, turn_id DESC
LIMIT ?
),
scoped_turns AS MATERIALIZED (
SELECT turns.agent_session_id, turns.turn_id, turns.settled_at_unix_ms
FROM recent_workspace_turns turns
JOIN workspace_agent_sessions sessions
ON sessions.workspace_id = ? AND sessions.agent_session_id = turns.agent_session_id
WHERE sessions.rail_section_key = ? AND sessions.deleted_at_unix_ms = 0
ORDER BY turns.settled_at_unix_ms DESC, turns.agent_session_id DESC, turns.turn_id DESC
LIMIT ?
)
SELECT source.file_changes_json
FROM scoped_turns scoped
JOIN workspace_agent_turns source
ON source.workspace_id = ?
AND source.agent_session_id = scoped.agent_session_id
AND source.turn_id = scoped.turn_id
`, "workspace", workspaceGeneratedFileTurnCandidateLimit, "workspace", RailSectionKeyConversations, sectionGeneratedFileTurnLimit, "workspace")
if err != nil {
t.Fatalf("EXPLAIN QUERY PLAN error = %v", err)
}
defer rows.Close()
var plan strings.Builder
for rows.Next() {
var id, parent, unused int
var detail string
if err := rows.Scan(&id, &parent, &unused, &detail); err != nil {
t.Fatalf("scan query plan: %v", err)
}
plan.WriteString(detail)
plan.WriteByte('\n')
}
if !strings.Contains(plan.String(), "idx_workspace_agent_turns_workspace_settled_recent") {
t.Fatalf("query plan does not use settled index:\n%s", plan.String())
}
}
func seedGeneratedFileSession(
t *testing.T,
ctx context.Context,
store *Store,
workspaceID string,
sessionID string,
agentTargetID string,
cwd string,
) {
t.Helper()
if _, err := store.ReportSessionState(ctx, SessionStateReport{
WorkspaceID: workspaceID,
AgentSessionID: sessionID,
Origin: "runtime",
AgentTargetID: agentTargetID,
Provider: "codex",
Cwd: cwd,
Status: "active",
OccurredAtUnixMS: 10,
}); err != nil {
t.Fatalf("ReportSessionState(%s) error = %v", sessionID, err)
}
}
func recordGeneratedFileTurn(
t *testing.T,
ctx context.Context,
store *Store,
workspaceID string,
sessionID string,
turnID string,
occurredAt int64,
files []any,
) {
t.Helper()
if _, accepted, err := store.RecordTurnTransition(ctx, TurnTransition{
WorkspaceID: workspaceID,
AgentSessionID: sessionID,
TurnID: turnID,
Phase: TurnPhaseSettled,
Outcome: TurnOutcomeCompleted,
Origin: TurnOriginLegacyUnknown,
FileChanges: map[string]any{"files": files},
OccurredAtUnixMS: occurredAt,
}); err != nil || !accepted {
t.Fatalf("RecordTurnTransition(%s) accepted=%v error=%v", turnID, accepted, err)
}
}