Merge pull request #1224 from gradido/test-send-email

feat: Test and Refactor Send Email
This commit is contained in:
Moriz Wahl 2022-01-13 10:32:49 +01:00 committed by GitHub
commit b53fe452c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 326 additions and 75 deletions

View File

@ -520,7 +520,7 @@ jobs:
report_name: Coverage Backend
type: lcov
result_path: ./backend/coverage/lcov.info
min_coverage: 40
min_coverage: 41
token: ${{ github.token }}
##############################################################################

View File

@ -6,7 +6,7 @@ import { Resolver, Query, Args, Authorized, Ctx, Mutation } from 'type-graphql'
import { getCustomRepository, getConnection, QueryRunner } from 'typeorm'
import CONFIG from '../../config'
import { sendEMail } from '../../util/sendEMail'
import { sendTransactionReceivedEmail } from '../../mailer/sendTransactionReceivedEmail'
import { Transaction } from '../model/Transaction'
import { TransactionList } from '../model/TransactionList'
@ -651,21 +651,14 @@ export class TransactionResolver {
}
// send notification email
// TODO: translate
await sendEMail({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: `${recipiantUser.firstName} ${recipiantUser.lastName} <${recipiantUser.email}>`,
subject: 'Gradido Überweisung',
text: `Hallo ${recipiantUser.firstName} ${recipiantUser.lastName}
Du hast soeben ${amount} GDD von ${senderUser.firstName} ${senderUser.lastName} erhalten.
${senderUser.firstName} ${senderUser.lastName} schreibt:
${memo}
Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen,
dein Gradido-Team`,
await sendTransactionReceivedEmail({
senderFirstName: senderUser.firstName,
senderLastName: senderUser.lastName,
recipientFirstName: recipiantUser.firstName,
recipientLastName: recipiantUser.lastName,
email: recipiantUser.email,
amount,
memo,
})
return 'success'

View File

@ -12,12 +12,12 @@ import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User'
import CONFIG from '../../config'
import { sendEMail } from '../../util/sendEMail'
import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail'
jest.mock('../../util/sendEMail', () => {
jest.mock('../../mailer/sendAccountActivationEmail', () => {
return {
__esModule: true,
sendEMail: jest.fn(),
sendAccountActivationEmail: jest.fn(),
}
})
@ -62,7 +62,6 @@ describe('UserResolver', () => {
let result: any
let emailOptIn: string
let newUser: User
beforeAll(async () => {
result = await mutate({ mutation, variables })
@ -90,7 +89,6 @@ describe('UserResolver', () => {
loginEmailOptIn = await getRepository(LoginEmailOptIn)
.createQueryBuilder('login_email_optin')
.getMany()
newUser = user[0]
emailOptIn = loginEmailOptIn[0].verificationCode.toString()
})
@ -165,13 +163,11 @@ describe('UserResolver', () => {
describe('account activation email', () => {
it('sends an account activation email', () => {
const activationLink = CONFIG.EMAIL_LINK_VERIFICATION.replace(/\$1/g, emailOptIn)
expect(sendEMail).toBeCalledWith({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: `${newUser.firstName} ${newUser.lastName} <${newUser.email}>`,
subject: 'Gradido: E-Mail Überprüfung',
text:
expect.stringContaining(`Hallo ${newUser.firstName} ${newUser.lastName},`) &&
expect.stringContaining(activationLink),
expect(sendAccountActivationEmail).toBeCalledWith({
link: activationLink,
firstName: 'Peter',
lastName: 'Lustig',
email: 'peter@lustig.de',
})
})
})

View File

@ -20,7 +20,8 @@ import { UserRepository } from '../../typeorm/repository/User'
import { LoginUser } from '@entity/LoginUser'
import { LoginUserBackup } from '@entity/LoginUserBackup'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { sendEMail } from '../../util/sendEMail'
import { sendResetPasswordEmail } from '../../mailer/sendResetPasswordEmail'
import { sendAccountActivationEmail } from '../../mailer/sendAccountActivationEmail'
import { LoginElopageBuysRepository } from '../../typeorm/repository/LoginElopageBuys'
import { signIn } from '../../apis/KlicktippController'
import { RIGHTS } from '../../auth/RIGHTS'
@ -450,12 +451,12 @@ export class UserResolver {
/\$1/g,
emailOptIn.verificationCode.toString(),
)
const emailSent = await this.sendAccountActivationEmail(
activationLink,
const emailSent = await sendAccountActivationEmail({
link: activationLink,
firstName,
lastName,
email,
)
})
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
@ -472,29 +473,6 @@ export class UserResolver {
return 'success'
}
private sendAccountActivationEmail(
activationLink: string,
firstName: string,
lastName: string,
email: string,
): Promise<boolean> {
return sendEMail({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: `${firstName} ${lastName} <${email}>`,
subject: 'Gradido: E-Mail Überprüfung',
text: `Hallo ${firstName} ${lastName},
Deine EMail wurde soeben bei Gradido registriert.
Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:
${activationLink}
oder kopiere den obigen Link in dein Browserfenster.
Mit freundlichen Grüßen,
dein Gradido-Team`,
})
}
@Mutation(() => Boolean)
async sendActivationEmail(@Arg('email') email: string): Promise<boolean> {
const loginUserRepository = getCustomRepository(LoginUserRepository)
@ -512,12 +490,12 @@ export class UserResolver {
emailOptIn.verificationCode.toString(),
)
const emailSent = await this.sendAccountActivationEmail(
activationLink,
loginUser.firstName,
loginUser.lastName,
const emailSent = await sendAccountActivationEmail({
link: activationLink,
firstName: loginUser.firstName,
lastName: loginUser.lastName,
email,
)
})
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
@ -549,18 +527,11 @@ export class UserResolver {
optInCode.verificationCode.toString(),
)
const emailSent = await sendEMail({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: `${loginUser.firstName} ${loginUser.lastName} <${email}>`,
subject: 'Gradido: Reset Password',
text: `Hallo ${loginUser.firstName} ${loginUser.lastName},
Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert.
Wenn du es warst, klicke bitte auf den Link: ${link}
oder kopiere den obigen Link in Dein Browserfenster.
Mit freundlichen Grüßen,
dein Gradido-Team`,
const emailSent = await sendResetPasswordEmail({
link,
firstName: loginUser.firstName,
lastName: loginUser.lastName,
email,
})
// In case EMails are disabled log the activation link for the user

View File

@ -0,0 +1,29 @@
import { sendAccountActivationEmail } from './sendAccountActivationEmail'
import { sendEMail } from './sendEMail'
jest.mock('./sendEMail', () => {
return {
__esModule: true,
sendEMail: jest.fn(),
}
})
describe('sendAccountActivationEmail', () => {
beforeEach(async () => {
await sendAccountActivationEmail({
link: 'activationLink',
firstName: 'Peter',
lastName: 'Lustig',
email: 'peter@lustig.de',
})
})
it('calls sendEMail', () => {
expect(sendEMail).toBeCalledWith({
to: `Peter Lustig <peter@lustig.de>`,
subject: 'Gradido: E-Mail Überprüfung',
text:
expect.stringContaining('Hallo Peter Lustig') && expect.stringContaining('activationLink'),
})
})
})

View File

@ -0,0 +1,15 @@
import { sendEMail } from './sendEMail'
import { accountActivation } from './text/accountActivation'
export const sendAccountActivationEmail = (data: {
link: string
firstName: string
lastName: string
email: string
}): Promise<boolean> => {
return sendEMail({
to: `${data.firstName} ${data.lastName} <${data.email}>`,
subject: accountActivation.de.subject,
text: accountActivation.de.text(data),
})
}

View File

@ -0,0 +1,92 @@
import { sendEMail } from './sendEMail'
import { createTransport } from 'nodemailer'
import CONFIG from '../config'
CONFIG.EMAIL = false
CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL'
CONFIG.EMAIL_SMTP_PORT = '1234'
CONFIG.EMAIL_USERNAME = 'user'
CONFIG.EMAIL_PASSWORD = 'pwd'
jest.mock('nodemailer', () => {
return {
__esModule: true,
createTransport: jest.fn(() => {
return {
sendMail: jest.fn(() => {
return {
messageId: 'message',
}
}),
}
}),
}
})
describe('sendEMail', () => {
let result: boolean
describe('config email is false', () => {
// eslint-disable-next-line no-console
const consoleLog = console.log
const consoleLogMock = jest.fn()
// eslint-disable-next-line no-console
console.log = consoleLogMock
beforeEach(async () => {
result = await sendEMail({
to: 'receiver@mail.org',
subject: 'Subject',
text: 'Text text text',
})
})
afterAll(() => {
// eslint-disable-next-line no-console
console.log = consoleLog
})
it('logs warining to console', () => {
expect(consoleLogMock).toBeCalledWith('Emails are disabled via config')
})
it('returns false', () => {
expect(result).toBeFalsy()
})
})
describe('config email is true', () => {
beforeEach(async () => {
CONFIG.EMAIL = true
result = await sendEMail({
to: 'receiver@mail.org',
subject: 'Subject',
text: 'Text text text',
})
})
it('calls the transporter', () => {
expect(createTransport).toBeCalledWith({
host: 'EMAIL_SMTP_URL',
port: 1234,
secure: false,
requireTLS: true,
auth: {
user: 'user',
pass: 'pwd',
},
})
})
it('calls sendMail of transporter', () => {
expect((createTransport as jest.Mock).mock.results[0].value.sendMail).toBeCalledWith({
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
to: 'receiver@mail.org',
subject: 'Subject',
text: 'Text text text',
})
})
it('returns true', () => {
expect(result).toBeTruthy()
})
})
})

View File

@ -3,7 +3,6 @@ import { createTransport } from 'nodemailer'
import CONFIG from '../config'
export const sendEMail = async (emailDef: {
from: string
to: string
subject: string
text: string
@ -23,7 +22,10 @@ export const sendEMail = async (emailDef: {
pass: CONFIG.EMAIL_PASSWORD,
},
})
const info = await transporter.sendMail(emailDef)
const info = await transporter.sendMail({
...emailDef,
from: `Gradido (nicht antworten) <${CONFIG.EMAIL_SENDER}>`,
})
if (!info.messageId) {
throw new Error('error sending notification email, but transaction succeed')
}

View File

@ -0,0 +1,28 @@
import { sendResetPasswordEmail } from './sendResetPasswordEmail'
import { sendEMail } from './sendEMail'
jest.mock('./sendEMail', () => {
return {
__esModule: true,
sendEMail: jest.fn(),
}
})
describe('sendResetPasswordEmail', () => {
beforeEach(async () => {
await sendResetPasswordEmail({
link: 'resetLink',
firstName: 'Peter',
lastName: 'Lustig',
email: 'peter@lustig.de',
})
})
it('calls sendEMail', () => {
expect(sendEMail).toBeCalledWith({
to: `Peter Lustig <peter@lustig.de>`,
subject: 'Gradido: Passwort zurücksetzen',
text: expect.stringContaining('Hallo Peter Lustig') && expect.stringContaining('resetLink'),
})
})
})

View File

@ -0,0 +1,15 @@
import { sendEMail } from './sendEMail'
import { resetPassword } from './text/resetPassword'
export const sendResetPasswordEmail = (data: {
link: string
firstName: string
lastName: string
email: string
}): Promise<boolean> => {
return sendEMail({
to: `${data.firstName} ${data.lastName} <${data.email}>`,
subject: resetPassword.de.subject,
text: resetPassword.de.text(data),
})
}

View File

@ -0,0 +1,35 @@
import { sendTransactionReceivedEmail } from './sendTransactionReceivedEmail'
import { sendEMail } from './sendEMail'
jest.mock('./sendEMail', () => {
return {
__esModule: true,
sendEMail: jest.fn(),
}
})
describe('sendTransactionReceivedEmail', () => {
beforeEach(async () => {
await sendTransactionReceivedEmail({
senderFirstName: 'Bibi',
senderLastName: 'Bloxberg',
recipientFirstName: 'Peter',
recipientLastName: 'Lustig',
email: 'peter@lustig.de',
amount: 42.0,
memo: 'Vielen herzlichen Dank für den neuen Hexenbesen!',
})
})
it('calls sendEMail', () => {
expect(sendEMail).toBeCalledWith({
to: `Peter Lustig <peter@lustig.de>`,
subject: 'Gradido Überweisung',
text:
expect.stringContaining('Hallo Peter Lustig') &&
expect.stringContaining('42,00 GDD') &&
expect.stringContaining('Bibi Bloxberg') &&
expect.stringContaining('Vielen herzlichen Dank für den neuen Hexenbesen!'),
})
})
})

View File

@ -0,0 +1,18 @@
import { sendEMail } from './sendEMail'
import { transactionReceived } from './text/transactionReceived'
export const sendTransactionReceivedEmail = (data: {
senderFirstName: string
senderLastName: string
recipientFirstName: string
recipientLastName: string
email: string
amount: number
memo: string
}): Promise<boolean> => {
return sendEMail({
to: `${data.recipientFirstName} ${data.recipientLastName} <${data.email}>`,
subject: transactionReceived.de.subject,
text: transactionReceived.de.text(data),
})
}

View File

@ -0,0 +1,16 @@
export const accountActivation = {
de: {
subject: 'Gradido: E-Mail Überprüfung',
text: (data: { link: string; firstName: string; lastName: string; email: string }): string =>
`Hallo ${data.firstName} ${data.lastName},
Deine EMail wurde soeben bei Gradido registriert.
Klicke bitte auf diesen Link, um die Registrierung abzuschließen und dein Gradido-Konto zu aktivieren:
${data.link}
oder kopiere den obigen Link in dein Browserfenster.
Mit freundlichen Grüßen,
dein Gradido-Team`,
},
}

View File

@ -0,0 +1,14 @@
export const resetPassword = {
de: {
subject: 'Gradido: Passwort zurücksetzen',
text: (data: { link: string; firstName: string; lastName: string; email: string }): string =>
`Hallo ${data.firstName} ${data.lastName},
Du oder jemand anderes hat für dieses Konto ein Zurücksetzen des Passworts angefordert.
Wenn du es warst, klicke bitte auf den Link: ${data.link}
oder kopiere den obigen Link in Dein Browserfenster.
Mit freundlichen Grüßen,
dein Gradido-Team`,
},
}

View File

@ -0,0 +1,27 @@
export const transactionReceived = {
de: {
subject: 'Gradido Überweisung',
text: (data: {
senderFirstName: string
senderLastName: string
recipientFirstName: string
recipientLastName: string
email: string
amount: number
memo: string
}): string =>
`Hallo ${data.recipientFirstName} ${data.recipientLastName}
Du hast soeben ${data.amount.toFixed(2).replace('.', ',')} GDD von ${data.senderFirstName} ${
data.senderLastName
} erhalten.
${data.senderFirstName} ${data.senderLastName} schreibt:
${data.memo}
Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen,
dein Gradido-Team`,
},
}