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); }); }); });