/** * Comprehensive Frontend Flow Test * Tests all possible actions from login through all features * Usage: ts-node -r tsconfig-paths/register scripts/test-frontend-flow.ts */ const API_BASE = 'http://localhost:3000/api/v1'; let authToken: string = ''; let operator: any = null; let createdPaymentId: string = ''; // Colors for terminal output const colors = { reset: '\x1b[0m', green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', }; function log(message: string, color: string = colors.reset) { console.log(`${color}${message}${colors.reset}`); } function logSuccess(message: string) { log(`✓ ${message}`, colors.green); } function logError(message: string) { log(`✗ ${message}`, colors.red); } function logInfo(message: string) { log(`→ ${message}`, colors.cyan); } async function makeRequest(method: string, endpoint: string, body?: any, requireAuth: boolean = true): Promise<{ response?: Response; data?: any; error?: string; ok: boolean }> { const headers: any = { 'Content-Type': 'application/json' }; if (requireAuth && authToken) { headers['Authorization'] = `Bearer ${authToken}`; } const options: any = { method, headers }; if (body) { options.body = JSON.stringify(body); } try { const response = await fetch(`${API_BASE}${endpoint}`, options); const data = await response.json(); return { response, data, ok: response.ok }; } catch (error: any) { return { error: error.message, ok: false }; } } async function testLogin() { log('\n=== TEST 1: LOGIN ===', colors.blue); logInfo('Attempting login with ADMIN001/admin123...'); const result = await makeRequest('POST', '/auth/login', { operatorId: 'ADMIN001', password: 'admin123', }, false); if (result.ok && result.data.token) { authToken = result.data.token; operator = result.data.operator; logSuccess(`Login successful - Operator: ${operator.operatorId} (${operator.name}) - Role: ${operator.role}`); return true; } else { logError(`Login failed: ${result.data?.error || result.error}`); return false; } } async function testGetMe() { log('\n=== TEST 2: GET CURRENT OPERATOR INFO ===', colors.blue); logInfo('Fetching current operator information...'); const result = await makeRequest('GET', '/auth/me'); if (result.ok && result.data.operatorId) { logSuccess(`Retrieved operator info: ${result.data.operatorId} (${result.data.role})`); return true; } else { logError(`Failed to get operator info: ${result.data?.error || result.error}`); return false; } } async function testCheckAccountBalance() { log('\n=== TEST 3: CHECK ACCOUNT BALANCE ===', colors.blue); logInfo('Checking balance for account US64000000000000000000001 (EUR)...'); const result = await makeRequest('GET', '/accounts/US64000000000000000000001/balance?currency=EUR'); if (result.ok && result.data.totalBalance !== undefined) { logSuccess(`Account balance retrieved successfully`); logInfo(` Total Balance: ${parseFloat(result.data.totalBalance).toLocaleString()} ${result.data.currency}`); logInfo(` Available: ${parseFloat(result.data.availableBalance).toLocaleString()} ${result.data.currency}`); logInfo(` Reserved: ${parseFloat(result.data.reservedBalance).toLocaleString()} ${result.data.currency}`); return true; } else { logError(`Failed to get balance: ${result.data?.error || result.error}`); return false; } } async function testListMessageTemplates() { log('\n=== TEST 4: LIST MESSAGE TEMPLATES ===', colors.blue); logInfo('Fetching available message templates...'); const result = await makeRequest('GET', '/message-templates'); if (result.ok && Array.isArray(result.data.templates)) { logSuccess(`Found ${result.data.templates.length} template(s)`); result.data.templates.forEach((template: string) => { logInfo(` - ${template}`); }); return true; } else { logError(`Failed to list templates: ${result.data?.error || result.error}`); return false; } } async function testLoadMessageTemplate() { log('\n=== TEST 5: LOAD MESSAGE TEMPLATE ===', colors.blue); logInfo('Loading pacs008-template-a.xml template...'); const result = await makeRequest('POST', '/message-templates/pacs008-template-a.xml', {}); if (result.ok && result.data.message) { logSuccess('Template loaded successfully'); logInfo(` Message ID: ${result.data.message.msgId}`); logInfo(` UETR: ${result.data.message.uetr}`); logInfo(` XML length: ${result.data.message.xml.length} bytes`); return true; } else { logError(`Failed to load template: ${result.data?.error || result.error}`); return false; } } async function testCreatePayment() { log('\n=== TEST 6: CREATE PAYMENT ===', colors.blue); logInfo('Creating a test payment...'); const paymentData = { type: 'CUSTOMER_CREDIT_TRANSFER', amount: 1000.00, currency: 'EUR', senderAccount: 'US64000000000000000000001', senderBIC: 'DFCUUGKA', receiverAccount: '02650010158937', receiverBIC: 'DFCUUGKA', beneficiaryName: 'Test Beneficiary', purpose: 'Test Payment', remittanceInfo: 'Test remittance information', }; const result = await makeRequest('POST', '/payments', paymentData); if (result.ok && result.data.paymentId) { createdPaymentId = result.data.paymentId; logSuccess(`Payment created successfully`); logInfo(` Payment ID: ${createdPaymentId}`); logInfo(` Status: ${result.data.status}`); return true; } else { const errorMsg = result.data?.error || result.data?.details || JSON.stringify(result.data) || result.error || 'Unknown error'; logError(`Failed to create payment: ${errorMsg}`); if (result.data?.details) { logInfo(` Details: ${JSON.stringify(result.data.details)}`); } return false; } } async function testGetPaymentStatus() { if (!createdPaymentId) { log('\n=== TEST 7: GET PAYMENT STATUS ===', colors.yellow); logInfo('Skipping - No payment ID available'); return false; } log('\n=== TEST 7: GET PAYMENT STATUS ===', colors.blue); logInfo(`Fetching status for payment ${createdPaymentId}...`); const result = await makeRequest('GET', `/payments/${createdPaymentId}`); if (result.ok && result.data.paymentId) { logSuccess('Payment status retrieved successfully'); logInfo(` Payment ID: ${result.data.paymentId}`); logInfo(` Status: ${result.data.status}`); logInfo(` Amount: ${result.data.amount} ${result.data.currency}`); logInfo(` UETR: ${result.data.uetr || 'Not yet generated'}`); return true; } else { logError(`Failed to get payment status: ${result.data?.error || result.error}`); return false; } } async function testListPayments() { log('\n=== TEST 8: LIST PAYMENTS ===', colors.blue); logInfo('Fetching list of payments...'); const result = await makeRequest('GET', '/payments?limit=10&offset=0'); if (result.ok && Array.isArray(result.data.payments)) { logSuccess(`Retrieved ${result.data.payments.length} payment(s)`); result.data.payments.slice(0, 3).forEach((payment: any) => { logInfo(` - ${payment.payment_id}: ${payment.amount} ${payment.currency} (${payment.status})`); }); return true; } else { logError(`Failed to list payments: ${result.data?.error || result.error}`); return false; } } async function testApprovePayment() { if (!createdPaymentId) { log('\n=== TEST 9: APPROVE PAYMENT ===', colors.yellow); logInfo('Skipping - No payment ID available'); return false; } log('\n=== TEST 9: APPROVE PAYMENT ===', colors.blue); logInfo(`Approving payment ${createdPaymentId}...`); // Note: This requires CHECKER role, but we're logged in as ADMIN which should work const result = await makeRequest('POST', `/payments/${createdPaymentId}/approve`); if (result.ok) { logSuccess('Payment approved successfully'); logInfo(` Message: ${result.data.message}`); return true; } else { // This might fail if payment is already approved or requires checker role logError(`Failed to approve payment: ${result.data?.error || result.error}`); return false; } } async function testGetPaymentStatusAfterApproval() { if (!createdPaymentId) { return false; } log('\n=== TEST 10: GET PAYMENT STATUS (AFTER APPROVAL) ===', colors.blue); logInfo(`Checking payment status after approval...`); const result = await makeRequest('GET', `/payments/${createdPaymentId}`); if (result.ok && result.data.paymentId) { logSuccess('Payment status retrieved'); logInfo(` Status: ${result.data.status}`); logInfo(` UETR: ${result.data.uetr || 'Not yet generated'}`); return true; } else { logError(`Failed to get payment status: ${result.data?.error || result.error}`); return false; } } async function testMessageTemplateSend() { log('\n=== TEST 11: SEND MESSAGE TEMPLATE ===', colors.blue); logInfo('Sending pacs008-template-a.xml template...'); const result = await makeRequest('POST', '/message-templates/pacs008-template-a.xml/send', {}); if (result.ok) { logSuccess('Template message sent successfully'); logInfo(` Message ID: ${result.data.messageDetails.msgId}`); logInfo(` UETR: ${result.data.messageDetails.uetr}`); return true; } else { logError(`Failed to send template: ${result.data?.error || result.error}`); return false; } } async function testLogout() { log('\n=== TEST 12: LOGOUT ===', colors.blue); logInfo('Logging out...'); const result = await makeRequest('POST', '/auth/logout'); if (result.ok) { logSuccess('Logout successful'); authToken = ''; operator = null; return true; } else { logError(`Logout failed: ${result.data?.error || result.error}`); return false; } } async function testProtectedEndpointAfterLogout() { log('\n=== TEST 13: TEST PROTECTED ENDPOINT AFTER LOGOUT ===', colors.blue); logInfo('Attempting to access protected endpoint without token...'); const result = await makeRequest('GET', '/auth/me'); if (!result.ok && (result.data?.error || result.error)) { logSuccess('Correctly rejected request without valid token'); logInfo(` Error: ${result.data?.error || result.error}`); return true; } else { logError('Security issue: Should have rejected request'); return false; } } async function runAllTests() { log('\n' + '='.repeat(60), colors.cyan); log('COMPREHENSIVE FRONTEND FLOW TEST', colors.cyan); log('='.repeat(60), colors.cyan); const results: { test: string; passed: boolean }[] = []; // Test 1: Login results.push({ test: 'Login', passed: await testLogin() }); if (!results[0].passed) { log('\n❌ Login failed. Cannot continue with other tests.', colors.red); return; } // Test 2: Get current operator results.push({ test: 'Get Operator Info', passed: await testGetMe() }); // Test 3: Check account balance results.push({ test: 'Check Account Balance', passed: await testCheckAccountBalance() }); // Test 4: List message templates results.push({ test: 'List Message Templates', passed: await testListMessageTemplates() }); // Test 5: Load message template results.push({ test: 'Load Message Template', passed: await testLoadMessageTemplate() }); // Test 6: Create payment results.push({ test: 'Create Payment', passed: await testCreatePayment() }); // Test 7: Get payment status results.push({ test: 'Get Payment Status', passed: await testGetPaymentStatus() }); // Test 8: List payments results.push({ test: 'List Payments', passed: await testListPayments() }); // Test 9: Approve payment results.push({ test: 'Approve Payment', passed: await testApprovePayment() }); // Test 10: Get payment status after approval results.push({ test: 'Get Payment Status (After Approval)', passed: await testGetPaymentStatusAfterApproval() }); // Test 11: Send message template results.push({ test: 'Send Message Template', passed: await testMessageTemplateSend() }); // Test 12: Logout results.push({ test: 'Logout', passed: await testLogout() }); // Test 13: Test protected endpoint after logout results.push({ test: 'Protected Endpoint After Logout', passed: await testProtectedEndpointAfterLogout() }); // Print summary log('\n' + '='.repeat(60), colors.cyan); log('TEST SUMMARY', colors.cyan); log('='.repeat(60), colors.cyan); const passed = results.filter(r => r.passed).length; const total = results.length; results.forEach((result, index) => { const status = result.passed ? '✓' : '✗'; const color = result.passed ? colors.green : colors.red; log(`${status} Test ${index + 1}: ${result.test}`, color); }); log('\n' + '='.repeat(60), colors.cyan); log(`Total: ${passed}/${total} tests passed`, passed === total ? colors.green : colors.yellow); log('='.repeat(60) + '\n', colors.cyan); process.exit(passed === total ? 0 : 1); } // Run tests if (require.main === module) { // Check if fetch is available (Node.js 18+) if (typeof fetch === 'undefined') { console.error('Error: fetch is not available. Please use Node.js 18+ or install node-fetch'); process.exit(1); } runAllTests().catch((error) => { logError(`Test suite failed: ${error.message}`); console.error(error); process.exit(1); }); } export { runAllTests };