123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
|
|
import { Email } from '../ts/mail/core/classes.email.js';
|
|
import { DKIMCreator } from '../ts/mail/security/classes.dkimcreator.js';
|
|
import { UnifiedEmailServer } from '../ts/mail/routing/classes.unified.email.server.js';
|
|
|
|
const storageMap = new Map<string, string>();
|
|
const serversToCleanup: UnifiedEmailServer[] = [];
|
|
const mockDcRouter = {
|
|
storageManager: {
|
|
get: async (key: string) => storageMap.get(key) || null,
|
|
set: async (key: string, value: string) => {
|
|
storageMap.set(key, value);
|
|
},
|
|
list: async (prefix: string) => Array.from(storageMap.keys()).filter((key) => key.startsWith(prefix)),
|
|
delete: async (key: string) => {
|
|
storageMap.delete(key);
|
|
},
|
|
},
|
|
};
|
|
|
|
tap.test('UnifiedEmailServer.sendEmail returns the actual queue item id', async () => {
|
|
const server = new UnifiedEmailServer(mockDcRouter, {
|
|
ports: [10025],
|
|
hostname: 'mail.example.com',
|
|
domains: [{ domain: 'example.com', dnsMode: 'forward' }],
|
|
routes: [],
|
|
});
|
|
serversToCleanup.push(server);
|
|
|
|
const route = {
|
|
name: 'test-deliver-route',
|
|
match: { recipients: '*@*' },
|
|
action: { type: 'deliver' as const },
|
|
};
|
|
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: ['recipient@example.net'],
|
|
subject: 'Queue ID contract',
|
|
text: 'hello',
|
|
});
|
|
|
|
const queueId = await server.sendEmail(email, 'mta', route);
|
|
const queuedItem = server.getQueueItem(queueId);
|
|
|
|
expect(queuedItem).toBeTruthy();
|
|
expect(queuedItem?.id).toEqual(queueId);
|
|
expect(server.getQueueStats().queueSize).toEqual(1);
|
|
expect(server.getQueueItems().map((item) => item.id)).toContain(queueId);
|
|
});
|
|
|
|
tap.test('UnifiedEmailServer.sendOutboundEmail uses outbound.hostname when configured', async () => {
|
|
const server = new UnifiedEmailServer(mockDcRouter, {
|
|
ports: [10026],
|
|
hostname: 'mail.example.com',
|
|
outbound: {
|
|
hostname: 'outbound.example.com',
|
|
},
|
|
domains: [{ domain: 'example.com', dnsMode: 'forward' }],
|
|
routes: [],
|
|
});
|
|
serversToCleanup.push(server);
|
|
|
|
const email = new Email({
|
|
from: 'sender@example.com',
|
|
to: ['recipient@example.net'],
|
|
subject: 'Outbound hostname contract',
|
|
text: 'hello',
|
|
});
|
|
|
|
let capturedOptions: any;
|
|
(server as any).rustBridge.sendOutboundEmail = async (options: any) => {
|
|
capturedOptions = options;
|
|
return {
|
|
accepted: ['recipient@example.net'],
|
|
rejected: [],
|
|
messageId: 'test-message-id',
|
|
response: '250 2.0.0 queued',
|
|
envelope: {
|
|
from: 'sender@example.com',
|
|
to: ['recipient@example.net'],
|
|
},
|
|
};
|
|
};
|
|
|
|
await server.sendOutboundEmail('smtp.target.example', 25, email);
|
|
|
|
expect(capturedOptions).toBeTruthy();
|
|
expect(capturedOptions.domain).toEqual('outbound.example.com');
|
|
});
|
|
|
|
tap.test('DKIMCreator returns selector-aligned DNS record names', async () => {
|
|
const tempDir = path.join(os.tmpdir(), `smartmta-dkim-${Date.now()}`);
|
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
|
|
try {
|
|
const creator = new DKIMCreator(tempDir);
|
|
|
|
await creator.createAndStoreDKIMKeys('example.com');
|
|
const defaultRecord = await creator.getDNSRecordForDomain('example.com');
|
|
expect(defaultRecord.name).toEqual('default._domainkey.example.com');
|
|
|
|
await creator.createAndStoreDKIMKeysForSelector('example.org', 'selector1');
|
|
const selectorRecord = await creator.getDNSRecordForDomain('example.org', 'selector1');
|
|
expect(selectorRecord.name).toEqual('selector1._domainkey.example.org');
|
|
} finally {
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
tap.test('cleanup', async () => {
|
|
for (const server of serversToCleanup) {
|
|
await server.stop();
|
|
}
|
|
await tap.stopForcefully();
|
|
});
|
|
|
|
export default tap.start();
|