From cb69bbe732bda3ad9258ec9b869280443e6e810b Mon Sep 17 00:00:00 2001 From: robbond Date: Mon, 23 Mar 2026 12:11:16 +0000 Subject: [PATCH] TASK22211: normalize case update and crm task endpoint contracts --- pages/api/endpoint/createcrmtask_api.js | 30 ++-- pages/api/endpoint/patchcase_api.js | 71 ++++----- pages/api/endpoint/updatecase_api.js | 88 +++++++---- .../endpoint-handler-contract.test.cjs | 137 ++++++++++++++++++ 4 files changed, 247 insertions(+), 79 deletions(-) diff --git a/pages/api/endpoint/createcrmtask_api.js b/pages/api/endpoint/createcrmtask_api.js index 37dca717..af2f66b1 100644 --- a/pages/api/endpoint/createcrmtask_api.js +++ b/pages/api/endpoint/createcrmtask_api.js @@ -42,35 +42,35 @@ */ import axios from "axios"; -import CryptoJS from "crypto-js"; -import _ from "lodash"; import { isNonEmptyString, sanitizeString } from "../../../actions/core/guards"; import { consoleLogger } from "../../../actions/core/logger"; import { getToken } from "../../../actions/core/token"; -import { getCaseBlob } from "../../../actions/azurestorage"; import { hashAPIPath } from "../../../actions/core/hash"; +import { respondError, respondSuccess } from "../middleware/apiResponse"; const WEBAPI_URL = process.env.RELAY_ROOT || "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; export default async function ApiProxy(req, res) { - var createTaskBody = req.body; + const createTaskBody = req.body; const contactEmail = sanitizeString(createTaskBody?.contactEmail); const contactSubject = sanitizeString(createTaskBody?.contactSubject); const contactBody = sanitizeString(createTaskBody?.contactbody); if (!isNonEmptyString(contactEmail) || !isNonEmptyString(contactSubject)) { - return res.status(400).json({ - error: "contactEmail and contactSubject are required" + return respondError(res, { + status: 400, + code: "CONTACT_EMAIL_AND_SUBJECT_REQUIRED", + message: "contactEmail and contactSubject are required" }); } - var queryUrl = "tasks"; - var token = await getToken(); + const queryUrl = "tasks"; + const token = await getToken(); - let contactValue = createTaskBody.typeOfContact; + const contactValue = createTaskBody.typeOfContact; const teamMap = { plq: "ec48450c-7e26-ec11-a97f-00224800e98c", //PET @@ -99,7 +99,7 @@ export default async function ApiProxy(req, res) { //console.log("/////Create Task:\n", payload, "\n//////////////"); - var config = { + const config = { method: "post", url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), headers: { @@ -117,11 +117,13 @@ export default async function ApiProxy(req, res) { try { const { data } = await axios(config); - return res.status(200).json(data); + return respondSuccess(res, data); } catch (error) { consoleLogger(error); - return res - .status(400) - .json({ error: "Failed to create CRM task", details: error }); + return respondError(res, { + status: 400, + code: "CRM_TASK_CREATE_FAILED", + message: "Failed to create CRM task" + }); } } diff --git a/pages/api/endpoint/patchcase_api.js b/pages/api/endpoint/patchcase_api.js index 7daa1b1d..6d2bb2e9 100644 --- a/pages/api/endpoint/patchcase_api.js +++ b/pages/api/endpoint/patchcase_api.js @@ -11,51 +11,54 @@ */ import axios from "axios"; -import CryptoJS from "crypto-js"; -import _ from "lodash"; import { consoleLogger } from "../../../actions/core/logger"; import { getToken } from "../../../actions/core/token"; import { hashAPIPath } from "../../../actions/core/hash"; +import { respondError, respondSuccess } from "../middleware/apiResponse"; const WEBAPI_URL = process.env.RELAY_ROOT || "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; export default async function ApiProxy(req, res) { - var incidentID = req.query.incidentid; - var queryUrl = "incidents(" + incidentID + ")"; - var token = await getToken(); + const incidentID = req.query.incidentid; - var data = JSON.stringify({ - "servicestage": 0 - }); + if (typeof incidentID !== "string" || incidentID.trim().length === 0) { + return respondError(res, { + status: 400, + code: "INCIDENT_ID_REQUIRED", + message: "incidentid is required" + }); + } - //console.log(data, WEBAPI_URL + queryUrl); + try { + const queryUrl = "incidents(" + incidentID + ")"; + const token = await getToken(); - var config = { - method: "patch", - url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - headers: { - "OData-MaxVersion": "4.0", - "OData-Version": "4.0", - "Accept": "application/json", - "Prefer": 'odata.include-annotations="*",return=representation', - "Authorization": "Bearer " + token.access_token, - "Content-Type": "application/json" - }, - data: data - }; + const config = { + method: "patch", + url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), + headers: { + "OData-MaxVersion": "4.0", + "OData-Version": "4.0", + "Accept": "application/json", + "Prefer": 'odata.include-annotations="*",return=representation', + "Authorization": "Bearer " + token.access_token, + "Content-Type": "application/json" + }, + data: JSON.stringify({ + "servicestage": 0 + }) + }; - var apiResponse = _.isEmpty(req.query) - ? res.status(400).json() - : axios(config) - .then(({ data }) => { - res.status(200).json(data); - }) - .catch((error) => { - consoleLogger(error); - res.status(400).json(error); - }); - - return apiResponse; + const { data } = await axios(config); + return respondSuccess(res, data); + } catch (error) { + consoleLogger(error); + return respondError(res, { + status: 400, + code: "CASE_PATCH_FAILED", + message: "Failed to patch case" + }); + } } diff --git a/pages/api/endpoint/updatecase_api.js b/pages/api/endpoint/updatecase_api.js index 87038c5d..3685abbb 100644 --- a/pages/api/endpoint/updatecase_api.js +++ b/pages/api/endpoint/updatecase_api.js @@ -11,45 +11,71 @@ */ import axios from "axios"; -import CryptoJS from "crypto-js"; +import { consoleLogger } from "../../../actions/core/logger"; import { getToken } from "../../../actions/core/token"; import { hashAPIPath } from "../../../actions/core/hash"; +import { respondError, respondSuccess } from "../middleware/apiResponse"; const WEBAPI_URL = process.env.RELAY_ROOT || "https://dev-pedw-ns.servicebus.windows.net/dev-pedw-hc/"; export default async function ApiProxy(req, res) { - var data = JSON.stringify(req.body); - var appealObj = req.query.appealObj; - var incidentID = req.query.incident; + const updatePayload = req.body; + const appealObj = req.query.appealObj; + const updateFormCollection = req.query.updateFormCollection; - var updateFormCollection = req.query.updateFormCollection; - var queryUrl = updateFormCollection + "(" + appealObj + ")"; - - var token = await getToken(); - - var config = { - method: "patch", - url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - headers: { - "OData-MaxVersion": "4.0", - "OData-Version": "4.0", - "Accept": "application/json", - "Prefer": 'odata.include-annotations="*",return=representation', - "Authorization": "Bearer " + token.access_token, - "Content-Type": "application/json" - }, - data: data - }; - - //console.log("update case:", data); - - return axios(config) - .then(({ data }) => { - res.status(200).json(data); - }) - .catch((error) => { - res.status(400); + if (!updatePayload || typeof updatePayload !== "object") { + return respondError(res, { + status: 400, + code: "CASE_UPDATE_PAYLOAD_REQUIRED", + message: "Case update payload is required" }); + } + if (typeof appealObj !== "string" || appealObj.trim().length === 0) { + return respondError(res, { + status: 400, + code: "APPEAL_OBJECT_ID_REQUIRED", + message: "appealObj is required" + }); + } + if ( + typeof updateFormCollection !== "string" || + updateFormCollection.trim().length === 0 + ) { + return respondError(res, { + status: 400, + code: "UPDATE_FORM_COLLECTION_REQUIRED", + message: "updateFormCollection is required" + }); + } + + try { + const queryUrl = updateFormCollection + "(" + appealObj + ")"; + const token = await getToken(); + + const config = { + method: "patch", + url: WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), + headers: { + "OData-MaxVersion": "4.0", + "OData-Version": "4.0", + "Accept": "application/json", + "Prefer": 'odata.include-annotations="*",return=representation', + "Authorization": "Bearer " + token.access_token, + "Content-Type": "application/json" + }, + data: JSON.stringify(updatePayload) + }; + + const { data } = await axios(config); + return respondSuccess(res, data); + } catch (error) { + consoleLogger(error); + return respondError(res, { + status: 400, + code: "CASE_UPDATE_FAILED", + message: "Failed to update case" + }); + } } diff --git a/tests/phase21/endpoint-handler-contract.test.cjs b/tests/phase21/endpoint-handler-contract.test.cjs index 267dcce0..ce82d259 100644 --- a/tests/phase21/endpoint-handler-contract.test.cjs +++ b/tests/phase21/endpoint-handler-contract.test.cjs @@ -3320,6 +3320,143 @@ test("createcase catch path returns CASE_CREATE_FAILED", async () => { assert.strictEqual(res.state.jsonBody.error.code, "CASE_CREATE_FAILED"); }); +test("updatecase returns CASE_UPDATE_PAYLOAD_REQUIRED when body missing", async () => { + const mod = loadModule("pages/api/endpoint/updatecase_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => ({ data: {} }), + consoleLogger: () => {} + }); + + const req = { + query: { appealObj: "a1", updateFormCollection: "pinswg_x" }, + body: undefined + }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "CASE_UPDATE_PAYLOAD_REQUIRED" + ); +}); + +test("updatecase catch path returns CASE_UPDATE_FAILED", async () => { + const mod = loadModule("pages/api/endpoint/updatecase_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => { + throw new Error("update failed"); + }, + consoleLogger: () => {} + }); + + const req = { + query: { appealObj: "a1", updateFormCollection: "pinswg_x" }, + body: { title: "updated" } + }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(res.state.jsonBody.error.code, "CASE_UPDATE_FAILED"); +}); + +test("patchcase returns INCIDENT_ID_REQUIRED when incidentid missing", async () => { + const mod = loadModule("pages/api/endpoint/patchcase_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => ({ data: {} }), + consoleLogger: () => {} + }); + + const req = { query: {} }; + 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("patchcase catch path returns CASE_PATCH_FAILED", async () => { + const mod = loadModule("pages/api/endpoint/patchcase_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => { + throw new Error("patch failed"); + }, + consoleLogger: () => {} + }); + + const req = { query: { incidentid: "i1" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(res.state.jsonBody.error.code, "CASE_PATCH_FAILED"); +}); + +test("createcrmtask returns CONTACT_EMAIL_AND_SUBJECT_REQUIRED when required fields missing", async () => { + const mod = loadModule("pages/api/endpoint/createcrmtask_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + isNonEmptyString: () => false, + sanitizeString: (v) => v, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => ({ data: {} }), + consoleLogger: () => {} + }); + + const req = { body: {} }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "CONTACT_EMAIL_AND_SUBJECT_REQUIRED" + ); +}); + +test("createcrmtask catch path returns CRM_TASK_CREATE_FAILED", async () => { + const mod = loadModule("pages/api/endpoint/createcrmtask_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + isNonEmptyString: () => true, + sanitizeString: (v) => v, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => { + throw new Error("create failed"); + }, + consoleLogger: () => {} + }); + + const req = { + body: { + contactEmail: "user@test.local", + contactSubject: "Need help", + contactbody: "Details", + typeOfContact: "plq" + } + }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(res.state.jsonBody.error.code, "CRM_TASK_CREATE_FAILED"); +}); + test("getsipsmedia returns success contract", async () => { const mod = loadModule("pages/api/endpoint/getsipsmedia_api.js", { respondSuccess: respondSuccessMock