TASK22269: group migrate remaining account/portal signed GET flows
This commit is contained in:
@@ -1,14 +1,21 @@
|
|||||||
import { requestJson } from "./endpointClient";
|
import { requestJson } from "./endpointClient";
|
||||||
import { buildHashedQueryUrl } from "./relayClient";
|
import { buildHashedQueryUrl } from "./relayClient";
|
||||||
|
|
||||||
|
export const buildSignedUrl = async (queryUrl, config = {}) => {
|
||||||
|
const { baseUrl = "" } = config;
|
||||||
|
|
||||||
|
return `${baseUrl}${await buildHashedQueryUrl(queryUrl)}`;
|
||||||
|
};
|
||||||
|
|
||||||
const signedRequestJson = async ({ method, queryUrl, data, config = {} }) => {
|
const signedRequestJson = async ({ method, queryUrl, data, config = {} }) => {
|
||||||
const signedUrl = await buildHashedQueryUrl(queryUrl);
|
const { baseUrl, ...requestConfig } = config;
|
||||||
|
const signedUrl = await buildSignedUrl(queryUrl, { baseUrl });
|
||||||
|
|
||||||
return requestJson({
|
return requestJson({
|
||||||
method,
|
method,
|
||||||
url: signedUrl,
|
url: signedUrl,
|
||||||
data,
|
data,
|
||||||
...config
|
...requestConfig
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { BASE_URL } from "../core/env";
|
import { BASE_URL } from "../core/env";
|
||||||
import { consoleLogger } from "../core/logger";
|
import { consoleLogger } from "../core/logger";
|
||||||
import { buildHashedQueryUrl } from "../clients/relayClient";
|
|
||||||
import { getJson, requestJson } from "../clients/endpointClient";
|
import { getJson, requestJson } from "../clients/endpointClient";
|
||||||
|
import { getSignedJson } from "../clients/signedRequestClient";
|
||||||
|
|
||||||
export const getPersonalAccount = (contactid) => {
|
export const getPersonalAccount = (contactid) => {
|
||||||
return getJson(
|
return getJson(
|
||||||
@@ -78,13 +78,11 @@ export const getPortalLogin = async (emailAddress) => {
|
|||||||
var queryUrl =
|
var queryUrl =
|
||||||
"/api/endpoint/getportallogin_api?emailAddress=" + emailAddress;
|
"/api/endpoint/getportallogin_api?emailAddress=" + emailAddress;
|
||||||
|
|
||||||
return getJson(BASE_URL + (await buildHashedQueryUrl(queryUrl))).catch(
|
return getSignedJson(queryUrl, { baseUrl: BASE_URL }).catch((error) => {
|
||||||
(error) => {
|
consoleLogger(error);
|
||||||
consoleLogger(error);
|
|
||||||
|
|
||||||
return JSON.stringify(error);
|
return JSON.stringify(error);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPortalLoginProxy = async (emailAddress) => {
|
export const getPortalLoginProxy = async (emailAddress) => {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { BASE_URL } from "../core/env";
|
import { BASE_URL } from "../core/env";
|
||||||
import { consoleLogger } from "../core/logger";
|
import { consoleLogger } from "../core/logger";
|
||||||
import { buildHashedQueryUrl } from "../clients/relayClient";
|
|
||||||
import { getJson, requestJson } from "../clients/endpointClient";
|
import { getJson, requestJson } from "../clients/endpointClient";
|
||||||
import { deleteSignedJson } from "../clients/signedRequestClient";
|
import {
|
||||||
|
deleteSignedJson,
|
||||||
|
buildSignedUrl
|
||||||
|
} from "../clients/signedRequestClient";
|
||||||
import {
|
import {
|
||||||
buildFileQuery,
|
buildFileQuery,
|
||||||
withBaseUrl,
|
withBaseUrl,
|
||||||
@@ -207,7 +209,7 @@ export const sendCaseCompleteMessage = async (
|
|||||||
inv
|
inv
|
||||||
});
|
});
|
||||||
|
|
||||||
var signedQueryUrl = await buildHashedQueryUrl(hashQueryPath);
|
var signedQueryUrl = await buildSignedUrl(hashQueryPath);
|
||||||
|
|
||||||
queryUrl = appendQuerySuffix(
|
queryUrl = appendQuerySuffix(
|
||||||
queryUrl,
|
queryUrl,
|
||||||
@@ -260,7 +262,7 @@ export const sendRepCompleteMessage = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var queryUrl = await buildHashedQueryUrl(hashQueryPath);
|
var queryUrl = await buildSignedUrl(hashQueryPath);
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
method: "get",
|
method: "get",
|
||||||
|
|||||||
@@ -2832,3 +2832,42 @@ Validation:
|
|||||||
Follow-ups:
|
Follow-ups:
|
||||||
|
|
||||||
- Candidate map now indicates remaining explicit signed request composition is primarily in account/portal signed GET edge paths (`getPortalLogin`, `sendRepCompleteMessage`, and signed suffix append flow in `sendCaseCompleteMessage`) for future bounded slices.
|
- Candidate map now indicates remaining explicit signed request composition is primarily in account/portal signed GET edge paths (`getPortalLogin`, `sendRepCompleteMessage`, and signed suffix append flow in `sendCaseCompleteMessage`) for future bounded slices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### CL-079: TASK22269 Slice B1.4 — signed GET consolidation bundle (portal + account)
|
||||||
|
|
||||||
|
date: 2026-03-25
|
||||||
|
author: Cline
|
||||||
|
scope: `actions/clients/signedRequestClient.js`, `actions/services/{portalDirectService,accountDirectService}.js`, `tests/{serviceHarness,phase22/portal-service-behaviour,phase7/service-behaviour}.cjs`
|
||||||
|
type: change
|
||||||
|
rationale: Continue grouped signed-request migration by consolidating remaining direct signed-GET composition paths onto shared signed helper primitives while preserving route behavior contracts.
|
||||||
|
impact: Reduces residual signing duplication and standardizes signed URL creation across portal/account service read/message flows.
|
||||||
|
status: completed
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
- Enhanced `signedRequestClient`:
|
||||||
|
- added `buildSignedUrl(queryUrl, { baseUrl? })` helper for signed URL generation reuse
|
||||||
|
- updated internal signed request execution to use `buildSignedUrl`
|
||||||
|
- Migrated account signed GET candidate:
|
||||||
|
- `accountDirectService.getPortalLogin` now uses `getSignedJson(queryUrl, { baseUrl: BASE_URL })`
|
||||||
|
- preserved existing error semantics (`consoleLogger` + `JSON.stringify(error)`)
|
||||||
|
- Migrated portal signed GET candidates:
|
||||||
|
- `portalDirectService.sendRepCompleteMessage` now uses `buildSignedUrl(hashQueryPath)`
|
||||||
|
- `portalDirectService.sendCaseCompleteMessage` now uses `buildSignedUrl(hashQueryPath)` + existing signed suffix append behavior
|
||||||
|
- preserved existing request method/URL shape and catch-path behavior
|
||||||
|
- Updated test harness and suites:
|
||||||
|
- `tests/serviceHarness.cjs` now provides defaults for `buildSignedUrl`, `getSignedJson`, `postSignedJson`
|
||||||
|
- `tests/phase22/portal-service-behaviour.test.cjs` includes assertion for `sendRepCompleteMessage` signed-helper delegation
|
||||||
|
- `tests/phase7/service-behaviour.test.cjs` account portal-login expectations aligned to request-config path used by shared signed helper
|
||||||
|
|
||||||
|
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
|
||||||
|
- `node tests/phase7/service-behaviour.test.cjs` -> pass (13/13)
|
||||||
|
|
||||||
|
Follow-ups:
|
||||||
|
|
||||||
|
- Remaining special-case signed pattern is now primarily the signed-suffix append composition in `sendCaseCompleteMessage` (already using shared `buildSignedUrl`), with broader module migrations to be planned in future bounded slices.
|
||||||
|
|||||||
@@ -134,6 +134,47 @@ test("portal/sendRepCompleteMessage rejects when hash signing fails before reque
|
|||||||
assert.strictEqual(logger.calls.length, 0);
|
assert.strictEqual(logger.calls.length, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("portal/sendRepCompleteMessage requests signed URL from shared signed helper", async () => {
|
||||||
|
const axios = createAxiosMock();
|
||||||
|
const logger = createLoggerMock();
|
||||||
|
|
||||||
|
const signedCalls = [];
|
||||||
|
const requestCalls = [];
|
||||||
|
|
||||||
|
const portal = loadServiceModule("portalDirectService.js", {
|
||||||
|
axios,
|
||||||
|
BASE_URL: "",
|
||||||
|
consoleLogger: logger.consoleLogger,
|
||||||
|
buildSignedUrl: async (queryUrl) => {
|
||||||
|
signedCalls.push(queryUrl);
|
||||||
|
return `${queryUrl}&hash=signed-portal`;
|
||||||
|
},
|
||||||
|
requestJson: async (config) => {
|
||||||
|
requestCalls.push(config);
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await portal.sendRepCompleteMessage(
|
||||||
|
"container-x",
|
||||||
|
"CASE-99",
|
||||||
|
"rep-a"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepStrictEqual(normalize(result), { ok: true });
|
||||||
|
assert.strictEqual(signedCalls.length, 1);
|
||||||
|
assert.strictEqual(
|
||||||
|
signedCalls[0],
|
||||||
|
"/api/file/createrepcompletemessage_api?container=container-x&tempcaseref=CASE-99&repid=rep-a"
|
||||||
|
);
|
||||||
|
assert.strictEqual(requestCalls.length, 1);
|
||||||
|
assert.strictEqual(requestCalls[0].method, "get");
|
||||||
|
assert.strictEqual(
|
||||||
|
requestCalls[0].url,
|
||||||
|
"/api/file/createrepcompletemessage_api?container=container-x&tempcaseref=CASE-99&repid=rep-a&hash=signed-portal"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("portal/deleteWatchedCases logs and returns undefined when signed delete fails", async () => {
|
test("portal/deleteWatchedCases logs and returns undefined when signed delete fails", async () => {
|
||||||
const axios = createAxiosMock();
|
const axios = createAxiosMock();
|
||||||
const logger = createLoggerMock();
|
const logger = createLoggerMock();
|
||||||
|
|||||||
@@ -256,9 +256,13 @@ test("account/getPortalLogin appends hash and returns res.data", async () => {
|
|||||||
return { data: { hash: "&hash=login123" } };
|
return { data: { hash: "&hash=login123" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data: { value: [{ id: "user-1" }] } };
|
throw new Error("Unexpected get url: " + url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
axios.requestHandler = async () => ({
|
||||||
|
data: { value: [{ id: "user-1" }] }
|
||||||
|
});
|
||||||
|
|
||||||
const account = loadServiceModule("accountDirectService.js", {
|
const account = loadServiceModule("accountDirectService.js", {
|
||||||
axios,
|
axios,
|
||||||
BASE_URL: "http://example.local",
|
BASE_URL: "http://example.local",
|
||||||
@@ -274,8 +278,9 @@ test("account/getPortalLogin appends hash and returns res.data", async () => {
|
|||||||
signCalls[0],
|
signCalls[0],
|
||||||
"/api/endpoint/gethash_api?path=%2Fapi%2Fendpoint%2Fgetportallogin_api%3FemailAddress%3Dperson%40example.com"
|
"/api/endpoint/gethash_api?path=%2Fapi%2Fendpoint%2Fgetportallogin_api%3FemailAddress%3Dperson%40example.com"
|
||||||
);
|
);
|
||||||
|
assert.strictEqual(axios.calls[1].config.method, "get");
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
axios.calls[1].url,
|
axios.calls[1].config.url,
|
||||||
"http://example.local/api/endpoint/getportallogin_api?emailAddress=person@example.com&hash=login123"
|
"http://example.local/api/endpoint/getportallogin_api?emailAddress=person@example.com&hash=login123"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -290,9 +295,11 @@ test("account/getPortalLogin returns JSON stringified error on failure", async (
|
|||||||
return { data: { hash: "&hash=err" } };
|
return { data: { hash: "&hash=err" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
throw new Error("Unexpected get url: " + url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
axios.requestHandler = async () => Promise.reject(error);
|
||||||
|
|
||||||
const account = loadServiceModule("accountDirectService.js", {
|
const account = loadServiceModule("accountDirectService.js", {
|
||||||
axios,
|
axios,
|
||||||
BASE_URL: "",
|
BASE_URL: "",
|
||||||
|
|||||||
@@ -107,6 +107,15 @@ const loadServiceModule = (fileName, injected = {}) => {
|
|||||||
return queryUrl + hashResponse.data.hash;
|
return queryUrl + hashResponse.data.hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultBuildSignedUrl = async (queryUrl, config = {}) => {
|
||||||
|
const { baseUrl = "" } = config;
|
||||||
|
const hashedUrl = await (
|
||||||
|
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl
|
||||||
|
)(queryUrl);
|
||||||
|
|
||||||
|
return `${baseUrl}${hashedUrl}`;
|
||||||
|
};
|
||||||
|
|
||||||
const defaultGetSignedFileJson = async (queryUrl) => {
|
const defaultGetSignedFileJson = async (queryUrl) => {
|
||||||
const hashedUrl = await (
|
const hashedUrl = await (
|
||||||
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl
|
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl
|
||||||
@@ -138,6 +147,33 @@ const loadServiceModule = (fileName, injected = {}) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultGetSignedJson = async (queryUrl, config = {}) => {
|
||||||
|
const { baseUrl, ...requestConfig } = config;
|
||||||
|
const signedUrl = await (
|
||||||
|
injected.buildSignedUrl || defaultBuildSignedUrl
|
||||||
|
)(queryUrl, { baseUrl });
|
||||||
|
|
||||||
|
return (injected.requestJson || defaultRequestJson)({
|
||||||
|
method: "get",
|
||||||
|
url: signedUrl,
|
||||||
|
...requestConfig
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultPostSignedJson = async (queryUrl, data, config = {}) => {
|
||||||
|
const { baseUrl, ...requestConfig } = config;
|
||||||
|
const signedUrl = await (
|
||||||
|
injected.buildSignedUrl || defaultBuildSignedUrl
|
||||||
|
)(queryUrl, { baseUrl });
|
||||||
|
|
||||||
|
return (injected.requestJson || defaultRequestJson)({
|
||||||
|
method: "post",
|
||||||
|
url: signedUrl,
|
||||||
|
data,
|
||||||
|
...requestConfig
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const defaultBuildFileQuery = (pathValue, params = {}, options = {}) => {
|
const defaultBuildFileQuery = (pathValue, params = {}, options = {}) => {
|
||||||
const { encode = false } = options;
|
const { encode = false } = options;
|
||||||
const entries = Object.entries(params).filter(([, value]) => {
|
const entries = Object.entries(params).filter(([, value]) => {
|
||||||
@@ -184,7 +220,10 @@ const loadServiceModule = (fileName, injected = {}) => {
|
|||||||
getSignedFileJson:
|
getSignedFileJson:
|
||||||
injected.getSignedFileJson || defaultGetSignedFileJson,
|
injected.getSignedFileJson || defaultGetSignedFileJson,
|
||||||
downloadFileBlob: injected.downloadFileBlob || defaultDownloadFileBlob,
|
downloadFileBlob: injected.downloadFileBlob || defaultDownloadFileBlob,
|
||||||
|
getSignedJson: injected.getSignedJson || defaultGetSignedJson,
|
||||||
|
postSignedJson: injected.postSignedJson || defaultPostSignedJson,
|
||||||
deleteSignedJson: injected.deleteSignedJson || defaultDeleteSignedJson,
|
deleteSignedJson: injected.deleteSignedJson || defaultDeleteSignedJson,
|
||||||
|
buildSignedUrl: injected.buildSignedUrl || defaultBuildSignedUrl,
|
||||||
buildHashedQueryUrl:
|
buildHashedQueryUrl:
|
||||||
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl,
|
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl,
|
||||||
buildFileQuery: injected.buildFileQuery || defaultBuildFileQuery,
|
buildFileQuery: injected.buildFileQuery || defaultBuildFileQuery,
|
||||||
|
|||||||
Reference in New Issue
Block a user