Files
wehub-resource-sync f99010fae1
Desktop Artifacts / Desktop Build (Linux) (push) Waiting to run
Desktop Artifacts / Desktop Build (Windows) (push) Waiting to run
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Waiting to run
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Waiting to run
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

1589 lines
44 KiB
Go

//go:build pgtest
package postgres
import (
"context"
"database/sql"
"fmt"
"regexp"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/db"
"go.kenn.io/agentsview/internal/export"
)
func cleanPGSchema(t *testing.T, pgURL string) {
t.Helper()
pg, err := sql.Open("pgx", pgURL)
require.NoError(t, err, "connecting to pg")
defer pg.Close()
_, _ = pg.Exec(
"DROP SCHEMA IF EXISTS agentsview CASCADE",
)
}
func cleanNamedPGSchema(t *testing.T, pgURL, schema string) {
t.Helper()
pg, err := sql.Open("pgx", pgURL)
require.NoError(t, err, "connecting to pg")
defer pg.Close()
quoted, err := quoteIdentifier(schema)
require.NoError(t, err, "quote schema")
_, _ = pg.Exec("DROP SCHEMA IF EXISTS " + quoted + " CASCADE")
}
func TestEnsureSchemaIdempotent(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "first EnsureSchema")
require.NoError(t, ps.EnsureSchema(ctx), "second EnsureSchema")
var eventIndex int
err = ps.pg.QueryRowContext(ctx,
"SELECT event_index FROM tool_result_events LIMIT 0",
).Scan(&eventIndex)
if err != nil && err != sql.ErrNoRows {
t.Fatalf("tool_result_events schema probe: %v", err)
}
}
func TestSyncEffectiveSyncStateFallsBackToLocalDB(t *testing.T) {
local := testDB(t)
require.NoError(t, local.SetSyncState(
"last_push_at",
"2026-03-11T12:34:56.123456789Z",
))
sync := &Sync{local: local}
require.NoError(t, NormalizeLocalSyncStateTimestamps(
sync.effectiveSyncState(),
))
got, err := sync.effectiveSyncState().GetSyncState("last_push_at")
require.NoError(t, err)
assert.Equal(t, "2026-03-11T12:34:56.123Z", got)
}
func TestSyncScopedStateUsesTargetKeys(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{
SyncStateTarget: "work",
MigrateLegacySyncState: true,
},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
started := "2026-03-11T12:00:00Z"
require.NoError(t, local.UpsertSession(db.Session{
ID: "sess-scoped-001",
Project: "test-project",
Machine: "local",
Agent: "claude",
StartedAt: &started,
MessageCount: 1,
}), "upsert session")
require.NoError(t, local.InsertMessages([]db.Message{{
SessionID: "sess-scoped-001",
Ordinal: 0,
Role: "user",
Content: "hello",
}}), "insert message")
_, err = ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
scopedLastPush, err := local.GetSyncState("last_push_at:work")
require.NoError(t, err)
assert.NotEmpty(t, scopedLastPush)
legacyLastPush, err := local.GetSyncState("last_push_at")
require.NoError(t, err)
assert.Empty(t, legacyLastPush)
scopedBoundary, err := local.GetSyncState(
lastPushBoundaryStateKey + ":work",
)
require.NoError(t, err)
assert.NotEmpty(t, scopedBoundary)
scopedFingerprint, err := local.GetSyncState(
lastPushTargetFingerprintKey + ":work",
)
require.NoError(t, err)
assert.NotEmpty(t, scopedFingerprint)
status, err := ps.Status(ctx)
require.NoError(t, err, "status")
assert.Equal(t, scopedLastPush, status.LastPushAt)
}
func TestEnsureSchemaMigratesLegacySchema(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
pg, err := Open(pgURL, "agentsview", true)
require.NoError(t, err, "connecting to pg")
defer pg.Close()
ctx := context.Background()
// Simulate a 0.16.x schema: create the schema and core
// tables but omit tool_result_events.
_, err = pg.ExecContext(ctx,
"CREATE SCHEMA IF NOT EXISTS agentsview",
)
require.NoError(t, err, "creating schema")
legacyDDL := `
CREATE TABLE IF NOT EXISTS sync_metadata (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY,
machine TEXT NOT NULL,
project TEXT NOT NULL,
agent TEXT NOT NULL,
first_message TEXT,
display_name TEXT,
created_at TIMESTAMPTZ,
started_at TIMESTAMPTZ,
ended_at TIMESTAMPTZ,
deleted_at TIMESTAMPTZ,
message_count INT NOT NULL DEFAULT 0,
user_message_count INT NOT NULL DEFAULT 0,
parent_session_id TEXT,
relationship_type TEXT NOT NULL DEFAULT '',
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS messages (
session_id TEXT NOT NULL,
ordinal INT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp TIMESTAMPTZ,
has_thinking BOOLEAN NOT NULL DEFAULT FALSE,
has_tool_use BOOLEAN NOT NULL DEFAULT FALSE,
content_length INT NOT NULL DEFAULT 0,
is_system BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (session_id, ordinal),
FOREIGN KEY (session_id)
REFERENCES sessions(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS tool_calls (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
tool_name TEXT NOT NULL,
category TEXT NOT NULL,
call_index INT NOT NULL DEFAULT 0,
tool_use_id TEXT NOT NULL DEFAULT '',
input_json TEXT,
skill_name TEXT,
result_content_length INT,
result_content TEXT,
subagent_session_id TEXT,
message_ordinal INT NOT NULL,
FOREIGN KEY (session_id)
REFERENCES sessions(id) ON DELETE CASCADE
);`
_, err = pg.ExecContext(ctx, legacyDDL)
require.NoError(t, err, "creating legacy tables")
// Verify tool_result_events does not exist yet.
require.Error(t, CheckSchemaCompat(ctx, pg),
"expected CheckSchemaCompat to fail on legacy schema")
// Run EnsureSchema — should create the missing table.
require.NoError(t, EnsureSchema(ctx, pg, "agentsview"),
"EnsureSchema on legacy schema")
// Now the compat check should pass.
require.NoError(t, CheckSchemaCompat(ctx, pg),
"CheckSchemaCompat after migration")
}
// TestCheckSchemaCompatMissingSecretsRulesVersion pins the schema-compat
// probe against a regression where pgSessionCols selects a column the
// compat check doesn't probe for: a PG schema missing only
// sessions.secrets_rules_version must fail CheckSchemaCompat rather
// than passing the probe and 500-ing at runtime on the first session
// query. EnsureSchema brings the column in via migration; this test
// then drops it to simulate a legacy/read-only schema and verifies the
// probe catches the absence.
func TestCheckSchemaCompatMissingSecretsRulesVersion(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
pg, err := Open(pgURL, "agentsview", true)
require.NoError(t, err, "connecting to pg")
defer pg.Close()
ctx := context.Background()
require.NoError(t, EnsureSchema(ctx, pg, "agentsview"), "EnsureSchema")
require.NoError(t, CheckSchemaCompat(ctx, pg),
"precondition: CheckSchemaCompat should pass after EnsureSchema")
_, err = pg.ExecContext(ctx,
`ALTER TABLE sessions DROP COLUMN secrets_rules_version`,
)
require.NoError(t, err, "dropping secrets_rules_version")
require.Error(t, CheckSchemaCompat(ctx, pg),
"CheckSchemaCompat should fail when secrets_rules_version is missing")
}
func TestPushSingleSession(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
started := "2026-03-11T12:00:00Z"
firstMsg := "hello world"
sess := db.Session{
ID: "sess-001",
Project: "test-project",
Machine: "local",
Agent: "claude",
FirstMessage: &firstMsg,
StartedAt: &started,
MessageCount: 1,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
require.NoError(t, local.InsertMessages([]db.Message{
{
SessionID: "sess-001",
Ordinal: 0,
Role: "user",
Content: firstMsg,
},
}), "insert messages")
result, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
assert.Equal(t, 1, result.SessionsPushed)
assert.Equal(t, 1, result.MessagesPushed)
var pgProject, pgMachine string
err = ps.pg.QueryRowContext(ctx,
"SELECT project, machine FROM sessions WHERE id = $1",
"sess-001",
).Scan(&pgProject, &pgMachine)
require.NoError(t, err, "querying pg session")
assert.Equal(t, "test-project", pgProject)
assert.Equal(t, "test-machine", pgMachine)
var pgMsgContent string
err = ps.pg.QueryRowContext(ctx,
"SELECT content FROM messages WHERE session_id = $1 AND ordinal = 0",
"sess-001",
).Scan(&pgMsgContent)
require.NoError(t, err, "querying pg message")
assert.Equal(t, firstMsg, pgMsgContent)
}
func TestPushIdempotent(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
started := "2026-03-11T12:00:00Z"
sess := db.Session{
ID: "sess-002",
Project: "test-project",
Machine: "local",
Agent: "claude",
StartedAt: &started,
MessageCount: 0,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
result1, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "first push")
assert.Equal(t, 1, result1.SessionsPushed)
result2, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "second push")
assert.Equal(t, 0, result2.SessionsPushed)
}
func TestPushWithToolCalls(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
started := "2026-03-11T12:00:00Z"
sess := db.Session{
ID: "sess-tc-001",
Project: "test-project",
Machine: "local",
Agent: "claude",
StartedAt: &started,
MessageCount: 1,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
require.NoError(t, local.InsertMessages([]db.Message{
{
SessionID: "sess-tc-001",
Ordinal: 0,
Role: "assistant",
Content: "tool use response",
HasToolUse: true,
ToolCalls: []db.ToolCall{
{
ToolName: "Read",
Category: "Read",
ToolUseID: "toolu_001",
ResultContentLength: 42,
ResultContent: "file content here",
},
},
},
}), "insert messages")
result, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
assert.Equal(t, 1, result.MessagesPushed)
var toolName string
var resultLen int
err = ps.pg.QueryRowContext(ctx,
"SELECT tool_name, result_content_length FROM tool_calls WHERE session_id = $1",
"sess-tc-001",
).Scan(&toolName, &resultLen)
require.NoError(t, err, "querying pg tool_call")
assert.Equal(t, "Read", toolName)
assert.Equal(t, 42, resultLen)
}
func TestPushWithToolResultEvents(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
sess := db.Session{
ID: "sess-events-001",
Project: "test-project",
Machine: "local",
Agent: "codex",
MessageCount: 1,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
require.NoError(t, local.InsertMessages([]db.Message{
{
SessionID: "sess-events-001",
Ordinal: 0,
Role: "assistant",
Content: "tool use response",
HasToolUse: true,
ToolCalls: []db.ToolCall{
{
ToolName: "wait",
Category: "Task",
ToolUseID: "call_wait",
ResultEvents: []db.ToolResultEvent{
{
ToolUseID: "call_wait",
AgentID: "agent-1",
SubagentSessionID: "codex:agent-1",
Source: "wait_output",
Status: "completed",
Content: "first result",
ContentLength: len("first result"),
Timestamp: "2026-03-27T10:00:00Z",
EventIndex: 0,
},
},
},
},
},
}), "insert messages")
_, err = ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
var count int
err = ps.pg.QueryRowContext(ctx,
"SELECT COUNT(*) FROM tool_result_events WHERE session_id = $1",
"sess-events-001",
).Scan(&count)
require.NoError(t, err, "querying pg tool_result_events")
assert.Equal(t, 1, count)
}
func TestStatus(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
status, err := ps.Status(ctx)
require.NoError(t, err, "status")
assert.Equal(t, "test-machine", status.Machine)
assert.Equal(t, 0, status.PGSessions)
}
func TestStatusMissingSchema(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
status, err := ps.Status(ctx)
require.NoError(t, err, "status on missing schema")
assert.Equal(t, 0, status.PGSessions)
assert.Equal(t, 0, status.PGMessages)
assert.Equal(t, "test-machine", status.Machine)
}
func TestNewRejectsMachineLocal(t *testing.T) {
pgURL := testPGURL(t)
local := testDB(t)
_, err := New(
pgURL, "agentsview", local, "local", true,
SyncOptions{},
)
require.Error(t, err, "expected error for machine=local")
}
func TestNewRejectsEmptyMachine(t *testing.T) {
pgURL := testPGURL(t)
local := testDB(t)
_, err := New(
pgURL, "agentsview", local, "", true,
SyncOptions{},
)
require.Error(t, err, "expected error for empty machine")
}
func TestNewRejectsEmptyURL(t *testing.T) {
local := testDB(t)
_, err := New(
"", "agentsview", local, "test", true,
SyncOptions{},
)
require.Error(t, err, "expected error for empty URL")
}
func TestPushUpdatedAtFormat(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
started := "2026-03-11T12:00:00Z"
sess := db.Session{
ID: "sess-ts-001",
Project: "test-project",
Machine: "local",
Agent: "claude",
StartedAt: &started,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
_, err = ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
var updatedAt time.Time
err = ps.pg.QueryRowContext(ctx,
"SELECT updated_at FROM sessions WHERE id = $1",
"sess-ts-001",
).Scan(&updatedAt)
require.NoError(t, err, "querying updated_at")
formatted := updatedAt.UTC().Format(
"2006-01-02T15:04:05.000000Z",
)
pattern := regexp.MustCompile(
`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$`,
)
assert.True(t, pattern.MatchString(formatted),
"updated_at = %q, want ISO-8601 microsecond format", formatted)
}
func TestPushBumpsUpdatedAtOnMessageRewrite(
t *testing.T,
) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"machine-a", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
started := time.Now().UTC().Format(time.RFC3339)
sess := db.Session{
ID: "sess-bump-001",
Project: "test",
Machine: "local",
Agent: "test-agent",
StartedAt: &started,
MessageCount: 1,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
msg := db.Message{
SessionID: "sess-bump-001",
Ordinal: 0,
Role: "user",
Content: "hello",
ContentLength: 5,
}
require.NoError(t, local.ReplaceSessionMessages(
"sess-bump-001", []db.Message{msg},
), "replace messages")
_, err = ps.Push(ctx, false, nil)
require.NoError(t, err, "initial push")
var updatedAt1 time.Time
require.NoError(t, ps.pg.QueryRowContext(ctx,
"SELECT updated_at FROM sessions WHERE id = $1",
"sess-bump-001",
).Scan(&updatedAt1), "querying updated_at")
time.Sleep(50 * time.Millisecond)
result, err := ps.Push(ctx, true, nil)
require.NoError(t, err, "full push")
require.NotZero(t, result.MessagesPushed,
"expected messages to be pushed on full push")
var updatedAt2 time.Time
require.NoError(t, ps.pg.QueryRowContext(ctx,
"SELECT updated_at FROM sessions WHERE id = $1",
"sess-bump-001",
).Scan(&updatedAt2), "querying updated_at after full push")
assert.True(t, updatedAt2.After(updatedAt1),
"updated_at not bumped: before=%v, after=%v", updatedAt1, updatedAt2)
}
func TestPushFullBypassesHeuristic(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
started := "2026-03-11T12:00:00Z"
sess := db.Session{
ID: "sess-full-001",
Project: "test-project",
Machine: "local",
Agent: "claude",
StartedAt: &started,
MessageCount: 1,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
require.NoError(t, local.InsertMessages([]db.Message{
{
SessionID: "sess-full-001",
Ordinal: 0,
Role: "user",
Content: "test",
},
}), "insert messages")
_, err = ps.Push(ctx, false, nil)
require.NoError(t, err, "first push")
require.NoError(t, local.SetSyncState(
"last_push_at", "",
), "resetting watermark")
result, err := ps.Push(ctx, true, nil)
require.NoError(t, err, "full push")
assert.Equal(t, 1, result.SessionsPushed)
assert.Equal(t, 1, result.MessagesPushed)
}
func TestPushDetectsSchemaReset(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
// Push a session so the watermark advances.
started := "2026-03-11T12:00:00Z"
sess := db.Session{
ID: "sess-reset-001",
Project: "test-project",
Machine: "local",
Agent: "claude",
StartedAt: &started,
MessageCount: 1,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
require.NoError(t, local.InsertMessages([]db.Message{{
SessionID: "sess-reset-001",
Ordinal: 0,
Role: "user",
Content: "hello",
ContentLength: 5,
}}), "insert message")
r1, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "initial push")
require.Equal(t, 1, r1.SessionsPushed)
// Simulate a PG schema reset — don't manually recreate;
// let Push detect and handle it via the coherence check.
cleanPGSchema(t, pgURL)
// An incremental push should detect the mismatch
// (local watermark set, PG has 0 sessions), recreate
// the schema, and automatically force a full push.
r2, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "post-reset push")
assert.Equal(t, 1, r2.SessionsPushed,
"should auto-detect schema reset")
assert.Equal(t, 1, r2.MessagesPushed)
}
func TestPushDetectsPGTargetChange(t *testing.T) {
pgURL := testPGURL(t)
cleanNamedPGSchema(t, pgURL, "agentsview_a")
cleanNamedPGSchema(t, pgURL, "agentsview_b")
t.Cleanup(func() {
cleanNamedPGSchema(t, pgURL, "agentsview_a")
cleanNamedPGSchema(t, pgURL, "agentsview_b")
})
local := testDB(t)
ctx := context.Background()
insertSession := func(id, createdAt string) {
require.NoError(t, local.UpsertSession(db.Session{
ID: id,
Project: "target-change",
Machine: "local",
Agent: "claude",
CreatedAt: createdAt,
MessageCount: 1,
}), "upsert session %s", id)
require.NoError(t, local.InsertMessages([]db.Message{{
SessionID: id,
Ordinal: 0,
Role: "user",
Content: "hello " + id,
ContentLength: len("hello " + id),
Timestamp: createdAt,
}}), "insert message %s", id)
}
insertSession("sess-target-001", "2026-03-11T12:00:00Z")
syncA, err := New(
pgURL, "agentsview_a", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync A")
defer syncA.Close()
syncB, err := New(
pgURL, "agentsview_b", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync B")
defer syncB.Close()
r1, err := syncA.Push(ctx, false, nil)
require.NoError(t, err, "initial push to schema A")
require.Equal(t, 1, r1.SessionsPushed)
r2, err := syncB.Push(ctx, false, nil)
require.NoError(t, err, "initial push to schema B")
require.Equal(t, 1, r2.SessionsPushed)
insertSession("sess-target-002", "2026-03-11T12:10:00Z")
r3, err := syncA.Push(ctx, false, nil)
require.NoError(t, err, "incremental push back to schema A")
require.GreaterOrEqual(t, r3.SessionsPushed, 1)
r4, err := syncB.Push(ctx, false, nil)
require.NoError(t, err, "switch back to populated schema B")
require.Equal(t, 2, r4.SessionsPushed)
var count int
err = syncB.DB().QueryRowContext(ctx,
"SELECT COUNT(*) FROM sessions WHERE id = $1",
"sess-target-002",
).Scan(&count)
require.NoError(t, err, "counting repushed session")
assert.Equal(t, 1, count)
}
func TestPushDetectsPGTargetChangeAfterFilteredPush(t *testing.T) {
pgURL := testPGURL(t)
cleanNamedPGSchema(t, pgURL, "agentsview_filtered_a")
cleanNamedPGSchema(t, pgURL, "agentsview_filtered_b")
t.Cleanup(func() {
cleanNamedPGSchema(t, pgURL, "agentsview_filtered_a")
cleanNamedPGSchema(t, pgURL, "agentsview_filtered_b")
})
local := testDB(t)
ctx := context.Background()
const project = "alpha"
const createdAt = "2026-03-11T12:00:00Z"
require.NoError(t, local.UpsertSession(db.Session{
ID: "sess-filtered-target-001",
Project: project,
Machine: "local",
Agent: "claude",
CreatedAt: createdAt,
MessageCount: 1,
}), "upsert session")
require.NoError(t, local.InsertMessages([]db.Message{{
SessionID: "sess-filtered-target-001",
Ordinal: 0,
Role: "user",
Content: "hello filtered target",
ContentLength: len("hello filtered target"),
Timestamp: createdAt,
}}), "insert message")
filteredA, err := New(
pgURL, "agentsview_filtered_a", local,
"test-machine", true,
SyncOptions{Projects: []string{project}},
)
require.NoError(t, err, "creating filtered sync A")
defer filteredA.Close()
unfilteredB, err := New(
pgURL, "agentsview_filtered_b", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating unfiltered sync B")
defer unfilteredB.Close()
r1, err := filteredA.Push(ctx, false, nil)
require.NoError(t, err, "filtered push to schema A")
require.Equal(t, 1, r1.SessionsPushed)
lastPush, err := local.GetSyncState("last_push_at")
require.NoError(t, err, "reading filtered watermark")
assert.Empty(t, lastPush,
"filtered push should keep global last_push_at empty")
scopedLastPush, err := filteredA.effectiveSyncState().GetSyncState("last_push_at")
require.NoError(t, err, "reading filtered scoped watermark")
assert.NotEmpty(t, scopedLastPush,
"filtered push should advance scoped last_push_at")
boundaryState, err := filteredA.effectiveSyncState().GetSyncState(lastPushBoundaryStateKey)
require.NoError(t, err, "reading filtered boundary state")
require.NotEmpty(t, boundaryState,
"filtered push should persist boundary fingerprints")
r2, err := unfilteredB.Push(ctx, false, nil)
require.NoError(t, err, "push to schema B after filtered target change")
require.Equal(t, 1, r2.SessionsPushed)
var count int
err = unfilteredB.DB().QueryRowContext(ctx,
"SELECT COUNT(*) FROM sessions WHERE id = $1",
"sess-filtered-target-001",
).Scan(&count)
require.NoError(t, err, "counting session in schema B")
assert.Equal(t, 1, count)
}
func TestPushFullAfterSchemaDropRecreatesSchema(
t *testing.T,
) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
ctx := context.Background()
sess := db.Session{
ID: "sess-full-drop",
Project: "proj",
Machine: "test-machine",
Agent: "claude",
CreatedAt: "2026-03-11T12:00:00.000Z",
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
r1, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "initial push")
require.Equal(t, 1, r1.SessionsPushed)
// Drop the schema without clearing local state.
cleanPGSchema(t, pgURL)
// A full push should recreate the schema even though
// schemaDone is memoized from the first push.
r2, err := ps.Push(ctx, true, nil)
require.NoError(t, err, "full push after drop")
assert.Equal(t, 1, r2.SessionsPushed)
}
func TestScopedPushFullAfterSchemaDropRecreatesSchema(
t *testing.T,
) {
pgURL := testPGURL(t)
const schema = "agentsview_scoped_full_drop"
cleanNamedPGSchema(t, pgURL, schema)
t.Cleanup(func() { cleanNamedPGSchema(t, pgURL, schema) })
local := testDB(t)
ps, err := New(
pgURL, schema, local,
"test-machine", true,
SyncOptions{Projects: []string{"proj"}},
)
require.NoError(t, err, "creating sync")
ctx := context.Background()
sess := db.Session{
ID: "sess-scoped-full-drop",
Project: "proj",
Machine: "test-machine",
Agent: "claude",
CreatedAt: "2026-03-11T12:00:00.000Z",
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
r1, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "initial push")
require.Equal(t, 1, r1.SessionsPushed)
cleanNamedPGSchema(t, pgURL, schema)
r2, err := ps.Push(ctx, true, nil)
require.NoError(t, err, "scoped full push after drop")
assert.Equal(t, 1, r2.SessionsPushed)
}
func TestPushBatchesMultipleSessions(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
// Create 75 sessions to exercise two batches (50 + 25).
const totalSessions = 75
for i := range totalSessions {
id := fmt.Sprintf("batch-sess-%03d", i)
started := "2026-03-11T12:00:00Z"
sess := db.Session{
ID: id,
Project: "batch-project",
Machine: "local",
Agent: "claude",
StartedAt: &started,
MessageCount: 2,
}
require.NoError(t, local.UpsertSession(sess),
"upsert session %d", i)
require.NoError(t, local.InsertMessages([]db.Message{
{
SessionID: id,
Ordinal: 0,
Role: "user",
Content: fmt.Sprintf("msg %d", i),
ContentLength: 5,
},
{
SessionID: id,
Ordinal: 1,
Role: "assistant",
Content: fmt.Sprintf("reply %d", i),
ContentLength: 7,
},
}), "insert messages %d", i)
}
result, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
assert.Equal(t, totalSessions, result.SessionsPushed)
assert.Equal(t, totalSessions*2, result.MessagesPushed)
assert.Equal(t, 0, result.Errors)
// Verify PG state.
var pgSessions, pgMessages int
require.NoError(t, ps.pg.QueryRowContext(ctx,
"SELECT COUNT(*) FROM sessions",
).Scan(&pgSessions), "counting pg sessions")
require.NoError(t, ps.pg.QueryRowContext(ctx,
"SELECT COUNT(*) FROM messages",
).Scan(&pgMessages), "counting pg messages")
assert.Equal(t, totalSessions, pgSessions)
assert.Equal(t, totalSessions*2, pgMessages)
}
func TestPushBulkInsertManyMessages(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
// Create a session with 250 messages to exercise
// multi-row VALUES batching (100 per batch).
const msgCount = 250
started := "2026-03-11T12:00:00Z"
sess := db.Session{
ID: "bulk-msg-sess",
Project: "test-project",
Machine: "local",
Agent: "claude",
StartedAt: &started,
MessageCount: msgCount,
}
require.NoError(t, local.UpsertSession(sess), "upsert session")
msgs := make([]db.Message, msgCount)
for i := range msgs {
role := "user"
if i%2 == 1 {
role = "assistant"
}
msgs[i] = db.Message{
SessionID: "bulk-msg-sess",
Ordinal: i,
Role: role,
Content: fmt.Sprintf("message %d", i),
ContentLength: len(fmt.Sprintf("message %d", i)),
}
// Add a tool call on every 10th assistant message.
if role == "assistant" && i%10 == 1 {
msgs[i].HasToolUse = true
msgs[i].ToolCalls = []db.ToolCall{{
ToolName: "Read",
Category: "Read",
ToolUseID: fmt.Sprintf("toolu_%d", i),
ResultContentLength: 10,
ResultContent: "some result",
}}
}
}
require.NoError(t, local.InsertMessages(msgs), "insert messages")
result, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
assert.Equal(t, 1, result.SessionsPushed)
assert.Equal(t, msgCount, result.MessagesPushed)
// Verify all messages landed in PG.
var pgMsgCount int
require.NoError(t, ps.pg.QueryRowContext(ctx,
"SELECT COUNT(*) FROM messages WHERE session_id = $1",
"bulk-msg-sess",
).Scan(&pgMsgCount), "counting pg messages")
assert.Equal(t, msgCount, pgMsgCount)
// Verify tool calls landed.
var pgTCCount int
require.NoError(t, ps.pg.QueryRowContext(ctx,
"SELECT COUNT(*) FROM tool_calls WHERE session_id = $1",
"bulk-msg-sess",
).Scan(&pgTCCount), "counting pg tool_calls")
// Every 10th assistant message (ordinals 1, 11, 21, ...).
expectedTC := 0
for i := range msgCount {
if i%2 == 1 && i%10 == 1 {
expectedTC++
}
}
assert.Equal(t, expectedTC, pgTCCount)
}
func TestPushSimplePK(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
ctx := context.Background()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
var constraintDef string
err = ps.pg.QueryRowContext(ctx, `
SELECT pg_get_constraintdef(c.oid)
FROM pg_constraint c
JOIN pg_namespace n ON n.oid = c.connamespace
WHERE n.nspname = 'agentsview'
AND c.conrelid = 'agentsview.sessions'::regclass
AND c.contype = 'p'
`).Scan(&constraintDef)
require.NoError(t, err, "querying sessions PK")
assert.Equal(t, "PRIMARY KEY (id)", constraintDef)
err = ps.pg.QueryRowContext(ctx, `
SELECT pg_get_constraintdef(c.oid)
FROM pg_constraint c
JOIN pg_namespace n ON n.oid = c.connamespace
WHERE n.nspname = 'agentsview'
AND c.conrelid = 'agentsview.messages'::regclass
AND c.contype = 'p'
`).Scan(&constraintDef)
require.NoError(t, err, "querying messages PK")
assert.Equal(t, "PRIMARY KEY (session_id, ordinal)", constraintDef)
}
func TestPushFilteredByProject(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
// Seed three sessions across three projects.
for _, s := range []db.Session{
{
ID: "s-alpha", Project: "alpha",
Machine: "local", Agent: "claude",
MessageCount: 1,
},
{
ID: "s-beta", Project: "beta",
Machine: "local", Agent: "claude",
MessageCount: 1,
},
{
ID: "s-gamma", Project: "gamma",
Machine: "local", Agent: "claude",
MessageCount: 1,
},
} {
require.NoError(t, local.UpsertSession(s), "upsert %s", s.ID)
require.NoError(t, local.InsertMessages([]db.Message{
{
SessionID: s.ID, Ordinal: 0,
Role: "user", Content: "msg " + s.ID,
},
}), "insert msg %s", s.ID)
}
ctx := context.Background()
// Step 1: push with project filter = ["alpha"].
filtered, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{Projects: []string{"alpha"}},
)
require.NoError(t, err, "creating filtered sync")
defer filtered.Close()
require.NoError(t, filtered.EnsureSchema(ctx), "ensure schema")
r1, err := filtered.Push(ctx, false, nil)
require.NoError(t, err, "filtered push")
require.Equal(t, 1, r1.SessionsPushed)
// Verify only alpha is in PG.
pgSessionCount := func(project string) int {
t.Helper()
var n int
err := filtered.pg.QueryRowContext(ctx,
"SELECT COUNT(*) FROM sessions "+
"WHERE project = $1",
project,
).Scan(&n)
require.NoError(t, err, "count %s", project)
return n
}
assert.Equal(t, 1, pgSessionCount("alpha"))
assert.Equal(t, 0, pgSessionCount("beta"))
assert.Equal(t, 0, pgSessionCount("gamma"))
// Step 2: push unfiltered — beta and gamma should arrive.
unfiltered, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating unfiltered sync")
defer unfiltered.Close()
r2, err := unfiltered.Push(ctx, false, nil)
require.NoError(t, err, "unfiltered push")
require.GreaterOrEqual(t, r2.SessionsPushed, 2)
// Verify all three projects are in PG.
for _, p := range []string{"alpha", "beta", "gamma"} {
assert.Equal(t, 1, pgSessionCount(p), "project %s", p)
}
// Step 3: second filtered push is a no-op (fingerprints
// match).
r3, err := filtered.Push(ctx, false, nil)
require.NoError(t, err, "second filtered push")
assert.Equal(t, 0, r3.SessionsPushed)
}
func TestFilteredPushAfterResetDoesNotMaskUnfilteredResetRecovery(t *testing.T) {
pgURL := testPGURL(t)
const schema = "agentsview_filtered_reset_marker"
cleanNamedPGSchema(t, pgURL, schema)
t.Cleanup(func() { cleanNamedPGSchema(t, pgURL, schema) })
local := testDB(t)
ctx := context.Background()
for _, s := range []db.Session{
{
ID: "reset-alpha", Project: "alpha",
Machine: "local", Agent: "claude",
CreatedAt: "2026-03-11T12:00:00Z",
MessageCount: 1,
},
{
ID: "reset-beta", Project: "beta",
Machine: "local", Agent: "claude",
CreatedAt: "2026-03-11T12:05:00Z",
MessageCount: 1,
},
} {
require.NoError(t, local.UpsertSession(s), "upsert %s", s.ID)
require.NoError(t, local.InsertMessages([]db.Message{{
SessionID: s.ID,
Ordinal: 0,
Role: "user",
Content: "msg " + s.ID,
Timestamp: s.CreatedAt,
}}), "insert message %s", s.ID)
require.NoError(t, local.UpsertProjectIdentityObservation(
ctx, export.ProjectIdentityObservation{
SessionID: s.ID, Project: s.Project, Machine: s.Machine,
RootPath: "/repo/alpha", ObservedAt: time.Now().UTC(),
},
), "upsert identity %s", s.ID)
}
unfiltered, err := New(
pgURL, schema, local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating unfiltered sync")
defer unfiltered.Close()
r1, err := unfiltered.Push(ctx, false, nil)
require.NoError(t, err, "initial unfiltered push")
require.Equal(t, 2, r1.SessionsPushed)
cleanNamedPGSchema(t, pgURL, schema)
filtered, err := New(
pgURL, schema, local,
"test-machine", true,
SyncOptions{Projects: []string{"alpha"}},
)
require.NoError(t, err, "creating filtered sync")
defer filtered.Close()
r2, err := filtered.Push(ctx, false, nil)
require.NoError(t, err, "filtered push after reset")
require.Equal(t, 1, r2.SessionsPushed)
unfilteredAfterReset, err := New(
pgURL, schema, local,
"test-machine", true,
SyncOptions{},
)
require.NoError(t, err, "creating unfiltered sync after reset")
defer unfilteredAfterReset.Close()
_, err = unfilteredAfterReset.Push(ctx, false, nil)
require.NoError(t, err, "unfiltered push after filtered reset")
var count int
err = unfilteredAfterReset.DB().QueryRowContext(ctx,
"SELECT COUNT(*) FROM sessions WHERE project IN ('alpha', 'beta')",
).Scan(&count)
require.NoError(t, err, "counting restored sessions")
assert.Equal(t, 2, count)
}
func TestFilteredPartialPushDetectsResetWithEmptyWatermark(t *testing.T) {
pgURL := testPGURL(t)
const schema = "agentsview_filtered_partial_reset"
cleanNamedPGSchema(t, pgURL, schema)
t.Cleanup(func() { cleanNamedPGSchema(t, pgURL, schema) })
local := testDB(t)
ctx := context.Background()
for _, s := range []db.Session{
{
ID: "partial-good", Project: "alpha",
Machine: "local", Agent: "claude",
CreatedAt: "2026-03-11T12:00:00Z",
MessageCount: 1,
},
{
ID: "partial-bad", Project: "alpha",
Machine: "local", Agent: "claude",
CreatedAt: "2026-03-11T12:05:00Z",
MessageCount: 1,
},
} {
require.NoError(t, local.UpsertSession(s), "upsert %s", s.ID)
require.NoError(t, local.InsertMessages([]db.Message{{
SessionID: s.ID,
Ordinal: 0,
Role: "user",
Content: "msg " + s.ID,
Timestamp: s.CreatedAt,
}}), "insert message %s", s.ID)
}
filtered, err := New(
pgURL, schema, local,
"test-machine", true,
SyncOptions{Projects: []string{"alpha"}},
)
require.NoError(t, err, "creating filtered sync")
defer filtered.Close()
require.NoError(t, filtered.EnsureSchema(ctx), "ensure schema")
quotedSchema, err := quoteIdentifier(schema)
require.NoError(t, err, "quote schema")
_, err = filtered.DB().ExecContext(ctx, fmt.Sprintf(`
CREATE OR REPLACE FUNCTION %[1]s.fail_partial_bad()
RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NEW.id = 'partial-bad' THEN
RAISE EXCEPTION 'forced partial push failure';
END IF;
RETURN NEW;
END;
$$;
CREATE TRIGGER fail_partial_bad
BEFORE INSERT OR UPDATE ON %[1]s.sessions
FOR EACH ROW EXECUTE FUNCTION %[1]s.fail_partial_bad();
`, quotedSchema))
require.NoError(t, err, "install partial failure trigger")
r1, err := filtered.Push(ctx, false, nil)
require.NoError(t, err, "initial partial filtered push")
require.Equal(t, 1, r1.SessionsPushed)
require.Equal(t, 1, r1.Errors)
var identityCount int
require.NoError(t, filtered.DB().QueryRowContext(ctx,
"SELECT COUNT(*) FROM source_project_identity_observations",
).Scan(&identityCount))
assert.Zero(t, identityCount)
require.NoError(t, filtered.DB().QueryRowContext(ctx,
"SELECT COUNT(*) FROM source_session_project_identity_snapshots",
).Scan(&identityCount))
assert.Zero(t, identityCount)
scopedLastPush, err := filtered.effectiveSyncState().GetSyncState("last_push_at")
require.NoError(t, err, "reading scoped watermark")
require.Empty(t, scopedLastPush)
boundaryState, err := filtered.effectiveSyncState().GetSyncState(lastPushBoundaryStateKey)
require.NoError(t, err, "reading scoped boundary state")
require.NotEmpty(t, boundaryState)
cleanNamedPGSchema(t, pgURL, schema)
filteredAfterReset, err := New(
pgURL, schema, local,
"test-machine", true,
SyncOptions{Projects: []string{"alpha"}},
)
require.NoError(t, err, "creating filtered sync after reset")
defer filteredAfterReset.Close()
r2, err := filteredAfterReset.Push(ctx, false, nil)
require.NoError(t, err, "filtered push after reset")
require.Equal(t, 2, r2.SessionsPushed)
var count int
err = filteredAfterReset.DB().QueryRowContext(ctx,
"SELECT COUNT(*) FROM sessions WHERE project = 'alpha'",
).Scan(&count)
require.NoError(t, err, "counting restored filtered sessions")
assert.Equal(t, 2, count)
}
func TestPushExcludeProject(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
for _, s := range []db.Session{
{
ID: "s-a", Project: "alpha",
Machine: "local", Agent: "claude",
MessageCount: 1,
},
{
ID: "s-b", Project: "beta",
Machine: "local", Agent: "claude",
MessageCount: 1,
},
} {
require.NoError(t, local.UpsertSession(s), "upsert %s", s.ID)
require.NoError(t, local.InsertMessages([]db.Message{
{
SessionID: s.ID, Ordinal: 0,
Role: "user", Content: "msg",
},
}), "insert msg %s", s.ID)
}
ctx := context.Background()
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{ExcludeProjects: []string{"beta"}},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
r, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "push")
require.Equal(t, 1, r.SessionsPushed)
var pgProject string
err = ps.pg.QueryRowContext(ctx,
"SELECT project FROM sessions LIMIT 1",
).Scan(&pgProject)
require.NoError(t, err, "query pg")
assert.Equal(t, "alpha", pgProject)
}
func TestPushFilteredFullIsIncremental(t *testing.T) {
pgURL := testPGURL(t)
cleanPGSchema(t, pgURL)
t.Cleanup(func() { cleanPGSchema(t, pgURL) })
local := testDB(t)
require.NoError(t, local.UpsertSession(db.Session{
ID: "s1", Project: "alpha",
Machine: "local", Agent: "claude",
MessageCount: 1,
}), "upsert")
require.NoError(t, local.InsertMessages([]db.Message{
{
SessionID: "s1", Ordinal: 0,
Role: "user", Content: "hello",
},
}), "insert msg")
ctx := context.Background()
ps, err := New(
pgURL, "agentsview", local,
"test-machine", true,
SyncOptions{Projects: []string{"alpha"}},
)
require.NoError(t, err, "creating sync")
defer ps.Close()
require.NoError(t, ps.EnsureSchema(ctx), "ensure schema")
// First push with --full.
r1, err := ps.Push(ctx, true, nil)
require.NoError(t, err, "first push")
require.Equal(t, 1, r1.SessionsPushed)
// Filtered --full must not advance the global watermark.
wm, err := local.GetSyncState("last_push_at")
require.NoError(t, err, "reading watermark")
assert.Empty(t, wm, "watermark after filtered --full")
scopedWM, err := ps.effectiveSyncState().GetSyncState("last_push_at")
require.NoError(t, err, "reading scoped watermark")
assert.NotEmpty(t, scopedWM, "scoped watermark after filtered --full")
// Boundary fingerprints must have been written.
bs, err := ps.effectiveSyncState().GetSyncState(lastPushBoundaryStateKey)
require.NoError(t, err, "reading boundary state")
require.NotEmpty(t, bs, "boundary state empty after filtered --full")
// Second push (not --full) should be a no-op because
// fingerprints were persisted after the filtered --full.
r2, err := ps.Push(ctx, false, nil)
require.NoError(t, err, "second push")
assert.Equal(t, 0, r2.SessionsPushed)
}