135 lines
5.3 KiB
TypeScript
135 lines
5.3 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { getCompletionEvents } from "../src/services/service-registry.js";
|
|
import { isStreakCompletionType, isTaskCompletionEvent, planSeedsForVariant, type CuratorCompletionEvent } from "../src/v1/curator/curator-store.js";
|
|
import { templateSetFor } from "../src/v1/curator/task-registry.js";
|
|
import type { CuratorIcpId } from "../src/v1/curator/icp-registry.js";
|
|
import { canonicalGrowEventType } from "../src/events/normalize.js";
|
|
|
|
const taskId = "curator-task-1";
|
|
const event = (type: string, correlatedTaskId = taskId): CuratorCompletionEvent => ({
|
|
type,
|
|
payload: {},
|
|
correlation: { task_id: correlatedTaskId },
|
|
});
|
|
|
|
const roleplayEvents = getCompletionEvents("roleplay-service");
|
|
assert.deepEqual(roleplayEvents, ["roleplay.scenario.completed", "roleplay.feedback.generated"]);
|
|
assert.equal(isTaskCompletionEvent(taskId, roleplayEvents, event("roleplay.scenario.configured")), false);
|
|
assert.equal(isTaskCompletionEvent(taskId, roleplayEvents, event("roleplay.scenario.completed")), true);
|
|
assert.equal(isTaskCompletionEvent(taskId, roleplayEvents, event("roleplay.feedback.generated")), true);
|
|
assert.equal(isTaskCompletionEvent(taskId, roleplayEvents, event("roleplay.scenario.completed", "other-task")), false);
|
|
|
|
const assertCompletionContract = (serviceId: string, accepted: string[], excluded: string[]) => {
|
|
const completionEvents = getCompletionEvents(serviceId);
|
|
assert.deepEqual(completionEvents, accepted);
|
|
for (const type of accepted) {
|
|
assert.equal(isTaskCompletionEvent(taskId, completionEvents, event(type)), true, `${serviceId} accepts ${type}`);
|
|
assert.equal(isTaskCompletionEvent(taskId, completionEvents, event(type, "other-task")), false, `${serviceId} requires task correlation for ${type}`);
|
|
}
|
|
for (const type of excluded) {
|
|
assert.equal(isTaskCompletionEvent(taskId, completionEvents, event(type)), false, `${serviceId} excludes ${type}`);
|
|
}
|
|
};
|
|
|
|
assertCompletionContract(
|
|
"courses-service",
|
|
["course.generated", "course.started", "course.progress.recorded", "course.completed"],
|
|
[],
|
|
);
|
|
|
|
assertCompletionContract(
|
|
"social-branding-service",
|
|
["brand.account.connected", "brand.post.drafted", "brand.profile.updated", "brand.post.exported", "brand.content.published"],
|
|
["social.account.connected", "social.profile.updated", "social.post.scheduled", "social.post.published"],
|
|
);
|
|
|
|
assertCompletionContract(
|
|
"resume-service",
|
|
["resume.uploaded", "resume.parsed", "resume.analysis.completed", "resume.updated", "resume.exported"],
|
|
["resume.configured"],
|
|
);
|
|
|
|
assertCompletionContract(
|
|
"matchmaking-service",
|
|
[
|
|
"matchmaking.feed.viewed",
|
|
"matchmaking.match.viewed",
|
|
"matchmaking.application.started",
|
|
"matchmaking.match.saved",
|
|
"matchmaking.matches.reviewed",
|
|
"matchmaking.match.applied",
|
|
"matchmaking.application.completed",
|
|
],
|
|
["matchmaking.matches.generated", "matchmaking.match.dismissed"],
|
|
);
|
|
|
|
assertCompletionContract(
|
|
"qscore-service",
|
|
["qscore.review.opened", "qscore.weak_driver.reviewed", "qscore.signal.projected", "qscore.updated"],
|
|
["qscore.baseline.created"],
|
|
);
|
|
|
|
for (const type of [
|
|
"resume.uploaded",
|
|
"resume.parsed",
|
|
"course.generated",
|
|
"course.started",
|
|
"course.progress.recorded",
|
|
"qscore.review.opened",
|
|
"brand.account.connected",
|
|
"brand.post.drafted",
|
|
"matchmaking.feed.viewed",
|
|
"matchmaking.match.viewed",
|
|
"matchmaking.application.started",
|
|
]) {
|
|
assert.equal(isStreakCompletionType(type), true, `${type} must qualify for the broad streak activity rule`);
|
|
}
|
|
for (const type of ["qscore.baseline.created", "assessment.started", "matchmaking.matches.generated"]) {
|
|
assert.equal(isStreakCompletionType(type), false, `${type} must remain non-completing`);
|
|
}
|
|
|
|
assert.equal(canonicalGrowEventType("interview.session_completed"), "interview.session.completed");
|
|
assert.equal(canonicalGrowEventType("roleplay.session_completed"), "roleplay.scenario.completed");
|
|
|
|
const icpIds: CuratorIcpId[] = [
|
|
"student_recent_grad",
|
|
"intern",
|
|
"fresher_early_professional",
|
|
"experienced_professional",
|
|
"freelancer",
|
|
"founder",
|
|
"enterprise",
|
|
];
|
|
|
|
for (const icpId of icpIds) {
|
|
const trialDays = planSeedsForVariant(templateSetFor(icpId), "2026-07-14", 2, "trial");
|
|
assert.equal(trialDays.length, 2, `${icpId} must expose exactly two trial days`);
|
|
for (const day of trialDays) {
|
|
assert.equal(day.plannedTasks.length, 4, `${icpId} day ${day.dayIndex} must expose four tasks`);
|
|
for (const [index, task] of day.plannedTasks.entries()) {
|
|
const completionEvents = getCompletionEvents(task.serviceId);
|
|
const syntheticTaskId = `${icpId}:day-${day.dayIndex}:task-${index + 1}`;
|
|
assert.ok(completionEvents.length > 0, `${syntheticTaskId} must have completion evidence`);
|
|
assert.equal(
|
|
isTaskCompletionEvent(syntheticTaskId, completionEvents, event(completionEvents[0]!, syntheticTaskId)),
|
|
true,
|
|
`${syntheticTaskId} must complete from its exact correlated service event`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const serviceId of [
|
|
"resume-service",
|
|
"interview-service",
|
|
"roleplay-service",
|
|
"courses-service",
|
|
"social-branding-service",
|
|
"matchmaking-service",
|
|
"qscore-service",
|
|
]) {
|
|
assert.ok(getCompletionEvents(serviceId).length > 0, `${serviceId} must define completion evidence`);
|
|
}
|
|
|
|
console.log("curator service completion contract tests passed");
|