From a66d55af803a7474d3ac6397c6eaed63d0b91999 Mon Sep 17 00:00:00 2001 From: robbond Date: Wed, 25 Mar 2026 10:59:20 +0000 Subject: [PATCH] refactor(actions): extract fileClient with phase22 coverage --- actions/clients/fileClient.js | 23 ++++ actions/clients/index.js | 1 + actions/services/documentDirectService.js | 43 +++---- memory-bank/change-log.md | 46 +++++++ tests/phase22/file-client-behaviour.test.cjs | 127 +++++++++++++++++++ tests/phase22/index.test.cjs | 2 + 6 files changed, 214 insertions(+), 28 deletions(-) create mode 100644 actions/clients/fileClient.js create mode 100644 tests/phase22/file-client-behaviour.test.cjs diff --git a/actions/clients/fileClient.js b/actions/clients/fileClient.js new file mode 100644 index 00000000..595417b6 --- /dev/null +++ b/actions/clients/fileClient.js @@ -0,0 +1,23 @@ +import { getJson, requestJson } from "./endpointClient"; +import { buildHashedQueryUrl } from "./relayClient"; + +export const getFileJson = (url) => { + return getJson(url); +}; + +export const getSignedFileJson = async (queryUrl) => { + const signedUrl = await buildHashedQueryUrl(queryUrl); + + return requestJson({ + method: "get", + url: signedUrl + }); +}; + +export const downloadFileBlob = (url) => { + return requestJson({ + method: "get", + url, + responseType: "blob" + }); +}; diff --git a/actions/clients/index.js b/actions/clients/index.js index 3676d8e2..e2c2f03c 100644 --- a/actions/clients/index.js +++ b/actions/clients/index.js @@ -1,2 +1,3 @@ export * from "./relayClient"; export * from "./endpointClient"; +export * from "./fileClient"; diff --git a/actions/services/documentDirectService.js b/actions/services/documentDirectService.js index 4e7b0793..f239ef41 100644 --- a/actions/services/documentDirectService.js +++ b/actions/services/documentDirectService.js @@ -3,6 +3,11 @@ import { consoleLogger } from "../core/logger"; import { hashAPIPath } from "../core/hash"; import { buildHashedQueryUrl } from "../clients/relayClient"; import { getJson, requestJson } from "../clients/endpointClient"; +import { + getFileJson, + getSignedFileJson, + downloadFileBlob +} from "../clients/fileClient"; export const getAwaitingSubmissionFromBlob = (containerName) => { return getJson( @@ -48,7 +53,7 @@ export const getAwaitingSubmissionFromBlobProxy = async (containerName) => { }; export const getFilesFromBlobproxy = (containerName, casefolderID) => { - return getJson( + return getFileJson( "/api/file/getbloblistproxy?container=" + containerName + "&casefolderID=" + @@ -68,16 +73,9 @@ export const deleteAwaitingSubmissionsFromBlob = ( "&casefolderID=" + casefolderID; - return buildHashedQueryUrl(queryUrl) - .then((signedUrl) => - requestJson({ - method: "get", - url: signedUrl - }) - ) - .catch((error) => { - consoleLogger(error); - }); + return getSignedFileJson(queryUrl).catch((error) => { + consoleLogger(error); + }); }; export const deleteMyRepresentationsFromBlob = ( @@ -93,16 +91,9 @@ export const deleteMyRepresentationsFromBlob = ( "&repfile=" + repfile; - return buildHashedQueryUrl(queryUrl) - .then((signedUrl) => - requestJson({ - method: "get", - url: signedUrl - }) - ) - .catch((error) => { - consoleLogger(error); - }); + return getSignedFileJson(queryUrl).catch((error) => { + consoleLogger(error); + }); }; export const uploadFiles = async ( @@ -299,7 +290,7 @@ export const deleteBlob = async ( deleteblobhash, casefolderID ) => { - return getJson( + return getFileJson( "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + @@ -319,7 +310,7 @@ export const deleteRepBlob = async ( casefolderID, filenamePrefix ) => { - return getJson( + return getFileJson( "/api/file/deleteblob?container=" + containerName + "&casefolderID=" + @@ -340,11 +331,7 @@ export const downloadBlob = (containerName, blobName) => { "&blobname=" + blobName; - return requestJson({ - method: "get", - url: queryUrl, - responseType: "blob" - }).catch((error) => { + return downloadFileBlob(queryUrl).catch((error) => { consoleLogger(error); }); }; diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index 776b655b..daa41e76 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -2266,3 +2266,49 @@ Validation: Follow-ups: - Optional next bounded slice: add focused phase22 behavioural coverage for any future shared client wrappers introduced beyond `endpointClient`/`relayClient`. + +--- + +### CL-065: TASK22260 next slice — fileClient extraction + phase22 behavioural coverage + +date: 2026-03-25 +author: Cline +scope: `actions/clients/{fileClient,index}.js`, `actions/services/documentDirectService.js`, `tests/phase22/{file-client-behaviour,index}.test.cjs` +type: change +rationale: Continue bounded shared-client decomposition by extracting repeated file-route request patterns into `fileClient` and hardening behaviour with dedicated phase22 tests. +impact: Reduces request-boilerplate duplication in document service and increases regression confidence for extracted file client helper contracts. +status: completed + +Summary: + +- Added new shared client wrapper: `actions/clients/fileClient.js`: + - `getFileJson(url)` + - `getSignedFileJson(queryUrl)` + - `downloadFileBlob(url)` +- Exported `fileClient` from `actions/clients/index.js` (and therefore via `actions/index.js` barrel path). +- Migrated a bounded subset of `actions/services/documentDirectService.js` call sites to `fileClient` while preserving catch-path logging behavior: + - `getFilesFromBlobproxy` -> `getFileJson` + - `deleteAwaitingSubmissionsFromBlob` -> `getSignedFileJson` + - `deleteMyRepresentationsFromBlob` -> `getSignedFileJson` + - `deleteBlob` -> `getFileJson` + - `deleteRepBlob` -> `getFileJson` + - `downloadBlob` -> `downloadFileBlob` +- Added `tests/phase22/file-client-behaviour.test.cjs` covering: + - delegation to `getJson` + - signed URL generation + `requestJson` invocation + - blob download config contract (`responseType: "blob"`) +- Updated aggregate runner `tests/phase22/index.test.cjs` to include file-client suite. + +Validation: + +- `node tests/phase22/file-client-behaviour.test.cjs` -> pass (3/3) +- `node tests/phase22/index.test.cjs` -> pass + - core-token: 2/2 + - client-utils: 5/5 + - file-client: 3/3 + - phase22 combined: pass +- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps`; no new lint errors) + +Follow-ups: + +- Optional next bounded slice: evaluate whether additional `documentDirectService` file-route call sites can adopt `fileClient` without altering current behavior contracts. diff --git a/tests/phase22/file-client-behaviour.test.cjs b/tests/phase22/file-client-behaviour.test.cjs new file mode 100644 index 00000000..31d8920f --- /dev/null +++ b/tests/phase22/file-client-behaviour.test.cjs @@ -0,0 +1,127 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); +const assert = require("assert"); + +const rootDir = path.resolve(__dirname, "..", ".."); + +const loadFileClientModule = (injected = {}) => { + const filePath = path.join(rootDir, "actions", "clients", "fileClient.js"); + let source = fs.readFileSync(filePath, "utf8"); + + source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); + source = source.replace(/export const\s+/g, "const "); + source += + "\nmodule.exports = { getFileJson, getSignedFileJson, downloadFileBlob };\n"; + + const context = { + module: { exports: {} }, + exports: {}, + require, + getJson: async () => { + throw new Error("getJson not injected"); + }, + requestJson: async () => { + throw new Error("requestJson not injected"); + }, + buildHashedQueryUrl: async () => { + throw new Error("buildHashedQueryUrl not injected"); + }, + ...injected + }; + + vm.runInNewContext(source, context, { filename: filePath }); + return context.module.exports; +}; + +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +test("clients/fileClient getFileJson delegates to getJson", async () => { + const calls = []; + + const mod = loadFileClientModule({ + getJson: async (url) => { + calls.push(url); + return { ok: true }; + } + }); + + const result = await mod.getFileJson("/api/file/getbloblistproxy?x=1"); + + assert.deepStrictEqual(JSON.parse(JSON.stringify(result)), { ok: true }); + assert.strictEqual(calls.length, 1); + assert.strictEqual(calls[0], "/api/file/getbloblistproxy?x=1"); +}); + +test("clients/fileClient getSignedFileJson signs url and requests json", async () => { + const signedCalls = []; + const requestCalls = []; + + const mod = loadFileClientModule({ + buildHashedQueryUrl: async (queryUrl) => { + signedCalls.push(queryUrl); + return queryUrl + "&hash=signed"; + }, + requestJson: async (config) => { + requestCalls.push(config); + return { deleted: true }; + } + }); + + const result = await mod.getSignedFileJson( + "/api/file/deleteblobcase?container=a&casefolderID=b" + ); + + assert.deepStrictEqual(JSON.parse(JSON.stringify(result)), { + 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" + ); +}); + +test("clients/fileClient downloadFileBlob requests blob response", async () => { + const requestCalls = []; + + const mod = loadFileClientModule({ + requestJson: async (config) => { + requestCalls.push(config); + return "blob-data"; + } + }); + + const result = await mod.downloadFileBlob("/api/file/downloadblob?x=1"); + + assert.strictEqual(result, "blob-data"); + assert.strictEqual(requestCalls.length, 1); + assert.strictEqual(requestCalls[0].method, "get"); + assert.strictEqual(requestCalls[0].responseType, "blob"); + assert.strictEqual(requestCalls[0].url, "/api/file/downloadblob?x=1"); +}); + +const run = async () => { + let passed = 0; + + for (const currentTest of tests) { + await currentTest.fn(); + passed += 1; + } + + console.log( + `Phase 22 file-client tests passed (${passed}/${tests.length}).` + ); +}; + +module.exports = run; + +if (require.main === module) { + run().catch((error) => { + console.error(error); + process.exit(1); + }); +} diff --git a/tests/phase22/index.test.cjs b/tests/phase22/index.test.cjs index e856ac47..976bf9ee 100644 --- a/tests/phase22/index.test.cjs +++ b/tests/phase22/index.test.cjs @@ -1,9 +1,11 @@ const runCoreTokenTests = require("./core-token-behaviour.test.cjs"); const runClientUtilsTests = require("./client-utils-behaviour.test.cjs"); +const runFileClientTests = require("./file-client-behaviour.test.cjs"); const run = async () => { await runCoreTokenTests(); await runClientUtilsTests(); + await runFileClientTests(); console.log("Phase 22 combined suite passed."); };