import { test, describe } from 'node:test'; import assert from 'node:assert/strict'; import vm from 'node:vm'; import http from 'node:http'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; import axios from 'axios'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const proxyPath = join(__dirname, '../../src/proxyScripts/kmeContentSourceAdapter.js'); const proxyCode = readFileSync(proxyPath, 'utf-8'); const proxyScript = new vm.Script(proxyCode, { filename: 'kmeContentSourceAdapter.js' }); /** * Start a minimal HTTP server that handles all POST requests with a fixed JSON body. * @param {number} statusCode * @param {object} responseBody * @returns {Promise<{ server: http.Server, url: string, close: () => Promise }>} */ function startMockTokenServer(statusCode, responseBody) { return new Promise((resolve, reject) => { const server = http.createServer((req, res) => { res.writeHead(statusCode, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(responseBody)); }); server.listen(0, '127.0.0.1', () => { const { port } = server.address(); const url = `http://127.0.0.1:${port}`; const close = () => new Promise((res, rej) => server.close(err => err ? rej(err) : res())); resolve({ server, url, close }); }); server.once('error', reject); }); } /** Build an in-memory Redis fake. */ function makeRedisFake() { const _store = {}; return { hSet: async (key, field, value) => { _store[`${key}:${field}`] = value; return 1; }, hGet: async (key, field) => _store[`${key}:${field}`] ?? null, }; } /** Build a capturable res object. */ function makeRes() { let statusCode = null; let body = ''; const headers = {}; return { writeHead: (code, hdrs = {}) => { statusCode = code; Object.assign(headers, hdrs); }, end: (b = '') => { body += String(b); }, get statusCode() { return statusCode; }, get body() { return body; }, get headers() { return headers; }, }; } // --------------------------------------------------------------------------- // Contract: 200 OK — successful OIDC token fetch // --------------------------------------------------------------------------- describe('proxy HTTP contract: 200 OK', () => { test('fresh token fetch → 200 Authorized with Content-Type text/plain', async () => { const mock = await startMockTokenServer(200, { id_token: 'contract-token', expires_in: 9_999_999_999, }); try { const res = makeRes(); const ctx = vm.createContext({ URLSearchParams, console, axios, redis: makeRedisFake(), kme_CSA_settings: { tokenUrl: mock.url, username: 'user', password: 'pass', clientId: 'client', scope: 'openid', }, req: { url: '/', method: 'GET', headers: {} }, res, }); await proxyScript.runInContext(ctx); assert.strictEqual(res.statusCode, 200); assert.strictEqual(res.body, 'Authorized'); assert.strictEqual(res.headers['Content-Type'], 'text/plain'); } finally { await mock.close(); } }); }); // --------------------------------------------------------------------------- // Contract: 401 Unauthorized — token service returns 4xx // --------------------------------------------------------------------------- describe('proxy HTTP contract: 401 Unauthorized', () => { test('token service 401 → proxy 401 with Unauthorized: prefix', async () => { const mock = await startMockTokenServer(401, {}); try { const res = makeRes(); const ctx = vm.createContext({ URLSearchParams, console, axios, redis: makeRedisFake(), kme_CSA_settings: { tokenUrl: mock.url, username: 'bad-user', password: 'bad-pass', clientId: 'client', scope: 'openid', }, req: { url: '/', method: 'GET', headers: {} }, res, }); await proxyScript.runInContext(ctx); assert.strictEqual(res.statusCode, 401); assert.match(res.body, /^Unauthorized: .+/); assert.strictEqual(res.headers['Content-Type'], 'text/plain'); } finally { await mock.close(); } }); });