const fs = require("fs"); const path = require("path"); const vm = require("vm"); const assert = require("assert"); const rootDir = path.resolve(__dirname, "..", ".."); const loadStageHelperModule = () => { const filePath = path.join( rootDir, "components", "case", "summary", "utils", "caseStagesByAppealType.js" ); let source = fs.readFileSync(filePath, "utf8"); source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); source = source.replace(/export const\s+/g, "const "); source += ` module.exports = { getStagesForAppealType, stagesByCaseType }; `; const context = { module: { exports: {} }, exports: {}, require, getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey, getStagesForCaseTypeKey: getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const loadWrapperModule = (stageHelperModule) => { const filePath = path.join( rootDir, "lib", "domain", "case-lifecycle", "getLifecycleStagesForCase.js" ); let source = fs.readFileSync(filePath, "utf8"); source = source.replace( /import\s+\{\s*getStagesForAppealType\s*\}\s+from\s+"[^"]+";\n?/, "" ); source = source.replace( /export function\s+getLifecycleStagesForCase/, "function getLifecycleStagesForCase" ); source += "\nmodule.exports = { getLifecycleStagesForCase };\n"; const context = { module: { exports: {} }, exports: {}, require, getStagesForAppealType: stageHelperModule.getStagesForAppealType }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const tests = []; const test = (name, fn) => tests.push({ name, fn }); const normalizeForAssertion = (value) => JSON.parse(JSON.stringify(value ?? null)); const loadMapAppealTypeModule = () => { const filePath = path.join( rootDir, "lib", "domain", "case-lifecycle", "mapAppealType.js" ); let source = fs.readFileSync(filePath, "utf8"); source = source.replace(/export const\s+/g, "const "); source = source.replace( /export function\s+mapAppealType/, "function mapAppealType" ); source += ` module.exports = { caseTypeAliases, caseTypeKeyByAppealTypeId, normaliseCaseType, mapAppealType }; `; const context = { module: { exports: {} }, exports: {}, require }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const loadMapSpecialistProcessStageTypeModule = () => { const filePath = path.join( rootDir, "lib", "domain", "case-lifecycle", "mapSpecialistProcessStageType.js" ); let source = fs.readFileSync(filePath, "utf8"); source = source.replace(/export const\s+/g, "const "); source = source.replace( /export function\s+mapSpecialistProcessStageType/, "function mapSpecialistProcessStageType" ); source += ` module.exports = { specialistProcessStageTypeByCaseType, mapSpecialistProcessStageType }; `; const context = { module: { exports: {} }, exports: {}, require }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const loadGetStageCaseTypeKeyModule = () => { const filePath = path.join( rootDir, "lib", "domain", "case-lifecycle", "getStageCaseTypeKey.js" ); let source = fs.readFileSync(filePath, "utf8"); source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); source = source.replace( /export function\s+getStageCaseTypeKey/, "function getStageCaseTypeKey" ); source += ` module.exports = { getStageCaseTypeKey }; `; const context = { module: { exports: {} }, exports: {}, require, caseTypeAliases: mapAppealTypeModule.caseTypeAliases, caseTypeKeyByAppealTypeId: mapAppealTypeModule.caseTypeKeyByAppealTypeId, mapAppealType: mapAppealTypeModule.mapAppealType, normaliseCaseType: mapAppealTypeModule.normaliseCaseType, mapSpecialistProcessStageType: mapSpecialistProcessStageTypeModule.mapSpecialistProcessStageType }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const loadGetStagesForCaseTypeKeyModule = () => { const filePath = path.join( rootDir, "lib", "domain", "case-lifecycle", "getStagesForCaseTypeKey.js" ); let source = fs.readFileSync(filePath, "utf8"); source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); source = source.replace( /export function\s+getStagesForCaseTypeKey/, "function getStagesForCaseTypeKey" ); source += ` module.exports = { getStagesForCaseTypeKey }; `; const context = { module: { exports: {} }, exports: {}, require: (modulePath) => { if ( modulePath === "../../../components/case/summary/utils/caseStagesByAppealType" ) { return stageCatalogueModule; } return require(modulePath); } }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const loadStageCatalogueModule = () => { const filePath = path.join( rootDir, "components", "case", "summary", "utils", "caseStagesByAppealType.js" ); let source = fs.readFileSync(filePath, "utf8"); source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); source = source.replace(/export const\s+/g, "const "); source += "\nmodule.exports = { stagesByCaseType };\n"; const context = { module: { exports: {} }, exports: {}, require, getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey, getStagesForCaseTypeKey: () => [] }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const mapAppealTypeModule = loadMapAppealTypeModule(); const mapSpecialistProcessStageTypeModule = loadMapSpecialistProcessStageTypeModule(); const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule(); const stageCatalogueModule = loadStageCatalogueModule(); const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule(); const stageHelperModule = loadStageHelperModule(); const wrapperModule = loadWrapperModule(stageHelperModule); const { getStagesForAppealType } = stageHelperModule; const { getLifecycleStagesForCase } = wrapperModule; test("S78 early-stage progression preserves stage ordering and statuses", () => { const result = normalizeForAssertion( getStagesForAppealType(846040000, 846040014) ); assert.ok(Array.isArray(result)); assert.strictEqual(result[0].stageId, 846040014); assert.strictEqual(result[0].titleKey, "appeal-submitted"); assert.strictEqual(result[0].status, "in-progress"); assert.strictEqual(result[1].status, "not-started"); assert.strictEqual(result[result.length - 1].titleKey, "case-closed"); }); test("S78 mid-stage progression preserves completed/current/future status split", () => { const result = normalizeForAssertion( getStagesForAppealType(846040000, 846040018) ); const currentStage = result.find((stage) => stage.stageId === 846040018); assert.ok(currentStage); assert.strictEqual(result[0].status, "complete"); assert.strictEqual(currentStage.status, "in-progress"); assert.strictEqual(result[result.length - 1].status, "not-started"); }); test("DNS stage mapping remains unchanged", () => { const result = normalizeForAssertion( getStagesForAppealType(846040011, 846040006) ); assert.deepStrictEqual( result.map((stage) => stage.stageId), [ 846040001, 846040002, 846040003, 846040004, 846040005, 846040006, 846040007, 846040008, 846040009, 846040011, 846040012, 846040013 ] ); assert.strictEqual( result.find((stage) => stage.stageId === 846040006).status, "in-progress" ); }); test("Householder stage mapping remains unchanged", () => { const result = normalizeForAssertion( getStagesForAppealType(846040004, 846040021) ); assert.deepStrictEqual( result.map((stage) => stage.stageId), [846040014, 846040015, 846040016, 1, 846040021, 846040022, 846040013] ); assert.strictEqual( result.find((stage) => stage.stageId === 846040021).status, "in-progress" ); }); test("Specialist-process-sensitive ROW mapping remains unchanged for representative process IDs", () => { const writtenReps = normalizeForAssertion( getStagesForAppealType(846040015, 846040032, 846040000) ); const hearing = normalizeForAssertion( getStagesForAppealType(846040015, 846040032, 846040001) ); assert.deepStrictEqual(writtenReps, hearing); assert.deepStrictEqual( writtenReps.map((stage) => stage.stageId), [ 846040033, 846040034, 846040035, 846040036, 846040062, 846040055, 846040037, 846040022, 846040013 ] ); assert.ok( writtenReps.every((stage) => stage.status === "not-started"), "Expected representative ROW input to preserve current all-not-started behaviour" ); }); test("Closed-case fallback behaviour remains unchanged for all recognised closed statuses", () => { const closedStatuses = [1000, 5, 6, 846040013, 846040060, 846040059]; for (const closedStatus of closedStatuses) { const result = normalizeForAssertion( getStagesForAppealType(846040000, closedStatus) ); const closedStage = result.find( (stage) => stage.titleKey === "case-closed" ); assert.ok( closedStage, `Expected case-closed stage for status ${closedStatus}` ); assert.strictEqual( closedStage.status, "in-progress", `Expected case-closed to be in-progress for status ${closedStatus}` ); assert.strictEqual(result[0].status, "complete"); } }); test("Unknown stage/status behaviour remains unchanged", () => { const result = normalizeForAssertion( getStagesForAppealType(846040000, 999999999) ); assert.ok(result.every((stage) => stage.status === "not-started")); }); test("Alias behaviour remains unchanged for representative aliases", () => { const planningAlias = normalizeForAssertion( getStagesForAppealType("PLANNING_S78", 846040018) ); const planningCanonical = normalizeForAssertion( getStagesForAppealType("S78", 846040018) ); const callInAlias = normalizeForAssertion( getStagesForAppealType("CALL_IN", 846040049) ); const callInCanonical = normalizeForAssertion( getStagesForAppealType("CALL_INS", 846040049) ); assert.deepStrictEqual(planningAlias, planningCanonical); assert.deepStrictEqual(callInAlias, callInCanonical); }); test("Wrapper parity matches existing helper for representative inputs", () => { const scenarios = [ [846040000, 846040014, undefined], [846040000, 846040018, undefined], [846040011, 846040006, undefined], [846040004, 846040021, undefined], [846040015, 846040032, 846040000], [846040015, 846040032, 846040001], [846040000, 846040013, undefined], [846040000, 999999999, undefined] ]; for (const [caseType, currentStageId, specialistProcess] of scenarios) { assert.deepStrictEqual( normalizeForAssertion( getLifecycleStagesForCase( caseType, currentStageId, specialistProcess ) ), normalizeForAssertion( getStagesForAppealType( caseType, currentStageId, specialistProcess ) ) ); } }); test("Wrapper preserves returned object shape for status consumer", () => { const result = normalizeForAssertion( getLifecycleStagesForCase(846040000, 846040014) ); assert.ok(result.length > 0); assert.ok(Object.prototype.hasOwnProperty.call(result[0], "id")); assert.ok(Object.prototype.hasOwnProperty.call(result[0], "stageId")); assert.ok(Object.prototype.hasOwnProperty.call(result[0], "titleKey")); assert.ok( Object.prototype.hasOwnProperty.call(result[0], "descriptionKey") ); assert.ok(Object.prototype.hasOwnProperty.call(result[0], "status")); }); const run = async () => { let passed = 0; for (const currentTest of tests) { await currentTest.fn(); passed += 1; } console.log( `Phase 22 case-lifecycle-stage-wrapper tests passed (${passed}/${tests.length}).` ); }; module.exports = run; if (require.main === module) { run().catch((error) => { console.error(error); process.exit(1); }); }