diff --git a/memory-bank/change-log.md b/memory-bank/change-log.md index f4718bc1..7f04cddd 100644 --- a/memory-bank/change-log.md +++ b/memory-bank/change-log.md @@ -2145,3 +2145,33 @@ Validation: Follow-ups: - Optional next bounded risk-reduction slice: assess whether any remaining non-service utility modules still use promise-chain axios extraction patterns and migrate them to shared clients where behavior contracts remain unchanged. + +--- + +### CL-061: TASK22260 next slice — core token regression coverage addition (bounded hardening) + +date: 2026-03-25 +author: Cline +scope: `tests/phase22/core-token-behaviour.test.cjs` +type: change +rationale: Follow the previous core token migration with a bounded verification slice to lock the request-client contract and prevent regression to direct axios extraction patterns. +impact: Improves confidence in `actions/core/token.js` behavior (success + failure semantics) without runtime code changes. +status: completed + +Summary: + +- Added new focused Phase 22 behavioural test suite: + - `tests/phase22/core-token-behaviour.test.cjs` +- Coverage asserts: + - `getToken` success path returns token payload and calls `requestJson` with expected URL/method/body/headers + - failure path logs via `consoleLogger` and returns the original error object +- Test harness uses VM import stripping consistent with existing phase behavioural suites. + +Validation: + +- `node tests/phase22/core-token-behaviour.test.cjs` -> pass (2/2) +- `npm run lint` -> warnings only (pre-existing `react-hooks/exhaustive-deps`; no new lint errors) + +Follow-ups: + +- Optional next bounded slice: add this phase22 suite to any aggregate test runner used in CI if/when phase-level suites are centrally orchestrated. diff --git a/tests/phase22/core-token-behaviour.test.cjs b/tests/phase22/core-token-behaviour.test.cjs new file mode 100644 index 00000000..771918ef --- /dev/null +++ b/tests/phase22/core-token-behaviour.test.cjs @@ -0,0 +1,110 @@ +const fs = require("fs"); +const path = require("path"); +const vm = require("vm"); +const assert = require("assert"); + +const rootDir = path.resolve(__dirname, "..", ".."); + +const loadTokenModule = (injected = {}) => { + const filePath = path.join(rootDir, "actions", "core", "token.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 = { getToken };\n"; + + const context = { + module: { exports: {} }, + exports: {}, + require, + process: { + env: { + GRANT_TYPE: "client_credentials", + CLIENT_ID: "client-id", + CLIENT_SECRET: "client-secret", + RELAYURI: "relay.local" + } + }, + ACCESS_TOKEN_ENDPOINT: "https://login.example/", + TENANT_ID: "tenant-123", + consoleLogger: () => {}, + requestJson: async () => { + throw new Error("requestJson not injected"); + }, + ...injected + }; + + vm.runInNewContext(source, context, { filename: filePath }); + return context.module.exports; +}; + +const tests = []; +const test = (name, fn) => tests.push({ name, fn }); + +test("core/getToken returns token payload and calls requestJson with expected config", async () => { + const calls = []; + + const mod = loadTokenModule({ + requestJson: async (config) => { + calls.push(config); + return { access_token: "abc", expires_in: 3600 }; + } + }); + + const result = await mod.getToken(); + + assert.deepStrictEqual(JSON.parse(JSON.stringify(result)), { + access_token: "abc", + expires_in: 3600 + }); + assert.strictEqual(calls.length, 1); + assert.strictEqual( + calls[0].url, + "https://login.example/tenant-123/oauth2/v2.0/token" + ); + assert.strictEqual(calls[0].method, "post"); + assert.strictEqual( + calls[0].data, + "grant_type=client_credentials&client_id=client-id&client_secret=client-secret&scope=https://relay.local/.default" + ); + assert.strictEqual( + calls[0].headers["Content-Type"], + "application/x-www-form-urlencoded" + ); +}); + +test("core/getToken logs and returns error object when requestJson throws", async () => { + const loggerCalls = []; + const error = new Error("token failed"); + + const mod = loadTokenModule({ + consoleLogger: (err) => loggerCalls.push(err), + requestJson: async () => { + throw error; + } + }); + + const result = await mod.getToken(); + + assert.strictEqual(result, error); + assert.strictEqual(loggerCalls.length, 1); + assert.strictEqual(loggerCalls[0], error); +}); + +const run = async () => { + let passed = 0; + + for (const currentTest of tests) { + await currentTest.fn(); + passed += 1; + } + + console.log( + `Phase 22 core-token tests passed (${passed}/${tests.length}).` + ); +}; + +run().catch((error) => { + console.error(error); + process.exit(1); +});