From ec87bea83658e2f269455f9602bad46f84c1823a Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 7 Mar 2023 14:31:24 +0100 Subject: [PATCH 1/2] feat(backend): test transaction links --- .../resolver/TransactionLinkResolver.test.ts | 30 ++++++++++++++++++- .../resolver/TransactionLinkResolver.ts | 12 ++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 60b4551be..b4e49cfe0 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -306,7 +306,6 @@ describe('TransactionLinkResolver', () => { }) }) - // 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 @@ -508,6 +507,35 @@ describe('TransactionLinkResolver', () => { }) }) }) + + describe('transaction link', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('link does not exits', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + it('throws and logs the error', async () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'not-valid', + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('Transaction link not found')], + }) + expect(logger.error).toBeCalledWith('Transaction link not found', 'not-valid') + }) + }) + }) }) }) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index ab5b52bad..ef55475db 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -285,12 +285,20 @@ export class TransactionLinkResolver { return true } else { const now = new Date() - const transactionLink = await DbTransactionLink.findOneOrFail({ code }) - const linkedUser = await DbUser.findOneOrFail( + const transactionLink = await DbTransactionLink.findOne({ code }) + if (!transactionLink) { + throw new LogError('Transaction link not found', code) + } + + const linkedUser = await DbUser.findOne( { id: transactionLink.userId }, { relations: ['emailContact'] }, ) + if (!linkedUser) { + throw new LogError('Linked user not found for given link', transactionLink.userId) + } + if (user.id === linkedUser.id) { throw new LogError('Cannot redeem own transaction link', user.id) } From a49251205f479edc46d621599c9811d7f6b3063a Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 7 Mar 2023 16:24:17 +0100 Subject: [PATCH 2/2] test double redeem of transaction link --- .../resolver/TransactionLinkResolver.test.ts | 66 +++++++++++++++++++ .../src/graphql/resolver/semaphore.test.ts | 46 +++++++++++++ backend/src/seeds/graphql/mutations.ts | 6 ++ 3 files changed, 118 insertions(+) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index b4e49cfe0..5c03c722b 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -17,10 +17,12 @@ import { createContribution, updateContribution, createTransactionLink, + confirmContribution, } from '@/seeds/graphql/mutations' import { listTransactionLinksAdmin } from '@/seeds/graphql/queries' import { ContributionLink as DbContributionLink } from '@entity/ContributionLink' import { User } from '@entity/User' +import { Transaction } from '@entity/Transaction' import { UnconfirmedContribution } from '@model/UnconfirmedContribution' import Decimal from 'decimal.js-light' import { GraphQLError } from 'graphql' @@ -137,6 +139,8 @@ describe('TransactionLinkResolver', () => { resetToken() }) + let contributionId: number + describe('unauthenticated', () => { it('throws an error', async () => { jest.clearAllMocks() @@ -331,6 +335,10 @@ describe('TransactionLinkResolver', () => { }) }) + afterAll(async () => { + await resetEntity(Transaction) + }) + it('has a daily contribution link in the database', async () => { const cls = await DbContributionLink.find() expect(cls).toHaveLength(1) @@ -372,6 +380,7 @@ describe('TransactionLinkResolver', () => { }, }) contribution = result.data.createContribution + contributionId = result.data.createContribution.id }) it('does not allow the user to redeem the contribution link', async () => { @@ -535,6 +544,63 @@ describe('TransactionLinkResolver', () => { expect(logger.error).toBeCalledWith('Transaction link not found', 'not-valid') }) }) + + describe('link exists', () => { + let myCode: string + + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) + await mutate({ + mutation: confirmContribution, + variables: { id: contributionId }, + }) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + const { + data: { + createTransactionLink: { code }, + }, + } = await mutate({ + mutation: createTransactionLink, + variables: { + amount: 200, + memo: 'This is a transaction link from bibi', + }, + }) + myCode = code + }) + + describe('own link', () => { + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + }) + + it('throws and logs an error', async () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: myCode, + }, + }), + ).resolves.toMatchObject({ + errors: [new GraphQLError('Cannot redeem own transaction link')], + }) + expect(logger.error).toBeCalledWith( + 'Cannot redeem own transaction link', + expect.any(Number), + ) + }) + }) + }) }) }) }) diff --git a/backend/src/graphql/resolver/semaphore.test.ts b/backend/src/graphql/resolver/semaphore.test.ts index e334910f1..740a899f3 100644 --- a/backend/src/graphql/resolver/semaphore.test.ts +++ b/backend/src/graphql/resolver/semaphore.test.ts @@ -187,4 +187,50 @@ describe('semaphore', () => { await expect(confirmBibisContribution).resolves.toMatchObject({ errors: undefined }) await expect(confirmBobsContribution).resolves.toMatchObject({ errors: undefined }) }) + + describe('redeem transaction link twice', () => { + let myCode: string + + beforeAll(async () => { + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) + const { + data: { createTransactionLink: bibisLink }, + } = await mutate({ + mutation: createTransactionLink, + variables: { + amount: 20, + memo: 'Bibis Link', + }, + }) + myCode = bibisLink.code + await mutate({ + mutation: login, + variables: { email: 'bob@baumeister.de', password: 'Aa12345_' }, + }) + }) + + it('does not throw, but should', async () => { + const redeem1 = mutate({ + mutation: redeemTransactionLink, + variables: { + code: myCode, + }, + }) + const redeem2 = mutate({ + mutation: redeemTransactionLink, + variables: { + code: myCode, + }, + }) + await expect(redeem1).resolves.toMatchObject({ + errors: undefined, + }) + await expect(redeem2).resolves.toMatchObject({ + errors: undefined, + }) + }) + }) }) diff --git a/backend/src/seeds/graphql/mutations.ts b/backend/src/seeds/graphql/mutations.ts index 79684d378..d9c293a0f 100644 --- a/backend/src/seeds/graphql/mutations.ts +++ b/backend/src/seeds/graphql/mutations.ts @@ -89,6 +89,12 @@ export const createTransactionLink = gql` } ` +export const deleteTransactionLink = gql` + mutation ($id: Int!) { + deleteTransactionLink(id: $id) + } +` + // from admin interface export const adminCreateContribution = gql`