Files
PaperClipAI/server/src/__tests__/live-events-toast.test.ts
T
Devin FoleyandPaperclip 3f36947e0c perf(realtime): batch task and run authorization per event (#17579)
Use one fresh authorization batch for each live event task and run binding. Preserve current membership, grant, company, and payload disclosure checks.

Build, typecheck, 70 affected local tests, and the complete required CI matrix passed. Greptile scored the exact head 5/5 at medium impact with no unresolved findings. The separate full local suite remains in progress.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 02:43:17 -07:00

201 lines
11 KiB
TypeScript

import { createServer } from "node:http";
import { once } from "node:events";
import { getTableName, type SQL } from "drizzle-orm";
import { PgDialect } from "drizzle-orm/pg-core";
import { activityToastIdentity } from "../realtime/activity-toast-identity.js";
import { WebSocket } from "ws";
import { describe, expect, it, vi } from "vitest";
import { setupLiveEventsWebSocketServer } from "../realtime/live-events-ws.js";
import { publishLiveEvent } from "../services/live-events.js";
import { taskToastEligible } from "../realtime/task-toast-eligibility.js";
vi.mock("../realtime/task-toast-eligibility.js", () => ({ taskToastEligible: vi.fn(async () => false) }));
vi.mock("../services/authorization.js", () => ({
authorizationService: () => ({ decide: async ({ resource }: { resource: { issueId: string } }) => ({ allowed: resource.issueId !== "hidden" }) }),
}));
vi.mock("../services/heartbeat-run-privacy.js", () => ({
getHeartbeatRunReadDecisions: async (_db: unknown, _access: unknown, _actor: unknown, runs: { issueId?: string }[]) =>
runs.map(run => run.issueId !== "hidden"),
}));
vi.mock("../services/case-access.js", () => ({
canActorReadCase: async (_db: unknown, _actor: unknown, record: { id: string }) => record.id !== "hidden-case",
}));
function database(onPredicate: (kind: "where" | "join", condition: SQL) => void = () => {}) {
return { select: (fields: Record<string, unknown>) => {
let table = "";
const query = {
from: (value: Parameters<typeof getTableName>[0]) => { table = getTableName(value); return query; },
innerJoin: (_table: unknown, condition: SQL) => { onPredicate("join", condition); return query; },
where: (condition: SQL) => { onPredicate("where", condition); return query; },
limit: async () => {
if (table === "issues") return [{ id: "task" }];
if (table === "company_memberships") return fields.name ? [{ name: "Alex Example", image: "/alex.png" }] : [{ id: "membership" }];
if (table === "agents") return [{ name: "Coder", appearance: { schemaVersion: 1, characterVersion: "cap-v1", paletteId: "bubblegum-sky" } }];
return [];
},
};
return query;
} };
}
describe("authenticated activity notifications", () => {
it("preserves only an authorized handoff task identity for empty case-panel refresh", async () => {
const server = createServer();
setupLiveEventsWebSocketServer(server, database() as never, { deploymentMode: "authenticated",
resolveCloudActor: async () => ({ userId: "viewer", companyIds: ["company"] }) });
server.listen(0, "127.0.0.1");
await once(server, "listening");
const socket = new WebSocket(`ws://127.0.0.1:${(server.address() as { port: number }).port}/api/companies/company/events/ws`);
try {
await once(socket, "open");
const messages: Array<{ payload: Record<string, unknown> }> = [];
socket.on("message", data => messages.push(JSON.parse(data.toString())));
const activity = (entityId: string, issueId: string, action = "case.handoff_applied") => publishLiveEvent({
companyId: "company", type: "activity.logged", payload: {
action, entityType: "case", entityId, issueId,
details: { instructions: "private handoff instructions", phase: "verification" },
},
});
activity("hidden-case", "visible");
activity("visible-case", "hidden");
const first = once(socket, "message");
activity("visible-case", "visible");
await first;
expect(messages).toHaveLength(1);
expect(messages[0].payload).toEqual({ action: "case.handoff_applied", entityType: "case", entityId: "visible-case", issueId: "visible", actorType: null, actorId: null });
const second = once(socket, "message");
activity("visible-case", "visible", "case.updated");
await second;
expect(messages[1].payload).toEqual({ action: "case.updated", entityType: "case", entityId: "visible-case", actorType: null, actorId: null });
} finally {
socket.terminate();
await new Promise<void>(resolve => server.close(() => resolve()));
}
});
it("uses the persisted local-board user identity for local task eligibility", async () => {
vi.mocked(taskToastEligible).mockImplementation(async (_db, _event, userId) => userId === "local-board");
const server = createServer();
setupLiveEventsWebSocketServer(server, database() as never, { deploymentMode: "local_trusted" });
server.listen(0, "127.0.0.1");
await once(server, "listening");
const socket = new WebSocket(`ws://127.0.0.1:${(server.address() as { port: number }).port}/api/companies/company/events/ws`);
try {
await once(socket, "open");
const delivery = once(socket, "message");
publishLiveEvent({ companyId: "company", type: "activity.logged", payload: {
action: "issue.updated", entityType: "issue", entityId: "visible", actorType: "agent", actorId: "agent",
} });
const [data] = await delivery;
expect(JSON.parse(data.toString()).toastEligible).toBe(true);
expect(taskToastEligible).toHaveBeenLastCalledWith(expect.anything(), expect.anything(), "local-board");
} finally {
socket.terminate();
await new Promise<void>((resolve) => server.close(() => resolve()));
vi.mocked(taskToastEligible).mockReset().mockResolvedValue(false);
}
});
it("projects eligibility per viewer and keeps authorized updates on lookup failure", async () => {
vi.mocked(taskToastEligible).mockImplementation(async (_db, event, userId) => {
if (event.payload.entityId === "lookup-failure") throw new Error("Relevance unavailable");
return userId === "alice";
});
const server = createServer();
setupLiveEventsWebSocketServer(server, database() as never, {
deploymentMode: "authenticated",
resolveCloudActor: async (req) => ({ userId: String(req.headers["x-test-viewer"]), companyIds: ["company"] }),
});
server.listen(0, "127.0.0.1");
await once(server, "listening");
const url = `ws://127.0.0.1:${(server.address() as { port: number }).port}/api/companies/company/events/ws`;
const alice = new WebSocket(url, { headers: { "x-test-viewer": "alice" } });
const bob = new WebSocket(url, { headers: { "x-test-viewer": "bob" } });
try {
await Promise.all([once(alice, "open"), once(bob, "open")]);
for (const entityId of ["visible", "lookup-failure"]) {
const deliveries = Promise.all([once(alice, "message"), once(bob, "message")]);
const original = publishLiveEvent({ companyId: "company", type: "activity.logged", payload: {
action: "issue.comment_added", entityType: "issue", entityId, actorType: "agent", actorId: "agent",
} });
const [a, b] = (await deliveries).map(([data]) => JSON.parse(data.toString()));
expect(a.toastEligible).toBe(entityId === "visible");
expect(b.toastEligible).toBe(false);
expect(a.payload.entityId).toBe(entityId);
expect(b.payload.entityId).toBe(entityId);
expect(original).not.toHaveProperty("toastEligible");
}
} finally {
alice.terminate(); bob.terminate();
await new Promise<void>((resolve) => server.close(() => resolve()));
vi.mocked(taskToastEligible).mockReset().mockResolvedValue(false);
}
});
it("keeps actor identity after authorization and excludes hidden tasks and private details", async () => {
const server = createServer();
setupLiveEventsWebSocketServer(server, database() as never, { deploymentMode: "authenticated",
resolveCloudActor: async () => ({ userId: "viewer", companyIds: ["company"] }) });
server.listen(0, "127.0.0.1");
await once(server, "listening");
const socket = new WebSocket(`ws://127.0.0.1:${(server.address() as { port: number }).port}/api/companies/company/events/ws`);
try {
await once(socket, "open");
const messages: Array<{ payload: Record<string, unknown> }> = [];
socket.on("message", (data) => messages.push(JSON.parse(data.toString())));
const activity = (entityId: string, actorType = "user") => publishLiveEvent({ companyId: "company", type: "activity.logged", payload: {
action: "issue.created", entityType: "issue", entityId, actorType, actorId: actorType === "user" ? "viewer" : "agent",
details: { bodySnippet: "private comment", secret: "not for the socket" },
} });
activity("hidden");
const first = once(socket, "message");
activity("visible");
await first;
expect(messages).toHaveLength(1);
expect(messages[0].payload).toEqual({ action: "issue.created", entityType: "issue", entityId: "visible", actorType: "user", actorId: "viewer", actorName: "Alex Example", actorImage: "/alex.png" });
const second = once(socket, "message");
activity("visible", "agent");
await second;
expect(messages[1].payload).toMatchObject({ actorName: "Coder", actorType: "agent", actorId: "agent", actorAppearance: { paletteId: "bubblegum-sky" } });
expect(messages[1].payload).not.toHaveProperty("details");
} finally {
socket.terminate();
await new Promise<void>((resolve) => server.close(() => resolve()));
}
});
});
describe("activity profile query boundaries", () => {
it("requires the exact company, human actor, and active membership before reading a person profile", async () => {
const predicates: { kind: string; sql: string; params: unknown[] }[] = [];
const db = database((kind, condition) => {
const { sql, params } = new PgDialect().sqlToQuery(condition);
predicates.push({ kind, sql, params });
});
await activityToastIdentity(db as never, "company-A", {
action: "issue.created", actorType: "user", actorId: "person-A",
});
expect(predicates).toEqual([
{ kind: "join", sql: '"user"."id" = "company_memberships"."principal_id"', params: [] },
{ kind: "where", sql: '("company_memberships"."company_id" = $1 and "company_memberships"."principal_type" = $2 and "company_memberships"."principal_id" = $3 and "company_memberships"."status" = $4)',
params: ["company-A", "user", "person-A", "active"] },
]);
});
it("requires both the exact company and agent ID before reading an agent profile", async () => {
const predicates: { kind: string; sql: string; params: unknown[] }[] = [];
const db = database((kind, condition) => {
const { sql, params } = new PgDialect().sqlToQuery(condition);
predicates.push({ kind, sql, params });
});
await activityToastIdentity(db as never, "company-A", {
action: "issue.created", actorType: "agent", actorId: "agent-A",
});
expect(predicates).toEqual([
{ kind: "where", sql: '("agents"."company_id" = $1 and "agents"."id" = $2)',
params: ["company-A", "agent-A"] },
]);
});
});