Merged PR 2208: update and clean azurestorage.js

Related work items: #22326
This commit is contained in:
Robert Bond
2026-03-26 11:42:42 +00:00
16 changed files with 974 additions and 217 deletions
+144 -128
View File
@@ -31,6 +31,88 @@ const QUEUE_PATH = process.env.AZURE_PEDW_QUEUE_ENDPOINT;
const accountName = process.env.AZURE_STORAGE_ACCOUNT_NAME;
const buildDownloadBlobQueryPath = ({
containerName,
casefolderID,
blobname,
encodeCasefolderID = true,
encodeBlobname = true
}) => {
const casefolderPart = encodeCasefolderID
? encodeURIComponent(casefolderID)
: casefolderID;
const blobPart = encodeBlobname ? encodeURIComponent(blobname) : blobname;
return (
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
casefolderPart +
"&blobname=" +
blobPart
);
};
const buildDeleteBlobQueryPath = ({
containerName,
casefolderID,
blobname,
encodeCasefolderID = true,
encodeBlobname = true
}) => {
const casefolderPart = encodeCasefolderID
? encodeURIComponent(casefolderID)
: casefolderID;
const blobPart = encodeBlobname ? encodeURIComponent(blobname) : blobname;
return (
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
casefolderPart +
"&blobname=" +
blobPart
);
};
const buildGetBlobListQueryPath = ({ containerName, casefolderID }) => {
return (
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
casefolderID
);
};
const buildCaseObjectPath = (casefolderID) => {
return casefolderID + "/" + casefolderID + "_case.json";
};
const buildHashMetadataPaths = ({ containerName, casefolderID, blobname }) => {
return {
"hashedfilepath": hashAPIPath(
buildDownloadBlobQueryPath({
containerName,
casefolderID,
blobname
})
),
"hasheddeletepath": hashAPIPath(
buildDeleteBlobQueryPath({
containerName,
casefolderID,
blobname
})
),
"hashgetblobs": hashAPIPath(
buildGetBlobListQueryPath({
containerName,
casefolderID
})
)
};
};
export const createContainerSas = async (containerName) => {
// Get environment variables
@@ -189,59 +271,36 @@ export const getBlobs = async (containerName, casefolderID) => {
for await (const blob of containerClient.listBlobsFlat({
prefix: casefolderID + "/files/"
})) {
let blobDocumentType = blob.name
.split("/")[2]
.slice(0, blob.name.split("/")[2].indexOf("_"));
const blobPathParts = blob.name.split("/");
const fileName = blobPathParts[2];
const contentLength = blob.properties.contentLength;
blobObj.push({
"name": blob.name.split("/")[2],
"name": fileName,
"path": blob.name,
"documentType": getDocumentTypeFromFilename(
blob.name.split("/")[2]
),
"documentType": getDocumentTypeFromFilename(fileName),
"versionId": blob.versionId,
"caseObj": casefolderID + "/" + casefolderID + "_case.json",
"caseObj": buildCaseObjectPath(casefolderID),
"isCurrentVersion": blob.isCurrentVersion,
"contentLength": blob.properties.contentLength,
"size": blob.properties.contentLength,
"contentLength": contentLength,
"size": contentLength,
"contentType": blob.contentType,
"lastModified": blob.properties.lastModified,
"filepath":
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blob.name),
"hashedfilepath": hashAPIPath(
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[2])
),
"deletepath":
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[2]),
"hasheddeletepath": hashAPIPath(
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(casefolderID) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[2])
),
"hashgetblobs": hashAPIPath(
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
casefolderID
)
"filepath": buildDownloadBlobQueryPath({
containerName,
casefolderID,
blobname: blob.name
}),
...buildHashMetadataPaths({
containerName,
casefolderID,
blobname: fileName
}),
"deletepath": buildDeleteBlobQueryPath({
containerName,
casefolderID,
blobname: fileName
})
});
}
//console.log("blobObj:", blobObj);
@@ -1235,38 +1294,25 @@ export const getProgressBlobs = async (containerName, caseReference) => {
for await (const blob of containerClient.listBlobsFlat({
prefix: caseReference + "/" + caseReference + "_appeal.json"
})) {
console.log("getProgressBlobs in here", blob.name.split("/"));
const blobPathParts = blob.name.split("/");
const appealBlobName = blobPathParts[1];
const contentLength = blob.properties.contentLength;
consoleLogger("getProgressBlobs in here", blobPathParts);
blobObj.push({
"name": blob.name.split("/")[1],
"name": appealBlobName,
"path": blob.name,
"versionId": blob.versionId,
"caseObj": caseReference + "/" + caseReference + "_case.json",
"caseObj": buildCaseObjectPath(caseReference),
"isCurrentVersion": blob.isCurrentVersion,
"contentLength": blob.properties.contentLength,
"contentLength": contentLength,
"contentType": blob.contentType,
"lastModified": blob.properties.lastModified,
"hashedfilepath": hashAPIPath(
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(caseReference) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[1])
),
"hasheddeletepath": hashAPIPath(
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(caseReference) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[1])
),
"hashgetblobs": hashAPIPath(
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
caseReference
)
...buildHashMetadataPaths({
containerName,
casefolderID: caseReference,
blobname: appealBlobName
})
});
}
@@ -1429,71 +1475,41 @@ export const getRepsFilesBlobs = async (
for await (const blob of containerClient.listBlobsFlat({
prefix: casefolderID + "/" + filenamePrefix + "/files/"
})) {
let blobDocumentType = blob.name
.split("/")[2]
.slice(0, blob.name.split("/")[2].indexOf("_"));
const blobPathParts = blob.name.split("/");
const casefolderPath = blobPathParts[0] + "/" + blobPathParts[1];
const repFileName = blobPathParts[3];
const contentLength = blob.properties.contentLength;
console.log(blob.name);
consoleLogger(blob.name);
blobObj.push({
"name": blob.name.split("/")[3],
"name": repFileName,
"path": blob.name,
"documentType": getDocumentTypeFromFilename(
blob.name.split("/")[3]
),
"documentType": getDocumentTypeFromFilename(repFileName),
"versionId": blob.versionId,
"isCurrentVersion": blob.isCurrentVersion,
"contentLength": blob.properties.contentLength,
"contentLength": contentLength,
"filenameprefix": filenamePrefix,
"filepath":
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(
blob.name.split("/")[0] + "/" + blob.name.split("/")[1]
) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[3]),
"hashedfilepath": hashAPIPath(
"/api/file/downloadblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(
blob.name.split("/")[0] + "/" + blob.name.split("/")[1]
) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[3])
),
"deletepath":
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
blob.name.split("/")[0] +
"/" +
blob.name.split("/")[1] +
"&blobname=" +
blob.name.split("/")[3],
"hasheddeletepath": hashAPIPath(
"/api/file/deleteblob?container=" +
containerName +
"&casefolderID=" +
encodeURIComponent(
blob.name.split("/")[0] + "/" + blob.name.split("/")[1]
) +
"&blobname=" +
encodeURIComponent(blob.name.split("/")[3])
),
"hashgetblobs": hashAPIPath(
"/api/file/getbloblist?container=" +
containerName +
"&casefolderID=" +
blob.name.split("/")[0] +
"/" +
blob.name.split("/")[1]
)
"filepath": buildDownloadBlobQueryPath({
containerName,
casefolderID: casefolderPath,
blobname: repFileName
}),
...buildHashMetadataPaths({
containerName,
casefolderID: casefolderPath,
blobname: repFileName
}),
"deletepath": buildDeleteBlobQueryPath({
containerName,
casefolderID: casefolderPath,
blobname: repFileName,
encodeCasefolderID: false,
encodeBlobname: false
})
});
}
console.log("blobObj:", blobObj);
consoleLogger("blobObj:", blobObj);
return blobObj;
};
+3 -15
View File
@@ -1,17 +1,12 @@
import { getJson, requestJson } from "./endpointClient";
import { buildHashedQueryUrl } from "./relayClient";
import { getSignedJson, postSignedJson } from "./signedRequestClient";
export const getFileJson = (url) => {
return getJson(url);
};
export const getSignedFileJson = async (queryUrl) => {
const signedUrl = await buildHashedQueryUrl(queryUrl);
return requestJson({
method: "get",
url: signedUrl
});
return getSignedJson(queryUrl);
};
export const downloadFileBlob = (url) => {
@@ -23,12 +18,5 @@ export const downloadFileBlob = (url) => {
};
export const postSignedFileJson = async (queryUrl, data, config = {}) => {
const signedUrl = await buildHashedQueryUrl(queryUrl);
return requestJson({
method: "post",
url: signedUrl,
data,
...config
});
return postSignedJson(queryUrl, data, config);
};
+4
View File
@@ -31,3 +31,7 @@ export const withBaseUrl = (baseUrl, route) => {
export const appendQuerySuffix = (route, suffix = "") => {
return `${route}${suffix}`;
};
export const appendHashSuffix = (route, hashBuilder) => {
return appendQuerySuffix(route, hashBuilder(route));
};
+1
View File
@@ -2,3 +2,4 @@ export * from "./relayClient";
export * from "./endpointClient";
export * from "./fileClient";
export * from "./fileRouteBuilder";
export * from "./signedRequestClient";
+45
View File
@@ -0,0 +1,45 @@
import { requestJson } from "./endpointClient";
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 { baseUrl, ...requestConfig } = config;
const signedUrl = await buildSignedUrl(queryUrl, { baseUrl });
return requestJson({
method,
url: signedUrl,
data,
...requestConfig
});
};
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
});
};
+5 -7
View File
@@ -1,7 +1,7 @@
import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { buildHashedQueryUrl } from "../clients/relayClient";
import { getJson, requestJson } from "../clients/endpointClient";
import { getSignedJson } from "../clients/signedRequestClient";
export const getPersonalAccount = (contactid) => {
return getJson(
@@ -78,13 +78,11 @@ export const getPortalLogin = async (emailAddress) => {
var queryUrl =
"/api/endpoint/getportallogin_api?emailAddress=" + emailAddress;
return getJson(BASE_URL + (await buildHashedQueryUrl(queryUrl))).catch(
(error) => {
consoleLogger(error);
return getSignedJson(queryUrl, { baseUrl: BASE_URL }).catch((error) => {
consoleLogger(error);
return JSON.stringify(error);
}
);
return JSON.stringify(error);
});
};
export const getPortalLoginProxy = async (emailAddress) => {
+7 -6
View File
@@ -4,7 +4,8 @@ import { hashAPIPath } from "../core/hash";
import {
buildFileQuery,
withBaseUrl,
appendQuerySuffix
appendQuerySuffix,
appendHashSuffix
} from "../clients/fileRouteBuilder";
import {
getFileJson,
@@ -19,7 +20,7 @@ export const getAwaitingSubmissionFromBlob = (containerName) => {
});
return getFileJson(
withBaseUrl(BASE_URL, appendQuerySuffix(route, hashAPIPath(route)))
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
).catch((error) => {
consoleLogger(error);
});
@@ -31,7 +32,7 @@ export const getRepsFromBlob = (containerName) => {
});
return getFileJson(
withBaseUrl(BASE_URL, appendQuerySuffix(route, hashAPIPath(route)))
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
).catch((error) => {
consoleLogger(error);
});
@@ -227,7 +228,7 @@ export const getFilesFromBlob = (containerName, casefolderID) => {
});
return getFileJson(
withBaseUrl(BASE_URL, appendQuerySuffix(route, hashAPIPath(route)))
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
).catch((error) => {
consoleLogger(error);
});
@@ -328,7 +329,7 @@ export const getProgressFromBlob = async (containerName, casereference) => {
);
return getFileJson(
withBaseUrl(BASE_URL, appendQuerySuffix(route, hashAPIPath(route)))
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
).catch((error) => {
consoleLogger(error);
});
@@ -338,7 +339,7 @@ export const createContainerProxy = (containerName) => {
var route = buildFileQuery("/api/file/setupcontainer", {
ident: containerName
});
var queryUrl = appendQuerySuffix(route, hashAPIPath(route));
var queryUrl = appendHashSuffix(route, hashAPIPath);
return getFileJson(withBaseUrl(BASE_URL, queryUrl)).catch((error) => {
consoleLogger(error);
+20 -31
View File
@@ -1,7 +1,10 @@
import { BASE_URL } from "../core/env";
import { consoleLogger } from "../core/logger";
import { buildHashedQueryUrl } from "../clients/relayClient";
import { getJson, requestJson } from "../clients/endpointClient";
import {
deleteSignedJson,
buildSignedUrl
} from "../clients/signedRequestClient";
import {
buildFileQuery,
withBaseUrl,
@@ -146,24 +149,17 @@ export const deleteMyRepresentations = (myRepresentationsID) => {
myRepresentationsID
});
return buildHashedQueryUrl(queryUrl)
.then((signedUrl) =>
requestJson({
method: "delete",
url: signedUrl,
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json;odata.metadata=none",
"Prefer":
'odata.include-annotations="*",return=representation',
"Content-Type": "application/json"
}
})
)
.catch((error) => {
consoleLogger(error);
});
return deleteSignedJson(queryUrl, {
headers: {
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Accept": "application/json;odata.metadata=none",
"Prefer": 'odata.include-annotations="*",return=representation',
"Content-Type": "application/json"
}
}).catch((error) => {
consoleLogger(error);
});
};
export const deleteAwaitingSubmissions = (incidentID) => {
@@ -189,16 +185,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 (
@@ -220,7 +209,7 @@ export const sendCaseCompleteMessage = async (
inv
});
var signedQueryUrl = await buildHashedQueryUrl(hashQueryPath);
var signedQueryUrl = await buildSignedUrl(hashQueryPath);
queryUrl = appendQuerySuffix(
queryUrl,
@@ -273,7 +262,7 @@ export const sendRepCompleteMessage = async (
}
);
var queryUrl = await buildHashedQueryUrl(hashQueryPath);
var queryUrl = await buildSignedUrl(hashQueryPath);
var config = {
method: "get",
+469
View File
@@ -2725,3 +2725,472 @@ 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).
---
### CL-077: TASK22269 Slice B1.2 — portal signed-delete bundle (headered delete migration)
date: 2026-03-25
author: Cline
scope: `actions/services/portalDirectService.js`
type: change
rationale: Continue signed-request consolidation using bounded grouping by migrating the remaining portal signed delete flow (`deleteMyRepresentations`) onto shared signed helper while preserving required OData headers.
impact: Further reduces duplicated sign+delete boilerplate in portal service and centralizes signed DELETE execution semantics.
status: completed
Summary:
- Migrated `deleteMyRepresentations` from inline `buildHashedQueryUrl(...).then(requestJson(...))` to shared `deleteSignedJson(queryUrl, { headers })`.
- Preserved behavior-critical headers exactly:
- `OData-MaxVersion`
- `OData-Version`
- `Accept`
- `Prefer`
- `Content-Type`
- Preserved existing catch/log behavior (`consoleLogger` with safe undefined return on failure).
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:
- Next bounded signed GET candidate in portal service is `sendRepCompleteMessage` (single signed URL + GET request path).
---
### CL-078: TASK22269 Slice B1.3 — fileClient signed helper delegation bundle
date: 2026-03-25
author: Cline
scope: `actions/clients/fileClient.js`, `tests/phase22/file-client-behaviour.test.cjs`
type: change
rationale: Continue grouped signed-request consolidation by reducing duplicate signing logic in `fileClient` and delegating signed GET/POST operations to shared `signedRequestClient` helpers.
impact: Centralizes signed method execution behavior in one helper layer and lowers drift risk across file-service call paths.
status: completed
Summary:
- Updated `actions/clients/fileClient.js`:
- replaced direct `buildHashedQueryUrl + requestJson` logic in:
- `getSignedFileJson` -> now delegates to `getSignedJson`
- `postSignedFileJson` -> now delegates to `postSignedJson`
- retained `downloadFileBlob` and `getFileJson` behavior unchanged.
- Updated `tests/phase22/file-client-behaviour.test.cjs` to assert delegation contracts for `getSignedJson` and `postSignedJson` rather than direct signing internals.
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:
- 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.
---
### CL-080: TASK22269 Slice B1.5 — document hash-suffix route normalization helper
date: 2026-03-25
author: Cline
scope: `actions/clients/fileRouteBuilder.js`, `actions/services/documentDirectService.js`, `tests/{serviceHarness,phase22/client-utils-behaviour}.cjs`
type: change
rationale: Continue grouped follow-on candidates by normalizing repeated deterministic hash-suffix route assembly in document service behind one shared route-builder helper.
impact: Reduces repeated `appendQuerySuffix(route, hashAPIPath(route))` composition drift risk while preserving route/query/hash behavior.
status: completed
Summary:
- Added `appendHashSuffix(route, hashBuilder)` to `fileRouteBuilder`.
- Migrated document service deterministic hash-suffix paths to new helper:
- `getAwaitingSubmissionFromBlob`
- `getRepsFromBlob`
- `getFilesFromBlob`
- `getProgressFromBlob`
- `createContainerProxy`
- Updated shared VM harness defaults (`tests/serviceHarness.cjs`) to inject `appendHashSuffix`.
- Expanded phase22 utility test to cover new helper behavior (`tests/phase22/client-utils-behaviour.test.cjs`).
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 non-service candidate for this stream is `actions/azurestorage.js` direct `hashAPIPath` metadata assembly (separate bounded slice if desired).
---
### CL-081: TASK22269 Slice B1.6 — azurestorage hash-query metadata builder normalization
date: 2026-03-25
author: Cline
scope: `actions/azurestorage.js`
type: change
rationale: Continue requested follow-on slice by reducing repeated hash-query path string composition in azure storage metadata builders behind local helper functions.
impact: Lowers duplication/drift risk in hashed metadata path generation while preserving existing route and encoding behavior.
status: completed
Summary:
- Added internal helper builders in `actions/azurestorage.js`:
- `buildDownloadBlobQueryPath(...)`
- `buildDeleteBlobQueryPath(...)`
- `buildGetBlobListQueryPath(...)`
- Replaced repeated inline hash path literals with helper usage in targeted metadata object builders:
- `getBlobs`
- `getProgressBlobs`
- `getRepsFilesBlobs`
- Preserved existing behavior semantics for hash path construction:
- encoded `casefolderID`/`blobname` where previously encoded
- unchanged `containerName` and `casefolderID` value sourcing
- unchanged returned object field names and shape
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 potential cleanups in `actions/azurestorage.js` are broader non-slice refactors (legacy logging verbosity, large function decomposition) and should be handled separately to keep risk bounded.
---
### CL-082: TASK22269 Slice B1.7 — azurestorage hash metadata helper consolidation
date: 2026-03-25
author: Cline
scope: `actions/azurestorage.js`
type: change
rationale: Continue bounded normalization by consolidating repeated hash metadata object field population into a single local helper.
impact: Reduces duplicated metadata field assembly and drift risk while preserving existing output shape and hash behavior.
status: completed
Summary:
- Added `buildHashMetadataPaths({ containerName, casefolderID, blobname })` helper.
- Replaced repeated per-object hash metadata assignment in:
- `getBlobs`
- `getProgressBlobs`
- `getRepsFilesBlobs`
- Preserved existing metadata contracts:
- keys unchanged: `hashedfilepath`, `hasheddeletepath`, `hashgetblobs`
- same encoded query path inputs and route semantics.
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:
- Any further `azurestorage.js` cleanup should remain bounded (e.g., logging-only normalization) and separated from behavior-affecting refactors.
---
### CL-083: TASK22269 Slice B1.8 — phase22 azurestorage helper contract coverage
date: 2026-03-26
author: Cline
scope: `tests/phase22/{azurestorage-helper-behaviour,index}.test.cjs`
type: change
rationale: Execute the selected bounded test-only follow-up by adding focused regression coverage for recently added azurestorage helper contracts.
impact: Improves confidence in query-path and hash-metadata helper output stability without changing runtime behavior.
status: completed
Summary:
- Added new phase22 suite: `tests/phase22/azurestorage-helper-behaviour.test.cjs`.
- Test suite isolates helper block from `actions/azurestorage.js` and verifies:
- `buildDownloadBlobQueryPath` default encoding output
- `buildDeleteBlobQueryPath` non-encoded option behavior
- `buildGetBlobListQueryPath` query output contract
- `buildHashMetadataPaths` key/value shape (`hashedfilepath`, `hasheddeletepath`, `hashgetblobs`)
- Wired suite into aggregate runner `tests/phase22/index.test.cjs`.
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 (includes new azurestorage-helper 4/4)
- `node tests/phase7/service-behaviour.test.cjs` -> pass (13/13)
Follow-ups:
- Optional next bounded slice: add an explicit assertion for encoded `casefolderID` variants containing reserved query characters (`?`, `&`) if those inputs are expected in future flows.
---
### CL-084: TASK22269 Slice B1.9 — azurestorage local split-value tidy in touched helper consumers
date: 2026-03-26
author: Cline
scope: `actions/azurestorage.js`
type: change
rationale: Execute the selected next bounded readability-only slice by reducing repeated `blob.name.split("/")` access in the recently touched helper-consumer functions.
impact: Non-behavioral maintainability improvement in azurestorage helper-consumer paths; no API/route contract changes.
status: completed
Summary:
- In targeted functions (`getBlobs`, `getProgressBlobs`, `getRepsFilesBlobs`), introduced local path-part variables to avoid repeated inline splitting:
- `blobPathParts`
- `fileName` / `appealBlobName` / `repFileName`
- `casefolderPath`
- Replaced repeated field reads and helper arguments with these locals in object construction and hash metadata composition.
- Preserved existing query composition and output shape/keys (including hashed path metadata fields).
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:
- Optional next bounded slice: logging-only normalization in these same azurestorage functions (no behavior change), done separately from structural refactors.
---
### CL-085: TASK22269 Slice B1.10 — azurestorage touched-function logging normalization
date: 2026-03-26
author: Cline
scope: `actions/azurestorage.js`
type: change
rationale: Execute the next bounded, logging-only slice by normalizing selected touched-function logs to `consoleLogger` for consistency with current helper/error logging style.
impact: Observability consistency improvement only; no API/route behavior or payload contract changes.
status: completed
Summary:
- In previously touched helper-consumer functions only:
- `getProgressBlobs`
- `getRepsFilesBlobs`
- Replaced selected direct `console.log(...)` calls with `consoleLogger(...)`:
- progress blob path-parts trace
- per-blob name trace in reps file listing
- final `blobObj` trace in reps file listing
- Scope intentionally excludes broader file-wide logging normalization to keep risk bounded.
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:
- Optional next bounded slice: prune currently-unused local `blobDocumentType` variables in the same touched functions (readability-only, no behavior change).
---
### CL-086: TASK22269 Slice B1.11 — azurestorage touched-function unused-local prune
date: 2026-03-26
author: Cline
scope: `actions/azurestorage.js`
type: change
rationale: Execute the next bounded readability-only slice by removing now-unused local variables left in recently touched helper-consumer functions.
impact: Maintainability/readability improvement only; no API/route behavior changes.
status: completed
Summary:
- Removed unused local `blobDocumentType` declarations from:
- `getBlobs`
- `getRepsFilesBlobs`
- No object shape, query generation, hash metadata logic, or routing behavior changed.
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:
- Optional next bounded slice: align remaining low-risk direct `console.log` calls in these functions to `consoleLogger` only where already touched and safe.
---
### CL-087: TASK22269 Slice B1.12 — azurestorage touched-function path assembly helper reuse
date: 2026-03-26
author: Cline
scope: `actions/azurestorage.js`
type: change
rationale: Execute the next bounded maintainability slice by reusing existing local query-path helpers for touched `filepath`/`deletepath` assembly, reducing repeated literal concatenation.
impact: Readability/consistency improvement only; preserves query parameter values and route behavior.
status: completed
Summary:
- In touched functions:
- `getBlobs`
- `getRepsFilesBlobs`
- Replaced inline `filepath` string concatenation with `buildDownloadBlobQueryPath(...)`.
- Replaced inline `deletepath` string concatenation with `buildDeleteBlobQueryPath(...)`.
- Preserved previous encoding behavior where required by passing explicit options:
- kept non-encoded `casefolderID`/`blobname` behavior in `getRepsFilesBlobs.deletepath` via helper options.
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:
- Optional next bounded slice: targeted helper-consumer tidy in the same functions for any remaining repeated query-path literals outside touched object fields.
---
### CL-088: TASK22269 Slice B1.13 — azurestorage touched-function caseObj helper reuse
date: 2026-03-26
author: Cline
scope: `actions/azurestorage.js`
type: change
rationale: Execute the next bounded readability slice by centralizing repeated case-object path composition in touched helper-consumer functions.
impact: Maintainability/readability improvement only; no route/query behavior changes.
status: completed
Summary:
- Added local helper `buildCaseObjectPath(casefolderID)`.
- Replaced repeated `caseObj` string assembly in touched functions:
- `getBlobs`
- `getProgressBlobs`
- Preserved existing `caseObj` output format (`<caseRef>/<caseRef>_case.json`).
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:
- Optional next bounded slice: continue tiny helper reuse in touched functions only if any duplicated path literals remain and can be reduced without behavior change.
---
### CL-089: TASK22269 Slice B1.14 — azurestorage touched-function contentLength local reuse
date: 2026-03-26
author: Cline
scope: `actions/azurestorage.js`
type: change
rationale: Execute the next tiny bounded readability slice by reusing local `contentLength` values in touched helper-consumer functions to reduce repeated property access and keep object assembly consistent.
impact: Maintainability/readability improvement only; no route/query/output behavior changes.
status: completed
Summary:
- In touched functions:
- `getBlobs`
- `getProgressBlobs`
- `getRepsFilesBlobs`
- Added local `contentLength` variable (`blob.properties.contentLength`) per loop iteration.
- Replaced repeated inline `blob.properties.contentLength` assignments in object assembly with the local variable.
- Preserved field contracts (`contentLength`, `size`) and values.
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:
- Optional next bounded slice: stop or switch scope; touched-function micro-tidies in this area are now largely exhausted.
@@ -0,0 +1,125 @@
const fs = require("fs");
const path = require("path");
const vm = require("vm");
const assert = require("assert");
const rootDir = path.resolve(__dirname, "..", "..");
const loadAzureStorageHelperModule = (injected = {}) => {
const filePath = path.join(rootDir, "actions", "azurestorage.js");
const source = fs.readFileSync(filePath, "utf8");
const start = source.indexOf("const buildDownloadBlobQueryPath =");
const end = source.indexOf("export const createContainerSas =");
if (start < 0 || end < 0 || end <= start) {
throw new Error("Unable to isolate azurestorage helper function block");
}
let helperSource = source.slice(start, end);
helperSource +=
"\nmodule.exports = { buildDownloadBlobQueryPath, buildDeleteBlobQueryPath, buildGetBlobListQueryPath, buildHashMetadataPaths };\n";
const context = {
module: { exports: {} },
exports: {},
require,
encodeURIComponent,
hashAPIPath: (route) => `HASH(${route})`,
...injected
};
vm.runInNewContext(helperSource, context, { filename: filePath });
return context.module.exports;
};
const tests = [];
const test = (name, fn) => tests.push({ name, fn });
test("azurestorage helper builds download path with default encoding", () => {
const mod = loadAzureStorageHelperModule();
const pathResult = mod.buildDownloadBlobQueryPath({
containerName: "alpha",
casefolderID: "A/B",
blobname: "my doc.pdf"
});
assert.strictEqual(
pathResult,
"/api/file/downloadblob?container=alpha&casefolderID=A%2FB&blobname=my%20doc.pdf"
);
});
test("azurestorage helper preserves raw values when encoding disabled", () => {
const mod = loadAzureStorageHelperModule();
const deletePath = mod.buildDeleteBlobQueryPath({
containerName: "alpha",
casefolderID: "A/B",
blobname: "my doc.pdf",
encodeCasefolderID: false,
encodeBlobname: false
});
assert.strictEqual(
deletePath,
"/api/file/deleteblob?container=alpha&casefolderID=A/B&blobname=my doc.pdf"
);
});
test("azurestorage helper builds getbloblist path without field mutation", () => {
const mod = loadAzureStorageHelperModule();
const listPath = mod.buildGetBlobListQueryPath({
containerName: "alpha",
casefolderID: "A/B"
});
assert.strictEqual(
listPath,
"/api/file/getbloblist?container=alpha&casefolderID=A/B"
);
});
test("azurestorage helper builds hash metadata map with stable keys", () => {
const mod = loadAzureStorageHelperModule({
hashAPIPath: (route) => `signed:${route}`
});
const metadata = mod.buildHashMetadataPaths({
containerName: "alpha",
casefolderID: "A/B",
blobname: "my doc.pdf"
});
assert.deepStrictEqual(JSON.parse(JSON.stringify(metadata)), {
hashedfilepath:
"signed:/api/file/downloadblob?container=alpha&casefolderID=A%2FB&blobname=my%20doc.pdf",
hasheddeletepath:
"signed:/api/file/deleteblob?container=alpha&casefolderID=A%2FB&blobname=my%20doc.pdf",
hashgetblobs:
"signed:/api/file/getbloblist?container=alpha&casefolderID=A/B"
});
});
const run = async () => {
let passed = 0;
for (const currentTest of tests) {
await currentTest.fn();
passed += 1;
}
console.log(
`Phase 22 azurestorage-helper tests passed (${passed}/${tests.length}).`
);
};
module.exports = run;
if (require.main === module) {
run().catch((error) => {
console.error(error);
process.exit(1);
});
}
@@ -70,7 +70,7 @@ const loadFileRouteBuilderModule = (injected = {}) => {
source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, "");
source = source.replace(/export const\s+/g, "const ");
source +=
"\nmodule.exports = { buildFileQuery, withBaseUrl, appendQuerySuffix };\n";
"\nmodule.exports = { buildFileQuery, withBaseUrl, appendQuerySuffix, appendHashSuffix };\n";
const context = {
module: { exports: {} },
@@ -211,6 +211,12 @@ test("clients/fileRouteBuilder builds query with optional encoding and suffix he
mod.appendQuerySuffix(unencoded, "&hash=123"),
"/api/file/getbloblist?container=abc&casefolderID=x/y&hash=123"
);
assert.strictEqual(
mod.appendHashSuffix(unencoded, (route) =>
route.includes("getbloblist") ? "&hash=abc" : ""
),
"/api/file/getbloblist?container=abc&casefolderID=x/y&hash=abc"
);
});
const run = async () => {
+15 -26
View File
@@ -24,8 +24,11 @@ const loadFileClientModule = (injected = {}) => {
requestJson: async () => {
throw new Error("requestJson not injected");
},
buildHashedQueryUrl: async () => {
throw new Error("buildHashedQueryUrl not injected");
getSignedJson: async () => {
throw new Error("getSignedJson not injected");
},
postSignedJson: async () => {
throw new Error("postSignedJson not injected");
},
...injected
};
@@ -56,15 +59,10 @@ test("clients/fileClient getFileJson delegates to getJson", async () => {
test("clients/fileClient getSignedFileJson signs url and requests json", async () => {
const signedCalls = [];
const requestCalls = [];
const mod = loadFileClientModule({
buildHashedQueryUrl: async (queryUrl) => {
getSignedJson: async (queryUrl) => {
signedCalls.push(queryUrl);
return queryUrl + "&hash=signed";
},
requestJson: async (config) => {
requestCalls.push(config);
return { deleted: true };
}
});
@@ -77,11 +75,9 @@ test("clients/fileClient getSignedFileJson signs url and requests json", async (
deleted: true
});
assert.strictEqual(signedCalls.length, 1);
assert.strictEqual(requestCalls.length, 1);
assert.strictEqual(requestCalls[0].method, "get");
assert.strictEqual(
requestCalls[0].url,
"/api/file/deleteblobcase?container=a&casefolderID=b&hash=signed"
signedCalls[0],
"/api/file/deleteblobcase?container=a&casefolderID=b"
);
});
@@ -106,15 +102,12 @@ test("clients/fileClient downloadFileBlob requests blob response", async () => {
test("clients/fileClient postSignedFileJson signs url and posts payload", async () => {
const signedCalls = [];
const requestCalls = [];
const postedCalls = [];
const mod = loadFileClientModule({
buildHashedQueryUrl: async (queryUrl) => {
postSignedJson: async (queryUrl, data, config) => {
signedCalls.push(queryUrl);
return queryUrl + "&hash=signed-post";
},
requestJson: async (config) => {
requestCalls.push(config);
postedCalls.push({ data, config });
return { uploaded: true };
}
});
@@ -132,18 +125,14 @@ test("clients/fileClient postSignedFileJson signs url and posts payload", async
uploaded: true
});
assert.strictEqual(signedCalls.length, 1);
assert.strictEqual(requestCalls.length, 1);
assert.strictEqual(requestCalls[0].method, "post");
assert.strictEqual(
requestCalls[0].url,
"/api/file/uploadsinglefile&hash=signed-post"
);
assert.strictEqual(postedCalls.length, 1);
assert.strictEqual(signedCalls[0], "/api/file/uploadsinglefile");
assert.deepStrictEqual(
JSON.parse(JSON.stringify(requestCalls[0].data)),
JSON.parse(JSON.stringify(postedCalls[0].data)),
payload
);
assert.strictEqual(
requestCalls[0].headers["content-type"],
postedCalls[0].config.headers["content-type"],
"multipart/form-data"
);
});
+2
View File
@@ -5,6 +5,7 @@ const runCaseServiceTests = require("./case-service-behaviour.test.cjs");
const runPortalServiceTests = require("./portal-service-behaviour.test.cjs");
const runAuthRedirectSafetyTests = require("./auth-redirect-safety.test.cjs");
const runI18nRouteParityTests = require("./i18n-route-parity.test.cjs");
const runAzurestorageHelperTests = require("./azurestorage-helper-behaviour.test.cjs");
const run = async () => {
await runCoreTokenTests();
@@ -14,6 +15,7 @@ const run = async () => {
await runPortalServiceTests();
await runAuthRedirectSafetyTests();
await runI18nRouteParityTests();
await runAzurestorageHelperTests();
console.log("Phase 22 combined suite passed.");
};
@@ -134,6 +134,68 @@ test("portal/sendRepCompleteMessage rejects when hash signing fails before reque
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 () => {
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;
+10 -3
View File
@@ -256,9 +256,13 @@ test("account/getPortalLogin appends hash and returns res.data", async () => {
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", {
axios,
BASE_URL: "http://example.local",
@@ -274,8 +278,9 @@ test("account/getPortalLogin appends hash and returns res.data", async () => {
signCalls[0],
"/api/endpoint/gethash_api?path=%2Fapi%2Fendpoint%2Fgetportallogin_api%3FemailAddress%3Dperson%40example.com"
);
assert.strictEqual(axios.calls[1].config.method, "get");
assert.strictEqual(
axios.calls[1].url,
axios.calls[1].config.url,
"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 Promise.reject(error);
throw new Error("Unexpected get url: " + url);
};
axios.requestHandler = async () => Promise.reject(error);
const account = loadServiceModule("accountDirectService.js", {
axios,
BASE_URL: "",
+55
View File
@@ -107,6 +107,15 @@ const loadServiceModule = (fileName, injected = {}) => {
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 hashedUrl = await (
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl
@@ -126,6 +135,45 @@ 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 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 { encode = false } = options;
const entries = Object.entries(params).filter(([, value]) => {
@@ -152,6 +200,8 @@ const loadServiceModule = (fileName, injected = {}) => {
const defaultWithBaseUrl = (baseUrl, route) => `${baseUrl}${route}`;
const defaultAppendQuerySuffix = (route, suffix = "") =>
`${route}${suffix}`;
const defaultAppendHashSuffix = (route, hashBuilder) =>
defaultAppendQuerySuffix(route, hashBuilder(route));
const context = {
module: { exports: {} },
@@ -172,12 +222,17 @@ const loadServiceModule = (fileName, injected = {}) => {
getSignedFileJson:
injected.getSignedFileJson || defaultGetSignedFileJson,
downloadFileBlob: injected.downloadFileBlob || defaultDownloadFileBlob,
getSignedJson: injected.getSignedJson || defaultGetSignedJson,
postSignedJson: injected.postSignedJson || defaultPostSignedJson,
deleteSignedJson: injected.deleteSignedJson || defaultDeleteSignedJson,
buildSignedUrl: injected.buildSignedUrl || defaultBuildSignedUrl,
buildHashedQueryUrl:
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl,
buildFileQuery: injected.buildFileQuery || defaultBuildFileQuery,
withBaseUrl: injected.withBaseUrl || defaultWithBaseUrl,
appendQuerySuffix:
injected.appendQuerySuffix || defaultAppendQuerySuffix,
appendHashSuffix: injected.appendHashSuffix || defaultAppendHashSuffix,
...injected
};