/** * Unit tests for src/middleware/opa.ts — Wasm mode and fail-closed edge cases. * * This file is kept separate from opa.test.ts because it needs different * `fs.existsSync` and `@open-policy-agent/opa-wasm` mock behaviour. * * Jest's module registry is isolated per test file, so the module-level * singletons (`wasmPolicy`, `scopesMap`) are fresh for each file. */ import path from 'path'; import { Request, Response, NextFunction } from 'express'; import { RequestHandler } from 'express'; import { ITokenPayload } from '../../../src/types/index'; import { AuthorizationError } from '../../../src/utils/errors'; // ─── Point POLICY_DIR at the real policies directory ───────────────────────── const PROJECT_ROOT = path.resolve(__dirname, '../../..'); const POLICIES_DIR = path.join(PROJECT_ROOT, 'policies'); process.env['POLICY_DIR'] = POLICIES_DIR; // ─── Wasm mock — a LoadedPolicy-like object ─────────────────────────────────── /** Tracks calls so individual tests can assert on evaluation results. */ const mockEvaluate = jest.fn(); const mockSetData = jest.fn(); const MOCK_LOADED_POLICY = { evaluate: mockEvaluate, setData: mockSetData, }; // Mock @open-policy-agent/opa-wasm BEFORE the module is loaded jest.mock('@open-policy-agent/opa-wasm', () => ({ loadPolicy: jest.fn().mockResolvedValue(MOCK_LOADED_POLICY), })); // ─── Mock fs: existsSync returns true for .wasm AND scopes.json ────────────── jest.mock('fs', () => { const actual = jest.requireActual('fs'); return { ...actual, existsSync: jest.fn((_filePath: unknown) => { // Both .wasm and other paths exist return true; }), readFileSync: jest.fn((filePath: unknown, encoding?: unknown) => { if (typeof filePath === 'string' && filePath.endsWith('.wasm')) { // Return a Buffer-like object for the Wasm bundle return Buffer.from('fake-wasm-bytes'); } // For scopes.json, delegate to the real fs return actual.readFileSync(filePath as string, encoding as BufferEncoding); }), }; }); // Import AFTER mocks import { createOpaMiddleware, reloadOpaPolicy } from '../../../src/middleware/opa'; // ─── Helpers ────────────────────────────────────────────────────────────────── function makeUser(scope: string): ITokenPayload { return { sub: 'agent-wasm-test', client_id: 'agent-wasm-test', scope, jti: 'jti-wasm-001', iat: 1000, exp: 9999999999, }; } function makeReq( method: string, baseUrl: string, reqPath: string, user?: ITokenPayload, ): Partial { return { method, baseUrl, path: reqPath, user }; } // ─── Tests ──────────────────────────────────────────────────────────────────── describe('createOpaMiddleware (Wasm mode)', () => { let middleware: RequestHandler; let next: jest.MockedFunction; beforeAll(async () => { middleware = await createOpaMiddleware(); }); beforeEach(() => { next = jest.fn(); mockEvaluate.mockReset(); }); it('should load in Wasm mode and call setData with scopes.json', () => { // setData is called once during createOpaMiddleware() → loadWasmPolicy() expect(mockSetData).toHaveBeenCalledTimes(1); }); it('should allow request when Wasm policy evaluate returns allow: true', () => { mockEvaluate.mockReturnValue([{ result: { allow: true } }]); const req = makeReq('GET', '/api/v1', '/agents', makeUser('agents:read')) as Request; middleware(req, {} as Response, next); expect(mockEvaluate).toHaveBeenCalledTimes(1); expect(next).toHaveBeenCalledWith(/* no args */); }); it('should deny request when Wasm policy evaluate returns allow: false', () => { mockEvaluate.mockReturnValue([{ result: { allow: false } }]); const req = makeReq('GET', '/api/v1', '/agents', makeUser('agents:read')) as Request; middleware(req, {} as Response, next); expect(next).toHaveBeenCalledWith(expect.any(AuthorizationError)); }); it('should deny request when Wasm evaluate returns empty result set', () => { mockEvaluate.mockReturnValue([]); const req = makeReq('POST', '/api/v1', '/agents', makeUser('agents:write')) as Request; middleware(req, {} as Response, next); expect(next).toHaveBeenCalledWith(expect.any(AuthorizationError)); }); it('should deny request when Wasm evaluate returns non-array result', () => { mockEvaluate.mockReturnValue(null); const req = makeReq('GET', '/api/v1', '/audit', makeUser('audit:read')) as Request; middleware(req, {} as Response, next); expect(next).toHaveBeenCalledWith(expect.any(AuthorizationError)); }); it('should propagate unexpected errors thrown by Wasm evaluate to next', () => { const wasmError = new Error('Wasm evaluation failure'); mockEvaluate.mockImplementation(() => { throw wasmError; }); const req = makeReq('GET', '/api/v1', '/agents', makeUser('agents:read')) as Request; middleware(req, {} as Response, next); expect(next).toHaveBeenCalledWith(wasmError); }); it('should call next(AuthorizationError) with "not authenticated" when req.user is absent in Wasm mode', () => { const req = makeReq('GET', '/api/v1', '/agents', undefined) as Request; middleware(req, {} as Response, next); const err = (next as jest.Mock).mock.calls[0][0] as AuthorizationError; expect(err).toBeInstanceOf(AuthorizationError); expect(err.message).toMatch(/not authenticated/i); }); }); describe('reloadOpaPolicy (Wasm mode)', () => { it('should reload in Wasm mode without error', async () => { await expect(reloadOpaPolicy()).resolves.toBeUndefined(); // setData should have been called again during reload expect(mockSetData).toHaveBeenCalled(); }); });