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 & { source: string; type: string; payload?: Record }): 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 & { source: string; type: string; payload?: Record }): 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);