TASK22224: harden getrepsblob against stale tag entries
This commit is contained in:
+18
-5
@@ -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);
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user