From d9f8f8cb0775956d21612d63778fdc38f0bc6a2c Mon Sep 17 00:00:00 2001 From: ogerly Date: Mon, 16 Jan 2023 10:19:10 +0100 Subject: [PATCH 01/30] normalized-amount-transaction-if-processed again --- frontend/src/components/Inputs/InputAmount.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Inputs/InputAmount.vue b/frontend/src/components/Inputs/InputAmount.vue index 7a72a3a77..3268a0ec1 100644 --- a/frontend/src/components/Inputs/InputAmount.vue +++ b/frontend/src/components/Inputs/InputAmount.vue @@ -20,7 +20,7 @@ trim v-focus="amountFocused" @focus="amountFocused = true" - @blur="normalizeAmount(true)" + @blur="normalizeAmount(valid)" :disabled="disabled" autocomplete="off" > @@ -90,5 +90,8 @@ export default { this.currentValue = this.$n(this.amountValue, 'ungroupedDecimal') }, }, + mounted() { + if (this.value !== '') this.normalizeAmount(true) + }, } From dab7fe584a1bb4911a83b8eeaf89c0765714c55a Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 16 Jan 2023 11:04:14 +0100 Subject: [PATCH 02/30] try catch around semaphore lock --- .../resolver/TransactionLinkResolver.ts | 265 +++++++++--------- 1 file changed, 134 insertions(+), 131 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index 897cf9252..ac57db131 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -170,148 +170,151 @@ export class TransactionLinkResolver { if (code.match(/^CL-/)) { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - logger.info('redeem contribution link...') - const now = new Date() - const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() - await queryRunner.startTransaction('REPEATABLE READ') try { - const contributionLink = await queryRunner.manager - .createQueryBuilder() - .select('contributionLink') - .from(DbContributionLink, 'contributionLink') - .where('contributionLink.code = :code', { code: code.replace('CL-', '') }) - .getOne() - if (!contributionLink) { - logger.error('no contribution link found to given code:', code) - throw new Error('No contribution link found') - } - logger.info('...contribution link found with id', contributionLink.id) - if (new Date(contributionLink.validFrom).getTime() > now.getTime()) { - logger.error( - 'contribution link is not valid yet. Valid from: ', - contributionLink.validFrom, - ) - throw new Error('Contribution link not valid yet') - } - if (contributionLink.validTo) { - if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) { - logger.error('contribution link is depricated. Valid to: ', contributionLink.validTo) - throw new Error('Contribution link is depricated') + logger.info('redeem contribution link...') + const now = new Date() + const queryRunner = getConnection().createQueryRunner() + await queryRunner.connect() + await queryRunner.startTransaction('REPEATABLE READ') + try { + const contributionLink = await queryRunner.manager + .createQueryBuilder() + .select('contributionLink') + .from(DbContributionLink, 'contributionLink') + .where('contributionLink.code = :code', { code: code.replace('CL-', '') }) + .getOne() + if (!contributionLink) { + logger.error('no contribution link found to given code:', code) + throw new Error('No contribution link found') } - } - let alreadyRedeemed: DbContribution | undefined - switch (contributionLink.cycle) { - case ContributionCycleType.ONCE: { - alreadyRedeemed = await queryRunner.manager - .createQueryBuilder() - .select('contribution') - .from(DbContribution, 'contribution') - .where('contribution.contributionLinkId = :linkId AND contribution.userId = :id', { - linkId: contributionLink.id, - id: user.id, - }) - .getOne() - if (alreadyRedeemed) { - logger.error( - 'contribution link with rule ONCE already redeemed by user with id', - user.id, - ) - throw new Error('Contribution link already redeemed') + logger.info('...contribution link found with id', contributionLink.id) + if (new Date(contributionLink.validFrom).getTime() > now.getTime()) { + logger.error( + 'contribution link is not valid yet. Valid from: ', + contributionLink.validFrom, + ) + throw new Error('Contribution link not valid yet') + } + if (contributionLink.validTo) { + if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) { + logger.error('contribution link is depricated. Valid to: ', contributionLink.validTo) + throw new Error('Contribution link is depricated') } - break } - case ContributionCycleType.DAILY: { - const start = new Date() - start.setHours(0, 0, 0, 0) - const end = new Date() - end.setHours(23, 59, 59, 999) - alreadyRedeemed = await queryRunner.manager - .createQueryBuilder() - .select('contribution') - .from(DbContribution, 'contribution') - .where( - `contribution.contributionLinkId = :linkId AND contribution.userId = :id - AND Date(contribution.confirmedAt) BETWEEN :start AND :end`, - { + let alreadyRedeemed: DbContribution | undefined + switch (contributionLink.cycle) { + case ContributionCycleType.ONCE: { + alreadyRedeemed = await queryRunner.manager + .createQueryBuilder() + .select('contribution') + .from(DbContribution, 'contribution') + .where('contribution.contributionLinkId = :linkId AND contribution.userId = :id', { linkId: contributionLink.id, id: user.id, - start, - end, - }, - ) - .getOne() - if (alreadyRedeemed) { - logger.error( - 'contribution link with rule DAILY already redeemed by user with id', - user.id, - ) - throw new Error('Contribution link already redeemed today') + }) + .getOne() + if (alreadyRedeemed) { + logger.error( + 'contribution link with rule ONCE already redeemed by user with id', + user.id, + ) + throw new Error('Contribution link already redeemed') + } + break + } + case ContributionCycleType.DAILY: { + const start = new Date() + start.setHours(0, 0, 0, 0) + const end = new Date() + end.setHours(23, 59, 59, 999) + alreadyRedeemed = await queryRunner.manager + .createQueryBuilder() + .select('contribution') + .from(DbContribution, 'contribution') + .where( + `contribution.contributionLinkId = :linkId AND contribution.userId = :id + AND Date(contribution.confirmedAt) BETWEEN :start AND :end`, + { + linkId: contributionLink.id, + id: user.id, + start, + end, + }, + ) + .getOne() + if (alreadyRedeemed) { + logger.error( + 'contribution link with rule DAILY already redeemed by user with id', + user.id, + ) + throw new Error('Contribution link already redeemed today') + } + break + } + default: { + logger.error('contribution link has unknown cycle', contributionLink.cycle) + throw new Error('Contribution link has unknown cycle') } - break } - default: { - logger.error('contribution link has unknown cycle', contributionLink.cycle) - throw new Error('Contribution link has unknown cycle') + + const creations = await getUserCreation(user.id, clientTimezoneOffset) + logger.info('open creations', creations) + validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset) + const contribution = new DbContribution() + contribution.userId = user.id + contribution.createdAt = now + contribution.contributionDate = now + contribution.memo = contributionLink.memo + contribution.amount = contributionLink.amount + contribution.contributionLinkId = contributionLink.id + contribution.contributionType = ContributionType.LINK + contribution.contributionStatus = ContributionStatus.CONFIRMED + + await queryRunner.manager.insert(DbContribution, contribution) + + const lastTransaction = await queryRunner.manager + .createQueryBuilder() + .select('transaction') + .from(DbTransaction, 'transaction') + .where('transaction.userId = :id', { id: user.id }) + .orderBy('transaction.id', 'DESC') + .getOne() + let newBalance = new Decimal(0) + + let decay: Decay | null = null + if (lastTransaction) { + decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now) + newBalance = decay.balance } + newBalance = newBalance.add(contributionLink.amount.toString()) + + const transaction = new DbTransaction() + transaction.typeId = TransactionTypeId.CREATION + transaction.memo = contribution.memo + transaction.userId = contribution.userId + transaction.previous = lastTransaction ? lastTransaction.id : null + transaction.amount = contribution.amount + transaction.creationDate = contribution.contributionDate + transaction.balance = newBalance + transaction.balanceDate = now + transaction.decay = decay ? decay.decay : new Decimal(0) + transaction.decayStart = decay ? decay.start : null + await queryRunner.manager.insert(DbTransaction, transaction) + + contribution.confirmedAt = now + contribution.transactionId = transaction.id + await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution) + + await queryRunner.commitTransaction() + logger.info('creation from contribution link commited successfuly.') + } catch (e) { + await queryRunner.rollbackTransaction() + logger.error(`Creation from contribution link was not successful: ${e}`) + throw new Error(`Creation from contribution link was not successful. ${e}`) + } finally { + await queryRunner.release() } - - const creations = await getUserCreation(user.id, clientTimezoneOffset) - logger.info('open creations', creations) - validateContribution(creations, contributionLink.amount, now, clientTimezoneOffset) - const contribution = new DbContribution() - contribution.userId = user.id - contribution.createdAt = now - contribution.contributionDate = now - contribution.memo = contributionLink.memo - contribution.amount = contributionLink.amount - contribution.contributionLinkId = contributionLink.id - contribution.contributionType = ContributionType.LINK - contribution.contributionStatus = ContributionStatus.CONFIRMED - - await queryRunner.manager.insert(DbContribution, contribution) - - const lastTransaction = await queryRunner.manager - .createQueryBuilder() - .select('transaction') - .from(DbTransaction, 'transaction') - .where('transaction.userId = :id', { id: user.id }) - .orderBy('transaction.id', 'DESC') - .getOne() - let newBalance = new Decimal(0) - - let decay: Decay | null = null - if (lastTransaction) { - decay = calculateDecay(lastTransaction.balance, lastTransaction.balanceDate, now) - newBalance = decay.balance - } - newBalance = newBalance.add(contributionLink.amount.toString()) - - const transaction = new DbTransaction() - transaction.typeId = TransactionTypeId.CREATION - transaction.memo = contribution.memo - transaction.userId = contribution.userId - transaction.previous = lastTransaction ? lastTransaction.id : null - transaction.amount = contribution.amount - transaction.creationDate = contribution.contributionDate - transaction.balance = newBalance - transaction.balanceDate = now - transaction.decay = decay ? decay.decay : new Decimal(0) - transaction.decayStart = decay ? decay.start : null - await queryRunner.manager.insert(DbTransaction, transaction) - - contribution.confirmedAt = now - contribution.transactionId = transaction.id - await queryRunner.manager.update(DbContribution, { id: contribution.id }, contribution) - - await queryRunner.commitTransaction() - logger.info('creation from contribution link commited successfuly.') - } catch (e) { - await queryRunner.rollbackTransaction() - logger.error(`Creation from contribution link was not successful: ${e}`) - throw new Error(`Creation from contribution link was not successful. ${e}`) } finally { - await queryRunner.release() releaseLock() } return true From c3f7f1542078ecc322ecb4cf453e1d0876434357 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 16 Jan 2023 11:21:38 +0100 Subject: [PATCH 03/30] refactor all semaphore-locks --- .../graphql/resolver/ContributionResolver.ts | 1 - .../graphql/resolver/TransactionResolver.ts | 41 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 7771c62ca..198d2e590 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -557,7 +557,6 @@ export class ContributionResolver { ): Promise { // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - try { const clientTimezoneOffset = getClientTimezoneOffset(context) const contribution = await DbContribution.findOne(id) diff --git a/backend/src/graphql/resolver/TransactionResolver.ts b/backend/src/graphql/resolver/TransactionResolver.ts index 33914583e..2f97596b2 100644 --- a/backend/src/graphql/resolver/TransactionResolver.ts +++ b/backend/src/graphql/resolver/TransactionResolver.ts @@ -45,29 +45,28 @@ export const executeTransaction = async ( recipient: dbUser, transactionLink?: dbTransactionLink | null, ): Promise => { - logger.info( - `executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`, - ) - - if (sender.id === recipient.id) { - logger.error(`Sender and Recipient are the same.`) - throw new Error('Sender and Recipient are the same.') - } - - if (memo.length > MEMO_MAX_CHARS) { - logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) - throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) - } - - if (memo.length < MEMO_MIN_CHARS) { - logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`) - throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) - } - // acquire lock const releaseLock = await TRANSACTIONS_LOCK.acquire() - try { + logger.info( + `executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`, + ) + + if (sender.id === recipient.id) { + logger.error(`Sender and Recipient are the same.`) + throw new Error('Sender and Recipient are the same.') + } + + if (memo.length > MEMO_MAX_CHARS) { + logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`) + throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`) + } + + if (memo.length < MEMO_MIN_CHARS) { + logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`) + throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) + } + // validate amount const receivedCallDate = new Date() const sendBalance = await calculateBalance( @@ -187,10 +186,10 @@ export const executeTransaction = async ( }) } logger.info(`finished executeTransaction successfully`) - return true } finally { releaseLock() } + return true } @Resolver() From 466e59a550226b647805a6707f3773a66a259740 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 16 Jan 2023 11:27:30 +0100 Subject: [PATCH 04/30] typo --- backend/src/graphql/resolver/ContributionResolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/graphql/resolver/ContributionResolver.ts b/backend/src/graphql/resolver/ContributionResolver.ts index 198d2e590..afa786f38 100644 --- a/backend/src/graphql/resolver/ContributionResolver.ts +++ b/backend/src/graphql/resolver/ContributionResolver.ts @@ -663,7 +663,6 @@ export class ContributionResolver { } finally { releaseLock() } - return true } From 7781011b1a5352d7b7cb77ef135443d644dbec30 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 16 Jan 2023 15:22:45 +0100 Subject: [PATCH 05/30] refactor(backend): field resolvers in statistics --- .../src/graphql/model/CommunityStatistics.ts | 27 ++-- .../graphql/resolver/StatisticsResolver.ts | 138 +++++++++++------- 2 files changed, 103 insertions(+), 62 deletions(-) diff --git a/backend/src/graphql/model/CommunityStatistics.ts b/backend/src/graphql/model/CommunityStatistics.ts index 61354115c..8b8a34f80 100644 --- a/backend/src/graphql/model/CommunityStatistics.ts +++ b/backend/src/graphql/model/CommunityStatistics.ts @@ -2,13 +2,25 @@ import { ObjectType, Field } from 'type-graphql' import Decimal from 'decimal.js-light' @ObjectType() -export class CommunityStatistics { - @Field(() => Number) - totalUsers: number - +export class TotalAvailable { @Field(() => Number) activeUsers: number + @Field(() => Decimal) + totalGradidoAvailable: Decimal + + @Field(() => Decimal) + totalGradidoUnbookedDecayed: Decimal +} + +@ObjectType() +export class CommunityStatistics { + @Field(() => Number) + allUsers: number + + @Field(() => Number) + totalUsers: number + @Field(() => Number) deletedUsers: number @@ -18,9 +30,6 @@ export class CommunityStatistics { @Field(() => Decimal) totalGradidoDecayed: Decimal - @Field(() => Decimal) - totalGradidoAvailable: Decimal - - @Field(() => Decimal) - totalGradidoUnbookedDecayed: Decimal + @Field(() => TotalAvailable) + totalAvailable: TotalAvailable } diff --git a/backend/src/graphql/resolver/StatisticsResolver.ts b/backend/src/graphql/resolver/StatisticsResolver.ts index f6c2b9e22..577ca2c81 100644 --- a/backend/src/graphql/resolver/StatisticsResolver.ts +++ b/backend/src/graphql/resolver/StatisticsResolver.ts @@ -1,81 +1,113 @@ import Decimal from 'decimal.js-light' -import { Resolver, Query, Authorized } from 'type-graphql' +import { Resolver, Query, Authorized, FieldResolver } from 'type-graphql' import { getConnection } from '@dbTools/typeorm' import { Transaction as DbTransaction } from '@entity/Transaction' import { User as DbUser } from '@entity/User' -import { CommunityStatistics } from '@model/CommunityStatistics' +import { CommunityStatistics, TotalAvailable } from '@model/CommunityStatistics' import { RIGHTS } from '@/auth/RIGHTS' import { calculateDecay } from '@/util/decay' -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ - -@Resolver() +/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ +@Resolver((of) => CommunityStatistics) export class StatisticsResolver { @Authorized([RIGHTS.COMMUNITY_STATISTICS]) @Query(() => CommunityStatistics) async communityStatistics(): Promise { - const allUsers = await DbUser.count({ withDeleted: true }) - const totalUsers = await DbUser.count() - const deletedUsers = allUsers - totalUsers + return new CommunityStatistics() + } + @FieldResolver(() => Decimal) + async allUsers(): Promise { + return await DbUser.count({ withDeleted: true }) + } + + @FieldResolver() + async totalUsers(): Promise { + return await DbUser.count() + } + + @FieldResolver() + async deletedUsers(): Promise { + return (await this.allUsers()) - (await this.totalUsers()) + } + + @FieldResolver() + async totalGradidoCreated(): Promise { + const queryRunner = getConnection().createQueryRunner() + try { + await queryRunner.connect() + const { totalGradidoCreated } = await queryRunner.manager + .createQueryBuilder() + .select('SUM(transaction.amount) AS totalGradidoCreated') + .from(DbTransaction, 'transaction') + .where('transaction.typeId = 1') + .getRawOne() + return totalGradidoCreated + } finally { + await queryRunner.release() + } + } + + @FieldResolver() + async totalGradidoDecayed(): Promise { + const queryRunner = getConnection().createQueryRunner() + try { + await queryRunner.connect() + const { totalGradidoDecayed } = await queryRunner.manager + .createQueryBuilder() + .select('SUM(transaction.decay) AS totalGradidoDecayed') + .from(DbTransaction, 'transaction') + .where('transaction.decay IS NOT NULL') + .getRawOne() + return totalGradidoDecayed + } finally { + await queryRunner.release() + } + } + + @FieldResolver() + async totalAvailable(): Promise { let totalGradidoAvailable: Decimal = new Decimal(0) let totalGradidoUnbookedDecayed: Decimal = new Decimal(0) const receivedCallDate = new Date() const queryRunner = getConnection().createQueryRunner() - await queryRunner.connect() + try { + await queryRunner.connect() - const lastUserTransactions = await queryRunner.manager - .createQueryBuilder(DbUser, 'user') - .select('transaction.balance', 'balance') - .addSelect('transaction.balance_date', 'balanceDate') - .innerJoin(DbTransaction, 'transaction', 'user.id = transaction.user_id') - .where( - `transaction.balance_date = (SELECT MAX(t.balance_date) FROM transactions AS t WHERE t.user_id = user.id)`, - ) - .orderBy('transaction.balance_date', 'DESC') - .addOrderBy('transaction.id', 'DESC') - .getRawMany() + const lastUserTransactions = await queryRunner.manager + .createQueryBuilder(DbUser, 'user') + .select('transaction.balance', 'balance') + .addSelect('transaction.balance_date', 'balanceDate') + .innerJoin(DbTransaction, 'transaction', 'user.id = transaction.user_id') + .where( + `transaction.balance_date = (SELECT MAX(t.balance_date) FROM transactions AS t WHERE t.user_id = user.id)`, + ) + .orderBy('transaction.balance_date', 'DESC') + .addOrderBy('transaction.id', 'DESC') + .getRawMany() - const activeUsers = lastUserTransactions.length + const activeUsers = lastUserTransactions.length - lastUserTransactions.forEach(({ balance, balanceDate }) => { - const decay = calculateDecay(new Decimal(balance), new Date(balanceDate), receivedCallDate) - if (decay) { - totalGradidoAvailable = totalGradidoAvailable.plus(decay.balance.toString()) - totalGradidoUnbookedDecayed = totalGradidoUnbookedDecayed.plus(decay.decay.toString()) + lastUserTransactions.forEach(({ balance, balanceDate }) => { + const decay = calculateDecay(new Decimal(balance), new Date(balanceDate), receivedCallDate) + if (decay) { + totalGradidoAvailable = totalGradidoAvailable.plus(decay.balance.toString()) + totalGradidoUnbookedDecayed = totalGradidoUnbookedDecayed.plus(decay.decay.toString()) + } + }) + + return { + activeUsers, + totalGradidoAvailable, + totalGradidoUnbookedDecayed, } - }) - - const { totalGradidoCreated } = await queryRunner.manager - .createQueryBuilder() - .select('SUM(transaction.amount) AS totalGradidoCreated') - .from(DbTransaction, 'transaction') - .where('transaction.typeId = 1') - .getRawOne() - - const { totalGradidoDecayed } = await queryRunner.manager - .createQueryBuilder() - .select('SUM(transaction.decay) AS totalGradidoDecayed') - .from(DbTransaction, 'transaction') - .where('transaction.decay IS NOT NULL') - .getRawOne() - - await queryRunner.release() - - return { - totalUsers, - activeUsers, - deletedUsers, - totalGradidoCreated, - totalGradidoDecayed, - totalGradidoAvailable, - totalGradidoUnbookedDecayed, + } finally { + await queryRunner.release() } } } From 6b1497a988e42c8d24f16135715695563e0fdc97 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 16 Jan 2023 15:23:17 +0100 Subject: [PATCH 06/30] statistics for field resolvers --- admin/src/graphql/communityStatistics.js | 8 +++++--- admin/src/pages/CommunityStatistic.spec.js | 8 +++++--- admin/src/pages/CommunityStatistic.vue | 8 +++++++- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/admin/src/graphql/communityStatistics.js b/admin/src/graphql/communityStatistics.js index 868bfd02a..1b427dbbe 100644 --- a/admin/src/graphql/communityStatistics.js +++ b/admin/src/graphql/communityStatistics.js @@ -4,12 +4,14 @@ export const communityStatistics = gql` query { communityStatistics { totalUsers - activeUsers deletedUsers totalGradidoCreated totalGradidoDecayed - totalGradidoAvailable - totalGradidoUnbookedDecayed + totalAvailable { + activeUsers + totalGradidoAvailable + totalGradidoUnbookedDecayed + } } } ` diff --git a/admin/src/pages/CommunityStatistic.spec.js b/admin/src/pages/CommunityStatistic.spec.js index 50e04d11f..f8bfa95e2 100644 --- a/admin/src/pages/CommunityStatistic.spec.js +++ b/admin/src/pages/CommunityStatistic.spec.js @@ -17,12 +17,14 @@ const defaultData = () => { return { communityStatistics: { totalUsers: 3113, - activeUsers: 1057, deletedUsers: 35, totalGradidoCreated: '4083774.05000000000000000000', totalGradidoDecayed: '-1062639.13634129622923372197', - totalGradidoAvailable: '2513565.869444365732411569', - totalGradidoUnbookedDecayed: '-500474.6738366222166261272', + totalAvailable: { + activeUsers: 1057, + totalGradidoAvailable: '2513565.869444365732411569', + totalGradidoUnbookedDecayed: '-500474.6738366222166261272', + }, }, } } diff --git a/admin/src/pages/CommunityStatistic.vue b/admin/src/pages/CommunityStatistic.vue index 3b4865ee3..6858e77f6 100644 --- a/admin/src/pages/CommunityStatistic.vue +++ b/admin/src/pages/CommunityStatistic.vue @@ -31,7 +31,13 @@ export default { return communityStatistics }, update({ communityStatistics }) { - this.statistics = communityStatistics + const totals = { ...communityStatistics.totalAvailable } + this.statistics = { ...communityStatistics, ...totals } + this.activeUsers = this.statistics.totalAvailable.activeUsers + this.totalGradidoAvailable = this.statistics.totalAvailable.totalGradidoAvailable + this.totalGradidoUnbookedDecayed = + this.statistics.totalAvailable.totalGradidoUnbookedDecayed + delete this.totalAvailable }, error({ message }) { this.toastError(message) From 9f3c43426dc925dcfcd809f3651306c454138f6c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 16 Jan 2023 15:28:20 +0100 Subject: [PATCH 07/30] clean up --- admin/src/pages/CommunityStatistic.vue | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/admin/src/pages/CommunityStatistic.vue b/admin/src/pages/CommunityStatistic.vue index 6858e77f6..6a2261d13 100644 --- a/admin/src/pages/CommunityStatistic.vue +++ b/admin/src/pages/CommunityStatistic.vue @@ -33,11 +33,7 @@ export default { update({ communityStatistics }) { const totals = { ...communityStatistics.totalAvailable } this.statistics = { ...communityStatistics, ...totals } - this.activeUsers = this.statistics.totalAvailable.activeUsers - this.totalGradidoAvailable = this.statistics.totalAvailable.totalGradidoAvailable - this.totalGradidoUnbookedDecayed = - this.statistics.totalAvailable.totalGradidoUnbookedDecayed - delete this.totalAvailable + delete this.statistics.totalAvailable }, error({ message }) { this.toastError(message) From 3bf69814cac29040d63c3e6745eff5549fe62b30 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 16 Jan 2023 16:47:33 +0100 Subject: [PATCH 08/30] test error cases for semaphore --- .../resolver/TransactionLinkResolver.test.ts | 926 ++++++++++-------- .../resolver/TransactionLinkResolver.ts | 9 +- 2 files changed, 538 insertions(+), 397 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 9f7d30244..e841e8fad 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -4,7 +4,7 @@ import { transactionLinkCode } from './TransactionLinkResolver' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { peterLustig } from '@/seeds/users/peter-lustig' -import { cleanDB, testEnvironment, resetToken } from '@test/helpers' +import { cleanDB, testEnvironment, resetToken, resetEntity } from '@test/helpers' import { creationFactory } from '@/seeds/factory/creation' import { creations } from '@/seeds/creation/index' import { userFactory } from '@/seeds/factory/user' @@ -50,238 +50,344 @@ afterAll(async () => { }) describe('TransactionLinkResolver', () => { - // TODO: have this test separated into a transactionLink and a contributionLink part (if possible) - 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 () => { - 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 - }) - - it('does not allow the user to redeem the contribution link', async () => { - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + (contributionLink ? contributionLink.code : ''), - }, - }), - ).resolves.toMatchObject({ - errors: [ - new GraphQLError( - 'Creation from contribution link was not successful. 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, - 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 () => { - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + (contributionLink ? contributionLink.code : ''), - }, - }), - ).resolves.toMatchObject({ - errors: [ - new GraphQLError( - 'Creation from contribution link was not successful. Error: Contribution link already redeemed today', - ), - ], - }) - }) - - describe('after one day', () => { + describe('redeemTransactionLink', () => { + describe('contributionLink', () => { + describe('input not valid', () => { beforeAll(async () => { - jest.useFakeTimers() - setTimeout(jest.fn(), 1000 * 60 * 60 * 24) - jest.runAllTimers() await mutate({ mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, }) }) - afterAll(() => { - jest.useRealTimers() - }) - - it('allows the user to redeem the contribution link again', async () => { + it('throws error when link does not exists', 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 () => { - await expect( - mutate({ - mutation: redeemTransactionLink, - variables: { - code: 'CL-' + (contributionLink ? contributionLink.code : ''), + code: 'CL-123456', }, }), ).resolves.toMatchObject({ errors: [ new GraphQLError( - 'Creation from contribution link was not successful. Error: Contribution link already redeemed today', + 'Creation from contribution link was not successful. Error: No contribution link found to given code: CL-123456', ), ], }) }) + + it('throws error when link is not valid yet', async () => { + const now = new Date() + 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: new Date(now.getFullYear() + 1, 11, 31, 23, 59, 59, 999).toISOString(), + maxAmountPerMonth: new Decimal(200), + maxPerCycle: 1, + }, + }) + const cls = await DbContributionLink.find() + expect(cls).toHaveLength(1) + const contributionLink = cls[0] + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + contributionLink.code, + }, + }), + ).resolves.toMatchObject({ + errors: [ + new GraphQLError( + 'Creation from contribution link was not successful. Error: Contribution link not valid yet', + ), + ], + }) + await resetEntity(DbContributionLink) + }) + + // TODO: why the fuck is this possible? ¯\_(ツ)_/¯ + it('throws error when contributionLink cycle is invalid', async () => { + const now = new Date() + 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, + }, + }) + const cls = await DbContributionLink.find() + expect(cls).toHaveLength(1) + const contributionLink = cls[0] + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + contributionLink.code, + }, + }), + ).resolves.toMatchObject({ + errors: [ + new GraphQLError( + 'Creation from contribution link was not successful. Error: Contribution link has unknown cycle', + ), + ], + }) + await resetEntity(DbContributionLink) + }) + + it('throws error when link is no longer valid', async () => { + const now = new Date() + 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: new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 999).toISOString(), + maxAmountPerMonth: new Decimal(200), + maxPerCycle: 1, + }, + }) + const cls = await DbContributionLink.find() + expect(cls).toHaveLength(1) + const contributionLink = cls[0] + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + contributionLink.code, + }, + }), + ).resolves.toMatchObject({ + errors: [ + new GraphQLError( + 'Creation from contribution link was not successful. Error: Contribution link is no longer valid', + ), + ], + }) + await resetEntity(DbContributionLink) + }) }) - }) - }) - describe('transaction links list', () => { - const variables = { - userId: 1, // dummy, may be replaced - filters: null, - currentPage: 1, - pageSize: 5, - } + // TODO: have this test separated into a transactionLink and a contributionLink part (if possible) + describe('redeem daily Contribution Link', () => { + const now = new Date() + let contributionLink: DbContributionLink | undefined + let contribution: UnconfirmedContribution | undefined - // TODO: there is a test not cleaning up after itself! Fix it! - beforeAll(async () => { - await cleanDB() - resetToken() - }) - - describe('unauthenticated', () => { - it('returns an error', async () => { - await expect( - query({ - query: listTransactionLinksAdmin, - variables, - }), - ).resolves.toEqual( - expect.objectContaining({ - errors: [new GraphQLError('401 Unauthorized')], - }), - ) - }) - }) - - describe('authenticated', () => { - describe('without admin rights', () => { beforeAll(async () => { - user = await userFactory(testEnv, bibiBloxberg) await mutate({ mutation: login, - variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + 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, + }, }) }) - afterAll(async () => { - await cleanDB() - resetToken() + 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 () => { + 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 + }) + + it('does not allow the user to redeem the contribution link', async () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + errors: [ + new GraphQLError( + 'Creation from contribution link was not successful. 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, + 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 () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + errors: [ + new GraphQLError( + 'Creation from contribution link was not successful. 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 () => { + await expect( + mutate({ + mutation: redeemTransactionLink, + variables: { + code: 'CL-' + (contributionLink ? contributionLink.code : ''), + }, + }), + ).resolves.toMatchObject({ + errors: [ + new GraphQLError( + 'Creation from contribution link was not successful. Error: Contribution link already redeemed today', + ), + ], + }) + }) + }) + }) + }) + }) + + describe('transaction links list', () => { + const variables = { + userId: 1, // dummy, may be replaced + filters: null, + currentPage: 1, + pageSize: 5, + } + + // TODO: there is a test not cleaning up after itself! Fix it! + beforeAll(async () => { + await cleanDB() + resetToken() + }) + + describe('unauthenticated', () => { it('returns an error', async () => { await expect( query({ @@ -296,40 +402,22 @@ describe('TransactionLinkResolver', () => { }) }) - describe('with admin rights', () => { - beforeAll(async () => { - // admin 'peter@lustig.de' has to exists for 'creationFactory' - await userFactory(testEnv, peterLustig) - - user = await userFactory(testEnv, bibiBloxberg) - variables.userId = user.id - variables.pageSize = 25 - // bibi needs GDDs - const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await creationFactory(testEnv, bibisCreation!) - // bibis transaktion links - const bibisTransaktionLinks = transactionLinks.filter( - (transactionLink) => transactionLink.email === 'bibi@bloxberg.de', - ) - for (let i = 0; i < bibisTransaktionLinks.length; i++) { - await transactionLinkFactory(testEnv, bibisTransaktionLinks[i]) - } - - // admin: only now log in - await mutate({ - mutation: login, - variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + describe('authenticated', () => { + describe('without admin rights', () => { + beforeAll(async () => { + user = await userFactory(testEnv, bibiBloxberg) + await mutate({ + mutation: login, + variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' }, + }) }) - }) - afterAll(async () => { - await cleanDB() - resetToken() - }) + afterAll(async () => { + await cleanDB() + resetToken() + }) - describe('without any filters', () => { - it('finds 6 open transaction links and no deleted or redeemed', async () => { + it('returns an error', async () => { await expect( query({ query: listTransactionLinksAdmin, @@ -337,185 +425,235 @@ describe('TransactionLinkResolver', () => { }), ).resolves.toEqual( expect.objectContaining({ - data: { - listTransactionLinksAdmin: { - linkCount: 6, - linkList: expect.not.arrayContaining([ - expect.objectContaining({ - memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', - createdAt: expect.any(String), - }), - expect.objectContaining({ - memo: 'Da habe ich mich wohl etwas übernommen.', - deletedAt: expect.any(String), - }), - ]), - }, - }, + errors: [new GraphQLError('401 Unauthorized')], }), ) }) }) - describe('all filters are null', () => { - it('finds 6 open transaction links and no deleted or redeemed', async () => { - await expect( - query({ - query: listTransactionLinksAdmin, - variables: { - ...variables, - filters: { - withDeleted: null, - withExpired: null, - withRedeemed: null, - }, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listTransactionLinksAdmin: { - linkCount: 6, - linkList: expect.not.arrayContaining([ - expect.objectContaining({ - memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', - createdAt: expect.any(String), - }), - expect.objectContaining({ - memo: 'Da habe ich mich wohl etwas übernommen.', - deletedAt: expect.any(String), - }), - ]), - }, - }, - }), - ) - }) - }) + describe('with admin rights', () => { + beforeAll(async () => { + // admin 'peter@lustig.de' has to exists for 'creationFactory' + await userFactory(testEnv, peterLustig) - describe('filter with deleted', () => { - it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => { - await expect( - query({ - query: listTransactionLinksAdmin, - variables: { - ...variables, - filters: { - withDeleted: true, - }, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listTransactionLinksAdmin: { - linkCount: 7, - linkList: expect.arrayContaining([ - expect.not.objectContaining({ - memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', - createdAt: expect.any(String), - }), - expect.objectContaining({ - memo: 'Da habe ich mich wohl etwas übernommen.', - deletedAt: expect.any(String), - }), - ]), - }, - }, - }), + user = await userFactory(testEnv, bibiBloxberg) + variables.userId = user.id + variables.pageSize = 25 + // bibi needs GDDs + const bibisCreation = creations.find( + (creation) => creation.email === 'bibi@bloxberg.de', ) - }) - }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await creationFactory(testEnv, bibisCreation!) + // bibis transaktion links + const bibisTransaktionLinks = transactionLinks.filter( + (transactionLink) => transactionLink.email === 'bibi@bloxberg.de', + ) + for (let i = 0; i < bibisTransaktionLinks.length; i++) { + await transactionLinkFactory(testEnv, bibisTransaktionLinks[i]) + } - describe('filter by expired', () => { - it('finds 5 open transaction links, 1 expired, and no redeemed', async () => { - await expect( - query({ - query: listTransactionLinksAdmin, - variables: { - ...variables, - filters: { - withExpired: true, - }, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listTransactionLinksAdmin: { - linkCount: 7, - linkList: expect.arrayContaining([ - expect.objectContaining({ - memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', - createdAt: expect.any(String), - }), - expect.not.objectContaining({ - memo: 'Da habe ich mich wohl etwas übernommen.', - deletedAt: expect.any(String), - }), - ]), - }, - }, - }), - ) + // admin: only now log in + await mutate({ + mutation: login, + variables: { email: 'peter@lustig.de', password: 'Aa12345_' }, + }) }) - }) - // TODO: works not as expected, because 'redeemedAt' and 'redeemedBy' have to be added to the transaktion link factory - describe.skip('filter by redeemed', () => { - it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => { - await expect( - query({ - query: listTransactionLinksAdmin, - variables: { - ...variables, - filters: { - withDeleted: null, - withExpired: null, - withRedeemed: true, + afterAll(async () => { + await cleanDB() + resetToken() + }) + + describe('without any filters', () => { + it('finds 6 open transaction links and no deleted or redeemed', async () => { + await expect( + query({ + query: listTransactionLinksAdmin, + variables, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + listTransactionLinksAdmin: { + linkCount: 6, + linkList: expect.not.arrayContaining([ + expect.objectContaining({ + memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', + createdAt: expect.any(String), + }), + expect.objectContaining({ + memo: 'Da habe ich mich wohl etwas übernommen.', + deletedAt: expect.any(String), + }), + ]), + }, }, - }, - }), - ).resolves.toEqual( - expect.objectContaining({ - data: { - listTransactionLinksAdmin: { - linkCount: 6, - linkList: expect.arrayContaining([ - expect.not.objectContaining({ - memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', - createdAt: expect.any(String), - }), - expect.objectContaining({ - memo: 'Yeah, eingelöst!', - redeemedAt: expect.any(String), - redeemedBy: expect.any(Number), - }), - expect.not.objectContaining({ - memo: 'Da habe ich mich wohl etwas übernommen.', - deletedAt: expect.any(String), - }), - ]), + }), + ) + }) + }) + + describe('all filters are null', () => { + it('finds 6 open transaction links and no deleted or redeemed', async () => { + await expect( + query({ + query: listTransactionLinksAdmin, + variables: { + ...variables, + filters: { + withDeleted: null, + withExpired: null, + withRedeemed: null, + }, }, - }, - }), - ) + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + listTransactionLinksAdmin: { + linkCount: 6, + linkList: expect.not.arrayContaining([ + expect.objectContaining({ + memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', + createdAt: expect.any(String), + }), + expect.objectContaining({ + memo: 'Da habe ich mich wohl etwas übernommen.', + deletedAt: expect.any(String), + }), + ]), + }, + }, + }), + ) + }) + }) + + describe('filter with deleted', () => { + it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => { + await expect( + query({ + query: listTransactionLinksAdmin, + variables: { + ...variables, + filters: { + withDeleted: true, + }, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + listTransactionLinksAdmin: { + linkCount: 7, + linkList: expect.arrayContaining([ + expect.not.objectContaining({ + memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', + createdAt: expect.any(String), + }), + expect.objectContaining({ + memo: 'Da habe ich mich wohl etwas übernommen.', + deletedAt: expect.any(String), + }), + ]), + }, + }, + }), + ) + }) + }) + + describe('filter by expired', () => { + it('finds 5 open transaction links, 1 expired, and no redeemed', async () => { + await expect( + query({ + query: listTransactionLinksAdmin, + variables: { + ...variables, + filters: { + withExpired: true, + }, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + listTransactionLinksAdmin: { + linkCount: 7, + linkList: expect.arrayContaining([ + expect.objectContaining({ + memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', + createdAt: expect.any(String), + }), + expect.not.objectContaining({ + memo: 'Da habe ich mich wohl etwas übernommen.', + deletedAt: expect.any(String), + }), + ]), + }, + }, + }), + ) + }) + }) + + // TODO: works not as expected, because 'redeemedAt' and 'redeemedBy' have to be added to the transaktion link factory + describe.skip('filter by redeemed', () => { + it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => { + await expect( + query({ + query: listTransactionLinksAdmin, + variables: { + ...variables, + filters: { + withDeleted: null, + withExpired: null, + withRedeemed: true, + }, + }, + }), + ).resolves.toEqual( + expect.objectContaining({ + data: { + listTransactionLinksAdmin: { + linkCount: 6, + linkList: expect.arrayContaining([ + expect.not.objectContaining({ + memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(', + createdAt: expect.any(String), + }), + expect.objectContaining({ + memo: 'Yeah, eingelöst!', + redeemedAt: expect.any(String), + redeemedBy: expect.any(Number), + }), + expect.not.objectContaining({ + memo: 'Da habe ich mich wohl etwas übernommen.', + deletedAt: expect.any(String), + }), + ]), + }, + }, + }), + ) + }) }) }) }) }) }) -}) -describe('transactionLinkCode', () => { - const date = new Date() + describe('transactionLinkCode', () => { + const date = new Date() - it('returns a string of length 24', () => { - expect(transactionLinkCode(date)).toHaveLength(24) - }) + it('returns a string of length 24', () => { + expect(transactionLinkCode(date)).toHaveLength(24) + }) - it('returns a string that ends with the hex value of date', () => { - const regexp = new RegExp(date.getTime().toString(16) + '$') - expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp)) + it('returns a string that ends with the hex value of date', () => { + const regexp = new RegExp(date.getTime().toString(16) + '$') + expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp)) + }) }) }) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.ts b/backend/src/graphql/resolver/TransactionLinkResolver.ts index ac57db131..df70b4bc9 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.ts @@ -185,7 +185,7 @@ export class TransactionLinkResolver { .getOne() if (!contributionLink) { logger.error('no contribution link found to given code:', code) - throw new Error('No contribution link found') + throw new Error(`No contribution link found to given code: ${code}`) } logger.info('...contribution link found with id', contributionLink.id) if (new Date(contributionLink.validFrom).getTime() > now.getTime()) { @@ -197,8 +197,11 @@ export class TransactionLinkResolver { } if (contributionLink.validTo) { if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) { - logger.error('contribution link is depricated. Valid to: ', contributionLink.validTo) - throw new Error('Contribution link is depricated') + logger.error( + 'contribution link is no longer valid. Valid to: ', + contributionLink.validTo, + ) + throw new Error('Contribution link is no longer valid') } } let alreadyRedeemed: DbContribution | undefined From 1e66cda707472f85a3a06c915f70706d000e09db Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 16 Jan 2023 19:41:34 +0100 Subject: [PATCH 09/30] change name of dynamic statitics fields --- admin/src/graphql/communityStatistics.js | 2 +- admin/src/pages/CommunityStatistic.spec.js | 2 +- admin/src/pages/CommunityStatistic.vue | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/admin/src/graphql/communityStatistics.js b/admin/src/graphql/communityStatistics.js index 1b427dbbe..3159ee258 100644 --- a/admin/src/graphql/communityStatistics.js +++ b/admin/src/graphql/communityStatistics.js @@ -7,7 +7,7 @@ export const communityStatistics = gql` deletedUsers totalGradidoCreated totalGradidoDecayed - totalAvailable { + dynamicStatisticsFields { activeUsers totalGradidoAvailable totalGradidoUnbookedDecayed diff --git a/admin/src/pages/CommunityStatistic.spec.js b/admin/src/pages/CommunityStatistic.spec.js index f8bfa95e2..528548f02 100644 --- a/admin/src/pages/CommunityStatistic.spec.js +++ b/admin/src/pages/CommunityStatistic.spec.js @@ -20,7 +20,7 @@ const defaultData = () => { deletedUsers: 35, totalGradidoCreated: '4083774.05000000000000000000', totalGradidoDecayed: '-1062639.13634129622923372197', - totalAvailable: { + dynamicStatisticsFields: { activeUsers: 1057, totalGradidoAvailable: '2513565.869444365732411569', totalGradidoUnbookedDecayed: '-500474.6738366222166261272', diff --git a/admin/src/pages/CommunityStatistic.vue b/admin/src/pages/CommunityStatistic.vue index 6a2261d13..e656fd2c8 100644 --- a/admin/src/pages/CommunityStatistic.vue +++ b/admin/src/pages/CommunityStatistic.vue @@ -31,9 +31,9 @@ export default { return communityStatistics }, update({ communityStatistics }) { - const totals = { ...communityStatistics.totalAvailable } + const totals = { ...communityStatistics.dynamicStatisticsFields } this.statistics = { ...communityStatistics, ...totals } - delete this.statistics.totalAvailable + delete this.statistics.dynamicStatisticsFields }, error({ message }) { this.toastError(message) From b66372bd90e501ffaeb2dc9ed843a8d197e24ea6 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 16 Jan 2023 19:42:01 +0100 Subject: [PATCH 10/30] change name of dynamic statitics fields --- backend/src/graphql/model/CommunityStatistics.ts | 7 ++++--- backend/src/graphql/resolver/StatisticsResolver.ts | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/src/graphql/model/CommunityStatistics.ts b/backend/src/graphql/model/CommunityStatistics.ts index 8b8a34f80..87ef595af 100644 --- a/backend/src/graphql/model/CommunityStatistics.ts +++ b/backend/src/graphql/model/CommunityStatistics.ts @@ -2,7 +2,7 @@ import { ObjectType, Field } from 'type-graphql' import Decimal from 'decimal.js-light' @ObjectType() -export class TotalAvailable { +export class DynamicStatisticsFields { @Field(() => Number) activeUsers: number @@ -30,6 +30,7 @@ export class CommunityStatistics { @Field(() => Decimal) totalGradidoDecayed: Decimal - @Field(() => TotalAvailable) - totalAvailable: TotalAvailable + // be carefull querying this, takes longer than 2 secs. + @Field(() => DynamicStatisticsFields) + dynamicStatisticsFields: DynamicStatisticsFields } diff --git a/backend/src/graphql/resolver/StatisticsResolver.ts b/backend/src/graphql/resolver/StatisticsResolver.ts index 577ca2c81..e91840f10 100644 --- a/backend/src/graphql/resolver/StatisticsResolver.ts +++ b/backend/src/graphql/resolver/StatisticsResolver.ts @@ -5,7 +5,7 @@ import { getConnection } from '@dbTools/typeorm' import { Transaction as DbTransaction } from '@entity/Transaction' import { User as DbUser } from '@entity/User' -import { CommunityStatistics, TotalAvailable } from '@model/CommunityStatistics' +import { CommunityStatistics, DynamicStatisticsFields } from '@model/CommunityStatistics' import { RIGHTS } from '@/auth/RIGHTS' import { calculateDecay } from '@/util/decay' @@ -69,7 +69,7 @@ export class StatisticsResolver { } @FieldResolver() - async totalAvailable(): Promise { + async dynamicStatisticsFields(): Promise { let totalGradidoAvailable: Decimal = new Decimal(0) let totalGradidoUnbookedDecayed: Decimal = new Decimal(0) From 044e10c97288f05b25b6bf64cb1b07bc619396ad Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 17 Jan 2023 09:36:46 +0100 Subject: [PATCH 11/30] feat(frontend): test transaction page --- frontend/src/pages/Transactions.spec.js | 225 ++++++++++-------------- 1 file changed, 95 insertions(+), 130 deletions(-) diff --git a/frontend/src/pages/Transactions.spec.js b/frontend/src/pages/Transactions.spec.js index 51fc15749..ea2a5e1f6 100644 --- a/frontend/src/pages/Transactions.spec.js +++ b/frontend/src/pages/Transactions.spec.js @@ -1,6 +1,7 @@ import { mount } from '@vue/test-utils' import Transactions from './Transactions' import { GdtEntryType } from '@/graphql/enums' +import { listGDTEntriesQuery } from '@/graphql/queries' import { toastErrorSpy } from '@test/testSetup' @@ -45,8 +46,8 @@ describe('Transactions', () => { }, } - const Wrapper = () => { - return mount(Transactions, { localVue, mocks }) + const Wrapper = (propsData = {}) => { + return mount(Transactions, { localVue, mocks, propsData }) } describe('mount', () => { @@ -77,147 +78,111 @@ describe('Transactions', () => { ) }) - it.skip('renders the transaction gradido transform table', () => { - beforeEach(() => { - wrapper.setData({ - gdt: true, - }) + it('renders the transaction gradido transform table when gdt is true', async () => { + await wrapper.setProps({ + gdt: true, }) expect(wrapper.findComponent({ name: 'GdtTransactionList' }).exists()).toBeTruthy() }) - describe.skip('tabs', () => { - it('shows the GDD transactions by default', () => { - expect(wrapper.findAll('div[role="tabpanel"]').at(0).isVisible()).toBeTruthy() - }) - - it('does not show the GDT transactions by default', () => { - expect(wrapper.findAll('div[role="tabpanel"]').at(1).isVisible()).toBeFalsy() - }) - - describe('click on GDT tab', () => { - describe('server returns valid data', () => { - beforeEach(() => { - apolloMock.mockResolvedValue({ - data: { - listGDTEntries: { - count: 4, - gdtEntries: [ - { - id: 1, - amount: 100, - gdt: 1700, - factor: 17, - comment: '', - date: '2021-05-02T17:20:11+00:00', - gdtEntryType: GdtEntryType.FORM, - }, - { - id: 2, - amount: 1810, - gdt: 362, - factor: 0.2, - comment: 'Dezember 20', - date: '2020-12-31T12:00:00+00:00', - gdtEntryType: GdtEntryType.GLOBAL_MODIFICATOR, - }, - { - id: 3, - amount: 100, - gdt: 1700, - factor: 17, - comment: '', - date: '2020-05-07T17:00:00+00:00', - gdtEntryType: GdtEntryType.FORM, - }, - { - id: 4, - amount: 100, - gdt: 110, - factor: 22, - comment: '', - date: '2020-04-10T13:28:00+00:00', - gdtEntryType: GdtEntryType.ELOPAGE_PUBLISHER, - }, - ], - }, - }, - }) - wrapper.findAll('li[ role="presentation"]').at(1).find('a').trigger('click') - }) - - it('does not show the GDD transactions', () => { - expect(wrapper.findAll('div[role="tabpanel"]').at(0).isVisible()).toBeFalsy() - }) - - it('shows the GDT transactions', () => { - expect(wrapper.findAll('div[role="tabpanel"]').at(1).isVisible()).toBeTruthy() - }) - - it('calls the API', () => { - expect(apolloMock).toBeCalledWith( - expect.objectContaining({ - variables: { - currentPage: 1, - pageSize: 25, - }, - }), - ) - }) - - it('scrolls to (0, 0) after API call', () => { - expect(windowScrollToMock).toBeCalledWith(0, 0) - }) - - describe('click on GDD tab', () => { - beforeEach(() => { - wrapper.findAll('li[ role="presentation"]').at(0).find('a').trigger('click') - }) - - it('shows the GDD transactions', () => { - expect(wrapper.findAll('div[role="tabpanel"]').at(0).isVisible()).toBeTruthy() - }) - - it('does not show the GDT', () => { - expect(wrapper.findAll('div[role="tabpanel"]').at(1).isVisible()).toBeFalsy() - }) - }) - }) - - describe('server returns error', () => { - beforeEach(() => { - apolloMock.mockRejectedValue({ message: 'Ouch!' }) - wrapper.findAll('li[ role="presentation"]').at(1).find('a').trigger('click') - }) - - it('toasts an error message', () => { - expect(toastErrorSpy).toBeCalledWith('Ouch!') - }) - - it('sets transactionGdtCount to -1', () => { - expect(wrapper.vm.transactionGdtCount).toBe(-1) - }) - }) - }) - }) - - describe.skip('update currentPage', () => { + describe('update gdt with success', () => { beforeEach(() => { - jest.clearAllMocks() - wrapper.setData({ - currentPage: 2, + apolloMock.mockResolvedValue({ + data: { + listGDTEntries: { + count: 4, + gdtEntries: [ + { + id: 1, + amount: 100, + gdt: 1700, + factor: 17, + comment: '', + date: '2021-05-02T17:20:11+00:00', + gdtEntryType: GdtEntryType.FORM, + }, + { + id: 2, + amount: 1810, + gdt: 362, + factor: 0.2, + comment: 'Dezember 20', + date: '2020-12-31T12:00:00+00:00', + gdtEntryType: GdtEntryType.GLOBAL_MODIFICATOR, + }, + { + id: 3, + amount: 100, + gdt: 1700, + factor: 17, + comment: '', + date: '2020-05-07T17:00:00+00:00', + gdtEntryType: GdtEntryType.FORM, + }, + { + id: 4, + amount: 100, + gdt: 110, + factor: 22, + comment: '', + date: '2020-04-10T13:28:00+00:00', + gdtEntryType: GdtEntryType.ELOPAGE_PUBLISHER, + }, + ], + }, + }, }) + wrapper = Wrapper({ gdt: true }) }) it('calls the API', () => { - expect(apolloMock).toBeCalledWith( - expect.objectContaining({ + expect(apolloMock).toBeCalledWith({ + query: listGDTEntriesQuery, + variables: { + currentPage: 1, + pageSize: 25, + }, + }) + }) + + it('does not show the GDD transactions', () => { + expect(wrapper.findAll('div.gdd-transaction-list').exists()).toBeFalsy() + }) + + it('shows the GDT transactions', () => { + expect(wrapper.findAll('div.gdt-transaction-list').exists()).toBeTruthy() + }) + + it('scrolls to (0, 0) after API call', () => { + expect(windowScrollToMock).toBeCalledWith(0, 0) + }) + + describe('update current page', () => { + beforeEach(() => { + jest.clearAllMocks() + wrapper.vm.currentPage = 2 + }) + + it('calls the API again', () => { + expect(apolloMock).toBeCalledWith({ + query: listGDTEntriesQuery, variables: { currentPage: 2, pageSize: 25, }, - }), - ) + }) + }) + }) + }) + + describe('update gdt with error', () => { + beforeEach(() => { + apolloMock.mockRejectedValue({ message: 'Oh no!' }) + wrapper = Wrapper({ gdt: true }) + }) + + it('toasts the error', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') }) }) }) From e39b829ad853db561ed4444f7e16fc47f073ba86 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 17 Jan 2023 09:37:24 +0100 Subject: [PATCH 12/30] coverage to 92% --- .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 ac60cfdf2..82785db18 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -437,7 +437,7 @@ jobs: report_name: Coverage Frontend type: lcov result_path: ./coverage/lcov.info - min_coverage: 91 + min_coverage: 92 token: ${{ github.token }} ############################################################################## From b52162320d2e6939064cd5025ac0446946e39bba Mon Sep 17 00:00:00 2001 From: mahula Date: Tue, 17 Jan 2023 11:32:22 +0100 Subject: [PATCH 13/30] fix unit tests for amout input component --- frontend/src/components/Inputs/InputAmount.spec.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/Inputs/InputAmount.spec.js b/frontend/src/components/Inputs/InputAmount.spec.js index b2d524e08..b20ab0f23 100644 --- a/frontend/src/components/Inputs/InputAmount.spec.js +++ b/frontend/src/components/Inputs/InputAmount.spec.js @@ -46,13 +46,14 @@ describe('InputAmount', () => { describe('amount normalization', () => { describe('if invalid', () => { - beforeEach(() => { + beforeEach(async () => { + await wrapper.setProps({ value: '12m34' }) valid = false }) it('is not normalized', () => { - wrapper.vm.normalizeAmount(valid) - expect(wrapper.vm.amountValue).toBe(0.0) + wrapper.vm.normalizeAmount(false) + expect(wrapper.vm.currentValue).toBe('12m34') }) }) @@ -97,13 +98,14 @@ describe('InputAmount', () => { describe('amount normalization', () => { describe('if invalid', () => { - beforeEach(() => { + beforeEach(async () => { + await wrapper.setProps({ value: '12m34' }) valid = false }) it('is not normalized', () => { wrapper.vm.normalizeAmount(valid) - expect(wrapper.vm.amountValue).toBe(0.0) + expect(wrapper.vm.currentValue).toBe('12m34') }) }) From 82fd6a9c5bef93c8e04d127245ca2981aee42c8e Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 17 Jan 2023 12:24:16 +0100 Subject: [PATCH 14/30] implement moriz requirements for test --- .../resolver/TransactionLinkResolver.test.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index e841e8fad..a7b31c64a 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -79,7 +79,9 @@ describe('TransactionLinkResolver', () => { it('throws error when link is not valid yet', async () => { const now = new Date() - await mutate({ + const { + data: { createContributionLink: contributionLink }, + } = await mutate({ mutation: createContributionLink, variables: { amount: new Decimal(5), @@ -92,9 +94,6 @@ describe('TransactionLinkResolver', () => { maxPerCycle: 1, }, }) - const cls = await DbContributionLink.find() - expect(cls).toHaveLength(1) - const contributionLink = cls[0] await expect( mutate({ mutation: redeemTransactionLink, @@ -115,7 +114,9 @@ describe('TransactionLinkResolver', () => { // TODO: why the fuck is this possible? ¯\_(ツ)_/¯ it('throws error when contributionLink cycle is invalid', async () => { const now = new Date() - await mutate({ + const { + data: { createContributionLink: contributionLink }, + } = await mutate({ mutation: createContributionLink, variables: { amount: new Decimal(5), @@ -128,9 +129,6 @@ describe('TransactionLinkResolver', () => { maxPerCycle: 1, }, }) - const cls = await DbContributionLink.find() - expect(cls).toHaveLength(1) - const contributionLink = cls[0] await expect( mutate({ mutation: redeemTransactionLink, @@ -150,7 +148,9 @@ describe('TransactionLinkResolver', () => { it('throws error when link is no longer valid', async () => { const now = new Date() - await mutate({ + const { + data: { createContributionLink: contributionLink }, + } = await mutate({ mutation: createContributionLink, variables: { amount: new Decimal(5), @@ -163,9 +163,6 @@ describe('TransactionLinkResolver', () => { maxPerCycle: 1, }, }) - const cls = await DbContributionLink.find() - expect(cls).toHaveLength(1) - const contributionLink = cls[0] await expect( mutate({ mutation: redeemTransactionLink, From a3a69bc012a8905e7e5918821780dbc30203487d Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 17 Jan 2023 12:29:28 +0100 Subject: [PATCH 15/30] removed comment --- backend/src/graphql/resolver/TransactionLinkResolver.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index a7b31c64a..484682947 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -111,7 +111,6 @@ describe('TransactionLinkResolver', () => { await resetEntity(DbContributionLink) }) - // TODO: why the fuck is this possible? ¯\_(ツ)_/¯ it('throws error when contributionLink cycle is invalid', async () => { const now = new Date() const { From cd203ab2c2a296530a2dee4b2d49876e0eeca261 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Tue, 17 Jan 2023 12:31:55 +0100 Subject: [PATCH 16/30] removed `if possible` comment --- backend/src/graphql/resolver/TransactionLinkResolver.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts index 484682947..50f9b0414 100644 --- a/backend/src/graphql/resolver/TransactionLinkResolver.test.ts +++ b/backend/src/graphql/resolver/TransactionLinkResolver.test.ts @@ -180,7 +180,7 @@ describe('TransactionLinkResolver', () => { }) }) - // TODO: have this test separated into a transactionLink and a contributionLink part (if possible) + // 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 From 782978579932fa6e45195310877fbbff64dd9788 Mon Sep 17 00:00:00 2001 From: ogerly Date: Tue, 17 Jan 2023 13:07:49 +0100 Subject: [PATCH 17/30] resolve conflicts --- frontend/src/components/Contributions/ContributionForm.spec.js | 1 + frontend/src/components/Inputs/InputAmount.spec.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Contributions/ContributionForm.spec.js b/frontend/src/components/Contributions/ContributionForm.spec.js index bc7ed9785..020e3f552 100644 --- a/frontend/src/components/Contributions/ContributionForm.spec.js +++ b/frontend/src/components/Contributions/ContributionForm.spec.js @@ -23,6 +23,7 @@ describe('ContributionForm', () => { const mocks = { $t: jest.fn((t) => t), $d: jest.fn((d) => d), + $n: jest.fn((n) => n), $store: { state: { creation: ['1000', '1000', '1000'], diff --git a/frontend/src/components/Inputs/InputAmount.spec.js b/frontend/src/components/Inputs/InputAmount.spec.js index b20ab0f23..ede54b8c3 100644 --- a/frontend/src/components/Inputs/InputAmount.spec.js +++ b/frontend/src/components/Inputs/InputAmount.spec.js @@ -9,10 +9,11 @@ describe('InputAmount', () => { const mocks = { $t: jest.fn((t) => t), + $n: jest.fn((n) => n), $i18n: { locale: jest.fn(() => 'en'), }, - $n: jest.fn((n) => String(n)), + // $n: jest.fn((n) => String(n)), $route: { params: {}, }, From 808eee7ae26290bd6ab3190b1fdbcde59095c956 Mon Sep 17 00:00:00 2001 From: ogerly Date: Tue, 17 Jan 2023 15:39:01 +0100 Subject: [PATCH 18/30] session extend , remove creation from query verifyLogin --- frontend/src/graphql/queries.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 65fde8d1d..3b1186a0c 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -13,7 +13,6 @@ export const verifyLogin = gql` hasElopage publisherId isAdmin - creation hideAmountGDD hideAmountGDT } From 35997f5499bbda70a62b36e4292bfbff64793181 Mon Sep 17 00:00:00 2001 From: ogerly Date: Tue, 17 Jan 2023 15:59:46 +0100 Subject: [PATCH 19/30] remove unused code --- frontend/src/components/Inputs/InputAmount.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/components/Inputs/InputAmount.spec.js b/frontend/src/components/Inputs/InputAmount.spec.js index ede54b8c3..0b03ce769 100644 --- a/frontend/src/components/Inputs/InputAmount.spec.js +++ b/frontend/src/components/Inputs/InputAmount.spec.js @@ -13,7 +13,6 @@ describe('InputAmount', () => { $i18n: { locale: jest.fn(() => 'en'), }, - // $n: jest.fn((n) => String(n)), $route: { params: {}, }, From 66edfaf0c89c3e6081544f47291194bd32aa5768 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 17 Jan 2023 18:09:32 +0100 Subject: [PATCH 20/30] fix(frontend): tunneled email on right side last transactions --- frontend/src/layouts/DashboardLayout.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/layouts/DashboardLayout.vue b/frontend/src/layouts/DashboardLayout.vue index ae1e0b75e..722f89fbc 100755 --- a/frontend/src/layouts/DashboardLayout.vue +++ b/frontend/src/layouts/DashboardLayout.vue @@ -125,14 +125,13 @@ :transactions="transactions" :transactionCount="transactionCount" :transactionLinkCount="transactionLinkCount" - @set-tunneled-email="setTunneledEmail" >