import * as plugins from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as net from 'net'; import { startTestServer, stopTestServer } from '../../helpers/server.loader.js'; const TEST_PORT = 2525; let testServer; tap.test('prepare server', async () => { testServer = await startTestServer({ port: TEST_PORT }); await new Promise(resolve => setTimeout(resolve, 100)); }); tap.test('REL-01: Long-running operation - Continuous email sending', async (tools) => { const done = tools.defer(); const testDuration = 30000; // 30 seconds const operationInterval = 2000; // 2 seconds between operations const startTime = Date.now(); const endTime = startTime + testDuration; let operations = 0; let successful = 0; let errors = 0; let connectionIssues = 0; const operationResults: Array<{ operation: number; success: boolean; duration: number; error?: string; timestamp: number; }> = []; console.log(`Running long-duration test for ${testDuration/1000} seconds...`); const performOperation = async (operationId: number): Promise => { const operationStart = Date.now(); operations++; try { const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 10000 }); const result = await new Promise<{ success: boolean; error?: string; connectionIssue?: boolean }>((resolve) => { let step = 'connecting'; let receivedData = ''; const timeout = setTimeout(() => { socket.destroy(); resolve({ success: false, error: `Timeout in step ${step}`, connectionIssue: true }); }, 10000); socket.on('connect', () => { step = 'connected'; }); socket.on('data', (chunk) => { receivedData += chunk.toString(); const lines = receivedData.split('\r\n'); for (const line of lines) { if (!line.trim()) continue; // Check for errors if (line.match(/^[45]\d\d\s/)) { clearTimeout(timeout); socket.destroy(); resolve({ success: false, error: `SMTP error in ${step}: ${line}`, connectionIssue: false }); return; } // Process responses if (step === 'connected' && line.startsWith('220')) { step = 'ehlo'; socket.write(`EHLO longrun-${operationId}\r\n`); } else if (step === 'ehlo' && line.includes('250 ') && !line.includes('250-')) { step = 'mail_from'; socket.write(`MAIL FROM:\r\n`); } else if (step === 'mail_from' && line.startsWith('250')) { step = 'rcpt_to'; socket.write(`RCPT TO:\r\n`); } else if (step === 'rcpt_to' && line.startsWith('250')) { step = 'data'; socket.write('DATA\r\n'); } else if (step === 'data' && line.startsWith('354')) { step = 'email_content'; const emailContent = [ `From: sender${operationId}@example.com`, `To: recipient${operationId}@example.com`, `Subject: Long Running Test Operation ${operationId}`, `Date: ${new Date().toUTCString()}`, '', `This is test operation ${operationId} for long-running reliability testing.`, `Timestamp: ${Date.now()}`, '.', '' ].join('\r\n'); socket.write(emailContent); } else if (step === 'email_content' && line.startsWith('250')) { step = 'quit'; socket.write('QUIT\r\n'); } else if (step === 'quit' && line.startsWith('221')) { clearTimeout(timeout); socket.end(); resolve({ success: true }); return; } } }); socket.on('error', (error) => { clearTimeout(timeout); resolve({ success: false, error: error.message, connectionIssue: true }); }); socket.on('close', () => { if (step !== 'quit') { clearTimeout(timeout); resolve({ success: false, error: 'Connection closed unexpectedly', connectionIssue: true }); } }); }); const duration = Date.now() - operationStart; if (result.success) { successful++; } else { errors++; if (result.connectionIssue) { connectionIssues++; } } operationResults.push({ operation: operationId, success: result.success, duration, error: result.error, timestamp: operationStart }); } catch (error) { errors++; operationResults.push({ operation: operationId, success: false, duration: Date.now() - operationStart, error: error instanceof Error ? error.message : 'Unknown error', timestamp: operationStart }); } }; try { // Run operations continuously until end time while (Date.now() < endTime) { const operationStart = Date.now(); await performOperation(operations + 1); // Calculate wait time for next operation const nextOperation = operationStart + operationInterval; const waitTime = nextOperation - Date.now(); if (waitTime > 0 && Date.now() < endTime) { await new Promise(resolve => setTimeout(resolve, waitTime)); } // Progress update every 5 operations if (operations % 5 === 0) { console.log(`Progress: ${operations} operations, ${successful} successful, ${errors} errors`); } } // Calculate results const totalDuration = Date.now() - startTime; const successRate = successful / operations; const connectionIssueRate = connectionIssues / operations; const avgOperationTime = operationResults.reduce((sum, r) => sum + r.duration, 0) / operations; console.log(`\nLong-Running Operation Results:`); console.log(`Total duration: ${(totalDuration/1000).toFixed(1)}s`); console.log(`Total operations: ${operations}`); console.log(`Successful: ${successful} (${(successRate * 100).toFixed(1)}%)`); console.log(`Errors: ${errors}`); console.log(`Connection issues: ${connectionIssues} (${(connectionIssueRate * 100).toFixed(1)}%)`); console.log(`Average operation time: ${avgOperationTime.toFixed(0)}ms`); // Show last few operations for debugging console.log('\nLast 5 operations:'); operationResults.slice(-5).forEach(op => { console.log(` Op ${op.operation}: ${op.success ? 'success' : 'failed'} (${op.duration}ms)${op.error ? ' - ' + op.error : ''}`); }); // Test passes with 85% success rate and max 10% connection issues expect(successRate).toBeGreaterThanOrEqual(0.85); expect(connectionIssueRate).toBeLessThanOrEqual(0.1); done.resolve(); } catch (error) { done.reject(error); } }); tap.test('REL-01: Long-running operation - Server stability check', async (tools) => { const done = tools.defer(); const checkDuration = 15000; // 15 seconds const checkInterval = 3000; // 3 seconds between checks const startTime = Date.now(); const endTime = startTime + checkDuration; const stabilityChecks: Array<{ timestamp: number; responseTime: number; success: boolean; error?: string; }> = []; console.log(`\nRunning server stability checks for ${checkDuration/1000} seconds...`); try { while (Date.now() < endTime) { const checkStart = Date.now(); const socket = net.createConnection({ host: 'localhost', port: TEST_PORT, timeout: 5000 }); const checkResult = await new Promise<{ success: boolean; responseTime: number; error?: string }>((resolve) => { const connectTime = Date.now(); let greetingReceived = false; const timeout = setTimeout(() => { socket.destroy(); resolve({ success: false, responseTime: Date.now() - connectTime, error: 'Timeout waiting for greeting' }); }, 5000); socket.on('connect', () => { // Connected }); socket.once('data', (chunk) => { const response = chunk.toString(); clearTimeout(timeout); greetingReceived = true; if (response.startsWith('220')) { socket.write('QUIT\r\n'); socket.end(); resolve({ success: true, responseTime: Date.now() - connectTime }); } else { socket.end(); resolve({ success: false, responseTime: Date.now() - connectTime, error: `Unexpected greeting: ${response.substring(0, 50)}` }); } }); socket.on('error', (error) => { clearTimeout(timeout); resolve({ success: false, responseTime: Date.now() - connectTime, error: error.message }); }); }); stabilityChecks.push({ timestamp: checkStart, responseTime: checkResult.responseTime, success: checkResult.success, error: checkResult.error }); console.log(`Stability check ${stabilityChecks.length}: ${checkResult.success ? 'OK' : 'FAILED'} (${checkResult.responseTime}ms)`); // Wait for next check const nextCheck = checkStart + checkInterval; const waitTime = nextCheck - Date.now(); if (waitTime > 0 && Date.now() < endTime) { await new Promise(resolve => setTimeout(resolve, waitTime)); } } // Analyze stability const successfulChecks = stabilityChecks.filter(c => c.success).length; const avgResponseTime = stabilityChecks .filter(c => c.success) .reduce((sum, c) => sum + c.responseTime, 0) / successfulChecks || 0; const maxResponseTime = Math.max(...stabilityChecks.filter(c => c.success).map(c => c.responseTime)); console.log(`\nStability Check Results:`); console.log(`Total checks: ${stabilityChecks.length}`); console.log(`Successful: ${successfulChecks} (${(successfulChecks/stabilityChecks.length * 100).toFixed(1)}%)`); console.log(`Average response time: ${avgResponseTime.toFixed(0)}ms`); console.log(`Max response time: ${maxResponseTime}ms`); // All checks should succeed for stable server expect(successfulChecks).toEqual(stabilityChecks.length); expect(avgResponseTime).toBeLessThan(1000); done.resolve(); } catch (error) { done.reject(error); } }); tap.test('cleanup server', async () => { await stopTestServer(testServer); }); export default tap.start();