Merged PR 2374: addeding domain layer extrraction
# Summary This PR introduces a **Case Lifecycle Domain Boundary** to centralize lifecycle decision logic and reduce coupling within the appeals application. The work is **behaviour-preserving** and introduces no intentional changes to business rules, CRM integrations, translations, dashboards, API routes, or user-facing functionality. ## What was added New lifecycle boundary: ```text lib/domain/case-lifecycle/ ``` Key responsibilities extracted: - Specialist process normalization - Appeal type mapping - Specialist process stage override mapping - Stage case-type resolution - Stage catalogue lookup - Closed-case status recognition - Lifecycle stage index resolution - Lifecycle stage status assignment ## Behaviour preserved Characterization tests were added before each extraction to preserve: - Appeal type mapping and aliases - Specialist process handling - Lifecycle stage progression - Closed-case handling - Status assignment (`complete`, `in-progress`, `not-started`) - Existing ROW behaviour - Existing `statuscode` lifecycle semantics Closed-case recognition remains unchanged for: ```text 1000 5 6 846040013 846040059 846040060 ``` ## Documentation Added: ```text lib/domain/case-lifecycle/README.md ``` Documenting: - Boundary ownership - Non-goals - Lifecycle invariants - Known architectural constraints - Future extraction roadmap ## Testing Added lifecycle characterization coverage for: - Stage wrapper behaviour - Specialist process normalization - Appeal type mapping - Specialist process stage mapping - Stage case-type resolution - Stage catalogue lookup - Progress behaviour - Closed-case status handling - Stage index resolution - Stage status assignment ## Validation - Lifecycle characterization tests passed - `npm run lint` passed with no errors ## Out of Scope No changes to: - Stage catalogue ownership - Representation eligibility - Dashboard calculations - CRM/OData queries - API routes - Redux state - EN/CY translations - Event visibility logic ## Risk **Low risk** The refactor was delivered through small, characterization-first slices with no functional changes intended. Related work items: #23527
This commit is contained in:
@@ -4,7 +4,7 @@ import { connect } from "react-redux";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import { formatDates } from "../utils";
|
||||
import { useState } from "react";
|
||||
import { getStagesForAppealType } from "./summary/utils/caseStagesByAppealType";
|
||||
import { getLifecycleStagesForCase } from "../../lib/domain/case-lifecycle";
|
||||
|
||||
const statusClassMap = {
|
||||
complete: "govuk-tag govuk-!-margin-top-2",
|
||||
@@ -29,7 +29,7 @@ const StatusDetails = (props) => {
|
||||
|
||||
console.log(props.currentView?.caseReference?.statuscode);
|
||||
|
||||
const stages = getStagesForAppealType(
|
||||
const stages = getLifecycleStagesForCase(
|
||||
props.currentView?.caseReference?.appealType,
|
||||
props.currentView?.caseReference?.statuscode,
|
||||
specialistProcess
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import { getStageCaseTypeKey } from "../../../../lib/domain/case-lifecycle/getStageCaseTypeKey";
|
||||
import { getStagesForCaseTypeKey } from "../../../../lib/domain/case-lifecycle/getStagesForCaseTypeKey";
|
||||
import { getLifecycleStageIndex } from "../../../../lib/domain/case-lifecycle/getLifecycleStageIndex";
|
||||
import { getLifecycleStageStatus } from "../../../../lib/domain/case-lifecycle/getLifecycleStageStatus";
|
||||
|
||||
const slugify = (value) =>
|
||||
value
|
||||
.toString()
|
||||
@@ -771,106 +776,10 @@ export const stagesByCaseType = {
|
||||
])
|
||||
};
|
||||
|
||||
export const caseTypeAliases = {
|
||||
// Planning S78 group
|
||||
S78: "PLANNING_S78",
|
||||
PLANNING_78: "PLANNING_S78",
|
||||
PLANNING_S78: "PLANNING_S78",
|
||||
CONDITIONS_73_79: "CONDITIONS_73_79",
|
||||
LBCAC: "LBCAC",
|
||||
LDCS: "LDCS",
|
||||
PLANNING_OBLIGATIONS_S106: "PLANNING_OBLIGATIONS_S106",
|
||||
PRIOR_NOTIFICATION: "PRIOR_NOTIFICATION",
|
||||
|
||||
// Other types
|
||||
HAS: "HAS",
|
||||
CALL_INS: "CALL_INS",
|
||||
CALL_IN: "CALL_INS",
|
||||
ENFORCEMENT: "ENFORCEMENT",
|
||||
ENFORCEMENT_LISTED_BUILDING: "ENFORCEMENT_LISTED_BUILDING",
|
||||
MAINTENANCE_OF_LAND: "MAINTENANCE_OF_LAND",
|
||||
RIGHTS_OF_WAY_SCHEDULE_14: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
RIGHTS_OF_WAY_ORDERS: "RIGHTS_OF_WAY_ORDERS",
|
||||
REQUESTS_FOR_DIRECTION: "REQUESTS_FOR_DIRECTION",
|
||||
ADVERTS: "ADVERTS",
|
||||
HEDGEROW_TPO: "HEDGEROW_TPO",
|
||||
COMMON_LAND: "COMMON_LAND",
|
||||
COMPULSORY_PURCHASE_ORDERS: "COMPULSORY_PURCHASE_ORDERS",
|
||||
ELECTRICITY_ACT: "ELECTRICITY_ACT",
|
||||
HARBOUR_REVISION_ORDER: "HARBOUR_REVISION_ORDER",
|
||||
TRANSPORT_WORKS: "TRANSPORT_WORKS",
|
||||
WAYLEAVE: "WAYLEAVE",
|
||||
NON_VALIDATION: "NON_VALIDATION",
|
||||
DNS: "DNS",
|
||||
SIP: "SIP"
|
||||
};
|
||||
|
||||
const normaliseCaseType = (caseType) =>
|
||||
caseType
|
||||
?.toString()
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replace(/&/g, "AND")
|
||||
.replace(/[^A-Z0-9]+/g, "_")
|
||||
.replace(/^_|_$/g, "");
|
||||
|
||||
const specialistProcessKeyById = {
|
||||
// TODO: replace these with the real CRM option-set values
|
||||
846040000: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040100: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040001: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040101: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040002: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040102: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040003: "REQUESTS_FOR_DIRECTION",
|
||||
846040103: "REQUESTS_FOR_DIRECTION"
|
||||
};
|
||||
|
||||
const appealTypeSpecialistProcessStageMap = {
|
||||
846040015: specialistProcessKeyById
|
||||
};
|
||||
|
||||
const resolveStages = (caseTypeOrAppealTypeId, specialistProcess) => {
|
||||
const appealTypeId = Number(caseTypeOrAppealTypeId);
|
||||
const key = getStageCaseTypeKey(caseTypeOrAppealTypeId, specialistProcess);
|
||||
|
||||
const specialistProcessKey =
|
||||
appealTypeSpecialistProcessStageMap[appealTypeId]?.[specialistProcess];
|
||||
|
||||
const appealTypeKey =
|
||||
specialistProcessKey || caseTypeKeyByAppealTypeId[appealTypeId];
|
||||
|
||||
const normalised = normaliseCaseType(caseTypeOrAppealTypeId);
|
||||
|
||||
const key = appealTypeKey || caseTypeAliases[normalised] || normalised;
|
||||
|
||||
const entry = stagesByCaseType[key];
|
||||
|
||||
if (!entry) return [];
|
||||
|
||||
if (typeof entry === "string") {
|
||||
return stagesByCaseType[entry] || [];
|
||||
}
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
||||
const caseClosedStatusIds = [1000, 5, 6, 846040013, 846040060, 846040059];
|
||||
|
||||
const isCaseClosedStatus = (stageId) =>
|
||||
caseClosedStatusIds.includes(Number(stageId));
|
||||
|
||||
const getStageIndex = (stages, currentStageNumber) => {
|
||||
const exactIndex = stages.findIndex(
|
||||
(stage) => Number(stage.stageId) === currentStageNumber
|
||||
);
|
||||
|
||||
if (exactIndex !== -1) return exactIndex;
|
||||
|
||||
if (isCaseClosedStatus(currentStageNumber)) {
|
||||
return stages.findIndex((stage) => stage.titleKey === "case-closed");
|
||||
}
|
||||
|
||||
return -1;
|
||||
return getStagesForCaseTypeKey(key);
|
||||
};
|
||||
|
||||
export const getStagesForAppealType = (
|
||||
@@ -879,46 +788,10 @@ export const getStagesForAppealType = (
|
||||
specialistProcess
|
||||
) => {
|
||||
const stages = resolveStages(caseType, specialistProcess);
|
||||
const currentStageNumber = Number(currentStageId);
|
||||
|
||||
const currentIndex = getStageIndex(stages, currentStageNumber);
|
||||
const currentIndex = getLifecycleStageIndex(stages, currentStageId);
|
||||
|
||||
return stages.map((stage, index) => ({
|
||||
...stage,
|
||||
status:
|
||||
currentIndex === -1
|
||||
? "not-started"
|
||||
: index < currentIndex
|
||||
? "complete"
|
||||
: index === currentIndex
|
||||
? "in-progress"
|
||||
: "not-started"
|
||||
status: getLifecycleStageStatus(index, currentIndex)
|
||||
}));
|
||||
};
|
||||
|
||||
export const caseTypeKeyByAppealTypeId = {
|
||||
846040000: "PLANNING_S78",
|
||||
846040001: "CONDITIONS_73_79",
|
||||
846040002: "SIP",
|
||||
846040003: "PLANNING_OBLIGATIONS_S106",
|
||||
846040004: "HAS",
|
||||
846040005: "ENFORCEMENT",
|
||||
846040006: "ENFORCEMENT_LISTED_BUILDING",
|
||||
846040007: "MAINTENANCE_OF_LAND",
|
||||
846040008: "LDCS",
|
||||
846040009: "PLANNING_S78",
|
||||
846040010: "CALL_INS",
|
||||
846040011: "DNS",
|
||||
846040012: "ELECTRICITY_ACT",
|
||||
846040013: "TRANSPORT_WORKS",
|
||||
846040014: "HARBOUR_REVISION_ORDER",
|
||||
846040015: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040016: "COMMON_LAND",
|
||||
846040017: "HEDGEROW_TPO",
|
||||
846040018: "ADVERTS",
|
||||
846040019: "COMPULSORY_PURCHASE_ORDERS",
|
||||
846040020: "PLANNING_S78",
|
||||
846040021: "WAYLEAVE",
|
||||
846040024: "NON_VALIDATION",
|
||||
846040025: "LBCAC"
|
||||
};
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
# Case Lifecycle Domain Boundary
|
||||
|
||||
## Purpose
|
||||
|
||||
This directory contains a narrow **case lifecycle decision boundary**, not a full domain model.
|
||||
|
||||
Its current purpose is to centralise **behaviour-preserving interpretation logic** that was previously embedded in UI-adjacent helpers, while keeping live behaviour unchanged during refactor.
|
||||
|
||||
This boundary should be treated as an incremental extraction seam for read-only lifecycle interpretation, not as a place to redesign lifecycle behaviour.
|
||||
|
||||
## Current ownership
|
||||
|
||||
### `getLifecycleStagesForCase(...)`
|
||||
|
||||
- thin wrapper around the existing stage/progress helper
|
||||
- preserves existing output shape for current consumers
|
||||
- does not define new lifecycle semantics
|
||||
|
||||
### `normalizeSpecialistProcess(...)`
|
||||
|
||||
- raw CRM field fallback only
|
||||
- preserves current canonical and misspelled field fallback behaviour
|
||||
- does not validate or reinterpret CRM values
|
||||
|
||||
### `mapAppealType(...)`
|
||||
|
||||
- current appeal-type-to-case-type mapping
|
||||
- current alias handling
|
||||
- current unknown input behaviour
|
||||
|
||||
### `mapSpecialistProcessStageType(...)`
|
||||
|
||||
- specialist-process stage override mapping
|
||||
- preserves current placeholder-like CRM specialist process values
|
||||
- preserves current override target keys and fallback behaviour
|
||||
|
||||
### `getStageCaseTypeKey(...)`
|
||||
|
||||
- current stage case-type resolution decision
|
||||
- combines:
|
||||
- appeal type mapping
|
||||
- specialist-process stage override mapping
|
||||
- alias normalization
|
||||
- current fallback behaviour
|
||||
|
||||
### `getStagesForCaseTypeKey(...)`
|
||||
|
||||
- current stage catalogue lookup only
|
||||
- preserves:
|
||||
- direct key lookup
|
||||
- string indirection resolution
|
||||
- fallback to `[]`
|
||||
- does not own stage catalogue contents
|
||||
|
||||
## Stage catalogue ownership and lookup
|
||||
|
||||
Stage catalogue arrays still live in:
|
||||
|
||||
- `components/case/summary/utils/caseStagesByAppealType.js`
|
||||
|
||||
The domain boundary currently owns **lookup behaviour only**:
|
||||
|
||||
- direct key lookup
|
||||
- string indirection resolution
|
||||
- fallback to `[]`
|
||||
|
||||
`getStagesForCaseTypeKey(...)` must not be treated as stage catalogue ownership yet.
|
||||
|
||||
Lazy access in `getStagesForCaseTypeKey(...)` is intentional:
|
||||
|
||||
- it avoids circular dependency issues while stage arrays remain in the legacy helper
|
||||
- the domain helper must not eagerly import catalogue contents while ownership remains outside the domain boundary
|
||||
|
||||
Any future catalogue migration must be a separate, explicitly scoped slice.
|
||||
|
||||
Until catalogue ownership moves:
|
||||
|
||||
- stage array contents remain the source of truth in the legacy helper
|
||||
- the domain lookup helper must preserve reference identity
|
||||
- the domain lookup helper must preserve current fallback behaviour
|
||||
|
||||
## Explicit non-goals
|
||||
|
||||
This boundary does **not** currently own:
|
||||
|
||||
- stage catalogue arrays
|
||||
- progress/status calculation
|
||||
- closed-case handling
|
||||
- representation eligibility
|
||||
- dashboard layout or calculations
|
||||
- translations / EN-CY labels
|
||||
- CRM/OData query construction
|
||||
- API routes
|
||||
- Redux state shape
|
||||
- upload, submit, finalisation, or notification logic
|
||||
|
||||
## Behaviour-preservation invariants
|
||||
|
||||
Future slices must preserve:
|
||||
|
||||
- current stage IDs
|
||||
- translation keys
|
||||
- alias behaviour
|
||||
- unknown input behaviour
|
||||
- current fallback semantics
|
||||
- current specialist-process override semantics
|
||||
- status-vs-stage ambiguity
|
||||
- current closed-case behaviour
|
||||
|
||||
## Known oddities to preserve
|
||||
|
||||
1. `statuscode` is currently passed into stage/progress logic.
|
||||
- Do not rename or reinterpret this without a separate behaviour-change decision.
|
||||
|
||||
2. Specialist-process overrides are intentionally narrow.
|
||||
- They currently apply only through the existing appeal-type-based resolution path.
|
||||
- Do not broaden this behaviour during refactor.
|
||||
|
||||
3. Some specialist process values appear placeholder-like.
|
||||
- Do not “correct” them without CRM/product confirmation.
|
||||
|
||||
4. Some ROW inputs currently produce non-intuitive stage output.
|
||||
- Preserve characterized behaviour unless explicitly changing business rules.
|
||||
|
||||
5. The test harnesses are VM-based and import-sensitive.
|
||||
- Future extractions must update harness injections carefully and avoid broad rewrites.
|
||||
|
||||
## Testing expectations
|
||||
|
||||
Future slices should run:
|
||||
|
||||
```bash
|
||||
node tests/phase22/case-lifecycle-stage-case-type-resolution.test.cjs
|
||||
node tests/phase22/case-lifecycle-specialist-process-stage-mapping.test.cjs
|
||||
node tests/phase22/case-lifecycle-appeal-type-mapping.test.cjs
|
||||
node tests/phase22/case-lifecycle-specialist-process-normalization.test.cjs
|
||||
node tests/phase22/case-lifecycle-stage-wrapper.test.cjs
|
||||
node tests/phase22/representation-build-reps-arr-rules.test.cjs
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## Future extraction order
|
||||
|
||||
Recommended future order:
|
||||
|
||||
1. stage catalogue lookup helper
|
||||
2. stage catalogue ownership only after enough characterization coverage
|
||||
3. progress/status calculation extraction
|
||||
4. additional read-only consumers
|
||||
5. representation eligibility only as a separate policy slice after characterization
|
||||
@@ -0,0 +1,17 @@
|
||||
import { isClosedCaseStatus } from "./isClosedCaseStatus";
|
||||
|
||||
export function getLifecycleStageIndex(stages, currentStageId) {
|
||||
const currentStageNumber = Number(currentStageId);
|
||||
|
||||
const exactIndex = stages.findIndex(
|
||||
(stage) => Number(stage.stageId) === currentStageNumber
|
||||
);
|
||||
|
||||
if (exactIndex !== -1) return exactIndex;
|
||||
|
||||
if (isClosedCaseStatus(currentStageNumber)) {
|
||||
return stages.findIndex((stage) => stage.titleKey === "case-closed");
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export function getLifecycleStageStatus(index, currentIndex) {
|
||||
return currentIndex === -1
|
||||
? "not-started"
|
||||
: index < currentIndex
|
||||
? "complete"
|
||||
: index === currentIndex
|
||||
? "in-progress"
|
||||
: "not-started";
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { getStagesForAppealType } from "../../../components/case/summary/utils/caseStagesByAppealType";
|
||||
|
||||
export function getLifecycleStagesForCase(
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
) {
|
||||
return getStagesForAppealType(caseType, currentStageId, specialistProcess);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
caseTypeAliases,
|
||||
caseTypeKeyByAppealTypeId,
|
||||
mapAppealType,
|
||||
normaliseCaseType
|
||||
} from "./mapAppealType";
|
||||
import { mapSpecialistProcessStageType } from "./mapSpecialistProcessStageType";
|
||||
|
||||
export function getStageCaseTypeKey(caseTypeOrAppealTypeId, specialistProcess) {
|
||||
const appealTypeId = Number(caseTypeOrAppealTypeId);
|
||||
const mappedCaseType = mapAppealType(caseTypeOrAppealTypeId);
|
||||
|
||||
const specialistProcessKey =
|
||||
caseTypeKeyByAppealTypeId[appealTypeId] === mappedCaseType
|
||||
? mapSpecialistProcessStageType(mappedCaseType, specialistProcess)
|
||||
: undefined;
|
||||
|
||||
const normalised = normaliseCaseType(caseTypeOrAppealTypeId);
|
||||
|
||||
return (
|
||||
specialistProcessKey ||
|
||||
mappedCaseType ||
|
||||
caseTypeAliases[normalised] ||
|
||||
normalised
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
const getStagesByCaseType = () =>
|
||||
require("../../../components/case/summary/utils/caseStagesByAppealType")
|
||||
.stagesByCaseType;
|
||||
|
||||
export function getStagesForCaseTypeKey(stageCaseTypeKey) {
|
||||
const stagesByCaseType = getStagesByCaseType();
|
||||
const entry = stagesByCaseType[stageCaseTypeKey];
|
||||
|
||||
if (!entry) return [];
|
||||
|
||||
if (typeof entry === "string") {
|
||||
return stagesByCaseType[entry] || [];
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export { getLifecycleStagesForCase } from "./getLifecycleStagesForCase";
|
||||
export { isClosedCaseStatus } from "./isClosedCaseStatus";
|
||||
export { getLifecycleStageIndex } from "./getLifecycleStageIndex";
|
||||
export { getLifecycleStageStatus } from "./getLifecycleStageStatus";
|
||||
export { getStageCaseTypeKey } from "./getStageCaseTypeKey";
|
||||
export { getStagesForCaseTypeKey } from "./getStagesForCaseTypeKey";
|
||||
export { mapAppealType } from "./mapAppealType";
|
||||
export { mapSpecialistProcessStageType } from "./mapSpecialistProcessStageType";
|
||||
export { normalizeSpecialistProcess } from "./normalizeSpecialistProcess";
|
||||
@@ -0,0 +1,5 @@
|
||||
const closedCaseStatusIds = [1000, 5, 6, 846040013, 846040060, 846040059];
|
||||
|
||||
export function isClosedCaseStatus(statusOrStageId) {
|
||||
return closedCaseStatusIds.includes(Number(statusOrStageId));
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
export const caseTypeAliases = {
|
||||
// Planning S78 group
|
||||
S78: "PLANNING_S78",
|
||||
PLANNING_78: "PLANNING_S78",
|
||||
PLANNING_S78: "PLANNING_S78",
|
||||
CONDITIONS_73_79: "CONDITIONS_73_79",
|
||||
LBCAC: "LBCAC",
|
||||
LDCS: "LDCS",
|
||||
PLANNING_OBLIGATIONS_S106: "PLANNING_OBLIGATIONS_S106",
|
||||
PRIOR_NOTIFICATION: "PRIOR_NOTIFICATION",
|
||||
|
||||
// Other types
|
||||
HAS: "HAS",
|
||||
CALL_INS: "CALL_INS",
|
||||
CALL_IN: "CALL_INS",
|
||||
ENFORCEMENT: "ENFORCEMENT",
|
||||
ENFORCEMENT_LISTED_BUILDING: "ENFORCEMENT_LISTED_BUILDING",
|
||||
MAINTENANCE_OF_LAND: "MAINTENANCE_OF_LAND",
|
||||
RIGHTS_OF_WAY_SCHEDULE_14: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
RIGHTS_OF_WAY_ORDERS: "RIGHTS_OF_WAY_ORDERS",
|
||||
REQUESTS_FOR_DIRECTION: "REQUESTS_FOR_DIRECTION",
|
||||
ADVERTS: "ADVERTS",
|
||||
HEDGEROW_TPO: "HEDGEROW_TPO",
|
||||
COMMON_LAND: "COMMON_LAND",
|
||||
COMPULSORY_PURCHASE_ORDERS: "COMPULSORY_PURCHASE_ORDERS",
|
||||
ELECTRICITY_ACT: "ELECTRICITY_ACT",
|
||||
HARBOUR_REVISION_ORDER: "HARBOUR_REVISION_ORDER",
|
||||
TRANSPORT_WORKS: "TRANSPORT_WORKS",
|
||||
WAYLEAVE: "WAYLEAVE",
|
||||
NON_VALIDATION: "NON_VALIDATION",
|
||||
DNS: "DNS",
|
||||
SIP: "SIP"
|
||||
};
|
||||
|
||||
export const caseTypeKeyByAppealTypeId = {
|
||||
846040000: "PLANNING_S78",
|
||||
846040001: "CONDITIONS_73_79",
|
||||
846040002: "SIP",
|
||||
846040003: "PLANNING_OBLIGATIONS_S106",
|
||||
846040004: "HAS",
|
||||
846040005: "ENFORCEMENT",
|
||||
846040006: "ENFORCEMENT_LISTED_BUILDING",
|
||||
846040007: "MAINTENANCE_OF_LAND",
|
||||
846040008: "LDCS",
|
||||
846040009: "PLANNING_S78",
|
||||
846040010: "CALL_INS",
|
||||
846040011: "DNS",
|
||||
846040012: "ELECTRICITY_ACT",
|
||||
846040013: "TRANSPORT_WORKS",
|
||||
846040014: "HARBOUR_REVISION_ORDER",
|
||||
846040015: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040016: "COMMON_LAND",
|
||||
846040017: "HEDGEROW_TPO",
|
||||
846040018: "ADVERTS",
|
||||
846040019: "COMPULSORY_PURCHASE_ORDERS",
|
||||
846040020: "PLANNING_S78",
|
||||
846040021: "WAYLEAVE",
|
||||
846040024: "NON_VALIDATION",
|
||||
846040025: "LBCAC"
|
||||
};
|
||||
|
||||
export const normaliseCaseType = (caseType) =>
|
||||
caseType
|
||||
?.toString()
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replace(/&/g, "AND")
|
||||
.replace(/[^A-Z0-9]+/g, "_")
|
||||
.replace(/^_|_$/g, "");
|
||||
|
||||
export function mapAppealType(appealTypeId) {
|
||||
const mappedCaseType = caseTypeKeyByAppealTypeId[Number(appealTypeId)];
|
||||
|
||||
if (mappedCaseType) {
|
||||
return mappedCaseType;
|
||||
}
|
||||
|
||||
const normalised = normaliseCaseType(appealTypeId);
|
||||
|
||||
return caseTypeAliases[normalised] || normalised;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export const specialistProcessStageTypeByCaseType = {
|
||||
RIGHTS_OF_WAY_SCHEDULE_14: {
|
||||
// TODO: replace these with the real CRM option-set values
|
||||
846040000: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040100: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040001: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040101: "RIGHTS_OF_WAY_ORDERS",
|
||||
846040002: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040102: "RIGHTS_OF_WAY_SCHEDULE_14",
|
||||
846040003: "REQUESTS_FOR_DIRECTION",
|
||||
846040103: "REQUESTS_FOR_DIRECTION"
|
||||
}
|
||||
};
|
||||
|
||||
export function mapSpecialistProcessStageType(caseTypeKey, specialistProcess) {
|
||||
return specialistProcessStageTypeByCaseType[caseTypeKey]?.[
|
||||
specialistProcess
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export function normalizeSpecialistProcess(source) {
|
||||
return (
|
||||
source?.pinswg_specialistcaseprocess ||
|
||||
source?.pinswg_speacialistcaseprocess ||
|
||||
""
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { getRepsFromBlob } from "../../actions/services/documentService";
|
||||
import { getBasicSearch } from "../../actions/services/searchService";
|
||||
import { consoleLogger } from "../../actions/core/logger";
|
||||
import { normalizeSpecialistProcess } from "../domain/case-lifecycle";
|
||||
import {
|
||||
getFormCollectionByID,
|
||||
getSearchDetails
|
||||
@@ -280,16 +281,9 @@ export const loadNewRepresentation = async ({ store, ctx, bootstrap }) => {
|
||||
"currentType": "myRepresentations",
|
||||
"incidentid": searchResultsObj?.value?.[0]?.incidentid,
|
||||
"appealType": searchResultsObj?.value?.[0]?.pinswg_appealcasetype,
|
||||
"specialistProcess":
|
||||
"specialistProcess": normalizeSpecialistProcess(
|
||||
searchDetailsObj?.[0]?.value?.[0]
|
||||
?.pinswg_speacialistcaseprocess != null ||
|
||||
searchDetailsObj?.[0]?.value?.[0]
|
||||
?.pinswg_specialistcaseprocess != null
|
||||
? searchDetailsObj?.[0]?.value?.[0]
|
||||
.pinswg_speacialistcaseprocess ||
|
||||
searchDetailsObj?.[0]?.value?.[0]
|
||||
.pinswg_specialistcaseprocess
|
||||
: ""
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { getBasicSearch } from "../../actions/services/searchService";
|
||||
import Breadcrumbs from "../../components/breadcrumbs";
|
||||
import Case from "../../components/case";
|
||||
import { normalizeSpecialistProcess } from "../../lib/domain/case-lifecycle";
|
||||
import CookieBanner from "../../components/cookieBanner";
|
||||
import Footer from "../../components/footer";
|
||||
import Header from "../../components/header";
|
||||
@@ -221,16 +222,9 @@ export const getServerSideProps = wrapper.getServerSideProps(
|
||||
"appealType":
|
||||
searchResultsObj.value[0].pinswg_appealcasetype,
|
||||
"showLoginCheck": true,
|
||||
"specialistProcess":
|
||||
"specialistProcess": normalizeSpecialistProcess(
|
||||
searchDetailsObj[0].value[0]
|
||||
?.pinswg_speacialistcaseprocess != null ||
|
||||
searchDetailsObj[0].value[0]
|
||||
?.pinswg_specialistcaseprocess != null
|
||||
? searchDetailsObj[0].value[0]
|
||||
?.pinswg_speacialistcaseprocess ||
|
||||
searchDetailsObj[0].value[0]
|
||||
?.pinswg_specialistcaseprocess
|
||||
: ""
|
||||
)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -368,16 +368,9 @@ export const getServerSideProps = wrapper.getServerSideProps(
|
||||
"appellant":
|
||||
searchResultsObj?.value[0].customerid_contact
|
||||
?.emailaddress1,
|
||||
"specialistProcess":
|
||||
"specialistProcess": normalizeSpecialistProcess(
|
||||
searchDetailsObj[0].value[0]
|
||||
.pinswg_speacialistcaseprocess != null ||
|
||||
searchDetailsObj[0].value[0]
|
||||
.pinswg_specialistcaseprocess != null
|
||||
? searchDetailsObj[0].value[0]
|
||||
.pinswg_speacialistcaseprocess ||
|
||||
searchDetailsObj[0].value[0]
|
||||
.pinswg_specialistcaseprocess
|
||||
: ""
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
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 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 += "\nmodule.exports = { getStagesForAppealType };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey,
|
||||
getStagesForCaseTypeKey:
|
||||
getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey
|
||||
};
|
||||
|
||||
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 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 += "\nmodule.exports = { getStagesForCaseTypeKey };\n";
|
||||
|
||||
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 tests = [];
|
||||
const test = (name, fn) => tests.push({ name, fn });
|
||||
|
||||
const normalizeForAssertion = (value) =>
|
||||
JSON.parse(JSON.stringify(value ?? null));
|
||||
|
||||
const mapAppealTypeModule = loadMapAppealTypeModule();
|
||||
const mapSpecialistProcessStageTypeModule =
|
||||
loadMapSpecialistProcessStageTypeModule();
|
||||
const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule();
|
||||
const stageCatalogueModule = loadStageCatalogueModule();
|
||||
const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule();
|
||||
const stageHelperModule = loadStageHelperModule();
|
||||
|
||||
const { mapAppealType, caseTypeKeyByAppealTypeId, caseTypeAliases } =
|
||||
mapAppealTypeModule;
|
||||
const { getStagesForAppealType } = stageHelperModule;
|
||||
|
||||
test("known mappings are preserved for representative appeal types", () => {
|
||||
assert.strictEqual(mapAppealType(846040000), "PLANNING_S78");
|
||||
assert.strictEqual(mapAppealType(846040011), "DNS");
|
||||
assert.strictEqual(mapAppealType(846040004), "HAS");
|
||||
assert.strictEqual(mapAppealType(846040010), "CALL_INS");
|
||||
assert.strictEqual(mapAppealType(846040015), "RIGHTS_OF_WAY_SCHEDULE_14");
|
||||
});
|
||||
|
||||
test("aliases are preserved exactly", () => {
|
||||
assert.strictEqual(mapAppealType("S78"), "PLANNING_S78");
|
||||
assert.strictEqual(mapAppealType("PLANNING_78"), "PLANNING_S78");
|
||||
assert.strictEqual(mapAppealType("CALL_IN"), "CALL_INS");
|
||||
assert.strictEqual(mapAppealType("DNS"), "DNS");
|
||||
});
|
||||
|
||||
test("unknown appeal types preserve current behaviour", () => {
|
||||
assert.strictEqual(mapAppealType(999999999), "999999999");
|
||||
assert.strictEqual(mapAppealType("UNKNOWN_CASE_TYPE"), "UNKNOWN_CASE_TYPE");
|
||||
});
|
||||
|
||||
test("null and undefined preserve current behaviour", () => {
|
||||
assert.strictEqual(mapAppealType(null), undefined);
|
||||
assert.strictEqual(mapAppealType(undefined), undefined);
|
||||
});
|
||||
|
||||
test("mapping helper remains consistent with extracted mapping tables", () => {
|
||||
for (const [appealTypeId, caseTypeKey] of Object.entries(
|
||||
caseTypeKeyByAppealTypeId
|
||||
)) {
|
||||
assert.strictEqual(mapAppealType(Number(appealTypeId)), caseTypeKey);
|
||||
}
|
||||
|
||||
for (const [alias, caseTypeKey] of Object.entries(caseTypeAliases)) {
|
||||
assert.strictEqual(mapAppealType(alias), caseTypeKey);
|
||||
}
|
||||
});
|
||||
|
||||
test("mapping helper returns keys that resolve to the same stage lists currently used by the source-of-truth helper", () => {
|
||||
const representativeIds = [846040000, 846040011, 846040004, 846040010];
|
||||
|
||||
for (const appealTypeId of representativeIds) {
|
||||
const mappedKey = mapAppealType(appealTypeId);
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(
|
||||
getStagesForAppealType(appealTypeId, 999999999)
|
||||
),
|
||||
normalizeForAssertion(getStagesForAppealType(mappedKey, 999999999))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("specialist-process-sensitive appeal type preserves extracted mapping without asserting stage-list equivalence outside specialist context", () => {
|
||||
assert.strictEqual(mapAppealType(846040015), "RIGHTS_OF_WAY_SCHEDULE_14");
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-appeal-type-mapping tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
const loadClosedCaseStatusModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"isClosedCaseStatus.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(
|
||||
/export function\s+isClosedCaseStatus/,
|
||||
"function isClosedCaseStatus"
|
||||
);
|
||||
source += "\nmodule.exports = { isClosedCaseStatus };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
const loadGetLifecycleStageIndexModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"getLifecycleStageIndex.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, "");
|
||||
source = source.replace(
|
||||
/export function\s+getLifecycleStageIndex/,
|
||||
"function getLifecycleStageIndex"
|
||||
);
|
||||
source += "\nmodule.exports = { getLifecycleStageIndex };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
isClosedCaseStatus: isClosedCaseStatusModule.isClosedCaseStatus
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
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 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: () => [],
|
||||
isClosedCaseStatus: isClosedCaseStatusModule.isClosedCaseStatus,
|
||||
getLifecycleStageIndex:
|
||||
getLifecycleStageIndexModule.getLifecycleStageIndex
|
||||
};
|
||||
|
||||
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 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 += "\nmodule.exports = { getStagesForAppealType };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey,
|
||||
getStagesForCaseTypeKey:
|
||||
getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey,
|
||||
isClosedCaseStatus: isClosedCaseStatusModule.isClosedCaseStatus,
|
||||
getLifecycleStageIndex:
|
||||
getLifecycleStageIndexModule.getLifecycleStageIndex
|
||||
};
|
||||
|
||||
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 isClosedCaseStatusModule = loadClosedCaseStatusModule();
|
||||
const getLifecycleStageIndexModule = loadGetLifecycleStageIndexModule();
|
||||
const mapAppealTypeModule = loadMapAppealTypeModule();
|
||||
const mapSpecialistProcessStageTypeModule =
|
||||
loadMapSpecialistProcessStageTypeModule();
|
||||
const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule();
|
||||
const stageCatalogueModule = loadStageCatalogueModule();
|
||||
const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule();
|
||||
const stageHelperModule = loadStageHelperModule();
|
||||
|
||||
const { isClosedCaseStatus } = isClosedCaseStatusModule;
|
||||
const { getStagesForAppealType } = stageHelperModule;
|
||||
|
||||
test("current closed statuses are recognized exactly", () => {
|
||||
const closedStatuses = [1000, 5, 6, 846040013, 846040059, 846040060];
|
||||
|
||||
for (const closedStatus of closedStatuses) {
|
||||
assert.strictEqual(isClosedCaseStatus(closedStatus), true);
|
||||
}
|
||||
});
|
||||
|
||||
test("representative non-closed statuses remain false", () => {
|
||||
const nonClosedStatuses = [
|
||||
1, 846040014, 846040018, 846040021, 846040049, 846040062
|
||||
];
|
||||
|
||||
for (const nonClosedStatus of nonClosedStatuses) {
|
||||
assert.strictEqual(isClosedCaseStatus(nonClosedStatus), false);
|
||||
}
|
||||
});
|
||||
|
||||
test("unknown values preserve current false fallback", () => {
|
||||
assert.strictEqual(isClosedCaseStatus(999999999), false);
|
||||
assert.strictEqual(isClosedCaseStatus("UNKNOWN"), false);
|
||||
});
|
||||
|
||||
test("null and undefined preserve current false fallback", () => {
|
||||
assert.strictEqual(isClosedCaseStatus(null), false);
|
||||
assert.strictEqual(isClosedCaseStatus(undefined), false);
|
||||
});
|
||||
|
||||
test("string and number handling preserves current Number-based closed-status behaviour", () => {
|
||||
assert.strictEqual(isClosedCaseStatus("1000"), true);
|
||||
assert.strictEqual(isClosedCaseStatus("5"), true);
|
||||
assert.strictEqual(isClosedCaseStatus("6"), true);
|
||||
assert.strictEqual(isClosedCaseStatus("846040013"), true);
|
||||
assert.strictEqual(isClosedCaseStatus("846040059"), true);
|
||||
assert.strictEqual(isClosedCaseStatus("846040060"), true);
|
||||
assert.strictEqual(isClosedCaseStatus("846040018"), false);
|
||||
});
|
||||
|
||||
test("integration parity preserves existing closed-case progress outputs", () => {
|
||||
const closedStatuses = [1000, 5, 6, 846040013, 846040059, 846040060];
|
||||
|
||||
for (const closedStatus of closedStatuses) {
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(846040000, closedStatus)
|
||||
);
|
||||
const closedStage = result.find(
|
||||
(stage) => stage.titleKey === "case-closed"
|
||||
);
|
||||
|
||||
assert.ok(closedStage, `Expected closed stage for ${closedStatus}`);
|
||||
assert.strictEqual(closedStage.status, "in-progress");
|
||||
assert.ok(
|
||||
result
|
||||
.slice(
|
||||
0,
|
||||
result.findIndex(
|
||||
(stage) => stage.titleKey === "case-closed"
|
||||
)
|
||||
)
|
||||
.every((stage) => stage.status === "complete")
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-closed-case-status tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,623 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
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 loadClosedCaseStatusModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"isClosedCaseStatus.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(
|
||||
/export function\s+isClosedCaseStatus/,
|
||||
"function isClosedCaseStatus"
|
||||
);
|
||||
source += `
|
||||
module.exports = {
|
||||
isClosedCaseStatus
|
||||
};
|
||||
`;
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
const loadGetLifecycleStageIndexModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"getLifecycleStageIndex.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, "");
|
||||
source = source.replace(
|
||||
/export function\s+getLifecycleStageIndex/,
|
||||
"function getLifecycleStageIndex"
|
||||
);
|
||||
source += `
|
||||
module.exports = {
|
||||
getLifecycleStageIndex
|
||||
};
|
||||
`;
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
isClosedCaseStatus: isClosedCaseStatusModule.isClosedCaseStatus
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
const loadGetLifecycleStageStatusModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"getLifecycleStageStatus.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(
|
||||
/export function\s+getLifecycleStageStatus/,
|
||||
"function getLifecycleStageStatus"
|
||||
);
|
||||
source += "\nmodule.exports = { getLifecycleStageStatus };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require
|
||||
};
|
||||
|
||||
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: () => [],
|
||||
isClosedCaseStatus: isClosedCaseStatusModule.isClosedCaseStatus,
|
||||
getLifecycleStageIndex:
|
||||
getLifecycleStageIndexModule.getLifecycleStageIndex,
|
||||
getLifecycleStageStatus:
|
||||
getLifecycleStageStatusModule.getLifecycleStageStatus
|
||||
};
|
||||
|
||||
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 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 +=
|
||||
"\nmodule.exports = { getStagesForAppealType, stagesByCaseType };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey,
|
||||
getStagesForCaseTypeKey:
|
||||
getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey,
|
||||
isClosedCaseStatus: isClosedCaseStatusModule.isClosedCaseStatus,
|
||||
getLifecycleStageIndex:
|
||||
getLifecycleStageIndexModule.getLifecycleStageIndex,
|
||||
getLifecycleStageStatus:
|
||||
getLifecycleStageStatusModule.getLifecycleStageStatus
|
||||
};
|
||||
|
||||
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 getStatusSummary = (stages) =>
|
||||
stages.map(({ stageId, titleKey, status }) => ({
|
||||
stageId,
|
||||
titleKey,
|
||||
status
|
||||
}));
|
||||
|
||||
const mapAppealTypeModule = loadMapAppealTypeModule();
|
||||
const mapSpecialistProcessStageTypeModule =
|
||||
loadMapSpecialistProcessStageTypeModule();
|
||||
const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule();
|
||||
const isClosedCaseStatusModule = loadClosedCaseStatusModule();
|
||||
const getLifecycleStageIndexModule = loadGetLifecycleStageIndexModule();
|
||||
const getLifecycleStageStatusModule = loadGetLifecycleStageStatusModule();
|
||||
const stageCatalogueModule = loadStageCatalogueModule();
|
||||
const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule();
|
||||
const stageHelperModule = loadStageHelperModule();
|
||||
const wrapperModule = loadWrapperModule(stageHelperModule);
|
||||
|
||||
const { getStagesForAppealType } = stageHelperModule;
|
||||
const { getLifecycleStagesForCase } = wrapperModule;
|
||||
|
||||
test("S78 progression preserves complete/current/future status assignment", () => {
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(846040000, 846040018)
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(getStatusSummary(result).slice(0, 7), [
|
||||
{
|
||||
stageId: 846040014,
|
||||
titleKey: "appeal-submitted",
|
||||
status: "complete"
|
||||
},
|
||||
{
|
||||
stageId: 846040015,
|
||||
titleKey: "appeal-registered",
|
||||
status: "complete"
|
||||
},
|
||||
{
|
||||
stageId: 846040016,
|
||||
titleKey: "appeal-validated",
|
||||
status: "complete"
|
||||
},
|
||||
{ stageId: 1, titleKey: "case-in-progress", status: "complete" },
|
||||
{
|
||||
stageId: 846040017,
|
||||
titleKey: "questionnaire-lpa-to-notify",
|
||||
status: "complete"
|
||||
},
|
||||
{
|
||||
stageId: 846040018,
|
||||
titleKey: "representations-period",
|
||||
status: "in-progress"
|
||||
},
|
||||
{
|
||||
stageId: 846040019,
|
||||
titleKey: "final-comments",
|
||||
status: "not-started"
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test("DNS progression preserves current stage and surrounding status split", () => {
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(846040011, 846040006)
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(getStatusSummary(result).slice(3, 8), [
|
||||
{
|
||||
stageId: 846040004,
|
||||
titleKey: "acceptance-checking",
|
||||
status: "complete"
|
||||
},
|
||||
{
|
||||
stageId: 846040005,
|
||||
titleKey: "invalid-submission",
|
||||
status: "complete"
|
||||
},
|
||||
{ stageId: 846040006, titleKey: "consultation", status: "in-progress" },
|
||||
{
|
||||
stageId: 846040007,
|
||||
titleKey: "re-consultation",
|
||||
status: "not-started"
|
||||
},
|
||||
{ stageId: 846040008, titleKey: "reporting", status: "not-started" }
|
||||
]);
|
||||
});
|
||||
|
||||
test("Householder progression preserves shortened lifecycle status behaviour", () => {
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(846040004, 846040021)
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(getStatusSummary(result), [
|
||||
{
|
||||
stageId: 846040014,
|
||||
titleKey: "appeal-submitted",
|
||||
status: "complete"
|
||||
},
|
||||
{
|
||||
stageId: 846040015,
|
||||
titleKey: "appeal-registered",
|
||||
status: "complete"
|
||||
},
|
||||
{
|
||||
stageId: 846040016,
|
||||
titleKey: "appeal-validated",
|
||||
status: "complete"
|
||||
},
|
||||
{ stageId: 1, titleKey: "case-in-progress", status: "complete" },
|
||||
{ stageId: 846040021, titleKey: "site-visit", status: "in-progress" },
|
||||
{
|
||||
stageId: 846040022,
|
||||
titleKey: "decision-issued",
|
||||
status: "not-started"
|
||||
},
|
||||
{ stageId: 846040013, titleKey: "case-closed", status: "not-started" }
|
||||
]);
|
||||
});
|
||||
|
||||
test("Call-In progression preserves current event-stage behaviour", () => {
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(846040010, 846040049)
|
||||
);
|
||||
|
||||
const currentStage = result.find((stage) => stage.stageId === 846040049);
|
||||
const completedCount = result.filter(
|
||||
(stage) => stage.status === "complete"
|
||||
).length;
|
||||
|
||||
assert.ok(currentStage);
|
||||
assert.strictEqual(currentStage.titleKey, "event");
|
||||
assert.strictEqual(currentStage.status, "in-progress");
|
||||
assert.strictEqual(completedCount, 7);
|
||||
assert.ok(
|
||||
result
|
||||
.slice(result.indexOf(currentStage) + 1)
|
||||
.every((stage) => stage.status === "not-started")
|
||||
);
|
||||
});
|
||||
|
||||
test("ROW progression preserves current all-not-started behaviour for representative input", () => {
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(846040015, 846040032, 846040000)
|
||||
);
|
||||
|
||||
assert.ok(result.every((stage) => stage.status === "not-started"));
|
||||
assert.deepStrictEqual(
|
||||
result.map((stage) => stage.stageId),
|
||||
[
|
||||
846040033, 846040034, 846040035, 846040036, 846040062, 846040055,
|
||||
846040037, 846040022, 846040013
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
test("closed-case fallback preserves current case-closed mapping for all recognised closed statuses", () => {
|
||||
const closedStatuses = [1000, 5, 6, 846040013, 846040059, 846040060];
|
||||
|
||||
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 ${closedStatus}`
|
||||
);
|
||||
assert.strictEqual(closedStage.status, "in-progress");
|
||||
assert.ok(
|
||||
result
|
||||
.slice(
|
||||
0,
|
||||
result.findIndex(
|
||||
(stage) => stage.titleKey === "case-closed"
|
||||
)
|
||||
)
|
||||
.every((stage) => stage.status === "complete")
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("unknown status handling preserves all-not-started fallback", () => {
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(846040000, 999999999)
|
||||
);
|
||||
|
||||
assert.ok(result.every((stage) => stage.status === "not-started"));
|
||||
});
|
||||
|
||||
test("status assignment preserves complete, in-progress and not-started semantics from index position", () => {
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(846040000, 846040020)
|
||||
);
|
||||
|
||||
const statusSequence = result.map((stage) => stage.status);
|
||||
|
||||
assert.deepStrictEqual(statusSequence, [
|
||||
"complete",
|
||||
"complete",
|
||||
"complete",
|
||||
"complete",
|
||||
"complete",
|
||||
"complete",
|
||||
"complete",
|
||||
"in-progress",
|
||||
"not-started",
|
||||
"not-started",
|
||||
"not-started"
|
||||
]);
|
||||
});
|
||||
|
||||
test("statuscode ambiguity is preserved when statuscode-like values are passed into stage logic", () => {
|
||||
const exactStageMatch = normalizeForAssertion(
|
||||
getStagesForAppealType(846040000, 846040013)
|
||||
);
|
||||
const closedStatusFallback = normalizeForAssertion(
|
||||
getStagesForAppealType(846040000, 846040059)
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
getStatusSummary(exactStageMatch),
|
||||
getStatusSummary(closedStatusFallback)
|
||||
);
|
||||
});
|
||||
|
||||
test("wrapper parity preserves current runtime outputs for representative progress scenarios", () => {
|
||||
const scenarios = [
|
||||
[846040000, 846040018, undefined],
|
||||
[846040011, 846040006, undefined],
|
||||
[846040004, 846040021, undefined],
|
||||
[846040010, 846040049, undefined],
|
||||
[846040015, 846040032, 846040000],
|
||||
[846040000, 846040059, undefined],
|
||||
[846040000, 999999999, undefined]
|
||||
];
|
||||
|
||||
for (const [caseType, currentStageId, specialistProcess] of scenarios) {
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(
|
||||
getLifecycleStagesForCase(
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
),
|
||||
normalizeForAssertion(
|
||||
getStagesForAppealType(
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-progress-behaviour tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
const loadNormalizationModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"normalizeSpecialistProcess.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(
|
||||
/export function\s+normalizeSpecialistProcess/,
|
||||
"function normalizeSpecialistProcess"
|
||||
);
|
||||
source += "\nmodule.exports = { normalizeSpecialistProcess };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
const tests = [];
|
||||
const test = (name, fn) => tests.push({ name, fn });
|
||||
|
||||
const { normalizeSpecialistProcess } = loadNormalizationModule();
|
||||
|
||||
test("returns pinswg_specialistcaseprocess when present", () => {
|
||||
assert.strictEqual(
|
||||
normalizeSpecialistProcess({ pinswg_specialistcaseprocess: 846040001 }),
|
||||
846040001
|
||||
);
|
||||
});
|
||||
|
||||
test("returns misspelled fallback when canonical field is absent", () => {
|
||||
assert.strictEqual(
|
||||
normalizeSpecialistProcess({
|
||||
pinswg_speacialistcaseprocess: 846040000
|
||||
}),
|
||||
846040000
|
||||
);
|
||||
});
|
||||
|
||||
test("canonical field wins when both fields exist", () => {
|
||||
assert.strictEqual(
|
||||
normalizeSpecialistProcess({
|
||||
pinswg_specialistcaseprocess: 846040001,
|
||||
pinswg_speacialistcaseprocess: 846040000
|
||||
}),
|
||||
846040001
|
||||
);
|
||||
});
|
||||
|
||||
test("returns empty string when neither field exists", () => {
|
||||
assert.strictEqual(normalizeSpecialistProcess({}), "");
|
||||
});
|
||||
|
||||
test("returns empty string for null or undefined source", () => {
|
||||
assert.strictEqual(normalizeSpecialistProcess(null), "");
|
||||
assert.strictEqual(normalizeSpecialistProcess(undefined), "");
|
||||
});
|
||||
|
||||
test("preserves number values without coercing to strings", () => {
|
||||
const result = normalizeSpecialistProcess({
|
||||
pinswg_specialistcaseprocess: 846040001
|
||||
});
|
||||
|
||||
assert.strictEqual(typeof result, "number");
|
||||
assert.strictEqual(result, 846040001);
|
||||
});
|
||||
|
||||
test("preserves string values without coercing to numbers", () => {
|
||||
const result = normalizeSpecialistProcess({
|
||||
pinswg_specialistcaseprocess: "846040001"
|
||||
});
|
||||
|
||||
assert.strictEqual(typeof result, "string");
|
||||
assert.strictEqual(result, "846040001");
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-specialist-process-normalization tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
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 loadStageHelperModule = (
|
||||
mapAppealTypeModule,
|
||||
mapSpecialistProcessStageTypeModule
|
||||
) => {
|
||||
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 = { getStagesForAppealType };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey,
|
||||
getStagesForCaseTypeKey:
|
||||
getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey
|
||||
};
|
||||
|
||||
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 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 += "\nmodule.exports = { getStagesForCaseTypeKey };\n";
|
||||
|
||||
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 tests = [];
|
||||
const test = (name, fn) => tests.push({ name, fn });
|
||||
|
||||
const normalizeForAssertion = (value) =>
|
||||
JSON.parse(JSON.stringify(value ?? null));
|
||||
|
||||
const mapAppealTypeModule = loadMapAppealTypeModule();
|
||||
const mapSpecialistProcessStageTypeModule =
|
||||
loadMapSpecialistProcessStageTypeModule();
|
||||
const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule();
|
||||
const stageCatalogueModule = loadStageCatalogueModule();
|
||||
const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule();
|
||||
const stageHelperModule = loadStageHelperModule(
|
||||
mapAppealTypeModule,
|
||||
mapSpecialistProcessStageTypeModule
|
||||
);
|
||||
|
||||
const { mapAppealType } = mapAppealTypeModule;
|
||||
const { specialistProcessStageTypeByCaseType, mapSpecialistProcessStageType } =
|
||||
mapSpecialistProcessStageTypeModule;
|
||||
const { getStagesForAppealType } = stageHelperModule;
|
||||
|
||||
test("known specialist-process override mappings are preserved exactly", () => {
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040000),
|
||||
"RIGHTS_OF_WAY_ORDERS"
|
||||
);
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040001),
|
||||
"RIGHTS_OF_WAY_ORDERS"
|
||||
);
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040002),
|
||||
"RIGHTS_OF_WAY_SCHEDULE_14"
|
||||
);
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040003),
|
||||
"REQUESTS_FOR_DIRECTION"
|
||||
);
|
||||
});
|
||||
|
||||
test("placeholder duplicate specialist-process IDs preserve the same override behaviour", () => {
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040100),
|
||||
"RIGHTS_OF_WAY_ORDERS"
|
||||
);
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040101),
|
||||
"RIGHTS_OF_WAY_ORDERS"
|
||||
);
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040102),
|
||||
"RIGHTS_OF_WAY_SCHEDULE_14"
|
||||
);
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040103),
|
||||
"REQUESTS_FOR_DIRECTION"
|
||||
);
|
||||
});
|
||||
|
||||
test("case types without an override table preserve current undefined fallback", () => {
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("PLANNING_S78", 846040000),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
test("unknown specialist process preserves current undefined fallback", () => {
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 999999999),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
test("null and undefined specialist process preserve current undefined fallback", () => {
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", null),
|
||||
undefined
|
||||
);
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", undefined),
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
test("numeric and string specialist process values preserve current lookup behaviour", () => {
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", 846040000),
|
||||
"RIGHTS_OF_WAY_ORDERS"
|
||||
);
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType("RIGHTS_OF_WAY_SCHEDULE_14", "846040000"),
|
||||
"RIGHTS_OF_WAY_ORDERS"
|
||||
);
|
||||
});
|
||||
|
||||
test("extracted helper remains consistent with the exported override table", () => {
|
||||
for (const [caseTypeKey, specialistProcessMap] of Object.entries(
|
||||
specialistProcessStageTypeByCaseType
|
||||
)) {
|
||||
for (const [specialistProcess, stageTypeKey] of Object.entries(
|
||||
specialistProcessMap
|
||||
)) {
|
||||
assert.strictEqual(
|
||||
mapSpecialistProcessStageType(caseTypeKey, specialistProcess),
|
||||
stageTypeKey
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("stage helper output remains unchanged for representative specialist override scenarios", () => {
|
||||
const specialistAppealType = 846040015;
|
||||
|
||||
const rightsOfWayOrders = normalizeForAssertion(
|
||||
getStagesForAppealType(specialistAppealType, 846040032, 846040000)
|
||||
);
|
||||
const requestsForDirection = normalizeForAssertion(
|
||||
getStagesForAppealType(specialistAppealType, 846040032, 846040003)
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
rightsOfWayOrders.map((stage) => stage.stageId),
|
||||
[
|
||||
846040033, 846040034, 846040035, 846040036, 846040062, 846040055,
|
||||
846040037, 846040022, 846040013
|
||||
]
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
requestsForDirection.map((stage) => stage.stageId),
|
||||
[
|
||||
846040042, 846040043, 846040054, 846040010, 846040063, 846040022,
|
||||
846040013
|
||||
]
|
||||
);
|
||||
assert.ok(
|
||||
rightsOfWayOrders.every((stage) => stage.status === "not-started")
|
||||
);
|
||||
assert.ok(
|
||||
requestsForDirection.every((stage) => stage.status === "not-started")
|
||||
);
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-specialist-process-stage-mapping tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
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 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 += "\nmodule.exports = { getStagesForAppealType };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey,
|
||||
getStagesForCaseTypeKey:
|
||||
getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey
|
||||
};
|
||||
|
||||
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 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 += "\nmodule.exports = { getStagesForCaseTypeKey };\n";
|
||||
|
||||
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 tests = [];
|
||||
const test = (name, fn) => tests.push({ name, fn });
|
||||
|
||||
const normalizeForAssertion = (value) =>
|
||||
JSON.parse(JSON.stringify(value ?? null));
|
||||
|
||||
const mapAppealTypeModule = loadMapAppealTypeModule();
|
||||
const mapSpecialistProcessStageTypeModule =
|
||||
loadMapSpecialistProcessStageTypeModule();
|
||||
const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule();
|
||||
const stageCatalogueModule = loadStageCatalogueModule();
|
||||
const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule();
|
||||
const stageHelperModule = loadStageHelperModule();
|
||||
|
||||
const { getStageCaseTypeKey } = getStageCaseTypeKeyModule;
|
||||
const { getStagesForAppealType } = stageHelperModule;
|
||||
|
||||
test("appeal type resolution is preserved for representative cases", () => {
|
||||
assert.strictEqual(getStageCaseTypeKey(846040000), "PLANNING_S78");
|
||||
assert.strictEqual(getStageCaseTypeKey(846040011), "DNS");
|
||||
assert.strictEqual(getStageCaseTypeKey(846040004), "HAS");
|
||||
assert.strictEqual(getStageCaseTypeKey(846040010), "CALL_INS");
|
||||
assert.strictEqual(
|
||||
getStageCaseTypeKey(846040015),
|
||||
"RIGHTS_OF_WAY_SCHEDULE_14"
|
||||
);
|
||||
});
|
||||
|
||||
test("alias resolution is preserved exactly", () => {
|
||||
assert.strictEqual(getStageCaseTypeKey("S78"), "PLANNING_S78");
|
||||
assert.strictEqual(getStageCaseTypeKey("PLANNING_78"), "PLANNING_S78");
|
||||
assert.strictEqual(getStageCaseTypeKey("CALL_IN"), "CALL_INS");
|
||||
});
|
||||
|
||||
test("specialist-process override resolution is preserved exactly", () => {
|
||||
assert.strictEqual(
|
||||
getStageCaseTypeKey(846040015, 846040000),
|
||||
"RIGHTS_OF_WAY_ORDERS"
|
||||
);
|
||||
assert.strictEqual(
|
||||
getStageCaseTypeKey(846040015, 846040002),
|
||||
"RIGHTS_OF_WAY_SCHEDULE_14"
|
||||
);
|
||||
assert.strictEqual(
|
||||
getStageCaseTypeKey(846040015, 846040003),
|
||||
"REQUESTS_FOR_DIRECTION"
|
||||
);
|
||||
});
|
||||
|
||||
test("no-override fallback preserves the original resolved case type key", () => {
|
||||
assert.strictEqual(
|
||||
getStageCaseTypeKey(846040000, 846040000),
|
||||
"PLANNING_S78"
|
||||
);
|
||||
assert.strictEqual(
|
||||
getStageCaseTypeKey("PLANNING_S78", 846040000),
|
||||
"PLANNING_S78"
|
||||
);
|
||||
});
|
||||
|
||||
test("unknown appeal type handling is preserved", () => {
|
||||
assert.strictEqual(getStageCaseTypeKey(999999999), "999999999");
|
||||
assert.strictEqual(
|
||||
getStageCaseTypeKey("UNKNOWN_CASE_TYPE"),
|
||||
"UNKNOWN_CASE_TYPE"
|
||||
);
|
||||
});
|
||||
|
||||
test("null and undefined inputs preserve current behaviour", () => {
|
||||
assert.strictEqual(getStageCaseTypeKey(null), undefined);
|
||||
assert.strictEqual(getStageCaseTypeKey(undefined), undefined);
|
||||
});
|
||||
|
||||
test("stage outputs remain identical for representative inputs after extraction", () => {
|
||||
const scenarios = [
|
||||
[846040000, 846040014, undefined],
|
||||
[846040011, 846040006, undefined],
|
||||
[846040004, 846040021, undefined],
|
||||
[846040010, 846040049, undefined],
|
||||
[846040015, 846040032, 846040000],
|
||||
[846040015, 846040032, 846040003],
|
||||
["S78", 846040018, undefined],
|
||||
["CALL_IN", 846040049, undefined]
|
||||
];
|
||||
|
||||
for (const [
|
||||
caseTypeOrAppealTypeId,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
] of scenarios) {
|
||||
const resolvedKey = getStageCaseTypeKey(
|
||||
caseTypeOrAppealTypeId,
|
||||
specialistProcess
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(
|
||||
getStagesForAppealType(
|
||||
caseTypeOrAppealTypeId,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
),
|
||||
normalizeForAssertion(
|
||||
getStagesForAppealType(
|
||||
resolvedKey,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-stage-case-type-resolution tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
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 += "\nmodule.exports = { getStageCaseTypeKey };\n";
|
||||
|
||||
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 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 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 += "\nmodule.exports = { getStagesForCaseTypeKey };\n";
|
||||
|
||||
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 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 +=
|
||||
"\nmodule.exports = { getStagesForAppealType, stagesByCaseType };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey,
|
||||
getStagesForCaseTypeKey:
|
||||
getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey
|
||||
};
|
||||
|
||||
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 mapAppealTypeModule = loadMapAppealTypeModule();
|
||||
const mapSpecialistProcessStageTypeModule =
|
||||
loadMapSpecialistProcessStageTypeModule();
|
||||
const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule();
|
||||
const stageCatalogueModule = loadStageCatalogueModule();
|
||||
const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule();
|
||||
const stageHelperModule = loadStageHelperModule();
|
||||
|
||||
const { getStageCaseTypeKey } = getStageCaseTypeKeyModule;
|
||||
const { getStagesForCaseTypeKey } = getStagesForCaseTypeKeyModule;
|
||||
const { getStagesForAppealType, stagesByCaseType } = stageHelperModule;
|
||||
|
||||
test("known catalogue lookups preserve current catalogue resolution", () => {
|
||||
const representativeKeys = [
|
||||
"PLANNING_S78",
|
||||
"DNS",
|
||||
"HAS",
|
||||
"CALL_INS",
|
||||
"RIGHTS_OF_WAY_SCHEDULE_14"
|
||||
];
|
||||
|
||||
for (const key of representativeKeys) {
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(getStagesForCaseTypeKey(key)),
|
||||
normalizeForAssertion(stagesByCaseType[key])
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("indirection behaviour preserves current target catalogue references", () => {
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(getStagesForCaseTypeKey("SIP")),
|
||||
normalizeForAssertion(stagesByCaseType.DNS)
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(getStagesForCaseTypeKey("CONDITIONS_73_79")),
|
||||
normalizeForAssertion(stagesByCaseType.PLANNING_S78)
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(
|
||||
getStagesForCaseTypeKey("ENFORCEMENT_LISTED_BUILDING")
|
||||
),
|
||||
normalizeForAssertion(stagesByCaseType.ENFORCEMENT)
|
||||
);
|
||||
});
|
||||
|
||||
test("unknown key handling preserves empty-array fallback", () => {
|
||||
const result = getStagesForCaseTypeKey("UNKNOWN_CASE_TYPE");
|
||||
|
||||
assert.ok(Array.isArray(result));
|
||||
assert.strictEqual(result.length, 0);
|
||||
});
|
||||
|
||||
test("null and undefined inputs preserve current empty-array fallback", () => {
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(getStagesForCaseTypeKey(null)),
|
||||
[]
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(getStagesForCaseTypeKey(undefined)),
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
test("reference integrity is preserved for direct and indirect catalogue lookups", () => {
|
||||
const direct = getStagesForCaseTypeKey("PLANNING_S78");
|
||||
const indirect = getStagesForCaseTypeKey("CONDITIONS_73_79");
|
||||
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(direct),
|
||||
normalizeForAssertion(stagesByCaseType.PLANNING_S78)
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(indirect),
|
||||
normalizeForAssertion(stagesByCaseType.PLANNING_S78)
|
||||
);
|
||||
assert.strictEqual(direct, indirect);
|
||||
});
|
||||
|
||||
test("lazy lookup preserves current catalogue access without cloning", () => {
|
||||
const firstLookup = getStagesForCaseTypeKey("DNS");
|
||||
const secondLookup = getStagesForCaseTypeKey("DNS");
|
||||
|
||||
assert.strictEqual(firstLookup, secondLookup);
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(firstLookup),
|
||||
normalizeForAssertion(stageCatalogueModule.stagesByCaseType.DNS)
|
||||
);
|
||||
});
|
||||
|
||||
test("lazy lookup preserves alias-target reference identity for indirect catalogues", () => {
|
||||
const directTarget = getStagesForCaseTypeKey("DNS");
|
||||
const indirectTarget = getStagesForCaseTypeKey("SIP");
|
||||
|
||||
assert.strictEqual(indirectTarget, directTarget);
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(indirectTarget),
|
||||
normalizeForAssertion(stageCatalogueModule.stagesByCaseType.DNS)
|
||||
);
|
||||
});
|
||||
|
||||
test("integration parity preserves stage outputs for representative lifecycle inputs", () => {
|
||||
const scenarios = [
|
||||
[846040000, 846040018, undefined],
|
||||
[846040011, 846040006, undefined],
|
||||
[846040004, 846040021, undefined],
|
||||
[846040010, 846040049, undefined],
|
||||
[846040015, 846040032, 846040000],
|
||||
[846040015, 846040032, 846040003],
|
||||
["S78", 846040018, undefined],
|
||||
["CALL_IN", 846040049, undefined],
|
||||
[999999999, 846040018, undefined],
|
||||
[null, 846040018, undefined],
|
||||
[undefined, 846040018, undefined]
|
||||
];
|
||||
|
||||
for (const [
|
||||
caseTypeOrAppealTypeId,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
] of scenarios) {
|
||||
const resolvedKey = getStageCaseTypeKey(
|
||||
caseTypeOrAppealTypeId,
|
||||
specialistProcess
|
||||
);
|
||||
const catalogue = getStagesForCaseTypeKey(resolvedKey);
|
||||
const result = normalizeForAssertion(
|
||||
getStagesForAppealType(
|
||||
caseTypeOrAppealTypeId,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
);
|
||||
|
||||
if (!catalogue.length) {
|
||||
assert.deepStrictEqual(result, []);
|
||||
continue;
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(
|
||||
result.map(({ id, stageId, titleKey, descriptionKey }) => ({
|
||||
id,
|
||||
stageId,
|
||||
titleKey,
|
||||
descriptionKey
|
||||
})),
|
||||
normalizeForAssertion(catalogue)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-stage-catalogue-lookup tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
const loadClosedCaseStatusModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"isClosedCaseStatus.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(
|
||||
/export function\s+isClosedCaseStatus/,
|
||||
"function isClosedCaseStatus"
|
||||
);
|
||||
source += "\nmodule.exports = { isClosedCaseStatus };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
const loadGetLifecycleStageIndexModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"getLifecycleStageIndex.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, "");
|
||||
source = source.replace(
|
||||
/export function\s+getLifecycleStageIndex/,
|
||||
"function getLifecycleStageIndex"
|
||||
);
|
||||
source += "\nmodule.exports = { getLifecycleStageIndex };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
isClosedCaseStatus: closedCaseStatusModule.isClosedCaseStatus
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
const loadGetLifecycleStageStatusModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"getLifecycleStageStatus.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(
|
||||
/export function\s+getLifecycleStageStatus/,
|
||||
"function getLifecycleStageStatus"
|
||||
);
|
||||
source += "\nmodule.exports = { getLifecycleStageStatus };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
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 += "\nmodule.exports = { getStageCaseTypeKey };\n";
|
||||
|
||||
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 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: () => [],
|
||||
isClosedCaseStatus: closedCaseStatusModule.isClosedCaseStatus,
|
||||
getLifecycleStageIndex:
|
||||
getLifecycleStageIndexModule.getLifecycleStageIndex,
|
||||
getLifecycleStageStatus:
|
||||
getLifecycleStageStatusModule.getLifecycleStageStatus
|
||||
};
|
||||
|
||||
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 += "\nmodule.exports = { getStagesForCaseTypeKey };\n";
|
||||
|
||||
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 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 +=
|
||||
"\nmodule.exports = { getStagesForAppealType, stagesByCaseType };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey,
|
||||
getStagesForCaseTypeKey:
|
||||
getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey,
|
||||
isClosedCaseStatus: closedCaseStatusModule.isClosedCaseStatus,
|
||||
getLifecycleStageIndex:
|
||||
getLifecycleStageIndexModule.getLifecycleStageIndex,
|
||||
getLifecycleStageStatus:
|
||||
getLifecycleStageStatusModule.getLifecycleStageStatus
|
||||
};
|
||||
|
||||
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 closedCaseStatusModule = loadClosedCaseStatusModule();
|
||||
const getLifecycleStageIndexModule = loadGetLifecycleStageIndexModule();
|
||||
const getLifecycleStageStatusModule = loadGetLifecycleStageStatusModule();
|
||||
const mapAppealTypeModule = loadMapAppealTypeModule();
|
||||
const mapSpecialistProcessStageTypeModule =
|
||||
loadMapSpecialistProcessStageTypeModule();
|
||||
const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule();
|
||||
const stageCatalogueModule = loadStageCatalogueModule();
|
||||
const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule();
|
||||
const stageHelperModule = loadStageHelperModule();
|
||||
|
||||
const { getLifecycleStageIndex } = getLifecycleStageIndexModule;
|
||||
const { getStagesForAppealType, stagesByCaseType } = stageHelperModule;
|
||||
|
||||
test("direct stage ID match returns the correct index", () => {
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.PLANNING_S78, 846040014),
|
||||
0
|
||||
);
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.DNS, 846040006),
|
||||
5
|
||||
);
|
||||
});
|
||||
|
||||
test("numeric and string stage ID forms preserve the same current index", () => {
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.PLANNING_S78, 846040018),
|
||||
5
|
||||
);
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.PLANNING_S78, "846040018"),
|
||||
5
|
||||
);
|
||||
});
|
||||
|
||||
test("unknown stage or status preserves -1 fallback", () => {
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.PLANNING_S78, 999999999),
|
||||
-1
|
||||
);
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.PLANNING_S78, "UNKNOWN"),
|
||||
-1
|
||||
);
|
||||
});
|
||||
|
||||
test("closed-case statuses resolve to the case-closed index where present", () => {
|
||||
const closedStatuses = [1000, 5, 6, 846040013, 846040059, 846040060];
|
||||
|
||||
for (const closedStatus of closedStatuses) {
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.PLANNING_S78, closedStatus),
|
||||
10
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("missing case-closed stage preserves current -1 fallback for closed statuses", () => {
|
||||
const stagesWithoutClosed = stagesByCaseType.PLANNING_S78.filter(
|
||||
(stage) => stage.titleKey !== "case-closed"
|
||||
);
|
||||
|
||||
assert.strictEqual(getLifecycleStageIndex(stagesWithoutClosed, 1000), -1);
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesWithoutClosed, 846040059),
|
||||
-1
|
||||
);
|
||||
});
|
||||
|
||||
test("null and undefined current stage input preserve current -1 fallback", () => {
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.PLANNING_S78, null),
|
||||
-1
|
||||
);
|
||||
assert.strictEqual(
|
||||
getLifecycleStageIndex(stagesByCaseType.PLANNING_S78, undefined),
|
||||
-1
|
||||
);
|
||||
});
|
||||
|
||||
test("integration parity preserves existing lifecycle outputs for representative inputs", () => {
|
||||
const scenarios = [
|
||||
[846040000, 846040018, undefined],
|
||||
[846040011, 846040006, undefined],
|
||||
[846040004, 846040021, undefined],
|
||||
[846040010, 846040049, undefined],
|
||||
[846040000, 846040059, undefined],
|
||||
[846040000, 999999999, undefined]
|
||||
];
|
||||
|
||||
const baselineGetStageIndex = (stages, currentStageId) => {
|
||||
const currentStageNumber = Number(currentStageId);
|
||||
|
||||
const exactIndex = stages.findIndex(
|
||||
(stage) => Number(stage.stageId) === currentStageNumber
|
||||
);
|
||||
|
||||
if (exactIndex !== -1) return exactIndex;
|
||||
|
||||
if (closedCaseStatusModule.isClosedCaseStatus(currentStageNumber)) {
|
||||
return stages.findIndex(
|
||||
(stage) => stage.titleKey === "case-closed"
|
||||
);
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
|
||||
const baselineGetStagesForAppealType = (
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
) => {
|
||||
const stages = normalizeForAssertion(
|
||||
getStagesForAppealType(caseType, currentStageId, specialistProcess)
|
||||
).map(({ status, ...stage }) => stage);
|
||||
const freshStages = normalizeForAssertion(
|
||||
getStagesForAppealType(caseType, currentStageId, specialistProcess)
|
||||
).map(({ status, ...stage }) => stage);
|
||||
const currentIndex = baselineGetStageIndex(freshStages, currentStageId);
|
||||
|
||||
return freshStages.map((stage, index) => ({
|
||||
...stage,
|
||||
status:
|
||||
currentIndex === -1
|
||||
? "not-started"
|
||||
: index < currentIndex
|
||||
? "complete"
|
||||
: index === currentIndex
|
||||
? "in-progress"
|
||||
: "not-started"
|
||||
}));
|
||||
};
|
||||
|
||||
for (const [caseType, currentStageId, specialistProcess] of scenarios) {
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(
|
||||
getStagesForAppealType(
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
),
|
||||
normalizeForAssertion(
|
||||
baselineGetStagesForAppealType(
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-stage-index tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const vm = require("vm");
|
||||
const assert = require("assert");
|
||||
|
||||
const rootDir = path.resolve(__dirname, "..", "..");
|
||||
|
||||
const loadGetLifecycleStageStatusModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"getLifecycleStageStatus.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(
|
||||
/export function\s+getLifecycleStageStatus/,
|
||||
"function getLifecycleStageStatus"
|
||||
);
|
||||
source += "\nmodule.exports = { getLifecycleStageStatus };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
const loadClosedCaseStatusModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"isClosedCaseStatus.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(
|
||||
/export function\s+isClosedCaseStatus/,
|
||||
"function isClosedCaseStatus"
|
||||
);
|
||||
source += "\nmodule.exports = { isClosedCaseStatus };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
const loadGetLifecycleStageIndexModule = () => {
|
||||
const filePath = path.join(
|
||||
rootDir,
|
||||
"lib",
|
||||
"domain",
|
||||
"case-lifecycle",
|
||||
"getLifecycleStageIndex.js"
|
||||
);
|
||||
|
||||
let source = fs.readFileSync(filePath, "utf8");
|
||||
source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, "");
|
||||
source = source.replace(
|
||||
/export function\s+getLifecycleStageIndex/,
|
||||
"function getLifecycleStageIndex"
|
||||
);
|
||||
source += "\nmodule.exports = { getLifecycleStageIndex };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
isClosedCaseStatus: closedCaseStatusModule.isClosedCaseStatus
|
||||
};
|
||||
|
||||
vm.runInNewContext(source, context, { filename: filePath });
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
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 += "\nmodule.exports = { getStageCaseTypeKey };\n";
|
||||
|
||||
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 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: () => [],
|
||||
isClosedCaseStatus: closedCaseStatusModule.isClosedCaseStatus,
|
||||
getLifecycleStageIndex:
|
||||
getLifecycleStageIndexModule.getLifecycleStageIndex,
|
||||
getLifecycleStageStatus:
|
||||
getLifecycleStageStatusModule.getLifecycleStageStatus
|
||||
};
|
||||
|
||||
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 += "\nmodule.exports = { getStagesForCaseTypeKey };\n";
|
||||
|
||||
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 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 +=
|
||||
"\nmodule.exports = { getStagesForAppealType, stagesByCaseType };\n";
|
||||
|
||||
const context = {
|
||||
module: { exports: {} },
|
||||
exports: {},
|
||||
require,
|
||||
getStageCaseTypeKey: getStageCaseTypeKeyModule.getStageCaseTypeKey,
|
||||
getStagesForCaseTypeKey:
|
||||
getStagesForCaseTypeKeyModule.getStagesForCaseTypeKey,
|
||||
isClosedCaseStatus: closedCaseStatusModule.isClosedCaseStatus,
|
||||
getLifecycleStageIndex:
|
||||
getLifecycleStageIndexModule.getLifecycleStageIndex,
|
||||
getLifecycleStageStatus:
|
||||
getLifecycleStageStatusModule.getLifecycleStageStatus
|
||||
};
|
||||
|
||||
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 closedCaseStatusModule = loadClosedCaseStatusModule();
|
||||
const getLifecycleStageStatusModule = loadGetLifecycleStageStatusModule();
|
||||
const getLifecycleStageIndexModule = loadGetLifecycleStageIndexModule();
|
||||
const mapAppealTypeModule = loadMapAppealTypeModule();
|
||||
const mapSpecialistProcessStageTypeModule =
|
||||
loadMapSpecialistProcessStageTypeModule();
|
||||
const getStageCaseTypeKeyModule = loadGetStageCaseTypeKeyModule();
|
||||
const stageCatalogueModule = loadStageCatalogueModule();
|
||||
const getStagesForCaseTypeKeyModule = loadGetStagesForCaseTypeKeyModule();
|
||||
const stageHelperModule = loadStageHelperModule();
|
||||
|
||||
const { getLifecycleStageStatus } = getLifecycleStageStatusModule;
|
||||
const { getLifecycleStageIndex } = getLifecycleStageIndexModule;
|
||||
const { getStagesForAppealType, stagesByCaseType } = stageHelperModule;
|
||||
|
||||
test("returns complete when index is less than current index", () => {
|
||||
assert.strictEqual(getLifecycleStageStatus(2, 5), "complete");
|
||||
});
|
||||
|
||||
test("returns in-progress when index matches current index", () => {
|
||||
assert.strictEqual(getLifecycleStageStatus(5, 5), "in-progress");
|
||||
});
|
||||
|
||||
test("returns not-started when index is greater than current index", () => {
|
||||
assert.strictEqual(getLifecycleStageStatus(6, 5), "not-started");
|
||||
});
|
||||
|
||||
test("preserves current all-not-started behaviour when currentIndex is -1", () => {
|
||||
assert.strictEqual(getLifecycleStageStatus(0, -1), "not-started");
|
||||
assert.strictEqual(getLifecycleStageStatus(4, -1), "not-started");
|
||||
assert.strictEqual(getLifecycleStageStatus(-3, -1), "not-started");
|
||||
});
|
||||
|
||||
test("preserves current comparison behaviour for unknown and negative indexes", () => {
|
||||
assert.strictEqual(getLifecycleStageStatus(-2, 3), "complete");
|
||||
assert.strictEqual(getLifecycleStageStatus(-2, -2), "in-progress");
|
||||
assert.strictEqual(getLifecycleStageStatus(3, -2), "not-started");
|
||||
});
|
||||
|
||||
test("preserves current null and undefined coercion behaviour", () => {
|
||||
assert.strictEqual(getLifecycleStageStatus(null, 2), "complete");
|
||||
assert.strictEqual(getLifecycleStageStatus(2, null), "not-started");
|
||||
assert.strictEqual(getLifecycleStageStatus(null, null), "in-progress");
|
||||
assert.strictEqual(getLifecycleStageStatus(undefined, 2), "not-started");
|
||||
assert.strictEqual(getLifecycleStageStatus(2, undefined), "not-started");
|
||||
assert.strictEqual(
|
||||
getLifecycleStageStatus(undefined, undefined),
|
||||
"in-progress"
|
||||
);
|
||||
});
|
||||
|
||||
test("integration parity preserves lifecycle outputs after status extraction", () => {
|
||||
const scenarios = [
|
||||
[846040000, 846040018, undefined],
|
||||
[846040011, 846040006, undefined],
|
||||
[846040004, 846040021, undefined],
|
||||
[846040010, 846040049, undefined],
|
||||
[846040015, 846040032, 846040000],
|
||||
[846040000, 846040059, undefined],
|
||||
[846040000, 999999999, undefined]
|
||||
];
|
||||
|
||||
const baselineGetStagesForAppealType = (
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
) => {
|
||||
const stages = normalizeForAssertion(
|
||||
getStagesForAppealType(caseType, currentStageId, specialistProcess)
|
||||
).map(({ status, ...stage }) => stage);
|
||||
const currentIndex = getLifecycleStageIndex(stages, currentStageId);
|
||||
|
||||
return stages.map((stage, index) => ({
|
||||
...stage,
|
||||
status:
|
||||
currentIndex === -1
|
||||
? "not-started"
|
||||
: index < currentIndex
|
||||
? "complete"
|
||||
: index === currentIndex
|
||||
? "in-progress"
|
||||
: "not-started"
|
||||
}));
|
||||
};
|
||||
|
||||
for (const [caseType, currentStageId, specialistProcess] of scenarios) {
|
||||
assert.deepStrictEqual(
|
||||
normalizeForAssertion(
|
||||
getStagesForAppealType(
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
),
|
||||
normalizeForAssertion(
|
||||
baselineGetStagesForAppealType(
|
||||
caseType,
|
||||
currentStageId,
|
||||
specialistProcess
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
for (const currentTest of tests) {
|
||||
await currentTest.fn();
|
||||
passed += 1;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Phase 22 case-lifecycle-stage-status tests passed (${passed}/${tests.length}).`
|
||||
);
|
||||
};
|
||||
|
||||
module.exports = run;
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user