refactor(review): address PR #186 re-review (approve-with-comments)
Approve-with-comments re-review; no blockers. All 7 actionable points (8 is a forward-looking architecture note — recommendation A, keep as-is): 1. chat-markdown.util spec: restore parity coverage of the removed client spec — tool error state (+ errorText), unknown-tool fallback (`Ran tool <name>` en / `Выполнил инструмент <name>` ru), and the circular-output stringify catch. 2. findAllByChat row cap is now testable (injectable limit) + an int-spec proves truncation on a modest volume. 3. Stability: the per-step durability updates are SERIALIZED via a promise chain (stepUpdateChain) so they commit in step order — onlyIfStreaming already closed the finalize race, this closes inter-step ordering. 4. findAllByChat keeps the NEWEST messages on truncation (order DESC + reverse, like findRecent) and logs a warning with chatId, instead of silently dropping the newest tail. 5. The LABELS parity comment already references the real path (tool-parts.tsx / toolLabelKey) — confirmed accurate. 6. Removed the redundant 'off-by-one boundary' test (strict subset of the two adjacent prepareAgentStep cases). 7. Extracted the terminal-finalize dispatch into a shared `applyFinalize`, used by BOTH the service's finalizeAssistant and its test — the test now exercises the real path, not a copy, so a production drift fails it. Verified: server build + 325 ai-chat unit + 6 integration; prettier clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -231,4 +231,40 @@ describe('AiChatMessageRepo.update + sweepStreaming [integration]', () => {
|
||||
// ...while the stale one alongside it was swept to 'aborted'.
|
||||
expect(byId.get(stale.id)!.status).toBe('aborted');
|
||||
});
|
||||
|
||||
it('findAllByChat caps the result, keeping the NEWEST messages in order (#183 review)', async () => {
|
||||
// A dedicated chat so the cap test is independent of the rows above.
|
||||
const cappedChat = (
|
||||
await createChat(db, { workspaceId, creatorId: userId })
|
||||
).id;
|
||||
const base = Date.now();
|
||||
// Three messages at strictly increasing timestamps.
|
||||
await createMessage(db, {
|
||||
workspaceId,
|
||||
chatId: cappedChat,
|
||||
content: 'm1-oldest',
|
||||
createdAt: new Date(base),
|
||||
});
|
||||
await createMessage(db, {
|
||||
workspaceId,
|
||||
chatId: cappedChat,
|
||||
content: 'm2',
|
||||
createdAt: new Date(base + 1000),
|
||||
});
|
||||
await createMessage(db, {
|
||||
workspaceId,
|
||||
chatId: cappedChat,
|
||||
content: 'm3-newest',
|
||||
createdAt: new Date(base + 2000),
|
||||
});
|
||||
|
||||
// Cap of 2 -> the OLDEST message is dropped; the newest two stay, in
|
||||
// chronological order (oldest -> newest).
|
||||
const capped = await repo.findAllByChat(cappedChat, workspaceId, 2);
|
||||
expect(capped.map((r) => r.content)).toEqual(['m2', 'm3-newest']);
|
||||
|
||||
// Without a cap (well above the row count) all three come back in order.
|
||||
const all = await repo.findAllByChat(cappedChat, workspaceId, 100);
|
||||
expect(all.map((r) => r.content)).toEqual(['m1-oldest', 'm2', 'm3-newest']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,6 +238,9 @@ export async function createMessage(
|
||||
content?: string | null;
|
||||
status?: string | null;
|
||||
metadata?: unknown;
|
||||
// Explicit timestamp so a test can control message ORDER (the default DB
|
||||
// now() can tie within a millisecond, and the v4 id is not time-ordered).
|
||||
createdAt?: Date;
|
||||
},
|
||||
): Promise<{ id: string }> {
|
||||
const id = randomUUID();
|
||||
@@ -252,6 +255,7 @@ export async function createMessage(
|
||||
content: args.content ?? null,
|
||||
status: args.status ?? null,
|
||||
metadata: (args.metadata ?? null) as any,
|
||||
...(args.createdAt ? { createdAt: args.createdAt } : {}),
|
||||
})
|
||||
.returning(['id'])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
Reference in New Issue
Block a user