Merge branch 'master' into 2547-normalized-amount-transaction-is-processed-again

This commit is contained in:
Alexander Friedland 2023-01-17 16:00:16 +01:00 committed by GitHub
commit aadd2149aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 683 additions and 546 deletions

View File

@ -557,7 +557,6 @@ export class ContributionResolver {
): Promise<boolean> { ): Promise<boolean> {
// acquire lock // acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire() const releaseLock = await TRANSACTIONS_LOCK.acquire()
try { try {
const clientTimezoneOffset = getClientTimezoneOffset(context) const clientTimezoneOffset = getClientTimezoneOffset(context)
const contribution = await DbContribution.findOne(id) const contribution = await DbContribution.findOne(id)
@ -664,7 +663,6 @@ export class ContributionResolver {
} finally { } finally {
releaseLock() releaseLock()
} }
return true return true
} }

View File

@ -4,7 +4,7 @@
import { transactionLinkCode } from './TransactionLinkResolver' import { transactionLinkCode } from './TransactionLinkResolver'
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg' import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
import { peterLustig } from '@/seeds/users/peter-lustig' 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 { creationFactory } from '@/seeds/factory/creation'
import { creations } from '@/seeds/creation/index' import { creations } from '@/seeds/creation/index'
import { userFactory } from '@/seeds/factory/user' import { userFactory } from '@/seeds/factory/user'
@ -50,7 +50,137 @@ afterAll(async () => {
}) })
describe('TransactionLinkResolver', () => { describe('TransactionLinkResolver', () => {
// TODO: have this test separated into a transactionLink and a contributionLink part (if possible) describe('redeemTransactionLink', () => {
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 () => {
await expect(
mutate({
mutation: redeemTransactionLink,
variables: {
code: 'CL-123456',
},
}),
).resolves.toMatchObject({
errors: [
new GraphQLError(
'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()
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: 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. Error: Contribution link not valid yet',
),
],
})
await resetEntity(DbContributionLink)
})
it('throws error when contributionLink cycle is invalid', async () => {
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. Error: Contribution link has unknown cycle',
),
],
})
await resetEntity(DbContributionLink)
})
it('throws error when link is no longer valid', async () => {
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: '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,
},
})
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)
})
})
// TODO: have this test separated into a transactionLink and a contributionLink part
describe('redeem daily Contribution Link', () => { describe('redeem daily Contribution Link', () => {
const now = new Date() const now = new Date()
let contributionLink: DbContributionLink | undefined let contributionLink: DbContributionLink | undefined
@ -237,6 +367,7 @@ describe('TransactionLinkResolver', () => {
}) })
}) })
}) })
})
describe('transaction links list', () => { describe('transaction links list', () => {
const variables = { const variables = {
@ -305,7 +436,9 @@ describe('TransactionLinkResolver', () => {
variables.userId = user.id variables.userId = user.id
variables.pageSize = 25 variables.pageSize = 25
// bibi needs GDDs // bibi needs GDDs
const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de') const bibisCreation = creations.find(
(creation) => creation.email === 'bibi@bloxberg.de',
)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await creationFactory(testEnv, bibisCreation!) await creationFactory(testEnv, bibisCreation!)
// bibis transaktion links // bibis transaktion links
@ -505,9 +638,9 @@ describe('TransactionLinkResolver', () => {
}) })
}) })
}) })
}) })
describe('transactionLinkCode', () => { describe('transactionLinkCode', () => {
const date = new Date() const date = new Date()
it('returns a string of length 24', () => { it('returns a string of length 24', () => {
@ -518,4 +651,5 @@ describe('transactionLinkCode', () => {
const regexp = new RegExp(date.getTime().toString(16) + '$') const regexp = new RegExp(date.getTime().toString(16) + '$')
expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp)) expect(transactionLinkCode(date)).toEqual(expect.stringMatching(regexp))
}) })
})
}) })

View File

@ -170,6 +170,7 @@ export class TransactionLinkResolver {
if (code.match(/^CL-/)) { if (code.match(/^CL-/)) {
// acquire lock // acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire() const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
logger.info('redeem contribution link...') logger.info('redeem contribution link...')
const now = new Date() const now = new Date()
const queryRunner = getConnection().createQueryRunner() const queryRunner = getConnection().createQueryRunner()
@ -184,7 +185,7 @@ export class TransactionLinkResolver {
.getOne() .getOne()
if (!contributionLink) { if (!contributionLink) {
logger.error('no contribution link found to given code:', code) 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) logger.info('...contribution link found with id', contributionLink.id)
if (new Date(contributionLink.validFrom).getTime() > now.getTime()) { if (new Date(contributionLink.validFrom).getTime() > now.getTime()) {
@ -196,8 +197,11 @@ export class TransactionLinkResolver {
} }
if (contributionLink.validTo) { if (contributionLink.validTo) {
if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) { if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) {
logger.error('contribution link is depricated. Valid to: ', contributionLink.validTo) logger.error(
throw new Error('Contribution link is depricated') 'contribution link is no longer valid. Valid to: ',
contributionLink.validTo,
)
throw new Error('Contribution link is no longer valid')
} }
} }
let alreadyRedeemed: DbContribution | undefined let alreadyRedeemed: DbContribution | undefined
@ -312,6 +316,8 @@ export class TransactionLinkResolver {
throw new Error(`Creation from contribution link was not successful. ${e}`) throw new Error(`Creation from contribution link was not successful. ${e}`)
} finally { } finally {
await queryRunner.release() await queryRunner.release()
}
} finally {
releaseLock() releaseLock()
} }
return true return true

View File

@ -45,6 +45,9 @@ export const executeTransaction = async (
recipient: dbUser, recipient: dbUser,
transactionLink?: dbTransactionLink | null, transactionLink?: dbTransactionLink | null,
): Promise<boolean> => { ): Promise<boolean> => {
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
logger.info( logger.info(
`executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`, `executeTransaction(amount=${amount}, memo=${memo}, sender=${sender}, recipient=${recipient})...`,
) )
@ -64,10 +67,6 @@ export const executeTransaction = async (
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`) throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
} }
// acquire lock
const releaseLock = await TRANSACTIONS_LOCK.acquire()
try {
// validate amount // validate amount
const receivedCallDate = new Date() const receivedCallDate = new Date()
const sendBalance = await calculateBalance( const sendBalance = await calculateBalance(
@ -187,10 +186,10 @@ export const executeTransaction = async (
}) })
} }
logger.info(`finished executeTransaction successfully`) logger.info(`finished executeTransaction successfully`)
return true
} finally { } finally {
releaseLock() releaseLock()
} }
return true
} }
@Resolver() @Resolver()