diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index c564b26d..6556676e 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -974,3 +974,35 @@ Validation: Follow-ups: - Continue P2-S2 with next bounded batch from remaining legacy GET endpoints (currently: `createwatchedcases_api`, `deletewatchedcasesproxy_api`, `getadvancedsearch_api`, `getadvancedsearchpaged_api`, `getappealid_api`, `getbasicdnssearchpaged_api`, `getbasicpartsaveddetails_api`, `getbasicsearch_by_address_api`, `getbasicsearch_by_lparref_api`, `getdnscoords_api`, `getdnslist_api`, `getmylpacases_api`, `getportallogin_api`). + +--- + +### CL-026: TASK22229 P2-S2 Batch 9 (appeal-id + DNS list/paged + part-saved relay GET cluster) + +date: 2026-03-24 +author: Cline +scope: `pages/api/endpoint/{getappealid_api,getbasicdnssearchpaged_api,getbasicpartsaveddetails_api,getdnslist_api}.js`, `tests/phase21/endpoint-handler-contract.test.cjs` +type: change +rationale: Continue bounded P2-S2 relay migration by moving another coherent set of read-only handlers to shared `relayGet`, preserving existing guards, transforms, and error contracts. +impact: Reduced duplicated relay plumbing and improved consistency for DNS list/paged and appeal detail lookup endpoints; no intended API contract changes. +status: completed + +Summary: + +- Migrated Batch 9 endpoints to shared `relayGet`: + - `getappealid_api.js` + - `getbasicdnssearchpaged_api.js` + - `getbasicpartsaveddetails_api.js` + - `getdnslist_api.js` +- Preserved existing input validation guards and endpoint-specific error contracts. +- Preserved endpoint-specific transform behavior (`@odata.nextLink` normalization in DNS list/paged handlers). +- Updated phase21 endpoint contract tests to inject `relayGet` mocks for these migrated handlers in guard and catch-path assertions. + +Validation: + +- `node tests/phase21/endpoint-handler-contract.test.cjs` -> pass (152/152) +- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps`; no new lint errors) + +Follow-ups: + +- Continue P2-S2 with next bounded batch from remaining legacy relay GET endpoints (`createwatchedcases_api`, `deletewatchedcasesproxy_api`, `getadvancedsearch_api`, `getadvancedsearchpaged_api`, `getbasicsearch_by_address_api`, `getbasicsearch_by_lparref_api`, `getdnscoords_api`, `getmylpacases_api`, `getportallogin_api`). diff --git a/pages/api/endpoint/getappealid_api.js b/pages/api/endpoint/getappealid_api.js index d01ce523..952f82d6 100644 --- a/pages/api/endpoint/getappealid_api.js +++ b/pages/api/endpoint/getappealid_api.js @@ -28,16 +28,9 @@ * description: Success */ -import axios from "axios"; 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/"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; export default async function ApiProxy(req, res) { const caseReference = req.query.caseReference; @@ -77,30 +70,24 @@ export default async function ApiProxy(req, res) { }); } - try { - const token = await getToken(); - const escapedCaseReference = caseReference.split("'").join("''"); + const escapedCaseReference = caseReference.split("'").join("''"); - const queryUrl = - updateFormCollection + - "?$count=true&$select=_" + - primaryAttribute + - "s_value&$filter=pinswg_name eq '" + - escapedCaseReference + - "'"; + const queryUrl = + updateFormCollection + + "?$count=true&$select=_" + + primaryAttribute + + "s_value&$filter=pinswg_name eq '" + + escapedCaseReference + + "'"; - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeaders(token.access_token) - ); - - return respondSuccess(res, data); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return relayGet({ + queryUrl, + res, + requestOptionsBuilder: (accessToken) => azureHeaders(accessToken), + errorResponse: { status: 400, code: "APPEAL_ID_FETCH_FAILED", message: "Failed to fetch appeal id" - }); - } + } + }); } diff --git a/pages/api/endpoint/getbasicdnssearchpaged_api.js b/pages/api/endpoint/getbasicdnssearchpaged_api.js index 4d8d7791..76336faa 100644 --- a/pages/api/endpoint/getbasicdnssearchpaged_api.js +++ b/pages/api/endpoint/getbasicdnssearchpaged_api.js @@ -42,17 +42,10 @@ * description: Success */ -import axios from "axios"; import _ from "lodash"; import { azureHeadersPagedCustom } from "../../../actions/core/headers"; -import { getToken } from "../../../actions/core/token"; -import { consoleLogger } from "../../../actions/core/logger"; -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/"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; export default async function ApiProxy(req, res) { const pageNumber = req.query.pageNumber; @@ -87,36 +80,33 @@ export default async function ApiProxy(req, res) { }); } - try { - const token = await getToken(); + const queryUrl = + "incidents?$select=pinswg_environmentalstatementlocation,pinswg_caseaddress,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title, _primarycontactid_value,pinswg_lpareference&$expand=primarycontactid($select=fullname)&$filter=(pinswg_appealcasetype eq 846040011 or pinswg_appealcasetype eq 846040002) and pinswg_publishtoweb eq true&$orderby=" + + orderby + + " " + + fieldSort + + "&$count=true" + + (typeof pageNumber != "undefined" + ? "&$skiptoken=" + '' + : ""); - const queryUrl = - "incidents?$select=pinswg_environmentalstatementlocation,pinswg_caseaddress,modifiedon,description,numberofchildincidents,_accountid_value,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,pinswg_appealcasetype,statuscode,ticketnumber,title, _primarycontactid_value,pinswg_lpareference&$expand=primarycontactid($select=fullname)&$filter=(pinswg_appealcasetype eq 846040011 or pinswg_appealcasetype eq 846040002) and pinswg_publishtoweb eq true&$orderby=" + - orderby + - " " + - fieldSort + - "&$count=true" + - (typeof pageNumber != "undefined" - ? "&$skiptoken=" + '' - : ""); + return relayGet({ + queryUrl, + res, + requestOptionsBuilder: (accessToken) => + azureHeadersPagedCustom(accessToken, showNumberOfRecords), + transformData: (data) => { + if (_.has(data, "@odata.nextLink") === true) { + const dataStr = JSON.stringify(data["@odata.nextLink"]); + data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]; + } - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeadersPagedCustom(token.access_token, showNumberOfRecords) - ); - - if (_.has(data, "@odata.nextLink") === true) { - const dataStr = JSON.stringify(data["@odata.nextLink"]); - data["@odata.nextLink"] = dataStr.split("/v8.2/")[1]; - } - - return respondSuccess(res, data); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return data; + }, + errorResponse: { status: 400, code: "BASIC_DNS_SEARCH_PAGED_FETCH_FAILED", message: "Failed to fetch paged basic DNS search results" - }); - } + } + }); } diff --git a/pages/api/endpoint/getbasicpartsaveddetails_api.js b/pages/api/endpoint/getbasicpartsaveddetails_api.js index 47ced874..a6fe1011 100644 --- a/pages/api/endpoint/getbasicpartsaveddetails_api.js +++ b/pages/api/endpoint/getbasicpartsaveddetails_api.js @@ -1,13 +1,6 @@ -import axios from "axios"; import { azureHeadersNoOdata } 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/"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; export default async function ApiProxy(req, res) { const appealTypeName = req.query.appealTypeName; @@ -44,29 +37,23 @@ export default async function ApiProxy(req, res) { }); } - try { - const token = await getToken(); + const queryUrl = + appealTypeName + + "?$filter=_" + + primaryIdAttribute + + "s_value eq " + + incidentID + + "&$count=true"; - const queryUrl = - appealTypeName + - "?$filter=_" + - primaryIdAttribute + - "s_value eq " + - incidentID + - "&$count=true"; - - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeadersNoOdata(token.access_token) - ); - - return respondSuccess(res, data); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return relayGet({ + queryUrl, + res, + requestOptionsBuilder: (accessToken) => + azureHeadersNoOdata(accessToken), + errorResponse: { status: 400, code: "BASIC_PART_SAVED_DETAILS_FETCH_FAILED", message: "Failed to fetch basic part-saved details" - }); - } + } + }); } diff --git a/pages/api/endpoint/getdnslist_api.js b/pages/api/endpoint/getdnslist_api.js index 14251a39..ab1439f3 100644 --- a/pages/api/endpoint/getdnslist_api.js +++ b/pages/api/endpoint/getdnslist_api.js @@ -1,40 +1,29 @@ -import axios from "axios"; import _ from "lodash"; 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/"; +import { respondError } from "../middleware/apiResponse"; +import { relayGet } from "../middleware/relayForwarding"; export default async function ApiProxy(req, res) { - try { - const token = await getToken(); + const queryUrl = + "incidents?$select=pinswg_environmentalstatementlocation,pinswg_appealcasetype,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,statuscode,ticketnumber,title &$filter=(pinswg_appealcasetype eq 846040011 or pinswg_appealcasetype eq 846040002) and pinswg_publishtoweb eq true&$orderby=createdon desc&$count=true"; - const queryUrl = - "incidents?$select=pinswg_environmentalstatementlocation,pinswg_appealcasetype,_customerid_value,_pinswg_associatedlpa_value,_ownerid_value,statuscode,ticketnumber,title &$filter=(pinswg_appealcasetype eq 846040011 or pinswg_appealcasetype eq 846040002) and pinswg_publishtoweb eq true&$orderby=createdon desc&$count=true"; + return relayGet({ + queryUrl, + res, + requestOptionsBuilder: (accessToken) => azureHeaders(accessToken), + transformData: (data) => { + let dataStr; - const { data } = await axios.get( - WEBAPI_URL + queryUrl + hashAPIPath(queryUrl), - azureHeaders(token.access_token) - ); + _.has(data, "@odata.nextLink") === true && + ((dataStr = JSON.stringify(data["@odata.nextLink"])), + (data["@odata.nextLink"] = dataStr.split("/v8.2/")[1])); - let dataStr; - - _.has(data, "@odata.nextLink") === true && - ((dataStr = JSON.stringify(data["@odata.nextLink"])), - (data["@odata.nextLink"] = dataStr.split("/v8.2/")[1])); - - return respondSuccess(res, data); - } catch (error) { - consoleLogger(error); - return respondError(res, { + return data; + }, + errorResponse: { status: 400, code: "DNS_LIST_FETCH_FAILED", message: "Failed to fetch DNS list" - }); - } + } + }); } diff --git a/tests/phase21/endpoint-handler-contract.test.cjs b/tests/phase21/endpoint-handler-contract.test.cjs index e4f9eae9..f0d311c0 100644 --- a/tests/phase21/endpoint-handler-contract.test.cjs +++ b/tests/phase21/endpoint-handler-contract.test.cjs @@ -2141,11 +2141,8 @@ test("getbasicdnssearchpaged returns ORDER_BY_REQUIRED when orderby missing", as const mod = loadModule("pages/api/endpoint/getbasicdnssearchpaged_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, - getToken: async () => ({ access_token: "token" }), - hashAPIPath: () => "&hash=expected", + relayGet: async () => ({}), azureHeadersPagedCustom: () => ({}), - axios: { get: async () => ({ data: { value: [] } }) }, - consoleLogger: () => {}, _: { has: () => false } }); @@ -2161,15 +2158,9 @@ test("getbasicdnssearchpaged catch path returns BASIC_DNS_SEARCH_PAGED_FETCH_FAI const mod = loadModule("pages/api/endpoint/getbasicdnssearchpaged_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, - getToken: async () => ({ access_token: "token" }), - hashAPIPath: () => "&hash=expected", + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), azureHeadersPagedCustom: () => ({}), - axios: { - get: async () => { - throw new Error("relay failed"); - } - }, - consoleLogger: () => {}, _: { has: () => false } }); @@ -2939,11 +2930,8 @@ test("getappealid returns CASE_REFERENCE_REQUIRED when caseReference missing", a const mod = loadModule("pages/api/endpoint/getappealid_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, - getToken: async () => ({ access_token: "token" }), - hashAPIPath: () => "&hash=expected", - azureHeaders: () => ({}), - axios: { get: async () => ({ data: { value: [] } }) }, - consoleLogger: () => {} + relayGet: async () => ({}), + azureHeaders: () => ({}) }); const req = { @@ -2966,15 +2954,9 @@ test("getappealid catch path returns APPEAL_ID_FETCH_FAILED", async () => { const mod = loadModule("pages/api/endpoint/getappealid_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, - getToken: async () => ({ access_token: "token" }), - hashAPIPath: () => "&hash=expected", - azureHeaders: () => ({}), - axios: { - get: async () => { - throw new Error("relay failed"); - } - }, - consoleLogger: () => {} + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), + azureHeaders: () => ({}) }); const req = { @@ -3196,15 +3178,9 @@ test("getdnslist catch path returns DNS_LIST_FETCH_FAILED", async () => { const mod = loadModule("pages/api/endpoint/getdnslist_api.js", { respondError: respondErrorMock, respondSuccess: respondSuccessMock, - getToken: async () => ({ access_token: "token" }), - hashAPIPath: () => "&hash=expected", + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), azureHeaders: () => ({}), - axios: { - get: async () => { - throw new Error("relay failed"); - } - }, - consoleLogger: () => {}, _: { has: () => false } }); @@ -3270,11 +3246,8 @@ test("getbasicpartsaveddetails returns APPEAL_TYPE_NAME_REQUIRED when appealType { respondError: respondErrorMock, respondSuccess: respondSuccessMock, - getToken: async () => ({ access_token: "token" }), - hashAPIPath: () => "&hash=expected", - azureHeadersNoOdata: () => ({}), - axios: { get: async () => ({ data: { value: [] } }) }, - consoleLogger: () => {} + relayGet: async () => ({}), + azureHeadersNoOdata: () => ({}) } ); @@ -3300,15 +3273,9 @@ test("getbasicpartsaveddetails catch path returns BASIC_PART_SAVED_DETAILS_FETCH { respondError: respondErrorMock, respondSuccess: respondSuccessMock, - getToken: async () => ({ access_token: "token" }), - hashAPIPath: () => "&hash=expected", - azureHeadersNoOdata: () => ({}), - axios: { - get: async () => { - throw new Error("relay failed"); - } - }, - consoleLogger: () => {} + relayGet: async ({ res, errorResponse }) => + respondErrorMock(res, errorResponse), + azureHeadersNoOdata: () => ({}) } );