diff --git a/actions/clients/index.js b/actions/clients/index.js index 3c9b8ee9..82af288e 100644 --- a/actions/clients/index.js +++ b/actions/clients/index.js @@ -2,3 +2,4 @@ export * from "./relayClient"; export * from "./endpointClient"; export * from "./fileClient"; export * from "./fileRouteBuilder"; +export * from "./signedRequestClient"; diff --git a/actions/clients/signedRequestClient.js b/actions/clients/signedRequestClient.js new file mode 100644 index 00000000..55aa6191 --- /dev/null +++ b/actions/clients/signedRequestClient.js @@ -0,0 +1,38 @@ +import { requestJson } from "./endpointClient"; +import { buildHashedQueryUrl } from "./relayClient"; + +const signedRequestJson = async ({ method, queryUrl, data, config = {} }) => { + const signedUrl = await buildHashedQueryUrl(queryUrl); + + return requestJson({ + method, + url: signedUrl, + data, + ...config + }); +}; + +export const getSignedJson = (queryUrl, config = {}) => { + return signedRequestJson({ + method: "get", + queryUrl, + config + }); +}; + +export const postSignedJson = (queryUrl, data, config = {}) => { + return signedRequestJson({ + method: "post", + queryUrl, + data, + config + }); +}; + +export const deleteSignedJson = (queryUrl, config = {}) => { + return signedRequestJson({ + method: "delete", + queryUrl, + config + }); +}; diff --git a/actions/services/portalDirectService.js b/actions/services/portalDirectService.js index 54b1eb92..d462f57f 100644 --- a/actions/services/portalDirectService.js +++ b/actions/services/portalDirectService.js @@ -2,6 +2,7 @@ import { BASE_URL } from "../core/env"; import { consoleLogger } from "../core/logger"; import { buildHashedQueryUrl } from "../clients/relayClient"; import { getJson, requestJson } from "../clients/endpointClient"; +import { deleteSignedJson } from "../clients/signedRequestClient"; import { buildFileQuery, withBaseUrl, @@ -189,16 +190,9 @@ export const deleteWatchedCases = async (watchedCaseID) => { watchedCaseID }); - return buildHashedQueryUrl(queryUrl) - .then((signedUrl) => - requestJson({ - method: "delete", - url: signedUrl - }) - ) - .catch((error) => { - consoleLogger(error); - }); + return deleteSignedJson(queryUrl).catch((error) => { + consoleLogger(error); + }); }; export const sendCaseCompleteMessage = async ( diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index 269a4093..3caa5361 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -2725,3 +2725,46 @@ Validation: Follow-ups: - Sequence A step3 targeted gaps are now covered; further test expansion should be treated as new scope (e.g., deeper end-to-end journey assertions). + +--- + +### CL-076: TASK22269 Slice B1.1 — signed-request helper set + portal pilot signed-flow migration + +date: 2026-03-25 +author: Cline +scope: `actions/clients/{signedRequestClient,index}.js`, `actions/services/portalDirectService.js`, `tests/{serviceHarness,phase22/portal-service-behaviour}.cjs` +type: change +rationale: Execute Sequence B Workstream B1 pilot by introducing shared signed request helpers (GET/POST/DELETE) and migrating one bounded portal signed flow without broader module rollout. +impact: Reduces duplication and drift risk in hash-signing + method execution paths while preserving existing signed-flow behavior contracts. +status: completed + +Summary: + +- Added new shared signed-request client helper module: + - `actions/clients/signedRequestClient.js` + - exports: + - `getSignedJson(queryUrl, config?)` + - `postSignedJson(queryUrl, data, config?)` + - `deleteSignedJson(queryUrl, config?)` + - all helpers use existing `buildHashedQueryUrl(...)` + `requestJson(...)` composition to preserve signing semantics +- Exported new helper module via `actions/clients/index.js`. +- Migrated exactly one pilot signed flow in portal service: + - `deleteWatchedCases` in `actions/services/portalDirectService.js` + - from inline `buildHashedQueryUrl(...).then(requestJson(...))` to `deleteSignedJson(queryUrl)` + - preserved existing catch/log behavior (`consoleLogger` + `undefined` return on catch) +- Added test harness compatibility for VM import-stripping suites: + - `tests/serviceHarness.cjs` now injects default `deleteSignedJson` mock behavior. +- Expanded portal behavioral tests with explicit negative-path assertion: + - `tests/phase22/portal-service-behaviour.test.cjs` + - verifies `deleteWatchedCases` logs and safely returns `undefined` when signed delete fails. + +Validation: + +- `npm run lint` -> pass with warnings only (pre-existing `react-hooks/exhaustive-deps`; no new lint errors) +- `node tests/phase22/index.test.cjs` -> pass + - portal-service suite now 5/5 including signed-delete failure path +- `node tests/phase7/service-behaviour.test.cjs` -> pass (13/13) + +Follow-ups: + +- Continue Sequence B B1 in future bounded slices by migrating additional signed flows one module/function cluster at a time (outside this slice). diff --git a/tests/phase22/portal-service-behaviour.test.cjs b/tests/phase22/portal-service-behaviour.test.cjs index f1f33b4b..cc386e80 100644 --- a/tests/phase22/portal-service-behaviour.test.cjs +++ b/tests/phase22/portal-service-behaviour.test.cjs @@ -134,6 +134,27 @@ test("portal/sendRepCompleteMessage rejects when hash signing fails before reque assert.strictEqual(logger.calls.length, 0); }); +test("portal/deleteWatchedCases logs and returns undefined when signed delete fails", async () => { + const axios = createAxiosMock(); + const logger = createLoggerMock(); + const error = createAxiosError(401, "Unauthorized"); + + const portal = loadServiceModule("portalDirectService.js", { + axios, + BASE_URL: "", + consoleLogger: logger.consoleLogger, + deleteSignedJson: async () => { + throw error; + } + }); + + const result = await portal.deleteWatchedCases("watch-2"); + + assert.strictEqual(result, undefined); + assert.strictEqual(logger.calls.length, 1); + assert.strictEqual(logger.calls[0], error); +}); + const run = async () => { let passed = 0; diff --git a/tests/serviceHarness.cjs b/tests/serviceHarness.cjs index 4cf494ff..c73a4961 100644 --- a/tests/serviceHarness.cjs +++ b/tests/serviceHarness.cjs @@ -126,6 +126,18 @@ const loadServiceModule = (fileName, injected = {}) => { }); }; + const defaultDeleteSignedJson = async (queryUrl, config = {}) => { + const hashedUrl = await ( + injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl + )(queryUrl); + + return (injected.requestJson || defaultRequestJson)({ + method: "delete", + url: hashedUrl, + ...config + }); + }; + const defaultBuildFileQuery = (pathValue, params = {}, options = {}) => { const { encode = false } = options; const entries = Object.entries(params).filter(([, value]) => { @@ -172,6 +184,7 @@ const loadServiceModule = (fileName, injected = {}) => { getSignedFileJson: injected.getSignedFileJson || defaultGetSignedFileJson, downloadFileBlob: injected.downloadFileBlob || defaultDownloadFileBlob, + deleteSignedJson: injected.deleteSignedJson || defaultDeleteSignedJson, buildHashedQueryUrl: injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl, buildFileQuery: injected.buildFileQuery || defaultBuildFileQuery,