From 5e2d6c3bc90534fef720ac0e1719ebee4a130d3f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 10 Nov 2022 19:01:16 +0100 Subject: [PATCH 01/15] 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/15] 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/15] 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/15] 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 cff2396bd12bd4798a46f3da46648c5baa199581 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Nov 2022 13:00:59 +0100 Subject: [PATCH 05/15] 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 06/15] 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 07/15] 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 08/15] 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 09/15] 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 10/15] 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 11/15] 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 12/15] 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 13/15] 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 14/15] 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 15/15] 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,