Files
growqr-backend/scripts/qscore-raw-event-contract.test.ts

96 lines
4.1 KiB
TypeScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { canonicalGrowEventEnvelope, correlateEventWithServiceSession } from "../src/events/record-grow-event.js";
import { normalizeGrowEvent } from "../src/events/normalize.js";
import {
projectedSignalsFromQscoreResponse,
QSCORE_GROW_EVENT_DESTINATION,
QSCORE_GROW_EVENT_TOPIC,
} from "../src/events/qscore-event-outbox.js";
const input = {
id: "evt-real-1",
userId: "untrusted-body-user",
orgId: "growqr",
source: "interview-service",
type: "interview.review.completed",
category: "service" as const,
occurredAt: "2026-07-14T10:00:00.000Z",
mission: { instanceId: "mission-1", missionId: "interview-to-offer", stageId: "mock-interview" },
subject: { serviceId: "interview", externalId: "session-1" },
correlation: { sessionId: "session-1", taskId: "task-1" },
payload: {
review: {
overall_score: 78,
rubric_scores: { communication: 82, technical_accuracy: 70 },
},
},
dedupeKey: "interview:session-1:review",
};
const normalized = normalizeGrowEvent(input, { userId: "clerk-user-real" });
assert.equal(
normalized.type,
"interview.feedback.generated",
"backend-only consumers may retain staging's legacy event alias",
);
const canonical = canonicalGrowEventEnvelope(normalized, "clerk-user-real");
assert.deepEqual(canonical, {
...input,
userId: "clerk-user-real",
raw: input,
});
const sessionOnlyInput = { ...input, correlation: { sessionId: "session-1" } };
const sessionOnlyNormalized = normalizeGrowEvent(sessionOnlyInput, { userId: "clerk-user-real" });
const correlated = correlateEventWithServiceSession(sessionOnlyNormalized, "clerk-user-real", {
userId: "clerk-user-real",
missionInstanceId: "mission-1",
missionId: "interview-to-offer",
stageId: "mock-interview",
metadata: { task_id: "task-derived" },
});
const enrichedCanonical = canonicalGrowEventEnvelope(correlated, "clerk-user-real");
assert.deepEqual(enrichedCanonical.correlation, {
sessionId: "session-1",
task_id: "task-derived",
missionInstanceId: "mission-1",
missionId: "interview-to-offer",
stageId: "mock-interview",
});
assert.deepEqual(enrichedCanonical.raw, sessionOnlyInput, "raw producer correlation must remain unchanged");
assert.equal(QSCORE_GROW_EVENT_TOPIC, "qscore.grow_event.raw");
assert.equal(QSCORE_GROW_EVENT_DESTINATION, "/v1/events/ingest");
const projected = projectedSignalsFromQscoreResponse({
projected_signals: [
{ signal_id: "interview.overall_score", score: 45, present: true, evidence: { eventId: input.id } },
{ signal_id: "interview.sessions_completed", evidence: { eventId: input.id } },
],
});
assert.deepEqual(projected, [
{ signalId: "interview.overall_score", score: 45, present: true, raw: { eventId: input.id } },
{ signalId: "interview.sessions_completed", raw: { eventId: input.id } },
]);
const projectorSource = await readFile(new URL("../src/events/projectors/qscore-projector.ts", import.meta.url), "utf8");
for (const forbidden of ["clampScore", "BASELINE_SCORE", "extractResumeSignals", "forwardSignalsToQscoreService"]) {
assert.equal(projectorSource.includes(forbidden), false, `projector must not contain ${forbidden}`);
}
const serviceAgentSource = await readFile(new URL("../src/services/service-agents.ts", import.meta.url), "utf8");
assert.equal(/q_score\s*:\s*\d/.test(serviceAgentSource), false, "service agents must not inject numeric QScore data");
const homeSource = await readFile(new URL("../src/home/home-feed.ts", import.meta.url), "utf8");
for (const forbidden of ["|| 47", "score - 29", "Math.max(35", "ensureOnboardingBaselineQscore"]) {
assert.equal(homeSource.includes(forbidden), false, `Home must not contain synthetic QScore fallback ${forbidden}`);
}
const servicesSource = await readFile(new URL("../src/routes/services.ts", import.meta.url), "utf8");
assert.equal(
/app\.get\("\/qscore\/current"[\s\S]*?qscore\.review\.opened/.test(servicesSource),
false,
"polling the current QScore must remain a read and must not emit a synthetic review event",
);
console.log("qscore raw-event ownership contract: ok");