Files
growqr-backend/scripts/service-session-projection-order.test.ts

123 lines
3.7 KiB
TypeScript

import assert from "node:assert/strict";
import type { GrowEventRow } from "../src/db/schema.js";
import { recordGatewayEvent } from "../src/routes/services.js";
for (const service of [
{
source: "interview-service",
type: "interview.session.configured",
serviceId: "interview",
},
{
source: "roleplay-service",
type: "roleplay.scenario.configured",
serviceId: "roleplay",
},
] as const) {
const event = {
id: `event-${service.serviceId}`,
userId: "user-1",
orgId: null,
source: service.source,
type: service.type,
category: "service",
occurredAt: new Date("2026-07-14T00:00:00.000Z"),
receivedAt: new Date("2026-07-14T00:00:00.000Z"),
mission: null,
subject: { serviceId: service.serviceId, externalId: "session-1" },
correlation: { sessionId: "session-1" },
payload: {},
raw: null,
dedupeKey: `dedupe-${service.serviceId}`,
processingStatus: "pending",
processingError: null,
processedAt: null,
} as GrowEventRow;
const order: string[] = [];
const returned = await recordGatewayEvent(
{
userId: "user-1",
source: service.source,
type: service.type,
payload: { session_id: "session-1" },
},
{
recordLegacyEvent: async () => {
order.push("legacy");
},
recordGrowEvent: async () => {
order.push("record");
return event;
},
applyServiceSessionProjection: async (projectedEvent) => {
assert.strictEqual(projectedEvent, event);
order.push("project");
return null;
},
routeGrowEventToUserActor: async (routedEvent) => {
assert.strictEqual(routedEvent, event);
order.push("route");
},
},
);
assert.strictEqual(returned, event);
assert.deepEqual(order, ["legacy", "record", "project", "route"], `${service.serviceId} must project ownership before actor routing`);
}
const configureDedupeKeys: string[] = [];
for (const sessionId of ["streak-session-1", "streak-session-2"]) {
await recordGatewayEvent(
{
userId: "user-1",
source: "interview-service",
type: "interview.session.configured",
payload: {
request: { curatorTaskId: "curator-task-1", taskId: "curator-task-1" },
result: { session_id: sessionId },
},
correlation: {
sessionId,
curatorTaskId: "curator-task-1",
taskId: "curator-task-1",
},
},
{
recordLegacyEvent: async () => undefined,
recordGrowEvent: async (input) => {
configureDedupeKeys.push(input.dedupeKey ?? "");
return {
id: `event-${sessionId}`,
userId: "user-1",
orgId: null,
source: "interview-service",
type: "interview.session.configured",
category: "service",
occurredAt: new Date("2026-07-14T00:00:00.000Z"),
receivedAt: new Date("2026-07-14T00:00:00.000Z"),
mission: null,
subject: { serviceId: "interview", externalId: sessionId },
correlation: { sessionId, curatorTaskId: "curator-task-1" },
payload: {},
raw: null,
dedupeKey: input.dedupeKey ?? null,
processingStatus: "pending",
processingError: null,
processedAt: null,
} as GrowEventRow;
},
applyServiceSessionProjection: async () => null,
routeGrowEventToUserActor: async () => undefined,
},
);
}
assert.notEqual(
configureDedupeKeys[0],
configureDedupeKeys[1],
"reconfiguring the same curator streak task must record ownership for each generated interview session",
);
console.log("interview and roleplay service session projection ordering tests passed");