281 lines
7.4 KiB
TypeScript
281 lines
7.4 KiB
TypeScript
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|||
|
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
|||
|
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js';
|
|||
|
import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.js';
|
|||
|
import { Email } from '../../../ts/mail/core/classes.email.js';
|
|||
|
|
|||
|
let authServer: ITestServer;
|
|||
|
|
|||
|
tap.test('setup - start SMTP server with authentication', async () => {
|
|||
|
authServer = await startTestServer({
|
|||
|
port: 2580,
|
|||
|
tlsEnabled: false,
|
|||
|
authRequired: true
|
|||
|
});
|
|||
|
|
|||
|
expect(authServer.port).toEqual(2580);
|
|||
|
expect(authServer.config.authRequired).toBeTrue();
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should fail without credentials', async () => {
|
|||
|
let errorCaught = false;
|
|||
|
|
|||
|
try {
|
|||
|
const noAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000
|
|||
|
// No auth provided
|
|||
|
});
|
|||
|
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: 'No Auth Test',
|
|||
|
text: 'Should fail without authentication'
|
|||
|
});
|
|||
|
|
|||
|
await noAuthClient.sendMail(email);
|
|||
|
} catch (error: any) {
|
|||
|
errorCaught = true;
|
|||
|
expect(error).toBeInstanceOf(Error);
|
|||
|
console.log('✅ Authentication required error:', error.message);
|
|||
|
}
|
|||
|
|
|||
|
expect(errorCaught).toBeTrue();
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should authenticate with PLAIN mechanism', async () => {
|
|||
|
const plainAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000,
|
|||
|
auth: {
|
|||
|
user: 'testuser',
|
|||
|
pass: 'testpass',
|
|||
|
method: 'PLAIN'
|
|||
|
},
|
|||
|
debug: true
|
|||
|
});
|
|||
|
|
|||
|
const isConnected = await plainAuthClient.verify();
|
|||
|
expect(isConnected).toBeTrue();
|
|||
|
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: 'PLAIN Auth Test',
|
|||
|
text: 'Sent with PLAIN authentication'
|
|||
|
});
|
|||
|
|
|||
|
const result = await plainAuthClient.sendMail(email);
|
|||
|
expect(result.success).toBeTrue();
|
|||
|
|
|||
|
await plainAuthClient.close();
|
|||
|
console.log('✅ PLAIN authentication successful');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should authenticate with LOGIN mechanism', async () => {
|
|||
|
const loginAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000,
|
|||
|
auth: {
|
|||
|
user: 'testuser',
|
|||
|
pass: 'testpass',
|
|||
|
method: 'LOGIN'
|
|||
|
},
|
|||
|
debug: true
|
|||
|
});
|
|||
|
|
|||
|
const isConnected = await loginAuthClient.verify();
|
|||
|
expect(isConnected).toBeTrue();
|
|||
|
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: 'LOGIN Auth Test',
|
|||
|
text: 'Sent with LOGIN authentication'
|
|||
|
});
|
|||
|
|
|||
|
const result = await loginAuthClient.sendMail(email);
|
|||
|
expect(result.success).toBeTrue();
|
|||
|
|
|||
|
await loginAuthClient.close();
|
|||
|
console.log('✅ LOGIN authentication successful');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should auto-select authentication method', async () => {
|
|||
|
const autoAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000,
|
|||
|
auth: {
|
|||
|
user: 'testuser',
|
|||
|
pass: 'testpass'
|
|||
|
// No method specified - should auto-select
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
const isConnected = await autoAuthClient.verify();
|
|||
|
expect(isConnected).toBeTrue();
|
|||
|
|
|||
|
await autoAuthClient.close();
|
|||
|
console.log('✅ Auto-selected authentication method');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should handle invalid credentials', async () => {
|
|||
|
let authFailed = false;
|
|||
|
|
|||
|
try {
|
|||
|
const badAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000,
|
|||
|
auth: {
|
|||
|
user: 'wronguser',
|
|||
|
pass: 'wrongpass'
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
await badAuthClient.verify();
|
|||
|
} catch (error: any) {
|
|||
|
authFailed = true;
|
|||
|
expect(error).toBeInstanceOf(Error);
|
|||
|
console.log('✅ Invalid credentials rejected:', error.message);
|
|||
|
}
|
|||
|
|
|||
|
expect(authFailed).toBeTrue();
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should handle special characters in credentials', async () => {
|
|||
|
const specialAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000,
|
|||
|
auth: {
|
|||
|
user: 'user@domain.com',
|
|||
|
pass: 'p@ssw0rd!#$%'
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Server might accept or reject based on implementation
|
|||
|
try {
|
|||
|
await specialAuthClient.verify();
|
|||
|
await specialAuthClient.close();
|
|||
|
console.log('✅ Special characters in credentials handled');
|
|||
|
} catch (error) {
|
|||
|
console.log('ℹ️ Test server rejected special character credentials');
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should prefer secure auth over TLS', async () => {
|
|||
|
// Start TLS-enabled server
|
|||
|
const tlsAuthServer = await startTestServer({
|
|||
|
port: 2581,
|
|||
|
tlsEnabled: true,
|
|||
|
authRequired: true
|
|||
|
});
|
|||
|
|
|||
|
const tlsAuthClient = createSmtpClient({
|
|||
|
host: tlsAuthServer.hostname,
|
|||
|
port: tlsAuthServer.port,
|
|||
|
secure: true,
|
|||
|
connectionTimeout: 5000,
|
|||
|
auth: {
|
|||
|
user: 'testuser',
|
|||
|
pass: 'testpass'
|
|||
|
},
|
|||
|
tls: {
|
|||
|
rejectUnauthorized: false
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
const isConnected = await tlsAuthClient.verify();
|
|||
|
expect(isConnected).toBeTrue();
|
|||
|
|
|||
|
await tlsAuthClient.close();
|
|||
|
await stopTestServer(tlsAuthServer);
|
|||
|
console.log('✅ Secure authentication over TLS');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should maintain auth state across multiple sends', async () => {
|
|||
|
const persistentAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
connectionTimeout: 5000,
|
|||
|
auth: {
|
|||
|
user: 'testuser',
|
|||
|
pass: 'testpass'
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
await persistentAuthClient.verify();
|
|||
|
|
|||
|
// Send multiple emails without re-authenticating
|
|||
|
for (let i = 0; i < 3; i++) {
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: 'recipient@example.com',
|
|||
|
subject: `Persistent Auth Test ${i + 1}`,
|
|||
|
text: `Email ${i + 1} using same auth session`
|
|||
|
});
|
|||
|
|
|||
|
const result = await persistentAuthClient.sendMail(email);
|
|||
|
expect(result.success).toBeTrue();
|
|||
|
}
|
|||
|
|
|||
|
await persistentAuthClient.close();
|
|||
|
console.log('✅ Authentication state maintained across sends');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('CCMD-05: AUTH - should handle auth with connection pooling', async () => {
|
|||
|
const pooledAuthClient = createSmtpClient({
|
|||
|
host: authServer.hostname,
|
|||
|
port: authServer.port,
|
|||
|
secure: false,
|
|||
|
pool: true,
|
|||
|
maxConnections: 3,
|
|||
|
connectionTimeout: 5000,
|
|||
|
auth: {
|
|||
|
user: 'testuser',
|
|||
|
pass: 'testpass'
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Send concurrent emails with pooled authenticated connections
|
|||
|
const promises = [];
|
|||
|
for (let i = 0; i < 5; i++) {
|
|||
|
const email = new Email({
|
|||
|
from: 'sender@example.com',
|
|||
|
to: `recipient${i}@example.com`,
|
|||
|
subject: `Pooled Auth Test ${i}`,
|
|||
|
text: 'Testing auth with connection pooling'
|
|||
|
});
|
|||
|
promises.push(pooledAuthClient.sendMail(email));
|
|||
|
}
|
|||
|
|
|||
|
const results = await Promise.all(promises);
|
|||
|
results.forEach(result => {
|
|||
|
expect(result.success).toBeTrue();
|
|||
|
});
|
|||
|
|
|||
|
const poolStatus = pooledAuthClient.getPoolStatus();
|
|||
|
console.log('📊 Auth pool status:', poolStatus);
|
|||
|
|
|||
|
await pooledAuthClient.close();
|
|||
|
console.log('✅ Authentication works with connection pooling');
|
|||
|
});
|
|||
|
|
|||
|
tap.test('cleanup - stop auth server', async () => {
|
|||
|
await stopTestServer(authServer);
|
|||
|
});
|
|||
|
|
|||
|
tap.start();
|