fix(e2e): #2788 residual — shell-safe chat seed JSON + Activity API auth #2792
@@ -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<{
|
||||
|
||||
@@ -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<void> {
|
||||
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(",");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user