From 08d8c008f6bd66c3c726785ba13e8125b9973638 Mon Sep 17 00:00:00 2001 From: elweyn Date: Tue, 31 Jan 2023 13:07:41 +0100 Subject: [PATCH 01/67] Add denyContribution mutation to the seeds. --- backend/src/seeds/graphql/mutations.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 2b4ed6656..8c3f97f17 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -266,6 +266,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) { From f3f749889f08aa148b4105f465b83e417c199e3e Mon Sep 17 00:00:00 2001 From: elweyn Date: Tue, 31 Jan 2023 13:24:59 +0100 Subject: [PATCH 02/67] Add Test for denyContribution. --- .../resolver/ContributionResolver.test.ts | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index abae8e446..f17851fd8 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -10,6 +10,7 @@ import { createContribution, updateContribution, deleteContribution, + denyContribution, confirmContribution, adminCreateContribution, adminCreateContributions, @@ -671,6 +672,123 @@ describe('ContributionResolver', () => { }) }) + describe('denyContribution', () => { + describe('unauthenticated', () => { + it('returns an error', async () => { + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: 1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [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(), + }, + }) + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) + + describe('wrong contribution id', () => { + it('throws an error', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: -1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith('Contribution not found for given id: -1') + }) + }) + + describe('wrong user tries to deny the contribution', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + it('throws an error', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: result.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('valid input', () => { + it('deny contribution', async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: result.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + denyContribution: true, + }, + }), + ) + }) + }) + }) + }) + describe('listAllContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { From 147035bf5d7917f6ac13a228fee6546aa42a6a0f Mon Sep 17 00:00:00 2001 From: ogerly Date: Wed, 1 Feb 2023 10:49:08 +0100 Subject: [PATCH 03/67] logo inserted with better quality. --- frontend/public/img/brand/gradido-logo.png | Bin 0 -> 51410 bytes frontend/src/components/Auth/AuthNavbar.vue | 13 +++++++------ frontend/src/components/Menu/Navbar.vue | 8 ++++---- frontend/src/pages/TransactionLink.vue | 1 - 4 files changed, 11 insertions(+), 11 deletions(-) create mode 100644 frontend/public/img/brand/gradido-logo.png diff --git a/frontend/public/img/brand/gradido-logo.png b/frontend/public/img/brand/gradido-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..39b4a66366141be55aec64e2c0d01c1c1e008320 GIT binary patch literal 51410 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

r}pp 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�t7TT*$VGv*nqAkt8apw|7e9X= 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 P00000NkvXXu0mjfv$x35 literal 0 HcmV?d00001 diff --git a/frontend/src/components/Auth/AuthNavbar.vue b/frontend/src/components/Auth/AuthNavbar.vue index 7d8c6f08a..f7a8b6f0a 100644 --- a/frontend/src/components/Auth/AuthNavbar.vue +++ b/frontend/src/components/Auth/AuthNavbar.vue @@ -3,16 +3,16 @@ @@ -35,7 +35,8 @@ export default { mixins: [authLinks], data() { return { - logo: '/img/brand/green.png', + background_header: '/img/template/gradido_background_header.png', + logo: '/img/brand/gradido-logo.png', sheet: '/img/template/Blaetter.png', } }, diff --git a/frontend/src/components/Menu/Navbar.vue b/frontend/src/components/Menu/Navbar.vue index 03217a5ae..73470a91b 100644 --- a/frontend/src/components/Menu/Navbar.vue +++ b/frontend/src/components/Menu/Navbar.vue @@ -4,10 +4,10 @@

@@ -60,7 +60,7 @@ export default { }, data() { return { - logo: '/img/brand/green.png', + logo: '/img/brand/gradido-logo.png', sheet: '/img/template/Blaetter.png', } }, diff --git a/frontend/src/pages/TransactionLink.vue b/frontend/src/pages/TransactionLink.vue index c3875d20e..17bd98031 100644 --- a/frontend/src/pages/TransactionLink.vue +++ b/frontend/src/pages/TransactionLink.vue @@ -45,7 +45,6 @@ export default { }, data() { return { - img: '/img/brand/green.png', linkData: { __typename: 'TransactionLink', amount: '123.45', From 4f0cfb9363a908ffba815639f4ad4d4f5b11c4a9 Mon Sep 17 00:00:00 2001 From: elweyn Date: Fri, 3 Feb 2023 13:55:08 +0100 Subject: [PATCH 04/67] Change structure of tests so that we create every data needed beforeAll unit tests. --- .../resolver/ContributionResolver.test.ts | 1166 ++++++++++------- 1 file changed, 688 insertions(+), 478 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 9c917368b..226ab63cd 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -17,6 +17,8 @@ import { adminUpdateContribution, adminDeleteContribution, login, + logout, + adminCreateContributionMessage, } from '@/seeds/graphql/mutations' import { listAllContributions, @@ -69,7 +71,13 @@ let mutate: any, query: any, con: any let testEnv: any let creation: Contribution | void let admin: User -let result: any +// let result: any +// let contribution: any +let pendingContribution: any +let inProgressContribution: any +let contributionToConfirm: any +let contributionToDeny: any +let contributionToDelete: any beforeAll(async () => { testEnv = await testEnvironment(logger, localization) @@ -86,6 +94,75 @@ afterAll(async () => { describe('ContributionResolver', () => { let bibi: any + let peter: any + + beforeAll(async () => { + bibi = await userFactory(testEnv, bibiBloxberg) + admin = peter = 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_' }, + }) + 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_PROGESS contribution', + creationDate: new Date().toString(), + }, + }) + await mutate({ + mutation: adminCreateContributionMessage, + variables: { + contributionId: inProgressContribution.data.createContribution.id, + message: 'Test message to IN_PROGESS contribution', + }, + }) + 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: logout, + }) + resetToken() + }) + + afterAll(async () => { + await cleanDB() + resetToken() + }) describe('createContribution', () => { describe('unauthenticated', () => { @@ -105,8 +182,6 @@ describe('ContributionResolver', () => { describe('authenticated with valid user', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) - bibi = await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, @@ -114,7 +189,6 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -222,27 +296,14 @@ 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(pendingContribution).toEqual( expect.objectContaining({ data: { createContribution: { id: expect.any(Number), amount: '100', - memo: 'Test env contribution', + memo: 'Test PENDING contribution', }, }, }), @@ -254,7 +315,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_CREATE, amount: expect.decimalEqual(100), - contributionId: contribution.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, userId: bibi.data.login.id, }), ) @@ -263,122 +324,6 @@ describe('ContributionResolver', () => { }) }) - 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', - }), - ]), - }, - }, - }), - ) - }) - }) - }) - }) - describe('updateContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { @@ -402,24 +347,13 @@ describe('ContributionResolver', () => { 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() }) @@ -456,7 +390,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 100.0, memo: 'Test', creationDate: date.toString(), @@ -482,7 +416,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + 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(), @@ -514,7 +448,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 10.0, memo: 'Test env contribution', creationDate: new Date().toString(), @@ -539,13 +473,20 @@ 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, + id: pendingContribution.data.createContribution.id, email: 'bibi@bloxberg.de', amount: 10.0, memo: 'Test env contribution', @@ -562,7 +503,7 @@ describe('ContributionResolver', () => { // TODO check that the error is logged (need to modify AdminResolver, avoid conflicts) }) - describe('update too much so that the limit is exceeded', () => { + describe('update to much so that the limit is exceeded', () => { beforeAll(async () => { await mutate({ mutation: login, @@ -576,7 +517,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 1019.0, memo: 'Test env contribution', creationDate: new Date().toString(), @@ -586,7 +527,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - '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.', ), ], }), @@ -595,7 +536,7 @@ describe('ContributionResolver', () => { 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.', ) }) }) @@ -608,7 +549,7 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 10.0, memo: 'Test env contribution', creationDate: date.setMonth(date.getMonth() - 3).toString(), @@ -635,9 +576,9 @@ describe('ContributionResolver', () => { mutate({ mutation: updateContribution, variables: { - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, amount: 10.0, - memo: 'Test contribution', + memo: 'Test PENDING contribution update', creationDate: new Date().toString(), }, }), @@ -645,9 +586,9 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { updateContribution: { - id: result.data.createContribution.id, + id: pendingContribution.data.createContribution.id, amount: '10', - memo: 'Test contribution', + memo: 'Test PENDING contribution update', }, }, }), @@ -664,7 +605,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_UPDATE, amount: expect.decimalEqual(10), - contributionId: result.data.createContribution.id, + contributionId: pendingContribution.data.createContribution.id, userId: bibi.data.login.id, }), ) @@ -691,22 +632,36 @@ describe('ContributionResolver', () => { }) }) - describe('authenticated', () => { + describe('authenticated without admin rights', () => { 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(() => { + resetToken() + }) + + it('returns an error', async () => { + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: 1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) + }) + }) + + describe('authenticated with admin rights', () => { + beforeAll(async () => { await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, @@ -714,13 +669,11 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) describe('wrong contribution id', () => { it('throws an error', async () => { - jest.clearAllMocks() await expect( mutate({ mutation: denyContribution, @@ -740,31 +693,6 @@ describe('ContributionResolver', () => { }) }) - describe('wrong user tries to deny the contribution', () => { - beforeAll(async () => { - await mutate({ - mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, - }) - }) - - it('throws an error', async () => { - jest.clearAllMocks() - await expect( - mutate({ - mutation: denyContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) - }) - }) - describe('valid input', () => { it('deny contribution', async () => { await mutate({ @@ -775,7 +703,7 @@ describe('ContributionResolver', () => { mutate({ mutation: denyContribution, variables: { - id: result.data.createContribution.id, + id: contributionToDeny.data.createContribution.id, }, }), ).resolves.toEqual( @@ -790,6 +718,282 @@ describe('ContributionResolver', () => { }) }) + 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')], + }), + ) + }) + }) + + describe('authenticated', () => { + beforeAll(async () => { + bibi = await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + resetToken() + }) + + describe('wrong contribution id', () => { + it('returns an error', async () => { + await expect( + mutate({ + mutation: deleteContribution, + variables: { + id: -1, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith('Contribution not found for given id') + }) + }) + + describe('other user sends a deleteContribution', () => { + it('returns an error', async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await expect( + mutate({ + mutation: deleteContribution, + variables: { + id: contributionToDelete.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [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') + }) + }) + + describe('User deletes own contribution', () => { + it('deletes successfully', async () => { + await expect( + mutate({ + mutation: deleteContribution, + variables: { + id: contributionToDelete.data.createContribution.id, + }, + }), + ).resolves.toBeTruthy() + }) + + it('stores the delete contribution event in the database', async () => { + const contribution = await mutate({ + mutation: createContribution, + variables: { + amount: 166.0, + memo: 'Whatever contribution', + creationDate: new Date().toString(), + }, + }) + + await mutate({ + mutation: deleteContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + await expect(EventProtocol.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventProtocolType.CONTRIBUTION_DELETE, + contributionId: contribution.data.createContribution.id, + amount: expect.decimalEqual(166), + userId: peter.id, + }), + ) + }) + }) + + describe('User deletes already confirmed contribution', () => { + it('throws an error', async () => { + jest.clearAllMocks() + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: confirmContribution, + variables: { + id: contributionToConfirm.data.createContribution.id, + }, + }) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + await expect( + mutate({ + mutation: deleteContribution, + variables: { + id: contributionToConfirm.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('A confirmed contribution can not be deleted')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith('A confirmed contribution can not be deleted') + }) + }) + }) + }) + + 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 mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(async () => { + 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: 6, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test contribution to deny', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + ]), + }, + }, + }), + ) + }) + }) + + 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: 4, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test contribution to delete', + amount: '100', + }), + ]), + }, + }, + }), + ) + }) + }) + }) + }) + describe('listAllContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { @@ -813,27 +1017,13 @@ describe('ContributionResolver', () => { 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() }) @@ -920,20 +1110,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 6, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), 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', - }), ]), }, }, @@ -956,20 +1170,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 6, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), 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', - }), ]), }, }, @@ -992,20 +1230,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 6, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), 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', - }), ]), }, }, @@ -1028,20 +1290,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 1, + contributionCount: 2, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), 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', - }), ]), }, }, @@ -1067,17 +1353,41 @@ describe('ContributionResolver', () => { contributionCount: 1, contributionList: expect.arrayContaining([ expect.not.objectContaining({ - id: expect.any(Number), + amount: '100', state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', + id: expect.any(Number), + memo: 'Test contribution to confirm', }), expect.objectContaining({ id: expect.any(Number), state: 'PENDING', - memo: 'Test env contribution', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', amount: '100', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), ]), }, }, @@ -1100,20 +1410,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 0, - contributionList: expect.not.arrayContaining([ + contributionCount: 1, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), expect.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), + 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', - }), ]), }, }, @@ -1136,20 +1470,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 0, - contributionList: expect.not.arrayContaining([ + contributionCount: 1, + contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), expect.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), + 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', - }), ]), }, }, @@ -1173,20 +1531,7 @@ describe('ContributionResolver', () => { 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', - }), - ]), + contributionList: [], }, }, }), @@ -1208,20 +1553,44 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 3, contributionList: expect.arrayContaining([ + expect.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'IN_PROGRESS', + memo: 'Test IN_PROGESS contribution', + amount: '100', + }), 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', - }), ]), }, }, @@ -1231,173 +1600,6 @@ describe('ContributionResolver', () => { }) }) - 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')], - }), - ) - }) - }) - - describe('authenticated', () => { - let peter: any - 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(), - }, - }) - }) - - afterAll(async () => { - await cleanDB() - resetToken() - }) - - describe('wrong contribution id', () => { - it('returns an error', async () => { - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: -1, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution not found for given id.')], - }), - ) - }) - - it('logs the error found', () => { - expect(logger.error).toBeCalledWith('Contribution not found for given id') - }) - }) - - describe('other user sends a deleteContribution', () => { - it('returns an error', async () => { - 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')], - }), - ) - }) - - it('logs the error found', () => { - expect(logger.error).toBeCalledWith('Can not delete contribution of another user') - }) - }) - - describe('User deletes own contribution', () => { - it('deletes successfully', async () => { - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: result.data.createContribution.id, - }, - }), - ).resolves.toBeTruthy() - }) - - it('stores the delete contribution event in the database', async () => { - const contribution = await mutate({ - mutation: createContribution, - variables: { - amount: 166.0, - memo: 'Whatever contribution', - creationDate: new Date().toString(), - }, - }) - - await mutate({ - mutation: deleteContribution, - variables: { - id: contribution.data.createContribution.id, - }, - }) - - await expect(EventProtocol.find()).resolves.toContainEqual( - expect.objectContaining({ - type: EventProtocolType.CONTRIBUTION_DELETE, - contributionId: contribution.data.createContribution.id, - amount: expect.decimalEqual(166), - userId: peter.id, - }), - ) - }) - }) - - describe('User deletes already confirmed contribution', () => { - it('throws an error', async () => { - jest.clearAllMocks() - await mutate({ - mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, - }) - await mutate({ - mutation: confirmContribution, - variables: { - id: result.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')], - }), - ) - }) - - it('logs the error found', () => { - expect(logger.error).toBeCalledWith('A confirmed contribution can not be deleted') - }) - }) - }) - }) - describe('contributions', () => { const variables = { email: 'bibi@bloxberg.de', @@ -1505,7 +1707,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_' }, @@ -1513,7 +1714,6 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -1614,7 +1814,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_' }, @@ -1622,7 +1821,6 @@ describe('ContributionResolver', () => { }) afterAll(async () => { - await cleanDB() resetToken() }) @@ -1644,6 +1842,7 @@ describe('ContributionResolver', () => { creation = await Contribution.findOneOrFail({ where: { memo: 'Herzlich Willkommen bei Gradido!', + amount: 400, }, }) }) @@ -1651,6 +1850,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), ) @@ -1658,15 +1858,13 @@ describe('ContributionResolver', () => { mutate({ mutation: adminCreateContribution, variables }), ).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('Could not find user with email: bibi@bloxberg.de')], + errors: [new GraphQLError('Could not find user with email: some@fake.email')], }), ) }) it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith( - 'Could not find user with email: bibi@bloxberg.de', - ) + expect(logger.error).toBeCalledWith('Could not find user with email: some@fake.email') }) }) @@ -1730,7 +1928,6 @@ describe('ContributionResolver', () => { describe('valid user to create for', () => { beforeAll(async () => { - await userFactory(testEnv, bibiBloxberg) variables.email = 'bibi@bloxberg.de' variables.creationDate = 'invalid-date' }) @@ -1812,7 +2009,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 (900 GDD) still available for this month.', ), ], }), @@ -1821,7 +2018,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 (900 GDD) still available for this month.', ) }) }) @@ -1834,7 +2031,7 @@ describe('ContributionResolver', () => { ).resolves.toEqual( expect.objectContaining({ data: { - adminCreateContribution: [1000, 1000, 800], + adminCreateContribution: [1000, 1000, 700], }, }), ) @@ -1859,7 +2056,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 (700 GDD) still available for this month.', ), ], }), @@ -1868,7 +2065,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 (700 GDD) still available for this month.', ) }) }) @@ -2013,6 +2210,7 @@ describe('ContributionResolver', () => { describe('user email does not match creation user', () => { it('throws an error', async () => { jest.clearAllMocks() + console.log('creation', creation) await expect( mutate({ mutation: adminUpdateContribution, @@ -2170,7 +2368,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listUnconfirmedContributions: expect.arrayContaining([ - { + expect.objectContaining({ id: expect.any(Number), firstName: 'Peter', lastName: 'Lustig', @@ -2179,9 +2377,9 @@ describe('ContributionResolver', () => { memo: 'Das war leider zu Viel!', amount: '200', moderator: admin.id, - creation: ['1000', '800', '500'], - }, - { + creation: ['1000', '600', '500'], + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Peter', lastName: 'Lustig', @@ -2190,9 +2388,20 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '800', '500'], - }, - { + creation: ['1000', '600', '500'], + }), + expect.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', '200'], + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Bibi', lastName: 'Bloxberg', @@ -2201,9 +2410,9 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '1000', '300'], - }, - { + creation: ['1000', '1000', '200'], + }), + expect.objectContaining({ id: expect.any(Number), firstName: 'Bibi', lastName: 'Bloxberg', @@ -2212,8 +2421,8 @@ describe('ContributionResolver', () => { memo: 'Aktives Grundeinkommen', amount: '200', moderator: admin.id, - creation: ['1000', '1000', '300'], - }, + creation: ['1000', '1000', '200'], + }), ]), }, }), @@ -2245,12 +2454,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, @@ -2266,7 +2476,7 @@ describe('ContributionResolver', () => { mutate({ mutation: adminDeleteContribution, variables: { - id: result.data.createContribution.id, + id: ownContribution.data.createContribution.id, }, }), ).resolves.toEqual( From 0575c513c4f91d0bd200bac620b32edc8d514796 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 08:01:29 +0100 Subject: [PATCH 05/67] End refactoring of ContributionResolver.test. --- .../resolver/ContributionResolver.test.ts | 147 ++++++++++++++---- 1 file changed, 113 insertions(+), 34 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 226ab63cd..a7b6716db 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -118,17 +118,10 @@ describe('ContributionResolver', () => { mutation: createContribution, variables: { amount: 100.0, - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', creationDate: new Date().toString(), }, }) - await mutate({ - mutation: adminCreateContributionMessage, - variables: { - contributionId: inProgressContribution.data.createContribution.id, - message: 'Test message to IN_PROGESS contribution', - }, - }) contributionToConfirm = await mutate({ mutation: createContribution, variables: { @@ -153,6 +146,17 @@ describe('ContributionResolver', () => { 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, }) @@ -770,11 +774,18 @@ describe('ContributionResolver', () => { }) describe('other user sends a deleteContribution', () => { - it('returns an error', async () => { + beforeAll(async () => { await mutate({ mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) + }) + + afterAll(() => { + resetToken() + }) + + it('returns an error', async () => { await expect( mutate({ mutation: deleteContribution, @@ -795,6 +806,17 @@ describe('ContributionResolver', () => { }) describe('User deletes own contribution', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(() => { + resetToken() + }) + it('deletes successfully', async () => { await expect( mutate({ @@ -803,10 +825,21 @@ describe('ContributionResolver', () => { id: contributionToDelete.data.createContribution.id, }, }), - ).resolves.toBeTruthy() + ).resolves.toEqual( + expect.objectContaining({ + data: { + deleteContribution: true, + }, + }), + ) }) it('stores the delete contribution event in the database', async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + const contribution = await mutate({ mutation: createContribution, variables: { @@ -945,7 +978,7 @@ describe('ContributionResolver', () => { }), expect.objectContaining({ id: expect.any(Number), - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -979,11 +1012,36 @@ describe('ContributionResolver', () => { listContributions: { contributionCount: 4, contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + amount: '100', + id: expect.any(Number), + memo: 'Test contribution to confirm', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test contribution to deny', + amount: '100', + }), expect.objectContaining({ id: expect.any(Number), memo: 'Test contribution to delete', amount: '100', }), + expect.objectContaining({ + id: expect.any(Number), + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + expect.not.objectContaining({ + id: expect.any(Number), + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), ]), }, }, @@ -1110,7 +1168,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 6, + contributionCount: 5, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1139,7 +1197,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -1170,7 +1228,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 6, + contributionCount: 5, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1199,7 +1257,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -1230,7 +1288,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 6, + contributionCount: 5, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1259,7 +1317,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -1319,7 +1377,7 @@ describe('ContributionResolver', () => { expect.not.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -1379,7 +1437,7 @@ describe('ContributionResolver', () => { expect.not.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.not.objectContaining({ @@ -1439,7 +1497,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.not.objectContaining({ @@ -1499,7 +1557,7 @@ describe('ContributionResolver', () => { expect.not.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.not.objectContaining({ @@ -1582,7 +1640,7 @@ describe('ContributionResolver', () => { expect.not.objectContaining({ id: expect.any(Number), state: 'IN_PROGRESS', - memo: 'Test IN_PROGESS contribution', + memo: 'Test IN_PROGRESS contribution', amount: '100', }), expect.objectContaining({ @@ -2009,7 +2067,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (2000 GDD) to be created exceeds the amount (900 GDD) still available for this month.', + 'The amount (2000 GDD) to be created exceeds the amount (790 GDD) still available for this month.', ), ], }), @@ -2018,7 +2076,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (2000 GDD) to be created exceeds the amount (900 GDD) still available for this month.', + 'The amount (2000 GDD) to be created exceeds the amount (790 GDD) still available for this month.', ) }) }) @@ -2031,7 +2089,7 @@ describe('ContributionResolver', () => { ).resolves.toEqual( expect.objectContaining({ data: { - adminCreateContribution: [1000, 1000, 700], + adminCreateContribution: [1000, 1000, 590], }, }), ) @@ -2056,7 +2114,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1000 GDD) to be created exceeds the amount (700 GDD) still available for this month.', + 'The amount (1000 GDD) to be created exceeds the amount (590 GDD) still available for this month.', ), ], }), @@ -2065,7 +2123,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (1000 GDD) to be created exceeds the amount (700 GDD) still available for this month.', + 'The amount (1000 GDD) to be created exceeds the amount (590 GDD) still available for this month.', ) }) }) @@ -2210,7 +2268,6 @@ describe('ContributionResolver', () => { describe('user email does not match creation user', () => { it('throws an error', async () => { jest.clearAllMocks() - console.log('creation', creation) await expect( mutate({ mutation: adminUpdateContribution, @@ -2377,7 +2434,7 @@ describe('ContributionResolver', () => { memo: 'Das war leider zu Viel!', amount: '200', moderator: admin.id, - creation: ['1000', '600', '500'], + creation: ['1000', '800', '500'], }), expect.objectContaining({ id: expect.any(Number), @@ -2388,9 +2445,9 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '600', '500'], + creation: ['1000', '800', '500'], }), - expect.objectContaining({ + expect.not.objectContaining({ id: expect.any(Number), firstName: 'Bibi', lastName: 'Bloxberg', @@ -2399,7 +2456,29 @@ describe('ContributionResolver', () => { memo: 'Test contribution to delete', amount: '100', moderator: null, - creation: ['1000', '1000', '200'], + 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), @@ -2410,7 +2489,7 @@ describe('ContributionResolver', () => { memo: 'Grundeinkommen', amount: '500', moderator: admin.id, - creation: ['1000', '1000', '200'], + creation: ['1000', '1000', '90'], }), expect.objectContaining({ id: expect.any(Number), @@ -2421,7 +2500,7 @@ describe('ContributionResolver', () => { memo: 'Aktives Grundeinkommen', amount: '200', moderator: admin.id, - creation: ['1000', '1000', '200'], + creation: ['1000', '1000', '90'], }), ]), }, From b7343fdc3e950d1b5988dd4d2ac2c97f1f45a7c3 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 09:25:44 +0100 Subject: [PATCH 06/67] Add test user raeuber hotzenplotz and deny already confirmed, deleted or denied contributions. --- .../resolver/ContributionResolver.test.ts | 263 +++++++++++++++++- 1 file changed, 257 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index a7b6716db..eaf157edc 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -49,6 +49,7 @@ 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' // mock account activation email to avoid console spam // mock account activation email to avoid console spam @@ -94,11 +95,13 @@ afterAll(async () => { describe('ContributionResolver', () => { let bibi: any + let raueber: any let peter: any beforeAll(async () => { bibi = await userFactory(testEnv, bibiBloxberg) admin = peter = await userFactory(testEnv, peterLustig) + raueber = await userFactory(testEnv, raeuberHotzenplotz) const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await creationFactory(testEnv, bibisCreation!) @@ -697,6 +700,158 @@ describe('ContributionResolver', () => { }) }) + describe('deny contribution that is already confirmed', () => { + let contribution: any + it('throws an error', async () => { + 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: confirmContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + `Contribution not found for given id: ${contribution.data.createContribution.id}`, + ) + }) + }) + + describe('deny contribution that is already deleted', () => { + let contribution: any + + it('throws an error', async () => { + 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: deleteContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + `Contribution not found for given id: ${contribution.data.createContribution.id}`, + ) + }) + }) + + describe('deny contribution that is already denied', () => { + let contribution: any + + it('throws an error', async () => { + 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, + }, + }) + + await expect( + mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('Contribution not found for given id.')], + }), + ) + }) + + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + `Contribution not found for given id: ${contribution.data.createContribution.id}`, + ) + }) + }) + describe('valid input', () => { it('deny contribution', async () => { await mutate({ @@ -1168,7 +1323,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 5, + contributionCount: 7, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1206,6 +1361,18 @@ describe('ContributionResolver', () => { 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', + }), ]), }, }, @@ -1228,7 +1395,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 5, + contributionCount: 7, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1266,6 +1433,18 @@ describe('ContributionResolver', () => { 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', + }), ]), }, }, @@ -1288,7 +1467,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 5, + contributionCount: 7, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1326,6 +1505,18 @@ describe('ContributionResolver', () => { 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', + }), ]), }, }, @@ -1348,7 +1539,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 2, + contributionCount: 3, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1386,6 +1577,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1446,6 +1649,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1506,6 +1721,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, @@ -1528,7 +1755,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 1, + contributionCount: 2, contributionList: expect.arrayContaining([ expect.not.objectContaining({ amount: '100', @@ -1566,6 +1793,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.not.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', + }), ]), }, }, @@ -1611,7 +1850,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { listAllContributions: { - contributionCount: 3, + contributionCount: 4, contributionList: expect.arrayContaining([ expect.objectContaining({ amount: '100', @@ -1649,6 +1888,18 @@ describe('ContributionResolver', () => { memo: 'Herzlich Willkommen bei Gradido!', amount: '1000', }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), ]), }, }, From 46e8a55d2e69cec40c2ff1e6257506b60b26dcd7 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 09:48:20 +0100 Subject: [PATCH 07/67] remove unused variable raeuber. --- backend/src/graphql/resolver/ContributionResolver.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index eaf157edc..7c239e699 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -95,13 +95,12 @@ afterAll(async () => { describe('ContributionResolver', () => { let bibi: any - let raueber: any let peter: any beforeAll(async () => { bibi = await userFactory(testEnv, bibiBloxberg) admin = peter = await userFactory(testEnv, peterLustig) - raueber = await userFactory(testEnv, raeuberHotzenplotz) + await userFactory(testEnv, raeuberHotzenplotz) const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await creationFactory(testEnv, bibisCreation!) From 8c00fcd6ccb8069a7df73053164226b91ad868df Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 14:34:40 +0100 Subject: [PATCH 08/67] Merge new unit tests for ContributionResolver. --- admin/package.json | 5 +- backend/package.json | 3 + .../resolver/ContributionResolver.test.ts | 175 +++++++++--------- .../graphql/resolver/ContributionResolver.ts | 59 +++--- backend/src/seeds/graphql/queries.ts | 53 ++++-- database/package.json | 3 + frontend/package.json | 5 +- 7 files changed, 167 insertions(+), 136 deletions(-) diff --git a/admin/package.json b/admin/package.json index 8270c4da6..30e93239b 100644 --- a/admin/package.json +++ b/admin/package.json @@ -86,5 +86,8 @@ "> 1%", "last 2 versions", "not ie <= 10" - ] + ], + "nodemonConfig": { + "ignore": ["**/*.spec.js"] + } } diff --git a/backend/package.json b/backend/package.json index bfcd61d5b..9a36c2ff8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -72,5 +72,8 @@ "ts-node": "^10.0.0", "tsconfig-paths": "^3.14.0", "typescript": "^4.3.4" + }, + "nodemonConfig": { + "ignore": ["**/*.test.ts"] } } diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 7c239e699..c64837285 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -23,7 +23,7 @@ import { import { listAllContributions, listContributions, - listUnconfirmedContributions, + adminListAllContributions, } from '@/seeds/graphql/queries' import { // sendAccountActivationEmail, @@ -1963,11 +1963,11 @@ describe('ContributionResolver', () => { }) }) - describe('listUnconfirmedContributions', () => { + describe('adminListAllContributions', () => { it('returns an error', async () => { await expect( query({ - query: listUnconfirmedContributions, + query: adminListAllContributions, }), ).resolves.toEqual( expect.objectContaining({ @@ -2071,11 +2071,11 @@ describe('ContributionResolver', () => { }) }) - describe('listUnconfirmedContributions', () => { + describe('adminListAllContributions', () => { it('returns an error', async () => { await expect( query({ - query: listUnconfirmedContributions, + query: adminListAllContributions, }), ).resolves.toEqual( expect.objectContaining({ @@ -2665,94 +2665,97 @@ describe('ContributionResolver', () => { }) }) - describe('listUnconfirmedContributions', () => { + describe('adminListAllContributions', () => { it('returns four pending creations', async () => { await expect( query({ - query: listUnconfirmedContributions, + query: adminListAllContributions, }), ).resolves.toEqual( expect.objectContaining({ data: { - listUnconfirmedContributions: expect.arrayContaining([ - expect.objectContaining({ - 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'], - }), - expect.objectContaining({ - 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'], - }), - 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', - email: 'bibi@bloxberg.de', - date: expect.any(String), - memo: 'Grundeinkommen', - amount: '500', - moderator: admin.id, - creation: ['1000', '1000', '90'], - }), - expect.objectContaining({ - 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', '90'], - }), - ]), + adminListAllContributions: { + contributionCount: 4, + contributionList: expect.arrayContaining([ + expect.objectContaining({ + 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'], + }), + expect.objectContaining({ + 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'], + }), + 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', + email: 'bibi@bloxberg.de', + date: expect.any(String), + memo: 'Grundeinkommen', + amount: '500', + moderator: admin.id, + creation: ['1000', '1000', '90'], + }), + expect.objectContaining({ + 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', '90'], + }), + ]), + }, }, }), ) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index c7946d2c8..6016a2c23 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -477,40 +477,39 @@ export class ContributionResolver { } @Authorized([RIGHTS.LIST_UNCONFIRMED_CONTRIBUTIONS]) - @Query(() => [UnconfirmedContribution]) - async listUnconfirmedContributions(@Ctx() context: Context): Promise { - const clientTimezoneOffset = getClientTimezoneOffset(context) - const contributions = await getConnection() + @Query(() => ContributionListResult) // [UnconfirmedContribution] + async adminListAllContributions( + @Args() + { currentPage = 1, pageSize = 3, order = Order.DESC }: Paginated, + @Arg('statusFilter', () => [ContributionStatus], { nullable: true }) + statusFilter?: ContributionStatus[], + ): Promise { + const where: { + contributionStatus?: FindOperator | null + } = {} + + if (statusFilter && statusFilter.length) { + where.contributionStatus = In(statusFilter) + } + + const [dbContributions, count] = await getConnection() .createQueryBuilder() .select('c') .from(DbContribution, 'c') - .leftJoinAndSelect('c.messages', 'm') - .where({ confirmedAt: IsNull() }) - .andWhere({ deniedAt: IsNull() }) - .getMany() + .innerJoinAndSelect('c.user', 'u') + .where(where) + .withDeleted() + .orderBy('c.createdAt', order) + .limit(pageSize) + .offset((currentPage - 1) * pageSize) + .getManyAndCount() - if (contributions.length === 0) { - return [] - } - - const userIds = contributions.map((p) => p.userId) - const userCreations = await getUserCreations(userIds, clientTimezoneOffset) - const users = await DbUser.find({ - where: { id: In(userIds) }, - withDeleted: true, - relations: ['emailContact'], - }) - - return contributions.map((contribution) => { - const user = users.find((u) => u.id === contribution.userId) - const creation = userCreations.find((c) => c.id === contribution.userId) - - return new UnconfirmedContribution( - contribution, - user, - creation ? creation.creations : FULL_CREATION_AVAILABLE, - ) - }) + console.log('dbContributions', dbContributions) + console.log('count', count) + return new ContributionListResult( + count, + dbContributions.map((contribution) => new Contribution(contribution, contribution.user)), + ) } @Authorized([RIGHTS.ADMIN_DELETE_CONTRIBUTION]) diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 385a69479..423575a16 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -177,6 +177,40 @@ query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusF contributionCount contributionList { id + firstName + lastName + amount + memo + createdAt + confirmedAt + confirmedBy + contributionDate + state + messagesCount + deniedAt + deniedBy + } + } +} +` +// from admin interface + +export const adminListAllContributions = gql` + query ( + $currentPage: Int = 1 + $pageSize: Int = 3 + $order: Order = DESC + $statusFilter: [ContributionStatus!] + ) { + adminListAllContributions( + currentPage: $currentPage + pageSize: $pageSize + order: $order + statusFilter: $statusFilter + ) { + contributionCount + contributionList { + id firstName lastName amount @@ -189,24 +223,7 @@ query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusF messagesCount deniedAt deniedBy - } - } -} -` -// from admin interface - -export const listUnconfirmedContributions = gql` - query { - listUnconfirmedContributions { - id - firstName - lastName - email - amount - memo - date - moderator - creation + } } } ` diff --git a/database/package.json b/database/package.json index f4e1c7e84..dc805da93 100644 --- a/database/package.json +++ b/database/package.json @@ -47,5 +47,8 @@ "ts-mysql-migrate": "^1.0.2", "typeorm": "^0.2.38", "uuid": "^8.3.2" + }, + "nodemonConfig": { + "ignore": ["**/*.test.ts"] } } diff --git a/frontend/package.json b/frontend/package.json index 29c440988..73651327f 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -104,5 +104,8 @@ ], "author": "Gradido-Akademie - https://www.gradido.net/", "license": "Apache-2.0", - "description": "Gradido, the Natural Economy of Life, is a way to worldwide prosperity and peace in harmony with nature. - Gradido, die Natürliche Ökonomie des lebens, ist ein Weg zu weltweitem Wohlstand und Frieden in Harmonie mit der Natur." + "description": "Gradido, the Natural Economy of Life, is a way to worldwide prosperity and peace in harmony with nature. - Gradido, die Natürliche Ökonomie des lebens, ist ein Weg zu weltweitem Wohlstand und Frieden in Harmonie mit der Natur.", + "nodemonConfig": { + "ignore": ["**/*.spec.js"] + } } From ad16aff1eaedc6ea53fef3864fee5a1afa9667a9 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 15:06:58 +0100 Subject: [PATCH 09/67] Change listUnconfirmedContributions to adminListAllContributions. --- .../resolver/ContributionResolver.test.ts | 271 +++++++++++++----- .../graphql/resolver/ContributionResolver.ts | 5 +- backend/src/seeds/graphql/queries.ts | 2 +- 3 files changed, 204 insertions(+), 74 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index c64837285..5ab4e531d 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -2675,84 +2675,217 @@ describe('ContributionResolver', () => { expect.objectContaining({ data: { adminListAllContributions: { - contributionCount: 4, + contributionCount: 14, contributionList: expect.arrayContaining([ expect.objectContaining({ + amount: '500', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Bibi', id: expect.any(Number), - firstName: 'Peter', - lastName: 'Lustig', - email: 'peter@lustig.de', - date: expect.any(String), - memo: 'Das war leider zu Viel!', + lastName: 'Bloxberg', + memo: 'Grundeinkommen', + messagesCount: 0, + state: 'PENDING', + }), + expect.objectContaining({ amount: '200', - moderator: admin.id, - creation: ['1000', '800', '500'], - }), - expect.objectContaining({ - 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'], - }), - expect.not.objectContaining({ - id: expect.any(Number), + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, 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', - email: 'bibi@bloxberg.de', - date: expect.any(String), - memo: 'Grundeinkommen', - amount: '500', - moderator: admin.id, - creation: ['1000', '1000', '90'], - }), - expect.objectContaining({ - id: expect.any(Number), - firstName: 'Bibi', - lastName: 'Bloxberg', - email: 'bibi@bloxberg.de', - date: expect.any(String), memo: 'Aktives Grundeinkommen', + messagesCount: 0, + state: 'PENDING', + }), + expect.objectContaining({ + amount: '500', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: 'Grundeinkommen', + messagesCount: 0, + state: 'PENDING', + }), + expect.objectContaining({ amount: '200', - moderator: admin.id, - creation: ['1000', '1000', '90'], + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: 'Das war leider zu Viel!', + messagesCount: 0, + state: 'PENDING', + }), + expect.objectContaining({ + amount: '166', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Peter', + id: expect.any(Number), + lastName: 'Lustig', + memo: 'Whatever contribution', + messagesCount: 0, + state: 'DELETED', + }), + expect.objectContaining({ + amount: '166', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: expect.any(String), + deniedBy: expect.any(Number), + firstName: 'Räuber', + id: expect.any(Number), + lastName: 'Hotzenplotz', + memo: 'Whatever contribution', + messagesCount: 0, + state: 'DENIED', + }), + expect.objectContaining({ + amount: '166', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Räuber', + id: expect.any(Number), + lastName: 'Hotzenplotz', + memo: 'Whatever contribution', + messagesCount: 0, + state: 'DELETED', + }), + expect.objectContaining({ + amount: '166', + confirmedAt: expect.any(String), + confirmedBy: expect.any(Number), + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Räuber', + id: expect.any(Number), + lastName: 'Hotzenplotz', + memo: 'Whatever contribution', + messagesCount: 0, + state: 'CONFIRMED', + }), + expect.objectContaining({ + amount: '100', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: expect.any(String), + deniedBy: expect.any(Number), + firstName: 'Bibi', + id: expect.any(Number), + lastName: 'Bloxberg', + memo: 'Test contribution to deny', + messagesCount: 0, + state: 'DENIED', + }), + expect.objectContaining({ + amount: '100', + confirmedAt: expect.any(String), + confirmedBy: expect.any(Number), + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Bibi', + id: expect.any(Number), + lastName: 'Bloxberg', + memo: 'Test contribution to confirm', + messagesCount: 0, + state: 'CONFIRMED', + }), + expect.objectContaining({ + amount: '100', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Bibi', + id: expect.any(Number), + lastName: 'Bloxberg', + memo: 'Test IN_PROGRESS contribution', + messagesCount: 0, + state: 'IN_PROGRESS', + }), + expect.objectContaining({ + amount: '10', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Bibi', + id: expect.any(Number), + lastName: 'Bloxberg', + memo: 'Test PENDING contribution update', + messagesCount: 0, + state: 'PENDING', + }), + expect.objectContaining({ + amount: '100', + confirmedAt: null, + confirmedBy: null, + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Bibi', + id: expect.any(Number), + lastName: 'Bloxberg', + memo: 'Test contribution to delete', + messagesCount: 0, + state: 'DELETED', + }), + expect.objectContaining({ + amount: '1000', + confirmedAt: expect.any(String), + confirmedBy: expect.any(Number), + contributionDate: expect.any(String), + createdAt: expect.any(String), + deniedAt: null, + deniedBy: null, + firstName: 'Bibi', + id: expect.any(Number), + lastName: 'Bloxberg', + memo: 'Herzlich Willkommen bei Gradido!', + messagesCount: 0, + state: 'CONFIRMED', }), ]), }, diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 6016a2c23..89973906a 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -30,12 +30,11 @@ import { backendLogger as logger } from '@/server/logger' import { getCreationDates, getUserCreation, - getUserCreations, validateContribution, updateCreations, isValidDateString, } from './util/creations' -import { MEMO_MAX_CHARS, MEMO_MIN_CHARS, FULL_CREATION_AVAILABLE } from './const/const' +import { MEMO_MAX_CHARS, MEMO_MIN_CHARS } from './const/const' import { Event, EventContributionCreate, @@ -504,8 +503,6 @@ export class ContributionResolver { .offset((currentPage - 1) * pageSize) .getManyAndCount() - console.log('dbContributions', dbContributions) - console.log('count', count) return new ContributionListResult( count, dbContributions.map((contribution) => new Contribution(contribution, contribution.user)), diff --git a/backend/src/seeds/graphql/queries.ts b/backend/src/seeds/graphql/queries.ts index 423575a16..400d41490 100644 --- a/backend/src/seeds/graphql/queries.ts +++ b/backend/src/seeds/graphql/queries.ts @@ -198,7 +198,7 @@ query ($currentPage: Int = 1, $pageSize: Int = 5, $order: Order = DESC, $statusF export const adminListAllContributions = gql` query ( $currentPage: Int = 1 - $pageSize: Int = 3 + $pageSize: Int = 25 $order: Order = DESC $statusFilter: [ContributionStatus!] ) { From 6a0ccb75b85888df2ff1a044024a9863c998f561 Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 15:32:12 +0100 Subject: [PATCH 10/67] Remove the ignore on nodemon watch of test files. --- admin/package.json | 5 +---- backend/package.json | 3 --- database/package.json | 3 --- frontend/package.json | 5 +---- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/admin/package.json b/admin/package.json index 30e93239b..8270c4da6 100644 --- a/admin/package.json +++ b/admin/package.json @@ -86,8 +86,5 @@ "> 1%", "last 2 versions", "not ie <= 10" - ], - "nodemonConfig": { - "ignore": ["**/*.spec.js"] - } + ] } diff --git a/backend/package.json b/backend/package.json index 9a36c2ff8..bfcd61d5b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -72,8 +72,5 @@ "ts-node": "^10.0.0", "tsconfig-paths": "^3.14.0", "typescript": "^4.3.4" - }, - "nodemonConfig": { - "ignore": ["**/*.test.ts"] } } diff --git a/database/package.json b/database/package.json index dc805da93..f4e1c7e84 100644 --- a/database/package.json +++ b/database/package.json @@ -47,8 +47,5 @@ "ts-mysql-migrate": "^1.0.2", "typeorm": "^0.2.38", "uuid": "^8.3.2" - }, - "nodemonConfig": { - "ignore": ["**/*.test.ts"] } } diff --git a/frontend/package.json b/frontend/package.json index 73651327f..29c440988 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -104,8 +104,5 @@ ], "author": "Gradido-Akademie - https://www.gradido.net/", "license": "Apache-2.0", - "description": "Gradido, the Natural Economy of Life, is a way to worldwide prosperity and peace in harmony with nature. - Gradido, die Natürliche Ökonomie des lebens, ist ein Weg zu weltweitem Wohlstand und Frieden in Harmonie mit der Natur.", - "nodemonConfig": { - "ignore": ["**/*.spec.js"] - } + "description": "Gradido, the Natural Economy of Life, is a way to worldwide prosperity and peace in harmony with nature. - Gradido, die Natürliche Ökonomie des lebens, ist ein Weg zu weltweitem Wohlstand und Frieden in Harmonie mit der Natur." } From 8eaacc669931be7dcf2debd76a16607da64b5bf2 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 7 Feb 2023 12:50:29 +0100 Subject: [PATCH 11/67] implement LogError on creations and fix all corressponding tests --- .../resolver/ContributionLinkResolver.test.ts | 18 ++++------ .../resolver/ContributionResolver.test.ts | 35 ++++++++++--------- .../resolver/TransactionLinkResolver.test.ts | 7 +++- .../src/graphql/resolver/util/creations.ts | 25 +++++-------- 4 files changed, 40 insertions(+), 45 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionLinkResolver.test.ts b/backend/src/graphql/resolver/ContributionLinkResolver.test.ts index 46296e009..2a17f0556 100644 --- a/backend/src/graphql/resolver/ContributionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionLinkResolver.test.ts @@ -257,17 +257,13 @@ describe('Contribution Links', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [ - new GraphQLError('Start-Date is not initialized. A Start-Date must be set!'), - ], + errors: [new GraphQLError('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!', - ) + expect(logger.error).toBeCalledWith('A Start-Date must be set') }) it('returns an error if missing endDate', async () => { @@ -282,15 +278,13 @@ describe('Contribution Links', () => { }), ).resolves.toEqual( expect.objectContaining({ - errors: [new GraphQLError('End-Date is not initialized. An End-Date must be set!')], + errors: [new GraphQLError('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!', - ) + expect(logger.error).toBeCalledWith('An End-Date must be set') }) it('returns an error if endDate is before startDate', async () => { @@ -307,7 +301,7 @@ describe('Contribution Links', () => { ).resolves.toEqual( expect.objectContaining({ errors: [ - new GraphQLError(`The value of validFrom must before or equals the validTo!`), + new GraphQLError(`The value of validFrom must before or equals the validTo`), ], }), ) @@ -315,7 +309,7 @@ describe('Contribution Links', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - `The value of validFrom must before or equals the validTo!`, + `The value of validFrom must before or equals the validTo`, ) }) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 1e0930d91..7f0231860 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -187,8 +187,8 @@ describe('ContributionResolver', () => { it('logs the error found', () => { expect(logger.error).toBeCalledWith( - 'No information for available creations with the given creationDate=', - 'Invalid Date', + 'No information for available creations for the given date', + expect.any(Date), ) }) @@ -215,8 +215,8 @@ describe('ContributionResolver', () => { it('logs the error found', () => { expect(logger.error).toBeCalledWith( - 'No information for available creations with the given creationDate=', - 'Invalid Date', + 'No information for available creations for the given date', + expect.any(Date), ) }) }) @@ -637,7 +637,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1019 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount to be created exceeds the amount still available for this month', ), ], }), @@ -646,7 +646,9 @@ describe('ContributionResolver', () => { 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 to be created exceeds the amount still available for this month', + new Decimal(1019), + new Decimal(1000), ) }) }) @@ -1717,8 +1719,8 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'No information for available creations with the given creationDate=', - new Date(variables.creationDate).toString(), + 'No information for available creations for the given date', + new Date(variables.creationDate), ) }) }) @@ -1742,8 +1744,8 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'No information for available creations with the given creationDate=', - new Date(variables.creationDate).toString(), + 'No information for available creations for the given date', + new Date(variables.creationDate), ) }) }) @@ -1758,7 +1760,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 to be created exceeds the amount still available for this month', ), ], }), @@ -1767,7 +1769,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 to be created exceeds the amount still available for this month', new Decimal(2000), new Decimal(1000) ) }) }) @@ -1798,6 +1800,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 }), @@ -1805,7 +1808,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 to be created exceeds the amount still available for this month', ), ], }), @@ -1814,7 +1817,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 to be created exceeds the amount still available for this month', new Decimal(1000), new Decimal(800) ) }) }) @@ -2007,7 +2010,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ errors: [ new GraphQLError( - 'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount to be created exceeds the amount still available for this month', ), ], }), @@ -2016,7 +2019,7 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.', + 'The amount to be created exceeds the amount still available for this month', new Decimal(1900), new Decimal(1000) ) }) }) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index af2e4fd59..0666efc8e 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -354,10 +354,15 @@ describe('TransactionLinkResolver', () => { }) it('logs the error thrown', () => { + /* expect(logger.error).toBeCalledWith( + 'The amount to be created exceeds the amount still available for this month', + new Decimal(5), + new Decimal(0), + ) */ expect(logger.error).toBeCalledWith( 'Creation from contribution link was not successful', new Error( - 'The amount (5 GDD) to be created exceeds the amount (0 GDD) still available for this month.', + 'The amount to be created exceeds the amount still available for this month', ), ) }) diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 00137eaa1..6a47915b1 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -1,3 +1,4 @@ +import LogError from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { getConnection } from '@dbTools/typeorm' import { Contribution } from '@entity/Contribution' @@ -19,19 +20,14 @@ export const validateContribution = ( const index = getCreationIndex(creationDate.getMonth(), timezoneOffset) if (index < 0) { - logger.error( - 'No information for available creations with the given creationDate=', - creationDate.toString(), - ) - throw new Error('No information for available creations for the given date') + throw new LogError('No information for available creations for the given date', creationDate) } if (amount.greaterThan(creations[index].toString())) { - logger.error( - `The amount (${amount} GDD) to be created exceeds the amount (${creations[index]} GDD) still available for this month.`, - ) - throw new Error( - `The amount (${amount} GDD) to be created exceeds the amount (${creations[index]} GDD) still available for this month.`, + throw new LogError( + 'The amount to be created exceeds the amount still available for this month', + amount, + creations[index], ) } } @@ -126,19 +122,16 @@ export const isStartEndDateValid = ( endDate: string | null | undefined, ): void => { if (!startDate) { - logger.error('Start-Date is not initialized. A Start-Date must be set!') - throw new Error('Start-Date is not initialized. A Start-Date must be set!') + throw new LogError('A Start-Date must be set') } if (!endDate) { - logger.error('End-Date is not initialized. An End-Date must be set!') - throw new Error('End-Date is not initialized. An End-Date must be set!') + throw new LogError('An End-Date must be set') } // check if endDate is before startDate if (new Date(endDate).getTime() - new Date(startDate).getTime() < 0) { - logger.error(`The value of validFrom must before or equals the validTo!`) - throw new Error(`The value of validFrom must before or equals the validTo!`) + throw new LogError(`The value of validFrom must before or equals the validTo`) } } From ea1f637bb29d9f32c680a477d4268c180bb3e037 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 7 Feb 2023 16:48:08 +0100 Subject: [PATCH 12/67] linting --- .../graphql/resolver/ContributionResolver.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 7f0231860..93d7d36d0 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -1769,7 +1769,9 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount to be created exceeds the amount still available for this month', new Decimal(2000), new Decimal(1000) + 'The amount to be created exceeds the amount still available for this month', + new Decimal(2000), + new Decimal(1000), ) }) }) @@ -1817,7 +1819,9 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount to be created exceeds the amount still available for this month', new Decimal(1000), new Decimal(800) + 'The amount to be created exceeds the amount still available for this month', + new Decimal(1000), + new Decimal(800), ) }) }) @@ -2019,7 +2023,9 @@ describe('ContributionResolver', () => { it('logs the error thrown', () => { expect(logger.error).toBeCalledWith( - 'The amount to be created exceeds the amount still available for this month', new Decimal(1900), new Decimal(1000) + 'The amount to be created exceeds the amount still available for this month', + new Decimal(1900), + new Decimal(1000), ) }) }) From d8c8d400d4001ce1193922514b0525b456f4f3f5 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 7 Feb 2023 16:55:04 +0100 Subject: [PATCH 13/67] implement LogError in EncryptorUtils --- backend/src/password/EncryptorUtils.ts | 27 ++++++++++---------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/backend/src/password/EncryptorUtils.ts b/backend/src/password/EncryptorUtils.ts index 971b6a32e..d03f5d169 100644 --- a/backend/src/password/EncryptorUtils.ts +++ b/backend/src/password/EncryptorUtils.ts @@ -1,4 +1,5 @@ import CONFIG from '@/config' +import LogError from '@/server/LogError' import { backendLogger as logger } from '@/server/logger' import { User } from '@entity/User' import { PasswordEncryptionType } from '@enum/PasswordEncryptionType' @@ -16,11 +17,10 @@ export const SecretKeyCryptographyCreateKey = (salt: string, password: string): const configLoginAppSecret = Buffer.from(CONFIG.LOGIN_APP_SECRET, 'hex') const configLoginServerKey = Buffer.from(CONFIG.LOGIN_SERVER_KEY, 'hex') if (configLoginServerKey.length !== sodium.crypto_shorthash_KEYBYTES) { - logger.error( - `ServerKey has an invalid size. The size must be ${sodium.crypto_shorthash_KEYBYTES} bytes.`, - ) - throw new Error( - `ServerKey has an invalid size. The size must be ${sodium.crypto_shorthash_KEYBYTES} bytes.`, + throw new LogError( + 'ServerKey has an invalid size', + configLoginServerKey.length, + sodium.crypto_shorthash_KEYBYTES, ) } @@ -52,20 +52,13 @@ export const SecretKeyCryptographyCreateKey = (salt: string, password: string): export const getUserCryptographicSalt = (dbUser: User): string => { switch (dbUser.passwordEncryptionType) { - case PasswordEncryptionType.NO_PASSWORD: { - logger.error('Password not set for user ' + dbUser.id) - throw new Error('Password not set for user ' + dbUser.id) // user has no password - } - case PasswordEncryptionType.EMAIL: { + case PasswordEncryptionType.NO_PASSWORD: + throw new LogError('User has no password set', dbUser.id) + case PasswordEncryptionType.EMAIL: return dbUser.emailContact.email - break - } - case PasswordEncryptionType.GRADIDO_ID: { + case PasswordEncryptionType.GRADIDO_ID: return dbUser.gradidoID - break - } default: - logger.error(`Unknown password encryption type: ${dbUser.passwordEncryptionType}`) - throw new Error(`Unknown password encryption type: ${dbUser.passwordEncryptionType}`) + throw new LogError('Unknown password encryption type', dbUser.passwordEncryptionType) } } From 3563aef346a8863f13921dce0c5a2b4c99d1aec7 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 7 Feb 2023 17:14:44 +0100 Subject: [PATCH 14/67] use LogError instead of Error where applicable --- backend/src/apis/KlicktippController.ts | 2 +- backend/src/auth/JWT.ts | 2 +- backend/src/graphql/directive/isAuthorized.ts | 9 +++---- .../resolver/ContributionMessageResolver.ts | 8 +++++-- backend/src/graphql/resolver/GdtResolver.ts | 9 +++---- .../resolver/TransactionLinkResolver.ts | 24 +++++++++++-------- .../src/graphql/resolver/util/creations.ts | 2 +- backend/src/server/context.ts | 5 ++-- backend/src/util/decay.ts | 2 +- backend/src/util/klicktipp.ts | 2 +- 10 files changed, 38 insertions(+), 27 deletions(-) diff --git a/backend/src/apis/KlicktippController.ts b/backend/src/apis/KlicktippController.ts index 824d40af2..ca64f4b2e 100644 --- a/backend/src/apis/KlicktippController.ts +++ b/backend/src/apis/KlicktippController.ts @@ -31,7 +31,7 @@ export const unsubscribe = async (email: string): Promise => { if (isLogin) { return await klicktippConnector.unsubscribe(email) } - throw new Error(`Could not unsubscribe ${email}`) + throw new LogError('Could not unsubscribe', email) } export const getKlickTippUser = async (email: string): Promise => { diff --git a/backend/src/auth/JWT.ts b/backend/src/auth/JWT.ts index 8399c881b..301d2ddad 100644 --- a/backend/src/auth/JWT.ts +++ b/backend/src/auth/JWT.ts @@ -3,7 +3,7 @@ import CONFIG from '@/config/' import { CustomJwtPayload } from './CustomJwtPayload' export const decode = (token: string): CustomJwtPayload | null => { - if (!token) throw new Error('401 Unauthorized') + if (!token) throw new LogError('401 Unauthorized') try { return jwt.verify(token, CONFIG.JWT_SECRET) } catch (err) { diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 2843225ae..59daa89f1 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -7,6 +7,7 @@ import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES' import { RIGHTS } from '@/auth/RIGHTS' import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS' import { User } from '@entity/User' +import LogError from '@/server/LogError' const isAuthorized: AuthChecker = async ({ context }, rights) => { context.role = ROLE_UNAUTHORIZED // unauthorized user @@ -17,13 +18,13 @@ const isAuthorized: AuthChecker = async ({ context }, rights) => { // Do we have a token? if (!context.token) { - throw new Error('401 Unauthorized') + throw new LogError('401 Unauthorized') } // Decode the token const decoded = decode(context.token) if (!decoded) { - throw new Error('403.13 - Client certificate revoked') + throw new LogError('403.13 - Client certificate revoked') } // Set context gradidoID context.gradidoID = decoded.gradidoID @@ -39,13 +40,13 @@ const isAuthorized: AuthChecker = async ({ context }, rights) => { context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER } catch { // in case the database query fails (user deleted) - throw new Error('401 Unauthorized') + throw new LogError('401 Unauthorized') } // check for correct rights const missingRights = (rights).filter((right) => !context.role.hasRight(right)) if (missingRights.length !== 0) { - throw new Error('401 Unauthorized') + throw new LogError('401 Unauthorized') } // set new header token diff --git a/backend/src/graphql/resolver/ContributionMessageResolver.ts b/backend/src/graphql/resolver/ContributionMessageResolver.ts index 3e6f86e53..fe6d0dd7e 100644 --- a/backend/src/graphql/resolver/ContributionMessageResolver.ts +++ b/backend/src/graphql/resolver/ContributionMessageResolver.ts @@ -33,10 +33,14 @@ export class ContributionMessageResolver { try { const contribution = await DbContribution.findOne({ id: contributionId }) if (!contribution) { - throw new Error('Contribution not found') + throw new LogError('Contribution not found', contributionId) } if (contribution.userId !== user.id) { - throw new Error('Can not send message to contribution of another user') + throw new LogError( + 'Can not send message to contribution of another user', + contribution.userId, + user.id, + ) } contributionMessage.contributionId = contributionId diff --git a/backend/src/graphql/resolver/GdtResolver.ts b/backend/src/graphql/resolver/GdtResolver.ts index 6f9691cd9..1745e7bbd 100644 --- a/backend/src/graphql/resolver/GdtResolver.ts +++ b/backend/src/graphql/resolver/GdtResolver.ts @@ -8,6 +8,7 @@ import { Context, getUser } from '@/server/context' import CONFIG from '@/config' import { apiGet, apiPost } from '@/apis/HttpRequest' import { RIGHTS } from '@/auth/RIGHTS' +import LogError from '@/server/LogError' @Resolver() export class GdtResolver { @@ -25,11 +26,11 @@ export class GdtResolver { `${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`, ) if (!resultGDT.success) { - throw new Error(resultGDT.data) + throw new LogError(resultGDT.data) } return new GdtEntryList(resultGDT.data) } catch (err) { - throw new Error('GDT Server is not reachable.') + throw new LogError('GDT Server is not reachable') } } @@ -42,7 +43,7 @@ export class GdtResolver { email: user.emailContact.email, }) if (!resultGDTSum.success) { - throw new Error('Call not successful') + throw new LogError('Call not successful') } return Number(resultGDTSum.data.sum) || 0 } catch (err) { @@ -59,7 +60,7 @@ export class GdtResolver { // load user const resultPID = await apiGet(`${CONFIG.GDT_API_URL}/publishers/checkPidApi/${pid}`) if (!resultPID.success) { - throw new Error(resultPID.data) + throw new LogError(resultPID.data) } return resultPID.data.pid } diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 696c51d97..5ec18112c 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -84,8 +84,8 @@ export class TransactionLinkResolver { transactionLink.code = transactionLinkCode(createdDate) transactionLink.createdAt = createdDate transactionLink.validUntil = validUntil - await DbTransactionLink.save(transactionLink).catch(() => { - throw new Error('Unable to save transaction link') + await DbTransactionLink.save(transactionLink).catch((e) => { + throw new LogError('Unable to save transaction link', e) }) return new TransactionLink(transactionLink, new User(user)) @@ -101,19 +101,23 @@ export class TransactionLinkResolver { const transactionLink = await DbTransactionLink.findOne({ id }) if (!transactionLink) { - throw new Error('Transaction Link not found!') + throw new LogError('Transaction link not found', id) } if (transactionLink.userId !== user.id) { - throw new Error('Transaction Link cannot be deleted!') + throw new LogError( + 'Transaction link cannot be deleted by another user', + transactionLink.userId, + user.id, + ) } if (transactionLink.redeemedBy) { - throw new Error('Transaction Link already redeemed!') + throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy) } - await transactionLink.softRemove().catch(() => { - throw new Error('Transaction Link could not be deleted!') + await transactionLink.softRemove().catch((e) => { + throw new LogError('Transaction link could not be deleted', e) }) return true @@ -316,18 +320,18 @@ export class TransactionLinkResolver { ) if (user.id === linkedUser.id) { - throw new Error('Cannot redeem own transaction link.') + throw new LogError('Cannot redeem own transaction link', user.id) } // TODO: The now check should be done within the semaphore lock, // since the program might wait a while till it is ready to proceed // writing the transaction. if (transactionLink.validUntil.getTime() < now.getTime()) { - throw new Error('Transaction Link is not valid anymore.') + throw new LogError('Transaction link is not valid anymore', transactionLink.validUntil) } if (transactionLink.redeemedBy) { - throw new Error('Transaction Link already redeemed.') + throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy) } await executeTransaction( diff --git a/backend/src/graphql/resolver/util/creations.ts b/backend/src/graphql/resolver/util/creations.ts index 6a47915b1..b9ba2e69f 100644 --- a/backend/src/graphql/resolver/util/creations.ts +++ b/backend/src/graphql/resolver/util/creations.ts @@ -143,7 +143,7 @@ export const updateCreations = ( const index = getCreationIndex(contribution.contributionDate.getMonth(), timezoneOffset) if (index < 0) { - throw new Error('You cannot create GDD for a month older than the last three months.') + throw new LogError('You cannot create GDD for a month older than the last three months') } creations[index] = creations[index].plus(contribution.amount.toString()) return creations diff --git a/backend/src/server/context.ts b/backend/src/server/context.ts index 8ba590dd3..32a765777 100644 --- a/backend/src/server/context.ts +++ b/backend/src/server/context.ts @@ -3,6 +3,7 @@ import { User as dbUser } from '@entity/User' import { Transaction as dbTransaction } from '@entity/Transaction' import Decimal from 'decimal.js-light' import { ExpressContext } from 'apollo-server-express' +import LogError from './LogError' export interface Context { token: string | null @@ -35,7 +36,7 @@ const context = (args: ExpressContext): Context => { export const getUser = (context: Context): dbUser => { if (context.user) return context.user - throw new Error('No user given in context!') + throw new LogError('No user given in context') } export const getClientTimezoneOffset = (context: Context): number => { @@ -45,7 +46,7 @@ export const getClientTimezoneOffset = (context: Context): number => { ) { return context.clientTimezoneOffset } - throw new Error('No valid client time zone offset in context!') + throw new LogError('No valid client time zone offset in context') } export default context diff --git a/backend/src/util/decay.ts b/backend/src/util/decay.ts index 48674dc50..4c09d62a5 100644 --- a/backend/src/util/decay.ts +++ b/backend/src/util/decay.ts @@ -22,7 +22,7 @@ function calculateDecay( const startBlockMs = startBlock.getTime() if (toMs < fromMs) { - throw new Error('to < from, reverse decay calculation is invalid') + throw new LogError('calculateDecay: to < from, reverse decay calculation is invalid') } // Initialize with no decay diff --git a/backend/src/util/klicktipp.ts b/backend/src/util/klicktipp.ts index 0432f196e..7dfc2c98e 100644 --- a/backend/src/util/klicktipp.ts +++ b/backend/src/util/klicktipp.ts @@ -5,7 +5,7 @@ import { User } from '@entity/User' export async function retrieveNotRegisteredEmails(): Promise { const con = await connection() if (!con) { - throw new Error('No connection to database') + throw new LogError('No connection to database') } const users = await User.find({ relations: ['emailContact'] }) const notRegisteredUser = [] From 868566f716e423e88a6000f2daf3fec70afbd5c0 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 7 Feb 2023 17:25:27 +0100 Subject: [PATCH 15/67] missing changes --- backend/src/apis/KlicktippController.ts | 2 +- backend/src/auth/JWT.ts | 1 + backend/src/util/decay.ts | 1 + backend/src/util/klicktipp.ts | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/apis/KlicktippController.ts b/backend/src/apis/KlicktippController.ts index ca64f4b2e..824d40af2 100644 --- a/backend/src/apis/KlicktippController.ts +++ b/backend/src/apis/KlicktippController.ts @@ -31,7 +31,7 @@ export const unsubscribe = async (email: string): Promise => { if (isLogin) { return await klicktippConnector.unsubscribe(email) } - throw new LogError('Could not unsubscribe', email) + throw new Error(`Could not unsubscribe ${email}`) } export const getKlickTippUser = async (email: string): Promise => { diff --git a/backend/src/auth/JWT.ts b/backend/src/auth/JWT.ts index 301d2ddad..3f9c052f5 100644 --- a/backend/src/auth/JWT.ts +++ b/backend/src/auth/JWT.ts @@ -1,6 +1,7 @@ import jwt from 'jsonwebtoken' import CONFIG from '@/config/' import { CustomJwtPayload } from './CustomJwtPayload' +import LogError from '@/server/LogError' export const decode = (token: string): CustomJwtPayload | null => { if (!token) throw new LogError('401 Unauthorized') diff --git a/backend/src/util/decay.ts b/backend/src/util/decay.ts index 4c09d62a5..641654756 100644 --- a/backend/src/util/decay.ts +++ b/backend/src/util/decay.ts @@ -1,6 +1,7 @@ import Decimal from 'decimal.js-light' import CONFIG from '@/config' import { Decay } from '@model/Decay' +import LogError from '@/server/LogError' // TODO: externalize all those definitions and functions into an external decay library diff --git a/backend/src/util/klicktipp.ts b/backend/src/util/klicktipp.ts index 7dfc2c98e..02bdd853b 100644 --- a/backend/src/util/klicktipp.ts +++ b/backend/src/util/klicktipp.ts @@ -1,6 +1,7 @@ import connection from '@/typeorm/connection' import { getKlickTippUser } from '@/apis/KlicktippController' import { User } from '@entity/User' +import LogError from '@/server/LogError' export async function retrieveNotRegisteredEmails(): Promise { const con = await connection() From 0d0d36135840f013ccbd362c418484ed6f7804d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Tue, 7 Feb 2023 23:34:11 +0100 Subject: [PATCH 16/67] docker and workflow files --- .github/workflows/test_federation.yml | 98 ++++++++++++++++++++++ docker-compose.override.yml | 26 ++++++ docker-compose.test.yml | 15 ++++ docker-compose.yml | 36 ++++++++ federation/Dockerfile | 115 ++++++++++++++++++++++++++ 5 files changed, 290 insertions(+) create mode 100644 .github/workflows/test_federation.yml create mode 100644 federation/Dockerfile diff --git a/.github/workflows/test_federation.yml b/.github/workflows/test_federation.yml new file mode 100644 index 000000000..240e6313b --- /dev/null +++ b/.github/workflows/test_federation.yml @@ -0,0 +1,98 @@ +name: gradido test_federation CI + +on: push + +jobs: + ############################################################################## + # JOB: DOCKER BUILD TEST ##################################################### + ############################################################################## + build: + name: Docker Build Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Build `test` image + run: | + docker build --target test -t "gradido/federation:test" -f federation/Dockerfile . + docker save "gradido/federation:test" > /tmp/federation.tar + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: docker-federation-test + path: /tmp/federation.tar + + ############################################################################## + # JOB: LINT ################################################################## + ############################################################################## + lint: + name: Lint + runs-on: ubuntu-latest + needs: [build] + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Download Docker Image + uses: actions/download-artifact@v3 + with: + name: docker-federation-test + path: /tmp + - name: Load Docker Image + run: docker load < /tmp/federation.tar + + - name: Lint + run: docker run --rm gradido/federation:test yarn run lint + + ############################################################################## + # JOB: UNIT TEST ############################################################# + ############################################################################## + unit_test: + name: Unit tests + runs-on: ubuntu-latest + needs: [build] + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Download Docker Image + uses: actions/download-artifact@v3 + with: + name: docker-federation-test + path: /tmp + + - name: Load Docker Image + run: docker load < /tmp/federation.tar + + - name: docker-compose mariadb + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mariadb + + - name: Sleep for 30 seconds + run: sleep 30s + shell: bash + + - name: docker-compose database + run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database + + - name: Sleep for 30 seconds + run: sleep 30s + shell: bash + + #- name: Unit tests + # run: cd database && yarn && yarn build && cd ../dht-node && yarn && yarn test + - name: Unit tests + run: | + docker run --env NODE_ENV=test --env DB_HOST=mariadb --network gradido_internal-net -v ~/coverage:/app/coverage --rm gradido/federation:test yarn run test + cp -r ~/coverage ./coverage + + - name: Coverage check + uses: webcraftmedia/coverage-check-action@master + with: + report_name: Coverage federation + type: lcov + #result_path: ./federation/coverage/lcov.info + result_path: ./coverage/lcov.info + min_coverage: 79 + token: ${{ github.token }} diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 09fa92ca2..79edda70c 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -84,6 +84,29 @@ services: - ./dht-node:/app - ./database:/database + ######################################################## + # FEDERATION ########################################### + ######################################################## + federation: + # name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there + image: gradido/federation:local-development + build: + target: development + networks: + - external-net + - internal-net + environment: + - NODE_ENV="development" + volumes: + # This makes sure the docker container has its own node modules. + # Therefore it is possible to have a different node version on the host machine + - federation_node_modules:/app/node_modules + - federation_database_node_modules:/database/node_modules + - federation_database_build:/database/build + # bind the local folder to the docker to allow live reload + - ./federation:/app + - ./database:/database + ######################################################## # DATABASE ############################################## ######################################################## @@ -155,5 +178,8 @@ volumes: dht_node_modules: dht_database_node_modules: dht_database_build: + federation_node_modules: + federation_database_node_modules: + federation_database_build: database_node_modules: database_build: \ No newline at end of file diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 84647ef03..16240a1b4 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -36,6 +36,21 @@ services: - NODE_ENV="test" - DB_HOST=mariadb + ######################################################## + # FEDERATION ########################################### + ######################################################## + federation: + # name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there + image: gradido/federation:test + build: + target: test + networks: + - external-net + - internal-net + environment: + - NODE_ENV="test" + - DB_HOST=mariadb + ######################################################## # DATABASE ############################################# ######################################################## diff --git a/docker-compose.yml b/docker-compose.yml index fb15c13d6..4105ef04a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -147,6 +147,42 @@ services: # : – mirror bidirectional path in local context with path in Docker container - ./logs/dht-node:/logs/dht-node + ######################################################## + # FEDERATION ########################################### + ######################################################## + federation: + # name the image so that it cannot be found in a DockerHub repository, otherwise it will not be built locally from the 'dockerfile' but pulled from there + image: gradido/federation:local-production + build: + # since we have to include the entities from ./database we cannot define the context as ./federation + # this might blow build image size to the moon ?! + context: ./ + dockerfile: ./federation/Dockerfile + target: production + networks: + - internal-net + - external-net + ports: + - 5000:5000 + depends_on: + - mariadb + restart: always + environment: + # Envs used in Dockerfile + # - DOCKER_WORKDIR="/app" + - PORT=5000 + - BUILD_DATE + - BUILD_VERSION + - BUILD_COMMIT + - NODE_ENV="production" + - DB_HOST=mariadb + # Application only envs + #env_file: + # - ./frontend/.env + volumes: + # : – mirror bidirectional path in local context with path in Docker container + - ./logs/federation:/logs/federation + ######################################################## # DATABASE ############################################# ######################################################## diff --git a/federation/Dockerfile b/federation/Dockerfile new file mode 100644 index 000000000..e2d74c817 --- /dev/null +++ b/federation/Dockerfile @@ -0,0 +1,115 @@ +################################################################################## +# BASE ########################################################################### +################################################################################## +FROM node:18.7.0-alpine3.16 as base + +# ENVs (available in production aswell, can be overwritten by commandline or env file) +## DOCKER_WORKDIR would be a classical ARG, but that is not multi layer persistent - shame +ENV DOCKER_WORKDIR="/app" +## We Cannot do `$(date -u +'%Y-%m-%dT%H:%M:%SZ')` here so we use unix timestamp=0 +ENV BUILD_DATE="1970-01-01T00:00:00.00Z" +## We cannot do $(npm run version).${BUILD_NUMBER} here so we default to 0.0.0.0 +ENV BUILD_VERSION="0.0.0.0" +## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000 +ENV BUILD_COMMIT="0000000" +## SET NODE_ENV +ENV NODE_ENV="production" +## App relevant Envs +ENV PORT="5000" + +# Labels +LABEL org.label-schema.build-date="${BUILD_DATE}" +LABEL org.label-schema.name="gradido:federation" +LABEL org.label-schema.description="Gradido GraphQL Federation" +LABEL org.label-schema.usage="https://github.com/gradido/gradido/blob/master/README.md" +LABEL org.label-schema.url="https://gradido.net" +LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido/tree/master/federation" +LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}" +LABEL org.label-schema.vendor="Gradido Community" +LABEL org.label-schema.version="${BUILD_VERSION}" +LABEL org.label-schema.schema-version="1.0" +LABEL maintainer="support@gradido.net" + +# Install Additional Software +## install: git +#RUN apk --no-cache add git + +# Settings +## Expose Container Port +EXPOSE ${PORT} + +## Workdir +RUN mkdir -p ${DOCKER_WORKDIR} +WORKDIR ${DOCKER_WORKDIR} + +RUN mkdir -p /database + +################################################################################## +# DEVELOPMENT (Connected to the local environment, to reload on demand) ########## +################################################################################## +FROM base as development + +# We don't need to copy or build anything since we gonna bind to the +# local filesystem which will need a rebuild anyway + +# Run command +# (for development we need to execute yarn install since the +# node_modules are on another volume and need updating) +CMD /bin/sh -c "cd /database && yarn install && yarn build && cd /app && yarn install && yarn run dev" + +################################################################################## +# BUILD (Does contain all files and is therefore bloated) ######################## +################################################################################## +FROM base as build + +# Copy everything from federation +COPY ./federation/ ./ +# Copy everything from database +COPY ./database/ ../database/ + +# yarn install federation +RUN yarn install --production=false --frozen-lockfile --non-interactive + +# yarn install database +RUN cd ../database && yarn install --production=false --frozen-lockfile --non-interactive + +# yarn build +RUN yarn run build + +# yarn build database +RUN cd ../database && yarn run build + +################################################################################## +# TEST ########################################################################### +################################################################################## +FROM build as test + +# Run command +CMD /bin/sh -c "yarn run start" + +################################################################################## +# PRODUCTION (Does contain only "binary"- and static-files to reduce image size) # +################################################################################## +FROM base as production + +# Copy "binary"-files from build image +COPY --from=build ${DOCKER_WORKDIR}/build ./build +COPY --from=build ${DOCKER_WORKDIR}/../database/build ../database/build +# We also copy the node_modules express and serve-static for the run script +COPY --from=build ${DOCKER_WORKDIR}/node_modules ./node_modules +COPY --from=build ${DOCKER_WORKDIR}/../database/node_modules ../database/node_modules + +# Copy static files +# COPY --from=build ${DOCKER_WORKDIR}/public ./public +# Copy package.json for script definitions (lock file should not be needed) +COPY --from=build ${DOCKER_WORKDIR}/package.json ./package.json +# Copy tsconfig.json to provide alias path definitions +COPY --from=build ${DOCKER_WORKDIR}/tsconfig.json ./tsconfig.json +# Copy log4js-config.json to provide log configuration +COPY --from=build ${DOCKER_WORKDIR}/log4js-config.json ./log4js-config.json + +# Copy run scripts run/ +# COPY --from=build ${DOCKER_WORKDIR}/run ./run + +# Run command +CMD /bin/sh -c "yarn run start" \ No newline at end of file From 42e2ac4877fa3a9f4343fd911be8ec8e10297802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Tue, 7 Feb 2023 23:47:22 +0100 Subject: [PATCH 17/67] linting --- federation/src/graphql/api/1_0/resolver/Test2Resolver.ts | 2 ++ federation/src/graphql/api/1_0/resolver/TestResolver.ts | 4 +++- federation/src/graphql/api/1_1/resolver/TestResolver.ts | 4 +++- federation/src/graphql/api/2_0/resolver/TestResolver.ts | 4 +++- federation/src/graphql/api/GetTestApiResult.ts | 2 ++ 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/Test2Resolver.ts b/federation/src/graphql/api/1_0/resolver/Test2Resolver.ts index 3388003b8..09797c6db 100644 --- a/federation/src/graphql/api/1_0/resolver/Test2Resolver.ts +++ b/federation/src/graphql/api/1_0/resolver/Test2Resolver.ts @@ -1,8 +1,10 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { Query, Resolver } from 'type-graphql' import { federationLogger as logger } from '@/server/logger' import { GetTestApiResult } from '../../GetTestApiResult' @Resolver() +// eslint-disable-next-line @typescript-eslint/no-unused-vars export class Test2Resolver { @Query(() => GetTestApiResult) async test2(): Promise { diff --git a/federation/src/graphql/api/1_0/resolver/TestResolver.ts b/federation/src/graphql/api/1_0/resolver/TestResolver.ts index 7291e5ef2..9fb2fc797 100644 --- a/federation/src/graphql/api/1_0/resolver/TestResolver.ts +++ b/federation/src/graphql/api/1_0/resolver/TestResolver.ts @@ -1,8 +1,10 @@ -import { Field, ObjectType, Query, Resolver } from 'type-graphql' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Query, Resolver } from 'type-graphql' import { federationLogger as logger } from '@/server/logger' import { GetTestApiResult } from '../../GetTestApiResult' @Resolver() +// eslint-disable-next-line @typescript-eslint/no-unused-vars export class TestResolver { @Query(() => GetTestApiResult) async test(): Promise { diff --git a/federation/src/graphql/api/1_1/resolver/TestResolver.ts b/federation/src/graphql/api/1_1/resolver/TestResolver.ts index 8880cad6e..74e02b74a 100644 --- a/federation/src/graphql/api/1_1/resolver/TestResolver.ts +++ b/federation/src/graphql/api/1_1/resolver/TestResolver.ts @@ -1,8 +1,10 @@ -import { Field, ObjectType, Query, Resolver } from 'type-graphql' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Query, Resolver } from 'type-graphql' import { federationLogger as logger } from '@/server/logger' import { GetTestApiResult } from '../../GetTestApiResult' @Resolver() +// eslint-disable-next-line @typescript-eslint/no-unused-vars export class TestResolver { @Query(() => GetTestApiResult) async test(): Promise { diff --git a/federation/src/graphql/api/2_0/resolver/TestResolver.ts b/federation/src/graphql/api/2_0/resolver/TestResolver.ts index f50149e33..4202bbbfe 100644 --- a/federation/src/graphql/api/2_0/resolver/TestResolver.ts +++ b/federation/src/graphql/api/2_0/resolver/TestResolver.ts @@ -1,8 +1,10 @@ -import { Field, ObjectType, Query, Resolver } from 'type-graphql' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Query, Resolver } from 'type-graphql' import { federationLogger as logger } from '@/server/logger' import { GetTestApiResult } from '../../GetTestApiResult' @Resolver() +// eslint-disable-next-line @typescript-eslint/no-unused-vars export class TestResolver { @Query(() => GetTestApiResult) async test(): Promise { diff --git a/federation/src/graphql/api/GetTestApiResult.ts b/federation/src/graphql/api/GetTestApiResult.ts index 7c8c6c487..942ff7b6c 100644 --- a/federation/src/graphql/api/GetTestApiResult.ts +++ b/federation/src/graphql/api/GetTestApiResult.ts @@ -1,6 +1,8 @@ +// eslint-disable-next-line @typescript-eslint/no-unused-vars import { Field, ObjectType } from 'type-graphql' @ObjectType() +// eslint-disable-next-line @typescript-eslint/no-unused-vars export class GetTestApiResult { constructor(apiVersion: string) { this.api = apiVersion From dcd7e0b7c323ddf78e478a5d34d12f9536858e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Wed, 8 Feb 2023 00:38:10 +0100 Subject: [PATCH 18/67] unit test, but is not working --- federation/package.json | 13 +- .../api/1_0/resolver/TestResolver.test.ts | 42 + federation/yarn.lock | 2144 ++++++++++++++++- 3 files changed, 2164 insertions(+), 35 deletions(-) create mode 100644 federation/src/graphql/api/1_0/resolver/TestResolver.test.ts diff --git a/federation/package.json b/federation/package.json index 56666e9b7..faa66e408 100644 --- a/federation/package.json +++ b/federation/package.json @@ -11,6 +11,7 @@ "build": "tsc --build", "clean": "tsc --build --clean", "start": "cross-env TZ=UTC TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/src/index.js", + "test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles", "dev": "cross-env TZ=UTC nodemon -w src --ext ts --exec ts-node -r dotenv/config -r tsconfig-paths/register src/index.ts", "lint": "eslint --max-warnings=0 --ext .js,.ts ." }, @@ -31,10 +32,13 @@ "type-graphql": "^1.1.1" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "^4.28.0", - "@typescript-eslint/parser": "^4.28.0", + "@types/express": "4.17.12", + "@types/jest": "27.0.2", "@types/lodash.clonedeep": "^4.5.6", "@types/node": "^16.10.3", + "@typescript-eslint/eslint-plugin": "^4.28.0", + "@typescript-eslint/parser": "^4.28.0", + "apollo-server-testing": "2.25.2", "eslint": "^7.29.0", "eslint-config-prettier": "^8.3.0", "eslint-config-standard": "^16.0.3", @@ -42,8 +46,9 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-promise": "^5.1.0", + "jest": "27.2.4", + "nodemon": "^2.0.7", "prettier": "^2.3.1", - "typescript": "^4.3.4", - "nodemon": "^2.0.7" + "typescript": "^4.3.4" } } diff --git a/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts b/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts new file mode 100644 index 000000000..615bc3305 --- /dev/null +++ b/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +import { createTestClient } from 'apollo-server-testing' +import createServer from '@/server/createServer' + +jest.mock('@/config') + +let query: any + +// to do: We need a setup for the tests that closes the connection +let con: any + +beforeAll(async () => { + const server = await createServer() + con = server.con + query = createTestClient(server.apollo).query +}) + +afterAll(async () => { + await con.close() +}) + +describe('TestResolver', () => { + const getTestQuery = ` + query { + test { + api + } + } + ` + + describe('getTestApi', () => { + it('returns 1_0', async () => { + await expect(query({ query: getTestQuery })).resolves.toMatchObject({ + data: { + api: '1_0', + }, + }) + }) + }) +}) diff --git a/federation/yarn.lock b/federation/yarn.lock index 2a6a305e0..5a0710e6f 100644 --- a/federation/yarn.lock +++ b/federation/yarn.lock @@ -2,6 +2,14 @@ # yarn lockfile v1 +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + "@apollo/protobufjs@1.2.2": version "1.2.2" resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.2.tgz#4bd92cd7701ccaef6d517cdb75af2755f049f87c" @@ -53,12 +61,144 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/helper-validator-identifier@^7.18.6": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.20.5": + version "7.20.14" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.14.tgz#4106fc8b755f3e3ee0a0a7c27dde5de1d2b2baf8" + integrity sha512-0YpKHD6ImkWMEINCyDAD0HLLUH/lPCefG8ld9it8DJB2wnApraKuhgYTvTY1z7UFIfBTGy5LwncZ+5HWWGbhFw== + +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" + integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helpers" "^7.20.7" + "@babel/parser" "^7.20.7" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.12" + "@babel/types" "^7.20.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + +"@babel/generator@^7.20.7", "@babel/generator@^7.7.2": + version "7.20.14" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.14.tgz#9fa772c9f86a46c6ac9b321039400712b96f64ce" + integrity sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg== + dependencies: + "@babel/types" "^7.20.7" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-compilation-targets@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" + integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== + dependencies: + "@babel/compat-data" "^7.20.5" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.21.3" + lru-cache "^5.1.1" + semver "^6.3.0" + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-function-name@^7.19.0": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" + integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== + dependencies: + "@babel/template" "^7.18.10" + "@babel/types" "^7.19.0" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.20.11": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz#df4c7af713c557938c50ea3ad0117a7944b2f1b0" + integrity sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.19.1" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.10" + "@babel/types" "^7.20.7" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.8.0": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" + integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== + +"@babel/helper-simple-access@^7.20.2": + version "7.20.2" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" + integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== + dependencies: + "@babel/types" "^7.20.2" + +"@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + +"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/highlight@^7.10.4": +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helpers@^7.20.7": + version "7.20.13" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.13.tgz#e3cb731fb70dc5337134cadc24cbbad31cc87ad2" + integrity sha512-nzJ0DWCL3gB5RCXbUO3KIMMsBY2Eqbx8mBpKGE/02PgyRQFcPQLbkQ1vyy596mZLaP+dAfD+R4ckASzNVmW3jg== + dependencies: + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.13" + "@babel/types" "^7.20.7" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== @@ -67,6 +207,141 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.13", "@babel/parser@^7.20.7": + version "7.20.15" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89" + integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.7.2": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz#4e9a0cfc769c85689b77a2e642d24e9f697fc8c7" + integrity sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.19.0" + +"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + +"@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.7.2": + version "7.20.13" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" + integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.19.0" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.20.13" + "@babel/types" "^7.20.7" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" + integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -103,17 +378,224 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + +"@jest/core@^27.2.4", "@jest/core@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/reporters" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.8.1" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^27.5.1" + jest-config "^27.5.1" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-resolve-dependencies "^27.5.1" + jest-runner "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + jest-watcher "^27.5.1" + micromatch "^4.0.4" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== + dependencies: + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + +"@jest/fake-timers@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== + dependencies: + "@jest/types" "^27.5.1" + "@sinonjs/fake-timers" "^8.0.1" + "@types/node" "*" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +"@jest/globals@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/types" "^27.5.1" + expect "^27.5.1" + +"@jest/reporters@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-haste-map "^27.5.1" + jest-resolve "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^8.1.0" + +"@jest/source-map@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.9" + source-map "^0.6.0" + +"@jest/test-result@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== + dependencies: + "@jest/console" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== + dependencies: + "@jest/test-result" "^27.5.1" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-runtime "^27.5.1" + +"@jest/transform@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^27.5.1" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-regex-util "^27.5.1" + jest-util "^27.5.1" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + "@josephg/resolvable@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb" integrity sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg== -"@jridgewell/resolve-uri@^3.0.3": +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"@jridgewell/sourcemap-codec@^1.4.10": +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.14" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== @@ -126,6 +608,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.9": + version "0.3.17" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" + integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== + dependencies: + "@jridgewell/resolve-uri" "3.1.0" + "@jridgewell/sourcemap-codec" "1.4.14" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -200,6 +690,25 @@ resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@sinonjs/commons@^1.7.0": + version "1.8.6" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9" + integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^8.0.1": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -227,6 +736,39 @@ dependencies: "@types/node" "*" +"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": + version "7.20.0" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.0.tgz#61bc5a4cae505ce98e1e36c5445e4bee060d8891" + integrity sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": + version "7.18.3" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.3.tgz#dfc508a85781e5698d5b33443416b6268c4b3e8d" + integrity sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w== + dependencies: + "@babel/types" "^7.3.0" + "@types/body-parser@*": version "1.19.2" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" @@ -270,6 +812,15 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.10.tgz#61cc8469849e5bcdd0c7044122265c39cec10cf4" integrity sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ== +"@types/express-serve-static-core@^4.17.18": + version "4.17.33" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.33.tgz#de35d30a9d637dc1450ad18dd583d75d5733d543" + integrity sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/express-serve-static-core@^4.17.21", "@types/express-serve-static-core@^4.17.31": version "4.17.32" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.32.tgz#93dda387f5516af616d8d3f05f2c4c79d81e1b82" @@ -289,6 +840,16 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/express@4.17.12": + version "4.17.12" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.12.tgz#4bc1bf3cd0cfe6d3f6f2853648b40db7d54de350" + integrity sha512-pTYas6FrP15B1Oa0bkN5tQMNqOcVXa9j4FTFtO8DWI9kppKib+6NJtfTOOLcwxuuYvcX2+dVG6et1SxW/Kc17Q== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/fs-capacitor@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/fs-capacitor/-/fs-capacitor-2.0.0.tgz#17113e25817f584f58100fb7a08eed288b81956e" @@ -304,6 +865,13 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/graceful-fs@^4.1.2": + version "4.1.6" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" + integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== + dependencies: + "@types/node" "*" + "@types/http-assert@*": version "1.5.3" resolved "https://registry.yarnpkg.com/@types/http-assert/-/http-assert-1.5.3.tgz#ef8e3d1a8d46c387f04ab0f2e8ab8cb0c5078661" @@ -314,6 +882,33 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.1.tgz#20172f9578b225f6c7da63446f56d4ce108d5a65" integrity sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ== +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@27.0.2": + version "27.0.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.0.2.tgz#ac383c4d4aaddd29bbf2b916d8d105c304a5fcd7" + integrity sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA== + dependencies: + jest-diff "^27.0.0" + pretty-format "^27.0.0" + "@types/json-schema@^7.0.7": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -397,6 +992,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.11.tgz#cbb15c12ca7c16c85a72b6bdc4d4b01151bb3cae" integrity sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA== +"@types/prettier@^2.1.5": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.2.tgz#6c2324641cc4ba050a8c710b2b251b377581fbf0" + integrity sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg== + "@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -420,6 +1020,11 @@ "@types/mime" "*" "@types/node" "*" +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + "@types/ws@^7.0.0": version "7.4.7" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.4.7.tgz#f7c390a36f7a0679aa69de2d501319f4f8d9b702" @@ -427,6 +1032,18 @@ dependencies: "@types/node" "*" +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^16.0.0": + version "16.0.5" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3" + integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@^4.28.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" @@ -504,6 +1121,11 @@ dependencies: tslib "^1.9.3" +abab@^2.0.3, abab@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -517,26 +1139,51 @@ accepts@^1.3.5, accepts@~1.3.7, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + acorn-jsx@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^7.1.1: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + acorn-walk@^8.1.1: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== -acorn@^7.4.0: +acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.2.4: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + acorn@^8.4.1: version "8.8.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + ajv@^6.10.0, ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -562,6 +1209,13 @@ ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -581,7 +1235,12 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -anymatch@~3.1.2: +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -638,7 +1297,7 @@ apollo-server-caching@^0.7.0: dependencies: lru-cache "^6.0.0" -apollo-server-core@^2.26.1: +apollo-server-core@^2.25.2, apollo-server-core@^2.26.1: version "2.26.1" resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-2.26.1.tgz#40a122b42f3ee2ddbfd1bd0c5775cd14eb454688" integrity sha512-YnO1YXhHOnCY7Q2SZ0uUtPq6SLCw+t2uI19l59mzWuCyZYdHrtSy3zUEU6pM3tR9vvUuRGkYIfMRlo/Q8a1U5g== @@ -712,6 +1371,13 @@ apollo-server-plugin-base@^0.14.0: dependencies: apollo-server-types "^0.10.0" +apollo-server-testing@2.25.2: + version "2.25.2" + resolved "https://registry.yarnpkg.com/apollo-server-testing/-/apollo-server-testing-2.25.2.tgz#0043e98b1a03720352e94b409215fb4782ae2e50" + integrity sha512-HjQV9wPbi/ZqpRbyyhNwCbaDnfjDM0hTRec5TOoOjurEZ/vh4hTPHwGkDZx3kbcWowhGxe2qoHM6KANSB/SxuA== + dependencies: + apollo-server-core "^2.25.2" + apollo-server-types@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/apollo-server-types/-/apollo-server-types-0.10.0.tgz#af578bf507151a0e86fbdf188f9673ece3f8f164" @@ -815,11 +1481,77 @@ async-retry@^1.2.1: dependencies: retry "0.13.1" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +babel-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== + dependencies: + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== + dependencies: + babel-plugin-jest-hoist "^27.5.1" + babel-preset-current-node-syntax "^1.0.0" + backo2@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" @@ -884,6 +1616,33 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserslist@^4.21.3: + version "4.21.5" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7" + integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== + dependencies: + caniuse-lite "^1.0.30001449" + electron-to-chromium "^1.4.284" + node-releases "^2.0.8" + update-browserslist-db "^1.0.10" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + busboy@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" @@ -914,6 +1673,21 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001449: + version "1.0.30001450" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001450.tgz#022225b91200589196b814b51b1bbe45144cf74f" + integrity sha512-qMBmvmQmFXaSxexkjjfMvD5rnDL0+m+dUMZKoDYsGG8iZN29RuYh9eRoMvKsT6uMAWlyUUGDEQGJJYjzCIO9ew== + chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -931,6 +1705,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + chokidar@^3.5.2: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -946,6 +1725,16 @@ chokidar@^3.5.2: optionalDependencies: fsevents "~2.3.2" +ci-info@^3.2.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.7.1.tgz#708a6cdae38915d597afdf3b145f2f8e1ff55f3f" + integrity sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w== + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + class-validator@^0.13.2: version "0.13.2" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.2.tgz#64b031e9f3f81a1e1dcd04a5d604734608b24143" @@ -954,6 +1743,25 @@ class-validator@^0.13.2: libphonenumber-js "^1.9.43" validator "^13.7.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -978,6 +1786,13 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + commander@^2.20.3: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -1007,6 +1822,11 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -1047,7 +1867,7 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-spawn@^7.0.1, cross-spawn@^7.0.2: +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -1061,6 +1881,32 @@ cssfilter@0.0.10: resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw== +cssom@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +data-urls@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== + dependencies: + abab "^2.0.3" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.0.0" + date-format@^4.0.14: version "4.0.14" resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" @@ -1073,6 +1919,13 @@ debug@2.6.9: dependencies: ms "2.0.0" +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -1080,23 +1933,31 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - decimal.js-light@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== -deep-is@^0.1.3: +decimal.js@^10.2.1: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^4.2.2: + version "4.3.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.0.tgz#65491893ec47756d44719ae520e0e2609233b59b" + integrity sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og== + define-properties@^1.1.3, define-properties@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" @@ -1105,6 +1966,11 @@ define-properties@^1.1.3, define-properties@^1.1.4: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -1130,6 +1996,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg== +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + dicer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" @@ -1137,6 +2008,11 @@ dicer@0.3.0: dependencies: streamsearch "0.1.2" +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -1163,6 +2039,13 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +domexception@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== + dependencies: + webidl-conversions "^5.0.0" + dotenv@10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" @@ -1173,6 +2056,16 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== +electron-to-chromium@^1.4.284: + version "1.4.288" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.288.tgz#bbce00eb03c1819fe3d0d0d861374b76c53f7507" + integrity sha512-8s9aJf3YiokIrR+HOQzNOGmEHFXVUQzXM/JaViVvKdCkNUjS+lEa/uT7xw3nDVG/IgfxiIwUGkwJ6AR1pTpYsQ== + +emittery@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1190,6 +2083,13 @@ enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + es-abstract@^1.19.0, es-abstract@^1.20.4: version "1.21.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.1.tgz#e6105a099967c08377830a0c9cb589d570dd86c6" @@ -1259,6 +2159,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -1269,11 +2174,28 @@ escape-string-regexp@^1.0.5: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-config-prettier@^8.3.0: version "8.6.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.6.0.tgz#dec1d29ab728f4fa63061774e1672ac4e363d207" @@ -1440,7 +2362,7 @@ espree@^7.3.0, espree@^7.3.1: acorn-jsx "^5.3.1" eslint-visitor-keys "^1.3.0" -esprima@^4.0.0: +esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -1484,6 +2406,36 @@ eventemitter3@^3.1.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expect@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== + dependencies: + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + express@4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -1583,7 +2535,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== @@ -1595,6 +2547,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fb-watchman@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" + integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== + dependencies: + bser "2.1.1" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -1635,6 +2594,14 @@ finalhandler@~1.1.2: statuses "~1.5.0" unpipe "~1.0.0" +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -1655,6 +2622,15 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -1684,7 +2660,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -1714,6 +2690,16 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" @@ -1723,6 +2709,16 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: has "^1.0.3" has-symbols "^1.0.3" +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -1738,7 +2734,7 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" -glob@^7.1.3, glob@^7.1.6: +glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -1750,6 +2746,11 @@ glob@^7.1.3, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + globals@^13.6.0, globals@^13.9.0: version "13.19.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" @@ -1783,7 +2784,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.6, graceful-fs@^4.2.0: +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== @@ -1880,6 +2881,18 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +html-encoding-sniffer@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== + dependencies: + whatwg-encoding "^1.0.5" + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -1924,6 +2937,28 @@ http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -1954,6 +2989,14 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -2000,6 +3043,11 @@ is-array-buffer@^3.0.1: get-intrinsic "^1.1.3" is-typed-array "^1.1.10" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -2051,6 +3099,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -2075,6 +3128,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -2090,6 +3148,11 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -2115,6 +3178,11 @@ is-typed-array@^1.1.10, is-typed-array@^1.1.9: gopd "^1.0.1" has-tostringtag "^1.0.0" +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -2127,11 +3195,458 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" + integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + iterall@^1.1.3, iterall@^1.2.1, iterall@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== +jest-changed-files@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== + dependencies: + "@jest/types" "^27.5.1" + execa "^5.0.0" + throat "^6.0.1" + +jest-circus@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + throat "^6.0.1" + +jest-cli@^27.2.4: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== + dependencies: + "@jest/core" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + prompts "^2.0.1" + yargs "^16.2.0" + +jest-config@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== + dependencies: + "@babel/core" "^7.8.0" + "@jest/test-sequencer" "^27.5.1" + "@jest/types" "^27.5.1" + babel-jest "^27.5.1" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.9" + jest-circus "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-get-type "^27.5.1" + jest-jasmine2 "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runner "^27.5.1" + jest-util "^27.5.1" + jest-validate "^27.5.1" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^27.5.1" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^27.0.0, jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-docblock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== + dependencies: + detect-newline "^3.0.0" + +jest-each@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + jest-get-type "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + +jest-environment-jsdom@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + jsdom "^16.6.0" + +jest-environment-node@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + jest-mock "^27.5.1" + jest-util "^27.5.1" + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-haste-map@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== + dependencies: + "@jest/types" "^27.5.1" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^27.5.1" + jest-serializer "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + micromatch "^4.0.4" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.3.2" + +jest-jasmine2@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^27.5.1" + is-generator-fn "^2.0.0" + jest-each "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-runtime "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + pretty-format "^27.5.1" + throat "^6.0.1" + +jest-leak-detector@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== + dependencies: + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" + integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== + +jest-regex-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== + +jest-resolve-dependencies@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== + dependencies: + "@jest/types" "^27.5.1" + jest-regex-util "^27.5.1" + jest-snapshot "^27.5.1" + +jest-resolve@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== + dependencies: + "@jest/types" "^27.5.1" + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-pnp-resolver "^1.2.2" + jest-util "^27.5.1" + jest-validate "^27.5.1" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== + dependencies: + "@jest/console" "^27.5.1" + "@jest/environment" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.8.1" + graceful-fs "^4.2.9" + jest-docblock "^27.5.1" + jest-environment-jsdom "^27.5.1" + jest-environment-node "^27.5.1" + jest-haste-map "^27.5.1" + jest-leak-detector "^27.5.1" + jest-message-util "^27.5.1" + jest-resolve "^27.5.1" + jest-runtime "^27.5.1" + jest-util "^27.5.1" + jest-worker "^27.5.1" + source-map-support "^0.5.6" + throat "^6.0.1" + +jest-runtime@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== + dependencies: + "@jest/environment" "^27.5.1" + "@jest/fake-timers" "^27.5.1" + "@jest/globals" "^27.5.1" + "@jest/source-map" "^27.5.1" + "@jest/test-result" "^27.5.1" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^27.5.1" + jest-message-util "^27.5.1" + jest-mock "^27.5.1" + jest-regex-util "^27.5.1" + jest-resolve "^27.5.1" + jest-snapshot "^27.5.1" + jest-util "^27.5.1" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-serializer@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.9" + +jest-snapshot@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== + dependencies: + "@babel/core" "^7.7.2" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.0.0" + "@jest/transform" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^27.5.1" + graceful-fs "^4.2.9" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + jest-haste-map "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + jest-util "^27.5.1" + natural-compare "^1.4.0" + pretty-format "^27.5.1" + semver "^7.3.2" + +jest-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== + dependencies: + "@jest/types" "^27.5.1" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^27.5.1" + leven "^3.1.0" + pretty-format "^27.5.1" + +jest-watcher@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== + dependencies: + "@jest/test-result" "^27.5.1" + "@jest/types" "^27.5.1" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^27.5.1" + string-length "^4.0.1" + +jest-worker@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@27.2.4: + version "27.2.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-27.2.4.tgz#70e27bef873138afc123aa4769f7124c50ad3efb" + integrity sha512-h4uqb1EQLfPulWyUFFWv9e9Nn8sCqsJ/j3wk/KCY0p4s4s0ICCfP3iMf6hRf5hEhsDyvyrCgKiZXma63gMz16A== + dependencies: + "@jest/core" "^27.2.4" + import-local "^3.0.2" + jest-cli "^27.2.4" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -2145,6 +3660,49 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +jsdom@^16.6.0: + version "16.7.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== + dependencies: + abab "^2.0.5" + acorn "^8.2.4" + acorn-globals "^6.0.0" + cssom "^0.4.4" + cssstyle "^2.3.0" + data-urls "^2.0.0" + decimal.js "^10.2.1" + domexception "^2.0.1" + escodegen "^2.0.0" + form-data "^3.0.0" + html-encoding-sniffer "^2.0.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^2.0.0" + webidl-conversions "^6.1.0" + whatwg-encoding "^1.0.5" + whatwg-mimetype "^2.3.0" + whatwg-url "^8.5.0" + ws "^7.4.6" + xml-name-validator "^3.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -2179,6 +3737,16 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -2187,11 +3755,31 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + libphonenumber-js@^1.9.43: version "1.10.18" resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.18.tgz#657c419071c8a02c638c0e80d9ee1232f152f280" integrity sha512-NS4ZEgNhwbcPz1gfSXCGFnQm0xEiyTSPRthIuWytDzOiEG9xnZ2FbLyfJC4tI2BMAAXpoWbNxHYH75pa3Dq9og== +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -2217,6 +3805,11 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== +lodash@^4.7.0: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + log4js@^6.7.1: version "6.7.1" resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.7.1.tgz#06e12b1ac915dd1067146ffad8215f666f7d2c51" @@ -2238,6 +3831,13 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -2245,11 +3845,25 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +make-dir@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -2260,6 +3874,11 @@ merge-descriptors@1.0.1: resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -2283,7 +3902,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -2295,6 +3914,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2344,6 +3968,16 @@ node-fetch@^2.6.1: dependencies: whatwg-url "^5.0.0" +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-releases@^2.0.8: + version "2.0.10" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f" + integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== + nodemon@^2.0.7: version "2.0.20" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.20.tgz#e3537de768a492e8d74da5c5813cb0c7486fc701" @@ -2372,6 +4006,18 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nwsapi@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== + object-assign@^4: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -2442,6 +4088,25 @@ once@^1.3.0: dependencies: wrappy "1" +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -2454,6 +4119,25 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -2461,17 +4145,37 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parseurl@^1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -2491,16 +4195,38 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pirates@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -2513,11 +4239,28 @@ prettier@^2.3.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.2.tgz#c4ea1b5b454d7c4b59966db2e06ed7eec5dfd160" integrity sha512-BtRV9BcncDyI2tsuS19zzhzoxD8Dh8LiCx7j7tHzrkz8GFXAexeWFdi22mjE1d16dftH2qNaytVxqiRTGlMfpw== +pretty-format@^27.0.0, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +prompts@^2.0.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + proxy-addr@~2.0.5, proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -2526,6 +4269,11 @@ proxy-addr@~2.0.5, proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" @@ -2536,6 +4284,11 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.2.0.tgz#2092cc57cd2582c38e4e7e8bb869dc8d3148bc74" integrity sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw== +punycode@^2.1.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + qs@6.11.0: version "6.11.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" @@ -2548,6 +4301,11 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" @@ -2578,6 +4336,11 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -2604,17 +4367,44 @@ regexpp@^3.0.0, regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.10.1, resolve@^1.22.1: +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve.exports@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999" + integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ== + +resolve@^1.10.1, resolve@^1.20.0, resolve@^1.22.1: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -2638,7 +4428,7 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== -rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -2676,12 +4466,19 @@ safe-regex-test@^1.0.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.1.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.1.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -2795,6 +4592,11 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + simple-update-notifier@^1.0.7: version "1.1.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" @@ -2802,6 +4604,11 @@ simple-update-notifier@^1.0.7: dependencies: semver "~7.0.0" +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -2816,11 +4623,36 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +source-map-support@^0.5.6: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -2845,7 +4677,15 @@ streamsearch@0.1.2: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA== -string-width@^4.2.3: +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2884,6 +4724,16 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -2907,13 +4757,28 @@ supports-color@^5.3.0, supports-color@^5.5.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" + integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -2924,6 +4789,11 @@ symbol-observable@^1.0.4: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + table@^6.0.9: version "6.8.1" resolved "https://registry.yarnpkg.com/table/-/table-6.8.1.tgz#ea2b71359fe03b017a5fbc296204471158080bdf" @@ -2935,11 +4805,43 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +throat@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.2.tgz#51a3fbb5e11ae72e2cf74861ed5c8020f89f29fe" + integrity sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ== + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -2964,6 +4866,23 @@ touch@^3.1.0: dependencies: nopt "~1.0.10" +tough-cookie@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" + integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -3038,11 +4957,28 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-graphql@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/type-graphql/-/type-graphql-1.1.1.tgz#dc0710d961713b92d3fee927981fa43bf71667a4" @@ -3074,6 +5010,13 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + typescript@^4.3.4: version "4.9.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" @@ -3099,11 +5042,24 @@ universalify@^0.1.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +update-browserslist-db@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" + integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -3111,6 +5067,14 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + util.promisify@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.1.1.tgz#77832f57ced2c9478174149cae9b96e9918cd54b" @@ -3147,6 +5111,15 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +v8-to-istanbul@^8.1.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + validator@^13.7.0: version "13.7.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" @@ -3157,11 +5130,54 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== + dependencies: + xml-name-validator "^3.0.0" + +walker@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + +webidl-conversions@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" + integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== + +whatwg-encoding@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== + dependencies: + iconv-lite "0.4.24" + +whatwg-mimetype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -3170,6 +5186,15 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" +whatwg-url@^8.0.0, whatwg-url@^8.5.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" + integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== + dependencies: + lodash "^4.7.0" + tr46 "^2.1.0" + webidl-conversions "^6.1.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -3200,21 +5225,50 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@^1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -"ws@^5.2.0 || ^6.0.0 || ^7.0.0": +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +"ws@^5.2.0 || ^6.0.0 || ^7.0.0", ws@^7.4.6: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xss@^1.0.8: version "1.0.14" resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.14.tgz#4f3efbde75ad0d82e9921cc3c95e6590dd336694" @@ -3223,11 +5277,39 @@ xss@^1.0.8: commander "^2.20.3" cssfilter "0.0.10" +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 65488b5c5c9705144b5689dd10dfa7aa2ae2fed0 Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 8 Feb 2023 11:29:38 +0100 Subject: [PATCH 19/67] Test of createContribution are now centered on the final object. --- .../resolver/ContributionResolver.test.ts | 139 +++++++----------- 1 file changed, 56 insertions(+), 83 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 7c239e699..ebf40b292 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -72,8 +72,6 @@ let mutate: any, query: any, con: any let testEnv: any let creation: Contribution | void let admin: User -// let result: any -// let contribution: any let pendingContribution: any let inProgressContribution: any let contributionToConfirm: any @@ -173,16 +171,12 @@ describe('ContributionResolver', () => { 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).toMatchObject([new GraphQLError('401 Unauthorized')]) }) }) @@ -202,20 +196,18 @@ 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 (5 characters minimum)')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: createContribution, + variables: { + amount: 100.0, + memo: 'Test', + creationDate: date.toString(), + }, + }) + + expect(errorObjects).toMatchObject([ + new GraphQLError('memo text is too short (5 characters minimum)'), + ]) }) it('logs the error found', () => { @@ -225,20 +217,17 @@ 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 (255 characters maximum)')], - }), - ) + 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).toMatchObject([ + new GraphQLError('memo text is too long (255 characters maximum)'), + ]) }) it('logs the error found', () => { @@ -247,22 +236,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).toMatchObject([ + new GraphQLError('No information for available creations for the given date'), + ]) }) it('logs the error found', () => { @@ -275,22 +259,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).toMatchObject([ + new GraphQLError('No information for available creations for the given date'), + ]) }) it('logs the error found', () => { @@ -303,17 +282,11 @@ describe('ContributionResolver', () => { describe('valid input', () => { it('creates contribution', async () => { - expect(pendingContribution).toEqual( - expect.objectContaining({ - data: { - createContribution: { - id: expect.any(Number), - amount: '100', - memo: 'Test PENDING contribution', - }, - }, - }), - ) + expect(pendingContribution.data.createContribution).toMatchObject({ + id: expect.any(Number), + amount: '100', + memo: 'Test PENDING contribution', + }) }) it('stores the create contribution event in the database', async () => { From 4cbfd83d5ec7e7dd7aff6f5aa8e99471827d8ed8 Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 8 Feb 2023 11:51:09 +0100 Subject: [PATCH 20/67] Add deconstructing variables for updateContribution. --- .../resolver/ContributionResolver.test.ts | 270 ++++++++---------- 1 file changed, 119 insertions(+), 151 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index ebf40b292..1e463dd4b 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -50,6 +50,7 @@ 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' // mock account activation email to avoid console spam // mock account activation email to avoid console spam @@ -306,21 +307,16 @@ 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).toMatchObject([new GraphQLError('401 Unauthorized')]) }) }) @@ -339,21 +335,18 @@ describe('ContributionResolver', () => { describe('wrong contribution id', () => { it('throws an error', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: -1, - amount: 100.0, - memo: 'Test env contribution', - creationDate: new Date().toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('No contribution found to given id.')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: updateContribution, + variables: { + contributionId: -1, + amount: 100.0, + memo: 'Test env contribution', + creationDate: new Date().toString(), + }, + }) + expect(errorObjects).toMatchObject([ + new GraphQLError('No contribution found to given id.'), + ]) }) it('logs the error found', () => { @@ -365,21 +358,18 @@ describe('ContributionResolver', () => { it('throws error', async () => { jest.clearAllMocks() const date = new Date() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: pendingContribution.data.createContribution.id, - amount: 100.0, - memo: 'Test', - creationDate: date.toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('memo text is too short (5 characters minimum)')], - }), - ) + 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).toMatchObject([ + new GraphQLError('memo text is too short (5 characters minimum)'), + ]) }) it('logs the error found', () => { @@ -391,21 +381,18 @@ describe('ContributionResolver', () => { it('throws error', async () => { jest.clearAllMocks() const date = new Date() - await expect( - 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(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('memo text is too long (255 characters maximum)')], - }), - ) + 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).toMatchObject([ + new GraphQLError('memo text is too long (255 characters maximum)'), + ]) }) it('logs the error found', () => { @@ -423,25 +410,18 @@ describe('ContributionResolver', () => { 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( - 'user of the pending contribution and send user does not correspond', - ), - ], - }), - ) + 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).toMatchObject([ + new GraphQLError('user of the pending contribution and send user does not correspond'), + ]) }) it('logs the error found', () => { @@ -461,25 +441,26 @@ describe('ContributionResolver', () => { it('throws an error', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: adminUpdateContribution, - variables: { - id: pendingContribution.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 a 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).toMatchObject([ + new GraphQLError('An admin is not allowed to update a user contribution.'), + ]) }) - // TODO check that the error is logged (need to modify AdminResolver, avoid conflicts) + it('logs the error found', () => { + expect(logger.error).toBeCalledWith( + 'An admin is not allowed to update a user contribution.', + ) + }) }) describe('update to much so that the limit is exceeded', () => { @@ -492,25 +473,20 @@ describe('ContributionResolver', () => { it('throws an error', async () => { jest.clearAllMocks() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: pendingContribution.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 (600 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).toMatchObject([ + new GraphQLError( + 'The amount (1019 GDD) to be created exceeds the amount (600 GDD) still available for this month.', + ), + ]) }) it('logs the error found', () => { @@ -524,21 +500,18 @@ describe('ContributionResolver', () => { it('throws an error', async () => { jest.clearAllMocks() const date = new Date() - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: pendingContribution.data.createContribution.id, - amount: 10.0, - memo: 'Test env contribution', - creationDate: date.setMonth(date.getMonth() - 3).toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Currently the month of the contribution cannot change.')], - }), - ) + 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).toMatchObject([ + new GraphQLError('Currently the month of the contribution cannot change.'), + ]) }) it.skip('logs the error found', () => { @@ -551,27 +524,22 @@ describe('ContributionResolver', () => { describe('valid input', () => { it('updates contribution', async () => { - await expect( - mutate({ - mutation: updateContribution, - variables: { - contributionId: pendingContribution.data.createContribution.id, - amount: 10.0, - memo: 'Test PENDING contribution update', - creationDate: new Date().toString(), - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - updateContribution: { - id: pendingContribution.data.createContribution.id, - amount: '10', - memo: 'Test PENDING contribution update', - }, - }, - }), - ) + 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 update contribution event in the database', async () => { From 712b64fd068c854d8cb83da3832d0c11c8fa9795 Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 8 Feb 2023 14:25:48 +0100 Subject: [PATCH 21/67] Add destructure for denyContribution tests. --- .../resolver/ContributionResolver.test.ts | 191 +++++++----------- 1 file changed, 70 insertions(+), 121 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 1e463dd4b..11ecbcd47 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -564,18 +564,13 @@ describe('ContributionResolver', () => { describe('denyContribution', () => { describe('unauthenticated', () => { it('returns an error', async () => { - await expect( - mutate({ - mutation: denyContribution, - 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).toMatchObject([new GraphQLError('401 Unauthorized')]) }) }) @@ -592,18 +587,13 @@ describe('ContributionResolver', () => { }) it('returns an error', async () => { - await expect( - mutate({ - mutation: denyContribution, - 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).toMatchObject([new GraphQLError('401 Unauthorized')]) }) }) @@ -621,18 +611,15 @@ describe('ContributionResolver', () => { describe('wrong contribution id', () => { it('throws an error', async () => { - await expect( - mutate({ - mutation: denyContribution, - variables: { - id: -1, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution not found for given id.')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: -1, + }, + }) + expect(errorObjects).toMatchObject([ + new GraphQLError('Contribution not found for given id.'), + ]) }) it('logs the error found', () => { @@ -669,18 +656,15 @@ describe('ContributionResolver', () => { }, }) - await expect( - mutate({ - mutation: denyContribution, - variables: { - id: contribution.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution not found for given id.')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + expect(errorObjects).toMatchObject([ + new GraphQLError('Contribution not found for given id.'), + ]) }) it('logs the error found', () => { @@ -720,18 +704,15 @@ describe('ContributionResolver', () => { variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - await expect( - mutate({ - mutation: denyContribution, - variables: { - id: contribution.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution not found for given id.')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + expect(errorObjects).toMatchObject([ + new GraphQLError('Contribution not found for given id.'), + ]) }) it('logs the error found', () => { @@ -771,18 +752,15 @@ describe('ContributionResolver', () => { }, }) - await expect( - mutate({ - mutation: denyContribution, - variables: { - id: contribution.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution not found for given id.')], - }), - ) + const { errors: errorObjects }: { errors: GraphQLError[] } = await mutate({ + mutation: denyContribution, + variables: { + id: contribution.data.createContribution.id, + }, + }) + expect(errorObjects).toMatchObject([ + new GraphQLError('Contribution not found for given id.'), + ]) }) it('logs the error found', () => { @@ -798,20 +776,15 @@ describe('ContributionResolver', () => { mutation: login, variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - await expect( - mutate({ - mutation: denyContribution, - variables: { - id: contributionToDeny.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - denyContribution: true, - }, - }), - ) + const { + data: { denyContribution: isDenied }, + }: { data: { denyContribution: boolean } } = await mutate({ + mutation: denyContribution, + variables: { + id: contributionToDeny.data.createContribution.id, + }, + }) + expect(isDenied).toBeTruthy() }) }) }) @@ -930,32 +903,11 @@ describe('ContributionResolver', () => { }) it('stores the delete contribution event in the database', async () => { - await mutate({ - mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, - }) - - const contribution = await mutate({ - mutation: createContribution, - variables: { - amount: 166.0, - memo: 'Whatever contribution', - creationDate: new Date().toString(), - }, - }) - - await mutate({ - mutation: deleteContribution, - variables: { - id: contribution.data.createContribution.id, - }, - }) - await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.CONTRIBUTION_DELETE, - contributionId: contribution.data.createContribution.id, - amount: expect.decimalEqual(166), + contributionId: contributionToDelete.data.createContribution.id, + amount: expect.decimalEqual(100), userId: peter.id, }), ) @@ -979,18 +931,15 @@ describe('ContributionResolver', () => { mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: contributionToConfirm.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).toMatchObject([ + new GraphQLError('A confirmed contribution can not be deleted'), + ]) }) it('logs the error found', () => { From 8e85705b9ee30c7fd9b93d3868ede8fed398f709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Wed, 8 Feb 2023 22:29:20 +0100 Subject: [PATCH 22/67] remove jest.mock('@/config') --- federation/src/graphql/api/1_0/resolver/TestResolver.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts b/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts index 615bc3305..974f8db5e 100644 --- a/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts @@ -1,11 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ - import { createTestClient } from 'apollo-server-testing' import createServer from '@/server/createServer' -jest.mock('@/config') - let query: any // to do: We need a setup for the tests that closes the connection From 10957fddfcc490455272f7b5789bffe470688880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Wed, 8 Feb 2023 23:17:48 +0100 Subject: [PATCH 23/67] additional files to buildup test-environment, don't know if complette --- federation/jest.config.js | 27 +++++++++++++++ federation/package.json | 8 +++-- federation/test/extensions.ts | 33 ++++++++++++++++++ federation/test/helpers.test.ts | 7 ++++ federation/test/helpers.ts | 59 +++++++++++++++++++++++++++++++++ federation/test/testSetup.ts | 22 ++++++++++++ federation/tsconfig.json | 4 +-- federation/yarn.lock | 55 ++++++++++++++++++++---------- 8 files changed, 194 insertions(+), 21 deletions(-) create mode 100644 federation/jest.config.js create mode 100644 federation/test/extensions.ts create mode 100644 federation/test/helpers.test.ts create mode 100644 federation/test/helpers.ts create mode 100644 federation/test/testSetup.ts diff --git a/federation/jest.config.js b/federation/jest.config.js new file mode 100644 index 000000000..d6683d292 --- /dev/null +++ b/federation/jest.config.js @@ -0,0 +1,27 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ +module.exports = { + verbose: true, + preset: 'ts-jest', + collectCoverage: true, + collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], + setupFiles: ['/test/testSetup.ts'], + setupFilesAfterEnv: ['/test/extensions.ts'], + modulePathIgnorePatterns: ['/build/'], + moduleNameMapper: { + '@/(.*)': '/src/$1', + '@arg/(.*)': '/src/graphql/arg/$1', + '@enum/(.*)': '/src/graphql/enum/$1', + '@model/(.*)': '/src/graphql/model/$1', + '@union/(.*)': '/src/graphql/union/$1', + '@repository/(.*)': '/src/typeorm/repository/$1', + '@test/(.*)': '/test/$1', + '@entity/(.*)': + process.env.NODE_ENV === 'development' + ? '/../database/entity/$1' + : '/../database/build/entity/$1', + '@dbTools/(.*)': + process.env.NODE_ENV === 'development' + ? '/../database/src/$1' + : '/../database/build/src/$1', + }, +} diff --git a/federation/package.json b/federation/package.json index faa66e408..8a0153cc5 100644 --- a/federation/package.json +++ b/federation/package.json @@ -27,8 +27,6 @@ "lodash.clonedeep": "^4.5.0", "log4js": "^6.7.1", "reflect-metadata": "^0.1.13", - "ts-node": "^10.9.1", - "tsconfig-paths": "^4.1.1", "type-graphql": "^1.1.1" }, "devDependencies": { @@ -47,8 +45,14 @@ "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-promise": "^5.1.0", "jest": "27.2.4", + "ts-jest": "27.0.5", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.1.1", "nodemon": "^2.0.7", "prettier": "^2.3.1", "typescript": "^4.3.4" + }, + "nodemonConfig": { + "ignore": ["**/*.test.ts"] } } diff --git a/federation/test/extensions.ts b/federation/test/extensions.ts new file mode 100644 index 000000000..69c2ff7a6 --- /dev/null +++ b/federation/test/extensions.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import Decimal from 'decimal.js-light' + +expect.extend({ + decimalEqual(received, value) { + const pass = new Decimal(value).equals(received.toString()) + if (pass) { + return { + message: () => `expected ${received} to not equal ${value}`, + pass: true, + } + } else { + return { + message: () => `expected ${received} to equal ${value}`, + pass: false, + } + } + }, +}) + +interface CustomMatchers { + decimalEqual(value: number): R +} + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Expect extends CustomMatchers {} + interface Matchers extends CustomMatchers {} + interface InverseAsymmetricMatchers extends CustomMatchers {} + } +} diff --git a/federation/test/helpers.test.ts b/federation/test/helpers.test.ts new file mode 100644 index 000000000..69d8f3fa4 --- /dev/null +++ b/federation/test/helpers.test.ts @@ -0,0 +1,7 @@ +import { contributionDateFormatter } from '@test/helpers' + +describe('contributionDateFormatter', () => { + it('formats the date correctly', () => { + expect(contributionDateFormatter(new Date('Thu Feb 29 2024 13:12:11'))).toEqual('2/29/2024') + }) +}) diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts new file mode 100644 index 000000000..b716aeb9a --- /dev/null +++ b/federation/test/helpers.ts @@ -0,0 +1,59 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +import { createTestClient } from 'apollo-server-testing' +import createServer from '../src/server/createServer' +import { initialize } from '@dbTools/helpers' +import { entities } from '@entity/index' +import { logger } from './testSetup' + +export const headerPushMock = jest.fn((t) => { + context.token = t.value +}) + +const context = { + token: '', + setHeaders: { + push: headerPushMock, + forEach: jest.fn(), + }, + clientTimezoneOffset: 0, +} + +export const cleanDB = async () => { + // this only works as lond we do not have foreign key constraints + for (let i = 0; i < entities.length; i++) { + await resetEntity(entities[i]) + } +} + +export const testEnvironment = async (testLogger: any = logger) => { + const server = await createServer() + const con = server.con + const testClient = createTestClient(server.apollo) + const mutate = testClient.mutate + const query = testClient.query + await initialize() + return { mutate, query, con } +} + +export const resetEntity = async (entity: any) => { + const items = await entity.find({ withDeleted: true }) + if (items.length > 0) { + const ids = items.map((i: any) => i.id) + await entity.delete(ids) + } +} + +export const resetToken = () => { + context.token = '' +} + +// format date string as it comes from the frontend for the contribution date +export const contributionDateFormatter = (date: Date): string => { + return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` +} + +export const setClientTimezoneOffset = (offset: number): void => { + context.clientTimezoneOffset = offset +} diff --git a/federation/test/testSetup.ts b/federation/test/testSetup.ts new file mode 100644 index 000000000..4341a1b49 --- /dev/null +++ b/federation/test/testSetup.ts @@ -0,0 +1,22 @@ +import { federationLogger as logger } from '@/server/logger' + +jest.setTimeout(1000000) + +jest.mock('@/server/logger', () => { + const originalModule = jest.requireActual('@/server/logger') + return { + __esModule: true, + ...originalModule, + backendLogger: { + addContext: jest.fn(), + trace: jest.fn(), + debug: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + }, + } +}) + +export { logger } diff --git a/federation/tsconfig.json b/federation/tsconfig.json index 2764b5604..b38c43ba1 100644 --- a/federation/tsconfig.json +++ b/federation/tsconfig.json @@ -52,14 +52,14 @@ // "@enum/*": ["src/graphql/enum/*"], // "@model/*": ["src/graphql/model/*"], "@repository/*": ["src/typeorm/repository/*"], - // "@test/*": ["test/*"], + "@test/*": ["test/*"], /* external */ "@typeorm/*": ["../backend/src/typeorm/*", "../../backend/src/typeorm/*"], "@dbTools/*": ["../database/src/*", "../../database/build/src/*"], "@entity/*": ["../database/entity/*", "../../database/build/entity/*"] }, // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - "typeRoots": ["src/dht_node/@types", "node_modules/@types"], /* List of folders to include type definitions from. */ + "typeRoots": ["node_modules/@types"], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ diff --git a/federation/yarn.lock b/federation/yarn.lock index 5a0710e6f..238d7c181 100644 --- a/federation/yarn.lock +++ b/federation/yarn.lock @@ -1631,6 +1631,13 @@ browserslist@^4.21.3: node-releases "^2.0.8" update-browserslist-db "^1.0.10" +bs-logger@0.x: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -2530,7 +2537,7 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -3592,7 +3599,7 @@ jest-snapshot@^27.5.1: pretty-format "^27.5.1" semver "^7.3.2" -jest-util@^27.5.1: +jest-util@^27.0.0, jest-util@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== @@ -3718,6 +3725,11 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json5@2.x, json5@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + json5@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -3725,11 +3737,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.2.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -3805,7 +3812,7 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== -lodash@^4.7.0: +lodash@4.x, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3852,7 +3859,7 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" -make-error@^1.1.1: +make-error@1.x, make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -4473,6 +4480,13 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" +semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -4483,13 +4497,6 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.2.1, semver@^7.3.2, semver@^7.3.5: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== - dependencies: - lru-cache "^6.0.0" - semver@~7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -4895,6 +4902,20 @@ ts-invariant@^0.4.0: dependencies: tslib "^1.9.3" +ts-jest@27.0.5: + version "27.0.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.0.5.tgz#0b0604e2271167ec43c12a69770f0bb65ad1b750" + integrity sha512-lIJApzfTaSSbtlksfFNHkWOzLJuuSm4faFAfo5kvzOiRAuoN4/eKxVJ2zEAho8aecE04qX6K1pAzfH5QHL1/8w== + dependencies: + bs-logger "0.x" + fast-json-stable-stringify "2.x" + jest-util "^27.0.0" + json5 "2.x" + lodash "4.x" + make-error "1.x" + semver "7.x" + yargs-parser "20.x" + ts-node@^10.9.1: version "10.9.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" @@ -5292,7 +5313,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^20.2.2: +yargs-parser@20.x, yargs-parser@^20.2.2: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== From 3015fb39e10334b54cc990db854725db56bb6b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Wed, 8 Feb 2023 23:18:23 +0100 Subject: [PATCH 24/67] additional tests files --- .../api/1_1/resolver/TestResolver.test.ts | 39 +++++++++++++++++++ .../api/2_0/resolver/TestResolver.test.ts | 39 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 federation/src/graphql/api/1_1/resolver/TestResolver.test.ts create mode 100644 federation/src/graphql/api/2_0/resolver/TestResolver.test.ts diff --git a/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts b/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts new file mode 100644 index 000000000..6b0a414f7 --- /dev/null +++ b/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { createTestClient } from 'apollo-server-testing' +import createServer from '@/server/createServer' + +let query: any + +// to do: We need a setup for the tests that closes the connection +let con: any + +beforeAll(async () => { + const server = await createServer() + con = server.con + query = createTestClient(server.apollo).query +}) + +afterAll(async () => { + await con.close() +}) + +describe('TestResolver', () => { + const getTestQuery = ` + query { + test { + api + } + } + ` + + describe('getTestApi', () => { + it('returns 1_1', async () => { + await expect(query({ query: getTestQuery })).resolves.toMatchObject({ + data: { + api: '1_1', + }, + }) + }) + }) +}) diff --git a/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts b/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts new file mode 100644 index 000000000..c2ce05994 --- /dev/null +++ b/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { createTestClient } from 'apollo-server-testing' +import createServer from '@/server/createServer' + +let query: any + +// to do: We need a setup for the tests that closes the connection +let con: any + +beforeAll(async () => { + const server = await createServer() + con = server.con + query = createTestClient(server.apollo).query +}) + +afterAll(async () => { + await con.close() +}) + +describe('TestResolver', () => { + const getTestQuery = ` + query { + test { + api + } + } + ` + + describe('getTestApi', () => { + it('returns 2_0', async () => { + await expect(query({ query: getTestQuery })).resolves.toMatchObject({ + data: { + api: '2_0', + }, + }) + }) + }) +}) From f35aab3504c67f0555c49734c33cea0160b9a207 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 9 Feb 2023 13:20:21 +0100 Subject: [PATCH 25/67] get unit tests running --- federation/jest.config.js | 7 ++++++- .../src/graphql/api/1_0/resolver/TestResolver.test.ts | 4 +++- .../src/graphql/api/1_1/resolver/TestResolver.test.ts | 4 +++- .../src/graphql/api/2_0/resolver/TestResolver.test.ts | 4 +++- federation/test/helpers.test.ts | 4 +++- federation/test/helpers.ts | 2 +- 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/federation/jest.config.js b/federation/jest.config.js index d6683d292..aa28622fd 100644 --- a/federation/jest.config.js +++ b/federation/jest.config.js @@ -3,7 +3,12 @@ module.exports = { verbose: true, preset: 'ts-jest', collectCoverage: true, - collectCoverageFrom: ['src/**/*.ts', '!**/node_modules/**', '!src/seeds/**', '!build/**'], + collectCoverageFrom: [ + 'src/**/*.ts', + '!**/node_modules/**', + '!src/seeds/**', + '!build/**', + ], setupFiles: ['/test/testSetup.ts'], setupFilesAfterEnv: ['/test/extensions.ts'], modulePathIgnorePatterns: ['/build/'], diff --git a/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts b/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts index 974f8db5e..e236ee5d8 100644 --- a/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts +++ b/federation/src/graphql/api/1_0/resolver/TestResolver.test.ts @@ -31,7 +31,9 @@ describe('TestResolver', () => { it('returns 1_0', async () => { await expect(query({ query: getTestQuery })).resolves.toMatchObject({ data: { - api: '1_0', + test: { + api: '1_0', + }, }, }) }) diff --git a/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts b/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts index 6b0a414f7..7c0a34bc9 100644 --- a/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts +++ b/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts @@ -31,7 +31,9 @@ describe('TestResolver', () => { it('returns 1_1', async () => { await expect(query({ query: getTestQuery })).resolves.toMatchObject({ data: { - api: '1_1', + test: { + api: '1_0', + }, }, }) }) diff --git a/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts b/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts index c2ce05994..26020f23a 100644 --- a/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts +++ b/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts @@ -31,7 +31,9 @@ describe('TestResolver', () => { it('returns 2_0', async () => { await expect(query({ query: getTestQuery })).resolves.toMatchObject({ data: { - api: '2_0', + test: { + api: '1_0', + }, }, }) }) diff --git a/federation/test/helpers.test.ts b/federation/test/helpers.test.ts index 69d8f3fa4..7e3eced5a 100644 --- a/federation/test/helpers.test.ts +++ b/federation/test/helpers.test.ts @@ -2,6 +2,8 @@ import { contributionDateFormatter } from '@test/helpers' describe('contributionDateFormatter', () => { it('formats the date correctly', () => { - expect(contributionDateFormatter(new Date('Thu Feb 29 2024 13:12:11'))).toEqual('2/29/2024') + expect( + contributionDateFormatter(new Date('Thu Feb 29 2024 13:12:11')) + ).toEqual('2/29/2024') }) }) diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts index b716aeb9a..9fb67d585 100644 --- a/federation/test/helpers.ts +++ b/federation/test/helpers.ts @@ -28,7 +28,7 @@ export const cleanDB = async () => { } export const testEnvironment = async (testLogger: any = logger) => { - const server = await createServer() + const server = await createServer(testLogger) const con = server.con const testClient = createTestClient(server.apollo) const mutate = testClient.mutate From 7ae99bf42b516bf2b0b815d97ef76c4dc38909fa Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 9 Feb 2023 13:28:03 +0100 Subject: [PATCH 26/67] set config federation api in unit tests --- federation/src/graphql/api/1_1/resolver/TestResolver.test.ts | 5 ++++- federation/src/graphql/api/2_0/resolver/TestResolver.test.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts b/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts index 7c0a34bc9..ad08345a7 100644 --- a/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts +++ b/federation/src/graphql/api/1_1/resolver/TestResolver.test.ts @@ -2,6 +2,9 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { createTestClient } from 'apollo-server-testing' import createServer from '@/server/createServer' +import CONFIG from '@/config' + +CONFIG.FEDERATION_API = '1_1' let query: any @@ -32,7 +35,7 @@ describe('TestResolver', () => { await expect(query({ query: getTestQuery })).resolves.toMatchObject({ data: { test: { - api: '1_0', + api: '1_1', }, }, }) diff --git a/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts b/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts index 26020f23a..02fd8c358 100644 --- a/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts +++ b/federation/src/graphql/api/2_0/resolver/TestResolver.test.ts @@ -2,6 +2,9 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { createTestClient } from 'apollo-server-testing' import createServer from '@/server/createServer' +import CONFIG from '@/config' + +CONFIG.FEDERATION_API = '2_0' let query: any @@ -32,7 +35,7 @@ describe('TestResolver', () => { await expect(query({ query: getTestQuery })).resolves.toMatchObject({ data: { test: { - api: '1_0', + api: '2_0', }, }, }) From bcf63ec334d6386d546fa4b9fe29550e0c2be573 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 9 Feb 2023 15:02:53 +0100 Subject: [PATCH 27/67] Deconstruct test for listContributions. --- .../resolver/ContributionResolver.test.ts | 290 ++++++++---------- backend/src/seeds/factory/creation.ts | 3 +- 2 files changed, 135 insertions(+), 158 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 11ecbcd47..a56e89ab0 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -51,6 +51,7 @@ 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' // mock account activation email to avoid console spam // mock account activation email to avoid console spam @@ -78,6 +79,7 @@ let inProgressContribution: any let contributionToConfirm: any let contributionToDeny: any let contributionToDelete: any +let bibiCreatedContribution: Contribution beforeAll(async () => { testEnv = await testEnvironment(logger, localization) @@ -102,7 +104,7 @@ describe('ContributionResolver', () => { await userFactory(testEnv, raeuberHotzenplotz) const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await creationFactory(testEnv, bibisCreation!) + bibiCreatedContribution = await creationFactory(testEnv, bibisCreation!) await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, @@ -183,7 +185,7 @@ describe('ContributionResolver', () => { describe('authenticated with valid user', () => { beforeAll(async () => { - bibi = await mutate({ + await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) @@ -296,7 +298,7 @@ describe('ContributionResolver', () => { type: EventProtocolType.CONTRIBUTION_CREATE, amount: expect.decimalEqual(100), contributionId: pendingContribution.data.createContribution.id, - userId: bibi.data.login.id, + userId: bibi.id, }), ) }) @@ -543,7 +545,7 @@ describe('ContributionResolver', () => { }) it('stores the update contribution event in the database', async () => { - bibi = await query({ + await query({ query: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) @@ -553,7 +555,7 @@ describe('ContributionResolver', () => { type: EventProtocolType.CONTRIBUTION_UPDATE, amount: expect.decimalEqual(10), contributionId: pendingContribution.data.createContribution.id, - userId: bibi.data.login.id, + userId: bibi.id, }), ) }) @@ -793,24 +795,19 @@ describe('ContributionResolver', () => { 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({ + query: deleteContribution, + variables: { + id: -1, + }, + }) + expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) }) }) describe('authenticated', () => { beforeAll(async () => { - bibi = await mutate({ + await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) @@ -822,18 +819,15 @@ describe('ContributionResolver', () => { describe('wrong contribution id', () => { it('returns an error', async () => { - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: -1, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Contribution not found for given id.')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: deleteContribution, + variables: { + id: -1, + }, + }) + expect(errorObjects).toMatchObject([ + new GraphQLError('Contribution not found for given id.'), + ]) }) it('logs the error found', () => { @@ -854,18 +848,15 @@ describe('ContributionResolver', () => { }) it('returns an error', async () => { - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: contributionToDelete.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('Can not delete contribution of another user')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await mutate({ + mutation: deleteContribution, + variables: { + id: contributionToDelete.data.createContribution.id, + }, + }) + expect(errorObjects).toMatchObject([ + new GraphQLError('Can not delete contribution of another user'), + ]) }) it('logs the error found', () => { @@ -886,20 +877,15 @@ describe('ContributionResolver', () => { }) it('deletes successfully', async () => { - await expect( - mutate({ - mutation: deleteContribution, - variables: { - id: contributionToDelete.data.createContribution.id, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - deleteContribution: true, - }, - }), - ) + const { + data: { deleteContribution: isDenied }, + }: { data: { deleteContribution: boolean } } = await mutate({ + mutation: deleteContribution, + variables: { + id: contributionToDelete.data.createContribution.id, + }, + }) + expect(isDenied).toBeTruthy() }) it('stores the delete contribution event in the database', async () => { @@ -908,7 +894,7 @@ describe('ContributionResolver', () => { type: EventProtocolType.CONTRIBUTION_DELETE, contributionId: contributionToDelete.data.createContribution.id, amount: expect.decimalEqual(100), - userId: peter.id, + userId: bibi.id, }), ) }) @@ -984,113 +970,103 @@ describe('ContributionResolver', () => { describe('filter confirmed is false', () => { it('returns creations', async () => { - await expect( - query({ - query: listContributions, - variables: { - currentPage: 1, - pageSize: 25, - order: 'DESC', - filterConfirmed: false, + 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([ + { + amount: '100', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listContributions: { - contributionCount: 6, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - amount: '100', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test contribution to deny', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test contribution to delete', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - ]), - }, + { + id: pendingContribution.data.createContribution.id, + memo: 'Test PENDING contribution update', + amount: '10', }, - }), - ) + { + id: contributionToDeny.data.createContribution.id, + memo: 'Test contribution to deny', + amount: '100', + }, + { + id: contributionToDelete.data.createContribution.id, + memo: 'Test contribution to delete', + amount: '100', + }, + { + id: inProgressContribution.data.createContribution.id, + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }, + { + id: bibiCreatedContribution.id, + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }, + ]), + }) }) }) 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: 4, - contributionList: expect.arrayContaining([ - expect.not.objectContaining({ - amount: '100', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test contribution to deny', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test contribution to delete', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - ]), - }, - }, - }), - ) + 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({ + 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.not.objectContaining({ + id: bibiCreatedContribution.id, + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + ]), + }) }) }) }) 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 } From 96feb30476b515d67d42a8bc5c3d3811c7583a22 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 9 Feb 2023 16:08:57 +0100 Subject: [PATCH 28/67] Deconstruct the listAllContributions test. --- .../resolver/ContributionResolver.test.ts | 1229 ++++++++--------- 1 file changed, 582 insertions(+), 647 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index a56e89ab0..eff7925b1 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -1075,21 +1075,16 @@ describe('ContributionResolver', () => { describe('listAllContribution', () => { 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 query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: null, + }, + }) + expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) }) }) @@ -1106,670 +1101,610 @@ describe('ContributionResolver', () => { }) 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.', - ), - ], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError | UserInputError] } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: ['NOT_VALID'], + }, + }) + expect(errorObjects).toMatchObject([ + 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.', - ), - ], - }), - ) + const { errors: errorObjects }: { errors: [Error] } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: [null], + }, + }) + expect(errorObjects).toMatchObject([ + 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.', - ), - ], - }), - ) + const { errors: errorObjects }: { errors: [Error] } = await query({ + query: listAllContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + statusFilter: [null, 'NOT_VALID'], + }, + }) + expect(errorObjects).toMatchObject([ + 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: 7, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - amount: '100', - state: 'CONFIRMED', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - 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', - }), - ]), - }, - }, - }), - ) + 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.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.not.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.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', + }), + ]), + }) }) 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: 7, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - amount: '100', - state: 'CONFIRMED', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - 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', - }), - ]), - }, - }, - }), - ) + 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.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.not.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.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', + }), + ]), + }) }) 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: 7, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - amount: '100', - state: 'CONFIRMED', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - 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', - }), - ]), - }, - }, - }), - ) + 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.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.not.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.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', + }), + ]), + }) }) 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: 3, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - amount: '100', - state: 'CONFIRMED', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - 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({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Whatever contribution', - amount: '166', - }), - ]), - }, - }, - }), - ) + 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.not.objectContaining({ + id: pendingContribution.data.createContribution.id, + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: contributionToDelete.data.createContribution.id, + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.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.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + ]), + }) }) 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({ - amount: '100', - state: 'CONFIRMED', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Whatever contribution', - amount: '166', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Whatever contribution', - amount: '166', - }), - ]), - }, - }, - }), - ) + 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({ + 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.not.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: contributionToDelete.data.createContribution.id, + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: inProgressContribution.data.createContribution.id, + state: 'IN_PROGRESS', + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + expect.not.objectContaining({ + id: bibiCreatedContribution.id, + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + ]), + }) }) 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: 1, - contributionList: expect.arrayContaining([ - expect.not.objectContaining({ - amount: '100', - state: 'CONFIRMED', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Whatever contribution', - amount: '166', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Whatever contribution', - amount: '166', - }), - ]), - }, - }, - }), - ) + 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({ + amount: '100', + state: 'CONFIRMED', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', + }), + expect.not.objectContaining({ + id: pendingContribution.data.createContribution.id, + state: 'PENDING', + memo: 'Test PENDING contribution update', + amount: '10', + }), + expect.not.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.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.not.objectContaining({ + id: bibiCreatedContribution.id, + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + ]), + }) }) 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: 2, - contributionList: expect.arrayContaining([ - expect.not.objectContaining({ - amount: '100', - state: 'CONFIRMED', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.not.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', - }), - ]), - }, - }, - }), - ) + 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.not.objectContaining({ + amount: '100', + state: 'CONFIRMED', + id: contributionToConfirm.data.createContribution.id, + memo: 'Test contribution to confirm', + }), + expect.not.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.not.objectContaining({ + id: contributionToDelete.data.createContribution.id, + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.objectContaining({ + id: inProgressContribution.data.createContribution.id, + state: 'IN_PROGRESS', + memo: 'Test IN_PROGRESS contribution', + amount: '100', + }), + expect.not.objectContaining({ + id: bibiCreatedContribution.id, + state: 'CONFIRMED', + memo: 'Herzlich Willkommen bei Gradido!', + amount: '1000', + }), + expect.not.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', + }), + ]), + }) }) - 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: [], - }, - }, - }), - ) + 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).toMatchObject({ + contributionCount: 0, + contributionList: [], + }) }) 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: 4, - contributionList: expect.arrayContaining([ - expect.objectContaining({ - amount: '100', - state: 'CONFIRMED', - id: expect.any(Number), - memo: 'Test contribution to confirm', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Whatever contribution', - amount: '166', - }), - expect.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Whatever contribution', - amount: '166', - }), - ]), - }, - }, - }), - ) + 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.not.objectContaining({ + id: contributionToDeny.data.createContribution.id, + state: 'DENIED', + memo: 'Test contribution to deny', + amount: '100', + }), + expect.not.objectContaining({ + id: contributionToDelete.data.createContribution.id, + state: 'DELETED', + memo: 'Test contribution to delete', + amount: '100', + }), + expect.not.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.not.objectContaining({ + id: expect.any(Number), + state: 'DENIED', + memo: 'Whatever contribution', + amount: '166', + }), + expect.objectContaining({ + id: expect.any(Number), + state: 'CONFIRMED', + memo: 'Whatever contribution', + amount: '166', + }), + ]), + }) }) }) }) From e5025a906cd649a3094b6822b8ea1232855fded0 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 9 Feb 2023 16:24:18 +0100 Subject: [PATCH 29/67] Deconstruct not authenticated. --- .../resolver/ContributionResolver.test.ts | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index eff7925b1..93a31fb77 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -938,21 +938,16 @@ describe('ContributionResolver', () => { 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')], - }), - ) + const { errors: errorObjects }: { errors: [GraphQLError] } = await query({ + query: listContributions, + variables: { + currentPage: 1, + pageSize: 25, + order: 'DESC', + filterConfirmed: false, + }, + }) + expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) }) }) From 0473a7df8169293c7421d67916420959dbee4e2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Thu, 9 Feb 2023 17:08:50 +0100 Subject: [PATCH 30/67] rework PR-comments --- docker-compose.yml | 4 +-- federation/Dockerfile | 3 +- federation/jest.config.js | 2 +- federation/src/config/index.ts | 4 +-- federation/test/extensions.ts | 33 ------------------ federation/test/helpers.test.ts | 9 ----- federation/test/helpers.ts | 59 --------------------------------- 7 files changed, 7 insertions(+), 107 deletions(-) delete mode 100644 federation/test/extensions.ts delete mode 100644 federation/test/helpers.test.ts delete mode 100644 federation/test/helpers.ts diff --git a/docker-compose.yml b/docker-compose.yml index 4105ef04a..7be98099d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -163,14 +163,14 @@ services: - internal-net - external-net ports: - - 5000:5000 + - 5010:5010 depends_on: - mariadb restart: always environment: # Envs used in Dockerfile # - DOCKER_WORKDIR="/app" - - PORT=5000 + - PORT=5010 - BUILD_DATE - BUILD_VERSION - BUILD_COMMIT diff --git a/federation/Dockerfile b/federation/Dockerfile index e2d74c817..959252d29 100644 --- a/federation/Dockerfile +++ b/federation/Dockerfile @@ -15,7 +15,8 @@ ENV BUILD_COMMIT="0000000" ## SET NODE_ENV ENV NODE_ENV="production" ## App relevant Envs -ENV PORT="5000" +ENV PORT="5010" +# ENV PORT="${env.FEDERATION_PORT}" # Labels LABEL org.label-schema.build-date="${BUILD_DATE}" diff --git a/federation/jest.config.js b/federation/jest.config.js index aa28622fd..742f35fbb 100644 --- a/federation/jest.config.js +++ b/federation/jest.config.js @@ -10,7 +10,7 @@ module.exports = { '!build/**', ], setupFiles: ['/test/testSetup.ts'], - setupFilesAfterEnv: ['/test/extensions.ts'], + setupFilesAfterEnv: [], modulePathIgnorePatterns: ['/build/'], moduleNameMapper: { '@/(.*)': '/src/$1', diff --git a/federation/src/config/index.ts b/federation/src/config/index.ts index 588f52c60..84259f09a 100644 --- a/federation/src/config/index.ts +++ b/federation/src/config/index.ts @@ -24,7 +24,7 @@ const constants = { } const server = { - PORT: process.env.PORT || 5000, + PORT: process.env.PORT || 5010, // JWT_SECRET: process.env.JWT_SECRET || 'secret123', // JWT_EXPIRES_IN: process.env.JWT_EXPIRES_IN || '10m', GRAPHIQL: process.env.GRAPHIQL === 'true' || false, @@ -73,7 +73,7 @@ if ( const federation = { // FEDERATION_DHT_TOPIC: process.env.FEDERATION_DHT_TOPIC || null, // FEDERATION_DHT_SEED: process.env.FEDERATION_DHT_SEED || null, - FEDERATION_PORT: process.env.FEDERATION_PORT || 5000, + FEDERATION_PORT: process.env.FEDERATION_PORT || 5010, FEDERATION_API: process.env.FEDERATION_API || '1_0', FEDERATION_COMMUNITY_URL: process.env.FEDERATION_COMMUNITY_URL || null, } diff --git a/federation/test/extensions.ts b/federation/test/extensions.ts deleted file mode 100644 index 69c2ff7a6..000000000 --- a/federation/test/extensions.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-interface */ - -import Decimal from 'decimal.js-light' - -expect.extend({ - decimalEqual(received, value) { - const pass = new Decimal(value).equals(received.toString()) - if (pass) { - return { - message: () => `expected ${received} to not equal ${value}`, - pass: true, - } - } else { - return { - message: () => `expected ${received} to equal ${value}`, - pass: false, - } - } - }, -}) - -interface CustomMatchers { - decimalEqual(value: number): R -} - -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace jest { - interface Expect extends CustomMatchers {} - interface Matchers extends CustomMatchers {} - interface InverseAsymmetricMatchers extends CustomMatchers {} - } -} diff --git a/federation/test/helpers.test.ts b/federation/test/helpers.test.ts deleted file mode 100644 index 7e3eced5a..000000000 --- a/federation/test/helpers.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { contributionDateFormatter } from '@test/helpers' - -describe('contributionDateFormatter', () => { - it('formats the date correctly', () => { - expect( - contributionDateFormatter(new Date('Thu Feb 29 2024 13:12:11')) - ).toEqual('2/29/2024') - }) -}) diff --git a/federation/test/helpers.ts b/federation/test/helpers.ts deleted file mode 100644 index 9fb67d585..000000000 --- a/federation/test/helpers.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ - -import { createTestClient } from 'apollo-server-testing' -import createServer from '../src/server/createServer' -import { initialize } from '@dbTools/helpers' -import { entities } from '@entity/index' -import { logger } from './testSetup' - -export const headerPushMock = jest.fn((t) => { - context.token = t.value -}) - -const context = { - token: '', - setHeaders: { - push: headerPushMock, - forEach: jest.fn(), - }, - clientTimezoneOffset: 0, -} - -export const cleanDB = async () => { - // this only works as lond we do not have foreign key constraints - for (let i = 0; i < entities.length; i++) { - await resetEntity(entities[i]) - } -} - -export const testEnvironment = async (testLogger: any = logger) => { - const server = await createServer(testLogger) - const con = server.con - const testClient = createTestClient(server.apollo) - const mutate = testClient.mutate - const query = testClient.query - await initialize() - return { mutate, query, con } -} - -export const resetEntity = async (entity: any) => { - const items = await entity.find({ withDeleted: true }) - if (items.length > 0) { - const ids = items.map((i: any) => i.id) - await entity.delete(ids) - } -} - -export const resetToken = () => { - context.token = '' -} - -// format date string as it comes from the frontend for the contribution date -export const contributionDateFormatter = (date: Date): string => { - return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}` -} - -export const setClientTimezoneOffset = (offset: number): void => { - context.clientTimezoneOffset = offset -} From 6ff43e2d4c4355261e7c6cd6676d5adb1a53a110 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 9 Feb 2023 17:10:27 +0100 Subject: [PATCH 31/67] Change the expect.not.objectContaining to the state instead of the whole object. --- .../resolver/ContributionResolver.test.ts | 225 ++++-------------- 1 file changed, 49 insertions(+), 176 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 93a31fb77..5b2c0fcaf 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -96,11 +96,10 @@ afterAll(async () => { describe('ContributionResolver', () => { let bibi: any - let peter: any beforeAll(async () => { bibi = await userFactory(testEnv, bibiBloxberg) - admin = peter = await userFactory(testEnv, peterLustig) + 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 @@ -1163,6 +1162,9 @@ describe('ContributionResolver', () => { expect(contributionListObject).toMatchObject({ contributionCount: 7, contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'DELETED', + }), expect.objectContaining({ amount: '100', state: 'CONFIRMED', @@ -1181,12 +1183,6 @@ describe('ContributionResolver', () => { memo: 'Test contribution to deny', amount: '100', }), - expect.not.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', @@ -1230,6 +1226,9 @@ describe('ContributionResolver', () => { expect(contributionListObject).toMatchObject({ contributionCount: 7, contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'DELETED', + }), expect.objectContaining({ amount: '100', state: 'CONFIRMED', @@ -1248,12 +1247,6 @@ describe('ContributionResolver', () => { memo: 'Test contribution to deny', amount: '100', }), - expect.not.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', @@ -1297,6 +1290,9 @@ describe('ContributionResolver', () => { expect(contributionListObject).toMatchObject({ contributionCount: 7, contributionList: expect.arrayContaining([ + expect.not.objectContaining({ + state: 'DELETED', + }), expect.objectContaining({ amount: '100', state: 'CONFIRMED', @@ -1315,12 +1311,6 @@ describe('ContributionResolver', () => { memo: 'Test contribution to deny', amount: '100', }), - expect.not.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', @@ -1370,30 +1360,6 @@ describe('ContributionResolver', () => { id: contributionToConfirm.data.createContribution.id, memo: 'Test contribution to confirm', }), - expect.not.objectContaining({ - id: pendingContribution.data.createContribution.id, - state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', - }), - expect.not.objectContaining({ - id: contributionToDeny.data.createContribution.id, - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: contributionToDelete.data.createContribution.id, - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.not.objectContaining({ - id: inProgressContribution.data.createContribution.id, - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), expect.objectContaining({ id: bibiCreatedContribution.id, state: 'CONFIRMED', @@ -1407,10 +1373,16 @@ describe('ContributionResolver', () => { amount: '166', }), expect.not.objectContaining({ - id: expect.any(Number), + state: 'PENDING', + }), + expect.not.objectContaining({ state: 'DENIED', - memo: 'Whatever contribution', - amount: '166', + }), + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.not.objectContaining({ + state: 'IN_PROGRESS', }), ]), }) @@ -1432,10 +1404,16 @@ describe('ContributionResolver', () => { contributionCount: 1, contributionList: expect.arrayContaining([ expect.not.objectContaining({ - amount: '100', state: 'CONFIRMED', - id: contributionToConfirm.data.createContribution.id, - memo: 'Test contribution to confirm', + }), + expect.not.objectContaining({ + state: 'DENIED', + }), + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.not.objectContaining({ + state: 'IN_PROGRESS', }), expect.objectContaining({ id: pendingContribution.data.createContribution.id, @@ -1443,42 +1421,6 @@ describe('ContributionResolver', () => { memo: 'Test PENDING contribution update', amount: '10', }), - expect.not.objectContaining({ - id: contributionToDeny.data.createContribution.id, - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: contributionToDelete.data.createContribution.id, - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.not.objectContaining({ - id: inProgressContribution.data.createContribution.id, - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.not.objectContaining({ - id: bibiCreatedContribution.id, - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Whatever contribution', - amount: '166', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Whatever contribution', - amount: '166', - }), ]), }) }) @@ -1499,28 +1441,16 @@ describe('ContributionResolver', () => { contributionCount: 1, contributionList: expect.arrayContaining([ expect.not.objectContaining({ - amount: '100', state: 'CONFIRMED', - id: contributionToConfirm.data.createContribution.id, - memo: 'Test contribution to confirm', }), expect.not.objectContaining({ - id: pendingContribution.data.createContribution.id, state: 'PENDING', - memo: 'Test PENDING contribution update', - amount: '10', }), expect.not.objectContaining({ - id: contributionToDeny.data.createContribution.id, state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', }), expect.not.objectContaining({ - id: contributionToDelete.data.createContribution.id, state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', }), expect.objectContaining({ id: inProgressContribution.data.createContribution.id, @@ -1528,24 +1458,6 @@ describe('ContributionResolver', () => { memo: 'Test IN_PROGRESS contribution', amount: '100', }), - expect.not.objectContaining({ - id: bibiCreatedContribution.id, - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'CONFIRMED', - memo: 'Whatever contribution', - amount: '166', - }), - expect.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Whatever contribution', - amount: '166', - }), ]), }) }) @@ -1565,54 +1477,30 @@ describe('ContributionResolver', () => { expect(contributionListObject).toMatchObject({ contributionCount: 2, contributionList: expect.arrayContaining([ - expect.not.objectContaining({ - amount: '100', - state: 'CONFIRMED', - id: contributionToConfirm.data.createContribution.id, - memo: 'Test contribution to confirm', - }), - expect.not.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.not.objectContaining({ - id: contributionToDelete.data.createContribution.id, - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.not.objectContaining({ - id: inProgressContribution.data.createContribution.id, - state: 'IN_PROGRESS', - memo: 'Test IN_PROGRESS contribution', - amount: '100', - }), - expect.not.objectContaining({ - id: bibiCreatedContribution.id, - state: 'CONFIRMED', - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), - expect.not.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.not.objectContaining({ + state: 'CONFIRMED', + }), + expect.not.objectContaining({ + state: 'DELETED', + }), + expect.not.objectContaining({ + state: 'IN_PROGRESS', + }), + expect.not.objectContaining({ + state: 'PENDING', + }), ]), }) }) @@ -1662,42 +1550,27 @@ describe('ContributionResolver', () => { memo: 'Test PENDING contribution update', amount: '10', }), - expect.not.objectContaining({ - id: contributionToDeny.data.createContribution.id, - state: 'DENIED', - memo: 'Test contribution to deny', - amount: '100', - }), - expect.not.objectContaining({ - id: contributionToDelete.data.createContribution.id, - state: 'DELETED', - memo: 'Test contribution to delete', - amount: '100', - }), - expect.not.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.not.objectContaining({ - id: expect.any(Number), - state: 'DENIED', - memo: 'Whatever contribution', - amount: '166', - }), 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', + }), ]), }) }) From a615c741c28bdd49c25295af237b58ec1d04f612 Mon Sep 17 00:00:00 2001 From: elweyn Date: Tue, 14 Feb 2023 07:55:38 +0100 Subject: [PATCH 32/67] Add tests that EVENT is stored in DB and has right values in userId. --- .../src/graphql/resolver/ContributionResolver.test.ts | 9 +++++++++ backend/src/graphql/resolver/ContributionResolver.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index fe98c0f6f..e8cdad748 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -815,6 +815,15 @@ describe('ContributionResolver', () => { }) expect(isDenied).toBeTruthy() }) + + it('stores the admin deny contribution event in the database', async () => { + await expect(EventProtocol.find()).resolves.toContainEqual( + expect.objectContaining({ + type: EventProtocolType.ADMIN_CONTRIBUTION_DENY, + userId: admin.id, + }), + ) + }) }) }) }) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 926742d8a..a18be7c38 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -732,7 +732,7 @@ export class ContributionResolver { const event = new Event() const eventAdminContributionDeny = new EventAdminContributionDeny() - eventAdminContributionDeny.userId = contributionToUpdate.userId + eventAdminContributionDeny.userId = moderator.id eventAdminContributionDeny.amount = contributionToUpdate.amount eventAdminContributionDeny.contributionId = contributionToUpdate.id await writeEvent(event.setEventAdminContributionDeny(eventAdminContributionDeny)) From 9ff045fdbfe7094a5e78dd6ab59a32307f66a50d Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 14 Feb 2023 20:31:56 +0100 Subject: [PATCH 33/67] test for authentication on TransactionLinkResolver --- .../resolver/TransactionLinkResolver.test.ts | 660 +++++++++--------- 1 file changed, 346 insertions(+), 314 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index f60ab45d0..09f2f9a02 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -53,65 +53,81 @@ afterAll(async () => { describe('TransactionLinkResolver', () => { describe('createTransactionLink', () => { - beforeAll(async () => { - await mutate({ - mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + describe('unauthenticated', () => { + it('throws an error', async () => { + jest.clearAllMocks() + resetToken() + await expect( + mutate({ mutation: createTransactionLink, variables: { amount: 0, memo: 'Test' } }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) }) }) - it('throws error when amount is zero', async () => { - jest.clearAllMocks() - await expect( - mutate({ - mutation: createTransactionLink, - variables: { - amount: 0, - memo: 'Test', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Amount must be a positive number')], + describe('authenticated', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) }) - }) - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(0)) - }) - it('throws error when amount is negative', async () => { - jest.clearAllMocks() - await expect( - mutate({ - mutation: createTransactionLink, - variables: { - amount: -10, - memo: 'Test', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Amount must be a positive number')], + it('throws error when amount is zero', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: createTransactionLink, + variables: { + amount: 0, + memo: 'Test', + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('Amount must be a positive number')], + }) + }) + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(0)) }) - }) - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(-10)) - }) - it('throws error when user has not enough GDD', async () => { - jest.clearAllMocks() - await expect( - mutate({ - mutation: createTransactionLink, - variables: { - amount: 1001, - memo: 'Test', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('User has not enough GDD')], + it('throws error when amount is negative', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: createTransactionLink, + variables: { + amount: -10, + memo: 'Test', + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('Amount must be a positive number')], + }) + }) + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(-10)) + }) + + it('throws error when user has not enough GDD', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: createTransactionLink, + variables: { + amount: 1001, + memo: 'Test', + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('User has not enough GDD')], + }) + }) + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith('User has not enough GDD', expect.any(Number)) }) - }) - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('User has not enough GDD', expect.any(Number)) }) }) @@ -121,236 +137,37 @@ describe('TransactionLinkResolver', () => { resetToken() }) - describe('contributionLink', () => { - describe('input not valid', () => { - beforeAll(async () => { - await mutate({ - mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, - }) - }) - - it('throws error when link does not exists', async () => { - jest.clearAllMocks() - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-123456', - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Creation from contribution link was not successful')], - }) - }) - - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith( - 'No contribution link found to given code', - 'CL-123456', - ) - expect(logger.error).toBeCalledWith( - 'Creation from contribution link was not successful', - new Error('No contribution link found to given code'), - ) - }) - - const now = new Date() - const validFrom = new Date(now.getFullYear() + 1, 0, 1) - - it('throws error when link is not valid yet', async () => { - jest.clearAllMocks() - const { - data: { createContributionLink: contributionLink }, - } = await mutate({ - mutation: createContributionLink, - variables: { - amount: new Decimal(5), - name: 'Daily Contribution Link', - memo: 'Thank you for contribute daily to the community', - cycle: 'DAILY', - validFrom: validFrom.toISOString(), - validTo: new Date(now.getFullYear() + 1, 11, 31, 23, 59, 59, 999).toISOString(), - maxAmountPerMonth: new Decimal(200), - maxPerCycle: 1, - }, - }) - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + contributionLink.code, - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Creation from contribution link was not successful')], - }) - await resetEntity(DbContributionLink) - }) - - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Contribution link is not valid yet', validFrom) - expect(logger.error).toBeCalledWith( - 'Creation from contribution link was not successful', - new Error('Contribution link is not valid yet'), - ) - }) - - it('throws error when contributionLink cycle is invalid', async () => { - jest.clearAllMocks() - const now = new Date() - const { - data: { createContributionLink: contributionLink }, - } = await mutate({ - mutation: createContributionLink, - variables: { - amount: new Decimal(5), - name: 'Daily Contribution Link', - memo: 'Thank you for contribute daily to the community', - cycle: 'INVALID', - validFrom: new Date(now.getFullYear(), 0, 1).toISOString(), - validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(), - maxAmountPerMonth: new Decimal(200), - maxPerCycle: 1, - }, - }) - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + contributionLink.code, - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Creation from contribution link was not successful')], - }) - await resetEntity(DbContributionLink) - }) - - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Contribution link has unknown cycle', 'INVALID') - expect(logger.error).toBeCalledWith( - 'Creation from contribution link was not successful', - new Error('Contribution link has unknown cycle'), - ) - }) - - const validTo = new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 0) - it('throws error when link is no longer valid', async () => { - jest.clearAllMocks() - const { - data: { createContributionLink: contributionLink }, - } = await mutate({ - mutation: createContributionLink, - variables: { - amount: new Decimal(5), - name: 'Daily Contribution Link', - memo: 'Thank you for contribute daily to the community', - cycle: 'DAILY', - validFrom: new Date(now.getFullYear() - 1, 0, 1).toISOString(), - validTo: validTo.toISOString(), - maxAmountPerMonth: new Decimal(200), - maxPerCycle: 1, - }, - }) - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + contributionLink.code, - }, - }), - ).resolves.toMatchObject({ - errors: [new GraphQLError('Creation from contribution link was not successful')], - }) - await resetEntity(DbContributionLink) - }) - - it('logs the error thrown', () => { - expect(logger.error).toBeCalledWith('Contribution link is no longer valid', validTo) - expect(logger.error).toBeCalledWith( - 'Creation from contribution link was not successful', - new Error('Contribution link is no longer valid'), - ) - }) + describe('unauthenticated', () => { + it('throws an error', async () => { + jest.clearAllMocks() + resetToken() + await expect( + mutate({ mutation: redeemTransactionLink, variables: { code: 'CL-123456' } }), + ).resolves.toEqual( + expect.objectContaining({ + errors: [new GraphQLError('401 Unauthorized')], + }), + ) }) + }) - // TODO: have this test separated into a transactionLink and a contributionLink part - describe('redeem daily Contribution Link', () => { - const now = new Date() - let contributionLink: DbContributionLink | undefined - let contribution: UnconfirmedContribution | undefined - - beforeAll(async () => { - await mutate({ - mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, - }) - await mutate({ - mutation: createContributionLink, - variables: { - amount: new Decimal(5), - name: 'Daily Contribution Link', - memo: 'Thank you for contribute daily to the community', - cycle: 'DAILY', - validFrom: new Date(now.getFullYear(), 0, 1).toISOString(), - validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(), - maxAmountPerMonth: new Decimal(200), - maxPerCycle: 1, - }, - }) - }) - - it('has a daily contribution link in the database', async () => { - const cls = await DbContributionLink.find() - expect(cls).toHaveLength(1) - contributionLink = cls[0] - expect(contributionLink).toEqual( - expect.objectContaining({ - id: expect.any(Number), - name: 'Daily Contribution Link', - memo: 'Thank you for contribute daily to the community', - validFrom: new Date(now.getFullYear(), 0, 1), - validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 0), - cycle: 'DAILY', - 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(5), - maxAmountPerMonth: expect.decimalEqual(200), - }), - ) - }) - - describe('user has pending contribution of 1000 GDD', () => { + describe('authenticated', () => { + describe('contributionLink', () => { + describe('input not valid', () => { beforeAll(async () => { await mutate({ mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) - const result = await mutate({ - mutation: createContribution, - variables: { - amount: new Decimal(1000), - memo: 'I was brewing potions for the community the whole month', - creationDate: now.toISOString(), - }, - }) - contribution = result.data.createContribution }) - it('does not allow the user to redeem the contribution link', async () => { + it('throws error when link does not exists', async () => { jest.clearAllMocks() await expect( mutate({ mutation: redeemTransactionLink, variables: { - code: 'CL-' + (contributionLink ? contributionLink.code : ''), + code: 'CL-123456', }, }), ).resolves.toMatchObject({ @@ -359,85 +176,247 @@ describe('TransactionLinkResolver', () => { }) it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith( + 'No contribution link found to given code', + 'CL-123456', + ) expect(logger.error).toBeCalledWith( 'Creation from contribution link was not successful', - new Error( - 'The amount (5 GDD) to be created exceeds the amount (0 GDD) still available for this month.', - ), + new Error('No contribution link found to given code'), ) }) - }) - describe('user has no pending contributions that would not allow to redeem the link', () => { - beforeAll(async () => { - await mutate({ - mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, - }) - await mutate({ - mutation: updateContribution, - variables: { - contributionId: contribution ? contribution.id : -1, - amount: new Decimal(800), - memo: 'I was brewing potions for the community the whole month', - creationDate: now.toISOString(), - }, - }) - }) + const now = new Date() + const validFrom = new Date(now.getFullYear() + 1, 0, 1) - it('allows the user to redeem the contribution link', async () => { - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + (contributionLink ? contributionLink.code : ''), - }, - }), - ).resolves.toMatchObject({ - data: { - redeemTransactionLink: true, - }, - errors: undefined, - }) - }) - - it('does not allow the user to redeem the contribution link a second time on the same day', async () => { + it('throws error when link is not valid yet', async () => { jest.clearAllMocks() + const { + data: { createContributionLink: contributionLink }, + } = await mutate({ + mutation: createContributionLink, + variables: { + amount: new Decimal(5), + name: 'Daily Contribution Link', + memo: 'Thank you for contribute daily to the community', + cycle: 'DAILY', + validFrom: validFrom.toISOString(), + validTo: new Date(now.getFullYear() + 1, 11, 31, 23, 59, 59, 999).toISOString(), + maxAmountPerMonth: new Decimal(200), + maxPerCycle: 1, + }, + }) await expect( mutate({ mutation: redeemTransactionLink, variables: { - code: 'CL-' + (contributionLink ? contributionLink.code : ''), + code: 'CL-' + contributionLink.code, }, }), ).resolves.toMatchObject({ errors: [new GraphQLError('Creation from contribution link was not successful')], }) + await resetEntity(DbContributionLink) }) it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith('Contribution link is not valid yet', validFrom) expect(logger.error).toBeCalledWith( 'Creation from contribution link was not successful', - new Error('Contribution link already redeemed today'), + new Error('Contribution link is not valid yet'), ) }) - describe('after one day', () => { + it('throws error when contributionLink cycle is invalid', async () => { + jest.clearAllMocks() + const now = new Date() + const { + data: { createContributionLink: contributionLink }, + } = await mutate({ + mutation: createContributionLink, + variables: { + amount: new Decimal(5), + name: 'Daily Contribution Link', + memo: 'Thank you for contribute daily to the community', + cycle: 'INVALID', + validFrom: new Date(now.getFullYear(), 0, 1).toISOString(), + validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(), + maxAmountPerMonth: new Decimal(200), + maxPerCycle: 1, + }, + }) + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + contributionLink.code, + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('Creation from contribution link was not successful')], + }) + await resetEntity(DbContributionLink) + }) + + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith('Contribution link has unknown cycle', 'INVALID') + expect(logger.error).toBeCalledWith( + 'Creation from contribution link was not successful', + new Error('Contribution link has unknown cycle'), + ) + }) + + const validTo = new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 0) + it('throws error when link is no longer valid', async () => { + jest.clearAllMocks() + const { + data: { createContributionLink: contributionLink }, + } = await mutate({ + mutation: createContributionLink, + variables: { + amount: new Decimal(5), + name: 'Daily Contribution Link', + memo: 'Thank you for contribute daily to the community', + cycle: 'DAILY', + validFrom: new Date(now.getFullYear() - 1, 0, 1).toISOString(), + validTo: validTo.toISOString(), + maxAmountPerMonth: new Decimal(200), + maxPerCycle: 1, + }, + }) + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + contributionLink.code, + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('Creation from contribution link was not successful')], + }) + await resetEntity(DbContributionLink) + }) + + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith('Contribution link is no longer valid', validTo) + expect(logger.error).toBeCalledWith( + 'Creation from contribution link was not successful', + new Error('Contribution link is no longer valid'), + ) + }) + }) + + // TODO: have this test separated into a transactionLink and a contributionLink part + describe('redeem daily Contribution Link', () => { + const now = new Date() + let contributionLink: DbContributionLink | undefined + let contribution: UnconfirmedContribution | undefined + + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: createContributionLink, + variables: { + amount: new Decimal(5), + name: 'Daily Contribution Link', + memo: 'Thank you for contribute daily to the community', + cycle: 'DAILY', + validFrom: new Date(now.getFullYear(), 0, 1).toISOString(), + validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(), + maxAmountPerMonth: new Decimal(200), + maxPerCycle: 1, + }, + }) + }) + + it('has a daily contribution link in the database', async () => { + const cls = await DbContributionLink.find() + expect(cls).toHaveLength(1) + contributionLink = cls[0] + expect(contributionLink).toEqual( + expect.objectContaining({ + id: expect.any(Number), + name: 'Daily Contribution Link', + memo: 'Thank you for contribute daily to the community', + validFrom: new Date(now.getFullYear(), 0, 1), + validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 0), + cycle: 'DAILY', + 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(5), + maxAmountPerMonth: expect.decimalEqual(200), + }), + ) + }) + + describe('user has pending contribution of 1000 GDD', () => { beforeAll(async () => { - jest.useFakeTimers() - setTimeout(jest.fn(), 1000 * 60 * 60 * 24) - jest.runAllTimers() await mutate({ mutation: login, variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, }) + const result = await mutate({ + mutation: createContribution, + variables: { + amount: new Decimal(1000), + memo: 'I was brewing potions for the community the whole month', + creationDate: now.toISOString(), + }, + }) + contribution = result.data.createContribution }) - afterAll(() => { - jest.useRealTimers() + it('does not allow the user to redeem the contribution link', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('Creation from contribution link was not successful')], + }) }) - it('allows the user to redeem the contribution link again', async () => { + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith( + 'Creation from contribution link was not successful', + new Error( + 'The amount (5 GDD) to be created exceeds the amount (0 GDD) still available for this month.', + ), + ) + }) + }) + + describe('user has no pending contributions that would not allow to redeem the link', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: updateContribution, + variables: { + contributionId: contribution ? contribution.id : -1, + amount: new Decimal(800), + memo: 'I was brewing potions for the community the whole month', + creationDate: now.toISOString(), + }, + }) + }) + + it('allows the user to redeem the contribution link', async () => { await expect( mutate({ mutation: redeemTransactionLink, @@ -473,6 +452,59 @@ describe('TransactionLinkResolver', () => { new Error('Contribution link already redeemed today'), ) }) + + describe('after one day', () => { + beforeAll(async () => { + jest.useFakeTimers() + setTimeout(jest.fn(), 1000 * 60 * 60 * 24) + jest.runAllTimers() + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + afterAll(() => { + jest.useRealTimers() + }) + + it('allows the user to redeem the contribution link again', async () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + data: { + redeemTransactionLink: true, + }, + errors: undefined, + }) + }) + + it('does not allow the user to redeem the contribution link a second time on the same day', async () => { + jest.clearAllMocks() + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('Creation from contribution link was not successful')], + }) + }) + + it('logs the error thrown', () => { + expect(logger.error).toBeCalledWith( + 'Creation from contribution link was not successful', + new Error('Contribution link already redeemed today'), + ) + }) + }) }) }) }) From 9b3449ff9317c804ab1f1d44ad60a82b5179853a Mon Sep 17 00:00:00 2001 From: clauspeterhuebner <86960882+clauspeterhuebner@users.noreply.github.com> Date: Tue, 14 Feb 2023 21:49:35 +0100 Subject: [PATCH 34/67] Update .github/workflows/test_federation.yml Co-authored-by: Hannes Heine --- .github/workflows/test_federation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_federation.yml b/.github/workflows/test_federation.yml index 240e6313b..4cddcb53e 100644 --- a/.github/workflows/test_federation.yml +++ b/.github/workflows/test_federation.yml @@ -94,5 +94,5 @@ jobs: type: lcov #result_path: ./federation/coverage/lcov.info result_path: ./coverage/lcov.info - min_coverage: 79 + min_coverage: 73 token: ${{ github.token }} From cf24708757f9649b3c845f6b370be03dbaf4f6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Tue, 14 Feb 2023 21:51:02 +0100 Subject: [PATCH 35/67] rework PR-comments --- .../src/graphql/api/1_0/resolver/Test2Resolver.ts | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 federation/src/graphql/api/1_0/resolver/Test2Resolver.ts diff --git a/federation/src/graphql/api/1_0/resolver/Test2Resolver.ts b/federation/src/graphql/api/1_0/resolver/Test2Resolver.ts deleted file mode 100644 index 09797c6db..000000000 --- a/federation/src/graphql/api/1_0/resolver/Test2Resolver.ts +++ /dev/null @@ -1,14 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { Query, Resolver } from 'type-graphql' -import { federationLogger as logger } from '@/server/logger' -import { GetTestApiResult } from '../../GetTestApiResult' - -@Resolver() -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export class Test2Resolver { - @Query(() => GetTestApiResult) - async test2(): Promise { - logger.info(`test api 2 1_0`) - return new GetTestApiResult('1_0') - } -} From 491dd12ae0b6442679d312244881f0664acac91d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Tue, 14 Feb 2023 22:12:58 +0100 Subject: [PATCH 36/67] reduce coverage --- .github/workflows/test_federation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_federation.yml b/.github/workflows/test_federation.yml index 4cddcb53e..2da78758e 100644 --- a/.github/workflows/test_federation.yml +++ b/.github/workflows/test_federation.yml @@ -94,5 +94,5 @@ jobs: type: lcov #result_path: ./federation/coverage/lcov.info result_path: ./coverage/lcov.info - min_coverage: 73 + min_coverage: 72 token: ${{ github.token }} From 34fe6b4e90a5fc6233105f1b56239459bb5e1a00 Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 15 Feb 2023 09:44:00 +0100 Subject: [PATCH 37/67] Rework reviews, add the properties used in the ui for listContributions. --- .../resolver/ContributionResolver.test.ts | 61 +++++++++++-------- backend/src/seeds/graphql/queries.ts | 9 +++ 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 6031bac30..a467cd9c9 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -813,14 +813,15 @@ describe('ContributionResolver', () => { id: contributionToDeny.data.createContribution.id, }, }) - expect(isDenied).toBeTruthy() + expect(isDenied).toBe(true) }) it('stores the admin deny contribution event in the database', async () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_DENY, - userId: admin.id, + userId: bibi.id, + xUserId: admin.id, }), ) }) @@ -925,7 +926,7 @@ describe('ContributionResolver', () => { id: contributionToDelete.data.createContribution.id, }, }) - expect(isDenied).toBeTruthy() + expect(isDenied).toBe(true) }) it('stores the CONTRIBUTION_DELETE event in the database', async () => { @@ -1022,38 +1023,39 @@ describe('ContributionResolver', () => { 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) }) }) @@ -1074,37 +1076,35 @@ describe('ContributionResolver', () => { contributionCount: 4, contributionList: expect.arrayContaining([ expect.not.objectContaining({ - amount: '100', - id: contributionToConfirm.data.createContribution.id, - memo: 'Test contribution to confirm', + 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.not.objectContaining({ - id: bibiCreatedContribution.id, - memo: 'Herzlich Willkommen bei Gradido!', - amount: '1000', - }), ]), }) + expect(contributionListResult.contributionList).toHaveLength(4) }) }) }) @@ -1253,6 +1253,7 @@ describe('ContributionResolver', () => { }), ]), }) + expect(contributionListObject.contributionList).toHaveLength(7) }) it('returns all contributions for statusFilter = null', async () => { @@ -1317,6 +1318,7 @@ describe('ContributionResolver', () => { }), ]), }) + expect(contributionListObject.contributionList).toHaveLength(7) }) it('returns all contributions for statusFilter = []', async () => { @@ -1381,6 +1383,7 @@ describe('ContributionResolver', () => { }), ]), }) + expect(contributionListObject.contributionList).toHaveLength(7) }) it('returns all CONFIRMED contributions', async () => { @@ -1430,6 +1433,7 @@ describe('ContributionResolver', () => { }), ]), }) + expect(contributionListObject.contributionList).toHaveLength(3) }) it('returns all PENDING contributions', async () => { @@ -1467,6 +1471,7 @@ describe('ContributionResolver', () => { }), ]), }) + expect(contributionListObject.contributionList).toHaveLength(1) }) it('returns all IN_PROGRESS Creation', async () => { @@ -1504,6 +1509,7 @@ describe('ContributionResolver', () => { }), ]), }) + expect(contributionListObject.contributionList).toHaveLength(1) }) it('returns all DENIED Creation', async () => { @@ -1547,6 +1553,7 @@ describe('ContributionResolver', () => { }), ]), }) + expect(contributionListObject.contributionList).toHaveLength(2) }) it('does not return any DELETED Creation', async () => { @@ -1565,6 +1572,7 @@ describe('ContributionResolver', () => { contributionCount: 0, contributionList: [], }) + expect(contributionListObject.contributionList).toHaveLength(0) }) it('returns all CONFIRMED and PENDING Creation', async () => { @@ -1617,6 +1625,7 @@ describe('ContributionResolver', () => { }), ]), }) + expect(contributionListObject.contributionList).toHaveLength(4) }) }) }) @@ -2336,7 +2345,7 @@ describe('ContributionResolver', () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, - userId: admin.id, + userId: bibi.id, }), ) }) @@ -2376,7 +2385,7 @@ describe('ContributionResolver', () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, - userId: admin.id, + userId: bibi.id, }), ) }) @@ -2554,7 +2563,7 @@ describe('ContributionResolver', () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE, - userId: admin.id, + userId: bibi.id, }), ) }) 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 } } } From d32b520145e35478270e8c4bbeddadcc832cff55 Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 15 Feb 2023 10:00:30 +0100 Subject: [PATCH 38/67] Change test description to better naming. --- backend/src/graphql/resolver/ContributionResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index a467cd9c9..1e7b451e1 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -816,7 +816,7 @@ describe('ContributionResolver', () => { expect(isDenied).toBe(true) }) - it('stores the admin deny contribution event in the database', async () => { + it('stores the ADMIN_CONTRIBUTION_DENY event in the database', async () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_DENY, From 11f19262b00c4cfaea393f68f654d21f68d05132 Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 15 Feb 2023 11:40:02 +0100 Subject: [PATCH 39/67] Add expected data to Protocols tests. --- .../graphql/resolver/ContributionResolver.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 1e7b451e1..e41c416ff 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -822,6 +822,8 @@ describe('ContributionResolver', () => { type: EventProtocolType.ADMIN_CONTRIBUTION_DENY, userId: bibi.id, xUserId: admin.id, + contributionId: contributionToDeny.data.createContribution.id, + amount: expect.decimalEqual(100), }), ) }) @@ -2080,6 +2082,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE, userId: admin.id, + amount: expect.decimalEqual(200), }), ) }) @@ -2345,7 +2348,8 @@ describe('ContributionResolver', () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, - userId: bibi.id, + userId: admin.id, + amount: 300, }), ) }) @@ -2385,7 +2389,8 @@ describe('ContributionResolver', () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE, - userId: bibi.id, + userId: admin.id, + amount: expect.decimalEqual(200), }), ) }) @@ -2563,7 +2568,8 @@ describe('ContributionResolver', () => { await expect(EventProtocol.find()).resolves.toContainEqual( expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE, - userId: bibi.id, + userId: admin.id, + amount: expect.decimalEqual(400), }), ) }) From 00a45d780517ba7c61a8750f3340f676aee86ca3 Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 15 Feb 2023 12:17:17 +0100 Subject: [PATCH 40/67] Corrected the expected amount from the ADMIN_CONTRIBUTION_DELETE event. --- backend/src/graphql/resolver/ContributionResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index e41c416ff..93ddb7c83 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -2569,7 +2569,7 @@ describe('ContributionResolver', () => { expect.objectContaining({ type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE, userId: admin.id, - amount: expect.decimalEqual(400), + amount: expect.decimalEqual(200), }), ) }) From b9c46d223849298deff72bbbef7313e6ebbab8cb Mon Sep 17 00:00:00 2001 From: elweyn Date: Wed, 15 Feb 2023 12:33:13 +0100 Subject: [PATCH 41/67] Correct spelling error. --- backend/src/graphql/resolver/ContributionResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 93ddb7c83..ce37449df 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -505,7 +505,7 @@ describe('ContributionResolver', () => { }) }) - describe('update to much so that the limit is exceeded', () => { + describe('update too much so that the limit is exceeded', () => { beforeAll(async () => { await mutate({ mutation: login, From fcbcfc40b57d4cc124d70736bc4277c85ee1af33 Mon Sep 17 00:00:00 2001 From: clauspeterhuebner <86960882+clauspeterhuebner@users.noreply.github.com> Date: Thu, 16 Feb 2023 13:50:02 +0100 Subject: [PATCH 42/67] Update dht-node/.env.template Co-authored-by: Moriz Wahl --- dht-node/.env.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dht-node/.env.template b/dht-node/.env.template index 1314972bb..b3e5d6478 100644 --- a/dht-node/.env.template +++ b/dht-node/.env.template @@ -12,4 +12,4 @@ TYPEORM_LOGGING_RELATIVE_PATH=$TYPEORM_LOGGING_RELATIVE_PATH FEDERATION_DHT_TOPIC=$FEDERATION_DHT_TOPIC FEDERATION_DHT_SEED=$FEDERATION_DHT_SEED FEDERATION_COMMUNITY_URL=$FEDERATION_COMMUNITY_URL -FEDERATION_COMMUNITY_API_PORT=FEDERATION_COMMUNITY_API_PORT +FEDERATION_COMMUNITY_API_PORT=$FEDERATION_COMMUNITY_API_PORT From 583ce91ff04cde1c02396ff111d856883f81e845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claus-Peter=20H=C3=BCbner?= Date: Thu, 16 Feb 2023 14:02:31 +0100 Subject: [PATCH 43/67] rework PR-comments --- dht-node/src/dht_node/index.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/dht-node/src/dht_node/index.ts b/dht-node/src/dht_node/index.ts index dd25d8d81..50ed46600 100644 --- a/dht-node/src/dht_node/index.ts +++ b/dht-node/src/dht_node/index.ts @@ -32,15 +32,6 @@ export const startDHT = async (topic: string): Promise => { logger.debug(`keyPairDHT: secretKey=${keyPair.secretKey.toString('hex')}`) const ownApiVersions = writeHomeCommunityEnries(keyPair.publicKey) - /* - const ownApiVersions = Object.values(ApiVersionType).map(function (apiEnum) { - const comApi: CommunityApi = { - api: apiEnum, - url: CONFIG.FEDERATION_COMMUNITY_URL, - } - return comApi - }) - */ logger.debug(`ApiList: ${JSON.stringify(ownApiVersions)}`) const node = new DHT({ keyPair }) @@ -216,6 +207,5 @@ async function writeHomeCommunityEnries(pubKey: any): Promise { } catch (err) { throw new Error(`Federation: Error writing HomeCommunity-Entries: ${err}`) } - return homeApiVersions } From de83f241d8a430636fd5bf769e9ccc50d742a9ea Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 16 Feb 2023 14:22:56 +0100 Subject: [PATCH 44/67] Update backend/src/graphql/resolver/TransactionLinkResolver.test.ts Co-authored-by: Hannes Heine --- backend/src/graphql/resolver/TransactionLinkResolver.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index c77a0bf64..2b0950a33 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -359,11 +359,6 @@ describe('TransactionLinkResolver', () => { }) it('logs the error thrown', () => { - /* expect(logger.error).toBeCalledWith( - 'The amount to be created exceeds the amount still available for this month', - new Decimal(5), - new Decimal(0), - ) */ expect(logger.error).toBeCalledWith( 'Creation from contribution link was not successful', new Error( From 7766187d296611945ad8798f82282be6217c8f09 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 16 Feb 2023 15:06:25 +0100 Subject: [PATCH 45/67] remove docker dependendies from locales jobs --- .github/workflows/test.yml | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7819a0703..5cab713ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -163,7 +163,6 @@ jobs: locales_frontend: name: Locales - Frontend runs-on: ubuntu-latest - needs: [build_test_frontend] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -171,20 +170,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Frontend) - uses: actions/download-artifact@v3 - with: - name: docker-frontend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/frontend.tar - ########################################################################## # LOCALES FRONTEND ####################################################### ########################################################################## - name: Frontend | Locales - run: docker run --rm gradido/frontend:test yarn run locales + run: cd frontend && yarn && yarn run locales ############################################################################## # JOB: LINT FRONTEND ######################################################### @@ -308,7 +297,6 @@ jobs: locales_admin: name: Locales - Admin Interface runs-on: ubuntu-latest - needs: [build_test_admin] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -316,20 +304,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Admin Interface) - uses: actions/download-artifact@v3 - with: - name: docker-admin-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/admin.tar - ########################################################################## # LOCALES FRONTEND ####################################################### ########################################################################## - - name: admin | Locales - run: docker run --rm gradido/admin:test yarn run locales + - name: Admin | Locales + run: cd admin && yarn && yarn run locales ############################################################################## # JOB: LINT BACKEND ########################################################## @@ -366,7 +344,6 @@ jobs: locales_backend: name: Locales - Backend runs-on: ubuntu-latest - needs: [build_test_backend] steps: ########################################################################## # CHECKOUT CODE ########################################################## From f12fea24330fecd52a7011bcf78c76b9e96fe4bf Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 16 Feb 2023 15:15:25 +0100 Subject: [PATCH 46/67] remove docker dependendies from linting jobs --- .github/workflows/test.yml | 82 ++++---------------------------------- 1 file changed, 8 insertions(+), 74 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5cab713ad..9a3895093 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -181,7 +181,6 @@ jobs: lint_frontend: name: Lint - Frontend runs-on: ubuntu-latest - needs: [build_test_frontend] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -189,20 +188,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Frontend) - uses: actions/download-artifact@v3 - with: - name: docker-frontend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/frontend.tar - ########################################################################## # LINT FRONTEND ########################################################## ########################################################################## - name: Frontend | Lint - run: docker run --rm gradido/frontend:test yarn run lint + run: cd frontend && yarn && yarn run lint ############################################################################## # JOB: STYLELINT FRONTEND #################################################### @@ -210,7 +199,6 @@ jobs: stylelint_frontend: name: Stylelint - Frontend runs-on: ubuntu-latest - needs: [build_test_frontend] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -218,20 +206,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Frontend) - uses: actions/download-artifact@v3 - with: - name: docker-frontend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/frontend.tar - ########################################################################## # STYLELINT FRONTEND ##################################################### ########################################################################## - name: Frontend | Stylelint - run: docker run --rm gradido/frontend:test yarn run stylelint + run: cd frontend && yarn && yarn run stylelint ############################################################################## # JOB: LINT ADMIN INTERFACE ################################################## @@ -239,7 +217,6 @@ jobs: lint_admin: name: Lint - Admin Interface runs-on: ubuntu-latest - needs: [build_test_admin] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -247,28 +224,17 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Admin Interface) - uses: actions/download-artifact@v3 - with: - name: docker-admin-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/admin.tar - ########################################################################## # LINT ADMIN INTERFACE ################################################### ########################################################################## - name: Admin Interface | Lint - run: docker run --rm gradido/admin:test yarn run lint + run: cd admin && yarn && yarn run lint ############################################################################## - # JOB: STYLELINT ADMIN INTERFACE ############################################## + # JOB: STYLELINT ADMIN INTERFACE ############################################# ############################################################################## stylelint_admin: name: Stylelint - Admin Interface runs-on: ubuntu-latest - needs: [build_test_admin] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -276,20 +242,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Admin Interface) - uses: actions/download-artifact@v3 - with: - name: docker-admin-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/admin.tar - ########################################################################## # STYLELINT ADMIN INTERFACE ############################################## ########################################################################## - name: Admin Interface | Stylelint - run: docker run --rm gradido/admin:test yarn run stylelint + run: cd admin && yarn && yarn run stylelint ############################################################################## # JOB: LOCALES ADMIN ######################################################### @@ -315,7 +271,6 @@ jobs: lint_backend: name: Lint - Backend runs-on: ubuntu-latest - needs: [build_test_backend] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -323,20 +278,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Backend) - uses: actions/download-artifact@v3 - with: - name: docker-backend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/backend.tar - ########################################################################## # LINT BACKEND ########################################################### ########################################################################## - name: backend | Lint - run: docker run --rm gradido/backend:test yarn run lint + run: cd backend && yarn && yarn run lint ############################################################################## # JOB: LOCALES BACKEND ####################################################### @@ -362,7 +307,6 @@ jobs: lint_database_up: name: Lint - Database Up runs-on: ubuntu-latest - needs: [build_test_database_up] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -370,20 +314,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGE ################################################## - ########################################################################## - - name: Download Docker Image (Backend) - uses: actions/download-artifact@v3 - with: - name: docker-database-test_up - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/database_up.tar - ########################################################################## # LINT DATABASE ########################################################## ########################################################################## - - name: database | Lint - run: docker run --rm gradido/database:test_up yarn run lint + - name: Database | Lint + run: cd database && yarn && yarn run lint ############################################################################## # JOB: UNIT TEST FRONTEND ################################################### From 86e52e5d712853780e8501c02d21b2416fde4156 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 16 Feb 2023 15:40:28 +0100 Subject: [PATCH 47/67] Change coverage from 80 to 81. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7819a0703..3ef94cfbd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -545,7 +545,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 80 + min_coverage: 81 token: ${{ github.token }} ########################################################################## From 40b2944b2474cb1a2742de0466d622b85a9d1f51 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 16 Feb 2023 15:41:04 +0100 Subject: [PATCH 48/67] Check toEqual. --- .../resolver/ContributionResolver.test.ts | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index ce37449df..670eaac2d 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -174,7 +174,7 @@ describe('ContributionResolver', () => { variables: { amount: 100.0, memo: 'Test Contribution', creationDate: 'not-valid' }, }) - expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) @@ -203,7 +203,7 @@ describe('ContributionResolver', () => { }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Memo text is too short')]) + expect(errorObjects).toEqual([new GraphQLError('Memo text is too short')]) }) it('logs the error found', () => { @@ -221,7 +221,7 @@ describe('ContributionResolver', () => { creationDate: date.toString(), }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Memo text is too long')]) + expect(errorObjects).toEqual([new GraphQLError('Memo text is too long')]) }) it('logs the error found', () => { @@ -238,7 +238,7 @@ describe('ContributionResolver', () => { creationDate: 'not-valid', }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new GraphQLError('No information for available creations for the given date'), ]) }) @@ -261,7 +261,7 @@ describe('ContributionResolver', () => { creationDate: date.setMonth(date.getMonth() - 3).toString(), }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new GraphQLError('No information for available creations for the given date'), ]) }) @@ -309,7 +309,7 @@ describe('ContributionResolver', () => { creationDate: 'not-valid', }, }) - expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) @@ -338,7 +338,7 @@ describe('ContributionResolver', () => { creationDate: date.toString(), }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Memo text is too short')]) + expect(errorObjects).toEqual([new GraphQLError('Memo text is too short')]) }) it('logs the error found', () => { @@ -359,7 +359,7 @@ describe('ContributionResolver', () => { creationDate: date.toString(), }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Memo text is too long')]) + expect(errorObjects).toEqual([new GraphQLError('Memo text is too long')]) }) it('logs the error found', () => { @@ -411,7 +411,7 @@ describe('ContributionResolver', () => { creationDate: new Date().toString(), }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new GraphQLError('Can not update contribution of another user'), ]) }) @@ -445,7 +445,7 @@ describe('ContributionResolver', () => { creationDate: new Date().toString(), }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new GraphQLError('An admin is not allowed to update an user contribution'), ]) }) @@ -524,7 +524,7 @@ describe('ContributionResolver', () => { creationDate: new Date().toString(), }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new GraphQLError( 'The amount (1019 GDD) to be created exceeds the amount (600 GDD) still available for this month.', ), @@ -551,7 +551,7 @@ describe('ContributionResolver', () => { creationDate: date.setMonth(date.getMonth() - 3).toString(), }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new GraphQLError('Month of contribution can not be changed'), ]) }) @@ -609,7 +609,7 @@ describe('ContributionResolver', () => { id: 1, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) @@ -632,7 +632,7 @@ describe('ContributionResolver', () => { id: 1, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) @@ -657,7 +657,7 @@ describe('ContributionResolver', () => { id: -1, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Contribution not found')]) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) }) it('logs the error found', () => { @@ -701,7 +701,7 @@ describe('ContributionResolver', () => { id: contribution.data.createContribution.id, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Contribution not found')]) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) }) it('logs the error found', () => { @@ -746,7 +746,7 @@ describe('ContributionResolver', () => { id: contribution.data.createContribution.id, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Contribution not found')]) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) }) it('logs the error found', () => { @@ -791,7 +791,7 @@ describe('ContributionResolver', () => { id: contribution.data.createContribution.id, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Contribution not found')]) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) }) it('logs the error found', () => { @@ -840,7 +840,7 @@ describe('ContributionResolver', () => { id: -1, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) @@ -865,7 +865,7 @@ describe('ContributionResolver', () => { id: -1, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('Contribution not found')]) + expect(errorObjects).toEqual([new GraphQLError('Contribution not found')]) }) it('logs the error found', () => { @@ -893,7 +893,7 @@ describe('ContributionResolver', () => { id: contributionToDelete.data.createContribution.id, }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new GraphQLError('Can not delete contribution of another user'), ]) }) @@ -966,7 +966,7 @@ describe('ContributionResolver', () => { id: contributionToConfirm.data.createContribution.id, }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new GraphQLError('A confirmed contribution can not be deleted'), ]) }) @@ -993,7 +993,7 @@ describe('ContributionResolver', () => { filterConfirmed: false, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) @@ -1124,7 +1124,7 @@ describe('ContributionResolver', () => { statusFilter: null, }, }) - expect(errorObjects).toMatchObject([new GraphQLError('401 Unauthorized')]) + expect(errorObjects).toEqual([new GraphQLError('401 Unauthorized')]) }) }) @@ -1150,7 +1150,7 @@ describe('ContributionResolver', () => { statusFilter: ['NOT_VALID'], }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new UserInputError( 'Variable "$statusFilter" got invalid value "NOT_VALID" at "statusFilter[0]"; Value "NOT_VALID" does not exist in "ContributionStatus" enum.', ), @@ -1167,7 +1167,7 @@ describe('ContributionResolver', () => { statusFilter: [null], }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new UserInputError( 'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.', ), @@ -1184,7 +1184,7 @@ describe('ContributionResolver', () => { statusFilter: [null, 'NOT_VALID'], }, }) - expect(errorObjects).toMatchObject([ + expect(errorObjects).toEqual([ new UserInputError( 'Variable "$statusFilter" got invalid value null at "statusFilter[0]"; Expected non-nullable type "ContributionStatus!" not to be null.', ), @@ -1570,7 +1570,7 @@ describe('ContributionResolver', () => { statusFilter: ['DELETED'], }, }) - expect(contributionListObject).toMatchObject({ + expect(contributionListObject).toEqual({ contributionCount: 0, contributionList: [], }) From baccc83f0016a571e31ef7dad649f95d74551b59 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 16 Feb 2023 16:33:49 +0100 Subject: [PATCH 49/67] remove docker dependendies from unit test jobs --- .github/workflows/test.yml | 40 +++----------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9a3895093..a29792a7d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -325,7 +325,6 @@ jobs: unit_test_frontend: name: Unit tests - Frontend runs-on: ubuntu-latest - needs: [build_test_frontend] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -333,30 +332,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGES ################################################# - ########################################################################## - - name: Download Docker Image (Frontend) - uses: actions/download-artifact@v3 - with: - name: docker-frontend-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/frontend.tar - ########################################################################## # UNIT TESTS FRONTEND #################################################### ########################################################################## - - name: frontend | Unit tests - run: | - docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test - cp -r ~/coverage ./coverage - ########################################################################## - # COVERAGE REPORT FRONTEND ############################################### - ########################################################################## - #- name: frontend | Coverage report - # uses: romeovs/lcov-reporter-action@v0.2.21 - # with: - # github-token: ${{ secrets.GITHUB_TOKEN }} - # lcov-file: ./coverage/lcov.info + - name: Frontend | Unit tests + run: cd frontend && yarn && yarn run test ########################################################################## # COVERAGE CHECK FRONTEND ################################################ ########################################################################## @@ -375,7 +354,6 @@ jobs: unit_test_admin: name: Unit tests - Admin Interface runs-on: ubuntu-latest - needs: [build_test_admin] steps: ########################################################################## # CHECKOUT CODE ########################################################## @@ -383,22 +361,10 @@ jobs: - name: Checkout code uses: actions/checkout@v3 ########################################################################## - # DOWNLOAD DOCKER IMAGES ################################################# - ########################################################################## - - name: Download Docker Image (Admin Interface) - uses: actions/download-artifact@v3 - with: - name: docker-admin-test - path: /tmp - - name: Load Docker Image - run: docker load < /tmp/admin.tar - ########################################################################## # UNIT TESTS ADMIN INTERFACE ############################################# ########################################################################## - name: Admin Interface | Unit tests - run: | - docker run -v ~/coverage:/app/coverage --rm gradido/admin:test yarn run test - cp -r ~/coverage ./coverage + run: cd admin && yarn && yarn run test ########################################################################## # COVERAGE CHECK ADMIN INTERFACE ######################################### ########################################################################## From f5c44e8ca3b40c6e85cb5f3e3a26bcdfee257052 Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 16 Feb 2023 16:59:03 +0100 Subject: [PATCH 50/67] adapt paths for unit test coverage jobs --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a29792a7d..2e55ba47e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -344,7 +344,7 @@ jobs: with: report_name: Coverage Frontend type: lcov - result_path: ./coverage/lcov.info + result_path: ./frontend/coverage/lcov.info min_coverage: 95 token: ${{ github.token }} @@ -373,7 +373,7 @@ jobs: with: report_name: Coverage Admin Interface type: lcov - result_path: ./coverage/lcov.info + result_path: ./admin/coverage/lcov.info min_coverage: 96 token: ${{ github.token }} From 72b8e2dc97c17005923f31330d5e7cd696c88b07 Mon Sep 17 00:00:00 2001 From: elweyn Date: Thu, 16 Feb 2023 17:22:41 +0100 Subject: [PATCH 51/67] Revert "Change coverage from 80 to 81." This reverts commit 86e52e5d712853780e8501c02d21b2416fde4156. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61000381d..de45a35aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -545,7 +545,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 81 + min_coverage: 80 token: ${{ github.token }} ########################################################################## From 9fbce956df6d39952465cf722a3c3b5b1385e0fc Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 16 Feb 2023 18:22:47 +0100 Subject: [PATCH 52/67] missing merge change --- backend/src/graphql/resolver/ContributionResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.test.ts b/backend/src/graphql/resolver/ContributionResolver.test.ts index 498fbaeba..b56180c45 100644 --- a/backend/src/graphql/resolver/ContributionResolver.test.ts +++ b/backend/src/graphql/resolver/ContributionResolver.test.ts @@ -526,7 +526,7 @@ describe('ContributionResolver', () => { }) expect(errorObjects).toEqual([ new GraphQLError( - 'The amount to be created exceeds the amount still available for this month',, + 'The amount to be created exceeds the amount still available for this month', ), ]) }) From a5fde7361cbbbd906a6a319bed019a0630ffb0af Mon Sep 17 00:00:00 2001 From: mahula Date: Thu, 16 Feb 2023 20:57:15 +0100 Subject: [PATCH 53/67] fix result path for admin unit test coverage ingo --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b3c15ddea..c2dd3a3b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -373,7 +373,7 @@ jobs: with: report_name: Coverage Admin Interface type: lcov - result_path: ./coverage/lcov.info + result_path: ./admin/coverage/lcov.info min_coverage: 97 token: ${{ github.token }} From de6834a6f0aea5a911d9e6a44811b204d0984160 Mon Sep 17 00:00:00 2001 From: mahula Date: Fri, 17 Feb 2023 02:35:24 +0100 Subject: [PATCH 54/67] add copying of coverage info for check --- .github/workflows/test.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2dd3a3b0..badb47e87 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -335,7 +335,9 @@ jobs: # UNIT TESTS FRONTEND #################################################### ########################################################################## - name: Frontend | Unit tests - run: cd frontend && yarn && yarn run test + run: | + cd frontend && yarn && yarn run test + cp -r ./coverage ../ ########################################################################## # COVERAGE CHECK FRONTEND ################################################ ########################################################################## @@ -364,7 +366,9 @@ jobs: # UNIT TESTS ADMIN INTERFACE ############################################# ########################################################################## - name: Admin Interface | Unit tests - run: cd admin && yarn && yarn run test + run: | + cd admin && yarn && yarn run test + cp -r ./coverage ../ ########################################################################## # COVERAGE CHECK ADMIN INTERFACE ######################################### ########################################################################## @@ -411,8 +415,9 @@ jobs: - name: backend | docker-compose database run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database - name: backend Unit tests | test - run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test - # run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test + run: | + cd database && yarn && yarn build && cd ../backend && yarn && yarn test + cp -r ./coverage ../ ########################################################################## # COVERAGE CHECK BACKEND ################################################# ########################################################################## From 86742d015823c7c657d1d84d9cc2292bb837c6ec Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 17 Feb 2023 09:48:05 +0100 Subject: [PATCH 55/67] refactor(frontend): community routes --- .../Template/ContentHeader/NavCommunity.vue | 25 +- .../Template/RightSide/ContributionInfo.vue | 22 +- .../src/layouts/templates/ContentHeader.vue | 17 +- frontend/src/layouts/templates/RightSide.vue | 32 +- frontend/src/pages/Community.vue | 485 +++++++++--------- frontend/src/routes/routes.js | 11 + 6 files changed, 304 insertions(+), 288 deletions(-) diff --git a/frontend/src/components/Template/ContentHeader/NavCommunity.vue b/frontend/src/components/Template/ContentHeader/NavCommunity.vue index 31a839af4..841304b2a 100644 --- a/frontend/src/components/Template/ContentHeader/NavCommunity.vue +++ b/frontend/src/components/Template/ContentHeader/NavCommunity.vue @@ -2,19 +2,19 @@