update
This commit is contained in:
@@ -22,42 +22,64 @@ const createConnection = async (): Promise<net.Socket> => {
|
||||
return socket;
|
||||
};
|
||||
|
||||
const getResponse = (socket: net.Socket, commandName: string): Promise<string> => {
|
||||
// Helper function to wait for SMTP response
|
||||
const waitForResponse = (socket: net.Socket, expectedCode?: string, timeout = 5000): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error(`${commandName} response timeout`));
|
||||
}, 3000);
|
||||
|
||||
socket.once('data', (chunk: Buffer) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(chunk.toString());
|
||||
});
|
||||
let buffer = '';
|
||||
const timer = setTimeout(() => {
|
||||
socket.removeListener('data', handler);
|
||||
reject(new Error(`Timeout waiting for ${expectedCode || 'any'} response`));
|
||||
}, timeout);
|
||||
|
||||
const handler = (data: Buffer) => {
|
||||
buffer += data.toString();
|
||||
const lines = buffer.split('\r\n');
|
||||
|
||||
// Check if we have a complete response
|
||||
for (const line of lines) {
|
||||
if (expectedCode) {
|
||||
if (line.startsWith(expectedCode + ' ')) {
|
||||
clearTimeout(timer);
|
||||
socket.removeListener('data', handler);
|
||||
resolve(buffer);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Any complete response line
|
||||
if (line.match(/^\d{3} /)) {
|
||||
clearTimeout(timer);
|
||||
socket.removeListener('data', handler);
|
||||
resolve(buffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socket.on('data', handler);
|
||||
});
|
||||
};
|
||||
|
||||
const getResponse = waitForResponse;
|
||||
|
||||
const testBasicSmtpFlow = async (socket: net.Socket): Promise<boolean> => {
|
||||
try {
|
||||
await getResponse(socket, 'GREETING');
|
||||
await waitForResponse(socket, '220');
|
||||
|
||||
socket.write('EHLO test.example.com\r\n');
|
||||
const ehloResp = await getResponse(socket, 'EHLO');
|
||||
const ehloResp = await waitForResponse(socket, '250');
|
||||
if (!ehloResp.includes('250')) return false;
|
||||
|
||||
// Wait for complete EHLO response
|
||||
if (ehloResp.includes('250-')) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
socket.write('MAIL FROM:<test@example.com>\r\n');
|
||||
const mailResp = await getResponse(socket, 'MAIL FROM');
|
||||
const mailResp = await waitForResponse(socket, '250');
|
||||
if (!mailResp.includes('250')) return false;
|
||||
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
const rcptResp = await getResponse(socket, 'RCPT TO');
|
||||
const rcptResp = await waitForResponse(socket, '250');
|
||||
if (!rcptResp.includes('250')) return false;
|
||||
|
||||
socket.write('DATA\r\n');
|
||||
const dataResp = await getResponse(socket, 'DATA');
|
||||
const dataResp = await waitForResponse(socket, '354');
|
||||
if (!dataResp.includes('354')) return false;
|
||||
|
||||
const testEmail = [
|
||||
@@ -71,7 +93,7 @@ const testBasicSmtpFlow = async (socket: net.Socket): Promise<boolean> => {
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(testEmail);
|
||||
const finalResp = await getResponse(socket, 'EMAIL DATA');
|
||||
const finalResp = await waitForResponse(socket, '250');
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
@@ -95,23 +117,13 @@ tap.test('REL-06: Network interruption - Sudden connection drop', async (tools)
|
||||
|
||||
// Phase 1: Create connection and drop it mid-session
|
||||
const socket1 = await createConnection();
|
||||
await getResponse(socket1, 'GREETING');
|
||||
await waitForResponse(socket1, '220');
|
||||
|
||||
socket1.write('EHLO testhost\r\n');
|
||||
let data = '';
|
||||
await new Promise<void>((resolve) => {
|
||||
const handleData = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('250 ') && !data.includes('250-')) {
|
||||
socket1.removeListener('data', handleData);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket1.on('data', handleData);
|
||||
});
|
||||
await waitForResponse(socket1, '250');
|
||||
|
||||
socket1.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
await getResponse(socket1, 'MAIL FROM');
|
||||
await waitForResponse(socket1, '250');
|
||||
|
||||
// Abruptly close connection during active session
|
||||
socket1.destroy();
|
||||
@@ -138,29 +150,19 @@ tap.test('REL-06: Network interruption - Data transfer interruption', async (too
|
||||
console.log('\nTesting connection interruption during data transfer...');
|
||||
|
||||
const socket = await createConnection();
|
||||
await getResponse(socket, 'GREETING');
|
||||
await waitForResponse(socket, '220');
|
||||
|
||||
socket.write('EHLO datatest\r\n');
|
||||
let data = '';
|
||||
await new Promise<void>((resolve) => {
|
||||
const handleData = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('250 ') && !data.includes('250-')) {
|
||||
socket.removeListener('data', handleData);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket.on('data', handleData);
|
||||
});
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
await getResponse(socket, 'MAIL FROM');
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
socket.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
await getResponse(socket, 'RCPT TO');
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
socket.write('DATA\r\n');
|
||||
const dataResp = await getResponse(socket, 'DATA');
|
||||
const dataResp = await waitForResponse(socket, '354');
|
||||
expect(dataResp).toInclude('354');
|
||||
|
||||
// Start sending data but interrupt midway
|
||||
@@ -257,7 +259,7 @@ tap.test('REL-06: Network interruption - Partial command interruption', async (t
|
||||
console.log('\nTesting partial command transmission interruption...');
|
||||
|
||||
const socket = await createConnection();
|
||||
await getResponse(socket, 'GREETING');
|
||||
await waitForResponse(socket, '220');
|
||||
|
||||
// Send partial EHLO command and interrupt
|
||||
socket.write('EH');
|
||||
@@ -291,7 +293,7 @@ tap.test('REL-06: Network interruption - Multiple interruption types', async (to
|
||||
// Test 1: Interrupt after greeting
|
||||
try {
|
||||
const socket = await createConnection();
|
||||
await getResponse(socket, 'GREETING');
|
||||
await waitForResponse(socket, '220');
|
||||
socket.destroy();
|
||||
results.push({ type: 'after-greeting', recovered: false });
|
||||
} catch (e) {
|
||||
@@ -303,7 +305,7 @@ tap.test('REL-06: Network interruption - Multiple interruption types', async (to
|
||||
// Test 2: Interrupt during EHLO
|
||||
try {
|
||||
const socket = await createConnection();
|
||||
await getResponse(socket, 'GREETING');
|
||||
await waitForResponse(socket, '220');
|
||||
socket.write('EHLO te');
|
||||
socket.destroy();
|
||||
results.push({ type: 'during-ehlo', recovered: false });
|
||||
@@ -316,7 +318,7 @@ tap.test('REL-06: Network interruption - Multiple interruption types', async (to
|
||||
// Test 3: Interrupt with invalid data
|
||||
try {
|
||||
const socket = await createConnection();
|
||||
await getResponse(socket, 'GREETING');
|
||||
await waitForResponse(socket, '220');
|
||||
socket.write('\x00\x01\x02\x03');
|
||||
socket.destroy();
|
||||
results.push({ type: 'invalid-data', recovered: false });
|
||||
@@ -361,23 +363,13 @@ tap.test('REL-06: Network interruption - Long delay recovery', async (tools) =>
|
||||
|
||||
// Create connection and start transaction
|
||||
const socket = await createConnection();
|
||||
await getResponse(socket, 'GREETING');
|
||||
await waitForResponse(socket, '220');
|
||||
|
||||
socket.write('EHLO longdelay\r\n');
|
||||
let data = '';
|
||||
await new Promise<void>((resolve) => {
|
||||
const handleData = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('250 ') && !data.includes('250-')) {
|
||||
socket.removeListener('data', handleData);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket.on('data', handleData);
|
||||
});
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
socket.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
await getResponse(socket, 'MAIL FROM');
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
// Simulate long network interruption
|
||||
socket.pause();
|
||||
@@ -391,7 +383,7 @@ tap.test('REL-06: Network interruption - Long delay recovery', async (tools) =>
|
||||
|
||||
let continuationFailed = false;
|
||||
try {
|
||||
await getResponse(socket, 'RCPT TO');
|
||||
await waitForResponse(socket, '250', 3000);
|
||||
} catch (error) {
|
||||
continuationFailed = true;
|
||||
console.log(' Continuation failed as expected');
|
||||
|
@@ -58,6 +58,44 @@ const captureResourceMetrics = async (): Promise<ResourceMetrics> => {
|
||||
};
|
||||
};
|
||||
|
||||
// Helper function to wait for SMTP response
|
||||
const waitForResponse = (socket: net.Socket, expectedCode?: string, timeout = 5000): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let buffer = '';
|
||||
const timer = setTimeout(() => {
|
||||
socket.removeListener('data', handler);
|
||||
reject(new Error(`Timeout waiting for ${expectedCode || 'any'} response`));
|
||||
}, timeout);
|
||||
|
||||
const handler = (data: Buffer) => {
|
||||
buffer += data.toString();
|
||||
const lines = buffer.split('\r\n');
|
||||
|
||||
// Check if we have a complete response
|
||||
for (const line of lines) {
|
||||
if (expectedCode) {
|
||||
if (line.startsWith(expectedCode + ' ')) {
|
||||
clearTimeout(timer);
|
||||
socket.removeListener('data', handler);
|
||||
resolve(buffer);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Any complete response line
|
||||
if (line.match(/^\d{3} /)) {
|
||||
clearTimeout(timer);
|
||||
socket.removeListener('data', handler);
|
||||
resolve(buffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socket.on('data', handler);
|
||||
});
|
||||
};
|
||||
|
||||
const analyzeResourceLeaks = (initial: ResourceMetrics, samples: Array<{ operation: number; metrics: ResourceMetrics }>, final: ResourceMetrics): LeakAnalysis => {
|
||||
const memoryGrowthMB = final.memoryUsage.heapUsed - initial.memoryUsage.heapUsed;
|
||||
|
||||
@@ -123,55 +161,24 @@ tap.test('REL-03: Resource leak detection - Memory leak analysis', async (tools)
|
||||
});
|
||||
|
||||
// Read greeting
|
||||
await new Promise<void>((resolve) => {
|
||||
socket.once('data', () => resolve());
|
||||
});
|
||||
await waitForResponse(socket, '220');
|
||||
|
||||
// Send EHLO
|
||||
socket.write(`EHLO leaktest-${i}\r\n`);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
let data = '';
|
||||
const handleData = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('250 ') && !data.includes('250-')) {
|
||||
socket.removeListener('data', handleData);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket.on('data', handleData);
|
||||
});
|
||||
await waitForResponse(socket, '250');
|
||||
|
||||
// Complete email transaction
|
||||
socket.write(`MAIL FROM:<sender${i}@example.com>\r\n`);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const mailResp = await waitForResponse(socket, '250');
|
||||
expect(mailResp).toInclude('250');
|
||||
|
||||
socket.write(`RCPT TO:<recipient${i}@example.com>\r\n`);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const rcptResp = await waitForResponse(socket, '250');
|
||||
expect(rcptResp).toInclude('250');
|
||||
|
||||
socket.write('DATA\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('354');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const dataResp = await waitForResponse(socket, '354');
|
||||
expect(dataResp).toInclude('354');
|
||||
|
||||
const emailContent = [
|
||||
`From: sender${i}@example.com`,
|
||||
@@ -185,22 +192,12 @@ tap.test('REL-03: Resource leak detection - Memory leak analysis', async (tools)
|
||||
].join('\r\n');
|
||||
|
||||
socket.write(emailContent);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const sendResp = await waitForResponse(socket, '250');
|
||||
expect(sendResp).toInclude('250');
|
||||
|
||||
socket.write('QUIT\r\n');
|
||||
await new Promise<void>((resolve) => {
|
||||
socket.once('data', () => {
|
||||
socket.end();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
await waitForResponse(socket, '221');
|
||||
socket.end();
|
||||
|
||||
// Capture metrics every 5 operations
|
||||
if ((i + 1) % 5 === 0) {
|
||||
|
@@ -7,6 +7,44 @@ const TEST_PORT = 2525;
|
||||
|
||||
let testServer;
|
||||
|
||||
// Helper function to wait for SMTP response
|
||||
const waitForResponse = (socket: net.Socket, expectedCode?: string, timeout = 5000): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let buffer = '';
|
||||
const timer = setTimeout(() => {
|
||||
socket.removeListener('data', handler);
|
||||
reject(new Error(`Timeout waiting for ${expectedCode || 'any'} response`));
|
||||
}, timeout);
|
||||
|
||||
const handler = (data: Buffer) => {
|
||||
buffer += data.toString();
|
||||
const lines = buffer.split('\r\n');
|
||||
|
||||
// Check if we have a complete response
|
||||
for (const line of lines) {
|
||||
if (expectedCode) {
|
||||
if (line.startsWith(expectedCode + ' ')) {
|
||||
clearTimeout(timer);
|
||||
socket.removeListener('data', handler);
|
||||
resolve(buffer);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Any complete response line
|
||||
if (line.match(/^\d{3} /)) {
|
||||
clearTimeout(timer);
|
||||
socket.removeListener('data', handler);
|
||||
resolve(buffer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
socket.on('data', handler);
|
||||
});
|
||||
};
|
||||
|
||||
tap.test('prepare server', async () => {
|
||||
testServer = await startTestServer({ port: TEST_PORT });
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
@@ -31,60 +69,26 @@ tap.test('REL-02: Restart recovery - Server state after restart', async (tools)
|
||||
});
|
||||
|
||||
// Read greeting
|
||||
const greeting1 = await new Promise<string>((resolve) => {
|
||||
socket1.once('data', (chunk) => {
|
||||
resolve(chunk.toString());
|
||||
});
|
||||
});
|
||||
|
||||
const greeting1 = await waitForResponse(socket1, '220');
|
||||
expect(greeting1).toInclude('220');
|
||||
console.log('Initial connection successful');
|
||||
|
||||
// Send EHLO
|
||||
socket1.write('EHLO testhost\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
let data = '';
|
||||
const handleData = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('250 ') && !data.includes('250-')) {
|
||||
socket1.removeListener('data', handleData);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket1.on('data', handleData);
|
||||
});
|
||||
await waitForResponse(socket1, '250');
|
||||
|
||||
// Complete a transaction
|
||||
socket1.write('MAIL FROM:<sender@example.com>\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket1.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const mailResp1 = await waitForResponse(socket1, '250');
|
||||
expect(mailResp1).toInclude('250');
|
||||
|
||||
socket1.write('RCPT TO:<recipient@example.com>\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket1.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const rcptResp1 = await waitForResponse(socket1, '250');
|
||||
expect(rcptResp1).toInclude('250');
|
||||
|
||||
socket1.write('DATA\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket1.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('354');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const dataResp1 = await waitForResponse(socket1, '354');
|
||||
expect(dataResp1).toInclude('354');
|
||||
|
||||
const emailContent = [
|
||||
'From: sender@example.com',
|
||||
@@ -97,16 +101,11 @@ tap.test('REL-02: Restart recovery - Server state after restart', async (tools)
|
||||
].join('\r\n');
|
||||
|
||||
socket1.write(emailContent);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket1.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const sendResp1 = await waitForResponse(socket1, '250');
|
||||
expect(sendResp1).toInclude('250');
|
||||
|
||||
socket1.write('QUIT\r\n');
|
||||
await waitForResponse(socket1, '221');
|
||||
socket1.end();
|
||||
|
||||
console.log('Pre-restart transaction completed successfully');
|
||||
@@ -141,49 +140,20 @@ tap.test('REL-02: Restart recovery - Server state after restart', async (tools)
|
||||
|
||||
// Verify server is fully functional after restart
|
||||
socket2.write('EHLO testhost-postrestart\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
let data = '';
|
||||
const handleData = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('250 ') && !data.includes('250-')) {
|
||||
socket2.removeListener('data', handleData);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket2.on('data', handleData);
|
||||
});
|
||||
await waitForResponse(socket2, '250');
|
||||
|
||||
// Complete another transaction to verify full recovery
|
||||
socket2.write('MAIL FROM:<sender2@example.com>\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket2.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const mailResp2 = await waitForResponse(socket2, '250');
|
||||
expect(mailResp2).toInclude('250');
|
||||
|
||||
socket2.write('RCPT TO:<recipient2@example.com>\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket2.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const rcptResp2 = await waitForResponse(socket2, '250');
|
||||
expect(rcptResp2).toInclude('250');
|
||||
|
||||
socket2.write('DATA\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket2.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('354');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const dataResp2 = await waitForResponse(socket2, '354');
|
||||
expect(dataResp2).toInclude('354');
|
||||
|
||||
const postRestartEmail = [
|
||||
'From: sender2@example.com',
|
||||
@@ -196,16 +166,11 @@ tap.test('REL-02: Restart recovery - Server state after restart', async (tools)
|
||||
].join('\r\n');
|
||||
|
||||
socket2.write(postRestartEmail);
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket2.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const sendResp2 = await waitForResponse(socket2, '250');
|
||||
expect(sendResp2).toInclude('250');
|
||||
|
||||
socket2.write('QUIT\r\n');
|
||||
await waitForResponse(socket2, '221');
|
||||
socket2.end();
|
||||
|
||||
console.log('Post-restart transaction completed successfully');
|
||||
@@ -250,23 +215,19 @@ tap.test('REL-02: Restart recovery - Multiple rapid reconnections', async (tools
|
||||
});
|
||||
|
||||
// Read greeting
|
||||
const greeting = await new Promise<string>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Greeting timeout'));
|
||||
}, 3000);
|
||||
|
||||
socket.once('data', (chunk) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(chunk.toString());
|
||||
});
|
||||
});
|
||||
|
||||
if (greeting.includes('220')) {
|
||||
successfulReconnects++;
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
} else {
|
||||
try {
|
||||
const greeting = await waitForResponse(socket, '220', 3000);
|
||||
if (greeting.includes('220')) {
|
||||
successfulReconnects++;
|
||||
socket.write('QUIT\r\n');
|
||||
await waitForResponse(socket, '221', 1000).catch(() => {});
|
||||
socket.end();
|
||||
} else {
|
||||
socket.destroy();
|
||||
}
|
||||
} catch (error) {
|
||||
socket.destroy();
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Very short delay between attempts
|
||||
@@ -307,35 +268,16 @@ tap.test('REL-02: Restart recovery - State persistence check', async (tools) =>
|
||||
});
|
||||
|
||||
// Read greeting
|
||||
await new Promise<void>((resolve) => {
|
||||
socket1.once('data', () => resolve());
|
||||
});
|
||||
await waitForResponse(socket1, '220');
|
||||
|
||||
// Send EHLO
|
||||
socket1.write('EHLO persistence-test\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
let data = '';
|
||||
const handleData = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('250 ') && !data.includes('250-')) {
|
||||
socket1.removeListener('data', handleData);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket1.on('data', handleData);
|
||||
});
|
||||
await waitForResponse(socket1, '250');
|
||||
|
||||
// Start transaction but don't complete it
|
||||
socket1.write('MAIL FROM:<incomplete@example.com>\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
socket1.once('data', (chunk) => {
|
||||
const response = chunk.toString();
|
||||
expect(response).toInclude('250');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const mailResp = await waitForResponse(socket1, '250');
|
||||
expect(mailResp).toInclude('250');
|
||||
|
||||
// Abruptly close connection
|
||||
socket1.destroy();
|
||||
@@ -357,38 +299,20 @@ tap.test('REL-02: Restart recovery - State persistence check', async (tools) =>
|
||||
});
|
||||
|
||||
// Read greeting
|
||||
await new Promise<void>((resolve) => {
|
||||
socket2.once('data', () => resolve());
|
||||
});
|
||||
await waitForResponse(socket2, '220');
|
||||
|
||||
// Send EHLO
|
||||
socket2.write('EHLO recovery-test\r\n');
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
let data = '';
|
||||
const handleData = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('250 ') && !data.includes('250-')) {
|
||||
socket2.removeListener('data', handleData);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
socket2.on('data', handleData);
|
||||
});
|
||||
await waitForResponse(socket2, '250');
|
||||
|
||||
// Try new transaction - should work without issues from previous incomplete one
|
||||
socket2.write('MAIL FROM:<recovery@example.com>\r\n');
|
||||
|
||||
const mailResponse = await new Promise<string>((resolve) => {
|
||||
socket2.once('data', (chunk) => {
|
||||
resolve(chunk.toString());
|
||||
});
|
||||
});
|
||||
|
||||
const mailResponse = await waitForResponse(socket2, '250');
|
||||
expect(mailResponse).toInclude('250');
|
||||
console.log('Server recovered successfully - new transaction started without issues');
|
||||
|
||||
socket2.write('QUIT\r\n');
|
||||
await waitForResponse(socket2, '221');
|
||||
socket2.end();
|
||||
|
||||
done.resolve();
|
||||
|
Reference in New Issue
Block a user