mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
separate Contribution- and TransactionLink Resolvers
This commit is contained in:
parent
a2c1b0ff96
commit
ecb99bd603
@ -9,9 +9,10 @@ module.exports = {
|
|||||||
modulePathIgnorePatterns: ['<rootDir>/build/'],
|
modulePathIgnorePatterns: ['<rootDir>/build/'],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'@/(.*)': '<rootDir>/src/$1',
|
'@/(.*)': '<rootDir>/src/$1',
|
||||||
'@model/(.*)': '<rootDir>/src/graphql/model/$1',
|
|
||||||
'@arg/(.*)': '<rootDir>/src/graphql/arg/$1',
|
'@arg/(.*)': '<rootDir>/src/graphql/arg/$1',
|
||||||
'@enum/(.*)': '<rootDir>/src/graphql/enum/$1',
|
'@enum/(.*)': '<rootDir>/src/graphql/enum/$1',
|
||||||
|
'@model/(.*)': '<rootDir>/src/graphql/model/$1',
|
||||||
|
'@union/(.*)': '<rootDir>/src/graphql/union/$1',
|
||||||
'@repository/(.*)': '<rootDir>/src/typeorm/repository/$1',
|
'@repository/(.*)': '<rootDir>/src/typeorm/repository/$1',
|
||||||
'@test/(.*)': '<rootDir>/test/$1',
|
'@test/(.*)': '<rootDir>/test/$1',
|
||||||
'@entity/(.*)':
|
'@entity/(.*)':
|
||||||
|
|||||||
650
backend/src/graphql/resolver/ContributionLinkResolver.test.ts
Normal file
650
backend/src/graphql/resolver/ContributionLinkResolver.test.ts
Normal file
@ -0,0 +1,650 @@
|
|||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
import { logger } from '@test/testSetup'
|
||||||
|
import { GraphQLError } from 'graphql'
|
||||||
|
import {
|
||||||
|
login,
|
||||||
|
createContributionLink,
|
||||||
|
deleteContributionLink,
|
||||||
|
updateContributionLink,
|
||||||
|
} from '@/seeds/graphql/mutations'
|
||||||
|
import { listContributionLinks } from '@/seeds/graphql/queries'
|
||||||
|
import { cleanDB, testEnvironment, resetToken } from '@test/helpers'
|
||||||
|
import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||||
|
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||||
|
import { User } from '@entity/User'
|
||||||
|
import { userFactory } from '@/seeds/factory/user'
|
||||||
|
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||||
|
|
||||||
|
let mutate: any, query: any, con: any
|
||||||
|
let testEnv: any
|
||||||
|
|
||||||
|
let user: User
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
testEnv = await testEnvironment()
|
||||||
|
mutate = testEnv.mutate
|
||||||
|
query = testEnv.query
|
||||||
|
con = testEnv.con
|
||||||
|
await cleanDB()
|
||||||
|
await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await userFactory(testEnv, peterLustig)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
await con.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Contribution Links', () => {
|
||||||
|
const now = new Date()
|
||||||
|
const variables = {
|
||||||
|
amount: new Decimal(200),
|
||||||
|
name: 'Dokumenta 2022',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||||
|
cycle: 'once',
|
||||||
|
validFrom: new Date(2022, 5, 18).toISOString(),
|
||||||
|
validTo: new Date(now.getFullYear() + 1, 7, 14).toISOString(),
|
||||||
|
maxAmountPerMonth: new Decimal(200),
|
||||||
|
maxPerCycle: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('createContributionLink', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('listContributionLinks', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('updateContributionLink', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
id: -1,
|
||||||
|
amount: new Decimal(400),
|
||||||
|
name: 'Dokumenta 2023',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('deleteContributionLink', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
||||||
|
).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_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('createContributionLink', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Set this test in new location to have datas
|
||||||
|
describe('listContributionLinks', () => {
|
||||||
|
it('returns an empty object', async () => {
|
||||||
|
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
listContributionLinks: {
|
||||||
|
count: 0,
|
||||||
|
links: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('updateContributionLink', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
id: -1,
|
||||||
|
amount: new Decimal(400),
|
||||||
|
name: 'Dokumenta 2023',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('deleteContributionLink', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('with admin rights', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
user = await userFactory(testEnv, peterLustig)
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('createContributionLink', () => {
|
||||||
|
it('returns a contribution link object', async () => {
|
||||||
|
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
createContributionLink: expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
amount: '200',
|
||||||
|
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||||
|
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
name: 'Dokumenta 2022',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||||
|
validFrom: expect.any(String),
|
||||||
|
validTo: expect.any(String),
|
||||||
|
maxAmountPerMonth: '200',
|
||||||
|
cycle: 'once',
|
||||||
|
maxPerCycle: 1,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('has a contribution link stored in db', async () => {
|
||||||
|
const cls = await DbContributionLink.find()
|
||||||
|
expect(cls).toHaveLength(1)
|
||||||
|
expect(cls[0]).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: expect.any(Number),
|
||||||
|
name: 'Dokumenta 2022',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||||
|
validFrom: new Date('2022-06-18T00:00:00.000Z'),
|
||||||
|
validTo: expect.any(Date),
|
||||||
|
cycle: 'once',
|
||||||
|
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(200),
|
||||||
|
maxAmountPerMonth: expect.decimalEqual(200),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if missing startDate', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
validFrom: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError('Start-Date is not initialized. A Start-Date must be set!'),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Start-Date is not initialized. A Start-Date must be set!',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if missing endDate', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
validTo: null,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('End-Date is not initialized. An End-Date must be set!')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'End-Date is not initialized. An End-Date must be set!',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if endDate is before startDate', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
validFrom: new Date('2022-06-18T00:00:00.001Z').toISOString(),
|
||||||
|
validTo: new Date('2022-06-18T00:00:00.000Z').toISOString(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(`The value of validFrom must before or equals the validTo!`),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
`The value of validFrom must before or equals the validTo!`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if name is an empty string', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
name: '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('The name must be initialized!')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith('The name must be initialized!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if name is shorter than 5 characters', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
name: '123',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
`The value of 'name' with a length of 3 did not fulfill the requested bounderies min=5 and max=100`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
`The value of 'name' with a length of 3 did not fulfill the requested bounderies min=5 and max=100`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if name is longer than 100 characters', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
name: '12345678901234567892123456789312345678941234567895123456789612345678971234567898123456789912345678901',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
`The value of 'name' with a length of 101 did not fulfill the requested bounderies min=5 and max=100`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
`The value of 'name' with a length of 101 did not fulfill the requested bounderies min=5 and max=100`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if memo is an empty string', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
memo: '',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('The memo must be initialized!')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith('The memo must be initialized!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if memo is shorter than 5 characters', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
memo: '123',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
`The value of 'memo' with a length of 3 did not fulfill the requested bounderies min=5 and max=255`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
`The value of 'memo' with a length of 3 did not fulfill the requested bounderies min=5 and max=255`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if memo is longer than 255 characters', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
memo: '1234567890123456789212345678931234567894123456789512345678961234567897123456789812345678991234567890123456789012345678921234567893123456789412345678951234567896123456789712345678981234567899123456789012345678901234567892123456789312345678941234567895123456',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [
|
||||||
|
new GraphQLError(
|
||||||
|
`The value of 'memo' with a length of 256 did not fulfill the requested bounderies min=5 and max=255`,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
`The value of 'memo' with a length of 256 did not fulfill the requested bounderies min=5 and max=255`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an error if amount is not positive', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: createContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
amount: new Decimal(0),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('The amount=0 must be initialized with a positiv value!')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'The amount=0 must be initialized with a positiv value!',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('listContributionLinks', () => {
|
||||||
|
describe('one link in DB', () => {
|
||||||
|
it('returns the link and count 1', async () => {
|
||||||
|
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
listContributionLinks: {
|
||||||
|
links: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
amount: '200',
|
||||||
|
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||||
|
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
name: 'Dokumenta 2022',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
||||||
|
validFrom: expect.any(String),
|
||||||
|
validTo: expect.any(String),
|
||||||
|
maxAmountPerMonth: '200',
|
||||||
|
cycle: 'once',
|
||||||
|
maxPerCycle: 1,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('updateContributionLink', () => {
|
||||||
|
describe('no valid id', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
id: -1,
|
||||||
|
amount: new Decimal(400),
|
||||||
|
name: 'Dokumenta 2023',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('Contribution Link not found to given id.')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('valid id', () => {
|
||||||
|
let linkId: number
|
||||||
|
beforeAll(async () => {
|
||||||
|
const links = await query({ query: listContributionLinks })
|
||||||
|
linkId = links.data.listContributionLinks.links[0].id
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns updated contribution link object', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateContributionLink,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
id: linkId,
|
||||||
|
amount: new Decimal(400),
|
||||||
|
name: 'Dokumenta 2023',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
updateContributionLink: {
|
||||||
|
id: linkId,
|
||||||
|
amount: '400',
|
||||||
|
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||||
|
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
name: 'Dokumenta 2023',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||||
|
validFrom: expect.any(String),
|
||||||
|
validTo: expect.any(String),
|
||||||
|
maxAmountPerMonth: '200',
|
||||||
|
cycle: 'once',
|
||||||
|
maxPerCycle: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updated the DB record', async () => {
|
||||||
|
await expect(DbContributionLink.findOne(linkId)).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: linkId,
|
||||||
|
name: 'Dokumenta 2023',
|
||||||
|
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
||||||
|
amount: expect.decimalEqual(400),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('deleteContributionLink', () => {
|
||||||
|
describe('no valid id', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('Contribution Link not found to given id.')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('valid id', () => {
|
||||||
|
let linkId: number
|
||||||
|
beforeAll(async () => {
|
||||||
|
const links = await query({ query: listContributionLinks })
|
||||||
|
linkId = links.data.listContributionLinks.links[0].id
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns a date string', async () => {
|
||||||
|
await expect(
|
||||||
|
mutate({ mutation: deleteContributionLink, variables: { id: linkId } }),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
deleteContributionLink: expect.any(String),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not list this contribution link anymore', async () => {
|
||||||
|
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
listContributionLinks: {
|
||||||
|
links: [],
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
152
backend/src/graphql/resolver/ContributionLinkResolver.ts
Normal file
152
backend/src/graphql/resolver/ContributionLinkResolver.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import Decimal from 'decimal.js-light'
|
||||||
|
import { Resolver, Args, Arg, Authorized, Mutation, Query, Int } from 'type-graphql'
|
||||||
|
import { MoreThan, IsNull } from '@dbTools/typeorm'
|
||||||
|
|
||||||
|
import {
|
||||||
|
CONTRIBUTIONLINK_NAME_MAX_CHARS,
|
||||||
|
CONTRIBUTIONLINK_NAME_MIN_CHARS,
|
||||||
|
MEMO_MAX_CHARS,
|
||||||
|
MEMO_MIN_CHARS,
|
||||||
|
} from './const/const'
|
||||||
|
import { isStartEndDateValid } from './util/creations'
|
||||||
|
import { ContributionLinkList } from '@model/ContributionLinkList'
|
||||||
|
import { ContributionLink } from '@model/ContributionLink'
|
||||||
|
import ContributionLinkArgs from '@arg/ContributionLinkArgs'
|
||||||
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
|
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||||
|
import { Order } from '@enum/Order'
|
||||||
|
import Paginated from '@arg/Paginated'
|
||||||
|
|
||||||
|
// TODO: this is a strange construct
|
||||||
|
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
|
||||||
|
|
||||||
|
@Resolver()
|
||||||
|
export class ContributionLinkResolver {
|
||||||
|
@Authorized([RIGHTS.CREATE_CONTRIBUTION_LINK])
|
||||||
|
@Mutation(() => ContributionLink)
|
||||||
|
async createContributionLink(
|
||||||
|
@Args()
|
||||||
|
{
|
||||||
|
amount,
|
||||||
|
name,
|
||||||
|
memo,
|
||||||
|
cycle,
|
||||||
|
validFrom,
|
||||||
|
validTo,
|
||||||
|
maxAmountPerMonth,
|
||||||
|
maxPerCycle,
|
||||||
|
}: ContributionLinkArgs,
|
||||||
|
): Promise<ContributionLink> {
|
||||||
|
isStartEndDateValid(validFrom, validTo)
|
||||||
|
if (!name) {
|
||||||
|
logger.error(`The name must be initialized!`)
|
||||||
|
throw new Error(`The name must be initialized!`)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS ||
|
||||||
|
name.length > CONTRIBUTIONLINK_NAME_MAX_CHARS
|
||||||
|
) {
|
||||||
|
const msg = `The value of 'name' with a length of ${name.length} did not fulfill the requested bounderies min=${CONTRIBUTIONLINK_NAME_MIN_CHARS} and max=${CONTRIBUTIONLINK_NAME_MAX_CHARS}`
|
||||||
|
logger.error(`${msg}`)
|
||||||
|
throw new Error(`${msg}`)
|
||||||
|
}
|
||||||
|
if (!memo) {
|
||||||
|
logger.error(`The memo must be initialized!`)
|
||||||
|
throw new Error(`The memo must be initialized!`)
|
||||||
|
}
|
||||||
|
if (memo.length < MEMO_MIN_CHARS || memo.length > MEMO_MAX_CHARS) {
|
||||||
|
const msg = `The value of 'memo' with a length of ${memo.length} did not fulfill the requested bounderies min=${MEMO_MIN_CHARS} and max=${MEMO_MAX_CHARS}`
|
||||||
|
logger.error(`${msg}`)
|
||||||
|
throw new Error(`${msg}`)
|
||||||
|
}
|
||||||
|
if (!amount) {
|
||||||
|
logger.error(`The amount must be initialized!`)
|
||||||
|
throw new Error('The amount must be initialized!')
|
||||||
|
}
|
||||||
|
if (!new Decimal(amount).isPositive()) {
|
||||||
|
logger.error(`The amount=${amount} must be initialized with a positiv value!`)
|
||||||
|
throw new Error(`The amount=${amount} must be initialized with a positiv value!`)
|
||||||
|
}
|
||||||
|
const dbContributionLink = new DbContributionLink()
|
||||||
|
dbContributionLink.amount = amount
|
||||||
|
dbContributionLink.name = name
|
||||||
|
dbContributionLink.memo = memo
|
||||||
|
dbContributionLink.createdAt = new Date()
|
||||||
|
dbContributionLink.code = contributionLinkCode(dbContributionLink.createdAt)
|
||||||
|
dbContributionLink.cycle = cycle
|
||||||
|
if (validFrom) dbContributionLink.validFrom = new Date(validFrom)
|
||||||
|
if (validTo) dbContributionLink.validTo = new Date(validTo)
|
||||||
|
dbContributionLink.maxAmountPerMonth = maxAmountPerMonth
|
||||||
|
dbContributionLink.maxPerCycle = maxPerCycle
|
||||||
|
await dbContributionLink.save()
|
||||||
|
logger.debug(`createContributionLink successful!`)
|
||||||
|
return new ContributionLink(dbContributionLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.LIST_CONTRIBUTION_LINKS])
|
||||||
|
@Query(() => ContributionLinkList)
|
||||||
|
async listContributionLinks(
|
||||||
|
@Args()
|
||||||
|
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
||||||
|
): Promise<ContributionLinkList> {
|
||||||
|
const [links, count] = await DbContributionLink.findAndCount({
|
||||||
|
where: [{ validTo: MoreThan(new Date()) }, { validTo: IsNull() }],
|
||||||
|
order: { createdAt: order },
|
||||||
|
skip: (currentPage - 1) * pageSize,
|
||||||
|
take: pageSize,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
links: links.map((link: DbContributionLink) => new ContributionLink(link)),
|
||||||
|
count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.DELETE_CONTRIBUTION_LINK])
|
||||||
|
@Mutation(() => Date, { nullable: true })
|
||||||
|
async deleteContributionLink(@Arg('id', () => Int) id: number): Promise<Date | null> {
|
||||||
|
const contributionLink = await DbContributionLink.findOne(id)
|
||||||
|
if (!contributionLink) {
|
||||||
|
logger.error(`Contribution Link not found to given id: ${id}`)
|
||||||
|
throw new Error('Contribution Link not found to given id.')
|
||||||
|
}
|
||||||
|
await contributionLink.softRemove()
|
||||||
|
logger.debug(`deleteContributionLink successful!`)
|
||||||
|
const newContributionLink = await DbContributionLink.findOne({ id }, { withDeleted: true })
|
||||||
|
return newContributionLink ? newContributionLink.deletedAt : null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Authorized([RIGHTS.UPDATE_CONTRIBUTION_LINK])
|
||||||
|
@Mutation(() => ContributionLink)
|
||||||
|
async updateContributionLink(
|
||||||
|
@Args()
|
||||||
|
{
|
||||||
|
amount,
|
||||||
|
name,
|
||||||
|
memo,
|
||||||
|
cycle,
|
||||||
|
validFrom,
|
||||||
|
validTo,
|
||||||
|
maxAmountPerMonth,
|
||||||
|
maxPerCycle,
|
||||||
|
}: ContributionLinkArgs,
|
||||||
|
@Arg('id', () => Int) id: number,
|
||||||
|
): Promise<ContributionLink> {
|
||||||
|
const dbContributionLink = await DbContributionLink.findOne(id)
|
||||||
|
if (!dbContributionLink) {
|
||||||
|
logger.error(`Contribution Link not found to given id: ${id}`)
|
||||||
|
throw new Error('Contribution Link not found to given id.')
|
||||||
|
}
|
||||||
|
dbContributionLink.amount = amount
|
||||||
|
dbContributionLink.name = name
|
||||||
|
dbContributionLink.memo = memo
|
||||||
|
dbContributionLink.cycle = cycle
|
||||||
|
if (validFrom) dbContributionLink.validFrom = new Date(validFrom)
|
||||||
|
if (validTo) dbContributionLink.validTo = new Date(validTo)
|
||||||
|
dbContributionLink.maxAmountPerMonth = maxAmountPerMonth
|
||||||
|
dbContributionLink.maxPerCycle = maxPerCycle
|
||||||
|
await dbContributionLink.save()
|
||||||
|
logger.debug(`updateContributionLink successful!`)
|
||||||
|
return new ContributionLink(dbContributionLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,19 +13,16 @@ import { transactionLinks } from '@/seeds/transactionLink/index'
|
|||||||
import {
|
import {
|
||||||
login,
|
login,
|
||||||
createContributionLink,
|
createContributionLink,
|
||||||
deleteContributionLink,
|
|
||||||
updateContributionLink,
|
|
||||||
redeemTransactionLink,
|
redeemTransactionLink,
|
||||||
createContribution,
|
createContribution,
|
||||||
updateContribution,
|
updateContribution,
|
||||||
} from '@/seeds/graphql/mutations'
|
} from '@/seeds/graphql/mutations'
|
||||||
import { listTransactionLinksAdmin, listContributionLinks } from '@/seeds/graphql/queries'
|
import { listTransactionLinksAdmin } from '@/seeds/graphql/queries'
|
||||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||||
import { User } from '@entity/User'
|
import { User } from '@entity/User'
|
||||||
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { logger } from '@test/testSetup'
|
|
||||||
|
|
||||||
let mutate: any, query: any, con: any
|
let mutate: any, query: any, con: any
|
||||||
let testEnv: any
|
let testEnv: any
|
||||||
@ -49,6 +46,7 @@ afterAll(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('TransactionLinkResolver', () => {
|
describe('TransactionLinkResolver', () => {
|
||||||
|
// TODO: have this test separated into a transactionLink and a contributionLink part (if possible)
|
||||||
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
|
||||||
@ -504,617 +502,6 @@ describe('TransactionLinkResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Contribution Links', () => {
|
|
||||||
const now = new Date()
|
|
||||||
const variables = {
|
|
||||||
amount: new Decimal(200),
|
|
||||||
name: 'Dokumenta 2022',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
|
||||||
cycle: 'once',
|
|
||||||
validFrom: new Date(2022, 5, 18).toISOString(),
|
|
||||||
validTo: new Date(now.getFullYear() + 1, 7, 14).toISOString(),
|
|
||||||
maxAmountPerMonth: new Decimal(200),
|
|
||||||
maxPerCycle: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
|
||||||
describe('createContributionLink', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('401 Unauthorized')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('listContributionLinks', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('401 Unauthorized')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('updateContributionLink', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: updateContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
id: -1,
|
|
||||||
amount: new Decimal(400),
|
|
||||||
name: 'Dokumenta 2023',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('401 Unauthorized')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('deleteContributionLink', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
|
||||||
).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_' },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await cleanDB()
|
|
||||||
resetToken()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('createContributionLink', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('401 Unauthorized')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: Set this test in new location to have datas
|
|
||||||
describe('listContributionLinks', () => {
|
|
||||||
it('returns an empty object', async () => {
|
|
||||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
listContributionLinks: {
|
|
||||||
count: 0,
|
|
||||||
links: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('updateContributionLink', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: updateContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
id: -1,
|
|
||||||
amount: new Decimal(400),
|
|
||||||
name: 'Dokumenta 2023',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('401 Unauthorized')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('deleteContributionLink', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('401 Unauthorized')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with admin rights', () => {
|
|
||||||
beforeAll(async () => {
|
|
||||||
user = await userFactory(testEnv, peterLustig)
|
|
||||||
await mutate({
|
|
||||||
mutation: login,
|
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
|
||||||
await cleanDB()
|
|
||||||
resetToken()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('createContributionLink', () => {
|
|
||||||
it('returns a contribution link object', async () => {
|
|
||||||
await expect(mutate({ mutation: createContributionLink, variables })).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
createContributionLink: expect.objectContaining({
|
|
||||||
id: expect.any(Number),
|
|
||||||
amount: '200',
|
|
||||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
|
||||||
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
name: 'Dokumenta 2022',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
|
||||||
validFrom: expect.any(String),
|
|
||||||
validTo: expect.any(String),
|
|
||||||
maxAmountPerMonth: '200',
|
|
||||||
cycle: 'once',
|
|
||||||
maxPerCycle: 1,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a contribution link stored in db', async () => {
|
|
||||||
const cls = await DbContributionLink.find()
|
|
||||||
expect(cls).toHaveLength(1)
|
|
||||||
expect(cls[0]).toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: expect.any(Number),
|
|
||||||
name: 'Dokumenta 2022',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
|
||||||
validFrom: new Date('2022-06-18T00:00:00.000Z'),
|
|
||||||
validTo: expect.any(Date),
|
|
||||||
cycle: 'once',
|
|
||||||
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(200),
|
|
||||||
maxAmountPerMonth: expect.decimalEqual(200),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if missing startDate', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
validFrom: null,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [
|
|
||||||
new GraphQLError('Start-Date is not initialized. A Start-Date must be set!'),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith(
|
|
||||||
'Start-Date is not initialized. A Start-Date must be set!',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if missing endDate', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
validTo: null,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('End-Date is not initialized. An End-Date must be set!')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith(
|
|
||||||
'End-Date is not initialized. An End-Date must be set!',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if endDate is before startDate', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
validFrom: new Date('2022-06-18T00:00:00.001Z').toISOString(),
|
|
||||||
validTo: new Date('2022-06-18T00:00:00.000Z').toISOString(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [
|
|
||||||
new GraphQLError(`The value of validFrom must before or equals the validTo!`),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith(
|
|
||||||
`The value of validFrom must before or equals the validTo!`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if name is an empty string', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('The name must be initialized!')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('The name must be initialized!')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if name is shorter than 5 characters', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
name: '123',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [
|
|
||||||
new GraphQLError(
|
|
||||||
`The value of 'name' with a length of 3 did not fulfill the requested bounderies min=5 and max=100`,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith(
|
|
||||||
`The value of 'name' with a length of 3 did not fulfill the requested bounderies min=5 and max=100`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if name is longer than 100 characters', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
name: '12345678901234567892123456789312345678941234567895123456789612345678971234567898123456789912345678901',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [
|
|
||||||
new GraphQLError(
|
|
||||||
`The value of 'name' with a length of 101 did not fulfill the requested bounderies min=5 and max=100`,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith(
|
|
||||||
`The value of 'name' with a length of 101 did not fulfill the requested bounderies min=5 and max=100`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if memo is an empty string', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
memo: '',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('The memo must be initialized!')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('The memo must be initialized!')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if memo is shorter than 5 characters', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
memo: '123',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [
|
|
||||||
new GraphQLError(
|
|
||||||
`The value of 'memo' with a length of 3 did not fulfill the requested bounderies min=5 and max=255`,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith(
|
|
||||||
`The value of 'memo' with a length of 3 did not fulfill the requested bounderies min=5 and max=255`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if memo is longer than 255 characters', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
memo: '1234567890123456789212345678931234567894123456789512345678961234567897123456789812345678991234567890123456789012345678921234567893123456789412345678951234567896123456789712345678981234567899123456789012345678901234567892123456789312345678941234567895123456',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [
|
|
||||||
new GraphQLError(
|
|
||||||
`The value of 'memo' with a length of 256 did not fulfill the requested bounderies min=5 and max=255`,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith(
|
|
||||||
`The value of 'memo' with a length of 256 did not fulfill the requested bounderies min=5 and max=255`,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error if amount is not positive', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: createContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
amount: new Decimal(0),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [
|
|
||||||
new GraphQLError('The amount=0 must be initialized with a positiv value!'),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith(
|
|
||||||
'The amount=0 must be initialized with a positiv value!',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('listContributionLinks', () => {
|
|
||||||
describe('one link in DB', () => {
|
|
||||||
it('returns the link and count 1', async () => {
|
|
||||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
listContributionLinks: {
|
|
||||||
links: expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
amount: '200',
|
|
||||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
|
||||||
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
name: 'Dokumenta 2022',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2022',
|
|
||||||
validFrom: expect.any(String),
|
|
||||||
validTo: expect.any(String),
|
|
||||||
maxAmountPerMonth: '200',
|
|
||||||
cycle: 'once',
|
|
||||||
maxPerCycle: 1,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
count: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('updateContributionLink', () => {
|
|
||||||
describe('no valid id', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: updateContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
id: -1,
|
|
||||||
amount: new Decimal(400),
|
|
||||||
name: 'Dokumenta 2023',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('Contribution Link not found to given id.')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('valid id', () => {
|
|
||||||
let linkId: number
|
|
||||||
beforeAll(async () => {
|
|
||||||
const links = await query({ query: listContributionLinks })
|
|
||||||
linkId = links.data.listContributionLinks.links[0].id
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns updated contribution link object', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: updateContributionLink,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
id: linkId,
|
|
||||||
amount: new Decimal(400),
|
|
||||||
name: 'Dokumenta 2023',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
updateContributionLink: {
|
|
||||||
id: linkId,
|
|
||||||
amount: '400',
|
|
||||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
|
||||||
link: expect.stringMatching(/^.*?\/CL-[0-9a-f]{24,24}$/),
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
name: 'Dokumenta 2023',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
|
||||||
validFrom: expect.any(String),
|
|
||||||
validTo: expect.any(String),
|
|
||||||
maxAmountPerMonth: '200',
|
|
||||||
cycle: 'once',
|
|
||||||
maxPerCycle: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('updated the DB record', async () => {
|
|
||||||
await expect(DbContributionLink.findOne(linkId)).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
id: linkId,
|
|
||||||
name: 'Dokumenta 2023',
|
|
||||||
memo: 'Danke für deine Teilnahme an der Dokumenta 2023',
|
|
||||||
amount: expect.decimalEqual(400),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('deleteContributionLink', () => {
|
|
||||||
describe('no valid id', () => {
|
|
||||||
it('returns an error', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({ mutation: deleteContributionLink, variables: { id: -1 } }),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('Contribution Link not found to given id.')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('valid id', () => {
|
|
||||||
let linkId: number
|
|
||||||
beforeAll(async () => {
|
|
||||||
const links = await query({ query: listContributionLinks })
|
|
||||||
linkId = links.data.listContributionLinks.links[0].id
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns a date string', async () => {
|
|
||||||
await expect(
|
|
||||||
mutate({ mutation: deleteContributionLink, variables: { id: linkId } }),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
deleteContributionLink: expect.any(String),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not list this contribution link anymore', async () => {
|
|
||||||
await expect(query({ query: listContributionLinks })).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
listContributionLinks: {
|
|
||||||
links: [],
|
|
||||||
count: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('transactionLinkCode', () => {
|
describe('transactionLinkCode', () => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { randomBytes } from 'crypto'
|
import { randomBytes } from 'crypto'
|
||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
|
|
||||||
import { getConnection, MoreThan, FindOperator, IsNull } from '@dbTools/typeorm'
|
import { getConnection, MoreThan, FindOperator } from '@dbTools/typeorm'
|
||||||
|
|
||||||
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
import { TransactionLink as DbTransactionLink } from '@entity/TransactionLink'
|
||||||
import { User as DbUser } from '@entity/User'
|
import { User as DbUser } from '@entity/User'
|
||||||
@ -13,7 +13,6 @@ import { User } from '@model/User'
|
|||||||
import { ContributionLink } from '@model/ContributionLink'
|
import { ContributionLink } from '@model/ContributionLink'
|
||||||
import { Decay } from '@model/Decay'
|
import { Decay } from '@model/Decay'
|
||||||
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
import { TransactionLink, TransactionLinkResult } from '@model/TransactionLink'
|
||||||
import { ContributionLinkList } from '@model/ContributionLinkList'
|
|
||||||
import { Order } from '@enum/Order'
|
import { Order } from '@enum/Order'
|
||||||
import { ContributionType } from '@enum/ContributionType'
|
import { ContributionType } from '@enum/ContributionType'
|
||||||
import { ContributionStatus } from '@enum/ContributionStatus'
|
import { ContributionStatus } from '@enum/ContributionStatus'
|
||||||
@ -22,38 +21,16 @@ import { ContributionCycleType } from '@enum/ContributionCycleType'
|
|||||||
import TransactionLinkArgs from '@arg/TransactionLinkArgs'
|
import TransactionLinkArgs from '@arg/TransactionLinkArgs'
|
||||||
import Paginated from '@arg/Paginated'
|
import Paginated from '@arg/Paginated'
|
||||||
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
|
import TransactionLinkFilters from '@arg/TransactionLinkFilters'
|
||||||
import ContributionLinkArgs from '@arg/ContributionLinkArgs'
|
|
||||||
|
|
||||||
import { backendLogger as logger } from '@/server/logger'
|
import { backendLogger as logger } from '@/server/logger'
|
||||||
import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
|
import { Context, getUser, getClientTimezoneOffset } from '@/server/context'
|
||||||
import {
|
import { Resolver, Args, Arg, Authorized, Ctx, Mutation, Query, Int } from 'type-graphql'
|
||||||
Resolver,
|
|
||||||
Args,
|
|
||||||
Arg,
|
|
||||||
Authorized,
|
|
||||||
Ctx,
|
|
||||||
Mutation,
|
|
||||||
Query,
|
|
||||||
Int,
|
|
||||||
createUnionType,
|
|
||||||
} from 'type-graphql'
|
|
||||||
import { calculateBalance } from '@/util/validate'
|
import { calculateBalance } from '@/util/validate'
|
||||||
import { RIGHTS } from '@/auth/RIGHTS'
|
import { RIGHTS } from '@/auth/RIGHTS'
|
||||||
import { calculateDecay } from '@/util/decay'
|
import { calculateDecay } from '@/util/decay'
|
||||||
import { getUserCreation, validateContribution, isStartEndDateValid } from './util/creations'
|
import { getUserCreation, validateContribution } from './util/creations'
|
||||||
import {
|
|
||||||
CONTRIBUTIONLINK_NAME_MAX_CHARS,
|
|
||||||
CONTRIBUTIONLINK_NAME_MIN_CHARS,
|
|
||||||
MEMO_MAX_CHARS,
|
|
||||||
MEMO_MIN_CHARS,
|
|
||||||
} from './const/const'
|
|
||||||
import { executeTransaction } from './TransactionResolver'
|
import { executeTransaction } from './TransactionResolver'
|
||||||
import { transactionLinkCode as contributionLinkCode } from './TransactionLinkResolver'
|
import QueryLinkResult from '@union/QueryLinkResult'
|
||||||
|
|
||||||
const QueryLinkResult = createUnionType({
|
|
||||||
name: 'QueryLinkResult', // the name of the GraphQL union
|
|
||||||
types: () => [TransactionLink, ContributionLink] as const, // function that returns tuple of object types classes
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: do not export, test it inside the resolver
|
// TODO: do not export, test it inside the resolver
|
||||||
export const transactionLinkCode = (date: Date): string => {
|
export const transactionLinkCode = (date: Date): string => {
|
||||||
@ -401,131 +378,4 @@ export class TransactionLinkResolver {
|
|||||||
linkList: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))),
|
linkList: transactionLinks.map((tl) => new TransactionLink(tl, new User(user))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authorized([RIGHTS.CREATE_CONTRIBUTION_LINK])
|
|
||||||
@Mutation(() => ContributionLink)
|
|
||||||
async createContributionLink(
|
|
||||||
@Args()
|
|
||||||
{
|
|
||||||
amount,
|
|
||||||
name,
|
|
||||||
memo,
|
|
||||||
cycle,
|
|
||||||
validFrom,
|
|
||||||
validTo,
|
|
||||||
maxAmountPerMonth,
|
|
||||||
maxPerCycle,
|
|
||||||
}: ContributionLinkArgs,
|
|
||||||
): Promise<ContributionLink> {
|
|
||||||
isStartEndDateValid(validFrom, validTo)
|
|
||||||
if (!name) {
|
|
||||||
logger.error(`The name must be initialized!`)
|
|
||||||
throw new Error(`The name must be initialized!`)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
name.length < CONTRIBUTIONLINK_NAME_MIN_CHARS ||
|
|
||||||
name.length > CONTRIBUTIONLINK_NAME_MAX_CHARS
|
|
||||||
) {
|
|
||||||
const msg = `The value of 'name' with a length of ${name.length} did not fulfill the requested bounderies min=${CONTRIBUTIONLINK_NAME_MIN_CHARS} and max=${CONTRIBUTIONLINK_NAME_MAX_CHARS}`
|
|
||||||
logger.error(`${msg}`)
|
|
||||||
throw new Error(`${msg}`)
|
|
||||||
}
|
|
||||||
if (!memo) {
|
|
||||||
logger.error(`The memo must be initialized!`)
|
|
||||||
throw new Error(`The memo must be initialized!`)
|
|
||||||
}
|
|
||||||
if (memo.length < MEMO_MIN_CHARS || memo.length > MEMO_MAX_CHARS) {
|
|
||||||
const msg = `The value of 'memo' with a length of ${memo.length} did not fulfill the requested bounderies min=${MEMO_MIN_CHARS} and max=${MEMO_MAX_CHARS}`
|
|
||||||
logger.error(`${msg}`)
|
|
||||||
throw new Error(`${msg}`)
|
|
||||||
}
|
|
||||||
if (!amount) {
|
|
||||||
logger.error(`The amount must be initialized!`)
|
|
||||||
throw new Error('The amount must be initialized!')
|
|
||||||
}
|
|
||||||
if (!new Decimal(amount).isPositive()) {
|
|
||||||
logger.error(`The amount=${amount} must be initialized with a positiv value!`)
|
|
||||||
throw new Error(`The amount=${amount} must be initialized with a positiv value!`)
|
|
||||||
}
|
|
||||||
const dbContributionLink = new DbContributionLink()
|
|
||||||
dbContributionLink.amount = amount
|
|
||||||
dbContributionLink.name = name
|
|
||||||
dbContributionLink.memo = memo
|
|
||||||
dbContributionLink.createdAt = new Date()
|
|
||||||
dbContributionLink.code = contributionLinkCode(dbContributionLink.createdAt)
|
|
||||||
dbContributionLink.cycle = cycle
|
|
||||||
if (validFrom) dbContributionLink.validFrom = new Date(validFrom)
|
|
||||||
if (validTo) dbContributionLink.validTo = new Date(validTo)
|
|
||||||
dbContributionLink.maxAmountPerMonth = maxAmountPerMonth
|
|
||||||
dbContributionLink.maxPerCycle = maxPerCycle
|
|
||||||
await dbContributionLink.save()
|
|
||||||
logger.debug(`createContributionLink successful!`)
|
|
||||||
return new ContributionLink(dbContributionLink)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.LIST_CONTRIBUTION_LINKS])
|
|
||||||
@Query(() => ContributionLinkList)
|
|
||||||
async listContributionLinks(
|
|
||||||
@Args()
|
|
||||||
{ currentPage = 1, pageSize = 5, order = Order.DESC }: Paginated,
|
|
||||||
): Promise<ContributionLinkList> {
|
|
||||||
const [links, count] = await DbContributionLink.findAndCount({
|
|
||||||
where: [{ validTo: MoreThan(new Date()) }, { validTo: IsNull() }],
|
|
||||||
order: { createdAt: order },
|
|
||||||
skip: (currentPage - 1) * pageSize,
|
|
||||||
take: pageSize,
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
links: links.map((link: DbContributionLink) => new ContributionLink(link)),
|
|
||||||
count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.DELETE_CONTRIBUTION_LINK])
|
|
||||||
@Mutation(() => Date, { nullable: true })
|
|
||||||
async deleteContributionLink(@Arg('id', () => Int) id: number): Promise<Date | null> {
|
|
||||||
const contributionLink = await DbContributionLink.findOne(id)
|
|
||||||
if (!contributionLink) {
|
|
||||||
logger.error(`Contribution Link not found to given id: ${id}`)
|
|
||||||
throw new Error('Contribution Link not found to given id.')
|
|
||||||
}
|
|
||||||
await contributionLink.softRemove()
|
|
||||||
logger.debug(`deleteContributionLink successful!`)
|
|
||||||
const newContributionLink = await DbContributionLink.findOne({ id }, { withDeleted: true })
|
|
||||||
return newContributionLink ? newContributionLink.deletedAt : null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Authorized([RIGHTS.UPDATE_CONTRIBUTION_LINK])
|
|
||||||
@Mutation(() => ContributionLink)
|
|
||||||
async updateContributionLink(
|
|
||||||
@Args()
|
|
||||||
{
|
|
||||||
amount,
|
|
||||||
name,
|
|
||||||
memo,
|
|
||||||
cycle,
|
|
||||||
validFrom,
|
|
||||||
validTo,
|
|
||||||
maxAmountPerMonth,
|
|
||||||
maxPerCycle,
|
|
||||||
}: ContributionLinkArgs,
|
|
||||||
@Arg('id', () => Int) id: number,
|
|
||||||
): Promise<ContributionLink> {
|
|
||||||
const dbContributionLink = await DbContributionLink.findOne(id)
|
|
||||||
if (!dbContributionLink) {
|
|
||||||
logger.error(`Contribution Link not found to given id: ${id}`)
|
|
||||||
throw new Error('Contribution Link not found to given id.')
|
|
||||||
}
|
|
||||||
dbContributionLink.amount = amount
|
|
||||||
dbContributionLink.name = name
|
|
||||||
dbContributionLink.memo = memo
|
|
||||||
dbContributionLink.cycle = cycle
|
|
||||||
if (validFrom) dbContributionLink.validFrom = new Date(validFrom)
|
|
||||||
if (validTo) dbContributionLink.validTo = new Date(validTo)
|
|
||||||
dbContributionLink.maxAmountPerMonth = maxAmountPerMonth
|
|
||||||
dbContributionLink.maxPerCycle = maxPerCycle
|
|
||||||
await dbContributionLink.save()
|
|
||||||
logger.debug(`updateContributionLink successful!`)
|
|
||||||
return new ContributionLink(dbContributionLink)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
7
backend/src/graphql/union/QueryLinkResult.ts
Normal file
7
backend/src/graphql/union/QueryLinkResult.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createUnionType } from 'type-graphql'
|
||||||
|
import { TransactionLink } from '@model/TransactionLink'
|
||||||
|
import { ContributionLink } from '@model/ContributionLink'
|
||||||
|
export default createUnionType({
|
||||||
|
name: 'QueryLinkResult', // the name of the GraphQL union
|
||||||
|
types: () => [TransactionLink, ContributionLink] as const, // function that returns tuple of object types classes
|
||||||
|
})
|
||||||
@ -51,6 +51,7 @@
|
|||||||
"@arg/*": ["src/graphql/arg/*"],
|
"@arg/*": ["src/graphql/arg/*"],
|
||||||
"@enum/*": ["src/graphql/enum/*"],
|
"@enum/*": ["src/graphql/enum/*"],
|
||||||
"@model/*": ["src/graphql/model/*"],
|
"@model/*": ["src/graphql/model/*"],
|
||||||
|
"@union/*": ["src/graphql/union/*"],
|
||||||
"@repository/*": ["src/typeorm/repository/*"],
|
"@repository/*": ["src/typeorm/repository/*"],
|
||||||
"@test/*": ["test/*"],
|
"@test/*": ["test/*"],
|
||||||
/* external */
|
/* external */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user