fix(e2e): #2788 CR2 #11517 — DB-level chat seed jsonb round-trip regression test #2822

Merged
devops-engineer merged 1 commits from fix/2788-e2e-chat-residual into main 2026-06-14 04:52:58 +00:00
2 changed files with 125 additions and 0 deletions
+96
View File
@@ -6,6 +6,7 @@ import {
startHeartbeat,
cleanupWorkspace,
seedChatHistory,
queryPsql,
} from "./fixtures/chat-seed";
const PLATFORM_URL = process.env.E2E_PLATFORM_URL ?? "http://localhost:8080";
@@ -354,6 +355,101 @@ test.describe("Data Flow — Initial Prompt in Chat", () => {
});
});
const describeWithDb = process.env.E2E_DATABASE_URL
? test.describe
: test.describe.skip;
describeWithDb("Chat seed DB round-trip", () => {
let cleanup: () => Promise<void> = async () => {};
let workspaceId = "";
test.beforeAll(async () => {
const echo = await startEchoRuntime();
const ws = await seedWorkspace(echo.baseURL);
workspaceId = ws.id;
const stopHeartbeat = startHeartbeat(ws.id, ws.authToken);
// Seed tricky payloads: double quotes, backslashes, apostrophes, and a
// newline. If the JSON is mangled by shell/SQL quoting, the round-trip
// assertion below will fail instead of silently passing.
await seedChatHistory(workspaceId, [
{
role: "user",
content: 'User said "hello" and \\backslash\\ plus an apostrophe\'s test',
},
{
role: "agent",
content: 'Agent replied "ok"\nwith a newline',
},
]);
cleanup = async () => {
stopHeartbeat();
await echo.stop();
};
});
test.afterAll(async () => {
await cleanupWorkspace(workspaceId);
await cleanup();
});
test("seeded jsonb round-trips exactly through psql", async () => {
interface SeededActivityRow {
id: string;
workspace_id: string;
activity_type: string;
source_id: string | null;
method: string;
request_body: unknown;
response_body: unknown;
status: string;
duration_ms: number;
created_at: string;
}
const rows = queryPsql<
SeededActivityRow[]
>(`SELECT jsonb_agg(row_to_json(t) ORDER BY t.created_at) FROM (SELECT id, workspace_id, activity_type, source_id, method, request_body, response_body, status, duration_ms, created_at FROM activity_logs WHERE workspace_id = '${workspaceId}' ORDER BY created_at) t`)[0];
expect(rows).toHaveLength(2);
const [userRow, agentRow] = rows;
expect(userRow.activity_type).toBe("a2a_receive");
expect(userRow.source_id).toBeNull();
expect(userRow.method).toBe("message/send");
expect(userRow.request_body).toEqual({
params: {
message: {
parts: [
{
kind: "text",
text: 'User said "hello" and \\backslash\\ plus an apostrophe\'s test',
},
],
},
},
});
expect(userRow.response_body).toEqual({});
expect(agentRow.activity_type).toBe("a2a_receive");
expect(agentRow.source_id).toBeNull();
expect(agentRow.method).toBe("message/send");
expect(agentRow.request_body).toEqual({});
expect(agentRow.response_body).toEqual({
result: {
parts: [
{
kind: "text",
text: 'Agent replied "ok"\nwith a newline',
},
],
},
});
});
});
test.describe("No JS Errors", () => {
let cleanup: () => Promise<void> = async () => {};
let workspaceId = "";
+29
View File
@@ -44,11 +44,40 @@ export function runPsql(sql: string, timeoutMs = 30_000): string {
env: { ...process.env, PGPASSWORD: pass },
stdio: "pipe",
timeout: timeoutMs,
encoding: "utf8",
},
);
return out.toString();
}
/**
* Execute a read-only psql query and return each row parsed as JSON.
* The caller is responsible for making the query return exactly one JSON
* value per output line (e.g. with `row_to_json` or `jsonb_agg`).
*/
export function queryPsql<T>(sql: string, timeoutMs = 30_000): T[] {
const creds = parseDbUrl();
if (!creds) {
throw new Error("E2E_DATABASE_URL must be set for DB seeding");
}
const { user, pass, host, port, db } = creds;
const out = execFileSync(
"psql",
["-h", host, "-p", port, "-U", user, "-d", db, "-tA", "-c", sql],
{
env: { ...process.env, PGPASSWORD: pass },
stdio: "pipe",
timeout: timeoutMs,
encoding: "utf8",
},
);
return out
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0)
.map((line) => JSON.parse(line) as T);
}
export interface SeededWorkspace {
id: string;
name: string;