feat(relay): apply policy overrides for my-portal case and representation endpoints

This commit is contained in:
2026-03-24 15:52:29 +00:00
parent 0a5819803e
commit 318f913397
7 changed files with 213 additions and 0 deletions
+38
View File
@@ -1369,3 +1369,41 @@ Validation:
Follow-ups:
- 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 +
" 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({
queryUrl,
res,
relayPolicy,
transformData: (data) => {
data.value.forEach(function (element) {
element.pinswg_title = element.title;
+9
View File
@@ -39,9 +39,18 @@ export default async function ApiProxy(req, res) {
loggedInUserId +
" and pinswg_appealcasetype ne null&$orderby=createdon desc&$count=true";
const relayPolicy = {
method: "GET",
timeoutMs: 8000,
maxRetries: 2,
retryBaseDelayMs: 150,
retryMaxDelayMs: 800
};
return relayGet({
queryUrl,
res,
relayPolicy,
transformData: (data) => {
data.value.forEach(function (element) {
element.pinswg_title = element.title;
@@ -39,9 +39,18 @@ export default async function ApiProxy(req, res) {
loggedInUserId +
"&$count=true&$orderby=createdon desc";
const relayPolicy = {
method: "GET",
timeoutMs: 8000,
maxRetries: 2,
retryBaseDelayMs: 150,
retryMaxDelayMs: 800
};
return relayGet({
queryUrl,
res,
relayPolicy,
errorResponse: {
status: 400,
code: "MY_REPRESENTATIONS_FETCH_FAILED",
@@ -36,9 +36,18 @@ export default async function ApiProxy(req, res) {
incidentID +
" and pinswg_publishtoweb eq true&$count=true&$orderby=createdon desc";
const relayPolicy = {
method: "GET",
timeoutMs: 8000,
maxRetries: 2,
retryBaseDelayMs: 150,
retryMaxDelayMs: 800
};
return relayGet({
queryUrl,
res,
relayPolicy,
errorResponse: {
status: 400,
code: "REPRESENTATIONS_FETCH_FAILED",
@@ -39,9 +39,18 @@ export default async function ApiProxy(req, res) {
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)";
const relayPolicy = {
method: "GET",
timeoutMs: 8000,
maxRetries: 2,
retryBaseDelayMs: 150,
retryMaxDelayMs: 800
};
return relayGet({
queryUrl,
res,
relayPolicy,
transformData: (data) => {
data.value.forEach(function (element) {
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");
});
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 () => {
const mod = loadModule("pages/api/endpoint/getmyrepresentations_api.js", {
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 () => {
const mod = loadModule("pages/api/endpoint/getwatchedcases_api.js", {
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 () => {
const mod = loadModule("pages/api/endpoint/getawaitingsubmission_api.js", {
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 () => {
const mod = loadModule("pages/api/endpoint/getbasicsearchdetails_api.js", {
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 () => {
const mod = loadModule("pages/api/endpoint/getdnscoords_api.js", {
respondError: respondErrorMock,