Fix for real email tests failing silently

This commit is contained in:
Rory Powell 2023-02-23 13:42:10 +00:00
parent d3a7286711
commit 2d993adec8
5 changed files with 26 additions and 75 deletions

View File

@ -1,3 +1,4 @@
jest.unmock("node-fetch")
import { TestConfiguration } from "../../../../tests" import { TestConfiguration } from "../../../../tests"
import { EmailTemplatePurpose } from "../../../../constants" import { EmailTemplatePurpose } from "../../../../constants"
const nodemailer = require("nodemailer") const nodemailer = require("nodemailer")

View File

@ -13,6 +13,7 @@ export class EmailAPI extends TestAPI {
email: "test@test.com", email: "test@test.com",
purpose, purpose,
tenantId: this.config.getTenantId(), tenantId: this.config.getTenantId(),
userId: this.config.user?._id!,
}) })
.set(this.config.defaultHeaders()) .set(this.config.defaultHeaders())
.expect("Content-Type", /json/) .expect("Content-Type", /json/)

View File

@ -55,8 +55,8 @@ export function smtpEthereal() {
host: "smtp.ethereal.email", host: "smtp.ethereal.email",
secure: false, secure: false,
auth: { auth: {
user: "don.bahringer@ethereal.email", user: "wyatt.zulauf29@ethereal.email",
pass: "yCKSH8rWyUPbnhGYk9", pass: "tEwDtHBWWxusVWAPfa",
}, },
connectionTimeout: 1000, // must be less than the jest default of 5000 connectionTimeout: 1000, // must be less than the jest default of 5000
}, },

View File

