diff --git a/pages/api/file/getrepsblob.js b/pages/api/file/getrepsblob.js index a059f5a2..45dff296 100644 --- a/pages/api/file/getrepsblob.js +++ b/pages/api/file/getrepsblob.js @@ -33,7 +33,6 @@ import { downloadAllRepsFiles, getRepsBlobs } from "../../../actions/azurestorage"; -import _ from "lodash"; import nextConnect from "next-connect"; import middleware from "../middleware/middleware"; import { hashAPIPath } from "../../../actions/core/hash"; @@ -44,10 +43,8 @@ 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 checkHash = req.query.hash; if ( typeof containerName === "undefined" || @@ -62,9 +59,16 @@ ApiProxy.get(async (req, res) => { }); } - var checkquerypath = "/api/file/getrepsblob?container=" + containerName; + const hashCandidatePaths = [ + "/api/file/getrepsblob?container=" + containerName, + "/api/file/getrepsblob?container=" + encodeURIComponent(containerName) + ]; - if (hashAPIPath(checkquerypath) != "&hash=" + checkHash) { + const isHashValid = hashCandidatePaths.some( + (candidatePath) => hashAPIPath(candidatePath) == "&hash=" + checkHash + ); + + if (!isHashValid) { return respondError(res, { status: 400, code: "INVALID_HASH", @@ -72,23 +76,19 @@ ApiProxy.get(async (req, res) => { }); } - const blobObj = await getRepsBlobs(containerName) - .then(async (data) => { - return data; - }) - .then(async (data) => { - let result = await downloadAllRepsFiles(containerName, data); - res.setHeader("Cache-Control", "no-store"); - return respondSuccess(res, result); - }) - .catch((error) => { - consoleLogger(error); - return respondError(res, { - status: 400, - code: "GET_REPS_BLOB_FAILED", - message: "Failed to retrieve representation blobs" - }); + try { + const repsBlobs = await getRepsBlobs(containerName); + const result = await downloadAllRepsFiles(containerName, repsBlobs); + res.setHeader("Cache-Control", "no-store"); + return respondSuccess(res, result); + } catch (error) { + consoleLogger(error); + return respondError(res, { + status: 400, + code: "GET_REPS_BLOB_FAILED", + message: "Failed to retrieve representation blobs" }); + } }); export default ApiProxy; diff --git a/pages/api/file/upload.js b/pages/api/file/upload.js index 80038527..70072889 100644 --- a/pages/api/file/upload.js +++ b/pages/api/file/upload.js @@ -1,8 +1,4 @@ -import { - createBlob, - createRepBlob, - uploadFile -} from "../../../actions/azurestorage"; +import { createBlob, createRepBlob } from "../../../actions/azurestorage"; import { hashAPIPath } from "../../../actions/core/hash"; import { respondError, respondSuccess } from "../middleware/apiResponse"; @@ -13,7 +9,7 @@ const ApiProxy = nextConnect(); ApiProxy.use(middleware); ApiProxy.post(async (req, res) => { - var checkHash = req.query.hash; + const checkHash = req.query.hash; if (typeof checkHash === "undefined" || checkHash.length === 0) { return respondError(res, { @@ -23,9 +19,12 @@ ApiProxy.post(async (req, res) => { }); } - var checkquerypath = "/api/file/upload"; + const hashCandidatePaths = ["/api/file/upload", "/api/file/upload?"]; + const isHashValid = hashCandidatePaths.some( + (candidatePath) => hashAPIPath(candidatePath) == "?hash=" + checkHash + ); - if (hashAPIPath(checkquerypath) != "?hash=" + checkHash) { + if (!isHashValid) { return respondError(res, { status: 400, code: "INVALID_HASH", @@ -51,21 +50,19 @@ ApiProxy.post(async (req, res) => { }); } - return ( - repOrAppeal + try { + const data = await (repOrAppeal ? createRepBlob(appealData, containerID, casefolderID) - : createBlob(appealData, containerID, casefolderID) - ) - .then((data) => { - return respondSuccess(res, { data }); - }) - .catch(() => { - return respondError(res, { - status: 400, - code: "UPLOAD_FAILED", - message: "Failed to upload blob" - }); + : createBlob(appealData, containerID, casefolderID)); + + return respondSuccess(res, { data }); + } catch { + return respondError(res, { + status: 400, + code: "UPLOAD_FAILED", + message: "Failed to upload blob" }); + } }); export const config = { diff --git a/pages/api/file/uploadsinglefile.js b/pages/api/file/uploadsinglefile.js index d5c0a99c..bef3de5a 100644 --- a/pages/api/file/uploadsinglefile.js +++ b/pages/api/file/uploadsinglefile.js @@ -1,8 +1,4 @@ -import { - createBlob, - createRepBlob, - uploadSingleFile -} from "../../../actions/azurestorage"; +import { uploadSingleFile } from "../../../actions/azurestorage"; import nextConnect from "next-connect"; import middleware from "../middleware/middleware"; @@ -37,7 +33,7 @@ const ApiProxy = nextConnect(); ApiProxy.use(middleware); ApiProxy.post(async (req, res) => { - var checkHash = req.query.hash; + const checkHash = req.query.hash; if (typeof checkHash === "undefined" || checkHash.length === 0) { return respondError(res, { @@ -47,9 +43,15 @@ ApiProxy.post(async (req, res) => { }); } - var checkquerypath = "/api/file/uploadsinglefile"; + const hashCandidatePaths = [ + "/api/file/uploadsinglefile", + "/api/file/uploadsinglefile?" + ]; + const isHashValid = hashCandidatePaths.some( + (candidatePath) => hashAPIPath(candidatePath) == "?hash=" + checkHash + ); - if (hashAPIPath(checkquerypath) != "?hash=" + checkHash) { + if (!isHashValid) { return respondError(res, { status: 400, code: "INVALID_HASH", @@ -87,8 +89,8 @@ ApiProxy.post(async (req, res) => { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" ]; - var allowedFilesFormData = {}; - var invalidFiles = []; // To store the names of invalid files + const allowedFilesFormData = {}; + const invalidFiles = []; // To store the names of invalid files for (const [fileName, fileDetails] of Object.entries(uploadedFiles)) { const file = fileDetails[0]; diff --git a/tests/phase21/file-handler-contract.test.cjs b/tests/phase21/file-handler-contract.test.cjs index 6287299f..d9520adf 100644 --- a/tests/phase21/file-handler-contract.test.cjs +++ b/tests/phase21/file-handler-contract.test.cjs @@ -51,6 +51,38 @@ test("upload handler returns INVALID_HASH for wrong hash", async () => { assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH"); }); +test("upload handler accepts alternate canonical hash candidate", async () => { + const mod = loadModule("pages/api/file/upload.js", { + nextConnect: createNextConnectMock(), + middleware: () => {}, + hashAPIPath: (queryPath) => + queryPath === "/api/file/upload?" + ? "?hash=expected" + : "?hash=other", + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + createBlob: async () => ({ id: "blob-alt" }), + createRepBlob: async () => ({ id: "rep-alt" }) + }); + + const req = { + query: { hash: "expected" }, + body: { + appealData: { key: "value" }, + containerID: ["c1"], + casefolderID: ["f1"], + repOrAppeal: false + } + }; + const res = createRes(); + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 200); + assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { + data: { id: "blob-alt" } + }); +}); + test("deleteblob handler returns MISSING_REQUIRED_QUERY when blobname missing", async () => { const mod = loadModule("pages/api/file/deleteblob.js", { nextConnect: createNextConnectMock(), @@ -375,6 +407,84 @@ test("getrepsblob handler dependency failure returns GET_REPS_BLOB_FAILED", asyn assert.strictEqual(res.state.jsonBody.error.code, "GET_REPS_BLOB_FAILED"); }); +test("getrepsblob handler accepts encoded container hash variant", async () => { + const mod = loadModule("pages/api/file/getrepsblob.js", { + nextConnect: createNextConnectMock(), + middleware: () => {}, + hashAPIPath: (queryPath) => + queryPath.includes("container=cont%2F1") + ? "&hash=expected" + : "&hash=other", + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + consoleLogger: () => {}, + getRepsBlobs: async () => [{ path: "cont/rep1.json" }], + downloadAllRepsFiles: async () => ({ value: [{ id: "r-encoded" }] }) + }); + + const req = { + query: { container: "cont/1", 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: "r-encoded" }] + }); +}); + +test("uploadsinglefile handler returns HASH_REQUIRED when hash missing", async () => { + const mod = loadModule("pages/api/file/uploadsinglefile.js", { + nextConnect: createNextConnectMock(), + middleware: () => {}, + hashAPIPath: () => "?hash=expected", + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + consoleLogger: () => {}, + uploadSingleFile: async () => ({ uploaded: 0 }), + fileTypeFromBuffer: async () => ({ mime: "application/pdf" }), + fs: { readFileSync: () => Buffer.from("x") } + }); + + const req = { query: {}, body: {}, files: {} }; + const res = createRes(); + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(res.state.jsonBody.error.code, "HASH_REQUIRED"); +}); + +test("uploadsinglefile handler dependency failure returns UPLOAD_SINGLE_FILE_FAILED", async () => { + const mod = loadModule("pages/api/file/uploadsinglefile.js", { + nextConnect: createNextConnectMock(), + middleware: () => {}, + hashAPIPath: () => "?hash=expected", + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + consoleLogger: () => {}, + uploadSingleFile: async () => { + throw new Error("upload failed"); + }, + fileTypeFromBuffer: async () => ({ mime: "application/pdf" }), + fs: { readFileSync: () => Buffer.from("x") } + }); + + const req = { + query: { hash: "expected" }, + body: { containerID: ["c1"], casefolderID: ["f1"] }, + files: {} + }; + const res = createRes(); + await mod.default.handler(req, res); + + assert.strictEqual(res.state.statusCode, 500); + assert.strictEqual( + res.state.jsonBody.error.code, + "UPLOAD_SINGLE_FILE_FAILED" + ); +}); + test("getawaitingsubmissionfromblob handler success returns payload", async () => { const mod = loadModule("pages/api/file/getawaitingsubmissionfromblob.js", { nextConnect: createNextConnectMock(),