TASK22224: harden file/static endpoint contracts and phase21 coverage
This commit is contained in:
@@ -129,3 +129,49 @@ Follow-ups:
|
|||||||
|
|
||||||
- Continue with remaining non-standard API outlier(s), notably `pages/api/file/generateappealpdfcopy.js`.
|
- Continue with remaining non-standard API outlier(s), notably `pages/api/file/generateappealpdfcopy.js`.
|
||||||
- Keep future slices logged in this file at commit-time now that memory-bank is versioned.
|
- Keep future slices logged in this file at commit-time now that memory-bank is versioned.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CL-004: TASK22224 file + static endpoint contract hardening bundle (phase21)
|
||||||
|
|
||||||
|
date: 2026-03-23
|
||||||
|
author: Cline
|
||||||
|
scope: `pages/api/file/{downloadblob,generateappealpdfcopy}.js`, `pages/api/endpoint/{getsipsmedia_api,getappealtypesfornewappeal_api}.js`, `tests/phase21/{file-handler-contract,endpoint-handler-contract}.test.cjs`
|
||||||
|
type: change
|
||||||
|
rationale: Deliver the agreed larger bounded slice for remaining non-standard file/static handlers, improving negative-path consistency while preserving current success payload behavior.
|
||||||
|
impact: Standardized error envelopes/codes for download and generated PDF copy flows, method guard parity for static endpoints, and expanded phase21 contract coverage for both file and endpoint handlers.
|
||||||
|
status: completed
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
- `downloadblob.js`:
|
||||||
|
- added explicit catch-path response via `respondError` with `DOWNLOAD_BLOB_FAILED`
|
||||||
|
- kept success behavior intact (attachment header + raw file body)
|
||||||
|
- removed dead internal helper (`streamToBuffer`) and tightened local declarations
|
||||||
|
- `generateappealpdfcopy.js`:
|
||||||
|
- removed unused imports/noisy console warnings
|
||||||
|
- standardized required-input and negative-path contracts:
|
||||||
|
- `INCIDENT_ID_REQUIRED` (400)
|
||||||
|
- `CASE_NOT_FOUND` (404)
|
||||||
|
- `FORM_COLLECTION_NOT_FOUND` (400)
|
||||||
|
- `APPEAL_PDF_COPY_GENERATION_FAILED` (400)
|
||||||
|
- preserved success output contract (PDF content headers + buffer body)
|
||||||
|
- `getsipsmedia_api.js` and `getappealtypesfornewappeal_api.js`:
|
||||||
|
- added method guard for non-GET requests using `METHOD_NOT_ALLOWED` (405)
|
||||||
|
- preserved existing GET success payloads
|
||||||
|
- Expanded phase21 tests:
|
||||||
|
- `file-handler-contract.test.cjs`: added coverage for download failure + full generated PDF copy contract/negative paths
|
||||||
|
- `endpoint-handler-contract.test.cjs`: added method guard tests for both static endpoints
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
|
||||||
|
- `node tests/phase21/api-contract-slice1.test.cjs` -> pass
|
||||||
|
- helper: 4/4
|
||||||
|
- file-handler: 17/17
|
||||||
|
- email-handler: 12/12
|
||||||
|
- endpoint-handler: 149/149
|
||||||
|
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps`; no new lint errors)
|
||||||
|
|
||||||
|
Follow-ups:
|
||||||
|
|
||||||
|
- If desired, next slice can target remaining file-route parity candidates outside this bundle, but this closes the planned TASK22224 scope.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { respondSuccess } from "../middleware/apiResponse";
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
@@ -13,6 +13,14 @@ import { respondSuccess } from "../middleware/apiResponse";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const ApiResponse = (req, res) => {
|
const ApiResponse = (req, res) => {
|
||||||
|
if (req.method && req.method !== "GET") {
|
||||||
|
return respondError(res, {
|
||||||
|
status: 405,
|
||||||
|
code: "METHOD_NOT_ALLOWED",
|
||||||
|
message: "Only GET is supported"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return respondSuccess(res, {
|
return respondSuccess(res, {
|
||||||
"value": [
|
"value": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,29 +1,14 @@
|
|||||||
import { respondSuccess } from "../middleware/apiResponse";
|
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||||
|
|
||||||
// export default async function ApiProxy(req, res) {
|
export default function ApiProxy(req, res) {
|
||||||
// var caseid = req.query.caseid;
|
if (req.method && req.method !== "GET") {
|
||||||
// var token = await getToken();
|
return respondError(res, {
|
||||||
|
status: 405,
|
||||||
|
code: "METHOD_NOT_ALLOWED",
|
||||||
|
message: "Only GET is supported"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// var queryUrl =
|
|
||||||
// "pinswg_sipsevents?$filter=_pinswg_sipseventsid_value eq " +
|
|
||||||
// caseid +
|
|
||||||
// "&$count=true";
|
|
||||||
|
|
||||||
// return axios
|
|
||||||
// .get(
|
|
||||||
// WEBAPI_URL + queryUrl + hashAPIPath(queryUrl),
|
|
||||||
// azureHeaders(token.access_token),
|
|
||||||
// )
|
|
||||||
// .then(({ data }) => {
|
|
||||||
// res.status(200).json(data);
|
|
||||||
// })
|
|
||||||
// .catch((error) => {
|
|
||||||
// consoleLogger(error);
|
|
||||||
// res.status(400).json(error);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
export default function ApiProx(req, res) {
|
|
||||||
const data = {
|
const data = {
|
||||||
"@odata.count": 4,
|
"@odata.count": 4,
|
||||||
"value": [
|
"value": [
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ const ApiProxy = nextConnect();
|
|||||||
ApiProxy.use(middleware);
|
ApiProxy.use(middleware);
|
||||||
|
|
||||||
ApiProxy.get(async (req, res) => {
|
ApiProxy.get(async (req, res) => {
|
||||||
var containerName = req.query.container;
|
const containerName = req.query.container;
|
||||||
var casefolderID = req.query.casefolderID;
|
const casefolderID = req.query.casefolderID;
|
||||||
var blobName = req.query.blobname;
|
const blobName = req.query.blobname;
|
||||||
var checkHash = req.query.hash;
|
const checkHash = req.query.hash;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
typeof containerName === "undefined" ||
|
typeof containerName === "undefined" ||
|
||||||
@@ -32,7 +32,7 @@ ApiProxy.get(async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var checkquerypath =
|
const checkquerypath =
|
||||||
"/api/file/downloadblob?container=" +
|
"/api/file/downloadblob?container=" +
|
||||||
containerName +
|
containerName +
|
||||||
"&casefolderID=" +
|
"&casefolderID=" +
|
||||||
@@ -48,34 +48,29 @@ ApiProxy.get(async (req, res) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const bloblocation =
|
try {
|
||||||
casefolderID + (blobName.indexOf(".json") > 0 ? "/" : "/files/");
|
const bloblocation =
|
||||||
|
casefolderID + (blobName.indexOf(".json") > 0 ? "/" : "/files/");
|
||||||
|
|
||||||
const downloaded = await downloadFile(
|
const downloaded = await downloadFile(
|
||||||
containerName,
|
containerName,
|
||||||
bloblocation + decodeURI(blobName)
|
bloblocation + decodeURI(blobName)
|
||||||
);
|
);
|
||||||
|
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
"content-disposition",
|
"content-disposition",
|
||||||
"attachment; filename=" + decodeURI(blobName)
|
"attachment; filename=" + decodeURI(blobName)
|
||||||
);
|
);
|
||||||
return res.status(200).send(downloaded);
|
return res.status(200).send(downloaded);
|
||||||
|
} catch (error) {
|
||||||
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "DOWNLOAD_BLOB_FAILED",
|
||||||
|
message: "Unable to download blob"
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function streamToBuffer(readableStream) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const chunks = [];
|
|
||||||
readableStream.on("data", (data) => {
|
|
||||||
chunks.push(data instanceof Buffer ? data : Buffer.from(data));
|
|
||||||
});
|
|
||||||
readableStream.on("end", () => {
|
|
||||||
resolve(Buffer.concat(chunks));
|
|
||||||
});
|
|
||||||
readableStream.on("error", reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
bodyParser: false
|
bodyParser: false
|
||||||
|
|||||||
@@ -1,45 +1,17 @@
|
|||||||
import {
|
|
||||||
createContainer,
|
|
||||||
getContainers,
|
|
||||||
getBlobs,
|
|
||||||
createRepBlob,
|
|
||||||
uploadFile,
|
|
||||||
uploadPDFAppealFiles,
|
|
||||||
downloadProgressFile,
|
|
||||||
getProgressBlobs,
|
|
||||||
getTempCaseBlob,
|
|
||||||
createAppealPDFBlob
|
|
||||||
} from "../../../actions/azurestorage";
|
|
||||||
import {
|
import {
|
||||||
getCaseByID,
|
getCaseByID,
|
||||||
getPortalModuleDetails,
|
getPortalModuleDetails,
|
||||||
getAppealPDFDocs
|
getAppealPDFDocs
|
||||||
} from "../../../actions/services/caseService";
|
} from "../../../actions/services/caseService";
|
||||||
import { getFormCollectionByID } from "../../../components/utils";
|
import { getFormCollectionByID } from "../../../components/utils";
|
||||||
import ReactPDF, {
|
import { pdf } from "@react-pdf/renderer";
|
||||||
Document,
|
|
||||||
Page,
|
|
||||||
Text,
|
|
||||||
View,
|
|
||||||
StyleSheet,
|
|
||||||
PDFViewer,
|
|
||||||
pdf
|
|
||||||
} from "@react-pdf/renderer";
|
|
||||||
|
|
||||||
import middleware from "../middleware/middleware";
|
|
||||||
import nextConnect from "next-connect";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { getPickLists } from "../../../actions/services/referenceDataService";
|
import { getPickLists } from "../../../actions/services/referenceDataService";
|
||||||
import { planningappeals78_pdf } from "../../../components/pdftemplates/planningappeals78_pdf";
|
import { planningappeals78_pdf } from "../../../components/pdftemplates/planningappeals78_pdf";
|
||||||
import { finalComments_pdf } from "../../../components/pdftemplates/finalComments_pdf";
|
|
||||||
import { statement_pdf } from "../../../components/pdftemplates/statement_pdf";
|
|
||||||
import { writtenStatement_pdf } from "../../../components/pdftemplates/writtenStatement_pdf";
|
|
||||||
import { other_pdf } from "../../../components/pdftemplates/other_pdf";
|
import { other_pdf } from "../../../components/pdftemplates/other_pdf";
|
||||||
|
import { respondError } from "../middleware/apiResponse";
|
||||||
|
|
||||||
const getDetails = (resultsObj, detailsType) => {
|
const getDetails = (resultsObj) => {
|
||||||
if (!Array.isArray(resultsObj)) {
|
if (!Array.isArray(resultsObj)) {
|
||||||
console.warn("getDetails: resultsObj is not an array");
|
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +23,6 @@ const getDetails = (resultsObj, detailsType) => {
|
|||||||
|
|
||||||
const form = getFormCollectionByID(appealType);
|
const form = getFormCollectionByID(appealType);
|
||||||
if (!form) {
|
if (!form) {
|
||||||
console.warn("No form found for appeal type:", appealType);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,54 +35,74 @@ const getDetails = (resultsObj, detailsType) => {
|
|||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
const incidentid = req.body?.docProps;
|
const incidentid = req.body?.docProps;
|
||||||
if (!incidentid) return res.status(400).send("Missing incident ID");
|
if (!incidentid) {
|
||||||
|
return respondError(res, {
|
||||||
const caseObj = await getCaseByID(incidentid);
|
status: 400,
|
||||||
if (!Array.isArray(caseObj) || caseObj.length === 0)
|
code: "INCIDENT_ID_REQUIRED",
|
||||||
return res.status(404).send("Case not found");
|
message: "Incident id is required"
|
||||||
|
});
|
||||||
const caseDetailsObj = await getDetails(caseObj, "myCases");
|
|
||||||
const fileList = (await getAppealPDFDocs(incidentid)) || [];
|
|
||||||
|
|
||||||
// Inject into the first item in `value` array
|
|
||||||
|
|
||||||
if (caseDetailsObj[0]?.value?.[0]) {
|
|
||||||
caseDetailsObj[0].value[0].pinswg_developmentdescription =
|
|
||||||
caseObj[0].description;
|
|
||||||
caseDetailsObj[0].value[0].caseObj = caseObj[0];
|
|
||||||
caseDetailsObj[0].value[0].filesList = fileList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const whichForm = getFormCollectionByID(caseObj[0].pinswg_appealcasetype);
|
try {
|
||||||
const pickListData = await getPickLists(whichForm.UrlName);
|
const caseObj = await getCaseByID(incidentid);
|
||||||
|
if (!Array.isArray(caseObj) || caseObj.length === 0) {
|
||||||
|
return respondError(res, {
|
||||||
|
status: 404,
|
||||||
|
code: "CASE_NOT_FOUND",
|
||||||
|
message: "Case not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const appealTypeMap = {
|
const caseDetailsObj = await getDetails(caseObj);
|
||||||
846040000: planningappeals78_pdf,
|
const fileList = (await getAppealPDFDocs(incidentid)) || [];
|
||||||
846040004: planningappeals78_pdf
|
|
||||||
};
|
|
||||||
|
|
||||||
//console.log(caseDetailsObj[0].value[0]);
|
if (caseDetailsObj[0]?.value?.[0]) {
|
||||||
|
caseDetailsObj[0].value[0].pinswg_developmentdescription =
|
||||||
|
caseObj[0].description;
|
||||||
|
caseDetailsObj[0].value[0].caseObj = caseObj[0];
|
||||||
|
caseDetailsObj[0].value[0].filesList = fileList;
|
||||||
|
}
|
||||||
|
|
||||||
const MyDocument = (values) => {
|
const whichForm = getFormCollectionByID(
|
||||||
const type = caseObj[0].pinswg_appealcasetype;
|
caseObj[0].pinswg_appealcasetype
|
||||||
const renderDocument = appealTypeMap[type] || other_pdf;
|
);
|
||||||
return renderDocument(values);
|
if (!whichForm?.UrlName) {
|
||||||
};
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "FORM_COLLECTION_NOT_FOUND",
|
||||||
|
message: "Unable to resolve form collection"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const pickListData = await getPickLists(whichForm.UrlName);
|
||||||
|
|
||||||
const pdfComponent = MyDocument({
|
const appealTypeMap = {
|
||||||
docProps: caseDetailsObj[0].value[0],
|
846040000: planningappeals78_pdf,
|
||||||
pickListData
|
846040004: planningappeals78_pdf
|
||||||
});
|
};
|
||||||
|
|
||||||
const pdfBuffer = await pdf(pdfComponent).toBuffer();
|
const renderDocument =
|
||||||
|
appealTypeMap[caseObj[0].pinswg_appealcasetype] || other_pdf;
|
||||||
|
const pdfComponent = renderDocument({
|
||||||
|
docProps: caseDetailsObj?.[0]?.value?.[0] || {},
|
||||||
|
pickListData
|
||||||
|
});
|
||||||
|
|
||||||
const repDate = new Date();
|
const pdfBuffer = await pdf(pdfComponent).toBuffer();
|
||||||
const formattedDate = repDate.toISOString().split("T")[0];
|
|
||||||
|
|
||||||
res.setHeader("Content-Type", "application/pdf");
|
const repDate = new Date();
|
||||||
res.setHeader(
|
const formattedDate = repDate.toISOString().split("T")[0];
|
||||||
"Content-Disposition",
|
|
||||||
`attachment; filename=${formattedDate}_-_Appeal_Form.pdf`
|
res.setHeader("Content-Type", "application/pdf");
|
||||||
);
|
res.setHeader(
|
||||||
res.send(pdfBuffer);
|
"Content-Disposition",
|
||||||
|
`attachment; filename=${formattedDate}_-_Appeal_Form.pdf`
|
||||||
|
);
|
||||||
|
return res.send(pdfBuffer);
|
||||||
|
} catch (error) {
|
||||||
|
return respondError(res, {
|
||||||
|
status: 400,
|
||||||
|
code: "APPEAL_PDF_COPY_GENERATION_FAILED",
|
||||||
|
message: "Unable to generate appeal PDF copy"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3688,6 +3688,37 @@ test("getsipsmedia returns success contract", async () => {
|
|||||||
assert.strictEqual(res.state.jsonBody["@odata.count"], 4);
|
assert.strictEqual(res.state.jsonBody["@odata.count"], 4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getsipsmedia returns METHOD_NOT_ALLOWED for non-GET methods", async () => {
|
||||||
|
const mod = loadModule("pages/api/endpoint/getsipsmedia_api.js", {
|
||||||
|
respondSuccess: respondSuccessMock,
|
||||||
|
respondError: respondErrorMock
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { method: "POST", query: {} };
|
||||||
|
const res = createRes();
|
||||||
|
await mod.default(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.state.statusCode, 405);
|
||||||
|
assert.strictEqual(res.state.jsonBody.error.code, "METHOD_NOT_ALLOWED");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getappealtypesfornewappeal returns METHOD_NOT_ALLOWED for non-GET methods", async () => {
|
||||||
|
const mod = loadModule(
|
||||||
|
"pages/api/endpoint/getappealtypesfornewappeal_api.js",
|
||||||
|
{
|
||||||
|
respondSuccess: respondSuccessMock,
|
||||||
|
respondError: respondErrorMock
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const req = { method: "POST", query: {} };
|
||||||
|
const res = createRes();
|
||||||
|
await mod.default(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.state.statusCode, 405);
|
||||||
|
assert.strictEqual(res.state.jsonBody.error.code, "METHOD_NOT_ALLOWED");
|
||||||
|
});
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
let passed = 0;
|
let passed = 0;
|
||||||
for (const currentTest of tests) {
|
for (const currentTest of tests) {
|
||||||
|
|||||||
@@ -263,6 +263,175 @@ test("setupcontainer dependency failure returns SETUP_CONTAINER_FAILED", async (
|
|||||||
assert.strictEqual(res.state.jsonBody.error.code, "SETUP_CONTAINER_FAILED");
|
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"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
let passed = 0;
|
let passed = 0;
|
||||||
for (const currentTest of tests) {
|
for (const currentTest of tests) {
|
||||||
|
|||||||
Reference in New Issue
Block a user