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

229 lines
7.2 KiB
Go

//go:build pgtest
package postgres
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/db"
)
// TestPushSessionGuardsAgainstCrossMachineCollision verifies that when two
// machines share the same session ID (from dotfile sync, directory restore, etc.),
// the second machine's push is skipped if the session is already owned by a
// different machine. This prevents the ping-pong effect where two pushers
// fight over the same row on every push cycle.
//
// Steps:
// 1. Insert a session with id="clash-001" and machine="machine-a" directly into PG.
// 2. Call pushSession with machine="machine-b" and a db.Session{ID: "clash-001", Machine: "machine-b", ...}.
// 3. Assert that the row's machine column is still "machine-a" (not overwritten).
// 4. Assert that no messages were written for the conflicting session.
func TestPushSessionGuardsAgainstCrossMachineCollision(t *testing.T) {
pgURL := testPGURL(t)
const schema = "agentsview_collision_guard_test"
pg, err := Open(pgURL, schema, true)
require.NoError(t, err, "Open")
defer pg.Close()
ctx := context.Background()
_, err = pg.Exec(`DROP SCHEMA IF EXISTS ` + schema + ` CASCADE`)
require.NoError(t, err, "drop schema")
require.NoError(t, EnsureSchema(ctx, pg, schema), "EnsureSchema")
// Local SQLite DB.
localDB, err := db.Open(filepath.Join(t.TempDir(), "local.db"))
require.NoError(t, err, "db.Open")
defer localDB.Close()
sync := &Sync{
pg: pg,
local: localDB,
machine: "machine-b",
schema: schema,
schemaDone: true,
}
const clashID = "clash-001"
// Step 1: Insert a session owned by machine-a directly into PG.
markerID, err := sync.pushMarkerID()
require.NoError(t, err, "pushMarkerID")
_, err = pg.ExecContext(ctx, `
INSERT INTO sessions (
id, machine, owner_marker, project, agent, created_at
) VALUES ($1, $2, $3, $4, $5, NOW())
`, clashID, "machine-a", "different-owner", "test-proj", "claude")
require.NoError(t, err, "insert existing session")
// Step 2: Attempt to push the same session from machine-b.
sess := db.Session{
ID: clashID,
Project: "test-proj",
Machine: "machine-b",
Agent: "claude",
MessageCount: 1,
CreatedAt: "2026-01-01T00:00:00Z",
}
require.NoError(t, localDB.UpsertSession(sess), "UpsertSession")
require.NoError(t, localDB.InsertMessages([]db.Message{{
SessionID: clashID,
Ordinal: 0,
Role: "user",
Content: "test",
ContentLength: 4,
}}), "InsertMessages")
// Execute pushSession.
tx, err := pg.BeginTx(ctx, nil)
require.NoError(t, err, "BeginTx")
err = sync.pushSession(ctx, tx, sess, markerID, nil)
require.ErrorIs(t, err, errSessionOwnershipConflict, "pushSession should return ownership conflict sentinel")
require.NoError(t, tx.Commit(), "Commit")
// Step 3: Verify the machine column is still "machine-a".
var existingMachine string
err = pg.QueryRowContext(ctx,
`SELECT machine FROM sessions WHERE id = $1`, clashID,
).Scan(&existingMachine)
require.NoError(t, err, "read back machine")
assert.Equal(t, "machine-a", existingMachine,
"machine should remain as 'machine-a', not overwritten by 'machine-b'")
// Step 4: Verify no messages were written for this session.
var messageCount int
err = pg.QueryRowContext(ctx,
`SELECT COUNT(*) FROM messages WHERE session_id = $1`, clashID,
).Scan(&messageCount)
require.NoError(t, err, "count messages")
assert.Equal(t, 0, messageCount,
"no messages should be written when session is skipped due to collision")
assert.NotEqual(t, markerID, "different-owner", "precondition: foreign owner marker differs from local marker")
}
func TestPushSessionAllowsMachineRenameForSameOwnerMarker(t *testing.T) {
pgURL := testPGURL(t)
const schema = "agentsview_collision_owner_marker_test"
pg, err := Open(pgURL, schema, true)
require.NoError(t, err, "Open")
defer pg.Close()
ctx := context.Background()
_, err = pg.Exec(`DROP SCHEMA IF EXISTS ` + schema + ` CASCADE`)
require.NoError(t, err, "drop schema")
require.NoError(t, EnsureSchema(ctx, pg, schema), "EnsureSchema")
localDB, err := db.Open(filepath.Join(t.TempDir(), "local.db"))
require.NoError(t, err, "db.Open")
defer localDB.Close()
sync := &Sync{
pg: pg,
local: localDB,
machine: "renamed-host",
schema: schema,
schemaDone: true,
}
markerID, err := sync.pushMarkerID()
require.NoError(t, err, "pushMarkerID")
const sessID = "rename-001"
_, err = pg.ExecContext(ctx, `
INSERT INTO sessions (
id, machine, owner_marker, project, agent, created_at
) VALUES ($1, $2, $3, $4, $5, NOW())
`, sessID, "old-host", markerID, "test-proj", "claude")
require.NoError(t, err, "insert existing session")
sess := db.Session{
ID: sessID,
Project: "test-proj",
Machine: "local",
Agent: "claude",
MessageCount: 1,
CreatedAt: "2026-01-01T00:00:00Z",
}
require.NoError(t, localDB.UpsertSession(sess), "UpsertSession")
tx, err := pg.BeginTx(ctx, nil)
require.NoError(t, err, "BeginTx")
require.NoError(t, sync.pushSession(ctx, tx, sess, markerID, nil), "pushSession")
require.NoError(t, tx.Commit(), "Commit")
var machine, ownerMarker string
err = pg.QueryRowContext(ctx,
`SELECT machine, owner_marker FROM sessions WHERE id = $1`, sessID,
).Scan(&machine, &ownerMarker)
require.NoError(t, err, "read back session")
assert.Equal(t, "renamed-host", machine)
assert.Equal(t, markerID, ownerMarker)
}
func TestPushSessionAdoptsLegacyLocalSentinelRow(t *testing.T) {
pgURL := testPGURL(t)
const schema = "agentsview_collision_legacy_local_test"
pg, err := Open(pgURL, schema, true)
require.NoError(t, err, "Open")
defer pg.Close()
ctx := context.Background()
_, err = pg.Exec(`DROP SCHEMA IF EXISTS ` + schema + ` CASCADE`)
require.NoError(t, err, "drop schema")
require.NoError(t, EnsureSchema(ctx, pg, schema), "EnsureSchema")
localDB, err := db.Open(filepath.Join(t.TempDir(), "local.db"))
require.NoError(t, err, "db.Open")
defer localDB.Close()
sync := &Sync{
pg: pg,
local: localDB,
machine: "host-a",
schema: schema,
schemaDone: true,
}
const sessID = "legacy-local-001"
_, err = pg.ExecContext(ctx, `
INSERT INTO sessions (
id, machine, owner_marker, project, agent, created_at
) VALUES ($1, $2, $3, $4, $5, NOW())
`, sessID, "local", "", "test-proj", "claude")
require.NoError(t, err, "insert legacy local sentinel row")
sess := db.Session{
ID: sessID,
Project: "test-proj",
Machine: "local",
Agent: "claude",
MessageCount: 1,
CreatedAt: "2026-01-01T00:00:00Z",
}
require.NoError(t, localDB.UpsertSession(sess), "UpsertSession")
tx, err := pg.BeginTx(ctx, nil)
require.NoError(t, err, "BeginTx")
markerID, err := sync.pushMarkerID()
require.NoError(t, err, "pushMarkerID")
require.NoError(t, sync.pushSession(ctx, tx, sess, markerID, nil), "pushSession")
require.NoError(t, tx.Commit(), "Commit")
var machine, ownerMarker string
err = pg.QueryRowContext(ctx,
`SELECT machine, owner_marker FROM sessions WHERE id = $1`, sessID,
).Scan(&machine, &ownerMarker)
require.NoError(t, err, "read back session")
assert.Equal(t, "host-a", machine)
assert.Equal(t, markerID, ownerMarker)
}