From 58e1eef1f5db217a88ea92b954ca37f888eee362 Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 26 Mar 2026 11:11:24 +0000 Subject: [PATCH] TASK22269: add phase22 azurestorage helper contract tests --- memory-bank/change-log.md | 32 +++++ .../azurestorage-helper-behaviour.test.cjs | 125 ++++++++++++++++++ tests/phase22/index.test.cjs | 2 + 3 files changed, 159 insertions(+) create mode 100644 tests/phase22/azurestorage-helper-behaviour.test.cjs diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index f9e737d4..15b9892a 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -2975,3 +2975,35 @@ Validation: 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. diff --git a/tests/phase22/azurestorage-helper-behaviour.test.cjs b/tests/phase22/azurestorage-helper-behaviour.test.cjs new file mode 100644 index 00000000..e9f68049 --- /dev/null +++ b/tests/phase22/azurestorage-helper-behaviour.test.cjs @@ -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); + }); +} diff --git a/tests/phase22/index.test.cjs b/tests/phase22/index.test.cjs index f5f3f3eb..02d4341c 100644 --- a/tests/phase22/index.test.cjs +++ b/tests/phase22/index.test.cjs @@ -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."); };