/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */

import { objectValuesToArray } from '@/util/utilities'
import { testEnvironment, resetToken, cleanDB } from '@test/helpers'
import { userFactory } from '@/seeds/factory/user'
import { creationFactory } from '@/seeds/factory/creation'
import { creations } from '@/seeds/creation/index'
import { transactionLinkFactory } from '@/seeds/factory/transactionLink'
import { transactionLinks } from '@/seeds/transactionLink/index'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { peterLustig } from '@/seeds/users/peter-lustig'
import { stephenHawking } from '@/seeds/users/stephen-hawking'
import { garrickOllivander } from '@/seeds/users/garrick-ollivander'
import {
  login,
  setUserRole,
  deleteUser,
  unDeleteUser,
  createContribution,
  adminCreateContribution,
  adminCreateContributions,
  adminUpdateContribution,
  adminDeleteContribution,
  confirmContribution,
  createContributionLink,
  deleteContributionLink,
  updateContributionLink,
} from '@/seeds/graphql/mutations'
import {
  listUnconfirmedContributions,
  searchUsers,
  listTransactionLinksAdmin,
  listContributionLinks,
} from '@/seeds/graphql/queries'
import { GraphQLError } from 'graphql'
import { User } from '@entity/User'
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail'
import Decimal from 'decimal.js-light'
import { Contribution } from '@entity/Contribution'
import { Transaction as DbTransaction } from '@entity/Transaction'
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
import { EventProtocol } from '@entity/EventProtocol'
import { EventProtocolType } from '@/event/EventProtocolType'
import { logger } from '@test/testSetup'

// mock account activation email to avoid console spam
jest.mock('@/mailer/sendAccountActivationEmail', () => {
  return {
    __esModule: true,
    sendAccountActivationEmail: jest.fn(),
  }
})

// mock account activation email to avoid console spam
jest.mock('@/mailer/sendContributionConfirmedEmail', () => {
  return {
    __esModule: true,
    sendContributionConfirmedEmail: jest.fn(),
  }
})

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

let admin: User
let user: User
let creation: Contribution | void
let result: any

