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`.
|
||||
- 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
|
||||
@@ -13,6 +13,14 @@ import { respondSuccess } from "../middleware/apiResponse";
|
||||
*/
|
||||
|
||||
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, {
|
||||
"value": [
|
||||
{
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
import { respondSuccess } from "../middleware/apiResponse";
|
||||
import { respondError, respondSuccess } from "../middleware/apiResponse";
|
||||
|
||||
// export default async function ApiProxy(req, res) {
|
||||
// var caseid = req.query.caseid;
|
||||
// var token = await getToken();
|
||||
export default function ApiProxy(req, res) {
|
||||
if (req.method && req.method !== "GET") {
|
||||
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 = {
|
||||
"@odata.count": 4,
|
||||
"value": [
|
||||
|
||||
@@ -10,10 +10,10 @@ const ApiProxy = nextConnect();
|
||||
ApiProxy.use(middleware);
|
||||
|
||||
ApiProxy.get(async (req, res) => {
|
||||
var containerName = req.query.container;
|
||||
var casefolderID = req.query.casefolderID;
|
||||
var blobName = req.query.blobname;
|
||||
var checkHash = req.query.hash;
|
||||
const containerName = req.query.container;
|
||||
const casefolderID = req.query.casefolderID;
|
||||
const blobName = req.query.blobname;
|
||||
const checkHash = req.query.hash;
|
||||
|
||||
if (
|
||||
typeof containerName === "undefined" ||
|
||||
@@ -32,7 +32,7 @@ ApiProxy.get(async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
var checkquerypath =
|
||||
const checkquerypath =
|
||||
"/api/file/downloadblob?container=" +
|
||||
containerName +
|
||||
"&casefolderID=" +
|
||||
@@ -48,34 +48,29 @@ ApiProxy.get(async (req, res) => {
|
||||
});
|
||||
}
|
||||
|
||||
const bloblocation =
|
||||
casefolderID + (blobName.indexOf(".json") > 0 ? "/" : "/files/");
|
||||
try {
|
||||
const bloblocation =
|
||||
casefolderID + (blobName.indexOf(".json") > 0 ? "/" : "/files/");
|
||||
|
||||
const downloaded = await downloadFile(
|
||||
containerName,
|
||||
bloblocation + decodeURI(blobName)
|
||||
);
|
||||
const downloaded = await downloadFile(
|
||||
containerName,
|
||||
bloblocation + decodeURI(blobName)
|
||||
);
|
||||
|
||||
res.setHeader(
|
||||
"content-disposition",
|
||||
"attachment; filename=" + decodeURI(blobName)
|
||||
);
|
||||
return res.status(200).send(downloaded);
|
||||
res.setHeader(
|
||||
"content-disposition",
|
||||
"attachment; filename=" + decodeURI(blobName)
|
||||
);
|
||||
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 = {
|
||||
api: {
|
||||
bodyParser: false
|
||||
|
||||
@@ -1,45 +1,17 @@
|
||||
import {
|
||||
createContainer,
|
||||
getContainers,
|
||||
getBlobs,
|
||||
createRepBlob,
|
||||
uploadFile,
|
||||
uploadPDFAppealFiles,
|
||||
downloadProgressFile,
|
||||
getProgressBlobs,
|
||||
getTempCaseBlob,
|
||||
createAppealPDFBlob
|
||||
} from "../../../actions/azurestorage";
|
||||
import {
|
||||
getCaseByID,
|
||||
getPortalModuleDetails,
|
||||
getAppealPDFDocs
|
||||
} from "../../../actions/services/caseService";
|
||||
import { getFormCollectionByID } from "../../../components/utils";
|
||||
import ReactPDF, {
|
||||
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 { pdf } from "@react-pdf/renderer";
|
||||
import { getPickLists } from "../../../actions/services/referenceDataService";
|
||||
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 { respondError } from "../middleware/apiResponse";
|
||||
|
||||
const getDetails = (resultsObj, detailsType) => {
|
||||
const getDetails = (resultsObj) => {
|
||||
if (!Array.isArray(resultsObj)) {
|
||||
console.warn("getDetails: resultsObj is not an array");
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
@@ -51,7 +23,6 @@ const getDetails = (resultsObj, detailsType) => {
|
||||
|
||||
const form = getFormCollectionByID(appealType);
|
||||
if (!form) {
|
||||
console.warn("No form found for appeal type:", appealType);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -64,54 +35,74 @@ const getDetails = (resultsObj, detailsType) => {
|
||||
|
||||
export default async function handler(req, res) {
|
||||
const incidentid = req.body?.docProps;
|
||||
if (!incidentid) return res.status(400).send("Missing incident ID");
|
||||
|
||||
const caseObj = await getCaseByID(incidentid);
|
||||
if (!Array.isArray(caseObj) || caseObj.length === 0)
|
||||
return res.status(404).send("Case not found");
|
||||
|
||||
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;
|
||||
if (!incidentid) {
|
||||
return respondError(res, {
|
||||
status: 400,
|
||||
code: "INCIDENT_ID_REQUIRED",
|
||||
message: "Incident id is required"
|
||||
});
|
||||
}
|
||||
|
||||
const whichForm = getFormCollectionByID(caseObj[0].pinswg_appealcasetype);
|
||||
const pickListData = await getPickLists(whichForm.UrlName);
|
||||
try {
|
||||
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 = {
|
||||
846040000: planningappeals78_pdf,
|
||||
846040004: planningappeals78_pdf
|
||||
};
|
||||
const caseDetailsObj = await getDetails(caseObj);
|
||||
const fileList = (await getAppealPDFDocs(incidentid)) || [];
|
||||
|
||||
//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 type = caseObj[0].pinswg_appealcasetype;
|
||||
const renderDocument = appealTypeMap[type] || other_pdf;
|
||||
return renderDocument(values);
|
||||
};
|
||||
const whichForm = getFormCollectionByID(
|
||||
caseObj[0].pinswg_appealcasetype
|
||||
);
|
||||
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({
|
||||
docProps: caseDetailsObj[0].value[0],
|
||||
pickListData
|
||||
});
|
||||
const appealTypeMap = {
|
||||
846040000: planningappeals78_pdf,
|
||||
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 formattedDate = repDate.toISOString().split("T")[0];
|
||||
const pdfBuffer = await pdf(pdfComponent).toBuffer();
|
||||
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
`attachment; filename=${formattedDate}_-_Appeal_Form.pdf`
|
||||
);
|
||||
res.send(pdfBuffer);
|
||||
const repDate = new Date();
|
||||
const formattedDate = repDate.toISOString().split("T")[0];
|
||||
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader(
|
||||
"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);
|
||||
});
|
||||
|
||||
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 () => {
|
||||
let passed = 0;
|
||||
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");
|
||||
});
|
||||
|
||||
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 () => {
|
||||
let passed = 0;
|
||||
for (const currentTest of tests) {
|
||||
|
||||
Reference in New Issue
Block a user