209 lines
7.9 KiB
TypeScript
209 lines
7.9 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { careerTransitionReducer } from "../src/missions/career-transition/reducer.js";
|
|
import { interviewToOfferReducer } from "../src/missions/interview-to-offer/reducer.js";
|
|
import { personalBrandOpportunityReducer } from "../src/missions/personal-brand-opportunity-engine/reducer.js";
|
|
import { promotionReadinessReducer } from "../src/missions/promotion-readiness/reducer.js";
|
|
import { salaryNegotiationReducer } from "../src/missions/salary-negotiation-war-room/reducer.js";
|
|
import type { GrowActiveMission } from "../src/actors/missions/types.js";
|
|
import type { MissionReducer } from "../src/missions/reducer-types.js";
|
|
import type { MissionReducerContext } from "../src/missions/reducer-types.js";
|
|
|
|
function missionFor(missionId: GrowActiveMission["missionId"], actorType: GrowActiveMission["actorType"]): GrowActiveMission {
|
|
return {
|
|
instanceId: `mission-${missionId}-test`,
|
|
missionId,
|
|
workflowId: missionId,
|
|
title: missionId,
|
|
shortTitle: missionId,
|
|
status: "active",
|
|
progressPercent: 0,
|
|
currentStageId: "resume",
|
|
actorType,
|
|
createdAt: Date.now(),
|
|
updatedAt: Date.now(),
|
|
};
|
|
}
|
|
|
|
const mission = missionFor("interview-to-offer", "interviewToOfferMissionActor");
|
|
|
|
function ctxWithMission(activeMission: GrowActiveMission, event: Partial<MissionReducerContext["event"]> & { source: string; type: string; payload?: Record<string, unknown> }): MissionReducerContext {
|
|
return {
|
|
userId: "user_test",
|
|
activeMission,
|
|
event: {
|
|
id: `event-${activeMission.missionId}-${event.type}`,
|
|
userId: "user_test",
|
|
orgId: null,
|
|
source: event.source,
|
|
type: event.type,
|
|
category: "service",
|
|
occurredAt: new Date(),
|
|
receivedAt: new Date(),
|
|
mission: event.mission,
|
|
subject: null,
|
|
correlation: null,
|
|
payload: event.payload ?? {},
|
|
raw: {},
|
|
dedupeKey: null,
|
|
processingStatus: "pending",
|
|
processingError: null,
|
|
processedAt: null,
|
|
},
|
|
qscoreSignals: [],
|
|
insight: {
|
|
summary: "test insight",
|
|
confidence: "low",
|
|
recommendedActions: [],
|
|
missionStageHints: [],
|
|
},
|
|
};
|
|
}
|
|
|
|
function ctx(event: Partial<MissionReducerContext["event"]> & { source: string; type: string; payload?: Record<string, unknown> }): MissionReducerContext {
|
|
return ctxWithMission(mission, event);
|
|
}
|
|
|
|
const interviewFeedbackPayload = {
|
|
review: {
|
|
status: "completed",
|
|
overall_score: 72,
|
|
weak_areas: ["impact metrics", "ownership clarity"],
|
|
proof_gaps: ["no scale numbers"],
|
|
story_issues: ["STAR structure is loose"],
|
|
summary: "Good direction, but missing measurable proof.",
|
|
},
|
|
};
|
|
|
|
const roleplayFeedbackPayload = {
|
|
review: {
|
|
status: "completed",
|
|
weak_areas: ["concision", "objection handling"],
|
|
story_gaps: ["needs clearer tradeoff story"],
|
|
summary: "Good empathy, but answers need tighter story framing.",
|
|
},
|
|
};
|
|
|
|
const reducerCases: Array<{
|
|
name: string;
|
|
reducer: MissionReducer;
|
|
mission: GrowActiveMission;
|
|
}> = [
|
|
{
|
|
name: "interview to offer",
|
|
reducer: interviewToOfferReducer,
|
|
mission,
|
|
},
|
|
{
|
|
name: "career transition",
|
|
reducer: careerTransitionReducer,
|
|
mission: missionFor("career-transition", "careerTransitionMissionActor"),
|
|
},
|
|
{
|
|
name: "promotion readiness",
|
|
reducer: promotionReadinessReducer,
|
|
mission: missionFor("promotion-readiness", "promotionReadinessMissionActor"),
|
|
},
|
|
{
|
|
name: "salary negotiation",
|
|
reducer: salaryNegotiationReducer,
|
|
mission: missionFor("salary-negotiation-war-room", "salaryNegotiationWarRoomMissionActor"),
|
|
},
|
|
{
|
|
name: "personal brand",
|
|
reducer: personalBrandOpportunityReducer,
|
|
mission: missionFor("personal-brand-opportunity-engine", "personalBrandOpportunityMissionActor"),
|
|
},
|
|
];
|
|
|
|
const resumeResult = interviewToOfferReducer.reduce(ctx({
|
|
source: "resume-builder",
|
|
type: "resume.analysis.completed",
|
|
payload: {
|
|
analysis: {
|
|
summary: "Strong backend platform project.",
|
|
strengths: ["Built an event-driven backend"],
|
|
gaps: ["Add impact metrics"],
|
|
missing_keywords: ["Kafka", "AWS"],
|
|
},
|
|
},
|
|
}));
|
|
|
|
const proofPractice = resumeResult.actions.find((action) => action.payload?.passiveAction === "resume_analysis_to_interview_practice");
|
|
assert.ok(proofPractice, "resume analysis should create an interview practice passive action");
|
|
assert.equal(proofPractice?.serviceId, "interview-service");
|
|
assert.equal(proofPractice?.toolName, "interview.configure_practice");
|
|
assert.match(String(proofPractice?.payload?.prompt), /event-driven backend/i);
|
|
|
|
const interviewResult = interviewToOfferReducer.reduce(ctx({
|
|
source: "interview-service",
|
|
type: "interview.feedback.generated",
|
|
payload: interviewFeedbackPayload,
|
|
}));
|
|
|
|
const resumeUpgrade = interviewResult.actions.find((action) => action.payload?.passiveAction === "interview_feedback_to_resume_upgrade");
|
|
assert.ok(resumeUpgrade, "interview feedback should create a resume upgrade passive action");
|
|
assert.equal(resumeUpgrade?.mode, "approval_required");
|
|
assert.equal(resumeUpgrade?.serviceId, "resume-service");
|
|
assert.deepEqual(resumeUpgrade?.payload?.missingProof, ["no scale numbers"]);
|
|
assert.deepEqual(resumeUpgrade?.payload?.storyIssues, ["STAR structure is loose", "add measurable impact proof"]);
|
|
|
|
const roleplayResult = interviewToOfferReducer.reduce(ctx({
|
|
source: "roleplay-service",
|
|
type: "roleplay.feedback.generated",
|
|
payload: roleplayFeedbackPayload,
|
|
}));
|
|
|
|
const storyArtifact = roleplayResult.artifacts.find((artifact) => artifact.type === "story_bank_update");
|
|
const communicationDrill = roleplayResult.actions.find((action) => action.payload?.passiveAction === "roleplay_feedback_to_communication_drill");
|
|
assert.ok(storyArtifact, "roleplay feedback should create a story bank artifact");
|
|
assert.ok(communicationDrill, "roleplay feedback should create a communication drill passive action");
|
|
assert.equal(communicationDrill?.serviceId, "interview-service");
|
|
assert.equal(communicationDrill?.toolName, "interview.configure_practice");
|
|
assert.deepEqual(communicationDrill?.payload?.storyIssues, ["needs clearer tradeoff story", "tighten STAR story structure"]);
|
|
|
|
for (const testCase of reducerCases) {
|
|
const reducerResumeResult = testCase.reducer.reduce(ctxWithMission(testCase.mission, {
|
|
source: "resume-builder",
|
|
type: "resume.analysis.completed",
|
|
payload: {
|
|
analysis: {
|
|
summary: "Strong backend platform project.",
|
|
strengths: ["Built an event-driven backend"],
|
|
gaps: ["Add impact metrics"],
|
|
missing_keywords: ["Kafka", "AWS"],
|
|
},
|
|
},
|
|
}));
|
|
assert.ok(
|
|
reducerResumeResult.actions.some((action) => action.payload?.passiveAction === "resume_analysis_to_interview_practice"),
|
|
`${testCase.name} resume analysis should create an interview practice passive action`,
|
|
);
|
|
|
|
const reducerInterviewResult = testCase.reducer.reduce(ctxWithMission(testCase.mission, {
|
|
source: "interview-service",
|
|
type: "interview.feedback.generated",
|
|
payload: interviewFeedbackPayload,
|
|
}));
|
|
assert.ok(
|
|
reducerInterviewResult.actions.some((action) => action.payload?.passiveAction === "interview_feedback_to_resume_upgrade"),
|
|
`${testCase.name} interview feedback should create a resume upgrade passive action`,
|
|
);
|
|
|
|
const reducerRoleplayResult = testCase.reducer.reduce(ctxWithMission(testCase.mission, {
|
|
source: "roleplay-service",
|
|
type: "roleplay.feedback.generated",
|
|
payload: roleplayFeedbackPayload,
|
|
}));
|
|
assert.ok(
|
|
reducerRoleplayResult.actions.some((action) => action.payload?.passiveAction === "roleplay_feedback_to_communication_drill"),
|
|
`${testCase.name} roleplay feedback should create a communication drill passive action`,
|
|
);
|
|
assert.ok(
|
|
reducerRoleplayResult.artifacts.some((artifact) => artifact.type === "story_bank_update"),
|
|
`${testCase.name} roleplay feedback should create a story bank update artifact`,
|
|
);
|
|
}
|
|
|
|
console.log("passive-actions tests passed");
|
|
process.exit(0);
|