diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index 9c0af5ec..e93dabc2 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -516,3 +516,37 @@ Validation: Follow-ups: - Remaining optional cleanup in these handlers is dead import/unused local pruning (non-behavioral) if we want a final low-risk tidy pass. + +--- + +### CL-013: TASK22224 completion-message route parity closure slice + +date: 2026-03-24 +author: Cline +scope: `pages/api/file/createappealcompletemessage_api.js`, `tests/phase21/file-handler-contract.test.cjs` +type: change +rationale: Continue next requested slice by closing the final promise-chain parity outlier in file completion-message flow and strengthening phase21 contract coverage. +impact: Improves maintainability and async error hygiene while preserving route behavior and existing error contracts. +status: completed + +Summary: + +- `pages/api/file/createappealcompletemessage_api.js` + - replaced inline `.catch(...)` on fire-and-forget `updateAccount(...)` with explicit async IIFE + `try/catch` and `void` invocation + - preserved non-blocking behavior and logging semantics for account-update failure path + - preserved primary route contracts and success payload (`{ status: "success" }`) +- phase21 file contract tests expanded: + - success path for encoded hash candidate on `createappealcompletemessage_api` + - dependency-failure contract assertion for `CREATE_APPEAL_COMPLETE_MESSAGE_FAILED` + +Validation: + +- `node tests/phase21/api-contract-slice1.test.cjs` -> pass + - helper: 4/4 + - file-handler: 53/53 + - email-handler: 12/12 + - endpoint-handler: 152/152 + +Follow-ups: + +- Optional final low-risk tidy sweep: remove dead imports/unused locals in legacy file handlers now that contract hardening stream is functionally complete. diff --git a/pages/api/file/createappealcompletemessage_api.js b/pages/api/file/createappealcompletemessage_api.js index 23455b6b..17c43b1e 100644 --- a/pages/api/file/createappealcompletemessage_api.js +++ b/pages/api/file/createappealcompletemessage_api.js @@ -115,15 +115,19 @@ ApiProxy.get(async (req, res) => { contactId = contactId.match(/\(([^)]+)\)/); if (typeofinvolvement != 846040000) { - updateAccount( - contactId[1], - { - pinswg_typeofinvolvement: 846040001 - }, - true - ).catch((error) => { - consoleLogger(error); - }); + void (async () => { + try { + await updateAccount( + contactId[1], + { + pinswg_typeofinvolvement: 846040001 + }, + true + ); + } catch (error) { + consoleLogger(error); + } + })(); } await createCaseCompleteMessage(containerName, tempCaseRef); diff --git a/tests/phase21/file-handler-contract.test.cjs b/tests/phase21/file-handler-contract.test.cjs index 8306a0f4..587cd8ad 100644 --- a/tests/phase21/file-handler-contract.test.cjs +++ b/tests/phase21/file-handler-contract.test.cjs @@ -694,6 +694,98 @@ test("createappealcompletemessageproxy dependency failure returns CREATE_APPEAL_ ); }); +test("createappealcompletemessage_api accepts encoded hash variant and returns success", async () => { + const mod = loadModule( + "pages/api/file/createappealcompletemessage_api.js", + { + nextConnect: createNextConnectMock(), + middleware: () => {}, + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + hashAPIPath: (queryPath) => + queryPath.includes("tempcaseref=TMP%2F1") + ? "&hash=expected" + : "&hash=other", + getProgressBlobs: async () => ({ path: "TMP/1/progress.json" }), + downloadProgressFile: async (_container, path) => { + if (path.includes("_case.json")) { + return { + "customerid_contact@odata.bind": + "/contacts(00000000-0000-0000-0000-000000000001)" + }; + } + + return { + pinswg_siteaddressline1: "Line 1", + pinswg_siteaddressline2: "", + pinswg_siteaddresstown: "Town", + pinswg_siteaddresscounty: "County", + pinswg_siteaddresspostcode: "CF1 1AA", + pinswg_lpaapplicationreference: "LPA-1", + pinswg_developmentdescription: "Desc", + pinswg_name: "tmp" + }; + }, + createBlob: async () => ({}), + getCaseBlob: async () => ({}), + createCaseCompleteMessage: async () => ({}), + updateAccount: async () => ({}), + consoleLogger: () => {}, + _: { isEmpty: (v) => v === undefined || v === null || v === "" } + } + ); + + const req = { + query: { + container: "cont/1", + tempcaseref: "TMP/1", + inv: "846040001", + hash: "expected" + } + }; + const res = createRes(); + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 200); + assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { + status: "success" + }); +}); + +test("createappealcompletemessage_api dependency failure returns CREATE_APPEAL_COMPLETE_MESSAGE_FAILED", async () => { + const mod = loadModule( + "pages/api/file/createappealcompletemessage_api.js", + { + nextConnect: createNextConnectMock(), + middleware: () => {}, + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + hashAPIPath: () => "&hash=expected", + getProgressBlobs: async () => { + throw new Error("progress failed"); + }, + consoleLogger: () => {}, + _: { isEmpty: () => true } + } + ); + + const req = { + query: { + container: "c1", + tempcaseref: "TMP1", + hash: "expected" + } + }; + const res = createRes(); + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "CREATE_APPEAL_COMPLETE_MESSAGE_FAILED" + ); +}); + test("createcaseinvolvement returns record exists on 412 conflict", async () => { const mod = loadModule("pages/api/file/createcaseinvolvement_api.js", { respondError: respondErrorMock,