194 lines
5.8 KiB
TypeScript
194 lines
5.8 KiB
TypeScript
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||
|
|
import * as plugins from '../ts/plugins.js';
|
||
|
|
import { EmailDomainManager } from '../ts/email/index.js';
|
||
|
|
import { DcRouterDb, DomainDoc } from '../ts/db/index.js';
|
||
|
|
import { EmailDomainDoc } from '../ts/db/documents/classes.email-domain.doc.js';
|
||
|
|
import type { IUnifiedEmailServerOptions } from '@push.rocks/smartmta';
|
||
|
|
|
||
|
|
const createTestDb = async () => {
|
||
|
|
const storagePath = plugins.path.join(
|
||
|
|
plugins.os.tmpdir(),
|
||
|
|
`dcrouter-email-domain-manager-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||
|
|
);
|
||
|
|
|
||
|
|
DcRouterDb.resetInstance();
|
||
|
|
const db = DcRouterDb.getInstance({
|
||
|
|
storagePath,
|
||
|
|
dbName: `dcrouter-email-domain-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
||
|
|
});
|
||
|
|
await db.start();
|
||
|
|
await db.getDb().mongoDb.createCollection('__test_init');
|
||
|
|
|
||
|
|
return {
|
||
|
|
async cleanup() {
|
||
|
|
await db.stop();
|
||
|
|
DcRouterDb.resetInstance();
|
||
|
|
await plugins.fs.promises.rm(storagePath, { recursive: true, force: true });
|
||
|
|
},
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
const testDbPromise = createTestDb();
|
||
|
|
|
||
|
|
const clearTestState = async () => {
|
||
|
|
for (const emailDomain of await EmailDomainDoc.findAll()) {
|
||
|
|
await emailDomain.delete();
|
||
|
|
}
|
||
|
|
for (const domain of await DomainDoc.findAll()) {
|
||
|
|
await domain.delete();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const createDomainDoc = async (id: string, name: string, source: 'dcrouter' | 'provider') => {
|
||
|
|
const doc = new DomainDoc();
|
||
|
|
doc.id = id;
|
||
|
|
doc.name = name;
|
||
|
|
doc.source = source;
|
||
|
|
doc.authoritative = source === 'dcrouter';
|
||
|
|
doc.createdAt = Date.now();
|
||
|
|
doc.updatedAt = Date.now();
|
||
|
|
doc.createdBy = 'test';
|
||
|
|
await doc.save();
|
||
|
|
return doc;
|
||
|
|
};
|
||
|
|
|
||
|
|
const createBaseEmailConfig = (): IUnifiedEmailServerOptions => ({
|
||
|
|
ports: [2525],
|
||
|
|
hostname: 'mail.example.com',
|
||
|
|
domains: [
|
||
|
|
{
|
||
|
|
domain: 'static.example.com',
|
||
|
|
dnsMode: 'external-dns',
|
||
|
|
},
|
||
|
|
],
|
||
|
|
routes: [],
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('EmailDomainManager syncs managed domains into runtime config and email server', async () => {
|
||
|
|
await testDbPromise;
|
||
|
|
await clearTestState();
|
||
|
|
|
||
|
|
const linkedDomain = await createDomainDoc('provider-domain', 'example.com', 'provider');
|
||
|
|
const updateCalls: Array<{ domains?: any[] }> = [];
|
||
|
|
|
||
|
|
const dcRouterStub = {
|
||
|
|
options: {
|
||
|
|
emailConfig: createBaseEmailConfig(),
|
||
|
|
},
|
||
|
|
emailServer: {
|
||
|
|
updateOptions: (options: { domains?: any[] }) => {
|
||
|
|
updateCalls.push(options);
|
||
|
|
},
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const manager = new EmailDomainManager(dcRouterStub);
|
||
|
|
await manager.start();
|
||
|
|
|
||
|
|
const created = await manager.createEmailDomain({
|
||
|
|
linkedDomainId: linkedDomain.id,
|
||
|
|
subdomain: 'mail',
|
||
|
|
dkimSelector: 'selector1',
|
||
|
|
rotateKeys: true,
|
||
|
|
rotationIntervalDays: 30,
|
||
|
|
});
|
||
|
|
|
||
|
|
const domainsAfterCreate = dcRouterStub.options.emailConfig.domains;
|
||
|
|
expect(domainsAfterCreate.length).toEqual(2);
|
||
|
|
expect(domainsAfterCreate.some((domain) => domain.domain === 'static.example.com')).toEqual(true);
|
||
|
|
|
||
|
|
const managedDomain = domainsAfterCreate.find((domain) => domain.domain === 'mail.example.com');
|
||
|
|
expect(managedDomain).toBeTruthy();
|
||
|
|
expect(managedDomain?.dnsMode).toEqual('external-dns');
|
||
|
|
expect(managedDomain?.dkim?.selector).toEqual('selector1');
|
||
|
|
expect(updateCalls.at(-1)?.domains?.some((domain) => domain.domain === 'mail.example.com')).toEqual(true);
|
||
|
|
|
||
|
|
await manager.updateEmailDomain(created.id, {
|
||
|
|
rotateKeys: false,
|
||
|
|
rateLimits: {
|
||
|
|
outbound: {
|
||
|
|
messagesPerMinute: 10,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const domainsAfterUpdate = dcRouterStub.options.emailConfig.domains;
|
||
|
|
const updatedManagedDomain = domainsAfterUpdate.find((domain) => domain.domain === 'mail.example.com');
|
||
|
|
expect(updatedManagedDomain?.dkim?.rotateKeys).toEqual(false);
|
||
|
|
expect(updatedManagedDomain?.rateLimits?.outbound?.messagesPerMinute).toEqual(10);
|
||
|
|
|
||
|
|
await manager.deleteEmailDomain(created.id);
|
||
|
|
expect(dcRouterStub.options.emailConfig.domains.map((domain) => domain.domain)).toEqual(['static.example.com']);
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('EmailDomainManager rejects domains already present in static config', async () => {
|
||
|
|
await testDbPromise;
|
||
|
|
await clearTestState();
|
||
|
|
|
||
|
|
const linkedDomain = await createDomainDoc('static-domain', 'static.example.com', 'provider');
|
||
|
|
const dcRouterStub = {
|
||
|
|
options: {
|
||
|
|
emailConfig: createBaseEmailConfig(),
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const manager = new EmailDomainManager(dcRouterStub);
|
||
|
|
|
||
|
|
let error: Error | undefined;
|
||
|
|
try {
|
||
|
|
await manager.createEmailDomain({ linkedDomainId: linkedDomain.id });
|
||
|
|
} catch (err: unknown) {
|
||
|
|
error = err as Error;
|
||
|
|
}
|
||
|
|
|
||
|
|
expect(error?.message).toEqual('Email domain already configured for static.example.com');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('EmailDomainManager start merges persisted managed domains after restart', async () => {
|
||
|
|
await testDbPromise;
|
||
|
|
await clearTestState();
|
||
|
|
|
||
|
|
const linkedDomain = await createDomainDoc('local-domain', 'managed.example.com', 'dcrouter');
|
||
|
|
const stored = new EmailDomainDoc();
|
||
|
|
stored.id = 'managed-email-domain';
|
||
|
|
stored.domain = 'mail.managed.example.com';
|
||
|
|
stored.linkedDomainId = linkedDomain.id;
|
||
|
|
stored.subdomain = 'mail';
|
||
|
|
stored.dkim = {
|
||
|
|
selector: 'default',
|
||
|
|
keySize: 2048,
|
||
|
|
rotateKeys: false,
|
||
|
|
rotationIntervalDays: 90,
|
||
|
|
};
|
||
|
|
stored.dnsStatus = {
|
||
|
|
mx: 'unchecked',
|
||
|
|
spf: 'unchecked',
|
||
|
|
dkim: 'unchecked',
|
||
|
|
dmarc: 'unchecked',
|
||
|
|
};
|
||
|
|
stored.createdAt = new Date().toISOString();
|
||
|
|
stored.updatedAt = new Date().toISOString();
|
||
|
|
await stored.save();
|
||
|
|
|
||
|
|
const dcRouterStub = {
|
||
|
|
options: {
|
||
|
|
emailConfig: createBaseEmailConfig(),
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
const manager = new EmailDomainManager(dcRouterStub);
|
||
|
|
await manager.start();
|
||
|
|
|
||
|
|
const managedDomain = dcRouterStub.options.emailConfig.domains.find((domain) => domain.domain === 'mail.managed.example.com');
|
||
|
|
expect(managedDomain?.dnsMode).toEqual('internal-dns');
|
||
|
|
});
|
||
|
|
|
||
|
|
tap.test('cleanup', async () => {
|
||
|
|
const testDb = await testDbPromise;
|
||
|
|
await clearTestState();
|
||
|
|
await testDb.cleanup();
|
||
|
|
await tap.stopForcefully();
|
||
|
|
});
|
||
|
|
|
||
|
|
export default tap.start();
|