Merged PR 2208: update and clean azurestorage.js
Related work items: #22326
This commit is contained in:
+144
-128
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from "./relayClient";
|
||||
export * from "./endpointClient";
|
||||
export * from "./fileClient";
|
||||
export * from "./fileRouteBuilder";
|
||||
export * from "./signedRequestClient";
|
||||
|
||||
@@ -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
|
||||
});
|
||||
};
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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: "",
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user