mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge pull request #1224 from gradido/test-send-email
feat: Test and Refactor Send Email
This commit is contained in:
commit
b53fe452c8
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -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 }}
|
||||
|
||||
##############################################################################
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -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
|
||||
|
||||
29
backend/src/mailer/sendAccountActivationEmail.test.ts
Normal file
29
backend/src/mailer/sendAccountActivationEmail.test.ts
Normal 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'),
|
||||
})
|
||||
})
|
||||
})
|
||||
15
backend/src/mailer/sendAccountActivationEmail.ts
Normal file
15
backend/src/mailer/sendAccountActivationEmail.ts
Normal 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),
|
||||
})
|
||||
}
|
||||
92
backend/src/mailer/sendEMail.test.ts
Normal file
92
backend/src/mailer/sendEMail.test.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -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')
|
||||
}
|
||||
28
backend/src/mailer/sendResetPasswordEmail.test.ts
Normal file
28
backend/src/mailer/sendResetPasswordEmail.test.ts
Normal 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'),
|
||||
})
|
||||
})
|
||||
})
|
||||
15
backend/src/mailer/sendResetPasswordEmail.ts
Normal file
15
backend/src/mailer/sendResetPasswordEmail.ts
Normal 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),
|
||||
})
|
||||
}
|
||||
35
backend/src/mailer/sendTransactionReceivedEmail.test.ts
Normal file
35
backend/src/mailer/sendTransactionReceivedEmail.test.ts
Normal 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!'),
|
||||
})
|
||||
})
|
||||
})
|
||||
18
backend/src/mailer/sendTransactionReceivedEmail.ts
Normal file
18
backend/src/mailer/sendTransactionReceivedEmail.ts
Normal 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),
|
||||
})
|
||||
}
|
||||
16
backend/src/mailer/text/accountActivation.ts
Normal file
16
backend/src/mailer/text/accountActivation.ts
Normal 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`,
|
||||
},
|
||||
}
|
||||
14
backend/src/mailer/text/resetPassword.ts
Normal file
14
backend/src/mailer/text/resetPassword.ts
Normal 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`,
|
||||
},
|
||||
}
|
||||
27
backend/src/mailer/text/transactionReceived.ts
Normal file
27
backend/src/mailer/text/transactionReceived.ts
Normal 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`,
|
||||
},
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user