TASK22224: harden getbloblist and getprogressobjblob parity

This commit is contained in:
2026-03-23 17:45:47 +00:00
parent c84287be3c
commit ab50430493
3 changed files with 206 additions and 35 deletions
+37 -19
View File
@@ -40,10 +40,9 @@ const ApiProxy = nextConnect();
ApiProxy.use(middleware);
ApiProxy.get(async (req, res) => {
var containerName = req.query.container;
var casefolderID = req.query.casefolderID;
var checkHash = req.query.hash;
const containerName = req.query.container;
const casefolderID = req.query.casefolderID;
const checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
@@ -60,13 +59,24 @@ ApiProxy.get(async (req, res) => {
});
}
var checkquerypath =
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
casefolderID;
const casefolderIDTrimmed = casefolderID.trim();
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
const hashCandidatePaths = [
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
casefolderIDTrimmed,
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderIDTrimmed)
];
const isHashValid = hashCandidatePaths.some(
(candidatePath) => hashAPIPath(candidatePath) == "&hash=" + checkHash
);
if (!isHashValid) {
return respondError(res, {
status: 400,
code: "INVALID_HASH",
@@ -74,16 +84,24 @@ ApiProxy.get(async (req, res) => {
});
}
const data =
casefolderID.split("/").length > 1
? await getRepsFilesBlobs(
containerName,
casefolderID.split("/")[0],
casefolderID.split("/")[1]
)
: await getBlobs(containerName, casefolderID);
try {
const data =
casefolderIDTrimmed.split("/").length > 1
? await getRepsFilesBlobs(
containerName,
casefolderIDTrimmed.split("/")[0],
casefolderIDTrimmed.split("/")[1]
)
: await getBlobs(containerName, casefolderIDTrimmed);
return respondSuccess(res, { "value": [data] });
return respondSuccess(res, { "value": [data] });
} catch (error) {
return respondError(res, {
status: 400,
code: "GET_BLOB_LIST_FAILED",
message: "Failed to retrieve blob list"
});
}
});
export const config = {
+37 -16
View File
@@ -12,10 +12,9 @@ const ApiProxy = nextConnect();
ApiProxy.use(middleware);
ApiProxy.get(async (req, res) => {
var containerName = req.query.container;
var casefolderID = req.query.casefolderID;
var checkHash = req.query.hash;
const containerName = req.query.container;
const casefolderID = req.query.casefolderID;
const checkHash = req.query.hash;
if (
typeof containerName === "undefined" ||
@@ -32,13 +31,24 @@ ApiProxy.get(async (req, res) => {
});
}
var checkquerypath =
"/api/file/getprogressobjblob?container=" +
containerName +
"&casefolderID=" +
casefolderID;
const casefolderIDTrimmed = casefolderID.trim();
if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) {
const hashCandidatePaths = [
"/api/file/getprogressobjblob?container=" +
containerName +
"&casefolderID=" +
casefolderIDTrimmed,
"/api/file/getprogressobjblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderIDTrimmed)
];
const isHashValid = hashCandidatePaths.some(
(candidatePath) => hashAPIPath(candidatePath) == "&hash=" + checkHash
);
if (!isHashValid) {
return respondError(res, {
status: 400,
code: "INVALID_HASH",
@@ -46,13 +56,24 @@ ApiProxy.get(async (req, res) => {
});
}
await getProgressBlobs(containerName, casefolderID)
.then((data) => {
return downloadProgressFile(containerName, data.path, casefolderID);
})
.then((data) => {
return respondSuccess(res, data);
try {
const progressBlob = await getProgressBlobs(
containerName,
casefolderIDTrimmed
);
const data = await downloadProgressFile(
containerName,
progressBlob.path,
casefolderIDTrimmed
);
return respondSuccess(res, data);
} catch (error) {
return respondError(res, {
status: 400,
code: "GET_PROGRESS_OBJ_BLOB_FAILED",
message: "Failed to retrieve progress blob"
});
}
});
export const config = {
@@ -377,6 +377,138 @@ test("getbloblist handler returns INVALID_HASH for mismatch", async () => {
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH");
});
test("getbloblist handler accepts encoded casefolder hash variant", async () => {
const mod = loadModule("pages/api/file/getbloblist.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: (queryPath) =>
queryPath.includes("casefolderID=CASE%2FSUB")
? "&hash=expected"
: "&hash=other",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getBlobs: async () => ({ file: "a.pdf" }),
getRepsFilesBlobs: async () => ({ file: "rep.pdf" })
});
const req = {
query: {
container: "c1",
casefolderID: "CASE/SUB",
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: [{ file: "rep.pdf" }]
});
});
test("getbloblist handler dependency failure returns GET_BLOB_LIST_FAILED", async () => {
const mod = loadModule("pages/api/file/getbloblist.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getBlobs: async () => {
throw new Error("blob lookup failed");
},
getRepsFilesBlobs: async () => ({ file: "rep.pdf" })
});
const req = {
query: { container: "c1", casefolderID: "f1", 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_BLOB_LIST_FAILED");
});
test("getprogressobjblob handler success returns payload", async () => {
const mod = loadModule("pages/api/file/getprogressobjblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getProgressBlobs: async () => ({ path: "CASE1/CASE1_appeal.json" }),
downloadProgressFile: async () => ({ id: "progress-1" })
});
const req = {
query: { container: "c1", casefolderID: "CASE1", 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)), {
id: "progress-1"
});
});
test("getprogressobjblob handler accepts encoded casefolder hash variant", async () => {
const mod = loadModule("pages/api/file/getprogressobjblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: (queryPath) =>
queryPath.includes("casefolderID=CASE%2FSUB")
? "&hash=expected"
: "&hash=other",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getProgressBlobs: async () => ({ path: "CASE/SUB/CASE_appeal.json" }),
downloadProgressFile: async () => ({ id: "progress-2" })
});
const req = {
query: {
container: "c1",
casefolderID: "CASE/SUB",
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)), {
id: "progress-2"
});
});
test("getprogressobjblob handler dependency failure returns GET_PROGRESS_OBJ_BLOB_FAILED", async () => {
const mod = loadModule("pages/api/file/getprogressobjblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getProgressBlobs: async () => {
throw new Error("progress lookup failed");
},
downloadProgressFile: async () => ({})
});
const req = {
query: { container: "c1", casefolderID: "CASE1", 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_PROGRESS_OBJ_BLOB_FAILED"
);
});
test("setupcontainer handler returns MISSING_REQUIRED_QUERY when ident missing", async () => {
const mod = loadModule("pages/api/file/setupcontainer.js", {
nextConnect: createNextConnectMock(),