diff --git a/pages/api/endpoint/createwatchedcases_api.js b/pages/api/endpoint/createwatchedcases_api.js index b1092ef4..55662558 100644 --- a/pages/api/endpoint/createwatchedcases_api.js +++ b/pages/api/endpoint/createwatchedcases_api.js @@ -11,10 +11,10 @@ // */ 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 || @@ -83,51 +83,65 @@ export default async function ApiProxy(req, res) { typeof contactBind !== "string" || contactBind.length === 0 ) { - return res.status(400).json(); + return respondError(res, { + status: 400, + code: "WATCHED_CASE_BINDINGS_REQUIRED", + message: + "pinswg_WatchedCase and pinswg_Contact bindings are required" + }); } const watchedCaseMatch = watchedCaseBind.match(/\(([^)]+)\)/); const contactMatch = contactBind.match(/\(([^)]+)\)/); if (!watchedCaseMatch || !contactMatch) { - return res.status(400).json(); - } - - const token = await getToken(); - - const incidentId = watchedCaseMatch[1]; - const contactId = contactMatch[1]; - - const existingRecord = await recordExists(incidentId, contactId, token); - - let method, queryUrl; - - if (existingRecord) { - method = "patch"; - queryUrl = `pinswg_watchlists(${existingRecord.pinswg_watchlistid})`; // adjust with your primary key logical name - } else { - method = "post"; - queryUrl = "pinswg_watchlists"; - } - - const config = { - method: method, - 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(data) - }; - - return axios(config) - .then(({ data }) => res.status(200).json(data)) - .catch((err) => { - consoleLogger(err); - res.status(400).json(err.response?.data || err); + return respondError(res, { + status: 400, + code: "INVALID_BINDING_FORMAT", + message: "Invalid odata.bind format for watched case or contact" }); + } + + try { + const token = await getToken(); + + const incidentId = watchedCaseMatch[1]; + const contactId = contactMatch[1]; + + const existingRecord = await recordExists(incidentId, contactId, token); + + let method, queryUrl; + + if (existingRecord) { + method = "patch"; + queryUrl = `pinswg_watchlists(${existingRecord.pinswg_watchlistid})`; + } else { + method = "post"; + queryUrl = "pinswg_watchlists"; + } + + const config = { + method: method, + 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(data) + }; + + const result = await axios(config); + return respondSuccess(res, result.data); + } catch (err) { + consoleLogger(err); + return respondError(res, { + status: 400, + code: "WATCHED_CASE_UPSERT_FAILED", + message: "Failed to create or update watched case" + }); + } } diff --git a/pages/api/endpoint/deleteawaitingsubmissions_api.js b/pages/api/endpoint/deleteawaitingsubmissions_api.js index 64b8d0b6..9128477b 100644 --- a/pages/api/endpoint/deleteawaitingsubmissions_api.js +++ b/pages/api/endpoint/deleteawaitingsubmissions_api.js @@ -11,39 +11,51 @@ */ 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 incidentID = req.query.incidentID; - var token = await getToken(); - var queryUrl = "incidents(" + incidentID + ")"; + const incidentID = req.query.incidentID; - var config = { - method: "delete", - 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" - } - }; - - return axios(config) - .then(({ data }) => { - res.status(200).json(data); - }) - .catch((error) => { - consoleLogger(error); - res.status(400).json(error); + if (typeof incidentID !== "string" || incidentID.trim().length === 0) { + return respondError(res, { + status: 400, + code: "INCIDENT_ID_REQUIRED", + message: "incidentID is required" }); + } + + try { + const token = await getToken(); + const queryUrl = "incidents(" + incidentID + ")"; + + const config = { + method: "delete", + 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" + } + }; + + const { data } = await axios(config); + return respondSuccess(res, data); + } catch (error) { + consoleLogger(error); + return respondError(res, { + status: 400, + code: "AWAITING_SUBMISSION_DELETE_FAILED", + message: "Failed to delete awaiting submission" + }); + } } diff --git a/pages/api/endpoint/deletemyrepresentations_api.js b/pages/api/endpoint/deletemyrepresentations_api.js index aff512fe..62e02b46 100644 --- a/pages/api/endpoint/deletemyrepresentations_api.js +++ b/pages/api/endpoint/deletemyrepresentations_api.js @@ -11,40 +11,56 @@ */ 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 myRepresentationsID = req.query.myRepresentationsID; - var token = await getToken(); - var queryUrl = "pinswg_representationses(" + myRepresentationsID + ")"; + const myRepresentationsID = req.query.myRepresentationsID; - var config = { - method: "delete", - 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" - } - }; - - return axios(config) - .then(({ data }) => { - res.status(200).json(data); - }) - .catch((error) => { - consoleLogger(error); - res.status(400).json(error); + if ( + typeof myRepresentationsID !== "string" || + myRepresentationsID.trim().length === 0 + ) { + return respondError(res, { + status: 400, + code: "MY_REPRESENTATION_ID_REQUIRED", + message: "myRepresentationsID is required" }); + } + + try { + const token = await getToken(); + const queryUrl = + "pinswg_representationses(" + myRepresentationsID + ")"; + + const config = { + method: "delete", + 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" + } + }; + + const { data } = await axios(config); + return respondSuccess(res, data); + } catch (error) { + consoleLogger(error); + return respondError(res, { + status: 400, + code: "MY_REPRESENTATION_DELETE_FAILED", + message: "Failed to delete representation" + }); + } } diff --git a/pages/api/endpoint/deletewatchedcases_api.js b/pages/api/endpoint/deletewatchedcases_api.js index 3972ccff..5c59ecff 100644 --- a/pages/api/endpoint/deletewatchedcases_api.js +++ b/pages/api/endpoint/deletewatchedcases_api.js @@ -11,45 +11,54 @@ */ 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 watchedCaseID = req.query.watchedCaseID; + const watchedCaseID = req.query.watchedCaseID; - if (typeof watchedCaseID === "undefined" || watchedCaseID.length === 0) { - return res.status(400).json(); + if ( + typeof watchedCaseID !== "string" || + watchedCaseID.trim().length === 0 + ) { + return respondError(res, { + status: 400, + code: "WATCHED_CASE_ID_REQUIRED", + message: "watchedCaseID is required" + }); } - var token = await getToken(); - var queryUrl = "pinswg_watchlists(" + watchedCaseID + ")"; + try { + const token = await getToken(); + const queryUrl = "pinswg_watchlists(" + watchedCaseID + ")"; - var config = { - method: "delete", - 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" - } - }; + const config = { + method: "delete", + 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" + } + }; - return axios(config) - .then(({ data }) => { - //console.log("deleted watched case - " + watchedCaseID); - res.status(200).json(data); - }) - .catch((error) => { - consoleLogger(error); - res.status(400).json(error); + const { data } = await axios(config); + return respondSuccess(res, data); + } catch (error) { + consoleLogger(error); + return respondError(res, { + status: 400, + code: "WATCHED_CASE_DELETE_FAILED", + message: "Failed to delete watched case" }); + } } diff --git a/pages/api/endpoint/deletewatchedcasesproxy_api.js b/pages/api/endpoint/deletewatchedcasesproxy_api.js index a9208db1..4aa4466a 100644 --- a/pages/api/endpoint/deletewatchedcasesproxy_api.js +++ b/pages/api/endpoint/deletewatchedcasesproxy_api.js @@ -18,61 +18,50 @@ */ import axios from "axios"; -import CryptoJS from "crypto-js"; import { azureHeaders } from "../../../actions/core/headers"; 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/"; -const BASE_URL = process.env.API_ROOT || `http://localhost:${port}`; +const BASE_URL = process.env.API_ROOT || "http://localhost:3000"; export default async function ApiProxy(req, res) { - var watchedCaseID = req.query.watchedCaseID; - var token = await getToken(); + const watchedCaseID = req.query.watchedCaseID; - var queryUrl = - "/api/endpoint/deletewatchedcases_api?watchedCaseID=" + watchedCaseID; + if ( + typeof watchedCaseID !== "string" || + watchedCaseID.trim().length === 0 + ) { + return respondError(res, { + status: 400, + code: "WATCHED_CASE_ID_REQUIRED", + message: "watchedCaseID is required" + }); + } - return axios - .get( + try { + const token = await getToken(); + const queryUrl = + "/api/endpoint/deletewatchedcases_api?watchedCaseID=" + + watchedCaseID; + + const { data } = await axios.get( BASE_URL + queryUrl + hashAPIPath(queryUrl), azureHeaders(token.access_token) - ) - .then(({ data }) => { - // data.value.forEach(function (element) { - // element.ticketnumber = element.pinswg_WatchedCase?.ticketnumber; - // element.pinswg_title = - // element[ - // "_pinswg_watchedcase_value@OData.Community.Display.V1.FormattedValue" - // ]; + ); - // element[ - // "_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue" - // ] = - // element.pinswg_WatchedCase?.[ - // "_pinswg_associatedlpa_value@OData.Community.Display.V1.FormattedValue" - // ]; - // element._pinswg_associatedlpa_value = - // element.pinswg_WatchedCase?._pinswg_associatedlpa_value; - // element[ - // "_ownerid_value@OData.Community.Display.V1.FormattedValue" - // ] = - // element.pinswg_WatchedCase?.[ - // "_ownerid_value@OData.Community.Display.V1.FormattedValue" - // ]; - // element._ownerid_value = - // element.pinswg_WatchedCase?._ownerid_value; - - // delete element.pinswg_WatchedCase; - // }); - res.status(200).json(data); - }) - .catch((error) => { - consoleLogger(error); - res.status(400).json(error); + return respondSuccess(res, data); + } catch (error) { + consoleLogger(error); + return respondError(res, { + status: 400, + code: "WATCHED_CASE_PROXY_DELETE_FAILED", + message: "Failed to delete watched case via proxy" }); + } } diff --git a/tests/phase21/endpoint-handler-contract.test.cjs b/tests/phase21/endpoint-handler-contract.test.cjs index 736421e7..288bad06 100644 --- a/tests/phase21/endpoint-handler-contract.test.cjs +++ b/tests/phase21/endpoint-handler-contract.test.cjs @@ -2283,6 +2283,275 @@ test("getincidentbyid catch path returns INCIDENT_BY_ID_FETCH_FAILED", async () ); }); +test("createwatchedcases returns WATCHED_CASE_BINDINGS_REQUIRED when bindings missing", async () => { + const mod = loadModule("pages/api/endpoint/createwatchedcases_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + 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, + "WATCHED_CASE_BINDINGS_REQUIRED" + ); +}); + +test("createwatchedcases returns INVALID_BINDING_FORMAT when bindings invalid", async () => { + const mod = loadModule("pages/api/endpoint/createwatchedcases_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => ({ data: {} }), + consoleLogger: () => {} + }); + + const req = { + body: { + "pinswg_WatchedCase@odata.bind": "/incidents", + "pinswg_Contact@odata.bind": "/contacts" + } + }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual(res.state.jsonBody.error.code, "INVALID_BINDING_FORMAT"); +}); + +test("createwatchedcases catch path returns WATCHED_CASE_UPSERT_FAILED", async () => { + const mod = loadModule("pages/api/endpoint/createwatchedcases_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => { + throw new Error("token failed"); + }, + hashAPIPath: () => "&hash=expected", + axios: async () => ({ data: {} }), + consoleLogger: () => {} + }); + + const req = { + body: { + "pinswg_WatchedCase@odata.bind": "/incidents(incident-id)", + "pinswg_Contact@odata.bind": "/contacts(contact-id)" + } + }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "WATCHED_CASE_UPSERT_FAILED" + ); +}); + +test("deletewatchedcases returns WATCHED_CASE_ID_REQUIRED when watchedCaseID missing", async () => { + const mod = loadModule("pages/api/endpoint/deletewatchedcases_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, + "WATCHED_CASE_ID_REQUIRED" + ); +}); + +test("deletewatchedcases catch path returns WATCHED_CASE_DELETE_FAILED", async () => { + const mod = loadModule("pages/api/endpoint/deletewatchedcases_api.js", { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => { + throw new Error("delete failed"); + }, + consoleLogger: () => {} + }); + + const req = { query: { watchedCaseID: "w1" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "WATCHED_CASE_DELETE_FAILED" + ); +}); + +test("deletewatchedcasesproxy returns WATCHED_CASE_ID_REQUIRED when watchedCaseID missing", async () => { + const mod = loadModule( + "pages/api/endpoint/deletewatchedcasesproxy_api.js", + { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + azureHeaders: () => ({}), + axios: { get: async () => ({ data: {} }) }, + consoleLogger: () => {}, + process: { env: {} } + } + ); + + const req = { query: {} }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "WATCHED_CASE_ID_REQUIRED" + ); +}); + +test("deletewatchedcasesproxy catch path returns WATCHED_CASE_PROXY_DELETE_FAILED", async () => { + const mod = loadModule( + "pages/api/endpoint/deletewatchedcasesproxy_api.js", + { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + azureHeaders: () => ({}), + axios: { + get: async () => { + throw new Error("proxy delete failed"); + } + }, + consoleLogger: () => {}, + process: { env: {} } + } + ); + + const req = { query: { watchedCaseID: "w1" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "WATCHED_CASE_PROXY_DELETE_FAILED" + ); +}); + +test("deletemyrepresentations returns MY_REPRESENTATION_ID_REQUIRED when myRepresentationsID missing", async () => { + const mod = loadModule( + "pages/api/endpoint/deletemyrepresentations_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, + "MY_REPRESENTATION_ID_REQUIRED" + ); +}); + +test("deletemyrepresentations catch path returns MY_REPRESENTATION_DELETE_FAILED", async () => { + const mod = loadModule( + "pages/api/endpoint/deletemyrepresentations_api.js", + { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => { + throw new Error("delete failed"); + }, + consoleLogger: () => {} + } + ); + + const req = { query: { myRepresentationsID: "r1" } }; + const res = createRes(); + await mod.default(req, res); + + assert.strictEqual(res.state.statusCode, 400); + assert.strictEqual( + res.state.jsonBody.error.code, + "MY_REPRESENTATION_DELETE_FAILED" + ); +}); + +test("deleteawaitingsubmissions returns INCIDENT_ID_REQUIRED when incidentID missing", async () => { + const mod = loadModule( + "pages/api/endpoint/deleteawaitingsubmissions_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("deleteawaitingsubmissions catch path returns AWAITING_SUBMISSION_DELETE_FAILED", async () => { + const mod = loadModule( + "pages/api/endpoint/deleteawaitingsubmissions_api.js", + { + respondError: respondErrorMock, + respondSuccess: respondSuccessMock, + getToken: async () => ({ access_token: "token" }), + hashAPIPath: () => "&hash=expected", + axios: async () => { + throw new Error("delete 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, + "AWAITING_SUBMISSION_DELETE_FAILED" + ); +}); + const run = async () => { let passed = 0; for (const currentTest of tests) {