Initial commit: ROA2WEB - FastAPI + Vue.js + Telegram Bot
Modern ERP Reports Application with microservices architecture Tech Stack: - Backend: FastAPI + python-oracledb (Oracle DB integration) - Frontend: Vue.js 3 + PrimeVue + Vite - Telegram Bot: python-telegram-bot + SQLite - Infrastructure: Shared database pool, JWT authentication, SSH tunnel Features: - FastAPI backend with async Oracle connection pool - Vue.js 3 responsive frontend with PrimeVue components - Telegram bot alternative interface - Microservices architecture with shared components - Complete deployment support (Linux Docker + Windows IIS) - Comprehensive testing (Playwright E2E + pytest) Repository Structure: - reports-app/ - Main application (backend, frontend, telegram-bot) - shared/ - Shared components (database pool, auth, utils) - deployment/ - Deployment scripts (Linux & Windows) - docs/ - Project documentation - security/ - Security scanning and git hooks
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* Cross-Schema Data Validation Tests
|
||||
* Validates data consistency between CONTAFIN_ORACLE authentication schema
|
||||
* and ROMFAST company data, ensuring proper Oracle data flow
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
authenticateWithRealCredentials,
|
||||
getRealCompanies,
|
||||
selectCompany,
|
||||
REAL_CREDENTIALS,
|
||||
API_ENDPOINTS
|
||||
} from '../../utils/real-auth.js';
|
||||
import {
|
||||
setupConsoleCapture,
|
||||
assertNoCriticalErrors,
|
||||
generateErrorReport
|
||||
} from '../../utils/console-monitor.js';
|
||||
|
||||
test.describe('Oracle Cross-Schema Data Consistency', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
setupConsoleCapture(page);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
const report = generateErrorReport(page, test.info().title);
|
||||
if (report.summary.classifications.critical > 0) {
|
||||
console.warn('🚨 Critical errors in data consistency test:', report.details.criticalErrors);
|
||||
}
|
||||
});
|
||||
|
||||
test('should validate CONTAFIN_ORACLE → ROMFAST data flow', async ({ page }) => {
|
||||
console.log('🔄 Testing Oracle cross-schema data flow...');
|
||||
|
||||
// Authenticate with CONTAFIN_ORACLE schema credentials
|
||||
const authResult = await authenticateWithRealCredentials(page);
|
||||
expect(authResult.success, 'CONTAFIN_ORACLE authentication failed').toBe(true);
|
||||
|
||||
console.log('✅ CONTAFIN_ORACLE authentication successful');
|
||||
|
||||
// Test companies endpoint returns ROMFAST from NOM_FIRME table
|
||||
console.log('🏢 Validating companies data from NOM_FIRME...');
|
||||
const companiesResponse = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`);
|
||||
expect(companiesResponse.status()).toBe(200);
|
||||
|
||||
const companies = await companiesResponse.json();
|
||||
expect(companies).toBeInstanceOf(Array);
|
||||
expect(companies.length).toBeGreaterThan(0);
|
||||
|
||||
console.log(`📊 Found ${companies.length} companies in NOM_FIRME table`);
|
||||
|
||||
// Validate ROMFAST company exists
|
||||
const romfast = companies.find(c => c.id_firma === 'ROMFAST');
|
||||
expect(romfast, 'ROMFAST company not found in NOM_FIRME table').toBeDefined();
|
||||
expect(romfast.name).toContain('ROMFAST');
|
||||
|
||||
console.log('✅ ROMFAST company validated in NOM_FIRME table:', romfast);
|
||||
|
||||
// Validate company data structure matches Oracle schema
|
||||
expect(romfast).toHaveProperty('id_firma');
|
||||
expect(romfast).toHaveProperty('name');
|
||||
|
||||
// Additional Oracle-specific fields that might be present
|
||||
const oracleFields = ['cui', 'reg_com', 'adresa', 'telefon', 'email'];
|
||||
oracleFields.forEach(field => {
|
||||
if (romfast.hasOwnProperty(field)) {
|
||||
console.log(`ℹ️ Oracle field '${field}' present:`, romfast[field]);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ Cross-schema authentication and company data flow validated');
|
||||
});
|
||||
|
||||
test('should validate invoice schema consistency', async ({ page }) => {
|
||||
console.log('📋 Validating invoice data schema consistency...');
|
||||
|
||||
await authenticateWithRealCredentials(page);
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
|
||||
// Get invoices data for ROMFAST
|
||||
console.log('📥 Fetching ROMFAST invoice data...');
|
||||
const invoicesResponse = await page.request.get(`${API_ENDPOINTS.backend}/api/invoices/ROMFAST`);
|
||||
expect(invoicesResponse.status()).toBe(200);
|
||||
|
||||
const invoicesData = await invoicesResponse.json();
|
||||
expect(invoicesData).toHaveProperty('data');
|
||||
expect(invoicesData.data).toBeInstanceOf(Array);
|
||||
|
||||
if (invoicesData.data.length > 0) {
|
||||
const sampleInvoice = invoicesData.data[0];
|
||||
console.log('📋 Sample invoice structure:', Object.keys(sampleInvoice));
|
||||
|
||||
// Validate Oracle-specific invoice fields are present
|
||||
const requiredOracleFields = [
|
||||
'numar_factura', // Invoice number
|
||||
'data_scadenta', // Due date
|
||||
'suma_totala' // Total amount
|
||||
];
|
||||
|
||||
requiredOracleFields.forEach(field => {
|
||||
expect(sampleInvoice, `Missing Oracle field: ${field}`).toHaveProperty(field);
|
||||
console.log(`✅ Oracle field '${field}':`, sampleInvoice[field]);
|
||||
});
|
||||
|
||||
// Validate data types
|
||||
expect(typeof sampleInvoice.numar_factura).toBe('string');
|
||||
expect(sampleInvoice.suma_totala).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Validate date format (should be ISO string or valid date)
|
||||
if (sampleInvoice.data_scadenta) {
|
||||
const date = new Date(sampleInvoice.data_scadenta);
|
||||
expect(date.toString()).not.toBe('Invalid Date');
|
||||
console.log(`✅ Date validation passed: ${sampleInvoice.data_scadenta}`);
|
||||
}
|
||||
|
||||
console.log(`✅ Invoice schema validation passed (${invoicesData.data.length} invoices)`);
|
||||
} else {
|
||||
console.log('ℹ️ No invoice data found for ROMFAST - schema validation skipped');
|
||||
}
|
||||
|
||||
// Check for console errors during data retrieval
|
||||
assertNoCriticalErrors(page, expect);
|
||||
});
|
||||
|
||||
test('should validate payment schema consistency', async ({ page }) => {
|
||||
console.log('💰 Validating payment data schema consistency...');
|
||||
|
||||
await authenticateWithRealCredentials(page);
|
||||
await selectCompany(page, REAL_CREDENTIALS.company);
|
||||
|
||||
// Get payments data for ROMFAST
|
||||
console.log('💳 Fetching ROMFAST payment data...');
|
||||
const paymentsResponse = await page.request.get(`${API_ENDPOINTS.backend}/api/payments/ROMFAST`);
|
||||
expect(paymentsResponse.status()).toBe(200);
|
||||
|
||||
const paymentsData = await paymentsResponse.json();
|
||||
expect(paymentsData).toHaveProperty('data');
|
||||
expect(paymentsData.data).toBeInstanceOf(Array);
|
||||
|
||||
if (paymentsData.data.length > 0) {
|
||||
const samplePayment = paymentsData.data[0];
|
||||
console.log('💳 Sample payment structure:', Object.keys(samplePayment));
|
||||
|
||||
// Validate Oracle-specific payment fields
|
||||
const requiredOracleFields = [
|
||||
'numar_plata', // Payment number
|
||||
'data_plata', // Payment date
|
||||
'suma_plata' // Payment amount
|
||||
];
|
||||
|
||||
requiredOracleFields.forEach(field => {
|
||||
expect(samplePayment, `Missing Oracle payment field: ${field}`).toHaveProperty(field);
|
||||
console.log(`✅ Oracle payment field '${field}':`, samplePayment[field]);
|
||||
});
|
||||
|
||||
// Validate payment data types
|
||||
expect(typeof samplePayment.numar_plata).toBe('string');
|
||||
expect(samplePayment.suma_plata).toBeGreaterThanOrEqual(0);
|
||||
|
||||
// Validate payment date
|
||||
if (samplePayment.data_plata) {
|
||||
const date = new Date(samplePayment.data_plata);
|
||||
expect(date.toString()).not.toBe('Invalid Date');
|
||||
console.log(`✅ Payment date validation passed: ${samplePayment.data_plata}`);
|
||||
}
|
||||
|
||||
console.log(`✅ Payment schema validation passed (${paymentsData.data.length} payments)`);
|
||||
} else {
|
||||
console.log('ℹ️ No payment data found for ROMFAST - schema validation skipped');
|
||||
}
|
||||
|
||||
// Check for console errors during data retrieval
|
||||
assertNoCriticalErrors(page, expect);
|
||||
});
|
||||
|
||||
test('should validate user permissions across schemas', async ({ page }) => {
|
||||
console.log('🔐 Validating user permissions across Oracle schemas...');
|
||||
|
||||
const authResult = await authenticateWithRealCredentials(page);
|
||||
expect(authResult.success).toBe(true);
|
||||
|
||||
// Test access to different endpoints with authenticated user
|
||||
const endpointsToTest = [
|
||||
{ url: '/api/companies', name: 'Companies List', expectAccess: true },
|
||||
{ url: '/api/invoices/ROMFAST', name: 'ROMFAST Invoices', expectAccess: true },
|
||||
{ url: '/api/payments/ROMFAST', name: 'ROMFAST Payments', expectAccess: true },
|
||||
{ url: '/api/user/profile', name: 'User Profile', expectAccess: true },
|
||||
{ url: '/api/admin/users', name: 'Admin Users', expectAccess: false } // Should fail
|
||||
];
|
||||
|
||||
const accessResults = [];
|
||||
|
||||
for (const endpoint of endpointsToTest) {
|
||||
console.log(`🔍 Testing access to ${endpoint.name}...`);
|
||||
|
||||
try {
|
||||
const response = await page.request.get(`${API_ENDPOINTS.backend}${endpoint.url}`);
|
||||
const hasAccess = response.status() < 400;
|
||||
|
||||
accessResults.push({
|
||||
endpoint: endpoint.name,
|
||||
url: endpoint.url,
|
||||
status: response.status(),
|
||||
hasAccess: hasAccess,
|
||||
expectAccess: endpoint.expectAccess
|
||||
});
|
||||
|
||||
if (endpoint.expectAccess) {
|
||||
expect(hasAccess, `Expected access to ${endpoint.name} but got ${response.status()}`).toBe(true);
|
||||
console.log(`✅ ${endpoint.name}: Access granted (${response.status()})`);
|
||||
} else {
|
||||
expect(hasAccess, `Expected no access to ${endpoint.name} but got ${response.status()}`).toBe(false);
|
||||
console.log(`✅ ${endpoint.name}: Access denied as expected (${response.status()})`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(`⚠️ ${endpoint.name}: Request failed - ${error.message}`);
|
||||
accessResults.push({
|
||||
endpoint: endpoint.name,
|
||||
url: endpoint.url,
|
||||
error: error.message,
|
||||
hasAccess: false,
|
||||
expectAccess: endpoint.expectAccess
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Summary of access results
|
||||
console.log('📊 Permission Validation Summary:');
|
||||
accessResults.forEach(result => {
|
||||
const status = result.hasAccess === result.expectAccess ? '✅' : '❌';
|
||||
console.log(` ${status} ${result.endpoint}: ${result.hasAccess ? 'Access' : 'No Access'}`);
|
||||
});
|
||||
|
||||
console.log('✅ User permission validation completed');
|
||||
});
|
||||
|
||||
test('should validate data relationships between tables', async ({ page }) => {
|
||||
console.log('🔗 Validating data relationships between Oracle tables...');
|
||||
|
||||
await authenticateWithRealCredentials(page);
|
||||
|
||||
// Get company data
|
||||
const companiesResponse = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`);
|
||||
const companies = await companiesResponse.json();
|
||||
const romfast = companies.find(c => c.id_firma === 'ROMFAST');
|
||||
|
||||
expect(romfast).toBeDefined();
|
||||
|
||||
// Get invoices for ROMFAST
|
||||
const invoicesResponse = await page.request.get(`${API_ENDPOINTS.backend}/api/invoices/ROMFAST`);
|
||||
const invoicesData = await invoicesResponse.json();
|
||||
|
||||
// Get payments for ROMFAST
|
||||
const paymentsResponse = await page.request.get(`${API_ENDPOINTS.backend}/api/payments/ROMFAST`);
|
||||
const paymentsData = await paymentsResponse.json();
|
||||
|
||||
console.log('📊 Data relationship summary:');
|
||||
console.log(` Company: ${romfast.name} (${romfast.id_firma})`);
|
||||
console.log(` Invoices: ${invoicesData.data?.length || 0}`);
|
||||
console.log(` Payments: ${paymentsData.data?.length || 0}`);
|
||||
|
||||
// Validate referential integrity
|
||||
if (invoicesData.data && invoicesData.data.length > 0) {
|
||||
const sampleInvoice = invoicesData.data[0];
|
||||
|
||||
// Invoice should reference the company
|
||||
if (sampleInvoice.cod_firma) {
|
||||
expect(sampleInvoice.cod_firma).toBe(romfast.id_firma);
|
||||
console.log('✅ Invoice-Company relationship validated');
|
||||
}
|
||||
|
||||
// Check if there are related payments
|
||||
if (paymentsData.data && paymentsData.data.length > 0) {
|
||||
console.log('✅ Payment data exists for company with invoices');
|
||||
|
||||
// Look for potential invoice-payment relationships
|
||||
const samplePayment = paymentsData.data[0];
|
||||
if (samplePayment.cod_firma) {
|
||||
expect(samplePayment.cod_firma).toBe(romfast.id_firma);
|
||||
console.log('✅ Payment-Company relationship validated');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate data consistency
|
||||
const totalInvoicesFromApi = invoicesData.data?.length || 0;
|
||||
const totalPaymentsFromApi = paymentsData.data?.length || 0;
|
||||
|
||||
// These should be reasonable numbers for a real company
|
||||
if (totalInvoicesFromApi > 0) {
|
||||
console.log(`✅ Company has ${totalInvoicesFromApi} invoices`);
|
||||
}
|
||||
|
||||
if (totalPaymentsFromApi > 0) {
|
||||
console.log(`✅ Company has ${totalPaymentsFromApi} payments`);
|
||||
}
|
||||
|
||||
// Check for console errors during relationship validation
|
||||
assertNoCriticalErrors(page, expect);
|
||||
|
||||
console.log('✅ Data relationship validation completed');
|
||||
});
|
||||
|
||||
test('should validate Oracle connection persistence during operations', async ({ page }) => {
|
||||
console.log('🔄 Testing Oracle connection persistence...');
|
||||
|
||||
await authenticateWithRealCredentials(page);
|
||||
|
||||
// Perform multiple operations to test connection persistence
|
||||
const operations = [
|
||||
{ name: 'Companies Load', action: () => page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`) },
|
||||
{ name: 'Invoice Load', action: () => page.request.get(`${API_ENDPOINTS.backend}/api/invoices/ROMFAST`) },
|
||||
{ name: 'Payment Load', action: () => page.request.get(`${API_ENDPOINTS.backend}/api/payments/ROMFAST`) },
|
||||
{ name: 'Health Check', action: () => page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`) }
|
||||
];
|
||||
|
||||
const connectionResults = [];
|
||||
|
||||
for (let cycle = 1; cycle <= 3; cycle++) {
|
||||
console.log(`🔄 Connection persistence test cycle ${cycle}/3`);
|
||||
|
||||
for (const operation of operations) {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const response = await operation.action();
|
||||
const responseTime = Date.now() - startTime;
|
||||
const success = response.status() < 400;
|
||||
|
||||
connectionResults.push({
|
||||
cycle,
|
||||
operation: operation.name,
|
||||
success,
|
||||
status: response.status(),
|
||||
responseTime
|
||||
});
|
||||
|
||||
if (success) {
|
||||
console.log(` ✅ ${operation.name}: ${response.status()} (${responseTime}ms)`);
|
||||
} else {
|
||||
console.log(` ❌ ${operation.name}: ${response.status()} (${responseTime}ms)`);
|
||||
}
|
||||
|
||||
// Brief delay between operations
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
} catch (error) {
|
||||
console.log(` ❌ ${operation.name}: ${error.message}`);
|
||||
connectionResults.push({
|
||||
cycle,
|
||||
operation: operation.name,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delay between cycles
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
// Analyze connection persistence
|
||||
const totalOperations = connectionResults.length;
|
||||
const successfulOperations = connectionResults.filter(r => r.success).length;
|
||||
const successRate = (successfulOperations / totalOperations) * 100;
|
||||
|
||||
console.log('📊 Connection Persistence Analysis:');
|
||||
console.log(` Total Operations: ${totalOperations}`);
|
||||
console.log(` Successful: ${successfulOperations}`);
|
||||
console.log(` Success Rate: ${successRate.toFixed(1)}%`);
|
||||
|
||||
// Connection should be persistent (>90% success rate)
|
||||
expect(successRate, 'Oracle connection not persistent enough').toBeGreaterThan(90);
|
||||
|
||||
// No connection should fail in the same cycle
|
||||
const cycleFailures = {};
|
||||
connectionResults.filter(r => !r.success).forEach(r => {
|
||||
cycleFailures[r.cycle] = (cycleFailures[r.cycle] || 0) + 1;
|
||||
});
|
||||
|
||||
Object.entries(cycleFailures).forEach(([cycle, failures]) => {
|
||||
if (failures === operations.length) {
|
||||
throw new Error(`Complete connection failure in cycle ${cycle}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('✅ Oracle connection persistence validated');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* Backend Health Monitoring Integration Tests
|
||||
* Validates backend services, database connectivity, and error handling
|
||||
* Monitors system health through comprehensive endpoint testing
|
||||
*/
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { API_ENDPOINTS } from '../../utils/real-auth.js';
|
||||
import {
|
||||
setupConsoleCapture,
|
||||
assertNoCriticalErrors,
|
||||
generateErrorReport,
|
||||
PerformanceBaselines,
|
||||
assertPerformanceBaseline
|
||||
} from '../../utils/console-monitor.js';
|
||||
|
||||
test.describe('Backend Health Monitoring', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
setupConsoleCapture(page);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
const report = generateErrorReport(page, test.info().title);
|
||||
if (report.summary.classifications.critical > 0) {
|
||||
console.warn('⚠️ Critical errors in health monitoring:', report.details.criticalErrors);
|
||||
}
|
||||
});
|
||||
|
||||
test('should validate database connectivity through health endpoint', async ({ page }) => {
|
||||
console.log('🏥 Testing backend health endpoint...');
|
||||
|
||||
const healthStartTime = Date.now();
|
||||
const response = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`);
|
||||
const healthResponseTime = Date.now() - healthStartTime;
|
||||
|
||||
// Validate response
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const health = await response.json();
|
||||
console.log('📊 Health response:', health);
|
||||
|
||||
// Validate health data structure
|
||||
expect(health).toHaveProperty('database');
|
||||
expect(health).toHaveProperty('api');
|
||||
expect(health).toHaveProperty('timestamp');
|
||||
|
||||
// Database should be connected (Oracle via SSH tunnel)
|
||||
expect(health.database).toBe('connected');
|
||||
expect(health.api).toBe('healthy');
|
||||
|
||||
// Validate response time
|
||||
assertPerformanceBaseline(
|
||||
healthResponseTime,
|
||||
1000, // Max 1s for health check
|
||||
'Health endpoint response',
|
||||
expect
|
||||
);
|
||||
|
||||
console.log(`✅ Backend health validated in ${healthResponseTime}ms`);
|
||||
});
|
||||
|
||||
test('should validate SSH tunnel dependency in health check', async ({ page }) => {
|
||||
console.log('🔐 Testing SSH tunnel dependency validation...');
|
||||
|
||||
const response = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`);
|
||||
expect(response.status()).toBe(200);
|
||||
|
||||
const health = await response.json();
|
||||
|
||||
// Should indicate SSH tunnel status
|
||||
if (health.ssh_tunnel !== undefined) {
|
||||
expect(health.ssh_tunnel).toBe('active');
|
||||
console.log('✅ SSH tunnel status confirmed in health check');
|
||||
}
|
||||
|
||||
// Database connection implies SSH tunnel is working
|
||||
expect(health.database).toBe('connected');
|
||||
|
||||
console.log('✅ SSH tunnel dependency validated through database connectivity');
|
||||
});
|
||||
|
||||
test('should handle Oracle connection failures gracefully', async ({ page }) => {
|
||||
console.log('💥 Testing Oracle connection failure handling...');
|
||||
|
||||
// First verify normal operation
|
||||
const normalResponse = await page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`);
|
||||
expect(normalResponse.status()).toBe(200);
|
||||
|
||||
// Navigate to app to test error handling in UI
|
||||
await page.goto('/dashboard');
|
||||
|
||||
// Mock database connection failure
|
||||
await page.route('**/api/**', async (route) => {
|
||||
if (route.request().url().includes('/health')) {
|
||||
await route.fulfill({
|
||||
status: 503,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
database: 'disconnected',
|
||||
api: 'degraded',
|
||||
error: 'Oracle connection failed'
|
||||
})
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger health check from frontend
|
||||
await page.reload();
|
||||
|
||||
// Should show appropriate error handling
|
||||
const errorElements = await page.locator('[data-testid*="error"], [data-testid*="warning"]').count();
|
||||
|
||||
if (errorElements > 0) {
|
||||
console.log(`🚨 Found ${errorElements} error indicators in UI`);
|
||||
}
|
||||
|
||||
// Check console for appropriate error messages (not critical failures)
|
||||
const consoleMessages = page.consoleMessages || [];
|
||||
const dbErrors = consoleMessages.filter(msg =>
|
||||
msg.text.includes('database') || msg.text.includes('Oracle') || msg.text.includes('503')
|
||||
);
|
||||
|
||||
expect(dbErrors.length).toBeGreaterThan(0);
|
||||
console.log(`📝 Found ${dbErrors.length} database-related console messages`);
|
||||
|
||||
// Should not crash the application
|
||||
const currentUrl = page.url();
|
||||
expect(currentUrl).not.toContain('/error');
|
||||
|
||||
console.log('✅ Oracle connection failure handled gracefully');
|
||||
});
|
||||
|
||||
test('should validate API endpoint availability and response times', async ({ page }) => {
|
||||
console.log('📡 Testing API endpoint availability...');
|
||||
|
||||
const endpoints = [
|
||||
{ path: API_ENDPOINTS.health, name: 'Health Check', maxTime: 1000 },
|
||||
{ path: API_ENDPOINTS.companies, name: 'Companies', maxTime: 2000 },
|
||||
{ path: `${API_ENDPOINTS.invoices}/ROMFAST`, name: 'ROMFAST Invoices', maxTime: 3000 },
|
||||
{ path: `${API_ENDPOINTS.payments}/ROMFAST`, name: 'ROMFAST Payments', maxTime: 3000 }
|
||||
];
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
console.log(`🔍 Testing ${endpoint.name} endpoint...`);
|
||||
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const response = await page.request.get(`${API_ENDPOINTS.backend}${endpoint.path}`);
|
||||
const responseTime = Date.now() - startTime;
|
||||
|
||||
const result = {
|
||||
name: endpoint.name,
|
||||
path: endpoint.path,
|
||||
status: response.status(),
|
||||
responseTime,
|
||||
success: response.ok(),
|
||||
maxTime: endpoint.maxTime
|
||||
};
|
||||
|
||||
results.push(result);
|
||||
|
||||
if (response.ok()) {
|
||||
console.log(`✅ ${endpoint.name}: ${result.status} (${responseTime}ms)`);
|
||||
|
||||
// Validate response time
|
||||
assertPerformanceBaseline(
|
||||
responseTime,
|
||||
endpoint.maxTime,
|
||||
`${endpoint.name} response time`,
|
||||
expect
|
||||
);
|
||||
} else {
|
||||
console.warn(`⚠️ ${endpoint.name}: ${result.status} (${responseTime}ms)`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ ${endpoint.name} failed:`, error.message);
|
||||
results.push({
|
||||
name: endpoint.name,
|
||||
path: endpoint.path,
|
||||
error: error.message,
|
||||
success: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
const successCount = results.filter(r => r.success).length;
|
||||
const totalEndpoints = endpoints.length;
|
||||
|
||||
console.log(`📊 API Endpoint Summary: ${successCount}/${totalEndpoints} successful`);
|
||||
|
||||
// At least health and companies endpoints should work
|
||||
const criticalEndpoints = results.filter(r =>
|
||||
r.name === 'Health Check' || r.name === 'Companies'
|
||||
);
|
||||
const criticalSuccess = criticalEndpoints.filter(r => r.success).length;
|
||||
|
||||
expect(criticalSuccess).toBe(2); // Both critical endpoints must work
|
||||
|
||||
console.log('✅ Critical API endpoints validated');
|
||||
});
|
||||
|
||||
test('should monitor backend error rates and patterns', async ({ page }) => {
|
||||
console.log('📈 Monitoring backend error rates...');
|
||||
|
||||
const testRequests = [
|
||||
{ url: `${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`, expected: 200 },
|
||||
{ url: `${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`, expected: 200 },
|
||||
{ url: `${API_ENDPOINTS.backend}/api/nonexistent`, expected: 404 },
|
||||
{ url: `${API_ENDPOINTS.backend}/api/invoices/INVALID_COMPANY`, expected: 404 }
|
||||
];
|
||||
|
||||
const errorPatterns = {};
|
||||
let totalRequests = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for (const request of testRequests) {
|
||||
totalRequests++;
|
||||
console.log(`📤 Testing: ${request.url}`);
|
||||
|
||||
try {
|
||||
const response = await page.request.get(request.url);
|
||||
const status = response.status();
|
||||
|
||||
if (status !== request.expected) {
|
||||
errorCount++;
|
||||
const pattern = `${Math.floor(status / 100)}xx`;
|
||||
errorPatterns[pattern] = (errorPatterns[pattern] || 0) + 1;
|
||||
|
||||
console.warn(`⚠️ Unexpected status: ${status} (expected ${request.expected})`);
|
||||
} else {
|
||||
console.log(`✅ Expected status: ${status}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
errorCount++;
|
||||
errorPatterns['network'] = (errorPatterns['network'] || 0) + 1;
|
||||
console.error(`❌ Network error:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
const errorRate = (errorCount / totalRequests) * 100;
|
||||
|
||||
console.log(`📊 Error Analysis:`);
|
||||
console.log(` Total Requests: ${totalRequests}`);
|
||||
console.log(` Errors: ${errorCount}`);
|
||||
console.log(` Error Rate: ${errorRate.toFixed(1)}%`);
|
||||
console.log(` Error Patterns:`, errorPatterns);
|
||||
|
||||
// Error rate should be reasonable (some 404s expected)
|
||||
expect(errorRate).toBeLessThan(75); // Allow some expected errors
|
||||
|
||||
// Should not have network errors
|
||||
expect(errorPatterns.network || 0).toBe(0);
|
||||
|
||||
console.log('✅ Backend error monitoring completed');
|
||||
});
|
||||
|
||||
test('should validate backend performance under concurrent requests', async ({ page }) => {
|
||||
console.log('⚡ Testing backend performance under load...');
|
||||
|
||||
const concurrentRequests = 5;
|
||||
const requestsPerBatch = 3;
|
||||
const totalBatches = concurrentRequests;
|
||||
|
||||
const performanceResults = [];
|
||||
|
||||
for (let batch = 0; batch < totalBatches; batch++) {
|
||||
console.log(`🔄 Batch ${batch + 1}/${totalBatches}`);
|
||||
|
||||
const batchStartTime = Date.now();
|
||||
const batchPromises = [];
|
||||
|
||||
// Create concurrent requests
|
||||
for (let req = 0; req < requestsPerBatch; req++) {
|
||||
const requestPromise = page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`)
|
||||
.then(response => ({
|
||||
status: response.status(),
|
||||
timing: Date.now() - batchStartTime
|
||||
}))
|
||||
.catch(error => ({
|
||||
error: error.message,
|
||||
timing: Date.now() - batchStartTime
|
||||
}));
|
||||
|
||||
batchPromises.push(requestPromise);
|
||||
}
|
||||
|
||||
// Wait for all requests in batch
|
||||
const batchResults = await Promise.all(batchPromises);
|
||||
const batchTime = Date.now() - batchStartTime;
|
||||
|
||||
performanceResults.push({
|
||||
batch: batch + 1,
|
||||
results: batchResults,
|
||||
totalTime: batchTime,
|
||||
successful: batchResults.filter(r => r.status === 200).length
|
||||
});
|
||||
|
||||
console.log(`⏱️ Batch ${batch + 1}: ${batchTime}ms (${batchResults.filter(r => r.status === 200).length}/${requestsPerBatch} successful)`);
|
||||
|
||||
// Small delay between batches
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
// Analyze results
|
||||
const totalRequests = totalBatches * requestsPerBatch;
|
||||
const successfulRequests = performanceResults.reduce((sum, batch) => sum + batch.successful, 0);
|
||||
const averageBatchTime = performanceResults.reduce((sum, batch) => sum + batch.totalTime, 0) / totalBatches;
|
||||
const successRate = (successfulRequests / totalRequests) * 100;
|
||||
|
||||
console.log(`📊 Concurrent Load Test Results:`);
|
||||
console.log(` Total Requests: ${totalRequests}`);
|
||||
console.log(` Successful: ${successfulRequests}`);
|
||||
console.log(` Success Rate: ${successRate.toFixed(1)}%`);
|
||||
console.log(` Average Batch Time: ${averageBatchTime.toFixed(0)}ms`);
|
||||
|
||||
// Validate performance under load
|
||||
expect(successRate).toBeGreaterThan(90); // 90%+ success rate
|
||||
expect(averageBatchTime).toBeLessThan(5000); // Max 5s per batch
|
||||
|
||||
// Individual requests should not be extremely slow
|
||||
const slowRequests = performanceResults
|
||||
.flatMap(batch => batch.results)
|
||||
.filter(result => result.timing > 10000); // > 10s
|
||||
|
||||
expect(slowRequests.length).toBe(0);
|
||||
|
||||
console.log('✅ Backend performance under concurrent load validated');
|
||||
});
|
||||
|
||||
test('should validate backend memory and resource monitoring', async ({ page }) => {
|
||||
console.log('💾 Testing backend resource monitoring...');
|
||||
|
||||
// Test multiple heavy operations to check for memory leaks
|
||||
const operations = [
|
||||
() => page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.companies}`),
|
||||
() => page.request.get(`${API_ENDPOINTS.backend}/api/invoices/ROMFAST`),
|
||||
() => page.request.get(`${API_ENDPOINTS.backend}/api/payments/ROMFAST`),
|
||||
() => page.request.get(`${API_ENDPOINTS.backend}${API_ENDPOINTS.health}`)
|
||||
];
|
||||
|
||||
const resourceMetrics = [];
|
||||
|
||||
// Perform operations multiple times
|
||||
for (let cycle = 0; cycle < 3; cycle++) {
|
||||
console.log(`🔄 Resource test cycle ${cycle + 1}/3`);
|
||||
|
||||
const cycleStartTime = Date.now();
|
||||
const cycleResults = [];
|
||||
|
||||
for (const operation of operations) {
|
||||
const opStartTime = Date.now();
|
||||
|
||||
try {
|
||||
const response = await operation();
|
||||
const responseTime = Date.now() - opStartTime;
|
||||
|
||||
cycleResults.push({
|
||||
success: response.ok(),
|
||||
status: response.status(),
|
||||
responseTime
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
cycleResults.push({
|
||||
success: false,
|
||||
error: error.message,
|
||||
responseTime: Date.now() - opStartTime
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const cycleTime = Date.now() - cycleStartTime;
|
||||
const avgResponseTime = cycleResults.reduce((sum, r) => sum + r.responseTime, 0) / cycleResults.length;
|
||||
|
||||
resourceMetrics.push({
|
||||
cycle: cycle + 1,
|
||||
totalTime: cycleTime,
|
||||
averageResponseTime: avgResponseTime,
|
||||
successCount: cycleResults.filter(r => r.success).length
|
||||
});
|
||||
|
||||
console.log(`📊 Cycle ${cycle + 1}: ${cycleTime}ms avg, ${avgResponseTime.toFixed(0)}ms response`);
|
||||
}
|
||||
|
||||
// Check for performance degradation over cycles (indicating resource leaks)
|
||||
const firstCycleAvg = resourceMetrics[0].averageResponseTime;
|
||||
const lastCycleAvg = resourceMetrics[resourceMetrics.length - 1].averageResponseTime;
|
||||
const degradationRatio = lastCycleAvg / firstCycleAvg;
|
||||
|
||||
console.log(`📈 Performance degradation ratio: ${degradationRatio.toFixed(2)}`);
|
||||
|
||||
// Should not degrade significantly (< 50% increase)
|
||||
expect(degradationRatio).toBeLessThan(1.5);
|
||||
|
||||
// All cycles should maintain good success rates
|
||||
resourceMetrics.forEach((metric, index) => {
|
||||
expect(metric.successCount).toBeGreaterThan(2); // At least 3/4 operations successful
|
||||
});
|
||||
|
||||
console.log('✅ Backend resource monitoring validated - no significant degradation detected');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user