package store import ( "context" "errors" "path/filepath" "testing" "time" dbpkg "ai-workflow-skill/internal/db" ) func TestClaimThreadReturnsLeaseConflictAfterBusyWrite(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() dbPath := filepath.Join(t.TempDir(), "coord.db") sqlDB, err := dbpkg.Open(ctx, dbPath) if err != nil { t.Fatalf("open base db: %v", err) } defer sqlDB.Close() if err := dbpkg.ApplyMigrations(ctx, sqlDB); err != nil { t.Fatalf("apply migrations: %v", err) } baseStore := NewInboxStore(sqlDB) thread, _, err := baseStore.Send(ctx, SendInput{ FromAgent: "leader", ToAgent: "worker-a", Subject: "race claim", Summary: "race claim", }) if err != nil { t.Fatalf("seed thread: %v", err) } lockerDB, err := dbpkg.Open(ctx, dbPath) if err != nil { t.Fatalf("open locker db: %v", err) } defer lockerDB.Close() lockTx, err := lockerDB.BeginTx(ctx, nil) if err != nil { t.Fatalf("begin locker tx: %v", err) } now := nowUTC() if _, err := lockTx.ExecContext( ctx, `INSERT INTO leases ( thread_id, agent_id, lease_token, claimed_at, expires_at, released_at ) VALUES (?, ?, ?, ?, ?, NULL)`, thread.ThreadID, "worker-a", "lease_locked", formatTime(now), formatTime(now.Add(5*time.Minute)), ); err != nil { t.Fatalf("seed active lease in tx: %v", err) } if _, err := lockTx.ExecContext( ctx, `UPDATE threads SET status = ?, assigned_to = ?, latest_message_id = ?, updated_at = ? WHERE thread_id = ?`, "claimed", "worker-a", "msg_locked", formatTime(now), thread.ThreadID, ); err != nil { t.Fatalf("seed claimed thread in tx: %v", err) } commitDone := make(chan error, 1) go func() { time.Sleep(100 * time.Millisecond) commitDone <- lockTx.Commit() }() claimDB, err := dbpkg.Open(ctx, dbPath) if err != nil { t.Fatalf("open claim db: %v", err) } defer claimDB.Close() claimStore := NewInboxStore(claimDB) _, err = claimStore.ClaimThread(ctx, ClaimInput{ ThreadID: thread.ThreadID, Agent: "worker-b", LeaseSeconds: 300, }) if !errors.Is(err, ErrLeaseConflict) { t.Fatalf("expected lease conflict after busy retry, got %v", err) } if err := <-commitDone; err != nil { t.Fatalf("commit locker tx: %v", err) } }