@ -1,11 +1,11 @@
import env from "../environment" import env from "../environment"
import { EmailTemplatePurpose, TemplateType, Config } from "../constants" import { EmailTemplatePurpose, TemplateType } from "../constants"
import { getTemplateByPurpose } from "../constants/templates" import { getTemplateByPurpose } from "../constants/templates"
import { getSettingsTemplateContext } from "./templates" import { getSettingsTemplateContext } from "./templates"
import { processString } from "@budibase/string-templates" import { processString } from "@budibase/string-templates"
import { getResetPasswordCode, getInviteCode } from "./redis" import { getResetPasswordCode, getInviteCode } from "./redis"
import { User, Database } from "@budibase/types" import { User, SMTPInnerConfig } from "@budibase/types"
import { tenancy, db as dbCore } from "@budibase/backend-core" import { configs } from "@budibase/backend-core"
const nodemailer = require("nodemailer") const nodemailer = require("nodemailer")
type SendEmailOpts = { type SendEmailOpts = {
@ -36,24 +36,24 @@ const FULL_EMAIL_PURPOSES = [
EmailTemplatePurpose.CUSTOM, EmailTemplatePurpose.CUSTOM,
] ]
function createSMTPTransport(config: any) { function createSMTPTransport(config?: SMTPInnerConfig) {
let options: any let options: any
let secure = config.secure let secure = config?.secure
// default it if not specified // default it if not specified
if (secure == null) { if (secure == null) {
secure = config.port === 465 secure = config?.port === 465
} }
if (!TEST_MODE) { if (!TEST_MODE) {
options = { options = {
port: config.port, port: config?.port,
host: config.host, host: config?.host,
secure: secure, secure: secure,
auth: config.auth, auth: config?.auth,
} }
options.tls = { options.tls = {
rejectUnauthorized: false, rejectUnauthorized: false,
} }
if (config.connectionTimeout) { if (config?.connectionTimeout) {
options.connectionTimeout = config.connectionTimeout options.connectionTimeout = config.connectionTimeout
} }
} else { } else {
@ -134,57 +134,16 @@ async function buildEmail(
}) })
} }
/**
* Utility function for finding most valid SMTP configuration.
* @param {object} db The CouchDB database which is to be looked up within.
* @param {string|null} workspaceId If using finer grain control of configs a workspace can be used.
* @param {boolean|null} automation Whether or not the configuration is being fetched for an email automation.
* @return {Promise<object|null>} returns the SMTP configuration if it exists
*/
async function getSmtpConfiguration(
db: Database,
workspaceId?: string,
automation?: boolean
) {
const params: any = {
type: Config.SMTP,
}
if (workspaceId) {
params.workspace = workspaceId
}
const customConfig = await dbCore.getScopedConfig(db, params)
if (customConfig) {
return customConfig
}
// Use an SMTP fallback configuration from env variables
if (!automation && env.SMTP_FALLBACK_ENABLED) {
return {
port: env.SMTP_PORT,
host: env.SMTP_HOST,
secure: false,
from: env.SMTP_FROM_ADDRESS,
auth: {
user: env.SMTP_USER,
pass: env.SMTP_PASSWORD,
},
}
}
}
/** /**
* Checks if a SMTP config exists based on passed in parameters. * Checks if a SMTP config exists based on passed in parameters.
* @return {Promise<boolean>} returns true if there is a configuration that can be used. * @return {Promise<boolean>} returns true if there is a configuration that can be used.
*/ */
export async function isEmailConfigured(workspaceId?: string) { export async function isEmailConfigured() {
// when "testing" or smtp fallback is enabled simply return true // when "testing" or smtp fallback is enabled simply return true
if (TEST_MODE || env.SMTP_FALLBACK_ENABLED) { if (TEST_MODE || env.SMTP_FALLBACK_ENABLED) {
return true return true
} }
const db = tenancy.getGlobalDB() const config = await configs.getSMTPConfig()
const config = await getSmtpConfiguration(db, workspaceId)
return config != null return config != null
} }
@ -202,22 +161,17 @@ export async function sendEmail(
purpose: EmailTemplatePurpose, purpose: EmailTemplatePurpose,
opts: SendEmailOpts opts: SendEmailOpts
) { ) {
const db = tenancy.getGlobalDB() const config = await configs.getSMTPConfig(opts?.automation)
let config = if (!config && !TEST_MODE) {
(await getSmtpConfiguration(db, opts?.workspaceId, opts?.automation)) || {}
if (Object.keys(config).length === 0 && !TEST_MODE) {
throw "Unable to find SMTP configuration." throw "Unable to find SMTP configuration."
} }
const transport = createSMTPTransport(config) const transport = createSMTPTransport(config)
// if there is a link code needed this will retrieve it // if there is a link code needed this will retrieve it
const code = await getLinkCode(purpose, email, opts.user, opts?.info) const code = await getLinkCode(purpose, email, opts.user, opts?.info)
let context let context = await getSettingsTemplateContext(purpose, code)
if (code) {
context = await getSettingsTemplateContext(purpose, code)
}
let message: any = { let message: any = {
from: opts?.from || config.from, from: opts?.from || config?.from,
html: await buildEmail(purpose, email, context, { html: await buildEmail(purpose, email, context, {
user: opts?.user, user: opts?.user,
contents: opts?.contents, contents: opts?.contents,
@ -231,9 +185,9 @@ export async function sendEmail(
bcc: opts?.bcc, bcc: opts?.bcc,
} }
if (opts?.subject || config.subject) { if (opts?.subject || config?.subject) {
message.subject = await processString( message.subject = await processString(
opts?.subject || config.subject, (opts?.subject || config?.subject) as string,
context context
) )
} }

View File

@ -1,6 +1,5 @@
import { db as dbCore, tenancy } from "@budibase/backend-core" import { tenancy, configs } from "@budibase/backend-core"
import { import {
Config,
InternalTemplateBinding, InternalTemplateBinding,
LOGO_URL, LOGO_URL,
EmailTemplatePurpose, EmailTemplatePurpose,
@ -10,20 +9,16 @@ const BASE_COMPANY = "Budibase"
export async function getSettingsTemplateContext( export async function getSettingsTemplateContext(
purpose: EmailTemplatePurpose, purpose: EmailTemplatePurpose,
code?: string code?: string | null
) { ) {
const db = tenancy.getGlobalDB() let settings = await configs.getSettingsConfig()
// TODO: use more granular settings in the future if required
let settings =
(await dbCore.getScopedConfig(db, { type: Config.SETTINGS })) || {}
const URL = settings.platformUrl const URL = settings.platformUrl
const context: any = { const context: any = {
[InternalTemplateBinding.LOGO_URL]: [InternalTemplateBinding.LOGO_URL]:
checkSlashesInUrl(`${URL}/${settings.logoUrl}`) || LOGO_URL, checkSlashesInUrl(`${URL}/${settings.logoUrl}`) || LOGO_URL,
[InternalTemplateBinding.PLATFORM_URL]: URL, [InternalTemplateBinding.PLATFORM_URL]: URL,
[InternalTemplateBinding.COMPANY]: settings.company || BASE_COMPANY, [InternalTemplateBinding.COMPANY]: settings.company || BASE_COMPANY,
[InternalTemplateBinding.DOCS_URL]: [InternalTemplateBinding.DOCS_URL]: "https://docs.budibase.com/",
settings.docsUrl || "https://docs.budibase.com/",
[InternalTemplateBinding.LOGIN_URL]: checkSlashesInUrl( [InternalTemplateBinding.LOGIN_URL]: checkSlashesInUrl(
tenancy.addTenantToUrl(`${URL}/login`) tenancy.addTenantToUrl(`${URL}/login`)
), ),