Initial commit: add .gitignore and README
This commit is contained in:
124
tests/unit/services/ledger-service.test.ts
Normal file
124
tests/unit/services/ledger-service.test.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { LedgerService } from '@/ledger/transactions/ledger-service';
|
||||
import { PaymentRepository } from '@/repositories/payment-repository';
|
||||
import { PaymentTransaction } from '@/models/payment';
|
||||
import { MockLedgerAdapter } from '@/ledger/mock/mock-ledger-adapter';
|
||||
import { TestHelpers } from '../../utils/test-helpers';
|
||||
import { LedgerAdapter } from '@/ledger/adapter/types';
|
||||
|
||||
describe('LedgerService', () => {
|
||||
let ledgerService: LedgerService;
|
||||
let paymentRepository: PaymentRepository;
|
||||
let mockAdapter: LedgerAdapter;
|
||||
let testPayment: PaymentTransaction;
|
||||
|
||||
beforeAll(async () => {
|
||||
paymentRepository = new PaymentRepository();
|
||||
mockAdapter = new MockLedgerAdapter();
|
||||
ledgerService = new LedgerService(paymentRepository, mockAdapter);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestHelpers.cleanDatabase();
|
||||
|
||||
const operator = await TestHelpers.createTestOperator('TEST_LEDGER', 'MAKER' as any);
|
||||
const paymentRequest = TestHelpers.createTestPaymentRequest();
|
||||
const paymentId = await paymentRepository.create(
|
||||
paymentRequest,
|
||||
operator.id,
|
||||
`TEST-LEDGER-${Date.now()}`
|
||||
);
|
||||
|
||||
const payment = await paymentRepository.findById(paymentId);
|
||||
if (!payment) {
|
||||
throw new Error('Failed to create test payment');
|
||||
}
|
||||
testPayment = payment;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await TestHelpers.cleanDatabase();
|
||||
});
|
||||
|
||||
describe('debitAndReserve', () => {
|
||||
it('should debit and reserve funds for payment', async () => {
|
||||
const transactionId = await ledgerService.debitAndReserve(testPayment);
|
||||
|
||||
expect(transactionId).toBeDefined();
|
||||
expect(typeof transactionId).toBe('string');
|
||||
|
||||
const updatedPayment = await paymentRepository.findById(testPayment.id);
|
||||
expect(updatedPayment?.internalTransactionId).toBe(transactionId);
|
||||
});
|
||||
|
||||
it('should return existing transaction ID if already posted', async () => {
|
||||
// First reservation
|
||||
const transactionId1 = await ledgerService.debitAndReserve(testPayment);
|
||||
|
||||
// Second attempt should return same transaction ID
|
||||
const paymentWithTxn = await paymentRepository.findById(testPayment.id);
|
||||
const transactionId2 = await ledgerService.debitAndReserve(paymentWithTxn!);
|
||||
|
||||
expect(transactionId2).toBe(transactionId1);
|
||||
});
|
||||
|
||||
it('should fail if insufficient funds', async () => {
|
||||
const largePayment: PaymentTransaction = {
|
||||
...testPayment,
|
||||
amount: 10000000, // Very large amount
|
||||
};
|
||||
|
||||
// Mock adapter should throw error for insufficient funds
|
||||
await expect(
|
||||
ledgerService.debitAndReserve(largePayment)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should update payment status after reservation', async () => {
|
||||
await ledgerService.debitAndReserve(testPayment);
|
||||
|
||||
const updatedPayment = await paymentRepository.findById(testPayment.id);
|
||||
expect(updatedPayment?.internalTransactionId).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('releaseReserve', () => {
|
||||
it('should release reserved funds', async () => {
|
||||
// First reserve funds
|
||||
await ledgerService.debitAndReserve(testPayment);
|
||||
|
||||
// Then release
|
||||
await ledgerService.releaseReserve(testPayment.id);
|
||||
|
||||
// Should complete without error
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle payment without transaction ID gracefully', async () => {
|
||||
// Payment without internal transaction ID
|
||||
await expect(
|
||||
ledgerService.releaseReserve(testPayment.id)
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should fail if payment not found', async () => {
|
||||
await expect(
|
||||
ledgerService.releaseReserve('non-existent-payment-id')
|
||||
).rejects.toThrow('Payment not found');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTransaction', () => {
|
||||
it('should retrieve transaction by ID', async () => {
|
||||
const transactionId = await ledgerService.debitAndReserve(testPayment);
|
||||
const transaction = await ledgerService.getTransaction(transactionId);
|
||||
|
||||
expect(transaction).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return null for non-existent transaction', async () => {
|
||||
const transaction = await ledgerService.getTransaction('non-existent-txn-id');
|
||||
expect(transaction).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
177
tests/unit/services/message-service.test.ts
Normal file
177
tests/unit/services/message-service.test.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { MessageService } from '@/messaging/message-service';
|
||||
import { MessageRepository } from '@/repositories/message-repository';
|
||||
import { PaymentRepository } from '@/repositories/payment-repository';
|
||||
import { PaymentTransaction, PaymentType, PaymentStatus } from '@/models/payment';
|
||||
import { MessageType } from '@/models/message';
|
||||
import { TestHelpers } from '../../utils/test-helpers';
|
||||
|
||||
describe('MessageService', () => {
|
||||
let messageService: MessageService;
|
||||
let messageRepository: MessageRepository;
|
||||
let paymentRepository: PaymentRepository;
|
||||
let testPayment: PaymentTransaction;
|
||||
|
||||
beforeAll(async () => {
|
||||
messageRepository = new MessageRepository();
|
||||
paymentRepository = new PaymentRepository();
|
||||
messageService = new MessageService(messageRepository, paymentRepository);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestHelpers.cleanDatabase();
|
||||
|
||||
// Create test payment with ledger transaction ID
|
||||
const operator = await TestHelpers.createTestOperator('TEST_MSG_SVC', 'MAKER' as any);
|
||||
const paymentRequest = TestHelpers.createTestPaymentRequest();
|
||||
const paymentId = await paymentRepository.create(
|
||||
paymentRequest,
|
||||
operator.id,
|
||||
`TEST-MSG-${Date.now()}`
|
||||
);
|
||||
|
||||
const payment = await paymentRepository.findById(paymentId);
|
||||
if (!payment) {
|
||||
throw new Error('Failed to create test payment');
|
||||
}
|
||||
|
||||
// Update payment with internal transaction ID (required for message generation)
|
||||
await paymentRepository.update(paymentId, {
|
||||
internalTransactionId: 'test-txn-123',
|
||||
status: PaymentStatus.LEDGER_POSTED,
|
||||
});
|
||||
|
||||
testPayment = (await paymentRepository.findById(paymentId))!;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await TestHelpers.cleanDatabase();
|
||||
});
|
||||
|
||||
describe('generateMessage', () => {
|
||||
it('should generate PACS.008 message for CUSTOMER_CREDIT_TRANSFER', async () => {
|
||||
const payment: PaymentTransaction = {
|
||||
...testPayment,
|
||||
type: PaymentType.CUSTOMER_CREDIT_TRANSFER,
|
||||
internalTransactionId: 'test-txn-001',
|
||||
};
|
||||
|
||||
const result = await messageService.generateMessage(payment);
|
||||
|
||||
expect(result.messageId).toBeDefined();
|
||||
expect(result.uetr).toBeDefined();
|
||||
expect(result.msgId).toBeDefined();
|
||||
expect(result.xml).toBeDefined();
|
||||
expect(result.hash).toBeDefined();
|
||||
expect(result.xml).toContain('pacs.008');
|
||||
expect(result.xml).toContain('FIToFICstmrCdtTrf');
|
||||
});
|
||||
|
||||
it('should generate PACS.009 message for FI_TO_FI', async () => {
|
||||
const payment: PaymentTransaction = {
|
||||
...testPayment,
|
||||
type: PaymentType.FI_TO_FI,
|
||||
internalTransactionId: 'test-txn-002',
|
||||
};
|
||||
|
||||
const result = await messageService.generateMessage(payment);
|
||||
|
||||
expect(result.messageId).toBeDefined();
|
||||
expect(result.uetr).toBeDefined();
|
||||
expect(result.msgId).toBeDefined();
|
||||
expect(result.xml).toBeDefined();
|
||||
expect(result.xml).toContain('pacs.009');
|
||||
expect(result.xml).toContain('FICdtTrf');
|
||||
});
|
||||
|
||||
it('should fail if ledger posting not found', async () => {
|
||||
const paymentWithoutLedger: PaymentTransaction = {
|
||||
...testPayment,
|
||||
internalTransactionId: undefined,
|
||||
};
|
||||
|
||||
await expect(
|
||||
messageService.generateMessage(paymentWithoutLedger)
|
||||
).rejects.toThrow('Ledger posting not found');
|
||||
});
|
||||
|
||||
it('should store message in repository', async () => {
|
||||
const payment: PaymentTransaction = {
|
||||
...testPayment,
|
||||
internalTransactionId: 'test-txn-003',
|
||||
};
|
||||
|
||||
const result = await messageService.generateMessage(payment);
|
||||
const storedMessage = await messageRepository.findById(result.messageId);
|
||||
|
||||
expect(storedMessage).not.toBeNull();
|
||||
expect(storedMessage?.messageType).toBe(MessageType.PACS_008);
|
||||
expect(storedMessage?.uetr).toBe(result.uetr);
|
||||
expect(storedMessage?.xmlContent).toBe(result.xml);
|
||||
});
|
||||
|
||||
it('should update payment with message information', async () => {
|
||||
const payment: PaymentTransaction = {
|
||||
...testPayment,
|
||||
internalTransactionId: 'test-txn-004',
|
||||
};
|
||||
|
||||
const result = await messageService.generateMessage(payment);
|
||||
const updatedPayment = await paymentRepository.findById(payment.id);
|
||||
|
||||
expect(updatedPayment?.uetr).toBe(result.uetr);
|
||||
expect(updatedPayment?.isoMessageId).toBe(result.messageId);
|
||||
expect(updatedPayment?.isoMessageHash).toBe(result.hash);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessage', () => {
|
||||
it('should retrieve message by ID', async () => {
|
||||
const payment: PaymentTransaction = {
|
||||
...testPayment,
|
||||
internalTransactionId: 'test-txn-005',
|
||||
};
|
||||
|
||||
const generated = await messageService.generateMessage(payment);
|
||||
const retrieved = await messageService.getMessage(generated.messageId);
|
||||
|
||||
expect(retrieved).not.toBeNull();
|
||||
expect(retrieved?.id).toBe(generated.messageId);
|
||||
expect(retrieved?.xmlContent).toBe(generated.xml);
|
||||
});
|
||||
|
||||
it('should return null for non-existent message', async () => {
|
||||
const message = await messageService.getMessage('non-existent-id');
|
||||
expect(message).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessageByPaymentId', () => {
|
||||
it('should retrieve message by payment ID', async () => {
|
||||
const payment: PaymentTransaction = {
|
||||
...testPayment,
|
||||
internalTransactionId: 'test-txn-006',
|
||||
};
|
||||
|
||||
const generated = await messageService.generateMessage(payment);
|
||||
const retrieved = await messageService.getMessageByPaymentId(payment.id);
|
||||
|
||||
expect(retrieved).not.toBeNull();
|
||||
expect(retrieved?.paymentId).toBe(payment.id);
|
||||
expect(retrieved?.id).toBe(generated.messageId);
|
||||
});
|
||||
|
||||
it('should return null if no message exists for payment', async () => {
|
||||
const operator = await TestHelpers.createTestOperator('TEST_NOMSG', 'MAKER' as any);
|
||||
const paymentRequest = TestHelpers.createTestPaymentRequest();
|
||||
const paymentId = await paymentRepository.create(
|
||||
paymentRequest,
|
||||
operator.id,
|
||||
`TEST-NOMSG-${Date.now()}`
|
||||
);
|
||||
|
||||
const message = await messageService.getMessageByPaymentId(paymentId);
|
||||
expect(message).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user