import nodemailer from 'nodemailer'; import { SMTPWSTransport, createTransport } from '../src/transport'; describe('SMTPWSTransport', () => { let transport: SMTPWSTransport; beforeEach(() => { transport = createTransport({ host: 'localhost', port: 3000, auth: { user: 'test-api-key' }, debug: false }); }); afterEach(async () => { await transport.close(); }); describe('constructor', () => { it('should create transport with correct configuration', () => { expect(transport.name).toBe('SMTPWS'); expect(transport.version).toBe('1.0.0'); }); it('should handle secure connection configuration', () => { const secureTransport = createTransport({ host: 'localhost', port: 443, secure: true, auth: { user: 'test-key' }, debug: false }); const info = secureTransport.getTransportInfo(); expect(info.secure).toBe(true); expect(info.port).toBe(443); }); }); describe('getTransportInfo', () => { it('should return transport information', () => { const info = transport.getTransportInfo(); expect(info).toMatchObject({ name: 'SMTPWS', version: '1.0.0', host: 'localhost', port: 3000, secure: false }); }); }); describe('send', () => { it('should send mail message', async () => { const mockMail = { data: { envelope: { from: 'test@example.com', to: ['recipient@example.com'] }, raw: 'Subject: Test\r\n\r\nTest message' } }; // Mock the internal client const mockSendCommand = jest.fn() .mockResolvedValueOnce('250 Hello') // EHLO .mockResolvedValueOnce('250 OK') // MAIL FROM .mockResolvedValueOnce('250 OK') // RCPT TO .mockResolvedValueOnce('354 Start mail input') // DATA .mockResolvedValueOnce('250 Message accepted') // Message content .mockResolvedValueOnce('221 Bye'); // QUIT (transport as any).client.sendSMTPCommand = mockSendCommand; const result = await transport.send(mockMail); expect(result).toMatchObject({ envelope: mockMail.data.envelope, accepted: ['recipient@example.com'], rejected: [], pending: [] }); expect(mockSendCommand).toHaveBeenCalledTimes(6); }); it('should handle rejected recipients', async () => { const mockMail = { data: { envelope: { from: 'test@example.com', to: ['good@example.com', 'bad@example.com'] }, raw: 'Subject: Test\r\n\r\nTest message' } }; const mockSendCommand = jest.fn() .mockResolvedValueOnce('250 Hello') // EHLO .mockResolvedValueOnce('250 OK') // MAIL FROM .mockResolvedValueOnce('250 OK') // RCPT TO (good) .mockResolvedValueOnce('550 No such user') // RCPT TO (bad) .mockResolvedValueOnce('354 Start mail input') // DATA .mockResolvedValueOnce('250 Message accepted') // Message content .mockResolvedValueOnce('221 Bye'); // QUIT (transport as any).client.sendSMTPCommand = mockSendCommand; const result = await transport.send(mockMail); expect(result.accepted).toEqual(['good@example.com']); expect(result.rejected).toEqual(['bad@example.com']); }); it('should call callback on success', (done) => { const mockMail = { data: { envelope: { from: 'test@example.com', to: ['recipient@example.com'] }, raw: 'Test message' } }; const mockSendCommand = jest.fn() .mockResolvedValueOnce('250 Hello') .mockResolvedValueOnce('250 OK') .mockResolvedValueOnce('250 OK') .mockResolvedValueOnce('354 Start mail input') .mockResolvedValueOnce('250 Message accepted') .mockResolvedValueOnce('221 Bye'); (transport as any).client.sendSMTPCommand = mockSendCommand; transport.send(mockMail, (err, info) => { expect(err).toBeNull(); expect(info).toBeDefined(); expect(info?.accepted).toEqual(['recipient@example.com']); done(); }); }); it('should call callback on error', (done) => { const mockMail = { envelope: { from: 'test@example.com', to: ['recipient@example.com'] }, raw: 'Test message' }; const mockSendCommand = jest.fn() .mockRejectedValueOnce(new Error('Connection failed')); (transport as any).client.sendSMTPCommand = mockSendCommand; transport.send(mockMail, (err, info) => { expect(err).toBeDefined(); expect(err?.message).toBe('Connection failed'); expect(info).toBeUndefined(); done(); }); }); }); describe('verify', () => { it('should verify transport connectivity', async () => { const mockSendCommand = jest.fn() .mockResolvedValueOnce('250 Hello'); (transport as any).client.sendSMTPCommand = mockSendCommand; const result = await transport.verify(); expect(result).toBe(true); expect(mockSendCommand).toHaveBeenCalledWith('EHLO transport-verify\r\n'); }); it('should throw error on verification failure', async () => { const mockSendCommand = jest.fn() .mockRejectedValueOnce(new Error('Connection refused')); (transport as any).client.sendSMTPCommand = mockSendCommand; await expect(transport.verify()).rejects.toThrow('Transport verification failed'); }); it('should throw error on missing API key during construction', async () => { expect(() => createTransport({ host: 'localhost', auth: { user: '' }, debug: false })).toThrow('API key is required'); }); }); describe('isIdle', () => { it('should return true when transport is idle', () => { expect(transport.isIdle()).toBe(true); }); it('should return false when transport has queued messages', () => { // Mock queue size (transport as any).client.getQueueSize = jest.fn().mockReturnValue(5); expect(transport.isIdle()).toBe(false); }); }); describe('nodemailer integration', () => { it('should work as nodemailer transport', async () => { const transporter = nodemailer.createTransport(transport); expect(transporter).toBeDefined(); }); }); });