diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts index 13df772c6..b8d29ef81 100644 --- a/server/tests/api/server/email.ts +++ b/server/tests/api/server/email.ts @@ -20,7 +20,7 @@ import { ServerInfo, setAccessTokensToServers } from '../../../../shared/utils' -import { mockSmtpServer } from '../../../../shared/utils/miscs/email' +import { MockSmtpServer } from '../../../../shared/utils/miscs/email' import { waitJobs } from '../../../../shared/utils/server/jobs' const expect = chai.expect @@ -41,7 +41,7 @@ describe('Test emails', function () { before(async function () { this.timeout(30000) - await mockSmtpServer(emails) + await MockSmtpServer.Instance.collectEmails(emails) await flushTests() diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts index b1733e45e..afc8a0059 100644 --- a/server/tests/api/users/users-verification.ts +++ b/server/tests/api/users/users-verification.ts @@ -7,7 +7,7 @@ import { userLogin, login, runServer, ServerInfo, verifyEmail, updateCustomSubConfig } from '../../../../shared/utils' import { setAccessTokensToServers } from '../../../../shared/utils/users/login' -import { mockSmtpServer } from '../../../../shared/utils/miscs/email' +import { MockSmtpServer } from '../../../../shared/utils/miscs/email' import { waitJobs } from '../../../../shared/utils/server/jobs' const expect = chai.expect @@ -30,7 +30,7 @@ describe('Test users account verification', function () { before(async function () { this.timeout(30000) - await mockSmtpServer(emails) + await MockSmtpServer.Instance.collectEmails(emails) await flushTests() diff --git a/shared/utils/miscs/email-child-process.js b/shared/utils/miscs/email-child-process.js new file mode 100644 index 000000000..40ae37d70 --- /dev/null +++ b/shared/utils/miscs/email-child-process.js @@ -0,0 +1,27 @@ +const MailDev = require('maildev') + +// must run maildev as forked ChildProcess +// failed instantiation stops main process with exit code 0 +process.on('message', (msg) => { + if (msg.start) { + const maildev = new MailDev({ + ip: '127.0.0.1', + smtp: 1025, + disableWeb: true, + silent: true + }) + + maildev.on('new', email => { + process.send({ email }) + }) + + maildev.listen(err => { + if (err) { + // cannot send as Error object + return process.send({ err: err.message }) + } + + return process.send({ err: null }) + }) + } +}) diff --git a/shared/utils/miscs/email.ts b/shared/utils/miscs/email.ts index 21accd09d..108f7d3d9 100644 --- a/shared/utils/miscs/email.ts +++ b/shared/utils/miscs/email.ts @@ -1,25 +1,55 @@ -import * as MailDev from 'maildev' +import * as child from 'child_process' -function mockSmtpServer (emailsCollection: object[]) { - const maildev = new MailDev({ - ip: '127.0.0.1', - smtp: 1025, - disableWeb: true, - silent: true - }) - maildev.on('new', email => emailsCollection.push(email)) +class MockSmtpServer { - return new Promise((res, rej) => { - maildev.listen(err => { - if (err) return rej(err) + private static instance: MockSmtpServer + private started = false + private emailChildProcess: child.ChildProcess + private emails: object[] - return res() + private constructor () { + this.emailChildProcess = child.fork(`${__dirname}/email-child-process`, [], { silent: true }) + this.emailChildProcess.on('message', (msg) => { + if (msg.email) { + return this.emails.push(msg.email) + } }) - }) + process.on('exit', () => { + this.emailChildProcess.kill() + }) + } + + collectEmails (emailsCollection: object[]) { + return new Promise((res, rej) => { + if (this.started) { + this.emails = emailsCollection + return res() + } + + // ensure maildev isn't started until + // unexpected exit can be reported to test runner + this.emailChildProcess.send({ start: true }) + this.emailChildProcess.on('exit', () => { + return rej(new Error('maildev exited unexpectedly, confirm port not in use')) + }) + this.emailChildProcess.on('message', (msg) => { + if (msg.err) { + return rej(new Error(msg.err)) + } + this.started = true + this.emails = emailsCollection + return res() + }) + }) + } + + static get Instance () { + return this.instance || (this.instance = new this()) + } } // --------------------------------------------------------------------------- export { - mockSmtpServer + MockSmtpServer }