407 lines
13 KiB
TypeScript
407 lines
13 KiB
TypeScript
|
|
/**
|
||
|
|
* 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 };
|