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 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) => {
|
export const createContainerSas = async (containerName) => {
|
||||||
// Get environment variables
|
// Get environment variables
|
||||||
|
|
||||||
@@ -189,59 +271,36 @@ export const getBlobs = async (containerName, casefolderID) => {
|
|||||||
for await (const blob of containerClient.listBlobsFlat({
|
for await (const blob of containerClient.listBlobsFlat({
|
||||||
prefix: casefolderID + "/files/"
|
prefix: casefolderID + "/files/"
|
||||||
})) {
|
})) {
|
||||||
let blobDocumentType = blob.name
|
const blobPathParts = blob.name.split("/");
|
||||||
.split("/")[2]
|
const fileName = blobPathParts[2];
|
||||||
.slice(0, blob.name.split("/")[2].indexOf("_"));
|
const contentLength = blob.properties.contentLength;
|
||||||
|
|
||||||
blobObj.push({
|
blobObj.push({
|
||||||
"name": blob.name.split("/")[2],
|
"name": fileName,
|
||||||
"path": blob.name,
|
"path": blob.name,
|
||||||
"documentType": getDocumentTypeFromFilename(
|
"documentType": getDocumentTypeFromFilename(fileName),
|
||||||
blob.name.split("/")[2]
|
|
||||||
),
|
|
||||||
"versionId": blob.versionId,
|
"versionId": blob.versionId,
|
||||||
"caseObj": casefolderID + "/" + casefolderID + "_case.json",
|
"caseObj": buildCaseObjectPath(casefolderID),
|
||||||
"isCurrentVersion": blob.isCurrentVersion,
|
"isCurrentVersion": blob.isCurrentVersion,
|
||||||
"contentLength": blob.properties.contentLength,
|
"contentLength": contentLength,
|
||||||
"size": blob.properties.contentLength,
|
"size": contentLength,
|
||||||
"contentType": blob.contentType,
|
"contentType": blob.contentType,
|
||||||
"lastModified": blob.properties.lastModified,
|
"lastModified": blob.properties.lastModified,
|
||||||
"filepath":
|
"filepath": buildDownloadBlobQueryPath({
|
||||||
"/api/file/downloadblob?container=" +
|
containerName,
|
||||||
containerName +
|
casefolderID,
|
||||||
"&casefolderID=" +
|
blobname: blob.name
|
||||||
encodeURIComponent(casefolderID) +
|
}),
|
||||||
"&blobname=" +
|
...buildHashMetadataPaths({
|
||||||
encodeURIComponent(blob.name),
|
containerName,
|
||||||
"hashedfilepath": hashAPIPath(
|
casefolderID,
|
||||||
"/api/file/downloadblob?container=" +
|
blobname: fileName
|
||||||
containerName +
|
}),
|
||||||
"&casefolderID=" +
|
"deletepath": buildDeleteBlobQueryPath({
|
||||||
encodeURIComponent(casefolderID) +
|
containerName,
|
||||||
"&blobname=" +
|
casefolderID,
|
||||||
encodeURIComponent(blob.name.split("/")[2])
|
blobname: fileName
|
||||||
),
|
})
|
||||||
"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
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//console.log("blobObj:", blobObj);
|
//console.log("blobObj:", blobObj);
|
||||||
@@ -1235,38 +1294,25 @@ export const getProgressBlobs = async (containerName, caseReference) => {
|
|||||||
for await (const blob of containerClient.listBlobsFlat({
|
for await (const blob of containerClient.listBlobsFlat({
|
||||||
prefix: caseReference + "/" + caseReference + "_appeal.json"
|
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({
|
blobObj.push({
|
||||||
"name": blob.name.split("/")[1],
|
"name": appealBlobName,
|
||||||
"path": blob.name,
|
"path": blob.name,
|
||||||
"versionId": blob.versionId,
|
"versionId": blob.versionId,
|
||||||
"caseObj": caseReference + "/" + caseReference + "_case.json",
|
"caseObj": buildCaseObjectPath(caseReference),
|
||||||
"isCurrentVersion": blob.isCurrentVersion,
|
"isCurrentVersion": blob.isCurrentVersion,
|
||||||
"contentLength": blob.properties.contentLength,
|
"contentLength": contentLength,
|
||||||
"contentType": blob.contentType,
|
"contentType": blob.contentType,
|
||||||
"lastModified": blob.properties.lastModified,
|
"lastModified": blob.properties.lastModified,
|
||||||
"hashedfilepath": hashAPIPath(
|
...buildHashMetadataPaths({
|
||||||
"/api/file/downloadblob?container=" +
|
containerName,
|
||||||
containerName +
|
casefolderID: caseReference,
|
||||||
"&casefolderID=" +
|
blobname: appealBlobName
|
||||||
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
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1429,71 +1475,41 @@ export const getRepsFilesBlobs = async (
|
|||||||
for await (const blob of containerClient.listBlobsFlat({
|
for await (const blob of containerClient.listBlobsFlat({
|
||||||
prefix: casefolderID + "/" + filenamePrefix + "/files/"
|
prefix: casefolderID + "/" + filenamePrefix + "/files/"
|
||||||
})) {
|
})) {
|
||||||
let blobDocumentType = blob.name
|
const blobPathParts = blob.name.split("/");
|
||||||
.split("/")[2]
|
const casefolderPath = blobPathParts[0] + "/" + blobPathParts[1];
|
||||||
.slice(0, blob.name.split("/")[2].indexOf("_"));
|
const repFileName = blobPathParts[3];
|
||||||
|
const contentLength = blob.properties.contentLength;
|
||||||
|
|
||||||
console.log(blob.name);
|
consoleLogger(blob.name);
|
||||||
|
|
||||||
blobObj.push({
|
blobObj.push({
|
||||||
"name": blob.name.split("/")[3],
|
"name": repFileName,
|
||||||
"path": blob.name,
|
"path": blob.name,
|
||||||
"documentType": getDocumentTypeFromFilename(
|
"documentType": getDocumentTypeFromFilename(repFileName),
|
||||||
blob.name.split("/")[3]
|
|
||||||
),
|
|
||||||
"versionId": blob.versionId,
|
"versionId": blob.versionId,
|
||||||
"isCurrentVersion": blob.isCurrentVersion,
|
"isCurrentVersion": blob.isCurrentVersion,
|
||||||
"contentLength": blob.properties.contentLength,
|
"contentLength": contentLength,
|
||||||
"filenameprefix": filenamePrefix,
|
"filenameprefix": filenamePrefix,
|
||||||
"filepath":
|
"filepath": buildDownloadBlobQueryPath({
|
||||||
"/api/file/downloadblob?container=" +
|
containerName,
|
||||||
containerName +
|
casefolderID: casefolderPath,
|
||||||
"&casefolderID=" +
|
blobname: repFileName
|
||||||
encodeURIComponent(
|
}),
|
||||||
blob.name.split("/")[0] + "/" + blob.name.split("/")[1]
|
...buildHashMetadataPaths({
|
||||||
) +
|
containerName,
|
||||||
"&blobname=" +
|
casefolderID: casefolderPath,
|
||||||
encodeURIComponent(blob.name.split("/")[3]),
|
blobname: repFileName
|
||||||
"hashedfilepath": hashAPIPath(
|
}),
|
||||||
"/api/file/downloadblob?container=" +
|
"deletepath": buildDeleteBlobQueryPath({
|
||||||
containerName +
|
containerName,
|
||||||
"&casefolderID=" +
|
casefolderID: casefolderPath,
|
||||||
encodeURIComponent(
|
blobname: repFileName,
|
||||||
blob.name.split("/")[0] + "/" + blob.name.split("/")[1]
|
encodeCasefolderID: false,
|
||||||
) +
|
encodeBlobname: false
|
||||||
"&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]
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log("blobObj:", blobObj);
|
consoleLogger("blobObj:", blobObj);
|
||||||
|
|
||||||
return blobObj;
|
return blobObj;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
import { getJson, requestJson } from "./endpointClient";
|
import { getJson, requestJson } from "./endpointClient";
|
||||||
import { buildHashedQueryUrl } from "./relayClient";
|
import { getSignedJson, postSignedJson } from "./signedRequestClient";
|
||||||
|
|
||||||
export const getFileJson = (url) => {
|
export const getFileJson = (url) => {
|
||||||
return getJson(url);
|
return getJson(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSignedFileJson = async (queryUrl) => {
|
export const getSignedFileJson = async (queryUrl) => {
|
||||||
const signedUrl = await buildHashedQueryUrl(queryUrl);
|
return getSignedJson(queryUrl);
|
||||||
|
|
||||||
return requestJson({
|
|
||||||
method: "get",
|
|
||||||
url: signedUrl
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const downloadFileBlob = (url) => {
|
export const downloadFileBlob = (url) => {
|
||||||
@@ -23,12 +18,5 @@ export const downloadFileBlob = (url) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const postSignedFileJson = async (queryUrl, data, config = {}) => {
|
export const postSignedFileJson = async (queryUrl, data, config = {}) => {
|
||||||
const signedUrl = await buildHashedQueryUrl(queryUrl);
|
return postSignedJson(queryUrl, data, config);
|
||||||
|
|
||||||
return requestJson({
|
|
||||||
method: "post",
|
|
||||||
url: signedUrl,
|
|
||||||
data,
|
|
||||||
...config
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,3 +31,7 @@ export const withBaseUrl = (baseUrl, route) => {
|
|||||||
export const appendQuerySuffix = (route, suffix = "") => {
|
export const appendQuerySuffix = (route, suffix = "") => {
|
||||||
return `${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 "./endpointClient";
|
||||||
export * from "./fileClient";
|
export * from "./fileClient";
|
||||||
export * from "./fileRouteBuilder";
|
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 { BASE_URL } from "../core/env";
|
||||||
import { consoleLogger } from "../core/logger";
|
import { consoleLogger } from "../core/logger";
|
||||||
import { buildHashedQueryUrl } from "../clients/relayClient";
|
|
||||||
import { getJson, requestJson } from "../clients/endpointClient";
|
import { getJson, requestJson } from "../clients/endpointClient";
|
||||||
|
import { getSignedJson } from "../clients/signedRequestClient";
|
||||||
|
|
||||||
export const getPersonalAccount = (contactid) => {
|
export const getPersonalAccount = (contactid) => {
|
||||||
return getJson(
|
return getJson(
|
||||||
@@ -78,13 +78,11 @@ export const getPortalLogin = async (emailAddress) => {
|
|||||||
var queryUrl =
|
var queryUrl =
|
||||||
"/api/endpoint/getportallogin_api?emailAddress=" + emailAddress;
|
"/api/endpoint/getportallogin_api?emailAddress=" + emailAddress;
|
||||||
|
|
||||||
return getJson(BASE_URL + (await buildHashedQueryUrl(queryUrl))).catch(
|
return getSignedJson(queryUrl, { baseUrl: BASE_URL }).catch((error) => {
|
||||||
(error) => {
|
consoleLogger(error);
|
||||||
consoleLogger(error);
|
|
||||||
|
|
||||||
return JSON.stringify(error);
|
return JSON.stringify(error);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPortalLoginProxy = async (emailAddress) => {
|
export const getPortalLoginProxy = async (emailAddress) => {
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import { hashAPIPath } from "../core/hash";
|
|||||||
import {
|
import {
|
||||||
buildFileQuery,
|
buildFileQuery,
|
||||||
withBaseUrl,
|
withBaseUrl,
|
||||||
appendQuerySuffix
|
appendQuerySuffix,
|
||||||
|
appendHashSuffix
|
||||||
} from "../clients/fileRouteBuilder";
|
} from "../clients/fileRouteBuilder";
|
||||||
import {
|
import {
|
||||||
getFileJson,
|
getFileJson,
|
||||||
@@ -19,7 +20,7 @@ export const getAwaitingSubmissionFromBlob = (containerName) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return getFileJson(
|
return getFileJson(
|
||||||
withBaseUrl(BASE_URL, appendQuerySuffix(route, hashAPIPath(route)))
|
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
consoleLogger(error);
|
consoleLogger(error);
|
||||||
});
|
});
|
||||||
@@ -31,7 +32,7 @@ export const getRepsFromBlob = (containerName) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return getFileJson(
|
return getFileJson(
|
||||||
withBaseUrl(BASE_URL, appendQuerySuffix(route, hashAPIPath(route)))
|
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
consoleLogger(error);
|
consoleLogger(error);
|
||||||
});
|
});
|
||||||
@@ -227,7 +228,7 @@ export const getFilesFromBlob = (containerName, casefolderID) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return getFileJson(
|
return getFileJson(
|
||||||
withBaseUrl(BASE_URL, appendQuerySuffix(route, hashAPIPath(route)))
|
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
consoleLogger(error);
|
consoleLogger(error);
|
||||||
});
|
});
|
||||||
@@ -328,7 +329,7 @@ export const getProgressFromBlob = async (containerName, casereference) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return getFileJson(
|
return getFileJson(
|
||||||
withBaseUrl(BASE_URL, appendQuerySuffix(route, hashAPIPath(route)))
|
withBaseUrl(BASE_URL, appendHashSuffix(route, hashAPIPath))
|
||||||
).catch((error) => {
|
).catch((error) => {
|
||||||
consoleLogger(error);
|
consoleLogger(error);
|
||||||
});
|
});
|
||||||
@@ -338,7 +339,7 @@ export const createContainerProxy = (containerName) => {
|
|||||||
var route = buildFileQuery("/api/file/setupcontainer", {
|
var route = buildFileQuery("/api/file/setupcontainer", {
|
||||||
ident: containerName
|
ident: containerName
|
||||||
});
|
});
|
||||||
var queryUrl = appendQuerySuffix(route, hashAPIPath(route));
|
var queryUrl = appendHashSuffix(route, hashAPIPath);
|
||||||
|
|
||||||
return getFileJson(withBaseUrl(BASE_URL, queryUrl)).catch((error) => {
|
return getFileJson(withBaseUrl(BASE_URL, queryUrl)).catch((error) => {
|
||||||
consoleLogger(error);
|
consoleLogger(error);
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { BASE_URL } from "../core/env";
|
import { BASE_URL } from "../core/env";
|
||||||
import { consoleLogger } from "../core/logger";
|
import { consoleLogger } from "../core/logger";
|
||||||
import { buildHashedQueryUrl } from "../clients/relayClient";
|
|
||||||
import { getJson, requestJson } from "../clients/endpointClient";
|
import { getJson, requestJson } from "../clients/endpointClient";
|
||||||
|
import {
|
||||||
|
deleteSignedJson,
|
||||||
|
buildSignedUrl
|
||||||
|
} from "../clients/signedRequestClient";
|
||||||
import {
|
import {
|
||||||
buildFileQuery,
|
buildFileQuery,
|
||||||
withBaseUrl,
|
withBaseUrl,
|
||||||
@@ -146,24 +149,17 @@ export const deleteMyRepresentations = (myRepresentationsID) => {
|
|||||||
myRepresentationsID
|
myRepresentationsID
|
||||||
});
|
});
|
||||||
|
|
||||||
return buildHashedQueryUrl(queryUrl)
|
return deleteSignedJson(queryUrl, {
|
||||||
.then((signedUrl) =>
|
headers: {
|
||||||
requestJson({
|
"OData-MaxVersion": "4.0",
|
||||||
method: "delete",
|
"OData-Version": "4.0",
|
||||||
url: signedUrl,
|
"Accept": "application/json;odata.metadata=none",
|
||||||
headers: {
|
"Prefer": 'odata.include-annotations="*",return=representation',
|
||||||
"OData-MaxVersion": "4.0",
|
"Content-Type": "application/json"
|
||||||
"OData-Version": "4.0",
|
}
|
||||||
"Accept": "application/json;odata.metadata=none",
|
}).catch((error) => {
|
||||||
"Prefer":
|
consoleLogger(error);
|
||||||
'odata.include-annotations="*",return=representation',
|
});
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
consoleLogger(error);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteAwaitingSubmissions = (incidentID) => {
|
export const deleteAwaitingSubmissions = (incidentID) => {
|
||||||
@@ -189,16 +185,9 @@ export const deleteWatchedCases = async (watchedCaseID) => {
|
|||||||
watchedCaseID
|
watchedCaseID
|
||||||
});
|
});
|
||||||
|
|
||||||
return buildHashedQueryUrl(queryUrl)
|
return deleteSignedJson(queryUrl).catch((error) => {
|
||||||
.then((signedUrl) =>
|
consoleLogger(error);
|
||||||
requestJson({
|
});
|
||||||
method: "delete",
|
|
||||||
url: signedUrl
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.catch((error) => {
|
|
||||||
consoleLogger(error);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendCaseCompleteMessage = async (
|
export const sendCaseCompleteMessage = async (
|
||||||
@@ -220,7 +209,7 @@ export const sendCaseCompleteMessage = async (
|
|||||||
inv
|
inv
|
||||||
});
|
});
|
||||||
|
|
||||||
var signedQueryUrl = await buildHashedQueryUrl(hashQueryPath);
|
var signedQueryUrl = await buildSignedUrl(hashQueryPath);
|
||||||
|
|
||||||
queryUrl = appendQuerySuffix(
|
queryUrl = appendQuerySuffix(
|
||||||
queryUrl,
|
queryUrl,
|
||||||
@@ -273,7 +262,7 @@ export const sendRepCompleteMessage = async (
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
var queryUrl = await buildHashedQueryUrl(hashQueryPath);
|
var queryUrl = await buildSignedUrl(hashQueryPath);
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
method: "get",
|
method: "get",
|
||||||
|
|||||||
@@ -2725,3 +2725,472 @@ Validation:
|
|||||||
Follow-ups:
|
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).
|
- 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(/import[\s\S]*?from\s+"[^"]+";\n?/g, "");
|
||||||
source = source.replace(/export const\s+/g, "const ");
|
source = source.replace(/export const\s+/g, "const ");
|
||||||
source +=
|
source +=
|
||||||
"\nmodule.exports = { buildFileQuery, withBaseUrl, appendQuerySuffix };\n";
|
"\nmodule.exports = { buildFileQuery, withBaseUrl, appendQuerySuffix, appendHashSuffix };\n";
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
module: { exports: {} },
|
module: { exports: {} },
|
||||||
@@ -211,6 +211,12 @@ test("clients/fileRouteBuilder builds query with optional encoding and suffix he
|
|||||||
mod.appendQuerySuffix(unencoded, "&hash=123"),
|
mod.appendQuerySuffix(unencoded, "&hash=123"),
|
||||||
"/api/file/getbloblist?container=abc&casefolderID=x/y&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 () => {
|
const run = async () => {
|
||||||
|
|||||||
@@ -24,8 +24,11 @@ const loadFileClientModule = (injected = {}) => {
|
|||||||
requestJson: async () => {
|
requestJson: async () => {
|
||||||
throw new Error("requestJson not injected");
|
throw new Error("requestJson not injected");
|
||||||
},
|
},
|
||||||
buildHashedQueryUrl: async () => {
|
getSignedJson: async () => {
|
||||||
throw new Error("buildHashedQueryUrl not injected");
|
throw new Error("getSignedJson not injected");
|
||||||
|
},
|
||||||
|
postSignedJson: async () => {
|
||||||
|
throw new Error("postSignedJson not injected");
|
||||||
},
|
},
|
||||||
...injected
|
...injected
|
||||||
};
|
};
|
||||||
@@ -56,15 +59,10 @@ test("clients/fileClient getFileJson delegates to getJson", async () => {
|
|||||||
|
|
||||||
test("clients/fileClient getSignedFileJson signs url and requests json", async () => {
|
test("clients/fileClient getSignedFileJson signs url and requests json", async () => {
|
||||||
const signedCalls = [];
|
const signedCalls = [];
|
||||||
const requestCalls = [];
|
|
||||||
|
|
||||||
const mod = loadFileClientModule({
|
const mod = loadFileClientModule({
|
||||||
buildHashedQueryUrl: async (queryUrl) => {
|
getSignedJson: async (queryUrl) => {
|
||||||
signedCalls.push(queryUrl);
|
signedCalls.push(queryUrl);
|
||||||
return queryUrl + "&hash=signed";
|
|
||||||
},
|
|
||||||
requestJson: async (config) => {
|
|
||||||
requestCalls.push(config);
|
|
||||||
return { deleted: true };
|
return { deleted: true };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -77,11 +75,9 @@ test("clients/fileClient getSignedFileJson signs url and requests json", async (
|
|||||||
deleted: true
|
deleted: true
|
||||||
});
|
});
|
||||||
assert.strictEqual(signedCalls.length, 1);
|
assert.strictEqual(signedCalls.length, 1);
|
||||||
assert.strictEqual(requestCalls.length, 1);
|
|
||||||
assert.strictEqual(requestCalls[0].method, "get");
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
requestCalls[0].url,
|
signedCalls[0],
|
||||||
"/api/file/deleteblobcase?container=a&casefolderID=b&hash=signed"
|
"/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 () => {
|
test("clients/fileClient postSignedFileJson signs url and posts payload", async () => {
|
||||||
const signedCalls = [];
|
const signedCalls = [];
|
||||||
const requestCalls = [];
|
const postedCalls = [];
|
||||||
|
|
||||||
const mod = loadFileClientModule({
|
const mod = loadFileClientModule({
|
||||||
buildHashedQueryUrl: async (queryUrl) => {
|
postSignedJson: async (queryUrl, data, config) => {
|
||||||
signedCalls.push(queryUrl);
|
signedCalls.push(queryUrl);
|
||||||
return queryUrl + "&hash=signed-post";
|
postedCalls.push({ data, config });
|
||||||
},
|
|
||||||
requestJson: async (config) => {
|
|
||||||
requestCalls.push(config);
|
|
||||||
return { uploaded: true };
|
return { uploaded: true };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -132,18 +125,14 @@ test("clients/fileClient postSignedFileJson signs url and posts payload", async
|
|||||||
uploaded: true
|
uploaded: true
|
||||||
});
|
});
|
||||||
assert.strictEqual(signedCalls.length, 1);
|
assert.strictEqual(signedCalls.length, 1);
|
||||||
assert.strictEqual(requestCalls.length, 1);
|
assert.strictEqual(postedCalls.length, 1);
|
||||||
assert.strictEqual(requestCalls[0].method, "post");
|
assert.strictEqual(signedCalls[0], "/api/file/uploadsinglefile");
|
||||||
assert.strictEqual(
|
|
||||||
requestCalls[0].url,
|
|
||||||
"/api/file/uploadsinglefile&hash=signed-post"
|
|
||||||
);
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
JSON.parse(JSON.stringify(requestCalls[0].data)),
|
JSON.parse(JSON.stringify(postedCalls[0].data)),
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
requestCalls[0].headers["content-type"],
|
postedCalls[0].config.headers["content-type"],
|
||||||
"multipart/form-data"
|
"multipart/form-data"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const runCaseServiceTests = require("./case-service-behaviour.test.cjs");
|
|||||||
const runPortalServiceTests = require("./portal-service-behaviour.test.cjs");
|
const runPortalServiceTests = require("./portal-service-behaviour.test.cjs");
|
||||||
const runAuthRedirectSafetyTests = require("./auth-redirect-safety.test.cjs");
|
const runAuthRedirectSafetyTests = require("./auth-redirect-safety.test.cjs");
|
||||||
const runI18nRouteParityTests = require("./i18n-route-parity.test.cjs");
|
const runI18nRouteParityTests = require("./i18n-route-parity.test.cjs");
|
||||||
|
const runAzurestorageHelperTests = require("./azurestorage-helper-behaviour.test.cjs");
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
await runCoreTokenTests();
|
await runCoreTokenTests();
|
||||||
@@ -14,6 +15,7 @@ const run = async () => {
|
|||||||
await runPortalServiceTests();
|
await runPortalServiceTests();
|
||||||
await runAuthRedirectSafetyTests();
|
await runAuthRedirectSafetyTests();
|
||||||
await runI18nRouteParityTests();
|
await runI18nRouteParityTests();
|
||||||
|
await runAzurestorageHelperTests();
|
||||||
console.log("Phase 22 combined suite passed.");
|
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);
|
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 () => {
|
const run = async () => {
|
||||||
let passed = 0;
|
let passed = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -256,9 +256,13 @@ test("account/getPortalLogin appends hash and returns res.data", async () => {
|
|||||||
return { data: { hash: "&hash=login123" } };
|
return { data: { hash: "&hash=login123" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { data: { value: [{ id: "user-1" }] } };
|
throw new Error("Unexpected get url: " + url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
axios.requestHandler = async () => ({
|
||||||
|
data: { value: [{ id: "user-1" }] }
|
||||||
|
});
|
||||||
|
|
||||||
const account = loadServiceModule("accountDirectService.js", {
|
const account = loadServiceModule("accountDirectService.js", {
|
||||||
axios,
|
axios,
|
||||||
BASE_URL: "http://example.local",
|
BASE_URL: "http://example.local",
|
||||||
@@ -274,8 +278,9 @@ test("account/getPortalLogin appends hash and returns res.data", async () => {
|
|||||||
signCalls[0],
|
signCalls[0],
|
||||||
"/api/endpoint/gethash_api?path=%2Fapi%2Fendpoint%2Fgetportallogin_api%3FemailAddress%3Dperson%40example.com"
|
"/api/endpoint/gethash_api?path=%2Fapi%2Fendpoint%2Fgetportallogin_api%3FemailAddress%3Dperson%40example.com"
|
||||||
);
|
);
|
||||||
|
assert.strictEqual(axios.calls[1].config.method, "get");
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
axios.calls[1].url,
|
axios.calls[1].config.url,
|
||||||
"http://example.local/api/endpoint/getportallogin_api?emailAddress=person@example.com&hash=login123"
|
"http://example.local/api/endpoint/getportallogin_api?emailAddress=person@example.com&hash=login123"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -290,9 +295,11 @@ test("account/getPortalLogin returns JSON stringified error on failure", async (
|
|||||||
return { data: { hash: "&hash=err" } };
|
return { data: { hash: "&hash=err" } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
throw new Error("Unexpected get url: " + url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
axios.requestHandler = async () => Promise.reject(error);
|
||||||
|
|
||||||
const account = loadServiceModule("accountDirectService.js", {
|
const account = loadServiceModule("accountDirectService.js", {
|
||||||
axios,
|
axios,
|
||||||
BASE_URL: "",
|
BASE_URL: "",
|
||||||
|
|||||||
@@ -107,6 +107,15 @@ const loadServiceModule = (fileName, injected = {}) => {
|
|||||||
return queryUrl + hashResponse.data.hash;
|
return queryUrl + hashResponse.data.hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultBuildSignedUrl = async (queryUrl, config = {}) => {
|
||||||
|
const { baseUrl = "" } = config;
|
||||||
|
const hashedUrl = await (
|
||||||
|
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl
|
||||||
|
)(queryUrl);
|
||||||
|
|
||||||
|
return `${baseUrl}${hashedUrl}`;
|
||||||
|
};
|
||||||
|
|
||||||
const defaultGetSignedFileJson = async (queryUrl) => {
|
const defaultGetSignedFileJson = async (queryUrl) => {
|
||||||
const hashedUrl = await (
|
const hashedUrl = await (
|
||||||
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl
|
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl
|
||||||
@@ -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 defaultBuildFileQuery = (pathValue, params = {}, options = {}) => {
|
||||||
const { encode = false } = options;
|
const { encode = false } = options;
|
||||||
const entries = Object.entries(params).filter(([, value]) => {
|
const entries = Object.entries(params).filter(([, value]) => {
|
||||||
@@ -152,6 +200,8 @@ const loadServiceModule = (fileName, injected = {}) => {
|
|||||||
const defaultWithBaseUrl = (baseUrl, route) => `${baseUrl}${route}`;
|
const defaultWithBaseUrl = (baseUrl, route) => `${baseUrl}${route}`;
|
||||||
const defaultAppendQuerySuffix = (route, suffix = "") =>
|
const defaultAppendQuerySuffix = (route, suffix = "") =>
|
||||||
`${route}${suffix}`;
|
`${route}${suffix}`;
|
||||||
|
const defaultAppendHashSuffix = (route, hashBuilder) =>
|
||||||
|
defaultAppendQuerySuffix(route, hashBuilder(route));
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
module: { exports: {} },
|
module: { exports: {} },
|
||||||
@@ -172,12 +222,17 @@ const loadServiceModule = (fileName, injected = {}) => {
|
|||||||
getSignedFileJson:
|
getSignedFileJson:
|
||||||
injected.getSignedFileJson || defaultGetSignedFileJson,
|
injected.getSignedFileJson || defaultGetSignedFileJson,
|
||||||
downloadFileBlob: injected.downloadFileBlob || defaultDownloadFileBlob,
|
downloadFileBlob: injected.downloadFileBlob || defaultDownloadFileBlob,
|
||||||
|
getSignedJson: injected.getSignedJson || defaultGetSignedJson,
|
||||||
|
postSignedJson: injected.postSignedJson || defaultPostSignedJson,
|
||||||
|
deleteSignedJson: injected.deleteSignedJson || defaultDeleteSignedJson,
|
||||||
|
buildSignedUrl: injected.buildSignedUrl || defaultBuildSignedUrl,
|
||||||
buildHashedQueryUrl:
|
buildHashedQueryUrl:
|
||||||
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl,
|
injected.buildHashedQueryUrl || defaultBuildHashedQueryUrl,
|
||||||
buildFileQuery: injected.buildFileQuery || defaultBuildFileQuery,
|
buildFileQuery: injected.buildFileQuery || defaultBuildFileQuery,
|
||||||
withBaseUrl: injected.withBaseUrl || defaultWithBaseUrl,
|
withBaseUrl: injected.withBaseUrl || defaultWithBaseUrl,
|
||||||
appendQuerySuffix:
|
appendQuerySuffix:
|
||||||
injected.appendQuerySuffix || defaultAppendQuerySuffix,
|
injected.appendQuerySuffix || defaultAppendQuerySuffix,
|
||||||
|
appendHashSuffix: injected.appendHashSuffix || defaultAppendHashSuffix,
|
||||||
...injected
|
...injected
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user