TASK22224: harden awaiting-submission blob retrieval contract
This commit is contained in:
@@ -293,3 +293,41 @@ Validation:
|
|||||||
Follow-ups:
|
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.
|
- 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.
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ const ApiProxy = nextConnect();
|
|||||||
ApiProxy.use(middleware);
|
ApiProxy.use(middleware);
|
||||||
|
|
||||||
ApiProxy.get(async (req, res) => {
|
ApiProxy.get(async (req, res) => {
|
||||||
var containerName = req.query.container;
|
const containerName = req.query.container;
|
||||||
var checkHash = req.query.hash;
|
const checkHash = req.query.hash;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof containerName === "undefined" ||
|
typeof containerName === "undefined" ||
|
||||||
@@ -28,7 +28,7 @@ ApiProxy.get(async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkquerypath =
|
const checkquerypath =
|
||||||
"/api/file/getawaitingsubmissionfromblob?container=" + containerName;
|
"/api/file/getawaitingsubmissionfromblob?container=" + containerName;
|
||||||
|
|
||||||
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
|
||||||
@@ -39,13 +39,20 @@ ApiProxy.get(async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const blobObj = await getAllProgressBlobs(containerName)
|
try {
|
||||||
.then((data) => {
|
const progressBlobs = await getAllProgressBlobs(containerName);
|
||||||
return downloadAllProgressFiles(containerName, data);
|
const data = await downloadAllProgressFiles(
|
||||||
})
|
containerName,
|
||||||
.then((data) => {
|
progressBlobs
|
||||||
return respondSuccess(res, data);
|
);
|
||||||
|
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 = {
|
export const config = {
|
||||||
|
|||||||
@@ -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");
|
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 () => {
|
test("getbloblist handler returns INVALID_HASH for mismatch", async () => {
|
||||||
const mod = loadModule("pages/api/file/getbloblist.js", {
|
const mod = loadModule("pages/api/file/getbloblist.js", {
|
||||||
nextConnect: createNextConnectMock(),
|
nextConnect: createNextConnectMock(),
|
||||||
|
|||||||
Reference in New Issue
Block a user