Initial commit: add .gitignore and README

This commit is contained in:
defiQUG
2026-02-09 21:51:45 -08:00
commit 929fe6f6b6
240 changed files with 40977 additions and 0 deletions

View File

@@ -0,0 +1,299 @@
/**
* Performance Tests for Export Functionality
*
* Tests for large batch exports, file size limits, and concurrent requests
*/
import { ExportService } from '@/exports/export-service';
import { MessageRepository } from '@/repositories/message-repository';
import { PaymentRepository } from '@/repositories/payment-repository';
import { TestHelpers } from '../../utils/test-helpers';
import { ExportFormat, ExportScope } from '@/exports/types';
import { PaymentStatus } from '@/models/payment';
import { MessageType, MessageStatus } from '@/models/message';
import { v4 as uuidv4 } from 'uuid';
import { query } from '@/database/connection';
describe('Export Performance Tests', () => {
let exportService: ExportService;
let messageRepository: MessageRepository;
let paymentRepository: PaymentRepository;
beforeAll(async () => {
messageRepository = new MessageRepository();
paymentRepository = new PaymentRepository();
exportService = new ExportService(messageRepository);
await TestHelpers.cleanDatabase();
}, 30000);
beforeEach(async () => {
await TestHelpers.cleanDatabase();
});
afterAll(async () => {
await TestHelpers.cleanDatabase();
}, 10000);
describe('Large Batch Export', () => {
it('should handle export of 100 messages efficiently', async () => {
const operator = await TestHelpers.createTestOperator('TEST_PERF_100', 'CHECKER' as any);
const paymentIds: string[] = [];
// Create 100 payments with messages
const startTime = Date.now();
for (let i = 0; i < 100; i++) {
const paymentRequest = TestHelpers.createTestPaymentRequest();
const paymentId = await paymentRepository.create(
paymentRequest,
operator.id,
`TEST-PERF-100-${i}`
);
const uetr = uuidv4();
await paymentRepository.update(paymentId, {
uetr,
status: PaymentStatus.LEDGER_POSTED,
});
const messageId = uuidv4();
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10">
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>MSG-PERF-${i}</MsgId>
<CreDtTm>${new Date().toISOString()}</CreDtTm>
</GrpHdr>
<CdtTrfTxInf>
<PmtId>
<UETR>${uetr}</UETR>
</PmtId>
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>`;
await messageRepository.create({
id: messageId,
messageId: messageId,
paymentId,
messageType: MessageType.PACS_008,
uetr,
msgId: `MSG-PERF-${i}`,
xmlContent,
xmlHash: 'test-hash',
status: MessageStatus.VALIDATED,
});
paymentIds.push(paymentId);
}
const setupTime = Date.now() - startTime;
console.log(`Setup time for 100 messages: ${setupTime}ms`);
// Export batch
const exportStartTime = Date.now();
const result = await exportService.exportMessages({
format: ExportFormat.RAW_ISO,
scope: ExportScope.MESSAGES,
batch: true,
});
const exportTime = Date.now() - exportStartTime;
console.log(`Export time for 100 messages: ${exportTime}ms`);
expect(result.recordCount).toBe(100);
expect(exportTime).toBeLessThan(10000); // Should complete in under 10 seconds
}, 60000);
it('should enforce batch size limit', async () => {
// This test verifies that the system properly rejects exports exceeding maxBatchSize
// Note: maxBatchSize is 10000, so we'd need to create that many messages
// For performance, we'll test the validation logic instead
const result = await exportService.exportMessages({
format: ExportFormat.RAW_ISO,
scope: ExportScope.MESSAGES,
batch: true,
});
// If we have messages, verify they're within limit
if (result.recordCount > 0) {
expect(result.recordCount).toBeLessThanOrEqual(10000);
}
});
});
describe('File Size Limits', () => {
it('should handle exports within file size limits', async () => {
const operator = await TestHelpers.createTestOperator('TEST_SIZE', 'CHECKER' as any);
// Create a payment with a message
const paymentRequest = TestHelpers.createTestPaymentRequest();
const paymentId = await paymentRepository.create(
paymentRequest,
operator.id,
`TEST-SIZE-${Date.now()}`
);
const uetr = uuidv4();
await paymentRepository.update(paymentId, {
uetr,
status: PaymentStatus.LEDGER_POSTED,
});
const messageId = uuidv4();
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10">
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>MSG-SIZE</MsgId>
<CreDtTm>${new Date().toISOString()}</CreDtTm>
</GrpHdr>
<CdtTrfTxInf>
<PmtId>
<UETR>${uetr}</UETR>
</PmtId>
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>`;
await messageRepository.create({
id: messageId,
messageId: messageId,
paymentId,
messageType: MessageType.PACS_008,
uetr,
msgId: 'MSG-SIZE',
xmlContent,
xmlHash: 'test-hash',
status: MessageStatus.VALIDATED,
});
const result = await exportService.exportMessages({
format: ExportFormat.RAW_ISO,
scope: ExportScope.MESSAGES,
batch: false,
});
const fileSize = Buffer.byteLength(result.content as string, 'utf8');
expect(fileSize).toBeLessThan(100 * 1024 * 1024); // 100 MB limit
});
});
describe('Concurrent Export Requests', () => {
it('should handle multiple concurrent export requests', async () => {
const operator = await TestHelpers.createTestOperator('TEST_CONCURRENT', 'CHECKER' as any);
// Create test data
const paymentRequest = TestHelpers.createTestPaymentRequest();
const paymentId = await paymentRepository.create(
paymentRequest,
operator.id,
`TEST-CONCURRENT-${Date.now()}`
);
const uetr = uuidv4();
await paymentRepository.update(paymentId, {
uetr,
status: PaymentStatus.LEDGER_POSTED,
});
const messageId = uuidv4();
const xmlContent = `<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pacs.008.001.10">
<FIToFICstmrCdtTrf>
<GrpHdr>
<MsgId>MSG-CONCURRENT</MsgId>
<CreDtTm>${new Date().toISOString()}</CreDtTm>
</GrpHdr>
<CdtTrfTxInf>
<PmtId>
<UETR>${uetr}</UETR>
</PmtId>
</CdtTrfTxInf>
</FIToFICstmrCdtTrf>
</Document>`;
await messageRepository.create({
id: messageId,
messageId: messageId,
paymentId,
messageType: MessageType.PACS_008,
uetr,
msgId: 'MSG-CONCURRENT',
xmlContent,
xmlHash: 'test-hash',
status: MessageStatus.VALIDATED,
});
// Make 5 concurrent export requests
const exportPromises = Array.from({ length: 5 }, () =>
exportService.exportMessages({
format: ExportFormat.RAW_ISO,
scope: ExportScope.MESSAGES,
batch: false,
})
);
const startTime = Date.now();
const results = await Promise.all(exportPromises);
const duration = Date.now() - startTime;
// All should succeed
results.forEach((result) => {
expect(result).toBeDefined();
expect(result.exportId).toBeDefined();
});
// Should complete reasonably quickly even with concurrency
expect(duration).toBeLessThan(10000); // 10 seconds
}, 30000);
});
describe('Export History Performance', () => {
it('should record export history efficiently', async () => {
const operator = await TestHelpers.createTestOperator('TEST_HIST', 'CHECKER' as any);
const paymentRequest = TestHelpers.createTestPaymentRequest();
const paymentId = await paymentRepository.create(
paymentRequest,
operator.id,
`TEST-HIST-${Date.now()}`
);
const uetr = uuidv4();
await paymentRepository.update(paymentId, {
uetr,
status: PaymentStatus.LEDGER_POSTED,
});
const messageId = uuidv4();
await messageRepository.create({
id: messageId,
messageId: messageId,
paymentId,
messageType: MessageType.PACS_008,
uetr,
msgId: 'MSG-HIST',
xmlContent: '<?xml version="1.0"?><Document></Document>',
xmlHash: 'test-hash',
status: MessageStatus.VALIDATED,
});
// Perform export
const result = await exportService.exportMessages({
format: ExportFormat.RAW_ISO,
scope: ExportScope.MESSAGES,
batch: false,
});
// Verify history was recorded
const historyResult = await query(
'SELECT * FROM export_history WHERE id = $1',
[result.exportId]
);
expect(historyResult.rows.length).toBe(1);
expect(historyResult.rows[0].format).toBe('raw-iso');
});
});
});

View File

@@ -0,0 +1,201 @@
/**
* Performance and Load Tests
* Tests system behavior under load
*/
import { TLSClient } from '@/transport/tls-client/tls-client';
import { LengthPrefixFramer } from '@/transport/framing/length-prefix';
import { readFileSync } from 'fs';
import { join } from 'path';
import { v4 as uuidv4 } from 'uuid';
describe('Performance and Load Tests', () => {
const pacs008Template = readFileSync(
join(__dirname, '../../../docs/examples/pacs008-template-a.xml'),
'utf-8'
);
describe('Connection Performance', () => {
it('should establish connection within acceptable time', async () => {
const tlsClient = new TLSClient();
const startTime = Date.now();
try {
const connection = await tlsClient.connect();
const duration = Date.now() - startTime;
expect(connection.connected).toBe(true);
expect(duration).toBeLessThan(10000); // Should connect within 10 seconds
} finally {
await tlsClient.close();
}
}, 15000);
it('should handle multiple sequential connections efficiently', async () => {
const connections: TLSClient[] = [];
const startTime = Date.now();
try {
for (let i = 0; i < 5; i++) {
const client = new TLSClient();
await client.connect();
connections.push(client);
}
const duration = Date.now() - startTime;
const avgTime = duration / 5;
expect(avgTime).toBeLessThan(5000); // Average should be under 5 seconds
} finally {
for (const client of connections) {
await client.close();
}
}
}, 60000);
});
describe('Message Framing Performance', () => {
it('should frame messages quickly', () => {
const message = Buffer.from(pacs008Template, 'utf-8');
const iterations = 1000;
const startTime = Date.now();
for (let i = 0; i < iterations; i++) {
LengthPrefixFramer.frame(message);
}
const duration = Date.now() - startTime;
const opsPerSecond = (iterations / duration) * 1000;
expect(opsPerSecond).toBeGreaterThan(10000); // Should handle 10k+ ops/sec
});
it('should unframe messages quickly', () => {
const message = Buffer.from(pacs008Template, 'utf-8');
const framed = LengthPrefixFramer.frame(message);
const iterations = 1000;
const startTime = Date.now();
for (let i = 0; i < iterations; i++) {
LengthPrefixFramer.unframe(framed);
}
const duration = Date.now() - startTime;
const opsPerSecond = (iterations / duration) * 1000;
expect(opsPerSecond).toBeGreaterThan(10000);
});
it('should handle large messages efficiently', () => {
const largeMessage = Buffer.alloc(1024 * 1024); // 1MB
largeMessage.fill('A');
const startTime = Date.now();
const framed = LengthPrefixFramer.frame(largeMessage);
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(100); // Should frame 1MB in under 100ms
expect(framed.length).toBe(4 + largeMessage.length);
});
});
describe('Concurrent Operations', () => {
it('should handle concurrent message transmissions', async () => {
const client = new TLSClient();
const messageCount = 10;
const startTime = Date.now();
try {
await client.connect();
const promises = Array.from({ length: messageCount }, async () => {
const messageId = uuidv4();
const paymentId = uuidv4();
const uetr = uuidv4();
const xmlContent = pacs008Template.replace(
'03BD66B4-6C81-48DB-B3D8-F5E5E0DC809A',
uetr
);
try {
await client.sendMessage(messageId, paymentId, uetr, xmlContent);
} catch (error) {
// Expected if receiver unavailable
}
});
await Promise.allSettled(promises);
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(60000); // Should complete within 60 seconds
} finally {
await client.close();
}
}, 120000);
});
describe('Memory Usage', () => {
it('should not leak memory with repeated connections', async () => {
const initialMemory = process.memoryUsage().heapUsed;
for (let i = 0; i < 10; i++) {
const client = new TLSClient();
try {
await client.connect();
await new Promise((resolve) => setTimeout(resolve, 100));
} catch (error) {
// Ignore connection errors
} finally {
await client.close();
}
}
// Force garbage collection if available
if (global.gc) {
global.gc();
}
await new Promise((resolve) => setTimeout(resolve, 1000));
const finalMemory = process.memoryUsage().heapUsed;
const memoryIncrease = finalMemory - initialMemory;
const memoryIncreaseMB = memoryIncrease / (1024 * 1024);
// Memory increase should be reasonable (less than 50MB for 10 connections)
expect(memoryIncreaseMB).toBeLessThan(50);
}, 60000);
});
describe('Throughput', () => {
it('should measure message throughput', async () => {
const client = new TLSClient();
const messageCount = 100;
const startTime = Date.now();
try {
await client.connect();
for (let i = 0; i < messageCount; i++) {
const messageId = uuidv4();
const paymentId = uuidv4();
const uetr = uuidv4();
const xmlContent = pacs008Template;
try {
await client.sendMessage(messageId, paymentId, uetr, xmlContent);
} catch (error) {
// Expected if receiver unavailable
}
}
const duration = Date.now() - startTime;
const messagesPerSecond = (messageCount / duration) * 1000;
// Should handle at least 1 message per second
expect(messagesPerSecond).toBeGreaterThan(1);
} finally {
await client.close();
}
}, 120000);
});
});