TASK22224: harden getrepsblob against stale tag entries

This commit is contained in:
2026-03-23 17:37:03 +00:00
parent 8e0131d1ce
commit f484f87694
3 changed files with 109 additions and 5 deletions
+18 -5
View File
@@ -1384,14 +1384,27 @@ export const getRepsBlobs = async (containerName) => {
listOptions
)) {
const blobClient = containerClient.getBlobClient(blob.name);
//console.log("getreps blob:", blob);
blob.name.split("/")[2].indexOf("_rep.json") > 0 &&
blob.name.split("/")[2].indexOf("undefined") < 0 &&
// Filter out soft-deleted/stale tag entries and malformed names.
const namePart = blob.name.split("/")[2] ?? "";
if (
namePart.indexOf("_rep.json") <= 0 ||
namePart.indexOf("undefined") >= 0
)
continue;
try {
const properties = await blobClient.getProperties();
blobObj.push({
"name": blob.name.split("/")[2],
"name": namePart,
"path": blob.name,
"size": blobClient.getProperties().contentLength
"size": properties.contentLength
});
} catch (error) {
if (error?.statusCode === 404) continue;
throw error;
}
}
//console.log("blobObjwwwww:", blobObj);
+37
View File
@@ -256,3 +256,40 @@ Validation:
Follow-ups:
- Optional next slice: apply same bounded hash-canonicalization parity to remaining high-sensitivity file routes where mixed encoded/raw callers may exist (`getbloblist`, `getprogressobjblob`) and add regression cases to phase21.
---
### CL-007: TASK22224 getrepsblob stability hotfix after delete representation flow
date: 2026-03-23
author: Cline
scope: `actions/azurestorage.js` (`getRepsBlobs`), `tests/phase21/file-handler-contract.test.cjs`
type: change
rationale: Resolve reported runtime 400 (`GET_REPS_BLOB_FAILED`) after delete representation actions, caused by stale soft-deleted blob tag hits during representation blob enumeration.
impact: Prevents transient/stale Azure tag index entries from breaking representation retrieval, improving reliability of post-delete refresh without relaxing route security contracts.
status: completed
Summary:
- Hardened `getRepsBlobs(containerName)` in `actions/azurestorage.js`:
- fixed async misuse (`blobClient.getProperties().contentLength` without await)
- added existence/property guard with explicit `await blobClient.getProperties()`
- skips 404s (soft-deleted/stale tag index results) instead of throwing
- preserves behavior for non-404 failures (rethrow for proper error visibility)
- kept existing `_rep.json`/`undefined` name filtering intact
- Added phase21 contract coverage for `getrepsblob` route:
- success payload contract test
- dependency failure contract test (`GET_REPS_BLOB_FAILED`)
Validation:
- `node tests/phase21/file-handler-contract.test.cjs` -> pass (25/25)
- `node tests/phase21/api-contract-slice1.test.cjs` -> pass
- helper: 4/4
- file-handler: 25/25
- email-handler: 12/12
- endpoint-handler: 149/149
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.
@@ -253,6 +253,60 @@ test("deleteblobrep handler dependency failure returns DELETE_BLOB_REP_FAILED",
assert.strictEqual(res.state.jsonBody.error.code, "DELETE_BLOB_REP_FAILED");
});
test("getrepsblob handler success returns representations payload", async () => {
const mod = loadModule("pages/api/file/getrepsblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
consoleLogger: () => {},
getRepsBlobs: async () => [
{ path: "c/ref_rep.json", name: "ref_rep.json" }
],
downloadAllRepsFiles: async () => ({
"@odata.count": 1,
value: [{ id: "r1" }]
})
});
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)), {
"@odata.count": 1,
value: [{ id: "r1" }]
});
});
test("getrepsblob handler dependency failure returns GET_REPS_BLOB_FAILED", async () => {
const mod = loadModule("pages/api/file/getrepsblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
consoleLogger: () => {},
getRepsBlobs: async () => {
throw new Error("storage failure");
},
downloadAllRepsFiles: 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_REPS_BLOB_FAILED");
});
test("getbloblist handler returns INVALID_HASH for mismatch", async () => {
const mod = loadModule("pages/api/file/getbloblist.js", {
nextConnect: createNextConnectMock(),