feat(relay): apply policy overrides for my-portal case and representation endpoints
This commit is contained in:
@@ -1369,3 +1369,41 @@ Validation:
|
|||||||
Follow-ups:
|
Follow-ups:
|
||||||
|
|
||||||
- Optional next slice: extract shared relay policy presets into constants to reduce duplication and enforce profile consistency across remaining relayGet endpoints.
|
- Optional next slice: extract shared relay policy presets into constants to reduce duplication and enforce profile consistency across remaining relayGet endpoints.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CL-037: TASK22242 portal/my-cases relayPolicy parity (my portal + representations)
|
||||||
|
|
||||||
|
date: 2026-03-24
|
||||||
|
author: Cline
|
||||||
|
scope: `pages/api/endpoint/{getmycases_api,getmyrepresentations_api,getwatchedcases_api,getawaitingsubmission_api,getrepresentations_api}.js`, `tests/phase21/endpoint-handler-contract.test.cjs`
|
||||||
|
type: change
|
||||||
|
rationale: Continue the branch-by-branch relay policy rollout by applying explicit policy posture to core my-portal retrieval endpoints.
|
||||||
|
impact: Improves consistency and operational predictability of relay behavior for portal case/representation listing flows without changing endpoint contracts.
|
||||||
|
status: completed
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
- Added explicit `relayPolicy` declarations to:
|
||||||
|
- `getmycases_api`
|
||||||
|
- `getmyrepresentations_api`
|
||||||
|
- `getwatchedcases_api`
|
||||||
|
- `getawaitingsubmission_api`
|
||||||
|
- `getrepresentations_api`
|
||||||
|
- Applied bounded read profile across the batch:
|
||||||
|
- `method: "GET"`
|
||||||
|
- `timeoutMs: 8000`
|
||||||
|
- `maxRetries: 2`
|
||||||
|
- `retryBaseDelayMs: 150`
|
||||||
|
- `retryMaxDelayMs: 800`
|
||||||
|
- Extended phase21 endpoint contract tests with relayPolicy pass-through assertions for each endpoint.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
|
||||||
|
- `node tests/phase21/endpoint-handler-contract.test.cjs` -> pass (164/164)
|
||||||
|
- `node tests/phase21/relay-forwarding-hardening.test.cjs` -> pass (10/10)
|
||||||
|
- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps`; no new lint errors)
|
||||||
|
|
||||||
|
Follow-ups:
|
||||||
|
|
||||||
|
- Optional next slice: extract shared relay policy presets into a constants module and reference them from all relayGet endpoints to reduce duplication.
|
||||||
|
|||||||
@@ -39,9 +39,18 @@ export default async function ApiProxy(req, res) {
|
|||||||
loggedInUserId +
|
loggedInUserId +
|
||||||
" and servicestage eq 1 and pinswg_appealcasetype ne null&$orderby=createdon desc&$count=true";
|
" and servicestage eq 1 and pinswg_appealcasetype ne null&$orderby=createdon desc&$count=true";
|
||||||
|
|
||||||
|
const relayPolicy = {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
};
|
||||||
|
|
||||||
return relayGet({
|
return relayGet({
|
||||||
queryUrl,
|
queryUrl,
|
||||||
res,
|
res,
|
||||||
|
relayPolicy,
|
||||||
transformData: (data) => {
|
transformData: (data) => {
|
||||||
data.value.forEach(function (element) {
|
data.value.forEach(function (element) {
|
||||||
element.pinswg_title = element.title;
|
element.pinswg_title = element.title;
|
||||||
|
|||||||
@@ -39,9 +39,18 @@ export default async function ApiProxy(req, res) {
|
|||||||
loggedInUserId +
|
loggedInUserId +
|
||||||
" and pinswg_appealcasetype ne null&$orderby=createdon desc&$count=true";
|
" and pinswg_appealcasetype ne null&$orderby=createdon desc&$count=true";
|
||||||
|
|
||||||
|
const relayPolicy = {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
};
|
||||||
|
|
||||||
return relayGet({
|
return relayGet({
|
||||||
queryUrl,
|
queryUrl,
|
||||||
res,
|
res,
|
||||||
|
relayPolicy,
|
||||||
transformData: (data) => {
|
transformData: (data) => {
|
||||||
data.value.forEach(function (element) {
|
data.value.forEach(function (element) {
|
||||||
element.pinswg_title = element.title;
|
element.pinswg_title = element.title;
|
||||||
|
|||||||
@@ -39,9 +39,18 @@ export default async function ApiProxy(req, res) {
|
|||||||
loggedInUserId +
|
loggedInUserId +
|
||||||
"&$count=true&$orderby=createdon desc";
|
"&$count=true&$orderby=createdon desc";
|
||||||
|
|
||||||
|
const relayPolicy = {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
};
|
||||||
|
|
||||||
return relayGet({
|
return relayGet({
|
||||||
queryUrl,
|
queryUrl,
|
||||||
res,
|
res,
|
||||||
|
relayPolicy,
|
||||||
errorResponse: {
|
errorResponse: {
|
||||||
status: 400,
|
status: 400,
|
||||||
code: "MY_REPRESENTATIONS_FETCH_FAILED",
|
code: "MY_REPRESENTATIONS_FETCH_FAILED",
|
||||||
|
|||||||
@@ -36,9 +36,18 @@ export default async function ApiProxy(req, res) {
|
|||||||
incidentID +
|
incidentID +
|
||||||
" and pinswg_publishtoweb eq true&$count=true&$orderby=createdon desc";
|
" and pinswg_publishtoweb eq true&$count=true&$orderby=createdon desc";
|
||||||
|
|
||||||
|
const relayPolicy = {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
};
|
||||||
|
|
||||||
return relayGet({
|
return relayGet({
|
||||||
queryUrl,
|
queryUrl,
|
||||||
res,
|
res,
|
||||||
|
relayPolicy,
|
||||||
errorResponse: {
|
errorResponse: {
|
||||||
status: 400,
|
status: 400,
|
||||||
code: "REPRESENTATIONS_FETCH_FAILED",
|
code: "REPRESENTATIONS_FETCH_FAILED",
|
||||||
|
|||||||
@@ -39,9 +39,18 @@ export default async function ApiProxy(req, res) {
|
|||||||
loggedInUserId +
|
loggedInUserId +
|
||||||
"&$select=pinswg_emailnotifications,modifiedon,pinswg_appealcasetype,pinswg_watchlistid,_pinswg_watchedcase_value,statuscode,pinswg_representationsubmitted,pinswg_representationtype&$count=true&$orderby=createdon desc&$expand=pinswg_WatchedCase($select=pinswg_AssociatedLPA)";
|
"&$select=pinswg_emailnotifications,modifiedon,pinswg_appealcasetype,pinswg_watchlistid,_pinswg_watchedcase_value,statuscode,pinswg_representationsubmitted,pinswg_representationtype&$count=true&$orderby=createdon desc&$expand=pinswg_WatchedCase($select=pinswg_AssociatedLPA)";
|
||||||
|
|
||||||
|
const relayPolicy = {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
};
|
||||||
|
|
||||||
return relayGet({
|
return relayGet({
|
||||||
queryUrl,
|
queryUrl,
|
||||||
res,
|
res,
|
||||||
|
relayPolicy,
|
||||||
transformData: (data) => {
|
transformData: (data) => {
|
||||||
data.value.forEach(function (element) {
|
data.value.forEach(function (element) {
|
||||||
element.ticketnumber = element.pinswg_WatchedCase?.ticketnumber;
|
element.ticketnumber = element.pinswg_WatchedCase?.ticketnumber;
|
||||||
|
|||||||
@@ -1725,6 +1725,32 @@ test("getmycases catch path returns MY_CASES_FETCH_FAILED", async () => {
|
|||||||
assert.strictEqual(res.state.jsonBody.error.code, "MY_CASES_FETCH_FAILED");
|
assert.strictEqual(res.state.jsonBody.error.code, "MY_CASES_FETCH_FAILED");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getmycases passes relayPolicy overrides to relayGet", async () => {
|
||||||
|
let capturedRelayPolicy = null;
|
||||||
|
|
||||||
|
const mod = loadModule("pages/api/endpoint/getmycases_api.js", {
|
||||||
|
respondError: respondErrorMock,
|
||||||
|
respondSuccess: respondSuccessMock,
|
||||||
|
relayGet: async ({ relayPolicy, res }) => {
|
||||||
|
capturedRelayPolicy = relayPolicy;
|
||||||
|
return respondSuccessMock(res, { value: [] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { query: { loggedInUserId: "c1" } };
|
||||||
|
const res = createRes();
|
||||||
|
await mod.default(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.state.statusCode, 200);
|
||||||
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(capturedRelayPolicy)), {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("getmyrepresentations returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
|
test("getmyrepresentations returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
|
||||||
const mod = loadModule("pages/api/endpoint/getmyrepresentations_api.js", {
|
const mod = loadModule("pages/api/endpoint/getmyrepresentations_api.js", {
|
||||||
respondError: respondErrorMock,
|
respondError: respondErrorMock,
|
||||||
@@ -1776,6 +1802,32 @@ test("getmyrepresentations catch path returns MY_REPRESENTATIONS_FETCH_FAILED",
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getmyrepresentations passes relayPolicy overrides to relayGet", async () => {
|
||||||
|
let capturedRelayPolicy = null;
|
||||||
|
|
||||||
|
const mod = loadModule("pages/api/endpoint/getmyrepresentations_api.js", {
|
||||||
|
respondError: respondErrorMock,
|
||||||
|
respondSuccess: respondSuccessMock,
|
||||||
|
relayGet: async ({ relayPolicy, res }) => {
|
||||||
|
capturedRelayPolicy = relayPolicy;
|
||||||
|
return respondSuccessMock(res, { value: [] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { query: { loggedInUserId: "c1" } };
|
||||||
|
const res = createRes();
|
||||||
|
await mod.default(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.state.statusCode, 200);
|
||||||
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(capturedRelayPolicy)), {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("getwatchedcases returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
|
test("getwatchedcases returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
|
||||||
const mod = loadModule("pages/api/endpoint/getwatchedcases_api.js", {
|
const mod = loadModule("pages/api/endpoint/getwatchedcases_api.js", {
|
||||||
respondError: respondErrorMock,
|
respondError: respondErrorMock,
|
||||||
@@ -1827,6 +1879,32 @@ test("getwatchedcases catch path returns WATCHED_CASES_FETCH_FAILED", async () =
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getwatchedcases passes relayPolicy overrides to relayGet", async () => {
|
||||||
|
let capturedRelayPolicy = null;
|
||||||
|
|
||||||
|
const mod = loadModule("pages/api/endpoint/getwatchedcases_api.js", {
|
||||||
|
respondError: respondErrorMock,
|
||||||
|
respondSuccess: respondSuccessMock,
|
||||||
|
relayGet: async ({ relayPolicy, res }) => {
|
||||||
|
capturedRelayPolicy = relayPolicy;
|
||||||
|
return respondSuccessMock(res, { value: [] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { query: { loggedInUserId: "c1" } };
|
||||||
|
const res = createRes();
|
||||||
|
await mod.default(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.state.statusCode, 200);
|
||||||
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(capturedRelayPolicy)), {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("getawaitingsubmission returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
|
test("getawaitingsubmission returns LOGGED_IN_USER_ID_REQUIRED when loggedInUserId missing", async () => {
|
||||||
const mod = loadModule("pages/api/endpoint/getawaitingsubmission_api.js", {
|
const mod = loadModule("pages/api/endpoint/getawaitingsubmission_api.js", {
|
||||||
respondError: respondErrorMock,
|
respondError: respondErrorMock,
|
||||||
@@ -1878,6 +1956,32 @@ test("getawaitingsubmission catch path returns AWAITING_SUBMISSION_FETCH_FAILED"
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getawaitingsubmission passes relayPolicy overrides to relayGet", async () => {
|
||||||
|
let capturedRelayPolicy = null;
|
||||||
|
|
||||||
|
const mod = loadModule("pages/api/endpoint/getawaitingsubmission_api.js", {
|
||||||
|
respondError: respondErrorMock,
|
||||||
|
respondSuccess: respondSuccessMock,
|
||||||
|
relayGet: async ({ relayPolicy, res }) => {
|
||||||
|
capturedRelayPolicy = relayPolicy;
|
||||||
|
return respondSuccessMock(res, { value: [] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { query: { loggedInUserId: "c1" } };
|
||||||
|
const res = createRes();
|
||||||
|
await mod.default(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.state.statusCode, 200);
|
||||||
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(capturedRelayPolicy)), {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("getbasicsearchdetails returns APPEAL_TYPE_NAME_REQUIRED when appealTypeName missing", async () => {
|
test("getbasicsearchdetails returns APPEAL_TYPE_NAME_REQUIRED when appealTypeName missing", async () => {
|
||||||
const mod = loadModule("pages/api/endpoint/getbasicsearchdetails_api.js", {
|
const mod = loadModule("pages/api/endpoint/getbasicsearchdetails_api.js", {
|
||||||
respondError: respondErrorMock,
|
respondError: respondErrorMock,
|
||||||
@@ -3609,6 +3713,32 @@ test("getrepresentations catch path returns REPRESENTATIONS_FETCH_FAILED", async
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getrepresentations passes relayPolicy overrides to relayGet", async () => {
|
||||||
|
let capturedRelayPolicy = null;
|
||||||
|
|
||||||
|
const mod = loadModule("pages/api/endpoint/getrepresentations_api.js", {
|
||||||
|
respondError: respondErrorMock,
|
||||||
|
respondSuccess: respondSuccessMock,
|
||||||
|
relayGet: async ({ relayPolicy, res }) => {
|
||||||
|
capturedRelayPolicy = relayPolicy;
|
||||||
|
return respondSuccessMock(res, { value: [] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = { query: { incidentID: "i1" } };
|
||||||
|
const res = createRes();
|
||||||
|
await mod.default(req, res);
|
||||||
|
|
||||||
|
assert.strictEqual(res.state.statusCode, 200);
|
||||||
|
assert.deepStrictEqual(JSON.parse(JSON.stringify(capturedRelayPolicy)), {
|
||||||
|
method: "GET",
|
||||||
|
timeoutMs: 8000,
|
||||||
|
maxRetries: 2,
|
||||||
|
retryBaseDelayMs: 150,
|
||||||
|
retryMaxDelayMs: 800
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("getdnscoords catch path returns DNS_COORDS_FETCH_FAILED", async () => {
|
test("getdnscoords catch path returns DNS_COORDS_FETCH_FAILED", async () => {
|
||||||
const mod = loadModule("pages/api/endpoint/getdnscoords_api.js", {
|
const mod = loadModule("pages/api/endpoint/getdnscoords_api.js", {
|
||||||
respondError: respondErrorMock,
|
respondError: respondErrorMock,
|
||||||
|
|||||||
Reference in New Issue
Block a user