Skip to content

Commit 1a92652

Browse files
Split chat route tests
Split the backend chat route regression suite into route-focused files and extract shared test support.
2 parents 99febc0 + e8cace3 commit 1a92652

6 files changed

Lines changed: 1430 additions & 1453 deletions

File tree

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
import { HttpError } from "../../errors";
4+
import { createChatRoutes } from "../../routes/chat";
5+
import { createChatSessionRequestedSessionIdConflictError } from "../errors";
6+
import type { RecoveredPaginatedSession } from "../runs";
7+
import {
8+
EXPLICIT_WORKSPACE_ID,
9+
LEGACY_WORKSPACE_ID,
10+
SESSION_ONE,
11+
SESSION_TWO,
12+
createExpectedChatConfig,
13+
createRoutesWithHttpErrorJson,
14+
createRequestContext,
15+
createRequestContextWithSelectedWorkspace,
16+
createSnapshot,
17+
} from "./chat-routes-test-support";
18+
19+
test("DELETE /chat is no longer routed", async () => {
20+
const app = createChatRoutes({
21+
allowedOrigins: [],
22+
loadRequestContextFromRequestFn: async () => ({
23+
requestAuthInputs: {} as never,
24+
requestContext: createRequestContext(),
25+
}),
26+
});
27+
28+
const response = await app.request("http://localhost/chat", {
29+
method: "DELETE",
30+
});
31+
32+
assert.equal(response.status, 404);
33+
});
34+
35+
36+
test("GET /chat prefers an explicit workspaceId query param over the legacy selected-workspace fallback", async () => {
37+
const requestedWorkspaceIds: string[] = [];
38+
const app = createChatRoutes({
39+
allowedOrigins: [],
40+
loadRequestContextFromRequestFn: async () => ({
41+
requestAuthInputs: {} as never,
42+
requestContext: createRequestContextWithSelectedWorkspace(LEGACY_WORKSPACE_ID),
43+
}),
44+
getRecoveredChatSessionSnapshotFn: async (_userId, workspaceId) => {
45+
requestedWorkspaceIds.push(workspaceId);
46+
return createSnapshot([]);
47+
},
48+
});
49+
50+
const response = await app.request(
51+
`http://localhost/chat?sessionId=${SESSION_ONE}&workspaceId=${EXPLICIT_WORKSPACE_ID}`,
52+
);
53+
54+
assert.equal(response.status, 200);
55+
assert.deepEqual(requestedWorkspaceIds, [EXPLICIT_WORKSPACE_ID]);
56+
});
57+
58+
59+
test("GET /chat preserves the legacy selected-workspace fallback when workspaceId is omitted", async () => {
60+
let requestedWorkspaceId: string | null = null;
61+
const app = createChatRoutes({
62+
allowedOrigins: [],
63+
loadRequestContextFromRequestFn: async () => ({
64+
requestAuthInputs: {} as never,
65+
requestContext: createRequestContextWithSelectedWorkspace(LEGACY_WORKSPACE_ID),
66+
}),
67+
getRecoveredChatSessionSnapshotFn: async (_userId, workspaceId) => {
68+
requestedWorkspaceId = workspaceId;
69+
return createSnapshot([]);
70+
},
71+
});
72+
73+
const response = await app.request(`http://localhost/chat?sessionId=${SESSION_ONE}`);
74+
75+
assert.equal(response.status, 200);
76+
assert.equal(requestedWorkspaceId, LEGACY_WORKSPACE_ID);
77+
});
78+
79+
80+
test("GET /chat returns assistant item ids in snapshot history and strips attachment payloads", async () => {
81+
const app = createChatRoutes({
82+
allowedOrigins: [],
83+
loadRequestContextFromRequestFn: async () => ({
84+
requestAuthInputs: {} as never,
85+
requestContext: createRequestContext(),
86+
}),
87+
resolveLiveCursorFn: async () => null,
88+
getRecoveredChatSessionSnapshotFn: async () => createSnapshot([
89+
{
90+
role: "user",
91+
content: [{ type: "text", text: "hello" }],
92+
timestamp: 1,
93+
isError: false,
94+
isStopped: false,
95+
cursor: "1",
96+
itemId: null,
97+
},
98+
{
99+
role: "assistant",
100+
content: [
101+
{ type: "text", text: "done" },
102+
{ type: "image", mediaType: "image/png", base64Data: "abc123" },
103+
],
104+
timestamp: 2,
105+
isError: false,
106+
isStopped: false,
107+
cursor: "2",
108+
itemId: "assistant-item-1",
109+
},
110+
]),
111+
});
112+
113+
const response = await app.request(`http://localhost/chat?sessionId=${SESSION_ONE}`);
114+
115+
assert.equal(response.status, 200);
116+
assert.deepEqual(await response.json(), {
117+
sessionId: SESSION_ONE,
118+
conversationScopeId: SESSION_ONE,
119+
conversation: {
120+
updatedAt: 1,
121+
mainContentInvalidationVersion: 0,
122+
messages: [
123+
{
124+
role: "user",
125+
content: [{ type: "text", text: "hello" }],
126+
timestamp: 1,
127+
isError: false,
128+
isStopped: false,
129+
cursor: "1",
130+
itemId: null,
131+
},
132+
{
133+
role: "assistant",
134+
content: [
135+
{ type: "text", text: "done" },
136+
{ type: "image", mediaType: "image/png", base64Data: "" },
137+
],
138+
timestamp: 2,
139+
isError: false,
140+
isStopped: false,
141+
cursor: "2",
142+
itemId: "assistant-item-1",
143+
},
144+
],
145+
},
146+
composerSuggestions: [],
147+
chatConfig: createExpectedChatConfig(),
148+
activeRun: null,
149+
});
150+
});
151+
152+
153+
test("GET /chat returns a stable conflict when the requested session id is owned by another scope", async () => {
154+
const routes = createChatRoutes({
155+
allowedOrigins: [],
156+
loadRequestContextFromRequestFn: async () => ({
157+
requestAuthInputs: {} as never,
158+
requestContext: createRequestContext(),
159+
}),
160+
getRecoveredChatSessionSnapshotFn: async () => {
161+
throw createChatSessionRequestedSessionIdConflictError(SESSION_TWO);
162+
},
163+
});
164+
const app = createRoutesWithHttpErrorJson();
165+
app.route("/", routes);
166+
167+
const response = await app.request(`http://localhost/chat?sessionId=${SESSION_TWO}`);
168+
169+
assert.equal(response.status, 409);
170+
assert.deepEqual(await response.json(), {
171+
error: "Requested chat session id is already in use.",
172+
requestId: null,
173+
code: "CHAT_SESSION_ID_CONFLICT",
174+
});
175+
});
176+
177+
178+
test("GET /chat stops before store access when the selected workspace is no longer accessible", async () => {
179+
let snapshotRequested = false;
180+
const routes = createChatRoutes({
181+
allowedOrigins: [],
182+
loadRequestContextFromRequestFn: async () => ({
183+
requestAuthInputs: {} as never,
184+
requestContext: createRequestContext(),
185+
}),
186+
resolveAccessibleChatWorkspaceIdFn: async () => {
187+
throw new HttpError(404, "Workspace not found", "WORKSPACE_NOT_FOUND");
188+
},
189+
getRecoveredChatSessionSnapshotFn: async () => {
190+
snapshotRequested = true;
191+
return createSnapshot([]);
192+
},
193+
});
194+
const app = createRoutesWithHttpErrorJson();
195+
app.route("/", routes);
196+
197+
const response = await app.request(`http://localhost/chat?sessionId=${SESSION_ONE}`);
198+
199+
assert.equal(snapshotRequested, false);
200+
assert.equal(response.status, 404);
201+
assert.deepEqual(await response.json(), {
202+
error: "Workspace not found",
203+
requestId: null,
204+
code: "WORKSPACE_NOT_FOUND",
205+
});
206+
});
207+
208+
209+
test("GET /chat preserves card content parts in snapshot history", async () => {
210+
const app = createChatRoutes({
211+
allowedOrigins: [],
212+
loadRequestContextFromRequestFn: async () => ({
213+
requestAuthInputs: {} as never,
214+
requestContext: createRequestContext(),
215+
}),
216+
resolveLiveCursorFn: async () => null,
217+
getRecoveredChatSessionSnapshotFn: async () => createSnapshot([
218+
{
219+
role: "user",
220+
content: [{
221+
type: "card",
222+
cardId: "card-1",
223+
frontText: "What is Rust?",
224+
backText: "A systems programming language.",
225+
tags: ["lang", "systems"],
226+
effortLevel: "medium",
227+
}],
228+
timestamp: 1,
229+
isError: false,
230+
isStopped: false,
231+
cursor: "1",
232+
itemId: null,
233+
},
234+
]),
235+
});
236+
237+
const response = await app.request(`http://localhost/chat?sessionId=${SESSION_ONE}`);
238+
239+
assert.equal(response.status, 200);
240+
assert.deepEqual(await response.json(), {
241+
sessionId: SESSION_ONE,
242+
conversationScopeId: SESSION_ONE,
243+
conversation: {
244+
updatedAt: 1,
245+
mainContentInvalidationVersion: 0,
246+
messages: [
247+
{
248+
role: "user",
249+
content: [{
250+
type: "card",
251+
cardId: "card-1",
252+
frontText: "What is Rust?",
253+
backText: "A systems programming language.",
254+
tags: ["lang", "systems"],
255+
effortLevel: "medium",
256+
}],
257+
timestamp: 1,
258+
isError: false,
259+
isStopped: false,
260+
cursor: "1",
261+
itemId: null,
262+
},
263+
],
264+
},
265+
composerSuggestions: [],
266+
chatConfig: createExpectedChatConfig(),
267+
activeRun: null,
268+
});
269+
});
270+
271+
272+
test("GET /chat paginated history returns assistant item ids and sanitized content", async () => {
273+
const paginatedSession: RecoveredPaginatedSession = {
274+
snapshot: createSnapshot([]),
275+
page: {
276+
newestCursor: "8",
277+
oldestCursor: "7",
278+
hasOlder: true,
279+
messages: [
280+
{
281+
sessionId: SESSION_ONE,
282+
itemId: "user-item-1",
283+
itemOrder: 7,
284+
role: "user",
285+
content: [{ type: "text", text: "hello" }],
286+
state: "completed",
287+
isError: false,
288+
isStopped: false,
289+
timestamp: 1,
290+
updatedAt: 1,
291+
},
292+
{
293+
sessionId: SESSION_ONE,
294+
itemId: "assistant-item-1",
295+
itemOrder: 8,
296+
role: "assistant",
297+
content: [{ type: "file", mediaType: "text/plain", base64Data: "abc123", fileName: "notes.txt" }],
298+
state: "completed",
299+
isError: false,
300+
isStopped: false,
301+
timestamp: 2,
302+
updatedAt: 2,
303+
},
304+
],
305+
},
306+
};
307+
308+
const app = createChatRoutes({
309+
allowedOrigins: [],
310+
loadRequestContextFromRequestFn: async () => ({
311+
requestAuthInputs: {} as never,
312+
requestContext: createRequestContext(),
313+
}),
314+
getRecoveredPaginatedSessionFn: async () => paginatedSession,
315+
});
316+
317+
const response = await app.request(`http://localhost/chat?sessionId=${SESSION_ONE}&limit=2`);
318+
319+
assert.equal(response.status, 200);
320+
assert.deepEqual(await response.json(), {
321+
sessionId: SESSION_ONE,
322+
conversationScopeId: SESSION_ONE,
323+
conversation: {
324+
updatedAt: 1,
325+
mainContentInvalidationVersion: 0,
326+
hasOlder: true,
327+
oldestCursor: "7",
328+
messages: [
329+
{
330+
role: "user",
331+
content: [{ type: "text", text: "hello" }],
332+
timestamp: 1,
333+
isError: false,
334+
isStopped: false,
335+
cursor: "7",
336+
itemId: null,
337+
},
338+
{
339+
role: "assistant",
340+
content: [{ type: "file", mediaType: "text/plain", base64Data: "", fileName: "notes.txt" }],
341+
timestamp: 2,
342+
isError: false,
343+
isStopped: false,
344+
cursor: "8",
345+
itemId: "assistant-item-1",
346+
},
347+
],
348+
},
349+
composerSuggestions: [],
350+
chatConfig: createExpectedChatConfig(),
351+
activeRun: null,
352+
});
353+
});
354+

0 commit comments

Comments
 (0)