From 50dc59cbd674dda7c98c07cffd7e750a08a4a913 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Tue, 14 Nov 2023 09:48:18 +0100 Subject: [PATCH] fix test, add english translation for new email --- .../sendEmailVariants.test.ts.snap | 167 ++++++++++++++++++ .../src/emails/sendEmailTranslated.test.ts | 1 + backend/src/emails/sendEmailVariants.test.ts | 66 +++++++ backend/src/emails/sendEmailVariants.ts | 28 +++ .../resolver/ContributionMessageResolver.ts | 18 +- .../resolver/ContributionResolver.test.ts | 30 +--- .../graphql/resolver/ContributionResolver.ts | 26 ++- .../UnconfirmedContribution.role.ts | 4 + .../UpdateUnconfirmedContribution.context.ts | 8 +- backend/src/locales/en.json | 5 + backend/src/util/time.ts | 6 +- 11 files changed, 318 insertions(+), 41 deletions(-) diff --git a/backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap b/backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap index da50bbcaf..00ec365f0 100644 --- a/backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap +++ b/backend/src/emails/__snapshots__/sendEmailVariants.test.ts.snap @@ -506,6 +506,173 @@ exports[`sendEmailVariants sendAddedContributionMessageEmail result has the corr " `; +exports[`sendEmailVariants sendContributionChangedByModeratorEmail result has the correct html as snapshot 1`] = ` +" + + + + + + + + +
+
+
\\"Gradido
+
+
+

Your common good contribution has been changed

+
+

Hello Peter Lustig,

+

your common good contribution 'My contribution.' has just been changed by Bibi Bloxberg and now reads as 'This is a better contribution memo.'

+
+
+

Contribution details

+
To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab.
To account +
Or copy the link into your browser window.
http://localhost/community/contributions +
Please do not reply to this email.
+
+
+

Kind regards,
your Gradido team +

+
+
+
+
+
\\"facebook\\"\\"Telegram\\"\\"Twitter\\"\\"youtube\\"
+
+
+
If you have any further questions, please contact our support.
support@gradido.net\\"Gradido +
Privacy Policy +
Gradido-Akademie
Institut für Wirtschaftsbionik
Pfarrweg 2
74653 Künzelsau
Deutschland


+
+
+
+
+ +" +`; + exports[`sendEmailVariants sendContributionConfirmedEmail result has the correct html as snapshot 1`] = ` " diff --git a/backend/src/emails/sendEmailTranslated.test.ts b/backend/src/emails/sendEmailTranslated.test.ts index b6ec0fbb5..d09d11bd5 100644 --- a/backend/src/emails/sendEmailTranslated.test.ts +++ b/backend/src/emails/sendEmailTranslated.test.ts @@ -10,6 +10,7 @@ import { sendEmailTranslated } from './sendEmailTranslated' CONFIG.EMAIL = false CONFIG.EMAIL_SMTP_URL = 'EMAIL_SMTP_URL' CONFIG.EMAIL_SMTP_PORT = 1234 +CONFIG.EMAIL_SENDER = 'info@gradido.net' CONFIG.EMAIL_USERNAME = 'user' CONFIG.EMAIL_PASSWORD = 'pwd' CONFIG.EMAIL_TLS = true diff --git a/backend/src/emails/sendEmailVariants.test.ts b/backend/src/emails/sendEmailVariants.test.ts index 3340a361d..f0c431586 100644 --- a/backend/src/emails/sendEmailVariants.test.ts +++ b/backend/src/emails/sendEmailVariants.test.ts @@ -11,6 +11,8 @@ import { logger, i18n as localization } from '@test/testSetup' import { CONFIG } from '@/config' +CONFIG.EMAIL_SENDER = 'info@gradido.net' + import { sendEmailTranslated } from './sendEmailTranslated' import { sendAddedContributionMessageEmail, @@ -22,6 +24,7 @@ import { sendResetPasswordEmail, sendTransactionLinkRedeemedEmail, sendTransactionReceivedEmail, + sendContributionChangedByModeratorEmail, } from './sendEmailVariants' let con: Connection @@ -286,6 +289,69 @@ describe('sendEmailVariants', () => { }) }) + describe('sendContributionChangedByModeratorEmail', () => { + beforeAll(async () => { + result = await sendContributionChangedByModeratorEmail({ + firstName: 'Peter', + lastName: 'Lustig', + email: 'peter@lustig.de', + language: 'en', + senderFirstName: 'Bibi', + senderLastName: 'Bloxberg', + contributionMemo: 'My contribution.', + contributionMemoUpdated: 'This is a better contribution memo.' + }) + }) + + describe('calls "sendEmailTranslated"', () => { + it('with expected parameters', () => { + expect(sendEmailTranslated).toBeCalledWith({ + receiver: { + to: 'Peter Lustig ', + }, + template: 'contributionChangedByModerator', + locals: { + firstName: 'Peter', + lastName: 'Lustig', + locale: 'en', + senderFirstName: 'Bibi', + senderLastName: 'Bloxberg', + contributionMemo: 'My contribution.', + contributionMemoUpdated: 'This is a better contribution memo.', + overviewURL: CONFIG.EMAIL_LINK_OVERVIEW, + supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, + communityURL: CONFIG.COMMUNITY_URL, + }, + }) + }) + }) + + describe('result', () => { + it('is the expected object', () => { + console.log(result.originalMessage.text) + expect(result).toMatchObject({ + envelope: { + from: 'info@gradido.net', + to: ['peter@lustig.de'], + }, + message: expect.any(String), + originalMessage: expect.objectContaining({ + to: 'Peter Lustig ', + from: 'Gradido (emails.general.doNotAnswer) ', + attachments: expect.any(Array), + subject: 'Your common good contribution has been changed', + html: expect.any(String), + text: expect.stringContaining('YOUR COMMON GOOD CONTRIBUTION HAS BEEN CHANGED'), + }), + }) + }) + + it('has the correct html as snapshot', () => { + expect(result.originalMessage.html).toMatchSnapshot() + }) + }) + }) + describe('sendContributionDeniedEmail', () => { beforeAll(async () => { result = await sendContributionDeniedEmail({ diff --git a/backend/src/emails/sendEmailVariants.ts b/backend/src/emails/sendEmailVariants.ts index ff7709380..8bcc9accd 100644 --- a/backend/src/emails/sendEmailVariants.ts +++ b/backend/src/emails/sendEmailVariants.ts @@ -105,6 +105,34 @@ export const sendContributionConfirmedEmail = (data: { }) } +export const sendContributionChangedByModeratorEmail = (data: { + firstName: string + lastName: string + email: string + language: string + senderFirstName: string + senderLastName: string + contributionMemo: string + contributionMemoUpdated: string +}): Promise | boolean | null> => { + return sendEmailTranslated({ + receiver: { to: `${data.firstName} ${data.lastName} <${data.email}>` }, + template: 'contributionChangedByModerator', + locals: { + firstName: data.firstName, + lastName: data.lastName, + locale: data.language, + senderFirstName: data.senderFirstName, + senderLastName: data.senderLastName, + contributionMemo: data.contributionMemo, + contributionMemoUpdated: data.contributionMemoUpdated, + overviewURL: CONFIG.EMAIL_LINK_OVERVIEW, + supportEmail: CONFIG.COMMUNITY_SUPPORT_MAIL, + communityURL: CONFIG.COMMUNITY_URL, + }, + }) +} + export const sendContributionDeletedEmail = (data: { firstName: string lastName: string diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.ts b/backend/src/graphql/resolver/ContributionMessageResolver.ts index a5af00f69..5910befa1 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.ts @@ -14,7 +14,7 @@ import { Order } from '@enum/Order' import { ContributionMessage, ContributionMessageListResult } from '@model/ContributionMessage' import { RIGHTS } from '@/auth/RIGHTS' -import { EmailBuilder, EmailType } from '@/emails/Email.builder' +import { sendAddedContributionMessageEmail } from '@/emails/sendEmailVariants' import { EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE, EVENT_CONTRIBUTION_MESSAGE_CREATE, @@ -170,13 +170,15 @@ export class ContributionMessageResolver { } // send email (never for moderator messages) - const emailBuilder = new EmailBuilder() - void emailBuilder - .setRecipient(contribution.user) - .setSender(moderator) - .setContribution(contribution) - .setType(EmailType.ADDED_CONTRIBUTION_MESSAGE) - .sendEmail() + void sendAddedContributionMessageEmail({ + firstName: contribution.user.firstName, + lastName: contribution.user.lastName, + email: contribution.user.emailContact.email, + language: contribution.user.language, + senderFirstName: moderator.firstName, + senderLastName: moderator.lastName, + contributionMemo: contribution.memo, + }) } await queryRunner.commitTransaction() await EVENT_ADMIN_CONTRIBUTION_MESSAGE_CREATE( diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index e6cb485a3..8b2bf141e 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -497,28 +497,6 @@ describe('ContributionResolver', () => { }) }) - it('throws an error', async () => { - jest.clearAllMocks() - const { errors: errorObjects } = await mutate({ - mutation: adminUpdateContribution, - variables: { - id: pendingContribution.data.createContribution.id, - amount: 10.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) - expect(errorObjects).toEqual([ - new GraphQLError('An admin is not allowed to update an user contribution'), - ]) - }) - - it('logs the error "An admin is not allowed to update an user contribution"', () => { - expect(logger.error).toBeCalledWith( - 'An admin is not allowed to update an user contribution', - ) - }) - describe('contribution has wrong status', () => { beforeAll(async () => { const contribution = await Contribution.findOneOrFail({ @@ -2824,7 +2802,7 @@ describe('ContributionResolver', () => { } = await query({ query: adminListContributions, }) - // console.log('17 contributions: %s', JSON.stringify(contributionListObject, null, 2)) + expect(contributionListObject.contributionList).toHaveLength(18) expect(contributionListObject).toMatchObject({ contributionCount: 18, @@ -2907,7 +2885,7 @@ describe('ContributionResolver', () => { id: expect.any(Number), lastName: 'Lustig', memo: 'Das war leider zu Viel!', - messagesCount: 0, + messagesCount: 1, status: 'DELETED', }), expect.objectContaining({ @@ -3092,7 +3070,7 @@ describe('ContributionResolver', () => { id: expect.any(Number), lastName: 'Lustig', memo: 'Das war leider zu Viel!', - messagesCount: 0, + messagesCount: 1, status: 'DELETED', }), ]), @@ -3137,7 +3115,7 @@ describe('ContributionResolver', () => { id: expect.any(Number), lastName: 'Lustig', memo: 'Das war leider zu Viel!', - messagesCount: 0, + messagesCount: 1, status: 'DELETED', }), ]), diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index dc6d10364..2cbbbc27a 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -45,7 +45,7 @@ import { getUserCreation, validateContribution, getOpenCreations } from './util/ import { findContributions } from './util/findContributions' import { getLastTransaction } from './util/getLastTransaction' import { sendTransactionsToDltConnector } from './util/sendTransactionsToDltConnector' -import { sendContributionConfirmedEmail, sendContributionDeletedEmail, sendContributionDeniedEmail } from '@/emails/sendEmailVariants' +import { sendContributionChangedByModeratorEmail, sendContributionConfirmedEmail, sendContributionDeletedEmail, sendContributionDeniedEmail } from '@/emails/sendEmailVariants' @Resolver() export class ContributionResolver { @@ -255,7 +255,7 @@ export class ContributionResolver { adminUpdateContributionArgs, context, ) - const { contribution, contributionMessage } = await updateUnconfirmedContributionContext.run() + const { contribution, contributionMessage, createdByUserChangedByModerator } = await updateUnconfirmedContributionContext.run() await getConnection().transaction(async (transactionalEntityManager: EntityManager) => { await Promise.all([ transactionalEntityManager.save(contribution), @@ -275,9 +275,31 @@ export class ContributionResolver { contribution, contribution.amount, ) + if (createdByUserChangedByModerator && adminUpdateContributionArgs.memo) { + void sendContributionChangedByModeratorEmail({ + firstName: contribution.user.firstName, + lastName: contribution.user.lastName, + email: contribution.user.emailContact.email, + language: contribution.user.language, + senderFirstName: moderator.firstName, + senderLastName: moderator.lastName, + contributionMemo: contribution.memo, + contributionMemoUpdated: adminUpdateContributionArgs.memo + }) + } return result } + /* + firstName: string + lastName: string + email: string + language: string + senderFirstName: string + senderLastName: string + contributionMemo: string + contributionMemoUpdated: string + */ @Authorized([RIGHTS.ADMIN_LIST_CONTRIBUTIONS]) @Query(() => ContributionListResult) diff --git a/backend/src/interactions/updateUnconfirmedContribution/UnconfirmedContribution.role.ts b/backend/src/interactions/updateUnconfirmedContribution/UnconfirmedContribution.role.ts index 472db4a96..acdb7b750 100644 --- a/backend/src/interactions/updateUnconfirmedContribution/UnconfirmedContribution.role.ts +++ b/backend/src/interactions/updateUnconfirmedContribution/UnconfirmedContribution.role.ts @@ -61,4 +61,8 @@ export abstract class UnconfirmedContributionRole { } return this.availableCreationSums } + + public isCreatedFromUser(): boolean { + return !this.self.moderatorId + } } diff --git a/backend/src/interactions/updateUnconfirmedContribution/UpdateUnconfirmedContribution.context.ts b/backend/src/interactions/updateUnconfirmedContribution/UpdateUnconfirmedContribution.context.ts index db98c87e5..0d70662d3 100644 --- a/backend/src/interactions/updateUnconfirmedContribution/UpdateUnconfirmedContribution.context.ts +++ b/backend/src/interactions/updateUnconfirmedContribution/UpdateUnconfirmedContribution.context.ts @@ -33,8 +33,10 @@ export class UpdateUnconfirmedContributionContext { public async run(): Promise<{ contribution: Contribution contributionMessage: ContributionMessage - availableCreationSums: Decimal[] + availableCreationSums: Decimal[], + createdByUserChangedByModerator: boolean }> { + let createdByUserChangedByModerator = false if (!this.context.role || !this.context.user) { throw new LogError("context didn't contain role or user") } @@ -64,6 +66,9 @@ export class UpdateUnconfirmedContributionContext { this.input, this.context.user, ) + if (unconfirmedContributionRole.isCreatedFromUser()) { + createdByUserChangedByModerator = true + } contributionMessageBuilder.setIsModerator(true) } if (!unconfirmedContributionRole) { @@ -77,6 +82,7 @@ export class UpdateUnconfirmedContributionContext { contribution: contributionToUpdate, contributionMessage: contributionMessageBuilder.build(), availableCreationSums: unconfirmedContributionRole.getAvailableCreationSums(), + createdByUserChangedByModerator } } } diff --git a/backend/src/locales/en.json b/backend/src/locales/en.json index cf4bbef47..0bcd5e42f 100644 --- a/backend/src/locales/en.json +++ b/backend/src/locales/en.json @@ -26,6 +26,11 @@ "contribution": { "toSeeContributionsAndMessages": "To see your common good contributions and related messages, go to the “Creation” menu in your Gradido account and click on the “My contributions” tab." }, + "contributionChangedByModerator": { + "commonGoodContributionConfirmed": "your common good contribution '{contributionMemo}' has just been changed by {senderFirstName} {senderLastName} and now reads as '{contributionMemoUpdated}'", + "subject": "Your common good contribution has been changed", + "title": "Your common good contribution has been changed" + }, "contributionConfirmed": { "commonGoodContributionConfirmed": "Your common good contribution “{contributionMemo}” has just been approved by {senderFirstName} {senderLastName}. Your Gradido account has been credited with {amountGDD} GDD.", "subject": "Your contribution to the common good was confirmed", diff --git a/backend/src/util/time.ts b/backend/src/util/time.ts index 7b8d09671..45b886702 100644 --- a/backend/src/util/time.ts +++ b/backend/src/util/time.ts @@ -1,9 +1,7 @@ -export interface TimeDuration { +export const getTimeDurationObject = (time: number): { hours?: number minutes: number -} - -export const getTimeDurationObject = (time: number): TimeDuration => { +} => { if (time > 60) { return { hours: Math.floor(time / 60),