Files
pedwfrontend/tests/phase21/file-handler-contract.test.cjs
T

3310 lines
112 KiB
JavaScript

const assert = require("assert");
const fs = require("fs");
const path = require("path");
const {
loadModule,
createRes,
createNextConnectMock,
respondSuccessMock,
respondErrorMock
} = require("./_shared.cjs");
const tests = [];
const test = (name, fn) => tests.push({ name, fn });
test("upload handler returns HASH_REQUIRED for missing hash", async () => {
const mod = loadModule("pages/api/file/upload.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "c1" } }),
hashAPIPath: () => "?hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createBlob: async () => ({ ok: true }),
createRepBlob: async () => ({ ok: true })
});
const req = { query: {}, body: {} };
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("upload handler returns INVALID_HASH for wrong hash", async () => {
const mod = loadModule("pages/api/file/upload.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "c1" } }),
hashAPIPath: () => "?hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createBlob: async () => ({ ok: true }),
createRepBlob: async () => ({ ok: true })
});
const req = {
query: { hash: "wrong" },
body: { containerID: ["c1"], casefolderID: ["f1"] }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
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: () => {},
getSession: async () => ({ user: { id: "c1" } }),
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" }
});
});
const loadUploadAuthorization = (overrides = {}) => {
const calls = { createBlob: [], createRepBlob: [], hash: [] };
const mod = loadModule("pages/api/file/upload.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "user-container" } }),
hashAPIPath: (queryPath) => {
calls.hash.push(queryPath);
return "?hash=expected";
},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createBlob: async (...args) => {
calls.createBlob.push(args);
return { id: "appeal-blob" };
},
createRepBlob: async (...args) => {
calls.createRepBlob.push(args);
return { id: "representation-blob" };
},
...overrides
});
return { mod, calls };
};
const uploadAuthorizationRequest = (overrides = {}) => ({
query: { hash: "expected" },
body: {
appealData: [JSON.stringify({ key: "value" })],
containerID: ["user-container"],
casefolderID: ["CASE-1"],
repOrAppeal: ["false"]
},
files: {},
...overrides
});
const assertNoUploadWrites = (calls) => {
assert.strictEqual(calls.createBlob.length, 0);
assert.strictEqual(calls.createRepBlob.length, 0);
};
test("upload rejects a missing NextAuth session before HMAC or Azure write", async () => {
const loaded = loadUploadAuthorization({ getSession: async () => null });
const res = createRes();
await loaded.mod.default.handler(uploadAuthorizationRequest(), res);
assert.strictEqual(res.state.statusCode, 401);
assert.strictEqual(res.state.jsonBody.error.code, "UNAUTHENTICATED");
assert.strictEqual(loaded.calls.hash.length, 0);
assertNoUploadWrites(loaded.calls);
});
test("upload rejects an unusable NextAuth session before HMAC or Azure write", async () => {
const loaded = loadUploadAuthorization({
getSession: async () => ({ user: {} })
});
const res = createRes();
await loaded.mod.default.handler(uploadAuthorizationRequest(), res);
assert.strictEqual(res.state.statusCode, 401);
assert.strictEqual(res.state.jsonBody.error.code, "UNAUTHENTICATED");
assert.strictEqual(loaded.calls.hash.length, 0);
assertNoUploadWrites(loaded.calls);
});
test("upload forbids another user's container before HMAC or Azure write", async () => {
const loaded = loadUploadAuthorization();
const req = uploadAuthorizationRequest();
req.body.containerID = ["other-container"];
const res = createRes();
await loaded.mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assert.strictEqual(loaded.calls.hash.length, 0);
assertNoUploadWrites(loaded.calls);
});
test("upload rejects invalid HMAC for the owning user without Azure write", async () => {
const loaded = loadUploadAuthorization({
hashAPIPath: () => "?hash=different"
});
const res = createRes();
await loaded.mod.default.handler(uploadAuthorizationRequest(), res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH");
assertNoUploadWrites(loaded.calls);
});
test("upload requires HMAC for the authenticated owning user", async () => {
const loaded = loadUploadAuthorization();
const req = uploadAuthorizationRequest({ query: {} });
const res = createRes();
await loaded.mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "HASH_REQUIRED");
assertNoUploadWrites(loaded.calls);
});
test("upload preserves the authorised owner success contract and helper arguments", async () => {
const loaded = loadUploadAuthorization();
const res = createRes();
await loaded.mod.default.handler(uploadAuthorizationRequest(), res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
data: { id: "appeal-blob" }
});
assert.deepStrictEqual(loaded.calls.createBlob, [
[JSON.stringify({ key: "value" }), "user-container", "CASE-1"]
]);
assert.strictEqual(loaded.calls.createRepBlob.length, 0);
});
test("deleteblob handler returns MISSING_REQUIRED_QUERY when blobname missing", async () => {
const mod = loadModule("pages/api/file/deleteblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlob: async () => ({ ok: true })
});
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, "MISSING_REQUIRED_QUERY");
});
test("deleteblob handler accepts encoded hash variant and normalizes prefixed path", async () => {
let deletedPath = null;
const mod = loadModule("pages/api/file/deleteblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: (queryPath) =>
queryPath.includes("/api/file/deleteblob?container=c1")
? "&hash=expected"
: "&hash=other",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlob: async (_container, blobPath) => {
deletedPath = blobPath;
return { deleted: true };
}
});
const req = {
query: {
container: "c1",
casefolderID: "TMP-LFE1C",
blobname:
"TMP-LFE1C/files/2026-03-18_-_Site_Location_Plan_-_test document 021.xlsx",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(
deletedPath,
"TMP-LFE1C/files/2026-03-18_-_Site_Location_Plan_-_test document 021.xlsx"
);
});
test("deleteblob handler dependency failure returns DELETE_BLOB_FAILED", async () => {
const mod = loadModule("pages/api/file/deleteblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlob: async () => {
throw new Error("delete failed");
}
});
const req = {
query: {
container: "c1",
casefolderID: "f1",
blobname: "doc.pdf",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "DELETE_BLOB_FAILED");
});
test("deleteblobcase handler accepts encoded hash variant", async () => {
let deletedCaseFolder = null;
const mod = loadModule("pages/api/file/deleteblobcase.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "c1" } }),
hashAPIPath: (queryPath) =>
queryPath.includes("casefolderID=CASE%2FSUB")
? "&hash=expected"
: "&hash=other",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlobCase: async (_container, casefolder) => {
deletedCaseFolder = casefolder;
return { deleted: true };
}
});
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.strictEqual(deletedCaseFolder, "CASE/SUB");
});
test("deleteblobcase handler dependency failure returns DELETE_BLOB_CASE_FAILED", async () => {
const mod = loadModule("pages/api/file/deleteblobcase.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "c1" } }),
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlobCase: async () => {
throw new Error("delete case failed");
}
});
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,
"DELETE_BLOB_CASE_FAILED"
);
});
test("deleteblobrep handler accepts encoded hash variant", async () => {
let deletedRepPath = null;
const mod = loadModule("pages/api/file/deleteblobrep.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "c1" } }),
hashAPIPath: (queryPath) =>
queryPath.includes("repfile=Rep%20Name")
? "&hash=expected"
: "&hash=other",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlobRep: async (_container, repPath) => {
deletedRepPath = repPath;
return { deleted: true };
}
});
const req = {
query: {
container: "c1",
casefolderID: "CASE-1",
repfile: "Rep Name",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(deletedRepPath, "CASE-1/Rep Name");
});
test("deleteblobrep handler dependency failure returns DELETE_BLOB_REP_FAILED", async () => {
const mod = loadModule("pages/api/file/deleteblobrep.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "c1" } }),
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlobRep: async () => {
throw new Error("delete rep failed");
}
});
const req = {
query: {
container: "c1",
casefolderID: "f1",
repfile: "rep1",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "DELETE_BLOB_REP_FAILED");
});
const ordinaryDeletionCases = [
{
name: "deleteblobcase",
route: "pages/api/file/deleteblobcase.js",
query: {
container: "user-container",
casefolderID: "CASE-1",
hash: "expected"
},
dependency: "deleteBlobCase"
},
{
name: "deleteblobrep",
route: "pages/api/file/deleteblobrep.js",
query: {
container: "user-container",
casefolderID: "CASE-1",
repfile: "representation.json",
hash: "expected"
},
dependency: "deleteBlobRep"
}
];
for (const deletionCase of ordinaryDeletionCases) {
test(`${deletionCase.name} forbids a missing NextAuth session before deletion`, async () => {
let deletionCalls = 0;
const mod = loadModule(deletionCase.route, {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => null,
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
[deletionCase.dependency]: async () => {
deletionCalls += 1;
}
});
const res = createRes();
await mod.default.handler({ query: deletionCase.query }, res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assert.strictEqual(deletionCalls, 0);
});
test(`${deletionCase.name} forbids a session without user id before deletion`, async () => {
let deletionCalls = 0;
const mod = loadModule(deletionCase.route, {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: {} }),
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
[deletionCase.dependency]: async () => {
deletionCalls += 1;
}
});
const res = createRes();
await mod.default.handler({ query: deletionCase.query }, res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(deletionCalls, 0);
});
test(`${deletionCase.name} forbids cross-container deletion before HMAC or Azure deletion`, async () => {
let hashCalls = 0;
let deletionCalls = 0;
const mod = loadModule(deletionCase.route, {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "different-user" } }),
hashAPIPath: () => {
hashCalls += 1;
return "&hash=expected";
},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
[deletionCase.dependency]: async () => {
deletionCalls += 1;
}
});
const res = createRes();
await mod.default.handler({ query: deletionCase.query }, res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(hashCalls, 0);
assert.strictEqual(deletionCalls, 0);
});
test(`${deletionCase.name} rejects invalid HMAC for the owning user without deletion`, async () => {
let deletionCalls = 0;
const mod = loadModule(deletionCase.route, {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "user-container" } }),
hashAPIPath: () => "&hash=different",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
[deletionCase.dependency]: async () => {
deletionCalls += 1;
}
});
const res = createRes();
await mod.default.handler({ query: deletionCase.query }, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH");
assert.strictEqual(deletionCalls, 0);
});
test(`${deletionCase.name} preserves valid same-user deletion`, async () => {
const deletionCalls = [];
const mod = loadModule(deletionCase.route, {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "user-container" } }),
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
[deletionCase.dependency]: async (...args) => {
deletionCalls.push(args);
return { deleted: true };
}
});
const res = createRes();
await mod.default.handler({ query: deletionCase.query }, res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(deletionCalls.length, 1);
assert.strictEqual(deletionCalls[0][0], "user-container");
});
}
const loadAdminDeletion = (overrides = {}) => {
let deletionCalls = 0;
const mod = loadModule("pages/api/admin/deleteblobrep.js", {
deleteBlobRep: async () => {
deletionCalls += 1;
return { deleted: true };
},
readAdminSessionCookie: () => "admin-token",
validateAdminSession: () => true,
hasTrustedSameOrigin: () => true,
setAdminAuthNoStore: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
...overrides
});
return { mod, getDeletionCalls: () => deletionCalls };
};
const validAdminDeletionRequest = () => ({
method: "POST",
headers: { origin: "https://portal.example.test" },
body: {
container: "ck-user-container",
casefolderID: "CASE-1",
repfile: "representation.json"
}
});
test("admin representation deletion forbids a missing administrative session", async () => {
const loaded = loadAdminDeletion({
readAdminSessionCookie: () => null,
validateAdminSession: () => false
});
const res = createRes();
await loaded.mod.default(validAdminDeletionRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assert.strictEqual(loaded.getDeletionCalls(), 0);
});
test("admin representation deletion forbids an invalid or expired administrative session", async () => {
const loaded = loadAdminDeletion({ validateAdminSession: () => false });
const res = createRes();
await loaded.mod.default(validAdminDeletionRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(loaded.getDeletionCalls(), 0);
});
for (const originCase of ["missing", "cross-origin"]) {
test(`admin representation deletion forbids ${originCase} Origin`, async () => {
const loaded = loadAdminDeletion({ hasTrustedSameOrigin: () => false });
const req = validAdminDeletionRequest();
if (originCase === "missing") delete req.headers.origin;
if (originCase === "cross-origin") {
req.headers.origin = "https://attacker.example";
}
const res = createRes();
await loaded.mod.default(req, res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(loaded.getDeletionCalls(), 0);
});
}
test("ordinary user HMAC cannot authorize administrative representation deletion", async () => {
const loaded = loadAdminDeletion({
readAdminSessionCookie: () => null,
validateAdminSession: () => false
});
const req = validAdminDeletionRequest();
req.body.hash = "ordinary-user-hmac";
const res = createRes();
await loaded.mod.default(req, res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(loaded.getDeletionCalls(), 0);
});
test("admin representation deletion rejects malformed paths without deletion", async () => {
for (const malformedBody of [
{ casefolderID: "../CASE-1" },
{ repfile: "folder/representation.json" },
{ repfile: ".." }
]) {
const loaded = loadAdminDeletion();
const req = validAdminDeletionRequest();
req.body = { ...req.body, ...malformedBody };
const res = createRes();
await loaded.mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_REQUEST");
assert.strictEqual(loaded.getDeletionCalls(), 0);
}
});
test("valid administrative session and origin authorize cross-container representation deletion", async () => {
const deletionCalls = [];
const loaded = loadAdminDeletion({
deleteBlobRep: async (...args) => {
deletionCalls.push(args);
return { deleted: args[1] };
}
});
const res = createRes();
await loaded.mod.default(validAdminDeletionRequest(), res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(deletionCalls)), [
["ck-user-container", "CASE-1/representation.json"]
]);
});
test("admin storage caller uses dedicated POST deletion while ordinary caller remains signed GET", () => {
const rootDir = path.resolve(__dirname, "..", "..");
const adminSource = fs.readFileSync(
path.join(rootDir, "components/admin/tabs/storage.js"),
"utf8"
);
const ordinarySource = fs.readFileSync(
path.join(rootDir, "actions/services/documentDirectService.js"),
"utf8"
);
assert.match(adminSource, /fetch\("\/api\/admin\/deleteblobrep"/);
assert.match(adminSource, /method:\s*"POST"/);
assert.doesNotMatch(adminSource, /deleteMyRepresentationsFromBlob/);
assert.match(
ordinarySource,
/buildFileQuery\("\/api\/file\/deleteblobrep"[\s\S]*getSignedFileJson\(queryUrl\)/
);
});
const loadAdminCaseDeletion = (overrides = {}) => {
const calls = {
noStore: 0,
readSession: 0,
validateSession: 0,
origin: 0,
deletion: []
};
const payload = { deleted: "TMP-CASE-1" };
const mod = loadModule("pages/api/admin/deleteblobcase.js", {
deleteBlobCase: async (...args) => {
calls.deletion.push(args);
return payload;
},
readAdminSessionCookie: () => {
calls.readSession += 1;
return "admin-token";
},
validateAdminSession: () => {
calls.validateSession += 1;
return true;
},
hasTrustedSameOrigin: () => {
calls.origin += 1;
return true;
},
setAdminAuthNoStore: () => {
calls.noStore += 1;
},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
...overrides
});
return { mod, calls, payload };
};
const validAdminCaseDeletionRequest = (overrides = {}) => ({
method: "POST",
headers: {
origin: "https://portal.example.test",
cookie: "pedw_admin_session=admin-token"
},
body: {
container: "selected-user-container",
casefolderID: "TMP-CASE-1"
},
...overrides
});
for (const method of ["GET", "DELETE"]) {
test(`admin case deletion rejects ${method} with 405 before authorization or deletion`, async () => {
const loaded = loadAdminCaseDeletion();
const res = createRes();
await loaded.mod.default(
validAdminCaseDeletionRequest({ method }),
res
);
assert.strictEqual(res.state.statusCode, 405);
assert.strictEqual(res.state.headers.allow, "POST");
assert.strictEqual(res.state.jsonBody.error.code, "METHOD_NOT_ALLOWED");
assert.strictEqual(loaded.calls.noStore, 1);
assert.strictEqual(loaded.calls.readSession, 0);
assert.strictEqual(loaded.calls.deletion.length, 0);
});
}
for (const sessionCase of [
{ name: "missing", token: null },
{ name: "invalid", token: "invalid-token" },
{ name: "expired", token: "expired-token" }
]) {
test(`admin case deletion rejects ${sessionCase.name} administrative session before deletion`, async () => {
const loaded = loadAdminCaseDeletion({
readAdminSessionCookie: () => sessionCase.token,
validateAdminSession: () => false
});
const res = createRes();
await loaded.mod.default(validAdminCaseDeletionRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assert.strictEqual(loaded.calls.origin, 0);
assert.strictEqual(loaded.calls.deletion.length, 0);
});
}
for (const originCase of [
{ name: "missing", headers: { cookie: "pedw_admin_session=admin-token" } },
{
name: "cross-origin",
headers: {
origin: "https://attacker.example.test",
cookie: "pedw_admin_session=admin-token"
}
}
]) {
test(`admin case deletion rejects ${originCase.name} Origin before deletion`, async () => {
const loaded = loadAdminCaseDeletion({
hasTrustedSameOrigin: () => false
});
const res = createRes();
await loaded.mod.default(
validAdminCaseDeletionRequest({ headers: originCase.headers }),
res
);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(loaded.calls.deletion.length, 0);
});
}
for (const inputCase of [
{ name: "missing container", body: { container: undefined } },
{ name: "malformed container", body: { container: "Invalid_Container" } },
{ name: "missing case identifier", body: { casefolderID: undefined } },
{ name: "case identifier path separator", body: { casefolderID: "TMP/CASE-1" } },
{ name: "case identifier traversal segment", body: { casefolderID: ".." } },
{ name: "case identifier control character", body: { casefolderID: "TMP\u0000CASE" } }
]) {
test(`admin case deletion rejects ${inputCase.name} without deletion`, async () => {
const loaded = loadAdminCaseDeletion();
const request = validAdminCaseDeletionRequest();
const res = createRes();
request.body = { ...request.body, ...inputCase.body };
Object.keys(request.body).forEach((key) => {
if (typeof request.body[key] === "undefined") delete request.body[key];
});
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_REQUEST");
assert.strictEqual(loaded.calls.deletion.length, 0);
});
}
test("valid administrator can delete a cross-container case prefix with the existing helper", async () => {
const loaded = loadAdminCaseDeletion();
const request = validAdminCaseDeletionRequest();
const res = createRes();
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(loaded.calls.deletion, [
[request.body.container, request.body.casefolderID]
]);
assert.deepStrictEqual(
JSON.parse(JSON.stringify(res.state.jsonBody)),
{ data: loaded.payload }
);
});
test("admin case deletion dependency failure returns DELETE_BLOB_CASE_FAILED", async () => {
const loaded = loadAdminCaseDeletion({
deleteBlobCase: async (...args) => {
loaded.calls.deletion.push(args);
throw new Error("delete case failed");
}
});
const res = createRes();
await loaded.mod.default(validAdminCaseDeletionRequest(), res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"DELETE_BLOB_CASE_FAILED"
);
assert.strictEqual(loaded.calls.deletion.length, 1);
});
test("admin storage case deletion uses dedicated POST route while ordinary callers remain signed GET", () => {
const rootDir = path.resolve(__dirname, "..", "..");
const adminSource = fs.readFileSync(
path.join(rootDir, "components/admin/tabs/storage.js"),
"utf8"
);
const ordinarySource = fs.readFileSync(
path.join(rootDir, "actions/services/documentDirectService.js"),
"utf8"
);
assert.match(adminSource, /fetch\(\s*"\/api\/admin\/deleteblobcase"/);
assert.match(
adminSource,
/"\/api\/admin\/deleteblobcase"[\s\S]*method:\s*"POST"[\s\S]*container:\s*c\.container[\s\S]*casefolderID:\s*folder\.folderPath/
);
assert.match(adminSource, /if \(!response\.ok\)/);
assert.doesNotMatch(adminSource, /deleteAwaitingSubmissionsFromBlob/);
assert.match(
ordinarySource,
/buildFileQuery\("\/api\/file\/deleteblobcase"[\s\S]*getSignedFileJson\(queryUrl\)/
);
});
const loadAdminRepJsonEdit = (overrides = {}) => {
let azureClientConstructions = 0;
const storageCalls = [];
const sourceJson = {
containerID: "ck-user-container",
ticketnumber: "CASE-1",
repComplete: true,
representationType: "Support",
nested: { retained: true }
};
const uploaded = {};
class DefaultAzureCredentialMock {}
class BlobServiceClientMock {
constructor(url, credential) {
azureClientConstructions += 1;
storageCalls.push(["construct", url, credential]);
}
getContainerClient(container) {
storageCalls.push(["container", container]);
return {
getBlockBlobClient(blobName) {
storageCalls.push(["blob", blobName]);
return {
async download(offset) {
storageCalls.push(["download", offset]);
const { Readable } = require("stream");
return {
readableStreamBody: Readable.from([
JSON.stringify(sourceJson)
])
};
},
async upload(content, length, options) {
uploaded.content = content;
uploaded.length = length;
uploaded.options = options;
storageCalls.push(["upload"]);
},
async setTags(tags) {
uploaded.tags = tags;
storageCalls.push(["tags"]);
},
async setMetadata(metadata) {
uploaded.metadata = metadata;
storageCalls.push(["metadata"]);
}
};
}
};
}
}
const mod = loadModule("pages/api/admin/editRepJson.js", {
Buffer,
DefaultAzureCredential: DefaultAzureCredentialMock,
BlobServiceClient: BlobServiceClientMock,
consoleLogger: () => {},
readAdminSessionCookie: () => "admin-token",
validateAdminSession: () => true,
hasTrustedSameOrigin: () => true,
setAdminAuthNoStore: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
...overrides
});
return {
mod,
getAzureClientConstructions: () => azureClientConstructions,
storageCalls,
sourceJson,
uploaded
};
};
const validAdminRepJsonEditRequest = () => ({
method: "POST",
headers: { origin: "https://portal.example.test" },
body: {
container: "ck-user-container",
blobName: "CASE-1/representation-1/representation_rep.json"
}
});
test("admin representation JSON edit forbids a missing administrative session before Azure access", async () => {
const loaded = loadAdminRepJsonEdit({
readAdminSessionCookie: () => null,
validateAdminSession: () => false
});
const res = createRes();
await loaded.mod.default(validAdminRepJsonEditRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assert.strictEqual(loaded.getAzureClientConstructions(), 0);
});
test("admin representation JSON edit forbids an invalid administrative session before Azure access", async () => {
const loaded = loadAdminRepJsonEdit({ validateAdminSession: () => false });
const res = createRes();
await loaded.mod.default(validAdminRepJsonEditRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(loaded.getAzureClientConstructions(), 0);
});
test("admin representation JSON edit forbids cross-origin requests before Azure access", async () => {
const loaded = loadAdminRepJsonEdit({ hasTrustedSameOrigin: () => false });
const req = validAdminRepJsonEditRequest();
req.headers.origin = "https://attacker.example";
const res = createRes();
await loaded.mod.default(req, res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(loaded.getAzureClientConstructions(), 0);
});
test("admin representation JSON edit rejects unsupported methods before Azure access", async () => {
const loaded = loadAdminRepJsonEdit();
const req = validAdminRepJsonEditRequest();
req.method = "GET";
const res = createRes();
await loaded.mod.default(req, res);
assert.strictEqual(res.state.statusCode, 405);
assert.strictEqual(res.state.jsonBody.error.code, "METHOD_NOT_ALLOWED");
assert.strictEqual(res.state.headers.allow, "POST");
assert.strictEqual(loaded.getAzureClientConstructions(), 0);
});
test("admin representation JSON edit rejects malformed identifiers before Azure access", async () => {
for (const malformedBody of [
{ container: "UPPERCASE" },
{ container: "ab" },
{ blobName: "../representation_rep.json" },
{ blobName: "CASE-1//representation_rep.json" },
{ blobName: "CASE-1\\representation_rep.json" },
{ blobName: " representation_rep.json" }
]) {
const loaded = loadAdminRepJsonEdit();
const req = validAdminRepJsonEditRequest();
req.body = { ...req.body, ...malformedBody };
const res = createRes();
await loaded.mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_REQUEST");
assert.strictEqual(loaded.getAzureClientConstructions(), 0);
}
});
test("valid administrative representation JSON edit preserves the existing mutation contract", async () => {
const loaded = loadAdminRepJsonEdit();
const res = createRes();
await loaded.mod.default(validAdminRepJsonEditRequest(), res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
message: "repComplete removed successfully"
});
assert.strictEqual(loaded.getAzureClientConstructions(), 1);
assert.deepStrictEqual(loaded.storageCalls.slice(1, 4), [
["container", "ck-user-container"],
["blob", "CASE-1/representation-1/representation_rep.json"],
["download", 0]
]);
const updatedJson = JSON.parse(loaded.uploaded.content);
assert.strictEqual(Object.hasOwn(updatedJson, "repComplete"), false);
assert.strictEqual(updatedJson.containerID, loaded.sourceJson.containerID);
assert.strictEqual(updatedJson.ticketnumber, loaded.sourceJson.ticketnumber);
assert.strictEqual(
updatedJson.representationType,
loaded.sourceJson.representationType
);
assert.deepStrictEqual(
JSON.parse(JSON.stringify(updatedJson.nested)),
loaded.sourceJson.nested
);
assert.strictEqual(
loaded.uploaded.length,
Buffer.byteLength(loaded.uploaded.content)
);
assert.deepStrictEqual(
JSON.parse(JSON.stringify(loaded.uploaded.options)),
{ blobHTTPHeaders: { blobContentType: "application/json" } }
);
assert.deepStrictEqual(JSON.parse(JSON.stringify(loaded.uploaded.tags)), {
containerid: "ck-user-container",
caseID: "CASE-1",
blobType: "Representation"
});
assert.deepStrictEqual(
JSON.parse(JSON.stringify(loaded.uploaded.metadata)),
JSON.parse(JSON.stringify(loaded.uploaded.tags))
);
});
test("retired file representation JSON endpoint cannot mutate storage", async () => {
let storageCalls = 0;
const mod = loadModule("pages/api/file/editRepJson.js", {
BlobServiceClient: class {
constructor() {
storageCalls += 1;
}
},
respondError: respondErrorMock
});
const res = createRes();
await mod.default(validAdminRepJsonEditRequest(), res);
assert.strictEqual(res.state.statusCode, 410);
assert.strictEqual(res.state.jsonBody.error.code, "ENDPOINT_RETIRED");
assert.strictEqual(storageCalls, 0);
});
test("admin storage caller uses the protected representation JSON edit endpoint", () => {
const rootDir = path.resolve(__dirname, "..", "..");
const adminSource = fs.readFileSync(
path.join(rootDir, "components/admin/tabs/storage.js"),
"utf8"
);
assert.match(adminSource, /fetch\(\s*"\/api\/admin\/editRepJson"/);
assert.doesNotMatch(adminSource, /fetch\(\s*"\/api\/file\/editRepJson"/);
});
test("deleteawaitingsubmissionfromblob accepts legacy deleteblob hash variant", async () => {
let deletedPath = null;
const mod = loadModule(
"pages/api/file/deleteawaitingsubmissionfromblob.js",
{
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: (queryPath) =>
queryPath.includes("/api/file/deleteblob?container=c1")
? "&hash=expected"
: "&hash=other",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlob: async (_container, blobPath) => {
deletedPath = blobPath;
return { deleted: true };
}
}
);
const req = {
query: {
container: "c1",
casefolderID: "CASE-1",
blobname: "CASE-1/files/doc.pdf",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(deletedPath, "CASE-1/files/doc.pdf");
});
test("deleteawaitingsubmissionfromblob dependency failure returns DELETE_AWAITING_SUBMISSION_BLOB_FAILED", async () => {
const mod = loadModule(
"pages/api/file/deleteawaitingsubmissionfromblob.js",
{
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
deleteBlob: async () => {
throw new Error("delete awaiting failed");
}
}
);
const req = {
query: {
container: "c1",
casefolderID: "CASE-1",
blobname: "doc.pdf",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"DELETE_AWAITING_SUBMISSION_BLOB_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 rejects missing HMAC before Azure access", async () => {
let azureCalls = 0;
const mod = loadModule("pages/api/file/getrepsblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
consoleLogger: () => {},
getRepsBlobs: async () => {
azureCalls += 1;
return [];
},
downloadAllRepsFiles: async () => {
azureCalls += 1;
return {};
}
});
const res = createRes();
await mod.default.handler({ query: { container: "c1" } }, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "MISSING_REQUIRED_QUERY");
assert.strictEqual(azureCalls, 0);
});
test("getrepsblob handler rejects invalid HMAC before Azure access", async () => {
let azureCalls = 0;
const mod = loadModule("pages/api/file/getrepsblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
consoleLogger: () => {},
getRepsBlobs: async () => {
azureCalls += 1;
return [];
},
downloadAllRepsFiles: async () => {
azureCalls += 1;
return {};
}
});
const res = createRes();
await mod.default.handler(
{ query: { container: "c1", hash: "wrong" } },
res
);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH");
assert.strictEqual(azureCalls, 0);
});
test("general hash endpoint does not authorize getrepsblob signing", async () => {
let hashCalls = 0;
const mod = loadModule("pages/api/endpoint/gethash_api.js", {
nextConnect: createNextConnectMock(),
getSession: async () => ({ user: { id: "user-container" } }),
hashAPIPath: () => {
hashCalls += 1;
return "&hash=unexpected";
},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock
});
const res = createRes();
await mod.default.handler(
{
query: {
path: "/api/file/getrepsblob?container=user-container"
}
},
res
);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH_PATH");
assert.strictEqual(hashCalls, 0);
});
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("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: () => {},
getSession: async () => ({ user: { id: "c1" } }),
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: () => {},
getSession: async () => ({ user: { id: "c1" } }),
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") },
path: { basename: (value) => value }
});
const req = {
query: { hash: "expected" },
body: { containerID: ["c1"], casefolderID: ["f1"] },
files: {
document: [
{
fieldName: "document.pdf",
originalFilename: "document.pdf",
path: "/tmp/document.pdf",
size: 1,
headers: { "content-type": "application/pdf" }
}
]
}
};
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"
);
});
const loadSingleFileUploadAuthorization = (overrides = {}) => {
const calls = { upload: [], hash: [], fileReads: 0, typeChecks: 0 };
const mod = loadModule("pages/api/file/uploadsinglefile.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "user-container" } }),
hashAPIPath: (queryPath) => {
calls.hash.push(queryPath);
return "?hash=expected";
},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
consoleLogger: () => {},
uploadSingleFile: async (...args) => {
calls.upload.push(args);
return [{ file: "document.pdf" }];
},
fileTypeFromBuffer: async () => {
calls.typeChecks += 1;
return { mime: "application/pdf" };
},
fs: {
readFileSync: () => {
calls.fileReads += 1;
return Buffer.from("synthetic-pdf");
}
},
path: { basename: (value) => value },
...overrides
});
return { mod, calls };
};
const singleFileUploadAuthorizationRequest = (overrides = {}) => ({
query: { hash: "expected" },
body: {
containerID: ["user-container"],
casefolderID: ["CASE-1"]
},
files: {
document: [
{
fieldName: "document.pdf",
originalFilename: "document.pdf",
path: "/tmp/document.pdf",
size: 1,
headers: { "content-type": "application/pdf" }
}
]
},
...overrides
});
const assertNoSingleFileWork = (calls, { noHash = false } = {}) => {
assert.strictEqual(calls.upload.length, 0);
assert.strictEqual(calls.fileReads, 0);
assert.strictEqual(calls.typeChecks, 0);
if (noHash) assert.strictEqual(calls.hash.length, 0);
};
test("uploadsinglefile rejects a missing NextAuth session before HMAC, file processing or Azure write", async () => {
const loaded = loadSingleFileUploadAuthorization({
getSession: async () => null
});
const res = createRes();
await loaded.mod.default.handler(
singleFileUploadAuthorizationRequest(),
res
);
assert.strictEqual(res.state.statusCode, 401);
assert.strictEqual(res.state.jsonBody.error.code, "UNAUTHENTICATED");
assertNoSingleFileWork(loaded.calls, { noHash: true });
});
test("uploadsinglefile rejects an unusable NextAuth session before HMAC, file processing or Azure write", async () => {
const loaded = loadSingleFileUploadAuthorization({
getSession: async () => ({ user: { id: "" } })
});
const res = createRes();
await loaded.mod.default.handler(
singleFileUploadAuthorizationRequest(),
res
);
assert.strictEqual(res.state.statusCode, 401);
assert.strictEqual(res.state.jsonBody.error.code, "UNAUTHENTICATED");
assertNoSingleFileWork(loaded.calls, { noHash: true });
});
test("uploadsinglefile forbids another user's container before HMAC, file processing or Azure write", async () => {
const loaded = loadSingleFileUploadAuthorization();
const req = singleFileUploadAuthorizationRequest();
req.body.containerID = ["other-container"];
const res = createRes();
await loaded.mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assertNoSingleFileWork(loaded.calls, { noHash: true });
});
test("uploadsinglefile rejects invalid HMAC for the owning user before file processing or Azure write", async () => {
const loaded = loadSingleFileUploadAuthorization({
hashAPIPath: () => "?hash=different"
});
const res = createRes();
await loaded.mod.default.handler(
singleFileUploadAuthorizationRequest(),
res
);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH");
assertNoSingleFileWork(loaded.calls);
});
test("uploadsinglefile requires HMAC for the authenticated owning user", async () => {
const loaded = loadSingleFileUploadAuthorization();
const req = singleFileUploadAuthorizationRequest({ query: {} });
const res = createRes();
await loaded.mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "HASH_REQUIRED");
assertNoSingleFileWork(loaded.calls);
});
test("uploadsinglefile preserves multipart owner success response and exact Azure helper arguments", async () => {
const loaded = loadSingleFileUploadAuthorization();
const res = createRes();
await loaded.mod.default.handler(
singleFileUploadAuthorizationRequest(),
res
);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
data: [{ file: "document.pdf" }],
invalidFiles: []
});
assert.strictEqual(loaded.calls.upload.length, 1);
assert.strictEqual(loaded.calls.upload[0][1], "user-container");
assert.strictEqual(loaded.calls.upload[0][2], "CASE-1");
assert.deepStrictEqual(
JSON.parse(JSON.stringify(loaded.calls.upload[0][0])),
{
document: [
{
fieldName: "document.pdf",
originalFilename: "document.pdf",
path: "/tmp/document.pdf",
size: 1,
headers: { "content-type": "application/pdf" },
contentType: "application/pdf"
}
]
}
);
assert.strictEqual(loaded.calls.fileReads, 1);
assert.strictEqual(loaded.calls.typeChecks, 1);
});
const loadOrdinaryRepCompletion = (overrides = {}) => {
const calls = { hash: [], queue: [] };
const payload = { messageId: "ordinary-message" };
const mod = loadModule("pages/api/file/createrepcompletemessage_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getSession: async () => ({ user: { id: "user-container" } }),
hashAPIPath: (queryPath) => {
calls.hash.push(queryPath);
return "&hash=expected";
},
createRepCompleteMessage: async (...args) => {
calls.queue.push(args);
return payload;
},
consoleLogger: () => {},
...overrides
});
return { mod, calls, payload };
};
const ordinaryRepCompletionRequest = (overrides = {}) => ({
method: "GET",
headers: {},
query: {
container: "user-container",
tempcaseref: "CAS-12345-A1B2C3",
repid: "2026-03-18-10:20:30_-_APP_-_Statement_-_Jones_Rob",
hash: "expected"
},
...overrides
});
const assertNoCompletionWork = (calls, { noHash = false } = {}) => {
assert.strictEqual(calls.queue.length, 0);
if (noHash) assert.strictEqual(calls.hash.length, 0);
};
test("createrepcompletemessage rejects unsupported methods before authorization work", async () => {
const loaded = loadOrdinaryRepCompletion();
const res = createRes();
await loaded.mod.default(
ordinaryRepCompletionRequest({ method: "POST" }),
res
);
assert.strictEqual(res.state.statusCode, 405);
assert.strictEqual(res.state.headers.allow, "GET");
assert.strictEqual(res.state.jsonBody.error.code, "METHOD_NOT_ALLOWED");
assertNoCompletionWork(loaded.calls, { noHash: true });
});
for (const inputCase of [
{ name: "missing container", query: { container: undefined } },
{ name: "invalid container type", query: { container: ["user-container"] } },
{ name: "malformed container", query: { container: "Invalid_Container" } },
{ name: "missing case reference", query: { tempcaseref: undefined } },
{ name: "unsafe case-reference path separator", query: { tempcaseref: "CAS/123" } },
{ name: "unsafe case-reference traversal segment", query: { tempcaseref: ".." } },
{ name: "missing representation identifier", query: { repid: undefined } },
{ name: "unsafe representation path separator", query: { repid: "rep\\file" } },
{ name: "unsafe representation control character", query: { repid: "rep\u0000file" } }
]) {
test(`createrepcompletemessage rejects ${inputCase.name} before authorization work`, async () => {
const loaded = loadOrdinaryRepCompletion();
const request = ordinaryRepCompletionRequest();
const res = createRes();
request.query = { ...request.query, ...inputCase.query };
Object.keys(request.query).forEach((key) => {
if (typeof request.query[key] === "undefined") delete request.query[key];
});
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 400);
assertNoCompletionWork(loaded.calls, { noHash: true });
});
}
for (const sessionCase of [
{ name: "missing NextAuth session", session: null },
{ name: "session without user id", session: { user: {} } },
{ name: "session with empty user id", session: { user: { id: "" } } },
{ name: "session with non-string user id", session: { user: { id: 123 } } }
]) {
test(`createrepcompletemessage rejects ${sessionCase.name} before HMAC processing`, async () => {
const loaded = loadOrdinaryRepCompletion({
getSession: async () => sessionCase.session
});
const res = createRes();
await loaded.mod.default(ordinaryRepCompletionRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assertNoCompletionWork(loaded.calls, { noHash: true });
});
}
test("createrepcompletemessage rejects cross-container ownership before HMAC processing", async () => {
const loaded = loadOrdinaryRepCompletion({
getSession: async () => ({ user: { id: "different-container" } })
});
const res = createRes();
await loaded.mod.default(ordinaryRepCompletionRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assertNoCompletionWork(loaded.calls, { noHash: true });
});
test("administrative cookie alone cannot authorize ordinary representation completion", async () => {
const loaded = loadOrdinaryRepCompletion({ getSession: async () => null });
const res = createRes();
await loaded.mod.default(
ordinaryRepCompletionRequest({
headers: { cookie: "pedw_admin_session=valid-admin-token" }
}),
res
);
assert.strictEqual(res.state.statusCode, 403);
assertNoCompletionWork(loaded.calls, { noHash: true });
});
test("previously valid HMAC cannot bypass a missing current ordinary session", async () => {
const loaded = loadOrdinaryRepCompletion({ getSession: async () => null });
const res = createRes();
await loaded.mod.default(ordinaryRepCompletionRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assertNoCompletionWork(loaded.calls, { noHash: true });
});
for (const hashCase of [
{ name: "missing HMAC", hash: undefined },
{ name: "invalid HMAC", hash: "wrong" }
]) {
test(`createrepcompletemessage rejects ${hashCase.name} without queue work`, async () => {
const loaded = loadOrdinaryRepCompletion();
const request = ordinaryRepCompletionRequest();
const res = createRes();
if (typeof hashCase.hash === "undefined") delete request.query.hash;
else request.query.hash = hashCase.hash;
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 400);
assertNoCompletionWork(loaded.calls);
});
}
test("createrepcompletemessage accepts punctuation and preserves exact identifiers and response", async () => {
const loaded = loadOrdinaryRepCompletion();
const request = ordinaryRepCompletionRequest();
const res = createRes();
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(loaded.calls.queue, [
[
request.query.container,
request.query.tempcaseref,
request.query.repid
]
]);
assert.deepStrictEqual(
JSON.parse(JSON.stringify(res.state.jsonBody)),
loaded.payload
);
});
test("createrepcompletemessage preserves encoded HMAC compatibility", async () => {
const loaded = loadOrdinaryRepCompletion({
hashAPIPath: (queryPath) => {
loaded.calls.hash.push(queryPath);
return queryPath.includes("repid=rep%20name%20%26%20notes.json")
? "&hash=expected"
: "&hash=other";
}
});
const request = ordinaryRepCompletionRequest();
request.query.repid = "rep name & notes.json";
const res = createRes();
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(loaded.calls.queue.length, 1);
assert.strictEqual(loaded.calls.hash.length, 2);
});
test("createrepcompletemessage dependency failure returns CREATE_REP_COMPLETE_MESSAGE_FAILED", async () => {
const loaded = loadOrdinaryRepCompletion({
createRepCompleteMessage: async (...args) => {
loaded.calls.queue.push(args);
throw new Error("rep message failed");
}
});
const res = createRes();
await loaded.mod.default(ordinaryRepCompletionRequest(), res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"CREATE_REP_COMPLETE_MESSAGE_FAILED"
);
assert.strictEqual(loaded.calls.queue.length, 1);
});
const loadAdminRepCompletion = (overrides = {}) => {
const calls = { noStore: 0, readSession: 0, validate: 0, origin: 0, queue: [] };
const payload = { messageId: "admin-message" };
const mod = loadModule("pages/api/admin/createrepcompletemessage.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
setAdminAuthNoStore: () => {
calls.noStore += 1;
},
readAdminSessionCookie: () => {
calls.readSession += 1;
return "admin-token";
},
validateAdminSession: () => {
calls.validate += 1;
return true;
},
hasTrustedSameOrigin: () => {
calls.origin += 1;
return true;
},
createRepCompleteMessage: async (...args) => {
calls.queue.push(args);
return payload;
},
consoleLogger: () => {},
...overrides
});
return { mod, calls, payload };
};
const adminRepCompletionRequest = (overrides = {}) => ({
method: "POST",
headers: {
origin: "https://portal.example.test",
cookie: "pedw_admin_session=admin-token"
},
body: {
container: "selected-user-container",
tempcaseref: "CAS-12345-A1B2C3",
repid: "2026-03-18-10:20:30_-_IP_-_Statement_-_Smith_Alex_rep.json"
},
...overrides
});
test("admin representation completion rejects unsupported methods before authentication", async () => {
const loaded = loadAdminRepCompletion();
const res = createRes();
await loaded.mod.default(adminRepCompletionRequest({ method: "GET" }), res);
assert.strictEqual(res.state.statusCode, 405);
assert.strictEqual(res.state.headers.allow, "POST");
assert.strictEqual(loaded.calls.noStore, 1);
assert.strictEqual(loaded.calls.readSession, 0);
assertNoCompletionWork(loaded.calls);
});
for (const sessionCase of [
{ name: "missing administrative session", token: null },
{ name: "invalid administrative session", token: "invalid-token" },
{ name: "expired administrative session", token: "expired-token" }
]) {
test(`admin representation completion rejects ${sessionCase.name}`, async () => {
const loaded = loadAdminRepCompletion({
readAdminSessionCookie: () => sessionCase.token,
validateAdminSession: () => false
});
const res = createRes();
await loaded.mod.default(adminRepCompletionRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(loaded.calls.origin, 0);
assertNoCompletionWork(loaded.calls);
});
}
for (const originCase of [
{ name: "missing Origin", headers: { cookie: "pedw_admin_session=admin-token" } },
{
name: "cross-origin request",
headers: {
origin: "https://attacker.example.test",
cookie: "pedw_admin_session=admin-token"
}
}
]) {
test(`admin representation completion rejects ${originCase.name}`, async () => {
const loaded = loadAdminRepCompletion({ hasTrustedSameOrigin: () => false });
const res = createRes();
await loaded.mod.default(
adminRepCompletionRequest({ headers: originCase.headers }),
res
);
assert.strictEqual(res.state.statusCode, 403);
assertNoCompletionWork(loaded.calls);
});
}
for (const inputCase of [
{ name: "missing container", body: { container: undefined } },
{ name: "malformed container", body: { container: "Invalid_Container" } },
{ name: "missing case reference", body: { tempcaseref: undefined } },
{ name: "unsafe case-reference segment", body: { tempcaseref: "CAS/123" } },
{ name: "missing representation identifier", body: { repid: undefined } },
{ name: "unsafe representation segment", body: { repid: "../rep.json" } }
]) {
test(`admin representation completion rejects ${inputCase.name} without queue work`, async () => {
const loaded = loadAdminRepCompletion();
const request = adminRepCompletionRequest();
const res = createRes();
request.body = { ...request.body, ...inputCase.body };
Object.keys(request.body).forEach((key) => {
if (typeof request.body[key] === "undefined") delete request.body[key];
});
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 400);
assertNoCompletionWork(loaded.calls);
});
}
test("ordinary NextAuth session alone cannot authorize admin representation completion", async () => {
const loaded = loadAdminRepCompletion({
readAdminSessionCookie: () => null,
validateAdminSession: () => false,
getSession: async () => ({ user: { id: "selected-user-container" } })
});
const res = createRes();
await loaded.mod.default(adminRepCompletionRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assertNoCompletionWork(loaded.calls);
});
test("admin representation completion preserves cross-container identifiers and response", async () => {
const loaded = loadAdminRepCompletion();
const request = adminRepCompletionRequest();
const res = createRes();
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(loaded.calls.queue, [
[request.body.container, request.body.tempcaseref, request.body.repid]
]);
assert.deepStrictEqual(
JSON.parse(JSON.stringify(res.state.jsonBody)),
loaded.payload
);
});
test("admin representation completion accepts legitimate filename punctuation", async () => {
const loaded = loadAdminRepCompletion();
const request = adminRepCompletionRequest();
request.body.repid = "rep name_-_notes.v2 (final)!.json";
const res = createRes();
await loaded.mod.default(request, res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(loaded.calls.queue.length, 1);
});
test("getbloblistproxy success returns proxied payload", async () => {
const mod = loadModule("pages/api/file/getbloblistproxy.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
azureHeaders: () => ({ headers: { Authorization: "Bearer tok" } }),
hashAPIPath: () => "&hash=abc",
consoleLogger: () => {},
process: { env: { API_ROOT: "http://example.com" } },
axios: {
get: async () => ({ data: { value: [{ id: "b1" }] } })
}
});
const req = {
query: { container: "cont/1", casefolderID: "CASE/1" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
value: [{ id: "b1" }]
});
});
test("getbloblistproxy dependency failure returns GET_BLOB_LIST_PROXY_FAILED", async () => {
const mod = loadModule("pages/api/file/getbloblistproxy.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
azureHeaders: () => ({ headers: {} }),
hashAPIPath: () => "&hash=abc",
consoleLogger: () => {},
process: { env: { API_ROOT: "http://example.com" } },
axios: {
get: async () => {
throw new Error("proxy failed");
}
}
});
const req = {
query: { container: "c1", casefolderID: "f1" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"GET_BLOB_LIST_PROXY_FAILED"
);
});
const loadOrdinaryRepsProxy = (overrides = {}) => {
const calls = { token: 0, hash: [], axios: [] };
const payload = { "@odata.count": 1, value: [{ id: "r1" }] };
const mod = loadModule("pages/api/file/getrepsblobproxy.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getSession: async () => ({ user: { id: "user-container" } }),
getToken: async () => {
calls.token += 1;
return { access_token: "tok" };
},
azureHeaders: () => ({ headers: { Authorization: "Bearer tok" } }),
hashAPIPath: (queryPath) => {
calls.hash.push(queryPath);
return "&hash=abc";
},
consoleLogger: () => {},
process: { env: { API_ROOT: "http://example.com" } },
axios: {
get: async (...args) => {
calls.axios.push(args);
return { data: payload };
}
},
...overrides
});
return { mod, calls, payload };
};
const ordinaryRepsRequest = (overrides = {}) => ({
method: "GET",
query: { container: "user-container" },
headers: {},
...overrides
});
const assertNoProxyWork = (calls) => {
assert.strictEqual(calls.token, 0);
assert.strictEqual(calls.hash.length, 0);
assert.strictEqual(calls.axios.length, 0);
};
for (const sessionCase of [
{ name: "missing session", session: null },
{ name: "session without user id", session: { user: {} } },
{ name: "session with empty user id", session: { user: { id: "" } } },
{ name: "session with non-string user id", session: { user: { id: 123 } } }
]) {
test(`getrepsblobproxy rejects ${sessionCase.name} before proxy work`, async () => {
const loaded = loadOrdinaryRepsProxy({
getSession: async () => sessionCase.session
});
const res = createRes();
await loaded.mod.default(ordinaryRepsRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assertNoProxyWork(loaded.calls);
});
}
test("getrepsblobproxy rejects cross-container access before proxy work", async () => {
const loaded = loadOrdinaryRepsProxy({
getSession: async () => ({ user: { id: "different-container" } })
});
const res = createRes();
await loaded.mod.default(ordinaryRepsRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assertNoProxyWork(loaded.calls);
});
test("administrative cookie alone cannot authorize getrepsblobproxy", async () => {
const loaded = loadOrdinaryRepsProxy({ getSession: async () => null });
const res = createRes();
await loaded.mod.default(
ordinaryRepsRequest({
headers: { cookie: "pedw_admin_session=valid-admin-token" }
}),
res
);
assert.strictEqual(res.state.statusCode, 403);
assertNoProxyWork(loaded.calls);
});
test("getrepsblobproxy rejects unsupported methods before proxy work", async () => {
const loaded = loadOrdinaryRepsProxy();
const res = createRes();
await loaded.mod.default(ordinaryRepsRequest({ method: "POST" }), res);
assert.strictEqual(res.state.statusCode, 405);
assert.strictEqual(res.state.jsonBody.error.code, "METHOD_NOT_ALLOWED");
assertNoProxyWork(loaded.calls);
});
for (const containerCase of [
{ name: "missing container", query: {} },
{ name: "empty container", query: { container: "" } },
{ name: "null container", query: { container: null } },
{ name: "non-string container", query: { container: ["user-container"] } },
{ name: "malformed container", query: { container: "Invalid_Container" } }
]) {
test(`getrepsblobproxy rejects ${containerCase.name} before proxy work`, async () => {
const loaded = loadOrdinaryRepsProxy();
const res = createRes();
await loaded.mod.default(
ordinaryRepsRequest({ query: containerCase.query }),
res
);
assert.strictEqual(res.state.statusCode, 400);
assertNoProxyWork(loaded.calls);
});
}
test("getrepsblobproxy preserves signed path and response for owning user", async () => {
const loaded = loadOrdinaryRepsProxy();
const res = createRes();
await loaded.mod.default(ordinaryRepsRequest(), res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(loaded.calls.token, 1);
assert.deepStrictEqual(loaded.calls.hash, [
"/api/file/getrepsblob?container=user-container"
]);
assert.strictEqual(
loaded.calls.axios[0][0],
"http://example.com/api/file/getrepsblob?container=user-container&hash=abc"
);
assert.deepStrictEqual(
JSON.parse(JSON.stringify(res.state.jsonBody)),
loaded.payload
);
});
const loadAdminRepsProxy = (overrides = {}) => {
const calls = { token: 0, hash: [], axios: [] };
const payload = { "@odata.count": 1, value: [{ id: "admin-r1" }] };
const mod = loadModule("pages/api/admin/getrepsblobproxy.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
readAdminSessionCookie: () => "admin-token",
validateAdminSession: () => true,
hasTrustedSameOrigin: () => true,
setAdminAuthNoStore: () => {},
getToken: async () => {
calls.token += 1;
return { access_token: "tok" };
},
azureHeaders: () => ({ headers: { Authorization: "Bearer tok" } }),
hashAPIPath: (queryPath) => {
calls.hash.push(queryPath);
return "&hash=abc";
},
consoleLogger: () => {},
process: { env: { API_ROOT: "http://example.com" } },
axios: {
get: async (...args) => {
calls.axios.push(args);
return { data: payload };
}
},
...overrides
});
return { mod, calls, payload };
};
const adminRepsRequest = (overrides = {}) => ({
method: "POST",
headers: { origin: "https://portal.example.test" },
body: { container: "selected-user-container" },
...overrides
});
for (const adminSessionCase of [
{
name: "missing administrative session",
overrides: {
readAdminSessionCookie: () => null,
validateAdminSession: () => false
}
},
{
name: "invalid administrative session",
overrides: { validateAdminSession: () => false }
},
{
name: "expired administrative session",
overrides: { validateAdminSession: () => false }
}
]) {
test(`admin getrepsblobproxy rejects ${adminSessionCase.name} before proxy work`, async () => {
const loaded = loadAdminRepsProxy(adminSessionCase.overrides);
const res = createRes();
await loaded.mod.default(adminRepsRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assert.strictEqual(res.state.jsonBody.error.code, "FORBIDDEN");
assertNoProxyWork(loaded.calls);
});
}
for (const originCase of ["missing Origin", "cross-origin request"]) {
test(`admin getrepsblobproxy rejects ${originCase} before proxy work`, async () => {
const loaded = loadAdminRepsProxy({ hasTrustedSameOrigin: () => false });
const req = adminRepsRequest();
if (originCase === "missing Origin") delete req.headers.origin;
if (originCase === "cross-origin request") {
req.headers.origin = "https://attacker.example";
}
const res = createRes();
await loaded.mod.default(req, res);
assert.strictEqual(res.state.statusCode, 403);
assertNoProxyWork(loaded.calls);
});
}
for (const containerCase of [
{ name: "missing container", body: {} },
{ name: "malformed container", body: { container: "Invalid_Container" } }
]) {
test(`admin getrepsblobproxy rejects ${containerCase.name} before proxy work`, async () => {
const loaded = loadAdminRepsProxy();
const res = createRes();
await loaded.mod.default(
adminRepsRequest({ body: containerCase.body }),
res
);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INVALID_REQUEST");
assertNoProxyWork(loaded.calls);
});
}
test("admin getrepsblobproxy rejects unsupported methods before proxy work", async () => {
const loaded = loadAdminRepsProxy();
const res = createRes();
await loaded.mod.default(adminRepsRequest({ method: "GET" }), res);
assert.strictEqual(res.state.statusCode, 405);
assert.strictEqual(res.state.jsonBody.error.code, "METHOD_NOT_ALLOWED");
assertNoProxyWork(loaded.calls);
});
test("ordinary NextAuth session alone cannot authorize admin getrepsblobproxy", async () => {
const loaded = loadAdminRepsProxy({
readAdminSessionCookie: () => null,
validateAdminSession: () => false,
getSession: async () => ({ user: { id: "selected-user-container" } })
});
const res = createRes();
await loaded.mod.default(adminRepsRequest(), res);
assert.strictEqual(res.state.statusCode, 403);
assertNoProxyWork(loaded.calls);
});
test("admin getrepsblobproxy preserves cross-container signed path and response", async () => {
const loaded = loadAdminRepsProxy();
const res = createRes();
await loaded.mod.default(adminRepsRequest(), res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(loaded.calls.token, 1);
assert.deepStrictEqual(loaded.calls.hash, [
"/api/file/getrepsblob?container=selected-user-container"
]);
assert.strictEqual(
loaded.calls.axios[0][0],
"http://example.com/api/file/getrepsblob?container=selected-user-container&hash=abc"
);
assert.deepStrictEqual(
JSON.parse(JSON.stringify(res.state.jsonBody)),
loaded.payload
);
});
test("admin storage refresh uses dedicated POST proxy while ordinary callers remain unchanged", () => {
const rootDir = path.resolve(__dirname, "..", "..");
const adminSource = fs.readFileSync(
path.join(rootDir, "components/admin/tabs/storage.js"),
"utf8"
);
const topThreeSource = fs.readFileSync(
path.join(rootDir, "components/myportal/topthree_reps.js"),
"utf8"
);
const viewAllSource = fs.readFileSync(
path.join(rootDir, "components/myportal/viewall.js"),
"utf8"
);
assert.match(adminSource, /fetch\("\/api\/admin\/getrepsblobproxy"/);
assert.match(
adminSource,
/fetch\("\/api\/admin\/getrepsblobproxy"[\s\S]*method:\s*"POST"[\s\S]*JSON\.stringify\(\{ container: containerID \}\)/
);
assert.doesNotMatch(adminSource, /getRepsFromBlobProxy/);
assert.match(topThreeSource, /getRepsFromBlobProxy\(containerID\)/);
assert.match(viewAllSource, /getRepsFromBlobProxy\(containerID\)/);
});
test("admin storage completion uses dedicated POST route while ordinary completion remains unchanged", () => {
const rootDir = path.resolve(__dirname, "..", "..");
const adminSource = fs.readFileSync(
path.join(rootDir, "components/admin/tabs/storage.js"),
"utf8"
);
const ordinarySource = fs.readFileSync(
path.join(
rootDir,
"components/case/representation/representationComplete.js"
),
"utf8"
);
assert.match(
adminSource,
/fetch\(\s*"\/api\/admin\/createrepcompletemessage",\s*\{\s*method:\s*"POST"/
);
assert.match(adminSource, /container:\s*c\.container/);
assert.match(
adminSource,
/tempcaseref:\s*folder\.repJsonBlob\.name\.split\(\s*"\/"\s*\)\[0\]/
);
assert.match(
adminSource,
/repid:\s*folder\.repJsonBlob\.name\.split\(\s*"\/"\s*\)\[1\]/
);
assert.match(adminSource, /if \(!response\.ok\)/);
assert.doesNotMatch(adminSource, /sendRepCompleteMessage/);
assert.match(ordinarySource, /sendRepCompleteMessage\(/);
});
test("getawaitingsubmissionfromblobproxy dependency failure returns GET_AWAITING_SUBMISSION_PROXY_FAILED", async () => {
const mod = loadModule(
"pages/api/file/getawaitingsubmissionfromblobproxy.js",
{
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
azureHeaders: () => ({ headers: {} }),
hashAPIPath: () => "&hash=abc",
consoleLogger: () => {},
process: { env: { API_ROOT: "http://example.com" } },
axios: {
get: async () => {
throw new Error("awaiting failed");
}
}
}
);
const req = {
query: { container: "c1" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"GET_AWAITING_SUBMISSION_PROXY_FAILED"
);
});
test("createappealcompletemessageproxy dependency failure returns CREATE_APPEAL_COMPLETE_MESSAGE_PROXY_FAILED", async () => {
const mod = loadModule(
"pages/api/file/createappealcompletemessageproxy_api.js",
{
nextConnect: createNextConnectMock(),
middleware: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
azureHeaders: () => ({ headers: {} }),
hashAPIPath: () => "&hash=abc",
consoleLogger: () => {},
process: { env: { API_ROOT: "http://example.com" } },
axios: {
get: async () => {
throw new Error("create appeal proxy failed");
}
}
}
);
const req = {
query: { container: "c1", tempcaseref: "TMP1" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"CREATE_APPEAL_COMPLETE_MESSAGE_PROXY_FAILED"
);
});
test("createappealcompletemessage_api accepts encoded hash variant and returns success", async () => {
const mod = loadModule(
"pages/api/file/createappealcompletemessage_api.js",
{
nextConnect: createNextConnectMock(),
middleware: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
hashAPIPath: (queryPath) =>
queryPath.includes("tempcaseref=TMP%2F1")
? "&hash=expected"
: "&hash=other",
getProgressBlobs: async () => ({ path: "TMP/1/progress.json" }),
downloadProgressFile: async (_container, path) => {
if (path.includes("_case.json")) {
return {
"customerid_contact@odata.bind":
"/contacts(00000000-0000-0000-0000-000000000001)"
};
}
return {
pinswg_siteaddressline1: "Line 1",
pinswg_siteaddressline2: "",
pinswg_siteaddresstown: "Town",
pinswg_siteaddresscounty: "County",
pinswg_siteaddresspostcode: "CF1 1AA",
pinswg_lpaapplicationreference: "LPA-1",
pinswg_developmentdescription: "Desc",
pinswg_name: "tmp"
};
},
createBlob: async () => ({}),
getCaseBlob: async () => ({}),
createCaseCompleteMessage: async () => ({}),
updateAccount: async () => ({}),
consoleLogger: () => {},
_: { isEmpty: (v) => v === undefined || v === null || v === "" }
}
);
const req = {
query: {
container: "cont/1",
tempcaseref: "TMP/1",
inv: "846040001",
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)), {
status: "success"
});
});
test("createappealcompletemessage_api dependency failure returns CREATE_APPEAL_COMPLETE_MESSAGE_FAILED", async () => {
const mod = loadModule(
"pages/api/file/createappealcompletemessage_api.js",
{
nextConnect: createNextConnectMock(),
middleware: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
hashAPIPath: () => "&hash=expected",
getProgressBlobs: async () => {
throw new Error("progress failed");
},
consoleLogger: () => {},
_: { isEmpty: () => true }
}
);
const req = {
query: {
container: "c1",
tempcaseref: "TMP1",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"CREATE_APPEAL_COMPLETE_MESSAGE_FAILED"
);
});
test("createcaseinvolvement returns record exists on 412 conflict", async () => {
const mod = loadModule("pages/api/file/createcaseinvolvement_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
hashAPIPath: () => "?hash=abc",
consoleLogger: () => {},
process: {
env: { CRMURL: "crm.example", RELAY_ROOT: "https://relay/" }
},
axios: async () => {
const error = new Error("exists");
error.status = 412;
throw error;
}
});
const req = {
body: { contactid: "c1", incidentid: "i1" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
record: "exists"
});
});
test("createrepinvolvement dependency failure returns CREATE_REP_INVOLVEMENT_FAILED", async () => {
const mod = loadModule("pages/api/file/createrepinvolvement_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
getPersonalAccount: async () => ({}),
hashAPIPath: () => "?hash=abc",
consoleLogger: () => {},
process: {
env: {
CRMURL: "crm.example",
CRMURL_VERSION: "v9.2",
RELAY_ROOT: "https://relay/"
}
},
axios: async () => {
throw new Error("rep involvement failed");
}
});
const req = {
body: { contactid: "c1", incidentid: "i1" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"CREATE_REP_INVOLVEMENT_FAILED"
);
});
test("updatecase dependency failure returns UPDATE_CASE_FAILED", async () => {
const mod = loadModule("pages/api/file/updatecase_api.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getToken: async () => ({ access_token: "tok" }),
hashAPIPath: () => "?hash=abc",
consoleLogger: () => {},
process: { env: { RELAY_ROOT: "https://relay/" } },
axios: async () => {
throw new Error("update failed");
}
});
const req = {
query: {
updateFormCollection: "pinswg_forms",
appealObj: "obj1",
incident: "inc1"
},
body: { field: "value" }
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "UPDATE_CASE_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 () => {
const mod = loadModule("pages/api/file/getbloblist.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getBlobs: async () => ({ id: "x" }),
getRepsFilesBlobs: async () => ({ id: "x" })
});
const req = {
query: { container: "c1", casefolderID: "f1", hash: "wrong" }
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
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(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
consoleLogger: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createContainer: async () => ({ ok: true })
});
const req = { query: { hash: "expected" } };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "MISSING_REQUIRED_QUERY");
});
test("setupcontainer handler accepts encoded ident hash variant", async () => {
const mod = loadModule("pages/api/file/setupcontainer.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: (queryPath) =>
queryPath.includes("ident=container%2Fname")
? "&hash=expected"
: "&hash=other",
consoleLogger: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createContainer: async () => ({ created: true })
});
const req = { query: { ident: "container/name", 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)), {
data: "success",
output: { created: true }
});
});
test("upload handler success returns data payload with 200", async () => {
const mod = loadModule("pages/api/file/upload.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "c1" } }),
hashAPIPath: () => "?hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createBlob: async () => ({ id: "blob-1" }),
createRepBlob: async () => ({ id: "rep-1" })
});
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-1" }
});
});
test("setupcontainer handler success returns expected contract", async () => {
const mod = loadModule("pages/api/file/setupcontainer.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
consoleLogger: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createContainer: async () => ({ created: true })
});
const req = { query: { ident: "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)), {
data: "success",
output: { created: true }
});
});
test("getbloblist handler success returns value array contract", async () => {
const mod = loadModule("pages/api/file/getbloblist.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
getBlobs: async () => ({ file: "a.pdf" }),
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, 200);
assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), {
value: [{ file: "a.pdf" }]
});
});
test("downloadblob handler success sets attachment header and returns body", async () => {
const mod = loadModule("pages/api/file/downloadblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
downloadFile: async () => Buffer.from("file-content")
});
const req = {
query: {
container: "c1",
casefolderID: "f1",
blobname: "doc.pdf",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 200);
assert.strictEqual(
res.state.headers["content-disposition"],
"attachment; filename=doc.pdf"
);
assert.strictEqual(res.state.sentBody.toString(), "file-content");
});
test("upload handler dependency failure returns UPLOAD_FAILED", async () => {
const mod = loadModule("pages/api/file/upload.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
getSession: async () => ({ user: { id: "c1" } }),
hashAPIPath: () => "?hash=expected",
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createBlob: async () => {
throw new Error("storage down");
},
createRepBlob: async () => ({ id: "rep-1" })
});
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, 400);
assert.strictEqual(res.state.jsonBody.error.code, "UPLOAD_FAILED");
});
test("setupcontainer dependency failure returns SETUP_CONTAINER_FAILED", async () => {
const mod = loadModule("pages/api/file/setupcontainer.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
consoleLogger: () => {},
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
createContainer: async () => {
throw new Error("container failed");
}
});
const req = { query: { ident: "cont-1", hash: "expected" } };
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "SETUP_CONTAINER_FAILED");
});
test("downloadblob dependency failure returns DOWNLOAD_BLOB_FAILED", async () => {
const mod = loadModule("pages/api/file/downloadblob.js", {
nextConnect: createNextConnectMock(),
middleware: () => {},
hashAPIPath: () => "&hash=expected",
respondError: respondErrorMock,
downloadFile: async () => {
throw new Error("download failed");
}
});
const req = {
query: {
container: "c1",
casefolderID: "f1",
blobname: "doc.pdf",
hash: "expected"
}
};
const res = createRes();
await mod.default.handler(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "DOWNLOAD_BLOB_FAILED");
});
test("generateappealpdfcopy returns INCIDENT_ID_REQUIRED when docProps missing", async () => {
const mod = loadModule("pages/api/file/generateappealpdfcopy.js", {
respondError: respondErrorMock,
getCaseByID: async () => [],
getPortalModuleDetails: async () => ({ value: [{}] }),
getAppealPDFDocs: async () => [],
getFormCollectionByID: () => ({ UrlName: "planningappeals78" }),
getPickLists: async () => ({}),
planningappeals78_pdf: () => ({}),
other_pdf: () => ({}),
pdf: () => ({ toBuffer: async () => Buffer.from("pdf") })
});
const req = { body: {} };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "INCIDENT_ID_REQUIRED");
});
test("generateappealpdfcopy returns CASE_NOT_FOUND when case lookup is empty", async () => {
const mod = loadModule("pages/api/file/generateappealpdfcopy.js", {
respondError: respondErrorMock,
getCaseByID: async () => [],
getPortalModuleDetails: async () => ({ value: [{}] }),
getAppealPDFDocs: async () => [],
getFormCollectionByID: () => ({ UrlName: "planningappeals78" }),
getPickLists: async () => ({}),
planningappeals78_pdf: () => ({}),
other_pdf: () => ({}),
pdf: () => ({ toBuffer: async () => Buffer.from("pdf") })
});
const req = { body: { docProps: "i1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 404);
assert.strictEqual(res.state.jsonBody.error.code, "CASE_NOT_FOUND");
});
test("generateappealpdfcopy returns FORM_COLLECTION_NOT_FOUND when collection is unresolved", async () => {
const mod = loadModule("pages/api/file/generateappealpdfcopy.js", {
respondError: respondErrorMock,
getCaseByID: async () => [
{
pinswg_appealcasetype: 846040000,
ticketnumber: "CAS-1",
description: "desc"
}
],
getPortalModuleDetails: async () => ({ value: [{}] }),
getAppealPDFDocs: async () => [],
getFormCollectionByID: () => null,
getPickLists: async () => ({}),
planningappeals78_pdf: () => ({}),
other_pdf: () => ({}),
pdf: () => ({ toBuffer: async () => Buffer.from("pdf") })
});
const req = { body: { docProps: "i1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"FORM_COLLECTION_NOT_FOUND"
);
});
test("generateappealpdfcopy success returns pdf buffer and headers", async () => {
const mod = loadModule("pages/api/file/generateappealpdfcopy.js", {
respondError: respondErrorMock,
getCaseByID: async () => [
{
pinswg_appealcasetype: 846040000,
ticketnumber: "CAS-1",
description: "desc"
}
],
getPortalModuleDetails: async () => ({ value: [{}] }),
getAppealPDFDocs: async () => [{ id: "d1" }],
getFormCollectionByID: () => ({
UrlName: "planningappeals78",
LogicalCollectionName: "pinswg_planningappeals78s"
}),
getPickLists: async () => ({ a: 1 }),
planningappeals78_pdf: () => ({ type: "pdfComponent" }),
other_pdf: () => ({ type: "otherComponent" }),
pdf: () => ({ toBuffer: async () => Buffer.from("pdf-content") })
});
const req = { body: { docProps: "i1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.headers["content-type"], "application/pdf");
assert.strictEqual(
typeof res.state.headers["content-disposition"],
"string"
);
assert.strictEqual(res.state.sentBody.toString(), "pdf-content");
});
test("generateappealpdfcopy catch path returns APPEAL_PDF_COPY_GENERATION_FAILED", async () => {
const mod = loadModule("pages/api/file/generateappealpdfcopy.js", {
respondError: respondErrorMock,
getCaseByID: async () => [
{
pinswg_appealcasetype: 846040000,
ticketnumber: "CAS-1",
description: "desc"
}
],
getPortalModuleDetails: async () => ({ value: [{}] }),
getAppealPDFDocs: async () => [{ id: "d1" }],
getFormCollectionByID: () => ({
UrlName: "planningappeals78",
LogicalCollectionName: "pinswg_planningappeals78s"
}),
getPickLists: async () => ({ a: 1 }),
planningappeals78_pdf: () => ({ type: "pdfComponent" }),
other_pdf: () => ({ type: "otherComponent" }),
pdf: () => ({
toBuffer: async () => {
throw new Error("pdf failure");
}
})
});
const req = { body: { docProps: "i1" } };
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"APPEAL_PDF_COPY_GENERATION_FAILED"
);
});
test("generatepdf catch path returns GENERATE_PDF_FAILED", async () => {
const { Readable } = require("stream");
const mod = loadModule("pages/api/file/generatepdf.js", {
Buffer,
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
hashAPIPath: () => "&hash=expected",
consoleLogger: () => {},
ReactPDF: {
renderToStream: async () => Readable.from(["pdf"])
},
createRepPDFBlob: async () => {
throw new Error("pdf write failed");
},
other_pdf: () => ({ type: "other" })
});
const req = {
query: { hash: "expected", download: "true" },
body: {
representationType: "Other",
repfile_name: "rep-file",
containerID: "c1",
casefolderID: "f1",
caseRef: "CAS-1"
}
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(res.state.jsonBody.error.code, "GENERATE_PDF_FAILED");
});
test("generateappealpdf catch path returns GENERATE_APPEAL_PDF_FAILED", async () => {
const mod = loadModule("pages/api/file/generateappealpdf.js", {
respondError: respondErrorMock,
respondSuccess: respondSuccessMock,
hashAPIPath: () => "&hash=expected",
getTempCaseBlob: async () => ({ ticketnumber: "CAS-1" }),
getProgressBlobs: async () => ({ path: "CAS-1/progress.json" }),
downloadProgressFile: async () => {
throw new Error("progress failed");
},
getPickLists: async () => ({}),
planningappeals78_pdf: () => ({ type: "planning" }),
other_pdf: () => ({ type: "other" }),
ReactPDF: {
renderToStream: async () => {
throw new Error("render failed");
}
}
});
const req = {
query: { hash: "expected", appealType: "846040000" },
body: {
containerID: "c1",
casefolderID: "f1",
caseRef: "CAS-1",
filesList: []
}
};
const res = createRes();
await mod.default(req, res);
assert.strictEqual(res.state.statusCode, 400);
assert.strictEqual(
res.state.jsonBody.error.code,
"GENERATE_APPEAL_PDF_FAILED"
);
});
const run = async () => {
let passed = 0;
for (const currentTest of tests) {
await currentTest.fn();
passed += 1;
}
console.log(
`Phase 21 file-handler contract tests passed (${passed}/${tests.length}).`
);
};
module.exports = run;
if (require.main === module) {
run().catch((error) => {
console.error(error);
process.exit(1);
});
}