describe('AdminResolver', () => {
  describe('set user role', () => {
    describe('unauthenticated', () => {
      it('returns an error', async () => {
        await expect(
          mutate({ mutation: setUserRole, variables: { userId: 1, isAdmin: true } }),
        ).resolves.toEqual(
          expect.objectContaining({
            errors: [new GraphQLError('401 Unauthorized')],
          }),
        )
      })
    })

    describe('authenticated', () => {
      describe('without admin rights', () => {
        beforeAll(async () => {
          user = await userFactory(testEnv, bibiBloxberg)
          await mutate({
            mutation: login,
            variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        it('returns an error', async () => {
          await expect(
            mutate({ mutation: setUserRole, variables: { userId: user.id + 1, isAdmin: true } }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('with admin rights', () => {
        beforeAll(async () => {
          admin = await userFactory(testEnv, peterLustig)
          await mutate({
            mutation: login,
            variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('user to get a new role does not exist', () => {
          it('throws an error', async () => {
            await expect(
              mutate({ mutation: setUserRole, variables: { userId: admin.id + 1, isAdmin: true } }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
          })
        })

        describe('change role with success', () => {
          beforeAll(async () => {
            user = await userFactory(testEnv, bibiBloxberg)
          })

          describe('user gets new role', () => {
            describe('to admin', () => {
              it('returns date string', async () => {
                const result = await mutate({
                  mutation: setUserRole,
                  variables: { userId: user.id, isAdmin: true },
                })
                expect(result).toEqual(
                  expect.objectContaining({
                    data: {
                      setUserRole: expect.any(String),
                    },
                  }),
                )
                expect(new Date(result.data.setUserRole)).toEqual(expect.any(Date))
              })
            })

            describe('to usual user', () => {
              it('returns null', async () => {
                await expect(
                  mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: false } }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    data: {
                      setUserRole: null,
                    },
                  }),
                )
              })
            })
          })
        })

        describe('change role with error', () => {
          describe('is own role', () => {
            it('throws an error', async () => {
              await expect(
                mutate({ mutation: setUserRole, variables: { userId: admin.id, isAdmin: false } }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('Administrator can not change his own role!')],
                }),
              )
            })
            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith('Administrator can not change his own role!')
            })
          })

          describe('user has already role to be set', () => {
            describe('to admin', () => {
              it('throws an error', async () => {
                await mutate({
                  mutation: setUserRole,
                  variables: { userId: user.id, isAdmin: true },
                })
                await expect(
                  mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: true } }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    errors: [new GraphQLError('User is already admin!')],
                  }),
                )
              })

              it('logs the error thrown', () => {
                expect(logger.error).toBeCalledWith('User is already admin!')
              })
            })

            describe('to usual user', () => {
              it('throws an error', async () => {
                await mutate({
                  mutation: setUserRole,
                  variables: { userId: user.id, isAdmin: false },
                })
                await expect(
                  mutate({ mutation: setUserRole, variables: { userId: user.id, isAdmin: false } }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    errors: [new GraphQLError('User is already a usual user!')],
                  }),
                )
              })

              it('logs the error thrown', () => {
                expect(logger.error).toBeCalledWith('User is already a usual user!')
              })
            })
          })
        })
      })
    })
  })

  describe('delete user', () => {
    describe('unauthenticated', () => {
      it('returns an error', async () => {
        await expect(mutate({ mutation: deleteUser, variables: { userId: 1 } })).resolves.toEqual(
          expect.objectContaining({
            errors: [new GraphQLError('401 Unauthorized')],
          }),
        )
      })
    })

    describe('authenticated', () => {
      describe('without admin rights', () => {
        beforeAll(async () => {
          user = await userFactory(testEnv, bibiBloxberg)
          await mutate({
            mutation: login,
            variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        it('returns an error', async () => {
          await expect(
            mutate({ mutation: deleteUser, variables: { userId: user.id + 1 } }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('with admin rights', () => {
        beforeAll(async () => {
          admin = await userFactory(testEnv, peterLustig)
          await mutate({
            mutation: login,
            variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('user to be deleted does not exist', () => {
          it('throws an error', async () => {
            await expect(
              mutate({ mutation: deleteUser, variables: { userId: admin.id + 1 } }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
          })
        })

        describe('delete self', () => {
          it('throws an error', async () => {
            await expect(
              mutate({ mutation: deleteUser, variables: { userId: admin.id } }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('Moderator can not delete his own account!')],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith('Moderator can not delete his own account!')
          })
        })

        describe('delete with success', () => {
          beforeAll(async () => {
            user = await userFactory(testEnv, bibiBloxberg)
          })

          it('returns date string', async () => {
            const result = await mutate({ mutation: deleteUser, variables: { userId: user.id } })
            expect(result).toEqual(
              expect.objectContaining({
                data: {
                  deleteUser: expect.any(String),
                },
              }),
            )
            expect(new Date(result.data.deleteUser)).toEqual(expect.any(Date))
          })

          describe('delete deleted user', () => {
            it('throws an error', async () => {
              await expect(
                mutate({ mutation: deleteUser, variables: { userId: user.id } }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError(`Could not find user with userId: ${user.id}`)],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith(`Could not find user with userId: ${user.id}`)
            })
          })
        })
      })
    })
  })

  describe('unDelete user', () => {
    describe('unauthenticated', () => {
      it('returns an error', async () => {
        await expect(mutate({ mutation: unDeleteUser, variables: { userId: 1 } })).resolves.toEqual(
          expect.objectContaining({
            errors: [new GraphQLError('401 Unauthorized')],
          }),
        )
      })
    })

    describe('authenticated', () => {
      describe('without admin rights', () => {
        beforeAll(async () => {
          user = await userFactory(testEnv, bibiBloxberg)
          await mutate({
            mutation: login,
            variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        it('returns an error', async () => {
          await expect(
            mutate({ mutation: unDeleteUser, variables: { userId: user.id + 1 } }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('with admin rights', () => {
        beforeAll(async () => {
          admin = await userFactory(testEnv, peterLustig)
          await mutate({
            mutation: login,
            variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('user to be undelete does not exist', () => {
          it('throws an error', async () => {
            await expect(
              mutate({ mutation: unDeleteUser, variables: { userId: admin.id + 1 } }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError(`Could not find user with userId: ${admin.id + 1}`)],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
          })
        })

        describe('user to undelete is not deleted', () => {
          beforeAll(async () => {
            user = await userFactory(testEnv, bibiBloxberg)
          })

          it('throws an error', async () => {
            await expect(
              mutate({ mutation: unDeleteUser, variables: { userId: user.id } }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('User is not deleted')],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith('User is not deleted')
          })

          describe('undelete deleted user', () => {
            beforeAll(async () => {
              await mutate({ mutation: deleteUser, variables: { userId: user.id } })
            })

            it('returns null', async () => {
              await expect(
                mutate({ mutation: unDeleteUser, variables: { userId: user.id } }),
              ).resolves.toEqual(
                expect.objectContaining({
                  data: { unDeleteUser: null },
                }),
              )
            })
          })
        })
      })
    })
  })

  describe('search users', () => {
    const variablesWithoutTextAndFilters = {
      searchText: '',
      currentPage: 1,
      pageSize: 25,
      filters: null,
    }

    describe('unauthenticated', () => {
      it('returns an error', async () => {
        await expect(
          query({
            query: searchUsers,
            variables: {
              ...variablesWithoutTextAndFilters,
            },
          }),
        ).resolves.toEqual(
          expect.objectContaining({
            errors: [new GraphQLError('401 Unauthorized')],
          }),
        )
      })
    })

    describe('authenticated', () => {
      describe('without admin rights', () => {
        beforeAll(async () => {
          user = await userFactory(testEnv, bibiBloxberg)
          await mutate({
            mutation: login,
            variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        it('returns an error', async () => {
          await expect(
            query({
              query: searchUsers,
              variables: {
                ...variablesWithoutTextAndFilters,
              },
            }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('with admin rights', () => {
        const allUsers = {
          bibi: expect.objectContaining({
            email: 'bibi@bloxberg.de',
          }),
          garrick: expect.objectContaining({
            email: 'garrick@ollivander.com',
          }),
          peter: expect.objectContaining({
            email: 'peter@lustig.de',
          }),
          stephen: expect.objectContaining({
            email: 'stephen@hawking.uk',
          }),
        }

        beforeAll(async () => {
          admin = await userFactory(testEnv, peterLustig)
          await mutate({
            mutation: login,
            variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
          })

          await userFactory(testEnv, bibiBloxberg)
          await userFactory(testEnv, stephenHawking)
          await userFactory(testEnv, garrickOllivander)
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('without any filters', () => {
          it('finds all users', async () => {
            await expect(
              query({
                query: searchUsers,
                variables: {
                  ...variablesWithoutTextAndFilters,
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  searchUsers: {
                    userCount: 4,
                    userList: expect.arrayContaining(objectValuesToArray(allUsers)),
                  },
                },
              }),
            )
          })
        })

        describe('all filters are null', () => {
          it('finds all users', async () => {
            await expect(
              query({
                query: searchUsers,
                variables: {
                  ...variablesWithoutTextAndFilters,
                  filters: {
                    byActivated: null,
                    byDeleted: null,
                  },
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  searchUsers: {
                    userCount: 4,
                    userList: expect.arrayContaining(objectValuesToArray(allUsers)),
                  },
                },
              }),
            )
          })
        })

        describe('filter by unchecked email', () => {
          it('finds only users with unchecked email', async () => {
            await expect(
              query({
                query: searchUsers,
                variables: {
                  ...variablesWithoutTextAndFilters,
                  filters: {
                    byActivated: false,
                    byDeleted: null,
                  },
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  searchUsers: {
                    userCount: 1,
                    userList: expect.arrayContaining([allUsers.garrick]),
                  },
                },
              }),
            )
          })
        })

        describe('filter by deleted users', () => {
          it('finds only users with deleted account', async () => {
            await expect(
              query({
                query: searchUsers,
                variables: {
                  ...variablesWithoutTextAndFilters,
                  filters: {
                    byActivated: null,
                    byDeleted: true,
                  },
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  searchUsers: {
                    userCount: 1,
                    userList: expect.arrayContaining([allUsers.stephen]),
                  },
                },
              }),
            )
          })
        })

        describe('filter by deleted account and unchecked email', () => {
          it('finds no users', async () => {
            await expect(
              query({
                query: searchUsers,
                variables: {
                  ...variablesWithoutTextAndFilters,
                  filters: {
                    byActivated: false,
                    byDeleted: true,
                  },
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  searchUsers: {
                    userCount: 0,
                    userList: [],
                  },
                },
              }),
            )
          })
        })
      })
    })
  })

  describe('creations', () => {
    const variables = {
      email: 'bibi@bloxberg.de',
      amount: new Decimal(2000),
      memo: 'Aktives Grundeinkommen',
      creationDate: 'not-valid',
    }

    describe('unauthenticated', () => {
      describe('adminCreateContribution', () => {
        it('returns an error', async () => {
          await expect(mutate({ mutation: adminCreateContribution, variables })).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('adminCreateContributions', () => {
        it('returns an error', async () => {
          await expect(
            mutate({
              mutation: adminCreateContributions,
              variables: { pendingCreations: [variables] },
            }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('adminUpdateContribution', () => {
        it('returns an error', async () => {
          await expect(
            mutate({
              mutation: adminUpdateContribution,
              variables: {
                id: 1,
                email: 'bibi@bloxberg.de',
                amount: new Decimal(300),
                memo: 'Danke Bibi!',
                creationDate: new Date().toString(),
              },
            }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('listUnconfirmedContributions', () => {
        it('returns an error', async () => {
          await expect(
            query({
              query: listUnconfirmedContributions,
            }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('adminDeleteContribution', () => {
        it('returns an error', async () => {
          await expect(
            mutate({
              mutation: adminDeleteContribution,
              variables: {
                id: 1,
              },
            }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('confirmContribution', () => {
        it('returns an error', async () => {
          await expect(
            mutate({
              mutation: confirmContribution,
              variables: {
                id: 1,
              },
            }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })
    })

    describe('authenticated', () => {
      describe('without admin rights', () => {
        beforeAll(async () => {
          user = await userFactory(testEnv, bibiBloxberg)
          await mutate({
            mutation: login,
            variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('adminCreateContribution', () => {
          it('returns an error', async () => {
            await expect(mutate({ mutation: adminCreateContribution, variables })).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })

        describe('adminCreateContributions', () => {
          it('returns an error', async () => {
            await expect(
              mutate({
                mutation: adminCreateContributions,
                variables: { pendingCreations: [variables] },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })

        describe('adminUpdateContribution', () => {
          it('returns an error', async () => {
            await expect(
              mutate({
                mutation: adminUpdateContribution,
                variables: {
                  id: 1,
                  email: 'bibi@bloxberg.de',
                  amount: new Decimal(300),
                  memo: 'Danke Bibi!',
                  creationDate: new Date().toString(),
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })

        describe('listUnconfirmedContributions', () => {
          it('returns an error', async () => {
            await expect(
              query({
                query: listUnconfirmedContributions,
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })

        describe('adminDeleteContribution', () => {
          it('returns an error', async () => {
            await expect(
              mutate({
                mutation: adminDeleteContribution,
                variables: {
                  id: 1,
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })

        describe('confirmContribution', () => {
          it('returns an error', async () => {
            await expect(
              mutate({
                mutation: confirmContribution,
                variables: {
                  id: 1,
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })
      })

      describe('with admin rights', () => {
        beforeAll(async () => {
          admin = await userFactory(testEnv, peterLustig)
          await mutate({
            mutation: login,
            variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('adminCreateContribution', () => {
          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(),
            })
          })

          describe('user to create for does not exist', () => {
            it('throws an error', async () => {
              await expect(
                mutate({ mutation: adminCreateContribution, variables }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('Could not find user with email: bibi@bloxberg.de')],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith(
                'Could not find user with email: bibi@bloxberg.de',
              )
            })
          })

          describe('user to create for is deleted', () => {
            beforeAll(async () => {
              user = await userFactory(testEnv, stephenHawking)
              variables.email = 'stephen@hawking.uk'
            })

            it('throws an error', async () => {
              await expect(
                mutate({ mutation: adminCreateContribution, variables }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [
                    new GraphQLError('This user was deleted. Cannot create a contribution.'),
                  ],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith(
                'This user was deleted. Cannot create a contribution.',
              )
            })
          })

          describe('user to create for has email not confirmed', () => {
            beforeAll(async () => {
              user = await userFactory(testEnv, garrickOllivander)
              variables.email = 'garrick@ollivander.com'
            })

            it('throws an error', async () => {
              await expect(
                mutate({ mutation: adminCreateContribution, variables }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [
                    new GraphQLError('Contribution could not be saved, Email is not activated'),
                  ],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith(
                'Contribution could not be saved, Email is not activated',
              )
            })
          })

          describe('valid user to create for', () => {
            beforeAll(async () => {
              user = await userFactory(testEnv, bibiBloxberg)
              variables.email = 'bibi@bloxberg.de'
            })

            describe('date of creation is not a date string', () => {
              it('throws an error', async () => {
                await expect(
                  mutate({ mutation: adminCreateContribution, variables }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    errors: [
                      new GraphQLError('No information for available creations for the given date'),
                    ],
                  }),
                )
              })

              it('logs the error thrown', () => {
                expect(logger.error).toBeCalledWith(
                  'No information for available creations with the given creationDate=',
                  'Invalid Date',
                )
              })
            })

            describe('date of creation is four months ago', () => {
              it('throws an error', async () => {
                const now = new Date()
                variables.creationDate = new Date(
                  now.getFullYear(),
                  now.getMonth() - 4,
                  1,
                ).toString()
                await expect(
                  mutate({ mutation: adminCreateContribution, variables }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    errors: [
                      new GraphQLError('No information for available creations for the given date'),
                    ],
                  }),
                )
              })

              it('logs the error thrown', () => {
                expect(logger.error).toBeCalledWith(
                  'No information for available creations with the given creationDate=',
                  variables.creationDate,
                )
              })
            })

            describe('date of creation is in the future', () => {
              it('throws an error', async () => {
                const now = new Date()
                variables.creationDate = new Date(
                  now.getFullYear(),
                  now.getMonth() + 4,
                  1,
                ).toString()
                await expect(
                  mutate({ mutation: adminCreateContribution, variables }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    errors: [
                      new GraphQLError('No information for available creations for the given date'),
                    ],
                  }),
                )
              })

              it('logs the error thrown', () => {
                expect(logger.error).toBeCalledWith(
                  'No information for available creations with the given creationDate=',
                  variables.creationDate,
                )
              })
            })

            describe('amount of creation is too high', () => {
              it('throws an error', async () => {
                variables.creationDate = new Date().toString()
                await expect(
                  mutate({ mutation: adminCreateContribution, variables }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    errors: [
                      new GraphQLError(
                        'The amount (2000 GDD) to be created exceeds the amount (1000 GDD) still available for this month.',
                      ),
                    ],
                  }),
                )
              })

              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.',
                )
              })
            })

            describe('creation is valid', () => {
              it('returns an array of the open creations for the last three months', async () => {
                variables.amount = new Decimal(200)
                await expect(
                  mutate({ mutation: adminCreateContribution, variables }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    data: {
                      adminCreateContribution: [1000, 1000, 800],
                    },
                  }),
                )
              })

              it('stores the admin create contribution event in the database', async () => {
                await expect(EventProtocol.find()).resolves.toContainEqual(
                  expect.objectContaining({
                    type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
                    userId: admin.id,
                  }),
                )
              })
            })

            describe('second creation surpasses the available amount ', () => {
              it('returns an array of the open creations for the last three months', async () => {
                variables.amount = new Decimal(1000)
                await expect(
                  mutate({ mutation: adminCreateContribution, variables }),
                ).resolves.toEqual(
                  expect.objectContaining({
                    errors: [
                      new GraphQLError(
                        'The amount (1000 GDD) to be created exceeds the amount (800 GDD) still available for this month.',
                      ),
                    ],
                  }),
                )
              })

              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.',
                )
              })
            })
          })
        })

        describe('adminCreateContributions', () => {
          // at this point we have this data in DB:
          // bibi@bloxberg.de: [1000, 1000, 800]
          // peter@lustig.de: [1000, 600, 1000]
          // stephen@hawking.uk: [1000, 1000, 1000] - deleted
          // garrick@ollivander.com: [1000, 1000, 1000] - not activated

          const massCreationVariables = [
            'bibi@bloxberg.de',
            'peter@lustig.de',
            'stephen@hawking.uk',
            'garrick@ollivander.com',
            'bob@baumeister.de',
          ].map((email) => {
            return {
              email,
              amount: new Decimal(500),
              memo: 'Grundeinkommen',
              creationDate: new Date().toString(),
            }
          })

          it('returns success, two successful creation and three failed creations', async () => {
            await expect(
              mutate({
                mutation: adminCreateContributions,
                variables: { pendingCreations: massCreationVariables },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  adminCreateContributions: {
                    success: true,
                    successfulContribution: ['bibi@bloxberg.de', 'peter@lustig.de'],
                    failedContribution: [
                      'stephen@hawking.uk',
                      'garrick@ollivander.com',
                      'bob@baumeister.de',
                    ],
                  },
                },
              }),
            )
          })
        })

        describe('adminUpdateContribution', () => {
          // at this I expect to have this data in DB:
          // bibi@bloxberg.de: [1000, 1000, 300]
          // peter@lustig.de: [1000, 600, 500]
          // stephen@hawking.uk: [1000, 1000, 1000] - deleted
          // garrick@ollivander.com: [1000, 1000, 1000] - not activated

          describe('user for creation to update does not exist', () => {
            it('throws an error', async () => {
              await expect(
                mutate({
                  mutation: adminUpdateContribution,
                  variables: {
                    id: 1,
                    email: 'bob@baumeister.de',
                    amount: new Decimal(300),
                    memo: 'Danke Bibi!',
                    creationDate: new Date().toString(),
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [
                    new GraphQLError('Could not find UserContact with email: bob@baumeister.de'),
                  ],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith(
                'Could not find UserContact with email: bob@baumeister.de',
              )
            })
          })

          describe('user for creation to update is deleted', () => {
            it('throws an error', async () => {
              await expect(
                mutate({
                  mutation: adminUpdateContribution,
                  variables: {
                    id: 1,
                    email: 'stephen@hawking.uk',
                    amount: new Decimal(300),
                    memo: 'Danke Bibi!',
                    creationDate: new Date().toString(),
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('User was deleted (stephen@hawking.uk)')],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith('User was deleted (stephen@hawking.uk)')
            })
          })

          describe('creation does not exist', () => {
            it('throws an error', async () => {
              await expect(
                mutate({
                  mutation: adminUpdateContribution,
                  variables: {
                    id: -1,
                    email: 'bibi@bloxberg.de',
                    amount: new Decimal(300),
                    memo: 'Danke Bibi!',
                    creationDate: new Date().toString(),
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('No contribution found to given id.')],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith('No contribution found to given id.')
            })
          })

          describe('user email does not match creation user', () => {
            it('throws an error', async () => {
              await expect(
                mutate({
                  mutation: adminUpdateContribution,
                  variables: {
                    id: creation ? creation.id : -1,
                    email: 'bibi@bloxberg.de',
                    amount: new Decimal(300),
                    memo: 'Danke Bibi!',
                    creationDate: creation
                      ? creation.contributionDate.toString()
                      : new Date().toString(),
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [
                    new GraphQLError(
                      'user of the pending contribution and send user does not correspond',
                    ),
                  ],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith(
                'user of the pending contribution and send user does not correspond',
              )
            })
          })

          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 () => {
              await expect(
                mutate({
                  mutation: adminUpdateContribution,
                  variables: {
                    id: creation ? creation.id : -1,
                    email: 'peter@lustig.de',
                    amount: new Decimal(1900),
                    memo: 'Danke Peter!',
                    creationDate: creation
                      ? creation.contributionDate.toString()
                      : new Date().toString(),
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [
                    new GraphQLError(
                      'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.',
                    ),
                  ],
                }),
              )
            })

            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.',
              )
            })
          })

          describe.skip('creation update is successful changing month', () => {
            // skipped as changing the month is currently disable
            it('returns update creation object', async () => {
              await expect(
                mutate({
                  mutation: adminUpdateContribution,
                  variables: {
                    id: creation ? creation.id : -1,
                    email: 'peter@lustig.de',
                    amount: new Decimal(300),
                    memo: 'Danke Peter!',
                    creationDate: creation
                      ? creation.contributionDate.toString()
                      : new Date().toString(),
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  data: {
                    adminUpdateContribution: {
                      date: expect.any(String),
                      memo: 'Danke Peter!',
                      amount: '300',
                      creation: ['1000', '700', '500'],
                    },
                  },
                }),
              )
            })

            it('stores the admin update contribution event in the database', async () => {
              await expect(EventProtocol.find()).resolves.toContainEqual(
                expect.objectContaining({
                  type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
                  userId: admin.id,
                }),
              )
            })
          })

          describe('creation update is successful without changing month', () => {
            // actually this mutation IS changing the month
            it('returns update creation object', async () => {
              await expect(
                mutate({
                  mutation: adminUpdateContribution,
                  variables: {
                    id: creation ? creation.id : -1,
                    email: 'peter@lustig.de',
                    amount: new Decimal(200),
                    memo: 'Das war leider zu Viel!',
                    creationDate: creation
                      ? creation.contributionDate.toString()
                      : new Date().toString(),
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  data: {
                    adminUpdateContribution: {
                      date: expect.any(String),
                      memo: 'Das war leider zu Viel!',
                      amount: '200',
                      creation: ['1000', '800', '500'],
                    },
                  },
                }),
              )
            })

            it('stores the admin update contribution event in the database', async () => {
              await expect(EventProtocol.find()).resolves.toContainEqual(
                expect.objectContaining({
                  type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
                  userId: admin.id,
                }),
              )
            })
          })
        })

        describe('listUnconfirmedContributions', () => {
          it('returns four pending creations', async () => {
            await expect(
              query({
                query: listUnconfirmedContributions,
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  listUnconfirmedContributions: expect.arrayContaining([
                    {
                      id: expect.any(Number),
                      firstName: 'Peter',
                      lastName: 'Lustig',
                      email: 'peter@lustig.de',
                      date: expect.any(String),
                      memo: 'Das war leider zu Viel!',
                      amount: '200',
                      moderator: admin.id,
                      creation: ['1000', '800', '500'],
                    },
                    {
                      id: expect.any(Number),
                      firstName: 'Peter',
                      lastName: 'Lustig',
                      email: 'peter@lustig.de',
                      date: expect.any(String),
                      memo: 'Grundeinkommen',
                      amount: '500',
                      moderator: admin.id,
                      creation: ['1000', '800', '500'],
                    },
                    {
                      id: expect.any(Number),
                      firstName: 'Bibi',
                      lastName: 'Bloxberg',
                      email: 'bibi@bloxberg.de',
                      date: expect.any(String),
                      memo: 'Grundeinkommen',
                      amount: '500',
                      moderator: admin.id,
                      creation: ['1000', '1000', '300'],
                    },
                    {
                      id: expect.any(Number),
                      firstName: 'Bibi',
                      lastName: 'Bloxberg',
                      email: 'bibi@bloxberg.de',
                      date: expect.any(String),
                      memo: 'Aktives Grundeinkommen',
                      amount: '200',
                      moderator: admin.id,
                      creation: ['1000', '1000', '300'],
                    },
                  ]),
                },
              }),
            )
          })
        })

        describe('adminDeleteContribution', () => {
          describe('creation id does not exist', () => {
            it('throws an error', async () => {
              await expect(
                mutate({
                  mutation: adminDeleteContribution,
                  variables: {
                    id: -1,
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('Contribution not found for given id.')],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith('Contribution not found for given id: -1')
            })
          })

          describe('admin deletes own user contribution', () => {
            beforeAll(async () => {
              await query({
                query: login,
                variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
              })
              result = await mutate({
                mutation: createContribution,
                variables: {
                  amount: 100.0,
                  memo: 'Test env contribution',
                  creationDate: new Date().toString(),
                },
              })
            })

            it('throws an error', async () => {
              await expect(
                mutate({
                  mutation: adminDeleteContribution,
                  variables: {
                    id: result.data.createContribution.id,
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('Own contribution can not be deleted as admin')],
                }),
              )
            })
          })

          describe('creation id does exist', () => {
            it('returns true', async () => {
              await expect(
                mutate({
                  mutation: adminDeleteContribution,
                  variables: {
                    id: creation ? creation.id : -1,
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  data: { adminDeleteContribution: true },
                }),
              )
            })

            it('stores the admin  delete contribution event in the database', async () => {
              await expect(EventProtocol.find()).resolves.toContainEqual(
                expect.objectContaining({
                  type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
                  userId: admin.id,
                }),
              )
            })
          })
        })

        describe('confirmContribution', () => {
          describe('creation does not exits', () => {
            it('throws an error', async () => {
              await expect(
                mutate({
                  mutation: confirmContribution,
                  variables: {
                    id: -1,
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('Contribution not found to given id.')],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith('Contribution not found for given id: -1')
            })
          })

          describe('confirm own creation', () => {
            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(),
              })
            })

            it('thows an error', async () => {
              await expect(
                mutate({
                  mutation: confirmContribution,
                  variables: {
                    id: creation ? creation.id : -1,
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('Moderator can not confirm own contribution')],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith('Moderator can not confirm own contribution')
            })
          })

          describe('confirm creation for other user', () => {
            beforeAll(async () => {
              const now = new Date()
              creation = await creationFactory(testEnv, {
                email: 'bibi@bloxberg.de',
                amount: 450,
                memo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
                creationDate: new Date(now.getFullYear(), now.getMonth() - 2, 1).toISOString(),
              })
            })

            it('returns true', async () => {
              await expect(
                mutate({
                  mutation: confirmContribution,
                  variables: {
                    id: creation ? creation.id : -1,
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  data: { confirmContribution: true },
                }),
              )
            })

            it('stores the contribution confirm event in the database', async () => {
              await expect(EventProtocol.find()).resolves.toContainEqual(
                expect.objectContaining({
                  type: EventProtocolType.CONTRIBUTION_CONFIRM,
                }),
              )
            })

            it('creates a transaction', async () => {
              const transaction = await DbTransaction.find()
              expect(transaction[0].amount.toString()).toBe('450')
              expect(transaction[0].memo).toBe('Herzlich Willkommen bei Gradido liebe Bibi!')
              expect(transaction[0].linkedTransactionId).toEqual(null)
              expect(transaction[0].transactionLinkId).toEqual(null)
              expect(transaction[0].previous).toEqual(null)
              expect(transaction[0].linkedUserId).toEqual(null)
              expect(transaction[0].typeId).toEqual(1)
            })

            it('calls sendContributionConfirmedEmail', async () => {
              expect(sendContributionConfirmedEmail).toBeCalledWith(
                expect.objectContaining({
                  contributionMemo: 'Herzlich Willkommen bei Gradido liebe Bibi!',
                  overviewURL: 'http://localhost/overview',
                  recipientEmail: 'bibi@bloxberg.de',
                  recipientFirstName: 'Bibi',
                  recipientLastName: 'Bloxberg',
                  senderFirstName: 'Peter',
                  senderLastName: 'Lustig',
                }),
              )
            })

            it('stores the send confirmation email event in the database', async () => {
              await expect(EventProtocol.find()).resolves.toContainEqual(
                expect.objectContaining({
                  type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
                }),
              )
            })
          })

          describe('confirm two creations one after the other quickly', () => {
            let c1: Contribution | void
            let c2: Contribution | void

            beforeAll(async () => {
              const now = new Date()
              c1 = 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(),
              })
              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(),
              })
            })

            // In the futrue this should not throw anymore
            it('throws an error for the second confirmation', async () => {
              const r1 = mutate({
                mutation: confirmContribution,
                variables: {
                  id: c1 ? c1.id : -1,
                },
              })
              const r2 = mutate({
                mutation: confirmContribution,
                variables: {
                  id: c2 ? c2.id : -1,
                },
              })
              await expect(r1).resolves.toEqual(
                expect.objectContaining({
                  data: { confirmContribution: true },
                }),
              )
              await expect(r2).resolves.toEqual(
                expect.objectContaining({
                  // data: { confirmContribution: true },
                  errors: [new GraphQLError('Creation was not successful.')],
                }),
              )
            })
          })
        })
      })
    })
  })

  describe('transaction links list', () => {
    const variables = {
      userId: 1, // dummy, may be replaced
      filters: null,
      currentPage: 1,
      pageSize: 5,
    }

    describe('unauthenticated', () => {
      it('returns an error', async () => {
        await expect(
          query({
            query: listTransactionLinksAdmin,
            variables,
          }),
        ).resolves.toEqual(
          expect.objectContaining({
            errors: [new GraphQLError('401 Unauthorized')],
          }),
        )
      })
    })

    describe('authenticated', () => {
      describe('without admin rights', () => {
        beforeAll(async () => {
          user = await userFactory(testEnv, bibiBloxberg)
          await mutate({
            mutation: login,
            variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        it('returns an error', async () => {
          await expect(
            query({
              query: listTransactionLinksAdmin,
              variables,
            }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('with admin rights', () => {
        beforeAll(async () => {
          // admin 'peter@lustig.de' has to exists for 'creationFactory'
          admin = await userFactory(testEnv, peterLustig)

          user = await userFactory(testEnv, bibiBloxberg)
          variables.userId = user.id
          variables.pageSize = 25
          // bibi needs GDDs
          const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de')
          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
          await creationFactory(testEnv, bibisCreation!)
          // bibis transaktion links
          const bibisTransaktionLinks = transactionLinks.filter(
            (transactionLink) => transactionLink.email === 'bibi@bloxberg.de',
          )
          for (let i = 0; i < bibisTransaktionLinks.length; i++) {
            await transactionLinkFactory(testEnv, bibisTransaktionLinks[i])
          }

          // admin: only now log in
          await mutate({
            mutation: login,
            variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('without any filters', () => {
          it('finds 6 open transaction links and no deleted or redeemed', async () => {
            await expect(
              query({
                query: listTransactionLinksAdmin,
                variables,
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  listTransactionLinksAdmin: {
                    linkCount: 6,
                    linkList: expect.not.arrayContaining([
                      expect.objectContaining({
                        memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
                        createdAt: expect.any(String),
                      }),
                      expect.objectContaining({
                        memo: 'Da habe ich mich wohl etwas übernommen.',
                        deletedAt: expect.any(String),
                      }),
                    ]),
                  },
                },
              }),
            )
          })
        })

        describe('all filters are null', () => {
          it('finds 6 open transaction links and no deleted or redeemed', async () => {
            await expect(
              query({
                query: listTransactionLinksAdmin,
                variables: {
                  ...variables,
                  filters: {
                    withDeleted: null,
                    withExpired: null,
                    withRedeemed: null,
                  },
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  listTransactionLinksAdmin: {
                    linkCount: 6,
                    linkList: expect.not.arrayContaining([
                      expect.objectContaining({
                        memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
                        createdAt: expect.any(String),
                      }),
                      expect.objectContaining({
                        memo: 'Da habe ich mich wohl etwas übernommen.',
                        deletedAt: expect.any(String),
                      }),
                    ]),
                  },
                },
              }),
            )
          })
        })

        describe('filter with deleted', () => {
          it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => {
            await expect(
              query({
                query: listTransactionLinksAdmin,
                variables: {
                  ...variables,
                  filters: {
                    withDeleted: true,
                  },
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  listTransactionLinksAdmin: {
                    linkCount: 7,
                    linkList: expect.arrayContaining([
                      expect.not.objectContaining({
                        memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
                        createdAt: expect.any(String),
                      }),
                      expect.objectContaining({
                        memo: 'Da habe ich mich wohl etwas übernommen.',
                        deletedAt: expect.any(String),
                      }),
                    ]),
                  },
                },
              }),
            )
          })
        })

        describe('filter by expired', () => {
          it('finds 5 open transaction links, 1 expired, and no redeemed', async () => {
            await expect(
              query({
                query: listTransactionLinksAdmin,
                variables: {
                  ...variables,
                  filters: {
                    withExpired: true,
                  },
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  listTransactionLinksAdmin: {
                    linkCount: 7,
                    linkList: expect.arrayContaining([
                      expect.objectContaining({
                        memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
                        createdAt: expect.any(String),
                      }),
                      expect.not.objectContaining({
                        memo: 'Da habe ich mich wohl etwas übernommen.',
                        deletedAt: expect.any(String),
                      }),
                    ]),
                  },
                },
              }),
            )
          })
        })

        // TODO: works not as expected, because 'redeemedAt' and 'redeemedBy' have to be added to the transaktion link factory
        describe.skip('filter by redeemed', () => {
          it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => {
            await expect(
              query({
                query: listTransactionLinksAdmin,
                variables: {
                  ...variables,
                  filters: {
                    withDeleted: null,
                    withExpired: null,
                    withRedeemed: true,
                  },
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                data: {
                  listTransactionLinksAdmin: {
                    linkCount: 6,
                    linkList: expect.arrayContaining([
                      expect.not.objectContaining({
                        memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
                        createdAt: expect.any(String),
                      }),
                      expect.objectContaining({
                        memo: 'Yeah, eingelöst!',
                        redeemedAt: expect.any(String),
                        redeemedBy: expect.any(Number),
                      }),
                      expect.not.objectContaining({
                        memo: 'Da habe ich mich wohl etwas übernommen.',
                        deletedAt: expect.any(String),
                      }),
                    ]),
                  },
                },
              }),
            )
          })
        })
      })
    })
  })

  describe('Contribution Links', () => {
    const now = new Date()
    const variables = {
      amount: new Decimal(200),
      name: 'Dokumenta 2022',
      memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
      cycle: 'once',
      validFrom: new Date(2022, 5, 18).toISOString(),
      validTo: new Date(now.getFullYear() + 1, 7, 14).toISOString(),
      maxAmountPerMonth: new Decimal(200),
      maxPerCycle: 1,
    }

    describe('unauthenticated', () => {
      describe('createContributionLink', () => {
        it('returns an error', async () => {
          await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('listContributionLinks', () => {
        it('returns an error', async () => {
          await expect(query({ query: listContributionLinks })).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('updateContributionLink', () => {
        it('returns an error', async () => {
          await expect(
            mutate({
              mutation: updateContributionLink,
              variables: {
                ...variables,
                id: -1,
                amount: new Decimal(400),
                name: 'Dokumenta 2023',
                memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
              },
            }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })

      describe('deleteContributionLink', () => {
        it('returns an error', async () => {
          await expect(
            mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
          ).resolves.toEqual(
            expect.objectContaining({
              errors: [new GraphQLError('401 Unauthorized')],
            }),
          )
        })
      })
    })

    describe('authenticated', () => {
      describe('without admin rights', () => {
        beforeAll(async () => {
          user = await userFactory(testEnv, bibiBloxberg)
          await mutate({
            mutation: login,
            variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('createContributionLink', () => {
          it('returns an error', async () => {
            await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })

        // TODO: Set this test in new location to have datas
        describe('listContributionLinks', () => {
          it('returns an empty object', async () => {
            await expect(query({ query: listContributionLinks })).resolves.toEqual(
              expect.objectContaining({
                data: {
                  listContributionLinks: {
                    count: 0,
                    links: [],
                  },
                },
              }),
            )
          })
        })

        describe('updateContributionLink', () => {
          it('returns an error', async () => {
            await expect(
              mutate({
                mutation: updateContributionLink,
                variables: {
                  ...variables,
                  id: -1,
                  amount: new Decimal(400),
                  name: 'Dokumenta 2023',
                  memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })

        describe('deleteContributionLink', () => {
          it('returns an error', async () => {
            await expect(
              mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('401 Unauthorized')],
              }),
            )
          })
        })
      })

      describe('with admin rights', () => {
        beforeAll(async () => {
          user = await userFactory(testEnv, peterLustig)
          await mutate({
            mutation: login,
            variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
          })
        })

        afterAll(async () => {
          await cleanDB()
          resetToken()
        })

        describe('createContributionLink', () => {
          it('returns a contribution link object', async () => {
            await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
              expect.objectContaining({
                data: {
                  createContributionLink: expect.objectContaining({
                    id: expect.any(Number),
                    amount: '200',
                    code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
                    link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
                    createdAt: expect.any(String),
                    name: 'Dokumenta 2022',
                    memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
                    validFrom: expect.any(String),
                    validTo: expect.any(String),
                    maxAmountPerMonth: '200',
                    cycle: 'once',
                    maxPerCycle: 1,
                  }),
                },
              }),
            )
          })

          it('has a contribution link stored in db', async () => {
            const cls = await DbContributionLink.find()
            expect(cls).toHaveLength(1)
            expect(cls[0]).toEqual(
              expect.objectContaining({
                id: expect.any(Number),
                name: 'Dokumenta 2022',
                memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
                validFrom: new Date('2022-06-18T00:00:00.000Z'),
                validTo: expect.any(Date),
                cycle: 'once',
                maxPerCycle: 1,
                totalMaxCountOfContribution: null,
                maxAccountBalance: null,
                minGapHours: null,
                createdAt: expect.any(Date),
                deletedAt: null,
                code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
                linkEnabled: true,
                amount: expect.decimalEqual(200),
                maxAmountPerMonth: expect.decimalEqual(200),
              }),
            )
          })

          it('returns an error if missing startDate', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  validFrom: null,
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [
                  new GraphQLError('Start-Date is not initialized. A Start-Date must be set!'),
                ],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(
              'Start-Date is not initialized. A Start-Date must be set!',
            )
          })

          it('returns an error if missing endDate', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  validTo: null,
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('End-Date is not initialized. An End-Date must be set!')],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(
              'End-Date is not initialized. An End-Date must be set!',
            )
          })

          it('returns an error if endDate is before startDate', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  validFrom: new Date('2022-06-18T00:00:00.001Z').toISOString(),
                  validTo: new Date('2022-06-18T00:00:00.000Z').toISOString(),
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [
                  new GraphQLError(`The value of validFrom must before or equals the validTo!`),
                ],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(
              `The value of validFrom must before or equals the validTo!`,
            )
          })

          it('returns an error if name is an empty string', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  name: '',
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('The name must be initialized!')],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith('The name must be initialized!')
          })

          it('returns an error if name is shorter than 5 characters', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  name: '123',
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [
                  new GraphQLError(
                    `The value of 'name' with a length of 3 did not fulfill the requested bounderies min=5 and max=100`,
                  ),
                ],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(
              `The value of 'name' with a length of 3 did not fulfill the requested bounderies min=5 and max=100`,
            )
          })

          it('returns an error if name is longer than 100 characters', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  name: '12345678901234567892123456789312345678941234567895123456789612345678971234567898123456789912345678901',
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [
                  new GraphQLError(
                    `The value of 'name' with a length of 101 did not fulfill the requested bounderies min=5 and max=100`,
                  ),
                ],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(
              `The value of 'name' with a length of 101 did not fulfill the requested bounderies min=5 and max=100`,
            )
          })

          it('returns an error if memo is an empty string', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  memo: '',
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [new GraphQLError('The memo must be initialized!')],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith('The memo must be initialized!')
          })

          it('returns an error if memo is shorter than 5 characters', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  memo: '123',
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [
                  new GraphQLError(
                    `The value of 'memo' with a length of 3 did not fulfill the requested bounderies min=5 and max=255`,
                  ),
                ],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(
              `The value of 'memo' with a length of 3 did not fulfill the requested bounderies min=5 and max=255`,
            )
          })

          it('returns an error if memo is longer than 255 characters', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  memo: '1234567890123456789212345678931234567894123456789512345678961234567897123456789812345678991234567890123456789012345678921234567893123456789412345678951234567896123456789712345678981234567899123456789012345678901234567892123456789312345678941234567895123456',
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [
                  new GraphQLError(
                    `The value of 'memo' with a length of 256 did not fulfill the requested bounderies min=5 and max=255`,
                  ),
                ],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(
              `The value of 'memo' with a length of 256 did not fulfill the requested bounderies min=5 and max=255`,
            )
          })

          it('returns an error if amount is not positive', async () => {
            await expect(
              mutate({
                mutation: createContributionLink,
                variables: {
                  ...variables,
                  amount: new Decimal(0),
                },
              }),
            ).resolves.toEqual(
              expect.objectContaining({
                errors: [
                  new GraphQLError('The amount=0 must be initialized with a positiv value!'),
                ],
              }),
            )
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith(
              'The amount=0 must be initialized with a positiv value!',
            )
          })
        })

        describe('listContributionLinks', () => {
          describe('one link in DB', () => {
            it('returns the link and count 1', async () => {
              await expect(query({ query: listContributionLinks })).resolves.toEqual(
                expect.objectContaining({
                  data: {
                    listContributionLinks: {
                      links: expect.arrayContaining([
                        expect.objectContaining({
                          amount: '200',
                          code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
                          link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
                          createdAt: expect.any(String),
                          name: 'Dokumenta 2022',
                          memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
                          validFrom: expect.any(String),
                          validTo: expect.any(String),
                          maxAmountPerMonth: '200',
                          cycle: 'once',
                          maxPerCycle: 1,
                        }),
                      ]),
                      count: 1,
                    },
                  },
                }),
              )
            })
          })
        })

        describe('updateContributionLink', () => {
          describe('no valid id', () => {
            it('returns an error', async () => {
              await expect(
                mutate({
                  mutation: updateContributionLink,
                  variables: {
                    ...variables,
                    id: -1,
                    amount: new Decimal(400),
                    name: 'Dokumenta 2023',
                    memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('Contribution Link not found to given id.')],
                }),
              )
            })
          })

          it('logs the error thrown', () => {
            expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
          })

          describe('valid id', () => {
            let linkId: number
            beforeAll(async () => {
              const links = await query({ query: listContributionLinks })
              linkId = links.data.listContributionLinks.links[0].id
            })

            it('returns updated contribution link object', async () => {
              await expect(
                mutate({
                  mutation: updateContributionLink,
                  variables: {
                    ...variables,
                    id: linkId,
                    amount: new Decimal(400),
                    name: 'Dokumenta 2023',
                    memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
                  },
                }),
              ).resolves.toEqual(
                expect.objectContaining({
                  data: {
                    updateContributionLink: {
                      id: linkId,
                      amount: '400',
                      code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
                      link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
                      createdAt: expect.any(String),
                      name: 'Dokumenta 2023',
                      memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
                      validFrom: expect.any(String),
                      validTo: expect.any(String),
                      maxAmountPerMonth: '200',
                      cycle: 'once',
                      maxPerCycle: 1,
                    },
                  },
                }),
              )
            })

            it('updated the DB record', async () => {
              await expect(DbContributionLink.findOne(linkId)).resolves.toEqual(
                expect.objectContaining({
                  id: linkId,
                  name: 'Dokumenta 2023',
                  memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
                  amount: expect.decimalEqual(400),
                }),
              )
            })
          })
        })

        describe('deleteContributionLink', () => {
          describe('no valid id', () => {
            it('returns an error', async () => {
              await expect(
                mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
              ).resolves.toEqual(
                expect.objectContaining({
                  errors: [new GraphQLError('Contribution Link not found to given id.')],
                }),
              )
            })

            it('logs the error thrown', () => {
              expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
            })
          })

          describe('valid id', () => {
            let linkId: number
            beforeAll(async () => {
              const links = await query({ query: listContributionLinks })
              linkId = links.data.listContributionLinks.links[0].id
            })

            it('returns a date string', async () => {
              await expect(
                mutate({ mutation: deleteContributionLink, variables: { id: linkId } }),
              ).resolves.toEqual(
                expect.objectContaining({
                  data: {
                    deleteContributionLink: expect.any(String),
                  },
                }),
              )
            })

            it('does not list this contribution link anymore', async () => {
              await expect(query({ query: listContributionLinks })).resolves.toEqual(
                expect.objectContaining({
                  data: {
                    listContributionLinks: {
                      links: [],
                      count: 0,
                    },
                  },
                }),
              )
            })
          })
        })
      })
    })
  })
})
