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()}` +}