Skip to content

Commit 398a298

Browse files
committed
test(webapp): cover the batch position allocator with concurrent turns
The concurrency test only exercised the single-message append, which allocates its position inside one statement. Replacing the batch allocator's atomic `next_message_position` bump with a read-then-write left every test passing — so the invariant that concurrent writers get disjoint ranges was not actually covered on the path a turn takes. Four concurrent three-message batches now assert twelve distinct positions and that each batch's own messages stayed contiguous and in order. Against the read-then-write version this fails on `chat_messages_chat_position_key`, which is the constraint doing the work rather than the application code.
1 parent 81e8349 commit 398a298

1 file changed

Lines changed: 31 additions & 0 deletions

File tree

apps/webapp/test/dashboardAgentTranscriptStore.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,37 @@ describe("invariant 2: concurrent different messages get distinct positions", ()
174174
30_000
175175
);
176176

177+
postgresTest(
178+
"concurrent batches reserve ranges that don't overlap",
179+
async ({ prisma, postgresContainer }) => {
180+
const chatId = "chat_concurrent_batches";
181+
await boot(prisma, postgresContainer.getConnectionUri(), chatId);
182+
183+
// Four turns writing three messages each, all at once. The ranges have to be
184+
// disjoint: if two batches read the same allocator value they collide on position.
185+
const batches = Array.from({ length: 4 }, (_, batch) =>
186+
Array.from({ length: 3 }, (_, index) => textMessage(`b${batch}m${index}`))
187+
);
188+
await Promise.all(batches.map((messages) => persistMessages(agentDb, { chatId, messages })));
189+
190+
const stored = await rows(prisma, chatId);
191+
expect(stored).toHaveLength(12);
192+
expect(new Set(stored.map((row) => row.position)).size).toBe(12);
193+
// And each batch's own three messages stayed together and in order.
194+
for (const [batch, messages] of batches.entries()) {
195+
const positions = messages.map(
196+
(message) => stored.find((row) => row.message_id === message.id)!.position
197+
);
198+
expect(positions, `batch ${batch}`).toEqual([
199+
positions[0]!,
200+
positions[0]! + 1,
201+
positions[0]! + 2,
202+
]);
203+
}
204+
},
205+
30_000
206+
);
207+
177208
postgresTest(
178209
"the database is what forbids two messages sharing a position",
179210
async ({ prisma, postgresContainer }) => {

0 commit comments

Comments
 (0)