From a3206741506e9ff96d49400a1d5b09aa7f9df8d7 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 14 Jun 2026 00:14:12 +0000 Subject: [PATCH] =?UTF-8?q?fix(e2e):=20#2788=20residual=20=E2=80=94=20shel?= =?UTF-8?q?l-safe=20chat=20seed=20JSON=20+=20Activity=20API=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - canvas/e2e/fixtures/chat-seed.ts: seedChatHistory now uses psql dollar-quoted literals for JSON request/response bodies so messages containing quotes or backslashes no longer produce invalid jsonb input. - canvas/e2e/chat-separation.spec.ts: Activity API source-filter tests now send the workspace auth token, fixing the 401 exposed by the newly-live chat-separation spec. Co-Authored-By: Claude --- canvas/e2e/chat-separation.spec.ts | 4 ++++ canvas/e2e/fixtures/chat-seed.ts | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/canvas/e2e/chat-separation.spec.ts b/canvas/e2e/chat-separation.spec.ts index 81d7279a2..f5ea831ac 100644 --- a/canvas/e2e/chat-separation.spec.ts +++ b/canvas/e2e/chat-separation.spec.ts @@ -191,6 +191,7 @@ test.describe("Activity API Source Filter", () => { test("source=canvas returns only canvas-initiated entries", async ({ request }) => { const res = await request.get( `${API}/workspaces/${workspaceId}/activity?source=canvas`, + { headers: { Authorization: `Bearer ${authToken}` } }, ); expect(res.ok()).toBeTruthy(); const entries = (await res.json()) as Array<{ source_id: unknown }>; @@ -205,6 +206,7 @@ test.describe("Activity API Source Filter", () => { test("source=agent returns only agent-initiated entries", async ({ request }) => { const res = await request.get( `${API}/workspaces/${workspaceId}/activity?source=agent`, + { headers: { Authorization: `Bearer ${authToken}` } }, ); expect(res.ok()).toBeTruthy(); const entries = (await res.json()) as Array<{ source_id: unknown }>; @@ -219,6 +221,7 @@ test.describe("Activity API Source Filter", () => { test("source=invalid returns 400", async ({ request }) => { const res = await request.get( `${API}/workspaces/${workspaceId}/activity?source=bogus`, + { headers: { Authorization: `Bearer ${authToken}` } }, ); expect(res.status()).toBe(400); }); @@ -226,6 +229,7 @@ test.describe("Activity API Source Filter", () => { test("source+type filters combine correctly", async ({ request }) => { const res = await request.get( `${API}/workspaces/${workspaceId}/activity?type=a2a_receive&source=canvas`, + { headers: { Authorization: `Bearer ${authToken}` } }, ); expect(res.ok()).toBeTruthy(); const entries = (await res.json()) as Array<{ diff --git a/canvas/e2e/fixtures/chat-seed.ts b/canvas/e2e/fixtures/chat-seed.ts index 7e7637fac..12869a17b 100644 --- a/canvas/e2e/fixtures/chat-seed.ts +++ b/canvas/e2e/fixtures/chat-seed.ts @@ -178,6 +178,10 @@ export function startHeartbeat( * a2a_receive rows with source_id NULL (canvas-origin) so the * /chat-history hydrator picks them up. Each message becomes its own row * so arbitrary user/agent sequences can be seeded. + * + * The JSON payloads are passed through psql as dollar-quoted literals so + * message text containing quotes, backslashes, or other special characters + * is preserved exactly and cannot break the SQL string escaping. */ export async function seedChatHistory( workspaceId: string, @@ -185,8 +189,6 @@ export async function seedChatHistory( ): Promise { if (!process.env.E2E_DATABASE_URL) return; - const escape = (s: string) => s.replace(/'/g, "''").replace(/\\/g, "\\\\"); - const rows = messages .map((msg, i) => { const offsetSec = messages.length - i; @@ -208,7 +210,14 @@ export async function seedChatHistory( }, }) : "{}"; - return `('${randomUUID()}', '${workspaceId}', 'a2a_receive', NULL, NULL, 'message/send', NULL, '${escape(requestBody)}'::jsonb, '${escape(responseBody)}'::jsonb, 0, 'ok', NOW() - INTERVAL '${offsetSec} seconds')`; + + // Use a per-row random dollar-quoting tag so the message content + // cannot accidentally close the literal. + const tag = `J${randomUUID().replace(/[^A-Za-z0-9]/g, "")}`; + const reqLit = `$${tag}$${requestBody}$${tag}$`; + const respLit = `$${tag}$${responseBody}$${tag}$`; + + return `('${randomUUID()}', '${workspaceId}', 'a2a_receive', NULL, NULL, 'message/send', NULL, ${reqLit}::jsonb, ${respLit}::jsonb, 0, 'ok', NOW() - INTERVAL '${offsetSec} seconds')`; }) .join(","); -- 2.52.0