const fs = require("fs"); const path = require("path"); const vm = require("vm"); const assert = require("assert"); const rootDir = path.resolve(__dirname, "..", ".."); const loadBuildRepsArrRulesModule = () => { const filePath = path.join( rootDir, "components", "case", "representation", "utils", "buildRepsArrRules.js" ); let source = fs.readFileSync(filePath, "utf8"); source = source.replace(/export const\s+/g, "const "); source += ` module.exports = { APPEAL_TYPES, SPECIALIST_PROCESS, INVOLVEMENT_TYPES, buildRepresentationContext, buildRepsArrFromContext }; `; const context = { module: { exports: {} }, exports: {}, require, Date }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const tests = []; const test = (name, fn) => tests.push({ name, fn }); const mod = loadBuildRepsArrRulesModule(); const { APPEAL_TYPES, SPECIALIST_PROCESS, INVOLVEMENT_TYPES, buildRepresentationContext, buildRepsArrFromContext } = mod; const BASE_DETAILS = { pinswg_startdate: "2026-04-01T00:00:00.000Z", pinswg_statementduedate: "2026-04-30T00:00:00.000Z", pinswg_finalcommentsduedate: "2026-05-25T00:00:00.000Z" }; const baseInput = (overrides = {}) => ({ detailsObj: BASE_DETAILS, appealType: 846040000, specialistProcess: SPECIALIST_PROCESS.WRITTEN_REPS, involvementType: 999, selectedCapacity: "appellant", isCaseOwner: false, isNRW: false, now: new Date("2026-04-10T10:00:00.000Z"), ...overrides }); const normalizeForAssertion = (value) => JSON.parse(JSON.stringify(value ?? null)); const buildOptions = (overrides = {}) => normalizeForAssertion( buildRepsArrFromContext( buildRepresentationContext(baseInput(overrides)) ) ); test("baseline truth-table: non-DNS non-SIPS across windows/capacities/ownership", () => { const capacities = [ { value: "appellant", hasFinalComments: true }, { value: "agent", hasFinalComments: true }, { value: "interestedparty", hasFinalComments: true }, { value: false, hasFinalComments: false }, { value: "", hasFinalComments: false }, { value: "unknown", hasFinalComments: false } ]; const cases = [ { label: "before start", now: "2026-03-31T10:00:00.000Z", expected: (c, isCaseOwner) => [] }, { label: "statement period", now: "2026-04-15T10:00:00.000Z", expected: (c, isCaseOwner) => (isCaseOwner ? [] : ["Statement"]) }, { label: "final comments period", now: "2026-05-10T10:00:00.000Z", expected: (c) => (c.hasFinalComments ? ["Final comments"] : []) }, { label: "after final comments due", now: "2026-05-26T10:00:00.000Z", expected: () => [] } ]; for (const capacity of capacities) { for (const scenario of cases) { for (const isCaseOwner of [true, false]) { const result = buildOptions({ selectedCapacity: capacity.value, isCaseOwner, now: new Date(scenario.now) }); assert.deepStrictEqual( result, scenario.expected(capacity, isCaseOwner), `Failed for ${scenario.label}, capacity=${capacity.value}, isCaseOwner=${isCaseOwner}` ); } } } }); test("LPA involvement: questionnaire and final comments windows", () => { assert.deepStrictEqual( buildOptions({ involvementType: INVOLVEMENT_TYPES.LPA, selectedCapacity: "unknown", now: new Date("2026-04-10T10:00:00.000Z") }), ["Questionnaire", "Statement"] ); assert.deepStrictEqual( buildOptions({ involvementType: INVOLVEMENT_TYPES.LPA, selectedCapacity: "unknown", now: new Date("2026-05-10T10:00:00.000Z") }), ["Questionnaire", "Final comments"] ); assert.deepStrictEqual( buildOptions({ involvementType: INVOLVEMENT_TYPES.LPA, selectedCapacity: "unknown", now: new Date("2026-05-26T10:00:00.000Z") }), [] ); }); test("questionnaire excluded appeal types for LPA", () => { const excluded = [846040019, 846040011, 846040015, 846040016]; for (const appealType of excluded) { const result = buildOptions({ appealType, involvementType: INVOLVEMENT_TYPES.LPA, now: new Date("2026-04-10T10:00:00.000Z") }); assert.strictEqual( result.includes("Questionnaire"), false, `Questionnaire should be excluded for ${appealType}` ); } }); test("householder: statements blocked; final comments depend on specialist process", () => { assert.deepStrictEqual( buildOptions({ appealType: APPEAL_TYPES.HOUSEHOLDER, selectedCapacity: "appellant", specialistProcess: SPECIALIST_PROCESS.HEARING, now: new Date("2026-04-15T10:00:00.000Z") }), [] ); assert.deepStrictEqual( buildOptions({ appealType: APPEAL_TYPES.HOUSEHOLDER, selectedCapacity: "appellant", specialistProcess: SPECIALIST_PROCESS.HEARING, now: new Date("2026-05-10T10:00:00.000Z") }), [] ); assert.deepStrictEqual( buildOptions({ appealType: APPEAL_TYPES.HOUSEHOLDER, selectedCapacity: "appellant", specialistProcess: SPECIALIST_PROCESS.WRITTEN_REPS, now: new Date("2026-05-10T10:00:00.000Z") }), ["Final comments"] ); }); test("advert appeal type: LPA written reps blocks statement, non-LPA non-owner can still submit statement", () => { assert.deepStrictEqual( buildOptions({ appealType: APPEAL_TYPES.ADVERT, involvementType: INVOLVEMENT_TYPES.LPA, specialistProcess: SPECIALIST_PROCESS.WRITTEN_REPS, now: new Date("2026-04-15T10:00:00.000Z") }), ["Questionnaire"] ); assert.deepStrictEqual( buildOptions({ appealType: APPEAL_TYPES.ADVERT, involvementType: 999, isCaseOwner: false, now: new Date("2026-04-15T10:00:00.000Z") }), ["Statement"] ); }); test("DNS rules: statement/LIR ownership behaviour and questionnaire exclusion", () => { const dnsDetails = { pinswg_applicationacceptedasvalid: "2026-04-01T00:00:00.000Z", pinswg_endofrepresentationperiod: "2026-05-25T00:00:00.000Z" }; const dnsOptions = (overrides = {}) => normalizeForAssertion( buildRepsArrFromContext( buildRepresentationContext( baseInput({ appealType: APPEAL_TYPES.DNS, detailsObj: dnsDetails, ...overrides }) ) ) ); assert.deepStrictEqual( dnsOptions({ involvementType: 999, isCaseOwner: false, now: new Date("2026-05-01T10:00:00.000Z") }), ["Statement"] ); assert.deepStrictEqual( dnsOptions({ involvementType: 999, isCaseOwner: true, now: new Date("2026-05-01T10:00:00.000Z") }), [] ); assert.deepStrictEqual( dnsOptions({ involvementType: INVOLVEMENT_TYPES.LPA, isCaseOwner: false, now: new Date("2026-05-01T10:00:00.000Z") }), ["Local Impact Report", "Statement", "Final comments"] ); assert.deepStrictEqual( dnsOptions({ involvementType: INVOLVEMENT_TYPES.LPA, isCaseOwner: true, now: new Date("2026-05-01T10:00:00.000Z") }), ["Final comments"] ); assert.strictEqual( dnsOptions({ involvementType: INVOLVEMENT_TYPES.LPA, isCaseOwner: false, now: new Date("2026-05-01T10:00:00.000Z") }).includes("Questionnaire"), false ); }); test("SIPS consultation behaviour is not date-gated", () => { const sips = (overrides = {}) => buildOptions({ appealType: APPEAL_TYPES.SIPS, ...overrides }); assert.deepStrictEqual( sips({ selectedCapacity: "appellant", isNRW: false }), ["Statement", "Consultation Response"] ); assert.deepStrictEqual(sips({ selectedCapacity: "agent", isNRW: false }), [ "Statement", "Consultation Response", "Local Impact Report" ]); assert.deepStrictEqual( sips({ selectedCapacity: "interestedparty", isNRW: true }), [ "Statement", "Consultation Response", "Local Impact Report", "Marine Impact Report" ] ); assert.deepStrictEqual( sips({ selectedCapacity: "appellant", now: new Date("2026-06-01T10:00:00.000Z") }), ["Consultation Response"] ); }); test("deduplication and ordering are stable", () => { const result = buildOptions({ appealType: APPEAL_TYPES.SIPS, involvementType: INVOLVEMENT_TYPES.LPA, selectedCapacity: "agent", isNRW: true, isCaseOwner: false, now: new Date("2026-05-10T10:00:00.000Z") }); assert.deepStrictEqual(result, [ "Questionnaire", "Final comments", "Consultation Response", "Local Impact Report", "Marine Impact Report" ]); assert.strictEqual(new Set(result).size, result.length); }); test("invalid or missing dates suppress date-gated options but SIPS consultation persists", () => { assert.deepStrictEqual( normalizeForAssertion( buildRepsArrFromContext( buildRepresentationContext( baseInput({ detailsObj: { pinswg_statementduedate: "2026-04-30T00:00:00.000Z", pinswg_finalcommentsduedate: "2026-05-25T00:00:00.000Z" } }) ) ) ), [] ); assert.deepStrictEqual( normalizeForAssertion( buildRepsArrFromContext( buildRepresentationContext( baseInput({ detailsObj: { pinswg_startdate: "2026-04-01T00:00:00.000Z", pinswg_finalcommentsduedate: "2026-05-25T00:00:00.000Z" } }) ) ) ), [] ); assert.deepStrictEqual( normalizeForAssertion( buildRepsArrFromContext( buildRepresentationContext( baseInput({ detailsObj: { pinswg_startdate: "2026-04-01T00:00:00.000Z", pinswg_statementduedate: "2026-04-30T00:00:00.000Z" }, involvementType: INVOLVEMENT_TYPES.LPA, now: new Date("2026-05-10T10:00:00.000Z") }) ) ) ), [] ); assert.deepStrictEqual( normalizeForAssertion( buildRepsArrFromContext( buildRepresentationContext( baseInput({ appealType: APPEAL_TYPES.SIPS, detailsObj: {}, selectedCapacity: "agent", isNRW: true, now: new Date("2026-06-20T10:00:00.000Z") }) ) ) ), ["Consultation Response", "Local Impact Report", "Marine Impact Report"] ); }); test("capacity normalization coverage", () => { const inFinalWindow = { now: new Date("2026-05-10T10:00:00.000Z") }; assert.deepStrictEqual( buildOptions({ selectedCapacity: "appellant", ...inFinalWindow }), ["Final comments"] ); assert.deepStrictEqual( buildOptions({ selectedCapacity: "Appellant", ...inFinalWindow }), ["Final comments"] ); assert.deepStrictEqual( buildOptions({ selectedCapacity: "Interested Party", ...inFinalWindow }), ["Final comments"] ); assert.deepStrictEqual( buildOptions({ selectedCapacity: "interested-party", ...inFinalWindow }), ["Final comments"] ); assert.deepStrictEqual( buildOptions({ selectedCapacity: "agent", ...inFinalWindow }), ["Final comments"] ); }); const run = async () => { let passed = 0; for (const currentTest of tests) { await currentTest.fn(); passed += 1; } console.log( `Phase 22 representation buildRepsArr rules tests passed (${passed}/${tests.length}).` ); }; module.exports = run; if (require.main === module) { run().catch((error) => { console.error(error); process.exit(1); }); }