budibase/packages/worker/src/tests/mocks/email.ts

171 lines
3.7 KiB
TypeScript

import MailDev from "maildev"
import { promisify } from "util"
import TestConfiguration from "../TestConfiguration"
export type Mailserver = InstanceType<typeof MailDev>
export type MailserverConfig = ConstructorParameters<typeof MailDev>[0]
export interface Attachment {
checksum: string
contentId: string
contentType: string
fileName: string
generatedFileName: string
length: number
transferEncoding: string
transformed: boolean
}
export interface Address {
address: string
args?: boolean
name?: string
}
export interface Alternative {
contentType: string
content: string
charset: string
method: string
transferEncoding: string
}
export interface Envelope {
from: Address
to: Address[]
host: string
remoteAddress: string
}
export interface Email {
attachments: Attachment[]
alternatives: Alternative[]
calculatedBcc: Address[]
cc: Address[]
date: string
envelope: Envelope
from: Address[]
headers: Record<string, string>
html: string
id: string
messageId: string
priority: string
read: boolean
size: number
sizeHuman: string
source: string
time: Date
to: Address[]
}
export function getUnusedPort(): Promise<number> {
return new Promise((resolve, reject) => {
const server = require("net").createServer()
server.unref()
server.on("error", reject)
server.listen(0, () => {
const port = server.address().port
server.close(() => {
resolve(port)
})
})
})
}
export async function captureEmail(
mailserver: Mailserver,
f: () => Promise<void>
): Promise<Email> {
const timeoutMs = 5000
let timeout: ReturnType<typeof setTimeout> | undefined = undefined
const cancel = () => {
if (timeout) {
clearTimeout(timeout)
timeout = undefined
}
}
const timeoutPromise = new Promise<never>((_, reject) => {
timeout = setTimeout(() => {
reject(new Error("Timed out waiting for email"))
}, timeoutMs)
})
const mailPromise = new Promise<Email>(resolve => {
// @ts-expect-error - types are wrong
mailserver.once("new", email => {
resolve(email as Email)
cancel()
})
})
const emailPromise = Promise.race([mailPromise, timeoutPromise])
try {
await f()
} finally {
cancel()
}
return await emailPromise
}
export async function startMailserver(
config: TestConfiguration,
opts?: MailserverConfig
): Promise<Mailserver> {
if (!opts) {
opts = {}
}
if (!opts.smtp) {
opts.smtp = await getUnusedPort()
}
const mailserver = new MailDev(opts || {})
await new Promise((resolve, reject) => {
mailserver.listen(err => {
if (err) {
return reject(err)
}
resolve(mailserver)
})
})
await config.saveSmtpConfig({
host: "localhost",
port: opts.smtp,
secure: false,
from: "test@example.com",
})
return mailserver
}
export function deleteAllEmail(mailserver: Mailserver) {
return promisify(mailserver.deleteAllEmail).bind(mailserver)()
}
export function stopMailserver(mailserver: Mailserver) {
return promisify(mailserver.close).bind(mailserver)()
}
export function getAttachment(
mailserver: Mailserver,
email: Email,
attachment: Attachment
) {
return new Promise<string>(resolve => {
// @ts-expect-error - types are wrong
mailserver.getEmailAttachment(
email.id,
attachment.generatedFileName,
(err: any, _contentType: string, stream: ReadableStream) => {
if (err) {
throw err
}
resolve(new Response(stream).text())
}
)
})
}
export function getAttachments(mailserver: Mailserver, email: Email) {
return Promise.all(
email.attachments.map(attachment =>
getAttachment(mailserver, email, attachment)
)
)
}