From c84287be3cb23626ad4a39c776ebfc6adede879d Mon Sep 17 00:00:00 2001 From: robbond Date: Mon, 23 Mar 2026 17:42:25 +0000 Subject: [PATCH] TASK22224: harden awaiting-submission blob retrieval contract --- memory-bank/change-log.md | 38 ++++++++++++++ .../api/file/getawaitingsubmissionfromblob.js | 25 ++++++---- tests/phase21/file-handler-contract.test.cjs | 49 +++++++++++++++++++ 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index f9151e7f..a69d3829 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -293,3 +293,41 @@ Validation: Follow-ups: - Optional: add the same stale-tag existence guard pattern to any remaining Azure tag-list readers that still consume `findBlobsByTags` results without property existence verification. + +--- + +### CL-008: TASK22224 awaiting-submission route resilience parity hardening + +date: 2026-03-23 +author: Cline +scope: `pages/api/file/getawaitingsubmissionfromblob.js`, `tests/phase21/file-handler-contract.test.cjs` +type: change +rationale: Add explicit catch-path contract parity for awaiting-submission blob retrieval route so unexpected dependency failures return consistent, actionable error envelopes. +impact: Improves reliability/diagnostics for post-delete case refresh and aligns file-route error handling style without changing success payload contract or hash verification behavior. +status: completed + +Summary: + +- Refactored `getawaitingsubmissionfromblob` handler to structured `try/catch` flow. +- Preserved existing guard behavior: + - `MISSING_REQUIRED_QUERY` for missing container/hash + - `INVALID_HASH` for signature mismatch +- Added explicit dependency failure contract: + - `GET_AWAITING_SUBMISSION_BLOB_FAILED` (400) + - message: `Failed to retrieve awaiting submission blobs` +- Added phase21 coverage for this route: + - success payload pass-through contract + - dependency failure contract assertion + +Validation: + +- `node tests/phase21/file-handler-contract.test.cjs` -> pass (27/27) +- `node tests/phase21/api-contract-slice1.test.cjs` -> pass + - helper: 4/4 + - file-handler: 27/27 + - email-handler: 12/12 + - endpoint-handler: 149/149 + +Follow-ups: + +- Optional parity sweep: apply the same explicit catch-path contract pattern to remaining file routes that still rely on implicit promise-chain errors. diff --git a/pages/api/file/getawaitingsubmissionfromblob.js b/pages/api/file/getawaitingsubmissionfromblob.js index d8ca4315..085a8754 100644 --- a/pages/api/file/getawaitingsubmissionfromblob.js +++ b/pages/api/file/getawaitingsubmissionfromblob.js @@ -12,8 +12,8 @@ const ApiProxy = nextConnect(); ApiProxy.use(middleware); ApiProxy.get(async (req, res) => { - var containerName = req.query.container; - var checkHash = req.query.hash; + const containerName = req.query.container; + const checkHash = req.query.hash; if ( typeof containerName === "undefined" || @@ -28,7 +28,7 @@ ApiProxy.get(async (req, res) => { }); } - var checkquerypath = + const checkquerypath = "/api/file/getawaitingsubmissionfromblob?container=" + containerName; if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) { @@ -39,13 +39,20 @@ ApiProxy.get(async (req, res) => { }); } - const blobObj = await getAllProgressBlobs(containerName) - .then((data) => { - return downloadAllProgressFiles(containerName, data); - }) - .then((data) => { - return respondSuccess(res, data); + try { + const progressBlobs = await getAllProgressBlobs(containerName); + const data = await downloadAllProgressFiles( + containerName, + progressBlobs + ); + return respondSuccess(res, data); + } catch (error) { + return respondError(res, { + status: 400, + code: "GET_AWAITING_SUBMISSION_BLOB_FAILED", + message: "Failed to retrieve awaiting submission blobs" }); + } }); export const config = { diff --git a/tests/phase21/file-handler-contract.test.cjs b/tests/phase21/file-handler-contract.test.cjs index dc366e35..bae7431b 100644 --- a/tests/phase21/file-handler-contract.test.cjs +++ b/tests/phase21/file-handler-contract.test.cjs @@ -307,6 +307,55 @@ test("getrepsblob handler dependency failure returns GET_REPS_BLOB_FAILED", asyn assert.strictEqual(res.state.jsonBody.error.code, "GET_REPS_BLOB_FAILED"); }); +test("getawaitingsubmissionfromblob handler success returns payload", async () => { + const mod = loadModule("pages/api/file/getawaitingsubmissionfromblob.js", { + nextConnect: createNextConnectMock(), + middleware: () => {}, + hashAPIPath: () => "&hash=expected", + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getAllProgressBlobs: async () => [{ path: "c1/case1_appeal.json" }], + downloadAllProgressFiles: async () => ({ value: [{ id: "a1" }] }) + }); + + const req = { + query: { container: "c1", 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)), { + value: [{ id: "a1" }] + }); +}); + +test("getawaitingsubmissionfromblob handler dependency failure returns GET_AWAITING_SUBMISSION_BLOB_FAILED", async () => { + const mod = loadModule("pages/api/file/getawaitingsubmissionfromblob.js", { + nextConnect: createNextConnectMock(), + middleware: () => {}, + hashAPIPath: () => "&hash=expected", + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getAllProgressBlobs: async () => { + throw new Error("progress fetch failed"); + }, + downloadAllProgressFiles: async () => ({}) + }); + + const req = { + query: { container: "c1", hash: "expected" } + }; + const res = createRes(); + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "GET_AWAITING_SUBMISSION_BLOB_FAILED" + ); +}); + test("getbloblist handler returns INVALID_HASH for mismatch", async () => { const mod = loadModule("pages/api/file/getbloblist.js", { nextConnect: createNextConnectMock(),