Files
dbis_core-lite/tests/e2e/payment-workflow-e2e.test.ts
2026-02-09 21:51:45 -08:00

225 lines
7.2 KiB
TypeScript

import request from 'supertest';
import app from '@/app';
import { TestHelpers } from '../utils/test-helpers';
import { PaymentType, Currency, PaymentStatus } from '@/models/payment';
import { PaymentRequest } from '@/gateway/validation/payment-validation';
describe('E2E Payment Workflow', () => {
let makerToken: string;
let checkerToken: string;
let makerOperator: any;
let checkerOperator: any;
beforeAll(async () => {
// Clean database first (non-blocking)
TestHelpers.cleanDatabase().catch(() => {}); // Fire and forget
// Create test operators with timeout protection
const operatorPromises = [
TestHelpers.createTestOperator('E2E_MAKER', 'MAKER' as any, 'Test123!@#'),
TestHelpers.createTestOperator('E2E_CHECKER', 'CHECKER' as any, 'Test123!@#')
];
[makerOperator, checkerOperator] = await Promise.all(operatorPromises);
// Generate tokens
makerToken = TestHelpers.generateTestToken(
makerOperator.operatorId,
makerOperator.id,
makerOperator.role
);
checkerToken = TestHelpers.generateTestToken(
checkerOperator.operatorId,
checkerOperator.id,
checkerOperator.role
);
}, 90000);
afterAll(async () => {
// Fast cleanup with timeout protection
await Promise.race([
TestHelpers.cleanDatabase(),
new Promise(resolve => setTimeout(resolve, 5000))
]);
}, 30000);
beforeEach(async () => {
// Skip cleanup in beforeEach to speed up tests
// Tests should clean up their own data
});
describe('Complete Payment Flow', () => {
it('should complete full payment workflow: initiate → approve → process', async () => {
// Step 1: Maker initiates payment
const paymentRequest: PaymentRequest = {
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
amount: 1000.50,
currency: Currency.USD,
senderAccount: 'ACC001',
senderBIC: 'TESTBIC1',
receiverAccount: 'ACC002',
receiverBIC: 'TESTBIC2',
beneficiaryName: 'E2E Test Beneficiary',
purpose: 'E2E test payment',
};
const initiateResponse = await request(app)
.post('/api/v1/payments')
.set('Authorization', `Bearer ${makerToken}`)
.send(paymentRequest)
.expect(201);
expect(initiateResponse.body.paymentId).toBeDefined();
expect(initiateResponse.body.status).toBe(PaymentStatus.PENDING_APPROVAL);
const paymentId = initiateResponse.body.paymentId;
// Step 2: Checker approves payment
const approveResponse = await request(app)
.post(`/api/v1/payments/${paymentId}/approve`)
.set('Authorization', `Bearer ${checkerToken}`)
.expect(200);
expect(approveResponse.body.message).toContain('approved');
// Step 3: Verify payment status updated
// Note: Processing happens asynchronously, so we check status
const statusResponse = await request(app)
.get(`/api/v1/payments/${paymentId}`)
.set('Authorization', `Bearer ${makerToken}`)
.expect(200);
expect(statusResponse.body.paymentId).toBe(paymentId);
expect(statusResponse.body.status).toBeDefined();
});
it('should reject payment when checker rejects', async () => {
const paymentRequest: PaymentRequest = {
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
amount: 2000,
currency: Currency.USD,
senderAccount: 'ACC003',
senderBIC: 'TESTBIC3',
receiverAccount: 'ACC004',
receiverBIC: 'TESTBIC4',
beneficiaryName: 'Test Beneficiary Reject',
};
const initiateResponse = await request(app)
.post('/api/v1/payments')
.set('Authorization', `Bearer ${makerToken}`)
.send(paymentRequest)
.expect(201);
const paymentId = initiateResponse.body.paymentId;
// Checker rejects payment
const rejectResponse = await request(app)
.post(`/api/v1/payments/${paymentId}/reject`)
.set('Authorization', `Bearer ${checkerToken}`)
.send({ reason: 'E2E test rejection' })
.expect(200);
expect(rejectResponse.body.message).toContain('rejected');
// Verify status
const statusResponse = await request(app)
.get(`/api/v1/payments/${paymentId}`)
.set('Authorization', `Bearer ${makerToken}`)
.expect(200);
expect(['REJECTED', 'CANCELLED']).toContain(statusResponse.body.status);
});
it('should enforce dual control - maker cannot approve', async () => {
const paymentRequest: PaymentRequest = {
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
amount: 3000,
currency: Currency.EUR,
senderAccount: 'ACC005',
senderBIC: 'TESTBIC5',
receiverAccount: 'ACC006',
receiverBIC: 'TESTBIC6',
beneficiaryName: 'Test Dual Control',
};
const initiateResponse = await request(app)
.post('/api/v1/payments')
.set('Authorization', `Bearer ${makerToken}`)
.send(paymentRequest)
.expect(201);
const paymentId = initiateResponse.body.paymentId;
// Maker tries to approve - should fail
await request(app)
.post(`/api/v1/payments/${paymentId}/approve`)
.set('Authorization', `Bearer ${makerToken}`)
.expect(403); // Forbidden
});
it('should allow maker to cancel before approval', async () => {
const paymentRequest: PaymentRequest = {
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
amount: 4000,
currency: Currency.GBP,
senderAccount: 'ACC007',
senderBIC: 'TESTBIC7',
receiverAccount: 'ACC008',
receiverBIC: 'TESTBIC8',
beneficiaryName: 'Test Cancellation',
};
const initiateResponse = await request(app)
.post('/api/v1/payments')
.set('Authorization', `Bearer ${makerToken}`)
.send(paymentRequest)
.expect(201);
const paymentId = initiateResponse.body.paymentId;
// Maker cancels payment
const cancelResponse = await request(app)
.post(`/api/v1/payments/${paymentId}/cancel`)
.set('Authorization', `Bearer ${makerToken}`)
.send({ reason: 'E2E test cancellation' })
.expect(200);
expect(cancelResponse.body.message).toContain('cancelled');
});
});
describe('Payment Listing', () => {
it('should list payments with pagination', async () => {
// Create multiple payments
for (let i = 0; i < 3; i++) {
const paymentRequest: PaymentRequest = {
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
amount: 1000 + i * 100,
currency: Currency.USD,
senderAccount: `ACC${i}`,
senderBIC: `TESTBIC${i}`,
receiverAccount: `ACCR${i}`,
receiverBIC: `TESTBICR${i}`,
beneficiaryName: `Beneficiary ${i}`,
};
await request(app)
.post('/api/v1/payments')
.set('Authorization', `Bearer ${makerToken}`)
.send(paymentRequest)
.expect(201);
}
const listResponse = await request(app)
.get('/api/v1/payments?limit=2&offset=0')
.set('Authorization', `Bearer ${makerToken}`)
.expect(200);
expect(listResponse.body.payments).toBeDefined();
expect(listResponse.body.payments.length).toBeLessThanOrEqual(2);
});
});
});