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 const\s+/g, "const "); source = source.replace( /export default\s+(\w+);/g, "module.exports.default = $1;" ); source += '\nif (typeof respondSuccess !== "undefined") module.exports.respondSuccess = respondSuccess;\n'; source += '\nif (typeof respondError !== "undefined") module.exports.respondError = respondError;\n'; source += '\nif (typeof ApiProxy !== "undefined" && !module.exports.default) module.exports.default = ApiProxy;\n'; const context = { module: { exports: {} }, exports: {}, require, process, console: { log: () => {}, info: () => {}, warn: () => {}, error: () => {} }, ...injected }; vm.runInNewContext(source, context, { filename: filePath }); return context.module.exports; }; 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 createNextConnectMock = () => { const router = { handler: null, use: () => {}, get(fn) { this.handler = fn; }, post(fn) { this.handler = fn; } }; return () => router; }; const respondSuccessMock = (res, data, status = 200) => { return res.status(status).json(data); }; const respondErrorMock = ( res, { status = 500, code = "INTERNAL_SERVER_ERROR", message = "Request failed" } = {} ) => { return res.status(status).json({ success: false, error: { code, message } }); }; const tests = []; const test = (name, fn) => tests.push({ name, fn }); test("apiResponse.respondSuccess returns data with default 200", () => { const mod = loadModule("pages/api/middleware/apiResponse.js"); const res = createRes(); mod.respondSuccess(res, { ok: true }); assert.strictEqual(res.state.statusCode, 200); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { ok: true }); }); test("apiResponse.respondError includes details outside production", () => { const original = process.env.NODE_ENV; process.env.NODE_ENV = "development"; const mod = loadModule("pages/api/middleware/apiResponse.js"); const res = createRes(); mod.respondError(res, { status: 400, code: "INVALID_HASH", message: "Invalid hash", details: { expected: "x", actual: "y" } }); assert.strictEqual(res.state.statusCode, 400); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { success: false, error: { code: "INVALID_HASH", message: "Invalid hash", details: { expected: "x", actual: "y" } } }); process.env.NODE_ENV = original; }); test("apiResponse.respondError suppresses details in production", () => { const original = process.env.NODE_ENV; process.env.NODE_ENV = "production"; const mod = loadModule("pages/api/middleware/apiResponse.js"); const res = createRes(); mod.respondError(res, { status: 500, code: "X", message: "Y", details: { shouldNot: "appear" } }); assert.strictEqual(res.state.statusCode, 500); assert.deepStrictEqual(JSON.parse(JSON.stringify(res.state.jsonBody)), { success: false, error: { code: "X", message: "Y" } }); process.env.NODE_ENV = original; }); test("upload handler returns HASH_REQUIRED for missing hash", async () => { const mod = loadModule("pages/api/file/upload.js", { nextConnect: createNextConnectMock(), middleware: () => {}, hashAPIPath: () => "?hash=expected", respondError: respondErrorMock, respondSuccess: respondSuccessMock, createBlob: async () => ({ ok: true }), createRepBlob: async () => ({ ok: true }) }); const req = { query: {}, body: {} }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 400); assert.strictEqual(res.state.jsonBody.error.code, "HASH_REQUIRED"); }); test("upload handler returns INVALID_HASH for wrong hash", async () => { const mod = loadModule("pages/api/file/upload.js", { nextConnect: createNextConnectMock(), middleware: () => {}, hashAPIPath: () => "?hash=expected", respondError: respondErrorMock, respondSuccess: respondSuccessMock, createBlob: async () => ({ ok: true }), createRepBlob: async () => ({ ok: true }) }); const req = { query: { hash: "wrong" }, body: { containerID: ["c1"], casefolderID: ["f1"] } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 400); assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH"); }); test("deleteblob handler returns MISSING_REQUIRED_QUERY when blobname missing", async () => { const mod = loadModule("pages/api/file/deleteblob.js", { nextConnect: createNextConnectMock(), middleware: () => {}, hashAPIPath: () => "&hash=expected", respondError: respondErrorMock, respondSuccess: respondSuccessMock, deleteBlob: async () => ({ ok: true }) }); const req = { query: { container: "c1", casefolderID: "f1", hash: "expected" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 400); assert.strictEqual(res.state.jsonBody.error.code, "MISSING_REQUIRED_QUERY"); }); test("getbloblist handler returns INVALID_HASH for mismatch", async () => { const mod = loadModule("pages/api/file/getbloblist.js", { nextConnect: createNextConnectMock(), middleware: () => {}, hashAPIPath: () => "&hash=expected", respondError: respondErrorMock, respondSuccess: respondSuccessMock, getBlobs: async () => ({ id: "x" }), getRepsFilesBlobs: async () => ({ id: "x" }) }); const req = { query: { container: "c1", casefolderID: "f1", hash: "wrong" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 400); assert.strictEqual(res.state.jsonBody.error.code, "INVALID_HASH"); }); test("setupcontainer handler returns MISSING_REQUIRED_QUERY when ident missing", async () => { const mod = loadModule("pages/api/file/setupcontainer.js", { nextConnect: createNextConnectMock(), middleware: () => {}, hashAPIPath: () => "&hash=expected", consoleLogger: () => {}, respondError: respondErrorMock, respondSuccess: respondSuccessMock, createContainer: async () => ({ ok: true }) }); const req = { query: { hash: "expected" } }; const res = createRes(); await mod.default.handler(req, res); assert.strictEqual(res.state.statusCode, 400); assert.strictEqual(res.state.jsonBody.error.code, "MISSING_REQUIRED_QUERY"); }); const run = async () => { let passed = 0; for (const currentTest of tests) { await currentTest.fn(); passed += 1; } console.log(`Phase 21 Slice 1 tests passed (${passed}/${tests.length}).`); }; run().catch((error) => { console.error(error); process.exit(1); });