update
This commit is contained in:
@ -0,0 +1,344 @@
|
||||
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<void> => {
|
||||
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:<sender${operationId}@example.com>\r\n`);
|
||||
} else if (step === 'mail_from' && line.startsWith('250')) {
|
||||
step = 'rcpt_to';
|
||||
socket.write(`RCPT TO:<recipient${operationId}@example.com>\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);
|
||||
});
|
||||
|
||||
tap.start();
|
Reference in New Issue
Block a user