201 lines
5.6 KiB
TypeScript
201 lines
5.6 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import { startTestSmtpServer } from '../../helpers/server.loader.js';
|
|
import { createSmtpClient } from '../../helpers/smtp.client.js';
|
|
import * as net from 'net';
|
|
import * as os from 'os';
|
|
|
|
let testServer: any;
|
|
|
|
tap.test('setup test SMTP server', async () => {
|
|
testServer = await startTestSmtpServer();
|
|
expect(testServer).toBeTruthy();
|
|
expect(testServer.port).toBeGreaterThan(0);
|
|
});
|
|
|
|
tap.test('CCM-09: Check system IPv6 support', async () => {
|
|
const networkInterfaces = os.networkInterfaces();
|
|
let hasIPv6 = false;
|
|
|
|
for (const interfaceName in networkInterfaces) {
|
|
const interfaces = networkInterfaces[interfaceName];
|
|
if (interfaces) {
|
|
for (const iface of interfaces) {
|
|
if (iface.family === 'IPv6' && !iface.internal) {
|
|
hasIPv6 = true;
|
|
console.log(`Found IPv6 address: ${iface.address} on ${interfaceName}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`System has IPv6 support: ${hasIPv6}`);
|
|
});
|
|
|
|
tap.test('CCM-09: IPv4 connection test', async () => {
|
|
const smtpClient = createSmtpClient({
|
|
host: '127.0.0.1', // Explicit IPv4
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
let connected = false;
|
|
let connectionFamily = '';
|
|
|
|
smtpClient.on('connection', (info: any) => {
|
|
connected = true;
|
|
if (info && info.socket) {
|
|
connectionFamily = info.socket.remoteFamily || '';
|
|
}
|
|
});
|
|
|
|
smtpClient.on('error', (error: Error) => {
|
|
console.error('IPv4 connection error:', error.message);
|
|
});
|
|
|
|
// Test connection
|
|
const result = await smtpClient.connect();
|
|
expect(result).toBeTruthy();
|
|
expect(smtpClient.isConnected()).toBeTruthy();
|
|
|
|
console.log(`Connected via IPv4, family: ${connectionFamily}`);
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCM-09: IPv6 connection test (if supported)', async () => {
|
|
// Check if IPv6 is available
|
|
const hasIPv6 = await new Promise<boolean>((resolve) => {
|
|
const testSocket = net.createConnection({
|
|
host: '::1',
|
|
port: 1, // Any port, will fail but tells us if IPv6 works
|
|
timeout: 100
|
|
});
|
|
|
|
testSocket.on('error', (err: any) => {
|
|
// ECONNREFUSED means IPv6 works but port is closed (expected)
|
|
// ENETUNREACH or EAFNOSUPPORT means IPv6 not available
|
|
resolve(err.code === 'ECONNREFUSED');
|
|
});
|
|
|
|
testSocket.on('connect', () => {
|
|
testSocket.end();
|
|
resolve(true);
|
|
});
|
|
});
|
|
|
|
if (!hasIPv6) {
|
|
console.log('IPv6 not available on this system, skipping IPv6 tests');
|
|
return;
|
|
}
|
|
|
|
// Try IPv6 connection
|
|
const smtpClient = createSmtpClient({
|
|
host: '::1', // IPv6 loopback
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
let connected = false;
|
|
let connectionFamily = '';
|
|
|
|
smtpClient.on('connection', (info: any) => {
|
|
connected = true;
|
|
if (info && info.socket) {
|
|
connectionFamily = info.socket.remoteFamily || '';
|
|
}
|
|
});
|
|
|
|
smtpClient.on('error', (error: Error) => {
|
|
console.error('IPv6 connection error:', error.message);
|
|
});
|
|
|
|
try {
|
|
const result = await smtpClient.connect();
|
|
if (result && smtpClient.isConnected()) {
|
|
console.log(`Connected via IPv6, family: ${connectionFamily}`);
|
|
await smtpClient.close();
|
|
}
|
|
} catch (error) {
|
|
console.log('IPv6 connection failed (server may not support IPv6):', error.message);
|
|
}
|
|
});
|
|
|
|
tap.test('CCM-09: Hostname resolution preference', async () => {
|
|
// Test that client can handle hostnames that resolve to both IPv4 and IPv6
|
|
const smtpClient = createSmtpClient({
|
|
host: 'localhost', // Should resolve to both 127.0.0.1 and ::1
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 5000,
|
|
debug: true
|
|
});
|
|
|
|
let connectionInfo: any = null;
|
|
|
|
smtpClient.on('connection', (info: any) => {
|
|
connectionInfo = info;
|
|
});
|
|
|
|
const result = await smtpClient.connect();
|
|
expect(result).toBeTruthy();
|
|
expect(smtpClient.isConnected()).toBeTruthy();
|
|
|
|
if (connectionInfo && connectionInfo.socket) {
|
|
console.log(`Connected to localhost via ${connectionInfo.socket.remoteFamily || 'unknown'}`);
|
|
console.log(`Local address: ${connectionInfo.socket.localAddress}`);
|
|
console.log(`Remote address: ${connectionInfo.socket.remoteAddress}`);
|
|
}
|
|
|
|
await smtpClient.close();
|
|
});
|
|
|
|
tap.test('CCM-09: Happy Eyeballs algorithm simulation', async () => {
|
|
// Test connecting to multiple addresses with preference
|
|
const addresses = ['127.0.0.1', '::1', 'localhost'];
|
|
const results: Array<{ address: string; time: number; success: boolean }> = [];
|
|
|
|
for (const address of addresses) {
|
|
const startTime = Date.now();
|
|
const smtpClient = createSmtpClient({
|
|
host: address,
|
|
port: testServer.port,
|
|
secure: false,
|
|
connectionTimeout: 1000,
|
|
debug: false
|
|
});
|
|
|
|
try {
|
|
const connected = await smtpClient.connect();
|
|
const elapsed = Date.now() - startTime;
|
|
results.push({ address, time: elapsed, success: !!connected });
|
|
|
|
if (connected) {
|
|
await smtpClient.close();
|
|
}
|
|
} catch (error) {
|
|
const elapsed = Date.now() - startTime;
|
|
results.push({ address, time: elapsed, success: false });
|
|
}
|
|
}
|
|
|
|
console.log('Connection race results:');
|
|
results.forEach(r => {
|
|
console.log(` ${r.address}: ${r.success ? 'SUCCESS' : 'FAILED'} in ${r.time}ms`);
|
|
});
|
|
|
|
// At least one should succeed
|
|
const successfulConnections = results.filter(r => r.success);
|
|
expect(successfulConnections.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
tap.test('cleanup test SMTP server', async () => {
|
|
if (testServer) {
|
|
await testServer.stop();
|
|
}
|
|
});
|
|
|
|
export default tap.start(); |