From 5e2d6c3bc90534fef720ac0e1719ebee4a130d3f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Nov 2022 19:01:16 +0100 Subject: [PATCH 01/29] fix: Timezone Problems on Change of Month --- .../graphql/resolver/AdminResolver.test.ts | 130 +++++----- .../graphql/resolver/util/creations.test.ts | 237 ++++++++++++++++++ backend/test/helpers.ts | 6 + 3 files changed, 305 insertions(+), 68 deletions(-) create mode 100644 backend/src/graphql/resolver/util/creations.test.ts diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index f26fce3d8..6eaec1547 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { objectValuesToArray } from '@/util/utilities' -import { testEnvironment, resetToken, cleanDB } from '@test/helpers' +import { testEnvironment, resetToken, cleanDB, contributionDateFormatter } from '@test/helpers' import { userFactory } from '@/seeds/factory/user' import { creationFactory } from '@/seeds/factory/creation' import { creations } from '@/seeds/creation/index' @@ -83,6 +83,12 @@ let user: User let creation: Contribution | void let result: any +describe('contributionDateFormatter', () => { + it('formats the date correctly', () => { + expect(contributionDateFormatter(new Date('Thu Feb 29 2024 13:12:11'))).toEqual('2/29/2024') + }) +}) + describe('AdminResolver', () => { describe('set user role', () => { describe('unauthenticated', () => { @@ -139,7 +145,6 @@ describe('AdminResolver', () => { describe('user to get a new role does not exist', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: setUserRole, variables: { userId: admin.id + 1, isAdmin: true } }), ).resolves.toEqual( @@ -196,7 +201,6 @@ describe('AdminResolver', () => { describe('change role with error', () => { describe('is own role', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: setUserRole, variables: { userId: admin.id, isAdmin: false } }), ).resolves.toEqual( @@ -213,7 +217,6 @@ describe('AdminResolver', () => { describe('user has already role to be set', () => { describe('to admin', () => { it('throws an error', async () => { - jest.clearAllMocks() await mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: true }, @@ -234,7 +237,6 @@ describe('AdminResolver', () => { describe('to usual user', () => { it('throws an error', async () => { - jest.clearAllMocks() await mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: false }, @@ -311,7 +313,6 @@ describe('AdminResolver', () => { describe('user to be deleted does not exist', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: deleteUser, variables: { userId: admin.id + 1 } }), ).resolves.toEqual( @@ -328,7 +329,6 @@ describe('AdminResolver', () => { describe('delete self', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: deleteUser, variables: { userId: admin.id } }), ).resolves.toEqual( @@ -362,7 +362,6 @@ describe('AdminResolver', () => { describe('delete deleted user', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: deleteUser, variables: { userId: user.id } }), ).resolves.toEqual( @@ -434,7 +433,6 @@ describe('AdminResolver', () => { describe('user to be undelete does not exist', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: unDeleteUser, variables: { userId: admin.id + 1 } }), ).resolves.toEqual( @@ -455,7 +453,6 @@ describe('AdminResolver', () => { }) it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: unDeleteUser, variables: { userId: user.id } }), ).resolves.toEqual( @@ -751,7 +748,7 @@ describe('AdminResolver', () => { email: 'bibi@bloxberg.de', amount: new Decimal(300), memo: 'Danke Bibi!', - creationDate: new Date().toString(), + creationDate: contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( @@ -861,7 +858,7 @@ describe('AdminResolver', () => { email: 'bibi@bloxberg.de', amount: new Decimal(300), memo: 'Danke Bibi!', - creationDate: new Date().toString(), + creationDate: contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( @@ -936,19 +933,24 @@ describe('AdminResolver', () => { }) describe('adminCreateContribution', () => { + const now = new Date() + beforeAll(async () => { - const now = new Date() creation = await creationFactory(testEnv, { email: 'peter@lustig.de', amount: 400, memo: 'Herzlich Willkommen bei Gradido!', - creationDate: new Date(now.getFullYear(), now.getMonth() - 1, 1).toISOString(), + creationDate: contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 1, 1), + ), }) }) describe('user to create for does not exist', () => { it('throws an error', async () => { - jest.clearAllMocks() + variables.creationDate = contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 1, 1), + ) await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -969,10 +971,12 @@ describe('AdminResolver', () => { beforeAll(async () => { user = await userFactory(testEnv, stephenHawking) variables.email = 'stephen@hawking.uk' + variables.creationDate = contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 1, 1), + ) }) it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -995,10 +999,12 @@ describe('AdminResolver', () => { beforeAll(async () => { user = await userFactory(testEnv, garrickOllivander) variables.email = 'garrick@ollivander.com' + variables.creationDate = contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 1, 1), + ) }) it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -1021,39 +1027,31 @@ describe('AdminResolver', () => { beforeAll(async () => { user = await userFactory(testEnv, bibiBloxberg) variables.email = 'bibi@bloxberg.de' + variables.creationDate = 'invalid-date' }) describe('date of creation is not a date string', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( expect.objectContaining({ - errors: [ - new GraphQLError('No information for available creations for the given date'), - ], + errors: [new GraphQLError(`invalid Date for creationDate=invalid-date`)], }), ) }) it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith( - 'No information for available creations with the given creationDate=', - 'Invalid Date', - ) + expect(logger.error).toBeCalledWith(`invalid Date for creationDate=invalid-date`) }) }) describe('date of creation is four months ago', () => { it('throws an error', async () => { jest.clearAllMocks() - const now = new Date() - variables.creationDate = new Date( - now.getFullYear(), - now.getMonth() - 4, - 1, - ).toString() + variables.creationDate = contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 4, 1), + ) await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -1068,7 +1066,7 @@ describe('AdminResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( 'No information for available creations with the given creationDate=', - variables.creationDate, + new Date(variables.creationDate).toISOString(), ) }) }) @@ -1076,12 +1074,9 @@ describe('AdminResolver', () => { describe('date of creation is in the future', () => { it('throws an error', async () => { jest.clearAllMocks() - const now = new Date() - variables.creationDate = new Date( - now.getFullYear(), - now.getMonth() + 4, - 1, - ).toString() + variables.creationDate = contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() + 4, 1), + ) await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -1096,15 +1091,14 @@ describe('AdminResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( 'No information for available creations with the given creationDate=', - variables.creationDate, + new Date(variables.creationDate).toISOString(), ) }) }) describe('amount of creation is too high', () => { it('throws an error', async () => { - jest.clearAllMocks() - variables.creationDate = new Date().toString() + variables.creationDate = contributionDateFormatter(now) await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -1192,7 +1186,7 @@ describe('AdminResolver', () => { email, amount: new Decimal(500), memo: 'Grundeinkommen', - creationDate: new Date().toString(), + creationDate: contributionDateFormatter(new Date()), } }) @@ -1229,7 +1223,6 @@ describe('AdminResolver', () => { describe('user for creation to update does not exist', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1238,7 +1231,7 @@ describe('AdminResolver', () => { email: 'bob@baumeister.de', amount: new Decimal(300), memo: 'Danke Bibi!', - creationDate: new Date().toString(), + creationDate: contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( @@ -1259,7 +1252,6 @@ describe('AdminResolver', () => { describe('user for creation to update is deleted', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1268,7 +1260,7 @@ describe('AdminResolver', () => { email: 'stephen@hawking.uk', amount: new Decimal(300), memo: 'Danke Bibi!', - creationDate: new Date().toString(), + creationDate: contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( @@ -1285,7 +1277,6 @@ describe('AdminResolver', () => { describe('creation does not exist', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1294,7 +1285,7 @@ describe('AdminResolver', () => { email: 'bibi@bloxberg.de', amount: new Decimal(300), memo: 'Danke Bibi!', - creationDate: new Date().toString(), + creationDate: contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( @@ -1311,7 +1302,6 @@ describe('AdminResolver', () => { describe('user email does not match creation user', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1321,8 +1311,8 @@ describe('AdminResolver', () => { amount: new Decimal(300), memo: 'Danke Bibi!', creationDate: creation - ? creation.contributionDate.toString() - : new Date().toString(), + ? contributionDateFormatter(creation.contributionDate) + : contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( @@ -1346,7 +1336,6 @@ describe('AdminResolver', () => { describe('creation update is not valid', () => { // as this test has not clearly defined that date, it is a false positive it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1356,15 +1345,15 @@ describe('AdminResolver', () => { amount: new Decimal(1900), memo: 'Danke Peter!', creationDate: creation - ? creation.contributionDate.toString() - : new Date().toString(), + ? contributionDateFormatter(creation.contributionDate) + : contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (1900 GDD) to be created exceeds the amount (600 GDD) still available for this month.', ), ], }), @@ -1373,7 +1362,7 @@ describe('AdminResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (1900 GDD) to be created exceeds the amount (600 GDD) still available for this month.', ) }) }) @@ -1390,8 +1379,8 @@ describe('AdminResolver', () => { amount: new Decimal(300), memo: 'Danke Peter!', creationDate: creation - ? creation.contributionDate.toString() - : new Date().toString(), + ? contributionDateFormatter(creation.contributionDate) + : contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( @@ -1430,8 +1419,8 @@ describe('AdminResolver', () => { amount: new Decimal(200), memo: 'Das war leider zu Viel!', creationDate: creation - ? creation.contributionDate.toString() - : new Date().toString(), + ? contributionDateFormatter(creation.contributionDate) + : contributionDateFormatter(new Date()), }, }), ).resolves.toEqual( @@ -1523,7 +1512,6 @@ describe('AdminResolver', () => { describe('adminDeleteContribution', () => { describe('creation id does not exist', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminDeleteContribution, @@ -1554,13 +1542,12 @@ describe('AdminResolver', () => { variables: { amount: 100.0, memo: 'Test env contribution', - creationDate: new Date().toString(), + creationDate: contributionDateFormatter(new Date()), }, }) }) it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: adminDeleteContribution, @@ -1606,7 +1593,6 @@ describe('AdminResolver', () => { describe('confirmContribution', () => { describe('creation does not exits', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: confirmContribution, @@ -1633,7 +1619,9 @@ describe('AdminResolver', () => { email: 'peter@lustig.de', amount: 400, memo: 'Herzlich Willkommen bei Gradido!', - creationDate: new Date(now.getFullYear(), now.getMonth() - 1, 1).toISOString(), + creationDate: contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 1, 1), + ), }) }) @@ -1664,7 +1652,9 @@ describe('AdminResolver', () => { email: 'bibi@bloxberg.de', amount: 450, memo: 'Herzlich Willkommen bei Gradido liebe Bibi!', - creationDate: new Date(now.getFullYear(), now.getMonth() - 2, 1).toISOString(), + creationDate: contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 2, 1), + ), }) }) @@ -1735,13 +1725,17 @@ describe('AdminResolver', () => { email: 'bibi@bloxberg.de', amount: 50, memo: 'Herzlich Willkommen bei Gradido liebe Bibi!', - creationDate: new Date(now.getFullYear(), now.getMonth() - 2, 1).toISOString(), + creationDate: contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 2, 1), + ), }) c2 = await creationFactory(testEnv, { email: 'bibi@bloxberg.de', amount: 50, memo: 'Herzlich Willkommen bei Gradido liebe Bibi!', - creationDate: new Date(now.getFullYear(), now.getMonth() - 2, 1).toISOString(), + creationDate: contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 2, 1), + ), }) }) diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts new file mode 100644 index 000000000..544a81d73 --- /dev/null +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -0,0 +1,237 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +import { + testEnvironment, + resetToken, + cleanDB, + contributionDateFormatter, + setClientRequestTime, + toJSTzone, + toPSTzone, +} from '@test/helpers' +import { logger } from '@test/testSetup' +import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' +import { peterLustig } from '@/seeds/users/peter-lustig' +import { User } from '@entity/User' +import { Contribution } from '@entity/Contribution' +import { userFactory } from '@/seeds/factory/user' +import { login, createContribution, adminCreateContribution } from '@/seeds/graphql/mutations' +import { getUserCreation } from './creations' + +let mutate: any, query: any, con: any +let testEnv: any + +beforeAll(async () => { + testEnv = await testEnvironment() + mutate = testEnv.mutate + query = testEnv.query + con = testEnv.con + await cleanDB() +}) + +afterAll(async () => { + // await cleanDB() + await con.close() +}) + +const setZeroHours = (date: Date): Date => { + return new Date(date.getFullYear(), date.getMonth(), date.getDate()) +} + +describe('util/creation', () => { + let user: User + let admin: User + + const now = new Date() + + beforeAll(async () => { + user = await userFactory(testEnv, bibiBloxberg) + admin = await userFactory(testEnv, peterLustig) + }) + + describe('getUserCreations', () => { + beforeAll(async () => { + setClientRequestTime(now.toString()) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: adminCreateContribution, + variables: { + email: 'bibi@bloxberg.de', + amount: 250.0, + memo: 'Admin contribution for this month', + creationDate: contributionDateFormatter(now), + }, + }) + await mutate({ + mutation: adminCreateContribution, + variables: { + email: 'bibi@bloxberg.de', + amount: 160.0, + memo: 'Admin contribution for the last month', + creationDate: contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()), + ), + }, + }) + await mutate({ + mutation: adminCreateContribution, + variables: { + email: 'bibi@bloxberg.de', + amount: 450.0, + memo: 'Admin contribution for two months ago', + creationDate: contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 2, now.getDate()), + ), + }, + }) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: createContribution, + variables: { + amount: 400.0, + memo: 'Contribution for this month', + creationDate: contributionDateFormatter(now), + }, + }) + await mutate({ + mutation: createContribution, + variables: { + amount: 500.0, + memo: 'Contribution for the last month', + creationDate: contributionDateFormatter( + new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()), + ), + }, + }) + }) + + it('has the correct data setup', async () => { + await expect(Contribution.find()).resolves.toEqual([ + expect.objectContaining({ + userId: user.id, + contributionDate: setZeroHours(now), + clientRequestTime: now.toString(), + amount: expect.decimalEqual(250), + memo: 'Admin contribution for this month', + moderatorId: admin.id, + contributionType: 'ADMIN', + contributionStatus: 'PENDING', + }), + expect.objectContaining({ + userId: user.id, + contributionDate: setZeroHours( + new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()), + ), + clientRequestTime: now.toString(), + amount: expect.decimalEqual(160), + memo: 'Admin contribution for the last month', + moderatorId: admin.id, + contributionType: 'ADMIN', + contributionStatus: 'PENDING', + }), + expect.objectContaining({ + userId: user.id, + contributionDate: setZeroHours( + new Date(now.getFullYear(), now.getMonth() - 2, now.getDate()), + ), + clientRequestTime: now.toString(), + amount: expect.decimalEqual(450), + memo: 'Admin contribution for two months ago', + moderatorId: admin.id, + contributionType: 'ADMIN', + contributionStatus: 'PENDING', + }), + expect.objectContaining({ + userId: user.id, + contributionDate: setZeroHours(now), + clientRequestTime: now.toString(), + amount: expect.decimalEqual(400), + memo: 'Contribution for this month', + moderatorId: null, + contributionType: 'USER', + contributionStatus: 'PENDING', + }), + expect.objectContaining({ + userId: user.id, + contributionDate: setZeroHours( + new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()), + ), + clientRequestTime: now.toString(), + amount: expect.decimalEqual(500), + memo: 'Contribution for the last month', + moderatorId: null, + contributionType: 'USER', + contributionStatus: 'PENDING', + }), + ]) + }) + + describe('call getUserCreation now', () => { + it('returns the expected open contributions', async () => { + await expect(getUserCreation(user.id, now.toString())).resolves.toEqual([ + expect.decimalEqual(550), + expect.decimalEqual(340), + expect.decimalEqual(350), + ]) + }) + + describe('run forward in time one hour before next month', () => { + const targetDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 0, 0) + + beforeAll(() => { + jest.useFakeTimers() + /* eslint-disable-next-line @typescript-eslint/no-empty-function */ + setTimeout(() => {}, targetDate.getTime() - now.getTime()) + jest.runAllTimers() + }) + + it('has the clock set correctly', () => { + expect(new Date().toISOString()).toContain( + `${targetDate.getFullYear()}-${targetDate.getMonth() + 1}-${targetDate.getDate()}T23:`, + ) + }) + + describe('call getUserCreation with UTC', () => { + beforeAll(() => { + setClientRequestTime(targetDate.toString()) + }) + + it('returns the expected open contributions', async () => { + await expect(getUserCreation(user.id, now.toString())).resolves.toEqual([ + expect.decimalEqual(550), + expect.decimalEqual(340), + expect.decimalEqual(350), + ]) + }) + }) + + describe('call getUserCreation with JST (GMT+0900)', () => { + beforeAll(() => { + setClientRequestTime(toJSTzone(targetDate.toString())) + }) + + it('returns the expected open contributions', async () => { + await expect( + getUserCreation(user.id, toJSTzone(targetDate.toString())), + ).resolves.toEqual([ + expect.decimalEqual(340), + expect.decimalEqual(350), + expect.decimalEqual(1000), + ]) + }) + }) + + afterAll(() => { + jest.useRealTimers() + }) + }) + }) + }) +}) diff --git a/backend/test/helpers.ts b/backend/test/helpers.ts index 6e1856b63..a8e313272 100644 --- a/backend/test/helpers.ts +++ b/backend/test/helpers.ts @@ -16,6 +16,7 @@ const context = { push: headerPushMock, forEach: jest.fn(), }, + clientRequestTime: '', } export const cleanDB = async () => { @@ -46,3 +47,8 @@ export const resetEntity = async (entity: any) => { export const resetToken = () => { context.token = '' } + +// format date string as it comes from the frontend for the contribution date +export const contributionDateFormatter = (date: Date): string => { + return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` +} From 2b27e6aaa8ceae94abf9f57bf1b7c5ffc8adc3b1 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Nov 2022 19:18:01 +0100 Subject: [PATCH 02/29] test for valid date string for creation date --- backend/src/graphql/resolver/AdminResolver.test.ts | 8 ++++---- backend/src/graphql/resolver/AdminResolver.ts | 5 +++++ backend/src/graphql/resolver/util/creations.ts | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 6eaec1547..c253d7bb0 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -1066,7 +1066,7 @@ describe('AdminResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( 'No information for available creations with the given creationDate=', - new Date(variables.creationDate).toISOString(), + new Date(variables.creationDate).toString(), ) }) }) @@ -1091,7 +1091,7 @@ describe('AdminResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( 'No information for available creations with the given creationDate=', - new Date(variables.creationDate).toISOString(), + new Date(variables.creationDate).toString(), ) }) }) @@ -1353,7 +1353,7 @@ describe('AdminResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1900 GDD) to be created exceeds the amount (600 GDD) still available for this month.', + 'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', ), ], }), @@ -1362,7 +1362,7 @@ describe('AdminResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (1900 GDD) to be created exceeds the amount (600 GDD) still available for this month.', + 'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', ) }) }) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 479d020ea..d4a5e6cf6 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -49,6 +49,7 @@ import { validateContribution, isStartEndDateValid, updateCreations, + isValidDateString, } from './util/creations' import { CONTRIBUTIONLINK_NAME_MAX_CHARS, @@ -237,6 +238,10 @@ export class AdminResolver { logger.info( `adminCreateContribution(email=${email}, amount=${amount}, memo=${memo}, creationDate=${creationDate})`, ) + if (!isValidDateString(creationDate)) { + logger.error(`invalid Date for creationDate=${creationDate}`) + throw new Error(`invalid Date for creationDate=${creationDate}`) + } const emailContact = await UserContact.findOne({ where: { email }, withDeleted: true, diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index abf4017cb..f56596998 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -137,3 +137,7 @@ export const updateCreations = (creations: Decimal[], contribution: Contribution creations[index] = creations[index].plus(contribution.amount.toString()) return creations } + +export const isValidDateString = (dateString: string): boolean => { + return new Date(dateString).toString() !== 'Invalid Date' +} From 37e94828070d157defdc584d07ee9fe0b555491b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Nov 2022 19:59:33 +0100 Subject: [PATCH 03/29] have timezoneOffset as optional parameter --- .../graphql/resolver/util/creations.test.ts | 95 +++++++++++++------ .../src/graphql/resolver/util/creations.ts | 33 ++++--- 2 files changed, 86 insertions(+), 42 deletions(-) diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts index 544a81d73..84cc0b920 100644 --- a/backend/src/graphql/resolver/util/creations.test.ts +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -1,15 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { - testEnvironment, - resetToken, - cleanDB, - contributionDateFormatter, - setClientRequestTime, - toJSTzone, - toPSTzone, -} from '@test/helpers' +import { testEnvironment, resetToken, cleanDB, contributionDateFormatter } from '@test/helpers' import { logger } from '@test/testSetup' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { peterLustig } from '@/seeds/users/peter-lustig' @@ -17,7 +9,7 @@ import { User } from '@entity/User' import { Contribution } from '@entity/Contribution' import { userFactory } from '@/seeds/factory/user' import { login, createContribution, adminCreateContribution } from '@/seeds/graphql/mutations' -import { getUserCreation } from './creations' +import { getUserCreation, validateContribution } from './creations' let mutate: any, query: any, con: any let testEnv: any @@ -52,7 +44,6 @@ describe('util/creation', () => { describe('getUserCreations', () => { beforeAll(async () => { - setClientRequestTime(now.toString()) await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, @@ -117,7 +108,6 @@ describe('util/creation', () => { expect.objectContaining({ userId: user.id, contributionDate: setZeroHours(now), - clientRequestTime: now.toString(), amount: expect.decimalEqual(250), memo: 'Admin contribution for this month', moderatorId: admin.id, @@ -129,7 +119,6 @@ describe('util/creation', () => { contributionDate: setZeroHours( new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()), ), - clientRequestTime: now.toString(), amount: expect.decimalEqual(160), memo: 'Admin contribution for the last month', moderatorId: admin.id, @@ -141,7 +130,6 @@ describe('util/creation', () => { contributionDate: setZeroHours( new Date(now.getFullYear(), now.getMonth() - 2, now.getDate()), ), - clientRequestTime: now.toString(), amount: expect.decimalEqual(450), memo: 'Admin contribution for two months ago', moderatorId: admin.id, @@ -151,7 +139,6 @@ describe('util/creation', () => { expect.objectContaining({ userId: user.id, contributionDate: setZeroHours(now), - clientRequestTime: now.toString(), amount: expect.decimalEqual(400), memo: 'Contribution for this month', moderatorId: null, @@ -163,7 +150,6 @@ describe('util/creation', () => { contributionDate: setZeroHours( new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()), ), - clientRequestTime: now.toString(), amount: expect.decimalEqual(500), memo: 'Contribution for the last month', moderatorId: null, @@ -175,7 +161,7 @@ describe('util/creation', () => { describe('call getUserCreation now', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, now.toString())).resolves.toEqual([ + await expect(getUserCreation(user.id)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), @@ -192,6 +178,10 @@ describe('util/creation', () => { jest.runAllTimers() }) + afterAll(() => { + jest.useRealTimers() + }) + it('has the clock set correctly', () => { expect(new Date().toISOString()).toContain( `${targetDate.getFullYear()}-${targetDate.getMonth() + 1}-${targetDate.getDate()}T23:`, @@ -199,12 +189,8 @@ describe('util/creation', () => { }) describe('call getUserCreation with UTC', () => { - beforeAll(() => { - setClientRequestTime(targetDate.toString()) - }) - it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, now.toString())).resolves.toEqual([ + await expect(getUserCreation(user.id)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), @@ -213,14 +199,8 @@ describe('util/creation', () => { }) describe('call getUserCreation with JST (GMT+0900)', () => { - beforeAll(() => { - setClientRequestTime(toJSTzone(targetDate.toString())) - }) - it('returns the expected open contributions', async () => { - await expect( - getUserCreation(user.id, toJSTzone(targetDate.toString())), - ).resolves.toEqual([ + await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ expect.decimalEqual(340), expect.decimalEqual(350), expect.decimalEqual(1000), @@ -228,8 +208,61 @@ describe('util/creation', () => { }) }) - afterAll(() => { - jest.useRealTimers() + describe('call getUserCreation with PST (GMT-0800)', () => { + it('returns the expected open contributions', async () => { + await expect(getUserCreation(user.id, true, 450)).resolves.toEqual([ + expect.decimalEqual(550), + expect.decimalEqual(340), + expect.decimalEqual(350), + ]) + }) + }) + + describe('run two hours forward to be in the next month in UTC', () => { + const nextMonthTargetDate = new Date() + nextMonthTargetDate.setTime(targetDate.getTime() + 2 * 60 * 60 * 1000) + + beforeAll(() => { + /* eslint-disable-next-line @typescript-eslint/no-empty-function */ + setTimeout(() => {}, 2 * 60 * 60 * 1000) + jest.runAllTimers() + }) + + it('has the clock set correctly', () => { + expect(new Date().toISOString()).toContain( + `${nextMonthTargetDate.getFullYear()}-${nextMonthTargetDate.getMonth() + 1}-01T01:`, + ) + }) + + describe('call getUserCreation with UTC', () => { + it('returns the expected open contributions', async () => { + await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ + expect.decimalEqual(340), + expect.decimalEqual(350), + expect.decimalEqual(1000), + ]) + }) + }) + + describe('call getUserCreation with JST (GMT+0900)', () => { + it('returns the expected open contributions', async () => { + await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ + expect.decimalEqual(340), + expect.decimalEqual(350), + expect.decimalEqual(1000), + ]) + }) + }) + + describe('call getUserCreation with PST (GMT-0800)', () => { + it('returns the expected open contributions', async () => { + await expect(getUserCreation(user.id, true, 450)).resolves.toEqual([ + expect.decimalEqual(550), + expect.decimalEqual(340), + expect.decimalEqual(350), + ]) + }) + }) }) }) }) diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index f56596998..29d8313e9 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -13,9 +13,10 @@ export const validateContribution = ( creations: Decimal[], amount: Decimal, creationDate: Date, + timezoneOffset = 0, ): void => { logger.trace('isContributionValid: ', creations, amount, creationDate) - const index = getCreationIndex(creationDate.getMonth()) + const index = getCreationIndex(creationDate.getMonth(), timezoneOffset) if (index < 0) { logger.error( @@ -38,9 +39,10 @@ export const validateContribution = ( export const getUserCreations = async ( ids: number[], includePending = true, + timezoneOffset = 0, ): Promise => { logger.trace('getUserCreations:', ids, includePending) - const months = getCreationMonths() + const months = getCreationMonths(timezoneOffset) logger.trace('getUserCreations months', months) const queryRunner = getConnection().createQueryRunner() @@ -87,15 +89,20 @@ export const getUserCreations = async ( }) } -export const getUserCreation = async (id: number, includePending = true): Promise => { - logger.trace('getUserCreation', id, includePending) - const creations = await getUserCreations([id], includePending) +export const getUserCreation = async ( + id: number, + includePending = true, + timezoneOffset = 0, +): Promise => { + logger.trace('getUserCreation', id, includePending, timezoneOffset) + const creations = await getUserCreations([id], includePending, timezoneOffset) logger.trace('getUserCreation creations=', creations) return creations[0] ? creations[0].creations : FULL_CREATION_AVAILABLE } -export const getCreationMonths = (): number[] => { - const now = new Date(Date.now()) +const getCreationMonths = (timezoneOffset: number): number[] => { + const now = new Date() + now.setTime(now.getTime() - timezoneOffset * 60 * 1000) return [ now.getMonth() + 1, new Date(now.getFullYear(), now.getMonth() - 1, 1).getMonth() + 1, @@ -103,8 +110,8 @@ export const getCreationMonths = (): number[] => { ].reverse() } -export const getCreationIndex = (month: number): number => { - return getCreationMonths().findIndex((el) => el === month + 1) +const getCreationIndex = (month: number, timezoneOffset: number): number => { + return getCreationMonths(timezoneOffset).findIndex((el) => el === month + 1) } export const isStartEndDateValid = ( @@ -128,8 +135,12 @@ export const isStartEndDateValid = ( } } -export const updateCreations = (creations: Decimal[], contribution: Contribution): Decimal[] => { - const index = getCreationIndex(contribution.contributionDate.getMonth()) +export const updateCreations = ( + creations: Decimal[], + contribution: Contribution, + timezoneOffset = 0, +): Decimal[] => { + const index = getCreationIndex(contribution.contributionDate.getMonth(), timezoneOffset) if (index < 0) { throw new Error('You cannot create GDD for a month older than the last three months.') From e3d3f4bb9406ff65e8337c179daa9e7942989438 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 11 Nov 2022 11:30:25 +0100 Subject: [PATCH 04/29] liniting and improved names --- backend/src/graphql/resolver/util/creations.test.ts | 10 ++++------ backend/src/graphql/resolver/util/creations.ts | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts index 84cc0b920..601d73027 100644 --- a/backend/src/graphql/resolver/util/creations.test.ts +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -1,23 +1,21 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { testEnvironment, resetToken, cleanDB, contributionDateFormatter } from '@test/helpers' -import { logger } from '@test/testSetup' +import { testEnvironment, cleanDB, contributionDateFormatter } from '@test/helpers' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { peterLustig } from '@/seeds/users/peter-lustig' import { User } from '@entity/User' import { Contribution } from '@entity/Contribution' import { userFactory } from '@/seeds/factory/user' import { login, createContribution, adminCreateContribution } from '@/seeds/graphql/mutations' -import { getUserCreation, validateContribution } from './creations' +import { getUserCreation } from './creations' -let mutate: any, query: any, con: any +let mutate: any, con: any let testEnv: any beforeAll(async () => { testEnv = await testEnvironment() mutate = testEnv.mutate - query = testEnv.query con = testEnv.con await cleanDB() }) @@ -210,7 +208,7 @@ describe('util/creation', () => { describe('call getUserCreation with PST (GMT-0800)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, 450)).resolves.toEqual([ + await expect(getUserCreation(user.id, true, 480)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 29d8313e9..decf4de27 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -101,12 +101,12 @@ export const getUserCreation = async ( } const getCreationMonths = (timezoneOffset: number): number[] => { - const now = new Date() - now.setTime(now.getTime() - timezoneOffset * 60 * 1000) + const clientNow = new Date() + clientNow.setTime(clientNow.getTime() - timezoneOffset * 60 * 1000) return [ - now.getMonth() + 1, - new Date(now.getFullYear(), now.getMonth() - 1, 1).getMonth() + 1, - new Date(now.getFullYear(), now.getMonth() - 2, 1).getMonth() + 1, + clientNow.getMonth() + 1, + new Date(clientNow.getFullYear(), clientNow.getMonth() - 1, 1).getMonth() + 1, + new Date(clientNow.getFullYear(), clientNow.getMonth() - 2, 1).getMonth() + 1, ].reverse() } From 6a47ba78297de6f98f604b184d713a333b4a569e Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 14 Nov 2022 14:29:16 +0100 Subject: [PATCH 05/29] close all open collapse by change tabs in community --- .../components/Contributions/ContributionListItem.vue | 2 ++ frontend/src/pages/Community.vue | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Contributions/ContributionListItem.vue b/frontend/src/components/Contributions/ContributionListItem.vue index 683d234ba..82b796e33 100644 --- a/frontend/src/components/Contributions/ContributionListItem.vue +++ b/frontend/src/components/Contributions/ContributionListItem.vue @@ -54,6 +54,7 @@ v-b-toggle="collapsId" icon="chat-dots" class="h2 mr-5" + @mousedown="$root.$emit('close-all-open-collapse')" @click="getListContributionMessages" > @@ -64,6 +65,7 @@ v-if="state === 'IN_PROGRESS'" v-b-toggle="collapsId" variant="warning" + @mousedown="$root.$emit('close-all-open-collapse')" @click="getListContributionMessages" > {{ $t('contribution.alert.answerQuestion') }} diff --git a/frontend/src/pages/Community.vue b/frontend/src/pages/Community.vue index 786307405..28d3bdabf 100644 --- a/frontend/src/pages/Community.vue +++ b/frontend/src/pages/Community.vue @@ -49,7 +49,7 @@ :pageSize="pageSize" /> - +

{{ $t('navigation.community') }}

@@ -70,6 +70,7 @@ :items="itemsAll" @update-list-contributions="updateListAllContributions" @update-contribution-form="updateContributionForm" + @close-all-open-collapse="closeAllOpenCollapse" :contributionCount="contributionCountAll" :showPagination="true" :pageSize="pageSizeAll" @@ -112,6 +113,13 @@ export default { } }, methods: { + closeAllOpenCollapse() { + // console.log('Community closeAllOpenCollapse ') + // console.log('closeAllOpenCollapse', this.$el.querySelectorAll('.collapse.show')) + this.$el.querySelectorAll('.collapse.show').forEach((value) => { + this.$root.$emit('bv::toggle::collapse', value.id) + }) + }, setContribution(data) { this.$apollo .mutate({ From 1afac12aa831380fa18cc56812b74f7a988b09b5 Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 14 Nov 2022 15:23:44 +0100 Subject: [PATCH 06/29] close open messagebox by open another messagebox --- frontend/src/components/Contributions/ContributionList.vue | 1 + frontend/src/components/Contributions/ContributionListItem.vue | 3 +-- frontend/src/pages/Community.vue | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Contributions/ContributionList.vue b/frontend/src/components/Contributions/ContributionList.vue index ca4e7a9a0..e76664e03 100644 --- a/frontend/src/components/Contributions/ContributionList.vue +++ b/frontend/src/components/Contributions/ContributionList.vue @@ -3,6 +3,7 @@

{{ $t('contribution.alert.answerQuestion') }} diff --git a/frontend/src/pages/Community.vue b/frontend/src/pages/Community.vue index 28d3bdabf..ae0dca62e 100644 --- a/frontend/src/pages/Community.vue +++ b/frontend/src/pages/Community.vue @@ -39,6 +39,7 @@
Date: Mon, 14 Nov 2022 15:31:42 +0100 Subject: [PATCH 07/29] same event on chat-dot icon --- frontend/src/components/Contributions/ContributionListItem.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/components/Contributions/ContributionListItem.vue b/frontend/src/components/Contributions/ContributionListItem.vue index 6cf8f4cab..d09b1c790 100644 --- a/frontend/src/components/Contributions/ContributionListItem.vue +++ b/frontend/src/components/Contributions/ContributionListItem.vue @@ -54,8 +54,7 @@ v-b-toggle="collapsId" icon="chat-dots" class="h2 mr-5" - @mousedown="$root.$emit('close-all-open-collapse')" - @click="getListContributionMessages" + @click="$emit('closeAllOpenCollapse'), getListContributionMessages" > From 6e83d9447a66d426d9ea22e37b8f48f69ea0c4e6 Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 14 Nov 2022 16:49:00 +0100 Subject: [PATCH 08/29] add test, coverage to over 95% --- .../Contributions/ContributionListItem.spec.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frontend/src/components/Contributions/ContributionListItem.spec.js b/frontend/src/components/Contributions/ContributionListItem.spec.js index 0b0519dda..f477d1c2c 100644 --- a/frontend/src/components/Contributions/ContributionListItem.spec.js +++ b/frontend/src/components/Contributions/ContributionListItem.spec.js @@ -132,6 +132,16 @@ describe('ContributionListItem', () => { expect(wrapper.emitted('delete-contribution')).toBeFalsy() }) }) + + describe('updateState', () => { + beforeEach(async () => { + await wrapper.vm.updateState() + }) + + it('emit update-state', () => { + expect(wrapper.vm.$emit('update-state')).toBeTruthy() + }) + }) }) }) }) From 1b3a25eb0dd2e4ad153ef95bbab0a0d55ff4c1d6 Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 14 Nov 2022 17:27:43 +0100 Subject: [PATCH 09/29] close all open collapse if tab new constribution and if edit --- .../Contributions/ContributionListItem.vue | 13 +++++++------ frontend/src/pages/Community.vue | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/Contributions/ContributionListItem.vue b/frontend/src/components/Contributions/ContributionListItem.vue index d09b1c790..22f7e791c 100644 --- a/frontend/src/components/Contributions/ContributionListItem.vue +++ b/frontend/src/components/Contributions/ContributionListItem.vue @@ -32,12 +32,13 @@ v-if="!['CONFIRMED', 'DELETED'].includes(state) && !allContribution" class="pointer ml-5" @click=" - $emit('update-contribution-form', { - id: id, - contributionDate: contributionDate, - memo: memo, - amount: amount, - }) + $emit('closeAllOpenCollapse'), + $emit('update-contribution-form', { + id: id, + contributionDate: contributionDate, + memo: memo, + amount: amount, + }) " > diff --git a/frontend/src/pages/Community.vue b/frontend/src/pages/Community.vue index ae0dca62e..58426207f 100644 --- a/frontend/src/pages/Community.vue +++ b/frontend/src/pages/Community.vue @@ -2,7 +2,7 @@
- + Date: Mon, 14 Nov 2022 17:44:48 +0100 Subject: [PATCH 10/29] release: Version 1.14.0 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ admin/package.json | 2 +- backend/package.json | 2 +- database/package.json | 2 +- frontend/package.json | 2 +- package.json | 2 +- 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 754566658..1178a09f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,37 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.14.0](https://github.com/gradido/gradido/compare/1.13.3...1.14.0) + +- fix(frontend): close all open collapse by change tabs in community [`#2388`](https://github.com/gradido/gradido/pull/2388) +- fix(backend): corrected E-Mail texts [`#2386`](https://github.com/gradido/gradido/pull/2386) +- fix(frontend): better history messages [`#2381`](https://github.com/gradido/gradido/pull/2381) +- fix(frontend): mailto link [`#2383`](https://github.com/gradido/gradido/pull/2383) +- fix(admin): fix text in admin area to uppercase [`#2365`](https://github.com/gradido/gradido/pull/2365) +- feat(frontend): move the information about gradido being free to the auth layout [`#2349`](https://github.com/gradido/gradido/pull/2349) +- fix(admin): load error fixed for contribution link [`#2364`](https://github.com/gradido/gradido/pull/2364) +- fix(admin): edit contribution link does not take old values [`#2362`](https://github.com/gradido/gradido/pull/2362) +- fix(other): corrected dockerfile descriptions [`#2346`](https://github.com/gradido/gradido/pull/2346) +- feat(backend): 🍰 Send email for rejected contributions [`#2340`](https://github.com/gradido/gradido/pull/2340) +- feat(admin): edit automatic contribution link [`#2309`](https://github.com/gradido/gradido/pull/2309) +- refactor(backend): fix logger mocks [`#2308`](https://github.com/gradido/gradido/pull/2308) +- fix(admin): update contribution list after admin updates contribution [`#2330`](https://github.com/gradido/gradido/pull/2330) +- fix(frontend): inconsistent labeling on login register [`#2350`](https://github.com/gradido/gradido/pull/2350) +- feat(backend): setup hyperswarm [`#1874`](https://github.com/gradido/gradido/pull/1874) +- feat(other): lint pull request workflow [`#2338`](https://github.com/gradido/gradido/pull/2338) +- Feature: 🍰 add updated at to contributions [`#2237`](https://github.com/gradido/gradido/pull/2237) +- Refactor: GitHub test workflow - disable video recording and reduce wait time [`#2336`](https://github.com/gradido/gradido/pull/2336) +- 2274 feature concept manuel user registration for admins [`#2289`](https://github.com/gradido/gradido/pull/2289) +- 1574 concept to introduce gradidoID and change password encryption [`#2252`](https://github.com/gradido/gradido/pull/2252) +- contributionlink stage-2 and stage-3 of capturing and activation [`#2241`](https://github.com/gradido/gradido/pull/2241) +- Github workflow: update actions to the current API version using Node v 16 [`#2323`](https://github.com/gradido/gradido/pull/2323) +- feature: Fullstack tests in GitHub workflow [`#2319`](https://github.com/gradido/gradido/pull/2319) + #### [1.13.3](https://github.com/gradido/gradido/compare/1.13.2...1.13.3) +> 1 November 2022 + +- release: Version 1.13.3 [`#2322`](https://github.com/gradido/gradido/pull/2322) - 2294 contribution links on its own page [`#2312`](https://github.com/gradido/gradido/pull/2312) - fix: Change Orange Color [`#2302`](https://github.com/gradido/gradido/pull/2302) - fix: Release Statistic Query Runner [`#2320`](https://github.com/gradido/gradido/pull/2320) diff --git a/admin/package.json b/admin/package.json index 82a2413de..2adbbce0c 100644 --- a/admin/package.json +++ b/admin/package.json @@ -3,7 +3,7 @@ "description": "Administraion Interface for Gradido", "main": "index.js", "author": "Moriz Wahl", - "version": "1.13.3", + "version": "1.14.0", "license": "Apache-2.0", "private": false, "scripts": { diff --git a/backend/package.json b/backend/package.json index 1db683b2a..92a31f4a0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "gradido-backend", - "version": "1.13.3", + "version": "1.14.0", "description": "Gradido unified backend providing an API-Service for Gradido Transactions", "main": "src/index.ts", "repository": "https://github.com/gradido/gradido/backend", diff --git a/database/package.json b/database/package.json index 096c7a9bd..08e6695a4 100644 --- a/database/package.json +++ b/database/package.json @@ -1,6 +1,6 @@ { "name": "gradido-database", - "version": "1.13.3", + "version": "1.14.0", "description": "Gradido Database Tool to execute database migrations", "main": "src/index.ts", "repository": "https://github.com/gradido/gradido/database", diff --git a/frontend/package.json b/frontend/package.json index 4e983d716..69aecf595 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "bootstrap-vue-gradido-wallet", - "version": "1.13.3", + "version": "1.14.0", "private": true, "scripts": { "start": "node run/server.js", diff --git a/package.json b/package.json index 8e5fcfc70..11a1cc473 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gradido", - "version": "1.13.3", + "version": "1.14.0", "description": "Gradido", "main": "index.js", "repository": "git@github.com:gradido/gradido.git", From fd1fb07184d739760017089a715493a9b5f2a1b7 Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 14 Nov 2022 19:42:54 +0100 Subject: [PATCH 11/29] load contributionMessages is fixed --- .../src/components/Contributions/ContributionListItem.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Contributions/ContributionListItem.vue b/frontend/src/components/Contributions/ContributionListItem.vue index 22f7e791c..9d38b9309 100644 --- a/frontend/src/components/Contributions/ContributionListItem.vue +++ b/frontend/src/components/Contributions/ContributionListItem.vue @@ -55,7 +55,7 @@ v-b-toggle="collapsId" icon="chat-dots" class="h2 mr-5" - @click="$emit('closeAllOpenCollapse'), getListContributionMessages" + @click="getListContributionMessages" >
@@ -65,7 +65,7 @@ v-if="state === 'IN_PROGRESS'" v-b-toggle="collapsId" variant="warning" - @click="$emit('closeAllOpenCollapse'), getListContributionMessages" + @click="getListContributionMessages" > {{ $t('contribution.alert.answerQuestion') }} @@ -181,6 +181,7 @@ export default { }, getListContributionMessages() { // console.log('getListContributionMessages', this.contributionId) + this.$emit('closeAllOpenCollapse') this.$apollo .query({ query: listContributionMessages, From ee570e9578b21f6bd17be78aad3d2268a02c564f Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 14 Nov 2022 20:22:36 +0100 Subject: [PATCH 12/29] fix errors --- .../ContributionMessages/ContributionMessagesFormular.vue | 2 +- .../ContributionMessages/ContributionMessagesList.vue | 4 ++-- .../src/components/Contributions/ContributionListItem.vue | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/ContributionMessages/ContributionMessagesFormular.vue b/frontend/src/components/ContributionMessages/ContributionMessagesFormular.vue index 1a5928cc3..c601de4f5 100644 --- a/frontend/src/components/ContributionMessages/ContributionMessagesFormular.vue +++ b/frontend/src/components/ContributionMessages/ContributionMessagesFormular.vue @@ -51,7 +51,7 @@ export default { }, }) .then((result) => { - this.$emit('get-list-contribution-messages', this.contributionId) + this.$emit('get-list-contribution-messages', false) this.$emit('update-state', this.contributionId) this.form.text = '' this.toastSuccess(this.$t('message.reply')) diff --git a/frontend/src/components/ContributionMessages/ContributionMessagesList.vue b/frontend/src/components/ContributionMessages/ContributionMessagesList.vue index 4b7045a40..52cf403e6 100644 --- a/frontend/src/components/ContributionMessages/ContributionMessagesList.vue +++ b/frontend/src/components/ContributionMessages/ContributionMessagesList.vue @@ -9,7 +9,7 @@ @@ -51,7 +51,7 @@ export default { }, methods: { getListContributionMessages() { - this.$emit('get-list-contribution-messages', this.contributionId) + this.$emit('get-list-contribution-messages') }, updateState(id) { this.$emit('update-state', id) diff --git a/frontend/src/components/Contributions/ContributionListItem.vue b/frontend/src/components/Contributions/ContributionListItem.vue index 9d38b9309..53de8c461 100644 --- a/frontend/src/components/Contributions/ContributionListItem.vue +++ b/frontend/src/components/Contributions/ContributionListItem.vue @@ -179,9 +179,10 @@ export default { if (value) this.$emit('delete-contribution', item) }) }, - getListContributionMessages() { - // console.log('getListContributionMessages', this.contributionId) - this.$emit('closeAllOpenCollapse') + getListContributionMessages(closeCollapse = true) { + if (closeCollapse) { + this.$emit('closeAllOpenCollapse') + } this.$apollo .query({ query: listContributionMessages, From 1a34db8b1894a757bba2fc5b2ac5953d1c923ad5 Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 14 Nov 2022 20:36:34 +0100 Subject: [PATCH 13/29] fix test --- .../ContributionMessagesFormular.spec.js | 4 ++-- .../Contributions/ContributionListItem.spec.js | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ContributionMessages/ContributionMessagesFormular.spec.js b/frontend/src/components/ContributionMessages/ContributionMessagesFormular.spec.js index aba5abc34..42deac9cb 100644 --- a/frontend/src/components/ContributionMessages/ContributionMessagesFormular.spec.js +++ b/frontend/src/components/ContributionMessages/ContributionMessagesFormular.spec.js @@ -67,9 +67,9 @@ describe('ContributionMessagesFormular', () => { await wrapper.find('form').trigger('submit') }) - it('emitted "get-list-contribution-messages" with data', async () => { + it('emitted "get-list-contribution-messages" with false', async () => { expect(wrapper.emitted('get-list-contribution-messages')).toEqual( - expect.arrayContaining([expect.arrayContaining([42])]), + expect.arrayContaining([expect.arrayContaining([false])]), ) }) diff --git a/frontend/src/components/Contributions/ContributionListItem.spec.js b/frontend/src/components/Contributions/ContributionListItem.spec.js index f477d1c2c..66e3bcef9 100644 --- a/frontend/src/components/Contributions/ContributionListItem.spec.js +++ b/frontend/src/components/Contributions/ContributionListItem.spec.js @@ -9,6 +9,7 @@ describe('ContributionListItem', () => { const mocks = { $t: jest.fn((t) => t), $d: jest.fn((d) => d), + $apollo: { query: jest.fn().mockResolvedValue() }, } const propsData = { @@ -143,5 +144,16 @@ describe('ContributionListItem', () => { }) }) }) + + describe('getListContributionMessages', () => { + beforeEach(() => { + wrapper + .findComponent({ name: 'ContributionMessagesList' }) + .vm.$emit('get-list-contribution-messages') + }) + it('emits closeAllOpenCollapse', () => { + expect(wrapper.emitted('closeAllOpenCollapse')).toBeTruthy() + }) + }) }) }) From 428c4fd22d702751a6a074dd7b794e8ea3b85586 Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 14 Nov 2022 20:38:55 +0100 Subject: [PATCH 14/29] remove unused code --- .../ContributionMessages/ContributionMessagesList.vue | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/components/ContributionMessages/ContributionMessagesList.vue b/frontend/src/components/ContributionMessages/ContributionMessagesList.vue index 52cf403e6..e9262c073 100644 --- a/frontend/src/components/ContributionMessages/ContributionMessagesList.vue +++ b/frontend/src/components/ContributionMessages/ContributionMessagesList.vue @@ -50,9 +50,6 @@ export default { }, }, methods: { - getListContributionMessages() { - this.$emit('get-list-contribution-messages') - }, updateState(id) { this.$emit('update-state', id) }, From dca87634722582626c7907edef8dc1937607eb1d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 14 Nov 2022 20:57:25 +0100 Subject: [PATCH 15/29] remove depricated test --- .../ContributionMessagesList.spec.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frontend/src/components/ContributionMessages/ContributionMessagesList.spec.js b/frontend/src/components/ContributionMessages/ContributionMessagesList.spec.js index 7798532b7..c5c26a2c0 100644 --- a/frontend/src/components/ContributionMessages/ContributionMessagesList.spec.js +++ b/frontend/src/components/ContributionMessages/ContributionMessagesList.spec.js @@ -40,16 +40,6 @@ describe('ContributionMessagesList', () => { expect(wrapper.findComponent({ name: 'ContributionMessagesFormular' }).exists()).toBe(true) }) - describe('get List Contribution Messages', () => { - beforeEach(() => { - wrapper.vm.getListContributionMessages() - }) - - it('emits getListContributionMessages', async () => { - expect(wrapper.vm.$emit('get-list-contribution-messages')).toBeTruthy() - }) - }) - describe('update State', () => { beforeEach(() => { wrapper.vm.updateState() From debc27549fd7df324b1413e3d46544b1c6bb007d Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 14 Nov 2022 21:13:41 +0100 Subject: [PATCH 16/29] v1.14.1 --- CHANGELOG.md | 7 +++++++ admin/package.json | 2 +- backend/package.json | 2 +- database/package.json | 2 +- frontend/package.json | 2 +- package.json | 2 +- 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1178a09f9..9ce354b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,15 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.14.1](https://github.com/gradido/gradido/compare/1.14.0...1.14.1) + +- fix(frontend): load contributionMessages is fixed [`#2390`](https://github.com/gradido/gradido/pull/2390) + #### [1.14.0](https://github.com/gradido/gradido/compare/1.13.3...1.14.0) +> 14 November 2022 + +- chore(release): version 1.14.0 [`#2389`](https://github.com/gradido/gradido/pull/2389) - fix(frontend): close all open collapse by change tabs in community [`#2388`](https://github.com/gradido/gradido/pull/2388) - fix(backend): corrected E-Mail texts [`#2386`](https://github.com/gradido/gradido/pull/2386) - fix(frontend): better history messages [`#2381`](https://github.com/gradido/gradido/pull/2381) diff --git a/admin/package.json b/admin/package.json index 2adbbce0c..7f0e7ffd5 100644 --- a/admin/package.json +++ b/admin/package.json @@ -3,7 +3,7 @@ "description": "Administraion Interface for Gradido", "main": "index.js", "author": "Moriz Wahl", - "version": "1.14.0", + "version": "1.14.1", "license": "Apache-2.0", "private": false, "scripts": { diff --git a/backend/package.json b/backend/package.json index 92a31f4a0..332f8ddae 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "gradido-backend", - "version": "1.14.0", + "version": "1.14.1", "description": "Gradido unified backend providing an API-Service for Gradido Transactions", "main": "src/index.ts", "repository": "https://github.com/gradido/gradido/backend", diff --git a/database/package.json b/database/package.json index 08e6695a4..6216a25fb 100644 --- a/database/package.json +++ b/database/package.json @@ -1,6 +1,6 @@ { "name": "gradido-database", - "version": "1.14.0", + "version": "1.14.1", "description": "Gradido Database Tool to execute database migrations", "main": "src/index.ts", "repository": "https://github.com/gradido/gradido/database", diff --git a/frontend/package.json b/frontend/package.json index 69aecf595..cfc12630e 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "bootstrap-vue-gradido-wallet", - "version": "1.14.0", + "version": "1.14.1", "private": true, "scripts": { "start": "node run/server.js", diff --git a/package.json b/package.json index 11a1cc473..72efee984 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gradido", - "version": "1.14.0", + "version": "1.14.1", "description": "Gradido", "main": "index.js", "repository": "git@github.com:gradido/gradido.git", From cff2396bd12bd4798a46f3da46648c5baa199581 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 13:00:59 +0100 Subject: [PATCH 17/29] change client request time to client time zone offset --- frontend/src/plugins/apolloProvider.js | 2 +- frontend/src/plugins/apolloProvider.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/plugins/apolloProvider.js b/frontend/src/plugins/apolloProvider.js index 05954d36b..7a37bcf62 100644 --- a/frontend/src/plugins/apolloProvider.js +++ b/frontend/src/plugins/apolloProvider.js @@ -12,7 +12,7 @@ const authLink = new ApolloLink((operation, forward) => { operation.setContext({ headers: { Authorization: token && token.length > 0 ? `Bearer ${token}` : '', - clientRequestTime: new Date().toString(), + clientTimezoneOffset: new Date().getTimezoneOffset(), }, }) return forward(operation).map((response) => { diff --git a/frontend/src/plugins/apolloProvider.test.js b/frontend/src/plugins/apolloProvider.test.js index 31e1a664b..584014213 100644 --- a/frontend/src/plugins/apolloProvider.test.js +++ b/frontend/src/plugins/apolloProvider.test.js @@ -98,7 +98,7 @@ describe('apolloProvider', () => { expect(setContextMock).toBeCalledWith({ headers: { Authorization: 'Bearer some-token', - clientRequestTime: expect.any(String), + clientTimezoneOffset: expect.any(Number), }, }) }) @@ -114,7 +114,7 @@ describe('apolloProvider', () => { expect(setContextMock).toBeCalledWith({ headers: { Authorization: '', - clientRequestTime: expect.any(String), + clientTimezoneOffset: expect.any(Number), }, }) }) From 1b6dab32bf505b592a2b1fbe398a9d0a758dbba0 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 13:03:42 +0100 Subject: [PATCH 18/29] change client request time to client time zone offset --- admin/src/plugins/apolloProvider.js | 2 +- admin/src/plugins/apolloProvider.test.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/src/plugins/apolloProvider.js b/admin/src/plugins/apolloProvider.js index 95b7aab7e..8b02013f4 100644 --- a/admin/src/plugins/apolloProvider.js +++ b/admin/src/plugins/apolloProvider.js @@ -10,7 +10,7 @@ const authLink = new ApolloLink((operation, forward) => { operation.setContext({ headers: { Authorization: token && token.length > 0 ? `Bearer ${token}` : '', - clientRequestTime: new Date().toString(), + clientTimezoneOffset: new Date().getTimezoneOffset(), }, }) return forward(operation).map((response) => { diff --git a/admin/src/plugins/apolloProvider.test.js b/admin/src/plugins/apolloProvider.test.js index 7889c3318..483862bea 100644 --- a/admin/src/plugins/apolloProvider.test.js +++ b/admin/src/plugins/apolloProvider.test.js @@ -94,7 +94,7 @@ describe('apolloProvider', () => { expect(setContextMock).toBeCalledWith({ headers: { Authorization: 'Bearer some-token', - clientRequestTime: expect.any(String), + clientTimezoneOffset: expect.any(Number), }, }) }) @@ -110,7 +110,7 @@ describe('apolloProvider', () => { expect(setContextMock).toBeCalledWith({ headers: { Authorization: '', - clientRequestTime: expect.any(String), + clientTimezoneOffset: expect.any(Number), }, }) }) From e89e30e33eb744082bc1a6503969f422c2790fef Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 13:14:25 +0100 Subject: [PATCH 19/29] change client request time to client timezone offset --- backend/src/server/context.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/src/server/context.ts b/backend/src/server/context.ts index 5bfc22e72..dd4056e42 100644 --- a/backend/src/server/context.ts +++ b/backend/src/server/context.ts @@ -9,7 +9,7 @@ export interface Context { setHeaders: { key: string; value: string }[] role?: Role user?: dbUser - clientRequestTime?: string + clientTimezoneOffset?: number // hack to use less DB calls for Balance Resolver lastTransaction?: dbTransaction transactionCount?: number @@ -19,7 +19,7 @@ export interface Context { const context = (args: ExpressContext): Context => { const authorization = args.req.headers.authorization - const clientRequestTime = args.req.headers.clientrequesttime + const clientTimezoneOffset = args.req.headers.clienttimezoneoffset const context: Context = { token: null, setHeaders: [], @@ -27,8 +27,8 @@ const context = (args: ExpressContext): Context => { if (authorization) { context.token = authorization.replace(/^Bearer /, '') } - if (clientRequestTime && typeof clientRequestTime === 'string') { - context.clientRequestTime = clientRequestTime + if (clientTimezoneOffset && typeof clientTimezoneOffset === 'string') { + context.clientTimezoneOffset = Number(clientTimezoneOffset) } return context } @@ -38,4 +38,10 @@ export const getUser = (context: Context): dbUser => { throw new Error('No user given in context!') } +export const getClientTimezoneOffset = (context: Context): number => { + if (context.clientTimezoneOffset && Math.abs(context.clientTimezoneOffset) <= 27 * 60) + return context.clientTimezoneOffset + throw new Error('No valid client time zone offset in context!') +} + export default context From fbc1b2bc3294a8cba42a7d83ed595c29850d4386 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 14:08:53 +0100 Subject: [PATCH 20/29] fix get client time zone offset --- backend/src/server/context.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/server/context.ts b/backend/src/server/context.ts index dd4056e42..8ba590dd3 100644 --- a/backend/src/server/context.ts +++ b/backend/src/server/context.ts @@ -39,8 +39,12 @@ export const getUser = (context: Context): dbUser => { } export const getClientTimezoneOffset = (context: Context): number => { - if (context.clientTimezoneOffset && Math.abs(context.clientTimezoneOffset) <= 27 * 60) + if ( + (context.clientTimezoneOffset || context.clientTimezoneOffset === 0) && + Math.abs(context.clientTimezoneOffset) <= 27 * 60 + ) { return context.clientTimezoneOffset + } throw new Error('No valid client time zone offset in context!') } From b7cf29017105e67c7d1e2328f6fd4af8a8ade7c0 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 14:09:33 +0100 Subject: [PATCH 21/29] setup tests for client time zone offset --- backend/test/helpers.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/test/helpers.ts b/backend/test/helpers.ts index a8e313272..53fd21607 100644 --- a/backend/test/helpers.ts +++ b/backend/test/helpers.ts @@ -16,7 +16,7 @@ const context = { push: headerPushMock, forEach: jest.fn(), }, - clientRequestTime: '', + clientTimezoneOffset: 0, } export const cleanDB = async () => { @@ -52,3 +52,7 @@ export const resetToken = () => { export const contributionDateFormatter = (date: Date): string => { return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` } + +export const setClientTimezoneOffset = (offset: number): void => { + context.clientTimezoneOffset = offset +} From d36fee0302bb935b6a40401623855c45a83e6c43 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 14:10:18 +0100 Subject: [PATCH 22/29] change order off arguements, implement changes for admin and contribution resolver --- backend/src/graphql/resolver/AdminResolver.ts | 38 +++++++++++++------ .../graphql/resolver/ContributionResolver.ts | 12 +++--- .../graphql/resolver/util/creations.test.ts | 14 +++---- .../src/graphql/resolver/util/creations.ts | 6 +-- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index d4a5e6cf6..1e1a0eb38 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -1,4 +1,4 @@ -import { Context, getUser } from '@/server/context' +import { Context, getUser, getClientTimezoneOffset } from '@/server/context' import { backendLogger as logger } from '@/server/logger' import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx, Int } from 'type-graphql' import { @@ -87,7 +87,9 @@ export class AdminResolver { async searchUsers( @Args() { searchText, currentPage = 1, pageSize = 25, filters }: SearchUsersArgs, + @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const userRepository = getCustomRepository(UserRepository) const userFields = [ 'id', @@ -115,7 +117,10 @@ export class AdminResolver { } } - const creations = await getUserCreations(users.map((u) => u.id)) + const creations = await getUserCreations( + users.map((u) => u.id), + clientTimezoneOffset, + ) const adminUsers = await Promise.all( users.map(async (user) => { @@ -238,6 +243,7 @@ export class AdminResolver { logger.info( `adminCreateContribution(email=${email}, amount=${amount}, memo=${memo}, creationDate=${creationDate})`, ) + const clientTimezoneOffset = getClientTimezoneOffset(context) if (!isValidDateString(creationDate)) { logger.error(`invalid Date for creationDate=${creationDate}`) throw new Error(`invalid Date for creationDate=${creationDate}`) @@ -267,11 +273,11 @@ export class AdminResolver { const event = new Event() const moderator = getUser(context) logger.trace('moderator: ', moderator.id) - const creations = await getUserCreation(emailContact.userId) + const creations = await getUserCreation(emailContact.userId, clientTimezoneOffset) logger.trace('creations:', creations) const creationDateObj = new Date(creationDate) logger.trace('creationDateObj:', creationDateObj) - validateContribution(creations, amount, creationDateObj) + validateContribution(creations, amount, creationDateObj, clientTimezoneOffset) const contribution = DbContribution.create() contribution.userId = emailContact.userId contribution.amount = amount @@ -294,7 +300,7 @@ export class AdminResolver { event.setEventAdminContributionCreate(eventAdminCreateContribution), ) - return getUserCreation(emailContact.userId) + return getUserCreation(emailContact.userId, clientTimezoneOffset) } @Authorized([RIGHTS.ADMIN_CREATE_CONTRIBUTIONS]) @@ -330,6 +336,7 @@ export class AdminResolver { @Args() { id, email, amount, memo, creationDate }: AdminUpdateContributionArgs, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const emailContact = await UserContact.findOne({ where: { email }, withDeleted: true, @@ -370,7 +377,7 @@ export class AdminResolver { } const creationDateObj = new Date(creationDate) - let creations = await getUserCreation(user.id) + let creations = await getUserCreation(user.id, clientTimezoneOffset) if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) { creations = updateCreations(creations, contributionToUpdate) @@ -380,7 +387,7 @@ export class AdminResolver { } // all possible cases not to be true are thrown in this function - validateContribution(creations, amount, creationDateObj) + validateContribution(creations, amount, creationDateObj, clientTimezoneOffset) contributionToUpdate.amount = amount contributionToUpdate.memo = memo contributionToUpdate.contributionDate = new Date(creationDate) @@ -394,7 +401,7 @@ export class AdminResolver { result.memo = contributionToUpdate.memo result.date = contributionToUpdate.contributionDate - result.creation = await getUserCreation(user.id) + result.creation = await getUserCreation(user.id, clientTimezoneOffset) const event = new Event() const eventAdminContributionUpdate = new EventAdminContributionUpdate() @@ -410,7 +417,8 @@ export class AdminResolver { @Authorized([RIGHTS.LIST_UNCONFIRMED_CONTRIBUTIONS]) @Query(() => [UnconfirmedContribution]) - async listUnconfirmedContributions(): Promise { + async listUnconfirmedContributions(@Ctx() context: Context): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const contributions = await getConnection() .createQueryBuilder() .select('c') @@ -424,7 +432,7 @@ export class AdminResolver { } const userIds = contributions.map((p) => p.userId) - const userCreations = await getUserCreations(userIds) + const userCreations = await getUserCreations(userIds, clientTimezoneOffset) const users = await dbUser.find({ where: { id: In(userIds) }, withDeleted: true, @@ -498,6 +506,7 @@ export class AdminResolver { @Arg('id', () => Int) id: number, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const contribution = await DbContribution.findOne(id) if (!contribution) { logger.error(`Contribution not found for given id: ${id}`) @@ -516,8 +525,13 @@ export class AdminResolver { logger.error('This user was deleted. Cannot confirm a contribution.') throw new Error('This user was deleted. Cannot confirm a contribution.') } - const creations = await getUserCreation(contribution.userId, false) - validateContribution(creations, contribution.amount, contribution.contributionDate) + const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false) + validateContribution( + creations, + contribution.amount, + contribution.contributionDate, + clientTimezoneOffset, + ) const receivedCallDate = new Date() diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index a061304b7..ef9f83f61 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -1,5 +1,5 @@ import { RIGHTS } from '@/auth/RIGHTS' -import { Context, getUser } from '@/server/context' +import { Context, getUser, getClientTimezoneOffset } from '@/server/context' import { backendLogger as logger } from '@/server/logger' import { Contribution as dbContribution } from '@entity/Contribution' import { Arg, Args, Authorized, Ctx, Int, Mutation, Query, Resolver } from 'type-graphql' @@ -31,6 +31,7 @@ export class ContributionResolver { @Args() { amount, memo, creationDate }: ContributionArgs, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) if (memo.length > MEMO_MAX_CHARS) { logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) @@ -44,10 +45,10 @@ export class ContributionResolver { const event = new Event() const user = getUser(context) - const creations = await getUserCreation(user.id) + const creations = await getUserCreation(user.id, clientTimezoneOffset) logger.trace('creations', creations) const creationDateObj = new Date(creationDate) - validateContribution(creations, amount, creationDateObj) + validateContribution(creations, amount, creationDateObj, clientTimezoneOffset) const contribution = dbContribution.create() contribution.userId = user.id @@ -171,6 +172,7 @@ export class ContributionResolver { @Args() { amount, memo, creationDate }: ContributionArgs, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) if (memo.length > MEMO_MAX_CHARS) { logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) @@ -206,7 +208,7 @@ export class ContributionResolver { ) } const creationDateObj = new Date(creationDate) - let creations = await getUserCreation(user.id) + let creations = await getUserCreation(user.id, clientTimezoneOffset) if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) { creations = updateCreations(creations, contributionToUpdate) } else { @@ -215,7 +217,7 @@ export class ContributionResolver { } // all possible cases not to be true are thrown in this function - validateContribution(creations, amount, creationDateObj) + validateContribution(creations, amount, creationDateObj, clientTimezoneOffset) const contributionMessage = ContributionMessage.create() contributionMessage.contributionId = contributionId diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts index 601d73027..104ba5fe9 100644 --- a/backend/src/graphql/resolver/util/creations.test.ts +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -159,7 +159,7 @@ describe('util/creation', () => { describe('call getUserCreation now', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id)).resolves.toEqual([ + await expect(getUserCreation(user.id, 0)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), @@ -188,7 +188,7 @@ describe('util/creation', () => { describe('call getUserCreation with UTC', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id)).resolves.toEqual([ + await expect(getUserCreation(user.id, 0)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), @@ -198,7 +198,7 @@ describe('util/creation', () => { describe('call getUserCreation with JST (GMT+0900)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ + await expect(getUserCreation(user.id, -540, true)).resolves.toEqual([ expect.decimalEqual(340), expect.decimalEqual(350), expect.decimalEqual(1000), @@ -208,7 +208,7 @@ describe('util/creation', () => { describe('call getUserCreation with PST (GMT-0800)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, 480)).resolves.toEqual([ + await expect(getUserCreation(user.id, 480, true)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), @@ -234,7 +234,7 @@ describe('util/creation', () => { describe('call getUserCreation with UTC', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ + await expect(getUserCreation(user.id, -540, true)).resolves.toEqual([ expect.decimalEqual(340), expect.decimalEqual(350), expect.decimalEqual(1000), @@ -244,7 +244,7 @@ describe('util/creation', () => { describe('call getUserCreation with JST (GMT+0900)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, -540)).resolves.toEqual([ + await expect(getUserCreation(user.id, -540, true)).resolves.toEqual([ expect.decimalEqual(340), expect.decimalEqual(350), expect.decimalEqual(1000), @@ -254,7 +254,7 @@ describe('util/creation', () => { describe('call getUserCreation with PST (GMT-0800)', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, true, 450)).resolves.toEqual([ + await expect(getUserCreation(user.id, 450, true)).resolves.toEqual([ expect.decimalEqual(550), expect.decimalEqual(340), expect.decimalEqual(350), diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index decf4de27..efd0ea3b0 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -38,8 +38,8 @@ export const validateContribution = ( export const getUserCreations = async ( ids: number[], - includePending = true, timezoneOffset = 0, + includePending = true, ): Promise => { logger.trace('getUserCreations:', ids, includePending) const months = getCreationMonths(timezoneOffset) @@ -91,11 +91,11 @@ export const getUserCreations = async ( export const getUserCreation = async ( id: number, - includePending = true, timezoneOffset = 0, + includePending = true, ): Promise => { logger.trace('getUserCreation', id, includePending, timezoneOffset) - const creations = await getUserCreations([id], includePending, timezoneOffset) + const creations = await getUserCreations([id], timezoneOffset, includePending) logger.trace('getUserCreation creations=', creations) return creations[0] ? creations[0].creations : FULL_CREATION_AVAILABLE } From ef79580387a35c1558b4a2768f8729acdd63c651 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 14:20:33 +0100 Subject: [PATCH 23/29] required argument client time zone offset for utils in creation --- backend/src/graphql/resolver/AdminResolver.ts | 2 +- backend/src/graphql/resolver/ContributionResolver.ts | 2 +- backend/src/graphql/resolver/TransactionLinkResolver.ts | 7 ++++--- backend/src/graphql/resolver/UserResolver.ts | 8 +++++--- backend/src/graphql/resolver/util/creations.ts | 8 ++++---- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index 1e1a0eb38..80c69a864 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -380,7 +380,7 @@ export class AdminResolver { let creations = await getUserCreation(user.id, clientTimezoneOffset) if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) { - creations = updateCreations(creations, contributionToUpdate) + creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset) } else { logger.error('Currently the month of the contribution cannot change.') throw new Error('Currently the month of the contribution cannot change.') diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index ef9f83f61..15bdbfc2e 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -210,7 +210,7 @@ export class ContributionResolver { const creationDateObj = new Date(creationDate) let creations = await getUserCreation(user.id, clientTimezoneOffset) if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) { - creations = updateCreations(creations, contributionToUpdate) + creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset) } else { logger.error('Currently the month of the contribution cannot change.') throw new Error('Currently the month of the contribution cannot change.') diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 74c531c54..a5c4a5f01 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -1,5 +1,5 @@ import { backendLogger as logger } from '@/server/logger' -import { Context, getUser } from '@/server/context' +import { Context, getUser, getClientTimezoneOffset } from '@/server/context' import { getConnection } from '@dbTools/typeorm' import { Resolver, @@ -169,6 +169,7 @@ export class TransactionLinkResolver { @Arg('code', () => String) code: string, @Ctx() context: Context, ): Promise { + const clientTimezoneOffset = getClientTimezoneOffset(context) const user = getUser(context) const now = new Date() @@ -258,9 +259,9 @@ export class TransactionLinkResolver { } } - const creations = await getUserCreation(user.id) + const creations = await getUserCreation(user.id, clientTimezoneOffset) logger.info('open creations', creations) - validateContribution(creations, contributionLink.amount, now) + validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset) const contribution = new DbContribution() contribution.userId = user.id contribution.createdAt = now diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 2287ede98..43735c0d6 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -1,6 +1,6 @@ import fs from 'fs' import { backendLogger as logger } from '@/server/logger' -import { Context, getUser } from '@/server/context' +import { Context, getUser, getClientTimezoneOffset } from '@/server/context' import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware, Mutation } from 'type-graphql' import { getConnection, getCustomRepository, IsNull, Not } from '@dbTools/typeorm' import CONFIG from '@/config' @@ -305,8 +305,9 @@ export class UserResolver { async verifyLogin(@Ctx() context: Context): Promise { logger.info('verifyLogin...') // TODO refactor and do not have duplicate code with login(see below) + const clientTimezoneOffset = getClientTimezoneOffset(context) const userEntity = getUser(context) - const user = new User(userEntity, await getUserCreation(userEntity.id)) + const user = new User(userEntity, await getUserCreation(userEntity.id, clientTimezoneOffset)) // user.pubkey = userEntity.pubKey.toString('hex') // Elopage Status & Stored PublisherId user.hasElopage = await this.hasElopage(context) @@ -323,6 +324,7 @@ export class UserResolver { @Ctx() context: Context, ): Promise { logger.info(`login with ${email}, ***, ${publisherId} ...`) + const clientTimezoneOffset = getClientTimezoneOffset(context) email = email.trim().toLowerCase() const dbUser = await findUserByEmail(email) if (dbUser.deletedAt) { @@ -353,7 +355,7 @@ export class UserResolver { logger.addContext('user', dbUser.id) logger.debug('validation of login credentials successful...') - const user = new User(dbUser, await getUserCreation(dbUser.id)) + const user = new User(dbUser, await getUserCreation(dbUser.id, clientTimezoneOffset)) logger.debug(`user= ${JSON.stringify(user, null, 2)}`) // Elopage Status & Stored PublisherId diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index efd0ea3b0..303337e2b 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -13,7 +13,7 @@ export const validateContribution = ( creations: Decimal[], amount: Decimal, creationDate: Date, - timezoneOffset = 0, + timezoneOffset: number, ): void => { logger.trace('isContributionValid: ', creations, amount, creationDate) const index = getCreationIndex(creationDate.getMonth(), timezoneOffset) @@ -38,7 +38,7 @@ export const validateContribution = ( export const getUserCreations = async ( ids: number[], - timezoneOffset = 0, + timezoneOffset: number, includePending = true, ): Promise => { logger.trace('getUserCreations:', ids, includePending) @@ -91,7 +91,7 @@ export const getUserCreations = async ( export const getUserCreation = async ( id: number, - timezoneOffset = 0, + timezoneOffset: number, includePending = true, ): Promise => { logger.trace('getUserCreation', id, includePending, timezoneOffset) @@ -138,7 +138,7 @@ export const isStartEndDateValid = ( export const updateCreations = ( creations: Decimal[], contribution: Contribution, - timezoneOffset = 0, + timezoneOffset: number, ): Decimal[] => { const index = getCreationIndex(contribution.contributionDate.getMonth(), timezoneOffset) From e4e308cdb4b1c666be3a3b534f4bc0666706842c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 14:34:20 +0100 Subject: [PATCH 24/29] fix seeds --- backend/src/graphql/resolver/util/creations.test.ts | 2 +- backend/src/seeds/index.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts index 104ba5fe9..7ab71c367 100644 --- a/backend/src/graphql/resolver/util/creations.test.ts +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -21,7 +21,7 @@ beforeAll(async () => { }) afterAll(async () => { - // await cleanDB() + await cleanDB() await con.close() }) diff --git a/backend/src/seeds/index.ts b/backend/src/seeds/index.ts index c5a55cb84..3675d381d 100644 --- a/backend/src/seeds/index.ts +++ b/backend/src/seeds/index.ts @@ -29,6 +29,7 @@ const context = { // eslint-disable-next-line @typescript-eslint/no-empty-function forEach: (): void => {}, }, + clientTimezoneOffset: 0, } export const cleanDB = async () => { From 103790cccf610e244cb54d29fb9356c30e453356 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 16 Nov 2022 14:24:17 +0100 Subject: [PATCH 25/29] use jest.fn() for void function, correct timezone offset --- backend/src/graphql/resolver/util/creations.test.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/backend/src/graphql/resolver/util/creations.test.ts b/backend/src/graphql/resolver/util/creations.test.ts index 7ab71c367..8d747e989 100644 --- a/backend/src/graphql/resolver/util/creations.test.ts +++ b/backend/src/graphql/resolver/util/creations.test.ts @@ -171,8 +171,7 @@ describe('util/creation', () => { beforeAll(() => { jest.useFakeTimers() - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - setTimeout(() => {}, targetDate.getTime() - now.getTime()) + setTimeout(jest.fn(), targetDate.getTime() - now.getTime()) jest.runAllTimers() }) @@ -221,8 +220,7 @@ describe('util/creation', () => { nextMonthTargetDate.setTime(targetDate.getTime() + 2 * 60 * 60 * 1000) beforeAll(() => { - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - setTimeout(() => {}, 2 * 60 * 60 * 1000) + setTimeout(jest.fn(), 2 * 60 * 60 * 1000) jest.runAllTimers() }) @@ -234,7 +232,7 @@ describe('util/creation', () => { describe('call getUserCreation with UTC', () => { it('returns the expected open contributions', async () => { - await expect(getUserCreation(user.id, -540, true)).resolves.toEqual([ + await expect(getUserCreation(user.id, 0, true)).resolves.toEqual([ expect.decimalEqual(340), expect.decimalEqual(350), expect.decimalEqual(1000), From eac290919dfeba2426fc444cbee6ce3725321d73 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 16 Nov 2022 14:38:38 +0100 Subject: [PATCH 26/29] avoid reverse, restore removed clear mocks --- .../graphql/resolver/AdminResolver.test.ts | 22 +++++++++++++++++++ .../src/graphql/resolver/util/creations.ts | 6 ++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index c253d7bb0..5a4a60239 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -145,6 +145,7 @@ describe('AdminResolver', () => { describe('user to get a new role does not exist', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: setUserRole, variables: { userId: admin.id + 1, isAdmin: true } }), ).resolves.toEqual( @@ -201,6 +202,7 @@ describe('AdminResolver', () => { describe('change role with error', () => { describe('is own role', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: setUserRole, variables: { userId: admin.id, isAdmin: false } }), ).resolves.toEqual( @@ -217,6 +219,7 @@ describe('AdminResolver', () => { describe('user has already role to be set', () => { describe('to admin', () => { it('throws an error', async () => { + jest.clearAllMocks() await mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: true }, @@ -237,6 +240,7 @@ describe('AdminResolver', () => { describe('to usual user', () => { it('throws an error', async () => { + jest.clearAllMocks() await mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: false }, @@ -313,6 +317,7 @@ describe('AdminResolver', () => { describe('user to be deleted does not exist', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: deleteUser, variables: { userId: admin.id + 1 } }), ).resolves.toEqual( @@ -329,6 +334,7 @@ describe('AdminResolver', () => { describe('delete self', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: deleteUser, variables: { userId: admin.id } }), ).resolves.toEqual( @@ -362,6 +368,7 @@ describe('AdminResolver', () => { describe('delete deleted user', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: deleteUser, variables: { userId: user.id } }), ).resolves.toEqual( @@ -433,6 +440,7 @@ describe('AdminResolver', () => { describe('user to be undelete does not exist', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: unDeleteUser, variables: { userId: admin.id + 1 } }), ).resolves.toEqual( @@ -453,6 +461,7 @@ describe('AdminResolver', () => { }) it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: unDeleteUser, variables: { userId: user.id } }), ).resolves.toEqual( @@ -948,6 +957,7 @@ describe('AdminResolver', () => { describe('user to create for does not exist', () => { it('throws an error', async () => { + jest.clearAllMocks() variables.creationDate = contributionDateFormatter( new Date(now.getFullYear(), now.getMonth() - 1, 1), ) @@ -977,6 +987,7 @@ describe('AdminResolver', () => { }) it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -1005,6 +1016,7 @@ describe('AdminResolver', () => { }) it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -1032,6 +1044,7 @@ describe('AdminResolver', () => { describe('date of creation is not a date string', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( @@ -1098,6 +1111,7 @@ describe('AdminResolver', () => { describe('amount of creation is too high', () => { it('throws an error', async () => { + jest.clearAllMocks() variables.creationDate = contributionDateFormatter(now) await expect( mutate({ mutation: adminCreateContribution, variables }), @@ -1223,6 +1237,7 @@ describe('AdminResolver', () => { describe('user for creation to update does not exist', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1252,6 +1267,7 @@ describe('AdminResolver', () => { describe('user for creation to update is deleted', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1277,6 +1293,7 @@ describe('AdminResolver', () => { describe('creation does not exist', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1302,6 +1319,7 @@ describe('AdminResolver', () => { describe('user email does not match creation user', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1336,6 +1354,7 @@ describe('AdminResolver', () => { describe('creation update is not valid', () => { // as this test has not clearly defined that date, it is a false positive it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminUpdateContribution, @@ -1511,6 +1530,7 @@ describe('AdminResolver', () => { describe('adminDeleteContribution', () => { describe('creation id does not exist', () => { + jest.clearAllMocks() it('throws an error', async () => { await expect( mutate({ @@ -1548,6 +1568,7 @@ describe('AdminResolver', () => { }) it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminDeleteContribution, @@ -1593,6 +1614,7 @@ describe('AdminResolver', () => { describe('confirmContribution', () => { describe('creation does not exits', () => { it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: confirmContribution, diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 303337e2b..eb4b6394d 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -104,10 +104,10 @@ const getCreationMonths = (timezoneOffset: number): number[] => { const clientNow = new Date() clientNow.setTime(clientNow.getTime() - timezoneOffset * 60 * 1000) return [ - clientNow.getMonth() + 1, - new Date(clientNow.getFullYear(), clientNow.getMonth() - 1, 1).getMonth() + 1, new Date(clientNow.getFullYear(), clientNow.getMonth() - 2, 1).getMonth() + 1, - ].reverse() + new Date(clientNow.getFullYear(), clientNow.getMonth() - 1, 1).getMonth() + 1, + clientNow.getMonth() + 1, + ] } const getCreationIndex = (month: number, timezoneOffset: number): number => { From 36185a286a5b7f4c0162d177de4bd848fccb7600 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 16 Nov 2022 14:42:45 +0100 Subject: [PATCH 27/29] move clear all mocks to correct position --- backend/src/graphql/resolver/AdminResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/AdminResolver.test.ts b/backend/src/graphql/resolver/AdminResolver.test.ts index 5a4a60239..503bab472 100644 --- a/backend/src/graphql/resolver/AdminResolver.test.ts +++ b/backend/src/graphql/resolver/AdminResolver.test.ts @@ -1530,8 +1530,8 @@ describe('AdminResolver', () => { describe('adminDeleteContribution', () => { describe('creation id does not exist', () => { - jest.clearAllMocks() it('throws an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: adminDeleteContribution, From 038d7a6a5fa5b0ba480740ab0cb4b480bba31adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 17 Nov 2022 13:49:18 +0100 Subject: [PATCH 28/29] Refine and cleanup documentation --- backend/src/emails/README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/backend/src/emails/README.md b/backend/src/emails/README.md index 0c0fb41d9..9ab1d1124 100644 --- a/backend/src/emails/README.md +++ b/backend/src/emails/README.md @@ -5,14 +5,6 @@ You'll find the GitHub repository of the `email-templates` package and the `pug` - [email-templates](https://github.com/forwardemail/email-templates) - [pug](https://www.npmjs.com/package/pug) -## Installation - -To install both packages with yarn run: - -```bash -yarn add email-templates pug -``` - ## `pug` Documentation The full `pug` documentation you'll find here: From c5469df9978858a8bb30e482bbb3497de225d201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 17 Nov 2022 13:50:01 +0100 Subject: [PATCH 29/29] Give 'html' tag the language of the email receiver --- backend/src/emails/accountMultiRegistration/html.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/emails/accountMultiRegistration/html.pug b/backend/src/emails/accountMultiRegistration/html.pug index e285c940b..4c8a94d28 100644 --- a/backend/src/emails/accountMultiRegistration/html.pug +++ b/backend/src/emails/accountMultiRegistration/html.pug @@ -1,5 +1,5 @@ doctype html -html(lang="en") +html(lang=locale) head title= t('emails.accountMultiRegistration.subject') body