diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 085fb74a1..670eaac2d 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -10,12 +10,15 @@ import { createContribution, updateContribution, deleteContribution, + denyContribution, confirmContribution, adminCreateContribution, adminCreateContributions, adminUpdateContribution, adminDeleteContribution, login, + logout, + adminCreateContributionMessage, } from '@/seeds/graphql/mutations' import { listAllContributions, @@ -42,6 +45,9 @@ import { User } from '@entity/User' import { EventProtocolType } from '@/event/EventProtocolType' import { logger, i18n as localization } from '@test/testSetup' import { UserInputError } from 'apollo-server-express' +import { raeuberHotzenplotz } from '@/seeds/users/raeuber-hotzenplotz' +import { UnconfirmedContribution } from '../model/UnconfirmedContribution' +import { ContributionListResult } from '../model/Contribution' import { ContributionStatus } from '../enum/ContributionStatus' // mock account activation email to avoid console spam @@ -64,7 +70,12 @@ let mutate: any, query: any, con: any let testEnv: any let creation: Contribution | void let admin: User -let result: any +let pendingContribution: any +let inProgressContribution: any +let contributionToConfirm: any +let contributionToDeny: any +let contributionToDelete: any +let bibiCreatedContribution: Contribution beforeAll(async () => { testEnv = await testEnvironment(logger, localization) @@ -82,34 +93,100 @@ afterAll(async () => { describe('ContributionResolver', () => { let bibi: any + beforeAll(async () => { + bibi = await userFactory(testEnv, bibiBloxberg) + admin = await userFactory(testEnv, peterLustig) + await userFactory(testEnv, raeuberHotzenplotz) + const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + bibiCreatedContribution = await creationFactory(testEnv, bibisCreation!) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + pendingContribution = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test PENDING contribution', + creationDate: new Date().toString(), + }, + }) + inProgressContribution = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test IN_PROGRESS contribution', + creationDate: new Date().toString(), + }, + }) + contributionToConfirm = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test contribution to confirm', + creationDate: new Date().toString(), + }, + }) + contributionToDeny = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test contribution to deny', + creationDate: new Date().toString(), + }, + }) + contributionToDelete = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test contribution to delete', + creationDate: new Date().toString(), + }, + }) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: adminCreateContributionMessage, + variables: { + contributionId: inProgressContribution.data.createContribution.id, + message: 'Test message to IN_PROGRESS contribution', + }, + }) + await mutate({ + mutation: logout, + }) + resetToken() + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + describe('createContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { - await expect( - mutate({ - mutation: createContribution, - variables: { amount: 100.0, memo: 'Test Contribution', creationDate: 'not-valid' }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: createContribution, + variables: { amount: 100.0, memo: 'Test Contribution', creationDate: 'not-valid' }, + }) + + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) describe('authenticated with valid user', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - - bibi = await mutate({ + await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -117,20 +194,16 @@ describe('ContributionResolver', () => { it('throws error when memo length smaller than 5 chars', async () => { jest.clearAllMocks() const date = new Date() - await expect( - mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test', - creationDate: date.toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Memo text is too short')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test', + creationDate: date.toString(), + }, + }) + + expect(errorObjects).toEqual([new GraphQLError('Memo text is too short')]) }) it('logs the error found', () => { @@ -140,20 +213,15 @@ describe('ContributionResolver', () => { it('throws error when memo length greater than 255 chars', async () => { jest.clearAllMocks() const date = new Date() - await expect( - mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test', - creationDate: date.toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Memo text is too long')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test', + creationDate: date.toString(), + }, + }) + expect(errorObjects).toEqual([new GraphQLError('Memo text is too long')]) }) it('logs the error found', () => { @@ -162,22 +230,17 @@ describe('ContributionResolver', () => { it('throws error when creationDate not-valid', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: 'not-valid', - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [ - new GraphQLError('No information for available creations for the given date'), - ], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test env contribution', + creationDate: 'not-valid', + }, + }) + expect(errorObjects).toEqual([ + new GraphQLError('No information for available creations for the given date'), + ]) }) it('logs the error found', () => { @@ -190,22 +253,17 @@ describe('ContributionResolver', () => { it('throws error when creationDate 3 month behind', async () => { jest.clearAllMocks() const date = new Date() - await expect( - mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: date.setMonth(date.getMonth() - 3).toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [ - new GraphQLError('No information for available creations for the given date'), - ], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test env contribution', + creationDate: date.setMonth(date.getMonth() - 3).toString(), + }, + }) + expect(errorObjects).toEqual([ + new GraphQLError('No information for available creations for the given date'), + ]) }) it('logs the error found', () => { @@ -217,31 +275,12 @@ describe('ContributionResolver', () => { }) describe('valid input', () => { - let contribution: any - - beforeAll(async () => { - contribution = await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) - }) - it('creates contribution', async () => { - expect(contribution).toEqual( - expect.objectContaining({ - data: { - createContribution: { - id: expect.any(Number), - amount: '100', - memo: 'Test env contribution', - }, - }, - }), - ) + expect(pendingContribution.data.createContribution).toMatchObject({ + id: expect.any(Number), + amount: '100', + memo: 'Test PENDING contribution', + }) }) it('stores the CONTRIBUTION_CREATE event in the database', async () => { @@ -249,124 +288,8 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_CREATE, amount: expect.decimalEqual(100), - contributionId: contribution.data.createContribution.id, - userId: bibi.data.login.id, - }), - ) - }) - }) - }) - }) - - describe('listContributions', () => { - describe('unauthenticated', () => { - it('returns an error', async () => { - await expect( - query({ - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - filterConfirmed: false, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) - }) - }) - - describe('authenticated', () => { - beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - await userFactory(testEnv, peterLustig) - const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await creationFactory(testEnv, bibisCreation!) - await mutate({ - mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, - }) - await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) - }) - - afterAll(async () => { - await cleanDB() - resetToken() - }) - - describe('filter confirmed is false', () => { - it('returns creations', async () => { - await expect( - query({ - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - filterConfirmed: false, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listContributions: { - contributionCount: 2, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - }) - - describe('filter confirmed is true', () => { - it('returns only unconfirmed creations', async () => { - await expect( - query({ - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - filterConfirmed: true, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listContributions: { - contributionCount: 1, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, + contributionId: pendingContribution.data.createContribution.id, + userId: bibi.id, }), ) }) @@ -377,44 +300,28 @@ describe('ContributionResolver', () => { describe('updateContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: 1, - amount: 100.0, - memo: 'Test Contribution', - creationDate: 'not-valid', - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: updateContribution, + variables: { + contributionId: 1, + amount: 100.0, + memo: 'Test Contribution', + creationDate: 'not-valid', + }, + }) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) describe('authenticated', () => { beforeAll(async () => { - await userFactory(testEnv, peterLustig) - await userFactory(testEnv, bibiBloxberg) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) - result = await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -422,21 +329,16 @@ describe('ContributionResolver', () => { it('throws error', async () => { jest.clearAllMocks() const date = new Date() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: result.data.createContribution.id, - amount: 100.0, - memo: 'Test', - creationDate: date.toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Memo text is too short')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: updateContribution, + variables: { + contributionId: pendingContribution.data.createContribution.id, + amount: 100.0, + memo: 'Test', + creationDate: date.toString(), + }, + }) + expect(errorObjects).toEqual([new GraphQLError('Memo text is too short')]) }) it('logs the error found', () => { @@ -448,21 +350,16 @@ describe('ContributionResolver', () => { it('throws error', async () => { jest.clearAllMocks() const date = new Date() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: result.data.createContribution.id, - amount: 100.0, - memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test', - creationDate: date.toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Memo text is too long')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: updateContribution, + variables: { + contributionId: pendingContribution.data.createContribution.id, + amount: 100.0, + memo: 'Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test', + creationDate: date.toString(), + }, + }) + expect(errorObjects).toEqual([new GraphQLError('Memo text is too long')]) }) it('logs the error found', () => { @@ -505,21 +402,18 @@ describe('ContributionResolver', () => { it('throws an error', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: result.data.createContribution.id, - amount: 10.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Can not update contribution of another user')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: updateContribution, + variables: { + contributionId: pendingContribution.data.createContribution.id, + amount: 10.0, + memo: 'Test env contribution', + creationDate: new Date().toString(), + }, + }) + expect(errorObjects).toEqual([ + new GraphQLError('Can not update contribution of another user'), + ]) }) it('logs the error found', () => { @@ -532,24 +426,28 @@ describe('ContributionResolver', () => { }) describe('admin tries to update a user contribution', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + }) + it('throws an error', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: adminUpdateContribution, - variables: { - id: result.data.createContribution.id, - email: 'bibi@bloxberg.de', - amount: 10.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('An admin is not allowed to update an user contribution')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: adminUpdateContribution, + variables: { + id: pendingContribution.data.createContribution.id, + email: 'bibi@bloxberg.de', + amount: 10.0, + memo: 'Test env contribution', + creationDate: new Date().toString(), + }, + }) + expect(errorObjects).toEqual([ + new GraphQLError('An admin is not allowed to update an user contribution'), + ]) }) it('logs the error found', () => { @@ -557,53 +455,53 @@ describe('ContributionResolver', () => { 'An admin is not allowed to update an user contribution', ) }) - }) - describe('contribution has wrong status', () => { - beforeAll(async () => { - const contribution = await Contribution.findOneOrFail({ - id: result.data.createContribution.id, + describe('contribution has wrong status', () => { + beforeAll(async () => { + const contribution = await Contribution.findOneOrFail({ + id: pendingContribution.data.createContribution.id, + }) + contribution.contributionStatus = ContributionStatus.DELETED + contribution.save() + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) }) - contribution.contributionStatus = ContributionStatus.DELETED - contribution.save() - await mutate({ - mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + + afterAll(async () => { + const contribution = await Contribution.findOneOrFail({ + id: pendingContribution.data.createContribution.id, + }) + contribution.contributionStatus = ContributionStatus.PENDING + contribution.save() }) - }) - afterAll(async () => { - const contribution = await Contribution.findOneOrFail({ - id: result.data.createContribution.id, + it('throws an error', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: updateContribution, + variables: { + contributionId: pendingContribution.data.createContribution.id, + amount: 10.0, + memo: 'Test env contribution', + creationDate: new Date().toString(), + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution can not be updated due to status')], + }), + ) }) - contribution.contributionStatus = ContributionStatus.PENDING - contribution.save() - }) - it('throws an error', async () => { - jest.clearAllMocks() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: result.data.createContribution.id, - amount: 10.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution can not be updated due to status')], - }), - ) - }) - - it('logs the error found', () => { - expect(logger.error).toBeCalledWith( - 'Contribution can not be updated due to status', - ContributionStatus.DELETED, - ) + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + 'Contribution can not be updated due to status', + ContributionStatus.DELETED, + ) + }) }) }) @@ -617,30 +515,25 @@ describe('ContributionResolver', () => { it('throws an error', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: result.data.createContribution.id, - amount: 1019.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [ - new GraphQLError( - 'The amount (1019 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', - ), - ], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: updateContribution, + variables: { + contributionId: pendingContribution.data.createContribution.id, + amount: 1019.0, + memo: 'Test env contribution', + creationDate: new Date().toString(), + }, + }) + expect(errorObjects).toEqual([ + new GraphQLError( + 'The amount (1019 GDD) to be created exceeds the amount (600 GDD) still available for this month.', + ), + ]) }) it('logs the error found', () => { expect(logger.error).toBeCalledWith( - 'The amount (1019 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (1019 GDD) to be created exceeds the amount (600 GDD) still available for this month.', ) }) }) @@ -649,21 +542,18 @@ describe('ContributionResolver', () => { it('throws an error', async () => { jest.clearAllMocks() const date = new Date() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: result.data.createContribution.id, - amount: 10.0, - memo: 'Test env contribution', - creationDate: date.setMonth(date.getMonth() - 3).toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Month of contribution can not be changed')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: updateContribution, + variables: { + contributionId: pendingContribution.data.createContribution.id, + amount: 10.0, + memo: 'Test env contribution', + creationDate: date.setMonth(date.getMonth() - 3).toString(), + }, + }) + expect(errorObjects).toEqual([ + new GraphQLError('Month of contribution can not be changed'), + ]) }) it('logs the error found', () => { @@ -673,31 +563,26 @@ describe('ContributionResolver', () => { describe('valid input', () => { it('updates contribution', async () => { - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: result.data.createContribution.id, - amount: 10.0, - memo: 'Test contribution', - creationDate: new Date().toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - updateContribution: { - id: result.data.createContribution.id, - amount: '10', - memo: 'Test contribution', - }, - }, - }), - ) + const { + data: { updateContribution: contribution }, + }: { data: { updateContribution: UnconfirmedContribution } } = await mutate({ + mutation: updateContribution, + variables: { + contributionId: pendingContribution.data.createContribution.id, + amount: 10.0, + memo: 'Test PENDING contribution update', + creationDate: new Date().toString(), + }, + }) + expect(contribution).toMatchObject({ + id: pendingContribution.data.createContribution.id, + amount: '10', + memo: 'Test PENDING contribution update', + }) }) it('stores the CONTRIBUTION_UPDATE event in the database', async () => { - bibi = await query({ + await query({ query: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) @@ -706,8 +591,8 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_UPDATE, amount: expect.decimalEqual(10), - contributionId: result.data.createContribution.id, - userId: bibi.data.login.id, + contributionId: pendingContribution.data.createContribution.id, + userId: bibi.id, }), ) }) @@ -715,505 +600,64 @@ describe('ContributionResolver', () => { }) }) - describe('listAllContribution', () => { + describe('denyContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: null, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: 1, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) - describe('authenticated', () => { + describe('authenticated without admin rights', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - await userFactory(testEnv, peterLustig) - const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await creationFactory(testEnv, bibisCreation!) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) - await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }) }) - afterAll(async () => { - await cleanDB() + afterAll(() => { resetToken() }) - it('throws an error with "NOT_VALID" in statusFilter', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: ['NOT_VALID'], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [ - new UserInputError( - 'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[0]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.', - ), - ], - }), - ) - }) - - it('throws an error with a null in statusFilter', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: [null], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [ - new UserInputError( - 'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.', - ), - ], - }), - ) - }) - - it('throws an error with null and "NOT_VALID" in statusFilter', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: [null, 'NOT_VALID'], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [ - new UserInputError( - 'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.', - ), - new UserInputError( - 'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[1]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.', - ), - ], - }), - ) - }) - - it('returns all contributions without statusFilter', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 2, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - - it('returns all contributions for statusFilter = null', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: null, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 2, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - - it('returns all contributions for statusFilter = []', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: [], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 2, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - - it('returns all CONFIRMED contributions', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: ['CONFIRMED'], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 1, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - - it('returns all PENDING contributions', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: ['PENDING'], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 1, - contributionList: expect.arrayContaining([ - expect.not.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - - it('returns all IN_PROGRESS Creation', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: ['IN_PROGRESS'], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 0, - contributionList: expect.not.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - - it('returns all DENIED Creation', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: ['DENIED'], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 0, - contributionList: expect.not.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - - it('returns all DELETED Creation', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: ['DELETED'], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 0, - contributionList: expect.not.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - - it('returns all CONFIRMED and PENDING Creation', async () => { - await expect( - query({ - query: listAllContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - statusFilter: ['CONFIRMED', 'PENDING'], - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listAllContributions: { - contributionCount: 2, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test env contribution', - amount: '100', - }), - ]), - }, - }, - }), - ) - }) - }) - }) - - describe('deleteContribution', () => { - describe('unauthenticated', () => { it('returns an error', async () => { - await expect( - query({ - query: deleteContribution, - variables: { - id: -1, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: 1, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) - describe('authenticated', () => { - let peter: any + describe('authenticated with admin rights', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - peter = await userFactory(testEnv, peterLustig) - await mutate({ mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, - }) - result = await mutate({ - mutation: createContribution, - variables: { - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) afterAll(async () => { - await cleanDB() resetToken() }) describe('wrong contribution id', () => { - it('returns an error', async () => { + it('throws an error', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: -1, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution not found')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: -1, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) }) it('logs the error found', () => { @@ -1221,50 +665,61 @@ describe('ContributionResolver', () => { }) }) - describe('other user sends a deleteContribution', () => { - it('returns an error', async () => { + describe('deny contribution that is already confirmed', () => { + let contribution: any + it('throws an error', async () => { jest.clearAllMocks() + await mutate({ + mutation: login, + variables: { email: 'raeuber@hotzenplotz.de', password: 'Aa12345_' }, + }) + + contribution = await mutate({ + mutation: createContribution, + variables: { + amount: 166.0, + memo: 'Whatever contribution', + creationDate: new Date().toString(), + }, + }) + await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Can not delete contribution of another user')], - }), - ) + + await mutate({ + mutation: confirmContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) }) it('logs the error found', () => { - expect(logger.error).toBeCalledWith( - 'Can not delete contribution of another user', - expect.any(Object), - expect.any(Number), - ) + expect(logger.error).toBeCalledWith('Contribution not found', expect.any(Number)) }) }) - describe('User deletes own contribution', () => { - it('deletes successfully', async () => { - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toBeTruthy() - }) + describe('deny contribution that is already deleted', () => { + let contribution: any - it('stores the CONTRIBUTION_DELETE event in the database', async () => { - const contribution = await mutate({ + it('throws an error', async () => { + jest.clearAllMocks() + await mutate({ + mutation: login, + variables: { email: 'raeuber@hotzenplotz.de', password: 'Aa12345_' }, + }) + + contribution = await mutate({ mutation: createContribution, variables: { amount: 166.0, @@ -1280,12 +735,209 @@ describe('ContributionResolver', () => { }, }) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith(`Contribution not found`, expect.any(Number)) + }) + }) + + describe('deny contribution that is already denied', () => { + let contribution: any + + it('throws an error', async () => { + jest.clearAllMocks() + await mutate({ + mutation: login, + variables: { email: 'raeuber@hotzenplotz.de', password: 'Aa12345_' }, + }) + + contribution = await mutate({ + mutation: createContribution, + variables: { + amount: 166.0, + memo: 'Whatever contribution', + creationDate: new Date().toString(), + }, + }) + + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + + await mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith(`Contribution not found`, expect.any(Number)) + }) + }) + + describe('valid input', () => { + it('deny contribution', async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + const { + data: { denyContribution: isDenied }, + }: { data: { denyContribution: boolean } } = await mutate({ + mutation: denyContribution, + variables: { + id: contributionToDeny.data.createContribution.id, + }, + }) + expect(isDenied).toBe(true) + }) + + it('stores the ADMIN_CONTRIBUTION_DENY event in the database', async () => { + await expect(EventProtocol.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventProtocolType.ADMIN_CONTRIBUTION_DENY, + userId: bibi.id, + xUserId: admin.id, + contributionId: contributionToDeny.data.createContribution.id, + amount: expect.decimalEqual(100), + }), + ) + }) + }) + }) + }) + + describe('deleteContribution', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + query: deleteContribution, + variables: { + id: -1, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) + }) + }) + + describe('authenticated', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + resetToken() + }) + + describe('wrong contribution id', () => { + it('returns an error', async () => { + jest.clearAllMocks() + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: deleteContribution, + variables: { + id: -1, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith('Contribution not found', expect.any(Number)) + }) + }) + + describe('other user sends a deleteContribution', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + }) + + afterAll(() => { + resetToken() + }) + + it('returns an error', async () => { + jest.clearAllMocks() + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: deleteContribution, + variables: { + id: contributionToDelete.data.createContribution.id, + }, + }) + expect(errorObjects).toEqual([ + new GraphQLError('Can not delete contribution of another user'), + ]) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + 'Can not delete contribution of another user', + expect.any(Contribution), + expect.any(Number), + ) + }) + }) + + describe('User deletes own contribution', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(() => { + resetToken() + }) + + it('deletes successfully', async () => { + const { + data: { deleteContribution: isDenied }, + }: { data: { deleteContribution: boolean } } = await mutate({ + mutation: deleteContribution, + variables: { + id: contributionToDelete.data.createContribution.id, + }, + }) + expect(isDenied).toBe(true) + }) + + it('stores the CONTRIBUTION_DELETE event in the database', async () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_DELETE, - contributionId: contribution.data.createContribution.id, - amount: expect.decimalEqual(166), - userId: peter.id, + contributionId: contributionToDelete.data.createContribution.id, + amount: expect.decimalEqual(100), + userId: bibi.id, }), ) }) @@ -1301,25 +953,22 @@ describe('ContributionResolver', () => { await mutate({ mutation: confirmContribution, variables: { - id: result.data.createContribution.id, + id: contributionToConfirm.data.createContribution.id, }, }) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('A confirmed contribution can not be deleted')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: deleteContribution, + variables: { + id: contributionToConfirm.data.createContribution.id, + }, + }) + expect(errorObjects).toEqual([ + new GraphQLError('A confirmed contribution can not be deleted'), + ]) }) it('logs the error found', () => { @@ -1332,6 +981,657 @@ describe('ContributionResolver', () => { }) }) + describe('listContributions', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + const { errors: errorObjects }: { errors: [GraphQLError] } = await query({ + query: listContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + filterConfirmed: false, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) + }) + }) + + describe('authenticated', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + resetToken() + }) + + describe('filter confirmed is false', () => { + it('returns creations', async () => { + const { + data: { listContributions: contributionListResult }, + }: { data: { listContributions: ContributionListResult } } = await query({ + query: listContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + filterConfirmed: false, + }, + }) + expect(contributionListResult).toMatchObject({ + contributionCount: 6, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: pendingContribution.data.createContribution.id, + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: contributionToDeny.data.createContribution.id, + memo: 'Test contribution to deny', + amount: '100', + }), + expect.objectContaining({ + id: contributionToDelete.data.createContribution.id, + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: inProgressContribution.data.createContribution.id, + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + expect.objectContaining({ + id: bibiCreatedContribution.id, + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + ]), + }) + expect(contributionListResult.contributionList).toHaveLength(6) + }) + }) + + describe('filter confirmed is true', () => { + it('returns only unconfirmed creations', async () => { + const { + data: { listContributions: contributionListResult }, + }: { data: { listContributions: ContributionListResult } } = await query({ + query: listContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + filterConfirmed: true, + }, + }) + expect(contributionListResult).toMatchObject({ + contributionCount: 4, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'CONFIRMED', + }), + expect.objectContaining({ + id: pendingContribution.data.createContribution.id, + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.objectContaining({ + id: contributionToDelete.data.createContribution.id, + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: inProgressContribution.data.createContribution.id, + state: 'IN_PROGRESS', + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + ]), + }) + expect(contributionListResult.contributionList).toHaveLength(4) + }) + }) + }) + }) + + describe('listAllContribution', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + const { errors: errorObjects }: { errors: [GraphQLError] } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: null, + }, + }) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) + }) + }) + + describe('authenticated', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + resetToken() + }) + + it('throws an error with "NOT_VALID" in statusFilter', async () => { + const { errors: errorObjects }: { errors: [GraphQLError | UserInputError] } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: ['NOT_VALID'], + }, + }) + expect(errorObjects).toEqual([ + new UserInputError( + 'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[0]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.', + ), + ]) + }) + + it('throws an error with a null in statusFilter', async () => { + const { errors: errorObjects }: { errors: [Error] } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: [null], + }, + }) + expect(errorObjects).toEqual([ + new UserInputError( + 'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.', + ), + ]) + }) + + it('throws an error with null and "NOT_VALID" in statusFilter', async () => { + const { errors: errorObjects }: { errors: [Error] } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: [null, 'NOT_VALID'], + }, + }) + expect(errorObjects).toEqual([ + new UserInputError( + 'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.', + ), + new UserInputError( + 'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[1]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.', + ), + ]) + }) + + it('returns all contributions without statusFilter', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + }, + }) + expect(contributionListObject).toMatchObject({ + contributionCount: 7, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: pendingContribution.data.createContribution.id, + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.objectContaining({ + id: inProgressContribution.data.createContribution.id, + state: 'IN_PROGRESS', + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + expect.objectContaining({ + id: bibiCreatedContribution.id, + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + ]), + }) + expect(contributionListObject.contributionList).toHaveLength(7) + }) + + it('returns all contributions for statusFilter = null', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: null, + }, + }) + expect(contributionListObject).toMatchObject({ + contributionCount: 7, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: pendingContribution.data.createContribution.id, + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.objectContaining({ + id: inProgressContribution.data.createContribution.id, + state: 'IN_PROGRESS', + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + expect.objectContaining({ + id: bibiCreatedContribution.id, + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + ]), + }) + expect(contributionListObject.contributionList).toHaveLength(7) + }) + + it('returns all contributions for statusFilter = []', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: [], + }, + }) + expect(contributionListObject).toMatchObject({ + contributionCount: 7, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: pendingContribution.data.createContribution.id, + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.objectContaining({ + id: inProgressContribution.data.createContribution.id, + state: 'IN_PROGRESS', + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + expect.objectContaining({ + id: bibiCreatedContribution.id, + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + ]), + }) + expect(contributionListObject.contributionList).toHaveLength(7) + }) + + it('returns all CONFIRMED contributions', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: ['CONFIRMED'], + }, + }) + expect(contributionListObject).toMatchObject({ + contributionCount: 3, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: bibiCreatedContribution.id, + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + state: 'PENDING', + }), + expect.not.objectContaining({ + state: 'DENIED', + }), + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.not.objectContaining({ + state: 'IN_PROGRESS', + }), + ]), + }) + expect(contributionListObject.contributionList).toHaveLength(3) + }) + + it('returns all PENDING contributions', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: ['PENDING'], + }, + }) + expect(contributionListObject).toMatchObject({ + contributionCount: 1, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'CONFIRMED', + }), + expect.not.objectContaining({ + state: 'DENIED', + }), + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.not.objectContaining({ + state: 'IN_PROGRESS', + }), + expect.objectContaining({ + id: pendingContribution.data.createContribution.id, + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + ]), + }) + expect(contributionListObject.contributionList).toHaveLength(1) + }) + + it('returns all IN_PROGRESS Creation', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: ['IN_PROGRESS'], + }, + }) + expect(contributionListObject).toMatchObject({ + contributionCount: 1, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'CONFIRMED', + }), + expect.not.objectContaining({ + state: 'PENDING', + }), + expect.not.objectContaining({ + state: 'DENIED', + }), + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.objectContaining({ + id: inProgressContribution.data.createContribution.id, + state: 'IN_PROGRESS', + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + ]), + }) + expect(contributionListObject.contributionList).toHaveLength(1) + }) + + it('returns all DENIED Creation', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: ['DENIED'], + }, + }) + expect(contributionListObject).toMatchObject({ + contributionCount: 2, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + state: 'CONFIRMED', + }), + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.not.objectContaining({ + state: 'IN_PROGRESS', + }), + expect.not.objectContaining({ + state: 'PENDING', + }), + ]), + }) + expect(contributionListObject.contributionList).toHaveLength(2) + }) + + it('does not return any DELETED Creation', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: ['DELETED'], + }, + }) + expect(contributionListObject).toEqual({ + contributionCount: 0, + contributionList: [], + }) + expect(contributionListObject.contributionList).toHaveLength(0) + }) + + it('returns all CONFIRMED and PENDING Creation', async () => { + const { + data: { listAllContributions: contributionListObject }, + }: { data: { listAllContributions: ContributionListResult } } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: ['CONFIRMED', 'PENDING'], + }, + }) + expect(contributionListObject).toMatchObject({ + contributionCount: 4, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: pendingContribution.data.createContribution.id, + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: bibiCreatedContribution.id, + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + state: 'DENIED', + }), + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.not.objectContaining({ + state: 'IN_PROGRESS', + }), + ]), + }) + expect(contributionListObject.contributionList).toHaveLength(4) + }) + }) + }) + describe('contributions', () => { const variables = { email: 'bibi@bloxberg.de', @@ -1439,7 +1739,6 @@ describe('ContributionResolver', () => { describe('authenticated', () => { describe('without admin rights', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, @@ -1447,7 +1746,6 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -1548,7 +1846,6 @@ describe('ContributionResolver', () => { describe('with admin rights', () => { beforeAll(async () => { - admin = await userFactory(testEnv, peterLustig) await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, @@ -1556,7 +1853,6 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -1578,6 +1874,7 @@ describe('ContributionResolver', () => { creation = await Contribution.findOneOrFail({ where: { memo: 'Herzlich Willkommen bei Gradido!', + amount: 400, }, }) }) @@ -1585,6 +1882,7 @@ describe('ContributionResolver', () => { describe('user to create for does not exist', () => { it('throws an error', async () => { jest.clearAllMocks() + variables.email = 'some@fake.email' variables.creationDate = contributionDateFormatter( new Date(now.getFullYear(), now.getMonth() - 1, 1), ) @@ -1598,7 +1896,7 @@ describe('ContributionResolver', () => { }) it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Could not find user', 'bibi@bloxberg.de') + expect(logger.error).toBeCalledWith('Could not find user', 'some@fake.email') }) }) @@ -1670,7 +1968,6 @@ describe('ContributionResolver', () => { describe('valid user to create for', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) variables.email = 'bibi@bloxberg.de' variables.creationDate = 'invalid-date' }) @@ -1752,7 +2049,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (2000 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (2000 GDD) to be created exceeds the amount (790 GDD) still available for this month.', ), ], }), @@ -1761,7 +2058,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (2000 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount (2000 GDD) to be created exceeds the amount (790 GDD) still available for this month.', ) }) }) @@ -1774,7 +2071,7 @@ describe('ContributionResolver', () => { ).resolves.toEqual( expect.objectContaining({ data: { - adminCreateContribution: [1000, 1000, 800], + adminCreateContribution: [1000, 1000, 590], }, }), ) @@ -1785,6 +2082,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE, userId: admin.id, + amount: expect.decimalEqual(200), }), ) }) @@ -1792,6 +2090,7 @@ describe('ContributionResolver', () => { describe('second creation surpasses the available amount ', () => { it('returns an array of the open creations for the last three months', async () => { + jest.clearAllMocks() variables.amount = new Decimal(1000) await expect( mutate({ mutation: adminCreateContribution, variables }), @@ -1799,7 +2098,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1000 GDD) to be created exceeds the amount (800 GDD) still available for this month.', + 'The amount (1000 GDD) to be created exceeds the amount (590 GDD) still available for this month.', ), ], }), @@ -1808,7 +2107,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (1000 GDD) to be created exceeds the amount (800 GDD) still available for this month.', + 'The amount (1000 GDD) to be created exceeds the amount (590 GDD) still available for this month.', ) }) }) @@ -2050,6 +2349,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, userId: admin.id, + amount: 300, }), ) }) @@ -2090,6 +2390,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, userId: admin.id, + amount: expect.decimalEqual(200), }), ) }) @@ -2106,7 +2407,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listUnconfirmedContributions: expect.arrayContaining([ - { + expect.objectContaining({ id: expect.any(Number), firstName: 'Peter', lastName: 'Lustig', @@ -2116,8 +2417,8 @@ describe('ContributionResolver', () => { amount: '200', moderator: admin.id, creation: ['1000', '800', '500'], - }, - { + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Peter', lastName: 'Lustig', @@ -2127,8 +2428,41 @@ describe('ContributionResolver', () => { amount: '500', moderator: admin.id, creation: ['1000', '800', '500'], - }, - { + }), + expect.not.objectContaining({ + id: expect.any(Number), + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + date: expect.any(String), + memo: 'Test contribution to delete', + amount: '100', + moderator: null, + creation: ['1000', '1000', '90'], + }), + expect.objectContaining({ + id: expect.any(Number), + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + date: expect.any(String), + memo: 'Test PENDING contribution update', + amount: '10', + moderator: null, + creation: ['1000', '1000', '90'], + }), + expect.objectContaining({ + id: expect.any(Number), + firstName: 'Bibi', + lastName: 'Bloxberg', + email: 'bibi@bloxberg.de', + date: expect.any(String), + memo: 'Test IN_PROGRESS contribution', + amount: '100', + moderator: null, + creation: ['1000', '1000', '90'], + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Bibi', lastName: 'Bloxberg', @@ -2137,9 +2471,9 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '1000', '300'], - }, - { + creation: ['1000', '1000', '90'], + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Bibi', lastName: 'Bloxberg', @@ -2148,8 +2482,8 @@ describe('ContributionResolver', () => { memo: 'Aktives Grundeinkommen', amount: '200', moderator: admin.id, - creation: ['1000', '1000', '300'], - }, + creation: ['1000', '1000', '90'], + }), ]), }, }), @@ -2181,12 +2515,13 @@ describe('ContributionResolver', () => { }) describe('admin deletes own user contribution', () => { + let ownContribution: any beforeAll(async () => { await query({ query: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - result = await mutate({ + ownContribution = await mutate({ mutation: createContribution, variables: { amount: 100.0, @@ -2202,7 +2537,7 @@ describe('ContributionResolver', () => { mutate({ mutation: adminDeleteContribution, variables: { - id: result.data.createContribution.id, + id: ownContribution.data.createContribution.id, }, }), ).resolves.toEqual( @@ -2234,6 +2569,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE, userId: admin.id, + amount: expect.decimalEqual(200), }), ) }) @@ -2320,6 +2656,7 @@ describe('ContributionResolver', () => { }) it('thows an error', async () => { + jest.clearAllMocks() await expect( mutate({ mutation: confirmContribution, diff --git a/backend/src/seeds/factory/creation.ts b/backend/src/seeds/factory/creation.ts index 09bf981bb..69d77aa03 100644 --- a/backend/src/seeds/factory/creation.ts +++ b/backend/src/seeds/factory/creation.ts @@ -16,7 +16,7 @@ export const nMonthsBefore = (date: Date, months = 1): string => { export const creationFactory = async ( client: ApolloServerTestClient, creation: CreationInterface, -): Promise => { +): Promise => { const { mutate } = client await mutate({ mutation: login, variables: { email: creation.email, password: 'Aa12345_' } }) @@ -51,6 +51,7 @@ export const creationFactory = async ( await confirmedContribution.save() } } + return confirmedContribution } else { return contribution } diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 5c05a4de9..4f9cbdeff 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -272,6 +272,12 @@ export const deleteContribution = gql` } ` +export const denyContribution = gql` + mutation ($id: Int!) { + denyContribution(id: $id) + } +` + export const createContributionMessage = gql` mutation ($contributionId: Float!, $message: String!) { createContributionMessage(contributionId: $contributionId, message: $message) { diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 385a69479..3469c200d 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -166,6 +166,15 @@ export const listContributions = gql` id amount memo + createdAt + contributionDate + confirmedAt + confirmedBy + deletedAt + state + messagesCount + deniedAt + deniedBy } } }