From 8c00fcd6ccb8069a7df73053164226b91ad868df Mon Sep 17 00:00:00 2001 From: elweyn Date: Mon, 6 Feb 2023 14:34:40 +0100 Subject: [PATCH 01/27] 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 02/27] 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 03/27] 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 04/27] 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 05/27] 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 06/27] 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 07/27] 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 08/27] 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 9ff045fdbfe7094a5e78dd6ab59a32307f66a50d Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 14 Feb 2023 20:31:56 +0100 Subject: [PATCH 09/27] 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 de83f241d8a430636fd5bf769e9ccc50d742a9ea Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 16 Feb 2023 14:22:56 +0100 Subject: [PATCH 10/27] 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 7f182a5927ffd5cab3ebfbb4af39ac2d4f1e1e21 Mon Sep 17 00:00:00 2001 From: ogerly Date: Thu, 16 Feb 2023 15:21:55 +0100 Subject: [PATCH 11/27] change fetchPolicy, add scripts.update --- frontend/src/pages/Community.vue | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/Community.vue b/frontend/src/pages/Community.vue index 113bcd4e4..7427aa2fa 100644 --- a/frontend/src/pages/Community.vue +++ b/frontend/src/pages/Community.vue @@ -122,13 +122,13 @@ export default { query() { return listAllContributions }, - fetchPolicy: 'network-only', variables() { return { currentPage: this.currentPageAll, pageSize: this.pageSizeAll, } }, + fetchPolicy: 'no-cache', update({ listAllContributions }) { this.contributionCountAll = listAllContributions.contributionCount this.itemsAll = listAllContributions.contributionList diff --git a/package.json b/package.json index 2220c1a85..85b8dfe53 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "author": "Ulf Gebhardt ", "license": "Apache-2.0", "scripts": { - "release": "scripts/release.sh" + "release": "scripts/release.sh", + "update": "cd admin && yarn && cd ../backend && yarn && cd ../database && yarn && cd ../dht-node && yarn && cd ../e2e-tests && yarn && cd ../federation && yarn && cd ../frontend && yarn" }, "dependencies": { "auto-changelog": "^2.4.0", From 6567babd3b516b459e9a5bbf4a522120de37f807 Mon Sep 17 00:00:00 2001 From: ogerly Date: Thu, 16 Feb 2023 15:28:40 +0100 Subject: [PATCH 12/27] remove package json script update --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 85b8dfe53..959e4d6d5 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "license": "Apache-2.0", "scripts": { "release": "scripts/release.sh", - "update": "cd admin && yarn && cd ../backend && yarn && cd ../database && yarn && cd ../dht-node && yarn && cd ../e2e-tests && yarn && cd ../federation && yarn && cd ../frontend && yarn" }, "dependencies": { "auto-changelog": "^2.4.0", From 10327e9fd507342b240b4c91e5d0f00201d0342c Mon Sep 17 00:00:00 2001 From: ogerly Date: Thu, 16 Feb 2023 15:39:33 +0100 Subject: [PATCH 13/27] remove comma --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 959e4d6d5..2220c1a85 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "author": "Ulf Gebhardt ", "license": "Apache-2.0", "scripts": { - "release": "scripts/release.sh", + "release": "scripts/release.sh" }, "dependencies": { "auto-changelog": "^2.4.0", From 9fbce956df6d39952465cf722a3c3b5b1385e0fc Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Thu, 16 Feb 2023 18:22:47 +0100 Subject: [PATCH 14/27] 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 86742d015823c7c657d1d84d9cc2292bb837c6ec Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 17 Feb 2023 09:48:05 +0100 Subject: [PATCH 15/27] 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 @@