217 lines
6.8 KiB
TypeScript
217 lines
6.8 KiB
TypeScript
import { requireRole } from '@/gateway/rbac/rbac';
|
|
import { OperatorRole } from '@/gateway/auth/types';
|
|
import { TestHelpers } from '../utils/test-helpers';
|
|
import { Response, NextFunction } from 'express';
|
|
|
|
describe('RBAC (Role-Based Access Control)', () => {
|
|
let makerOperator: any;
|
|
let checkerOperator: any;
|
|
let adminOperator: any;
|
|
|
|
beforeAll(async () => {
|
|
makerOperator = await TestHelpers.createTestOperator('TEST_RBAC_MAKER', 'MAKER' as any);
|
|
checkerOperator = await TestHelpers.createTestOperator('TEST_RBAC_CHECKER', 'CHECKER' as any);
|
|
adminOperator = await TestHelpers.createTestOperator('TEST_RBAC_ADMIN', 'ADMIN' as any);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await TestHelpers.cleanDatabase();
|
|
});
|
|
|
|
describe('requireRole', () => {
|
|
it('should allow MAKER role for MAKER endpoints', async () => {
|
|
const middleware = requireRole(OperatorRole.MAKER);
|
|
const req = {
|
|
operator: {
|
|
id: makerOperator.id,
|
|
operatorId: makerOperator.operatorId,
|
|
role: OperatorRole.MAKER,
|
|
},
|
|
} as any;
|
|
const res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
middleware(req, res, next);
|
|
// Middleware is synchronous, next should be called immediately
|
|
expect(next).toHaveBeenCalled(); // No error passed
|
|
});
|
|
|
|
it('should allow ADMIN role for MAKER endpoints', async () => {
|
|
const middleware = requireRole(OperatorRole.MAKER);
|
|
const req = {
|
|
operator: {
|
|
id: adminOperator.id,
|
|
operatorId: adminOperator.operatorId,
|
|
role: OperatorRole.ADMIN,
|
|
},
|
|
} as any;
|
|
const res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
middleware(req, res, next);
|
|
// Middleware is synchronous, next should be called immediately
|
|
expect(next).toHaveBeenCalled(); // No error passed
|
|
});
|
|
|
|
it('should reject CHECKER role for MAKER-only endpoints', async () => {
|
|
const middleware = requireRole(OperatorRole.MAKER);
|
|
const req = {
|
|
operator: {
|
|
id: checkerOperator.id,
|
|
operatorId: checkerOperator.operatorId,
|
|
role: OperatorRole.CHECKER,
|
|
},
|
|
} as any;
|
|
const res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
await middleware(req, res, next);
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
});
|
|
|
|
it('should allow CHECKER role for CHECKER endpoints', async () => {
|
|
const middleware = requireRole(OperatorRole.CHECKER);
|
|
const req = {
|
|
operator: {
|
|
id: checkerOperator.id,
|
|
operatorId: checkerOperator.operatorId,
|
|
role: OperatorRole.CHECKER,
|
|
},
|
|
} as any;
|
|
const res = {} as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
await middleware(req, res, next);
|
|
// Middleware is synchronous, next should be called immediately
|
|
expect(next).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should allow ADMIN role for CHECKER endpoints', async () => {
|
|
const middleware = requireRole(OperatorRole.CHECKER);
|
|
const req = {
|
|
operator: {
|
|
id: adminOperator.id,
|
|
operatorId: adminOperator.operatorId,
|
|
role: OperatorRole.ADMIN,
|
|
},
|
|
} as any;
|
|
const res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
middleware(req, res, next);
|
|
// Middleware is synchronous, next should be called immediately
|
|
expect(next).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should require ADMIN role for ADMIN-only endpoints', async () => {
|
|
const middleware = requireRole(OperatorRole.ADMIN);
|
|
const req = {
|
|
operator: {
|
|
id: adminOperator.id,
|
|
operatorId: adminOperator.operatorId,
|
|
role: OperatorRole.ADMIN,
|
|
},
|
|
} as any;
|
|
const res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
middleware(req, res, next);
|
|
// Middleware is synchronous, next should be called immediately
|
|
expect(next).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should reject MAKER role for ADMIN-only endpoints', async () => {
|
|
const middleware = requireRole(OperatorRole.ADMIN);
|
|
const req = {
|
|
operator: {
|
|
id: makerOperator.id,
|
|
operatorId: makerOperator.operatorId,
|
|
role: OperatorRole.MAKER,
|
|
},
|
|
} as any;
|
|
const res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
await middleware(req, res, next);
|
|
expect(res.status).toHaveBeenCalledWith(403);
|
|
});
|
|
});
|
|
|
|
describe('Dual Control Enforcement', () => {
|
|
it('should enforce MAKER can initiate but not approve', () => {
|
|
// MAKER can use MAKER endpoints
|
|
const makerMiddleware = requireRole(OperatorRole.MAKER);
|
|
const makerReq = {
|
|
operator: {
|
|
id: makerOperator.id,
|
|
role: OperatorRole.MAKER,
|
|
},
|
|
} as any;
|
|
const res = {} as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
makerMiddleware(makerReq, res, next);
|
|
expect(next).toHaveBeenCalledWith();
|
|
|
|
// MAKER cannot use CHECKER endpoints
|
|
const checkerMiddleware = requireRole(OperatorRole.CHECKER);
|
|
const checkerRes = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
|
|
checkerMiddleware(makerReq, checkerRes, next);
|
|
expect(checkerRes.status).toHaveBeenCalledWith(403);
|
|
});
|
|
|
|
it('should enforce CHECKER can approve but not initiate', () => {
|
|
// CHECKER can use CHECKER endpoints
|
|
const checkerMiddleware = requireRole(OperatorRole.CHECKER);
|
|
const checkerReq = {
|
|
operator: {
|
|
id: checkerOperator.id,
|
|
operatorId: checkerOperator.operatorId,
|
|
role: OperatorRole.CHECKER,
|
|
},
|
|
} as any;
|
|
const res = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
const next = jest.fn() as NextFunction;
|
|
|
|
checkerMiddleware(checkerReq, res, next);
|
|
expect(next).toHaveBeenCalledWith();
|
|
|
|
// CHECKER cannot use MAKER-only endpoints (if restricted)
|
|
const makerMiddleware = requireRole(OperatorRole.MAKER);
|
|
const makerRes = {
|
|
status: jest.fn().mockReturnThis(),
|
|
json: jest.fn(),
|
|
} as unknown as Response;
|
|
|
|
makerMiddleware(checkerReq, makerRes, next);
|
|
expect(makerRes.status).toHaveBeenCalledWith(403);
|
|
});
|
|
});
|
|
});
|
|
|