137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import { DualControl } from '@/orchestration/dual-control/dual-control';
|
|
import { PaymentRepository } from '@/repositories/payment-repository';
|
|
import { PaymentStatus } from '@/models/payment';
|
|
import { TestHelpers } from '../utils/test-helpers';
|
|
import { PaymentRequest } from '@/gateway/validation/payment-validation';
|
|
import { PaymentType, Currency } from '@/models/payment';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
describe('Dual Control Compliance', () => {
|
|
let paymentRepository: PaymentRepository;
|
|
let makerOperator: any;
|
|
let checkerOperator: any;
|
|
let paymentId: string;
|
|
|
|
beforeAll(async () => {
|
|
paymentRepository = new PaymentRepository();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await TestHelpers.cleanDatabase();
|
|
|
|
// Create operators for each test
|
|
makerOperator = await TestHelpers.createTestOperator('TEST_MAKER', 'MAKER' as any);
|
|
checkerOperator = await TestHelpers.createTestOperator('TEST_CHECKER', 'CHECKER' as any);
|
|
|
|
// Create a payment in PENDING_APPROVAL status
|
|
const paymentRequest: PaymentRequest = {
|
|
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
|
|
amount: 1000,
|
|
currency: Currency.USD,
|
|
senderAccount: 'ACC001',
|
|
senderBIC: 'TESTBIC1',
|
|
receiverAccount: 'ACC002',
|
|
receiverBIC: 'TESTBIC2',
|
|
beneficiaryName: 'Test Beneficiary',
|
|
};
|
|
|
|
paymentId = await paymentRepository.create(
|
|
paymentRequest,
|
|
makerOperator.id,
|
|
`TEST-DUAL-${Date.now()}`
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await TestHelpers.cleanDatabase();
|
|
});
|
|
|
|
describe('canApprove', () => {
|
|
it('should allow CHECKER to approve payment', async () => {
|
|
const result = await DualControl.canApprove(paymentId, checkerOperator.id);
|
|
expect(result.allowed).toBe(true);
|
|
});
|
|
|
|
it('should allow ADMIN to approve payment', async () => {
|
|
const adminOperator = await TestHelpers.createTestOperator('TEST_ADMIN', 'ADMIN' as any);
|
|
const result = await DualControl.canApprove(paymentId, adminOperator.id);
|
|
expect(result.allowed).toBe(true);
|
|
});
|
|
|
|
it('should reject if MAKER tries to approve their own payment', async () => {
|
|
const result = await DualControl.canApprove(paymentId, makerOperator.id);
|
|
expect(result.allowed).toBe(false);
|
|
// MAKER role is checked first, so error will be about role, not "same as maker"
|
|
expect(result.reason).toBeDefined();
|
|
expect(result.reason).toContain('CHECKER role');
|
|
});
|
|
|
|
it('should reject if payment is not in PENDING_APPROVAL status', async () => {
|
|
// Create a fresh payment for this test to avoid state issues
|
|
const freshPaymentRequest: PaymentRequest = {
|
|
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
|
|
amount: 2000,
|
|
currency: Currency.USD,
|
|
senderAccount: 'ACC005',
|
|
senderBIC: 'TESTBIC5',
|
|
receiverAccount: 'ACC006',
|
|
receiverBIC: 'TESTBIC6',
|
|
beneficiaryName: 'Test Beneficiary Status',
|
|
};
|
|
const freshPaymentId = await paymentRepository.create(
|
|
freshPaymentRequest,
|
|
makerOperator.id,
|
|
`TEST-DUAL-STATUS-${Date.now()}`
|
|
);
|
|
|
|
await paymentRepository.updateStatus(freshPaymentId, PaymentStatus.APPROVED);
|
|
|
|
const result = await DualControl.canApprove(freshPaymentId, checkerOperator.id);
|
|
expect(result.allowed).toBe(false);
|
|
expect(result.reason).toBeDefined();
|
|
expect(result.reason).toMatch(/status|PENDING_APPROVAL/i);
|
|
});
|
|
|
|
it('should reject if payment does not exist', async () => {
|
|
const result = await DualControl.canApprove(uuidv4(), checkerOperator.id);
|
|
expect(result.allowed).toBe(false);
|
|
expect(result.reason).toContain('not found');
|
|
});
|
|
});
|
|
|
|
describe('enforceDualControl', () => {
|
|
it('should enforce maker and checker are different', async () => {
|
|
// Create payment by maker
|
|
const paymentRequest: PaymentRequest = {
|
|
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
|
|
amount: 2000,
|
|
currency: Currency.USD,
|
|
senderAccount: 'ACC003',
|
|
senderBIC: 'TESTBIC3',
|
|
receiverAccount: 'ACC004',
|
|
receiverBIC: 'TESTBIC4',
|
|
beneficiaryName: 'Test Beneficiary 2',
|
|
};
|
|
|
|
const newPaymentId = await paymentRepository.create(
|
|
paymentRequest,
|
|
makerOperator.id,
|
|
`TEST-DUAL2-${Date.now()}`
|
|
);
|
|
|
|
// Try to approve with same maker - should fail
|
|
const canApprove = await DualControl.canApprove(newPaymentId, makerOperator.id);
|
|
expect(canApprove.allowed).toBe(false);
|
|
});
|
|
|
|
it('should require checker role', async () => {
|
|
const makerOnly = await TestHelpers.createTestOperator('TEST_MAKER_ONLY', 'MAKER' as any);
|
|
|
|
const result = await DualControl.canApprove(paymentId, makerOnly.id);
|
|
// Should fail because maker-only cannot approve
|
|
expect(result.allowed).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|