const fs = require("fs"); const path = require("path"); const vm = require("vm"); const assert = require("assert"); const rootDir = path.resolve(__dirname, "..", ".."); const loadModule = (relativePath, injected = {}) => { const filePath = path.join(rootDir, relativePath); let source = fs.readFileSync(filePath, "utf8"); source = source.replace(/import[\s\S]*?from\s+"[^"]+";\n?/g, ""); source = source.replace( /export default async function\s+(\w+)\s*\(/, "async function $1(" ); source = source.replace(/export const\s+/g, "const "); source = source.replace( /export default\s+(\w+);/g, "module.exports.default = $1;" ); source += '\nif (typeof ApiProxy !== "undefined" && !module.exports.default) module.exports.default = ApiProxy;\n'; const context = { module: { exports: {} }, exports: {}, require, process, console: { log: () => {}, error: () => {} }, ...injected }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; const createNextConnectMock = () => { const router = { handler: null, use: () => {}, get(fn) { this.handler = fn; } }; return () => router; }; const createRes = () => { const state = { statusCode: null, jsonBody: undefined }; return { state, status(code) { state.statusCode = code; return this; }, json(payload) { state.jsonBody = payload; return payload; } }; }; const tests = []; const test = (name, fn) => tests.push({ name, fn }); test("gethash_api rejects missing path with 400", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "?hash=expected", getSession: async () => ({ user: { id: "u1" } }), nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: {} }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 400); }); test("gethash_api rejects non-api path with 400", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "?hash=expected", getSession: async () => ({ user: { id: "u1" } }), nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: { path: "/not-api/path" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 400); }); test("gethash_api returns hash for valid api path", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "&hash=expected", getSession: async () => ({ user: { id: "u1" } }), nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: { path: "/api/file/upload" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 200); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { hash: "&hash=expected" }); }); test("gethash_api returns hash for allow-listed getportallogin path", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "&hash=login", getSession: async () => ({ user: { id: "u1" } }), nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: { path: "/api/endpoint/getportallogin_api?emailAddress=a@b.com" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 200); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { hash: "&hash=login" }); }); test("gethash_api returns hash for allow-listed deleteblobcase path", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "&hash=deletecase", getSession: async () => ({ user: { id: "u1" } }), nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: { path: "/api/file/deleteblobcase?container=c1&casefolderID=r1" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 200); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { hash: "&hash=deletecase" }); }); test("gethash_api returns hash for allow-listed deleteblobrep path", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "&hash=deleterep", getSession: async () => ({ user: { id: "u1" } }), nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: { path: "/api/file/deleteblobrep?container=c1&casefolderID=r1&repfile=f1" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 200); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { hash: "&hash=deleterep" }); }); test("gethash_api returns hash for allow-listed deletewatchedcases path", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "&hash=watch", getSession: async () => ({ user: { id: "u1" } }), nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: { path: "/api/endpoint/deletewatchedcases_api?watchedCaseID=123" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 200); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { hash: "&hash=watch" }); }); test("gethash_api returns hash for allow-listed deletemyrepresentations path", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "&hash=delrep", getSession: async () => ({ user: { id: "u1" } }), nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: { path: "/api/endpoint/deletemyrepresentations_api?myRepresentationsID=abc" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 200); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { hash: "&hash=delrep" }); }); test("gethash_api rejects unauthenticated requests with 401", async () => { const mod = loadModule("pages/api/endpoint/gethash_api.js", { hashAPIPath: () => "&hash=expected", getSession: async () => null, nextConnect: createNextConnectMock(), middleware: () => {} }); const req = { query: { path: "/api/file/upload" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 401); }); const run = async () => { let passed = 0; for (const t of tests) { await t.fn(); passed += 1; } console.log( `Phase 14 behavioural tests passed (${passed}/${tests.length}).` ); }; run().catch((error) => { console.error(error); process.exit(1); });