const fs = require("fs"); const path = require("path"); const vm = require("vm"); const assert = require("assert"); const rootDir = path.resolve(__dirname, "..", ".."); const loadSelectQueryTypesModule = (relayPath) => { const filePath = path.join(rootDir, "actions", "selectQueryTypes.js"); let source = fs.readFileSync(filePath, "utf8"); source = source.replace( /export const\s+getSelectQuery\s*=\s*/, "const getSelectQuery = " ); source += ` module.exports = { getSelectQuery }; `; const context = { module: { exports: {} }, exports: {}, require, process: { env: { RELAYPATH: relayPath } } }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const tests = []; const test = (name, fn) => tests.push({ name, fn }); const splitFields = (query) => { assert.ok( query.startsWith("&$select="), `Expected query to start with &$select= but received: ${query}` ); return query .replace(/^&\$select=/, "") .split(",") .map((field) => field.trim()) .filter(Boolean); }; const getFieldSet = (appealTypeName, relayPath) => new Set( splitFields( loadSelectQueryTypesModule(relayPath).getSelectQuery(appealTypeName) ) ); const assertHasFields = (fieldSet, expectedFields, label) => { for (const field of expectedFields) { assert.ok(fieldSet.has(field), `${label}: expected field \`${field}\``); } }; test("planning-style family preserves common base, location, decision, and status fields across representative bundles", () => { const s78Dev = getFieldSet("pinswg_planningappeals78s", "dev-pedw-hc"); const rows = getFieldSet("pinswg_rows"); const householder = getFieldSet("pinswg_householderappealhases"); const planningBaseFamily = [ "modifiedon", "_pinswg_appellant_value", "pinswg_name", "pinswg_dateeventrequested", "statuscode" ]; for (const [label, fields] of [ ["S78 dev", s78Dev], ["ROW", rows], ["Householder", householder] ]) { assertHasFields(fields, planningBaseFamily, label); } assertHasFields( s78Dev, [ "pinswg_siteaddressline1", "pinswg_siteaddressline2", "pinswg_siteaddresspostcode", "pinswg_siteaddresstown", "pinswg_siteaddresscounty", "pinswg_casedecisiondate", "pinswg_dicision" ], "S78 dev planning family" ); assertHasFields( rows, [ "pinswg_siteaddressline1", "pinswg_siteaddressline2", "pinswg_siteaddresspostcode", "pinswg_siteaddresstown", "pinswg_casedecisiondate", "pinswg_decision" ], "ROW planning family" ); assertHasFields( householder, [ "pinswg_siteaddressline1", "pinswg_siteaddressline2", "pinswg_siteaddresspostcode", "pinswg_siteaddresstown", "pinswg_casedecisiondate", "pinswg_decision" ], "Householder planning family" ); }); test("representation-window family preserves current start and end date variants without normalization", () => { const rows = getFieldSet("pinswg_rows"); const enforcementListed = getFieldSet( "pinswg_enforcementlistedbuildingconservationaps" ); const dns = getFieldSet("pinswg_dnses"); const callIns = getFieldSet("pinswg_callinss77s"); assertHasFields( rows, [ "pinswg_startdate", "pinswg_statementduedate", "pinswg_finalcommentsduedate" ], "ROW representation-window family" ); assertHasFields( enforcementListed, [ "pinswg_startdates", "pinswg_statementduedate", "pinswg_finalcommentsduedate" ], "Enforcement listed representation-window family" ); assertHasFields( dns, [ "pinswg_applicationacceptedasvalid", "pinswg_endofrepresentationperiod" ], "DNS representation-window family" ); assertHasFields( callIns, [ "pinswg_startdate", "pinswg_statementsduedate", "pinswg_finalcommentsduedate" ], "Call-ins representation-window family" ); assert.ok( !dns.has("pinswg_startdate") && !dns.has("pinswg_startdates"), "DNS should preserve infrastructure-style start-date variant rather than planning-style start fields" ); }); test("specialist-process family preserves intentional field-variant divergence across bundles", () => { const rows = getFieldSet("pinswg_rows"); const adverts = getFieldSet("pinswg_advertses"); const hedges = getFieldSet( "pinswg_hedgeshedgerowstreepreservationreplacems" ); const householder = getFieldSet("pinswg_householderappealhases"); assert.ok( rows.has("pinswg_specialistcaseprocess") && !rows.has("pinswg_speacialistcaseprocess"), "ROW should preserve canonical-only specialist-process selection" ); assert.ok( adverts.has("pinswg_speacialistcaseprocess") && !adverts.has("pinswg_specialistcaseprocess"), "Adverts should preserve misspelled-only specialist-process selection" ); assert.ok( hedges.has("pinswg_specialistcaseprocess") && !hedges.has("pinswg_speacialistcaseprocess"), "Hedges should preserve canonical-only specialist-process selection" ); assert.ok( !householder.has("pinswg_specialistcaseprocess") && !householder.has("pinswg_speacialistcaseprocess"), "Householder should preserve absence of specialist-process fields" ); }); test("infrastructure-style family preserves project-oriented fields and schema variants across representative bundles", () => { const dns = getFieldSet("pinswg_dnses"); const harbour = getFieldSet("pinswg_harbourrevisionorders"); const electricity = getFieldSet("pinswg_electricityacts"); const commonInfrastructureFields = [ "pinswg_eiarequired", "pinswg_name", "pinswg_addressline1", "pinswg_addresstown", "pinswg_postcode", "pinswg_applicationacceptedasvalid", "pinswg_endofrepresentationperiod", "pinswg_projectlocation", "pinswg_recommendation", "pinswg_reportdueby", "pinswg_validnoticeofintentiontosubmitanapplicati", "pinswg_writtenacceptanceofnotification", "pinswg_startdateofevent", "pinswg_typeofevent", "statuscode" ]; for (const [label, fields] of [ ["DNS", dns], ["Harbour", harbour], ["Electricity", electricity] ]) { assertHasFields(fields, commonInfrastructureFields, label); } assert.ok( dns.has("pinswg_projectdescription") && !dns.has("pinswg_projectdiscription"), "DNS should preserve canonical project description field" ); assert.ok( harbour.has("pinswg_projectdiscription") && !harbour.has("pinswg_projectdescription"), "Harbour should preserve misspelled project description variant" ); assert.ok( dns.has("pinswg_suspensionstartdates") && !dns.has("pinswg_suspensionstartdate"), "DNS should preserve plural suspension start field" ); assert.ok( harbour.has("pinswg_suspensionstartdate") && !harbour.has("pinswg_suspensionstartdates"), "Harbour should preserve singular suspension start field variant" ); assert.ok( electricity.has("modifiedon") && !dns.has("modifiedon"), "Electricity should preserve current modifiedon overlap while DNS preserves its current omission" ); }); test("authority and LPA family preserves current field distinctions rather than collapsing them", () => { const s78Dev = getFieldSet("pinswg_planningappeals78s", "dev-pedw-hc"); const rows = getFieldSet("pinswg_rows"); const dns = getFieldSet("pinswg_dnses"); assert.ok( s78Dev.has("_pinswg_localplanningauthority_value") && !s78Dev.has("_pinswg_associatedlpa_value"), "S78 should preserve local-planning-authority field variant" ); assert.ok( rows.has("_pinswg_associatedlpa_value") && rows.has("pinswg_relevantauthorityname"), "ROW should preserve overlap of associated LPA and relevant authority fields" ); assert.ok( dns.has("_pinswg_associatedlpa_value") && !dns.has("pinswg_relevantauthorityname"), "DNS should preserve associated LPA without planning-style relevant-authority field" ); }); test("consultation family is currently specific to SIPS-style profiles", () => { const sips = getFieldSet("pinswg_sipses"); const dns = getFieldSet("pinswg_dnses"); const rows = getFieldSet("pinswg_rows"); assertHasFields( sips, ["pinswg_consultationopen", "pinswg_consultationclose"], "SIPS consultation family" ); assert.ok( !dns.has("pinswg_consultationopen") && !dns.has("pinswg_consultationclose") && !rows.has("pinswg_consultationopen") && !rows.has("pinswg_consultationclose"), "Consultation fields should preserve their current absence from non-SIPS representative bundles" ); }); test("minimal-profile family preserves intentionally small bundles and divergence from larger families", () => { const nonValidation = getFieldSet("pinswg_nonvalidations"); const ldp = getFieldSet("pinswg_ldps"); const rows = getFieldSet("pinswg_rows"); assert.deepStrictEqual([...ldp], ["modifiedon", "pinswg_name"]); assertHasFields( nonValidation, [ "modifiedon", "_pinswg_appellant_value", "pinswg_name", "pinswg_siteaddressline1", "pinswg_siteaddresspostcode", "_pinswg_associatedlpa_value", "statuscode" ], "Nonvalidation minimal family" ); assert.ok( !nonValidation.has("pinswg_questionnaireduedate") && !nonValidation.has("pinswg_startdate") && !nonValidation.has("pinswg_applicationacceptedasvalid"), "Nonvalidation should preserve absence of representation-window and questionnaire family fields" ); assert.ok( rows.size > nonValidation.size && nonValidation.size > ldp.size, "Minimal bundles should preserve their intentionally smaller profiles relative to larger planning bundles" ); }); test("family overlaps and divergences are preserved across representative bundles", () => { const rows = getFieldSet("pinswg_rows"); const dns = getFieldSet("pinswg_dnses"); const sips = getFieldSet("pinswg_sipses"); const nonValidation = getFieldSet("pinswg_nonvalidations"); assert.ok( rows.has("pinswg_startdate") && rows.has("pinswg_finalcommentsduedate"), "Planning + representation overlap should be preserved for ROW" ); assert.ok( dns.has("pinswg_applicationacceptedasvalid") && dns.has("pinswg_endofrepresentationperiod") && dns.has("pinswg_projectlocation"), "Infrastructure + representation overlap should be preserved for DNS" ); assert.ok( sips.has("pinswg_consultationopen") && sips.has("pinswg_consultationclose") && !sips.has("pinswg_finalcommentsduedate"), "SIPS + consultation overlap and planning-style divergence should be preserved" ); assert.ok( !nonValidation.has("pinswg_projectlocation") && !nonValidation.has("pinswg_consultationopen") && !nonValidation.has("pinswg_questionnaireduedate"), "Minimal profiles should preserve divergence from infrastructure, consultation, and richer planning families" ); }); const run = async () => { let passed = 0; for (const currentTest of tests) { await currentTest.fn(); passed += 1; } console.log( `Phase 22 select-query-types field family tests passed (${passed}/${tests.length}).` ); }; module.exports = run; if (require.main === module) { run().catch((error) => { console.error(error); process.exit(1); }); }