mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into change-orange-color
This commit is contained in:
commit
c57e2ca2a5
16
CHANGELOG.md
16
CHANGELOG.md
@ -4,8 +4,24 @@ All notable changes to this project will be documented in this file. Dates are d
|
||||
|
||||
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||
|
||||
#### [1.13.2](https://github.com/gradido/gradido/compare/1.13.1...1.13.2)
|
||||
|
||||
- fix: 🍰 Links In Contribution Messages Target Blank [`#2306`](https://github.com/gradido/gradido/pull/2306)
|
||||
- fix: Link in Contribution Messages [`#2305`](https://github.com/gradido/gradido/pull/2305)
|
||||
- Refactor: 🍰 Change the query so that we only look on the ``contributions`` table. [`#2217`](https://github.com/gradido/gradido/pull/2217)
|
||||
- Refactor: Admin Resolver Events and Logging [`#2244`](https://github.com/gradido/gradido/pull/2244)
|
||||
- contibution messages, links are recognised [`#2248`](https://github.com/gradido/gradido/pull/2248)
|
||||
- fix: Include Deleted Email Contacts in User Search [`#2281`](https://github.com/gradido/gradido/pull/2281)
|
||||
- fix: Pagination Contributions jumps to wrong Page [`#2284`](https://github.com/gradido/gradido/pull/2284)
|
||||
- fix: Changed some texts in E-Mails and Frontend [`#2276`](https://github.com/gradido/gradido/pull/2276)
|
||||
- Feat: 🍰 Add `deletedBy` To Contributions And Admin Can Not Delete Own User Contribution [`#2236`](https://github.com/gradido/gradido/pull/2236)
|
||||
- deleted contributions are displayed to the user [`#2277`](https://github.com/gradido/gradido/pull/2277)
|
||||
|
||||
#### [1.13.1](https://github.com/gradido/gradido/compare/1.13.0...1.13.1)
|
||||
|
||||
> 20 October 2022
|
||||
|
||||
- release: Version 1.13.1 [`#2279`](https://github.com/gradido/gradido/pull/2279)
|
||||
- Fix: correctly evaluate to EMAIL_TEST_MODE to false [`#2273`](https://github.com/gradido/gradido/pull/2273)
|
||||
- Refactor: Contribution resolver logs and events [`#2231`](https://github.com/gradido/gradido/pull/2231)
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
"description": "Administraion Interface for Gradido",
|
||||
"main": "index.js",
|
||||
"author": "Moriz Wahl",
|
||||
"version": "1.13.1",
|
||||
"version": "1.13.2",
|
||||
"license": "Apache-2.0",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="mt-2">
|
||||
<span v-for="({ type, text }, index) in linkifiedMessage" :key="index">
|
||||
<b-link v-if="type === 'link'" :to="text">{{ text }}</b-link>
|
||||
<b-link v-if="type === 'link'" :href="text" target="_blank">{{ text }}</b-link>
|
||||
<span v-else>{{ text }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-backend",
|
||||
"version": "1.13.1",
|
||||
"version": "1.13.2",
|
||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/backend",
|
||||
|
||||
@ -66,6 +66,9 @@ export class EventTransactionCreation extends EventBasicTx {}
|
||||
export class EventTransactionReceive extends EventBasicTxX {}
|
||||
export class EventTransactionReceiveRedeem extends EventBasicTxX {}
|
||||
export class EventContributionCreate extends EventBasicCt {}
|
||||
export class EventAdminContributionCreate extends EventBasicCt {}
|
||||
export class EventAdminContributionDelete extends EventBasicCt {}
|
||||
export class EventAdminContributionUpdate extends EventBasicCt {}
|
||||
export class EventUserCreateContributionMessage extends EventBasicCtMsg {}
|
||||
export class EventAdminCreateContributionMessage extends EventBasicCtMsg {}
|
||||
export class EventContributionDelete extends EventBasicCt {}
|
||||
@ -74,6 +77,14 @@ export class EventContributionConfirm extends EventBasicCtX {}
|
||||
export class EventContributionDeny extends EventBasicCtX {}
|
||||
export class EventContributionLinkDefine extends EventBasicCt {}
|
||||
export class EventContributionLinkActivateRedeem extends EventBasicCt {}
|
||||
export class EventDeleteUser extends EventBasicUserId {}
|
||||
export class EventUndeleteUser extends EventBasicUserId {}
|
||||
export class EventChangeUserRole extends EventBasicUserId {}
|
||||
export class EventAdminUpdateContribution extends EventBasicCt {}
|
||||
export class EventAdminDeleteContribution extends EventBasicCt {}
|
||||
export class EventCreateContributionLink extends EventBasicCt {}
|
||||
export class EventDeleteContributionLink extends EventBasicCt {}
|
||||
export class EventUpdateContributionLink extends EventBasicCt {}
|
||||
|
||||
export class Event {
|
||||
constructor()
|
||||
@ -289,6 +300,27 @@ export class Event {
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventAdminContributionCreate(ev: EventAdminContributionCreate): Event {
|
||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
||||
this.type = EventProtocolType.ADMIN_CONTRIBUTION_CREATE
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventAdminContributionDelete(ev: EventAdminContributionDelete): Event {
|
||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
||||
this.type = EventProtocolType.ADMIN_CONTRIBUTION_DELETE
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventAdminContributionUpdate(ev: EventAdminContributionUpdate): Event {
|
||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
||||
this.type = EventProtocolType.ADMIN_CONTRIBUTION_UPDATE
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventUserCreateContributionMessage(ev: EventUserCreateContributionMessage): Event {
|
||||
this.setByBasicCtMsg(ev.userId, ev.contributionId, ev.amount, ev.messageId)
|
||||
this.type = EventProtocolType.USER_CREATE_CONTRIBUTION_MESSAGE
|
||||
@ -345,6 +377,62 @@ export class Event {
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventDeleteUser(ev: EventDeleteUser): Event {
|
||||
this.setByBasicUser(ev.userId)
|
||||
this.type = EventProtocolType.DELETE_USER
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventUndeleteUser(ev: EventUndeleteUser): Event {
|
||||
this.setByBasicUser(ev.userId)
|
||||
this.type = EventProtocolType.UNDELETE_USER
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventChangeUserRole(ev: EventChangeUserRole): Event {
|
||||
this.setByBasicUser(ev.userId)
|
||||
this.type = EventProtocolType.CHANGE_USER_ROLE
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventAdminUpdateContribution(ev: EventAdminUpdateContribution): Event {
|
||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
||||
this.type = EventProtocolType.ADMIN_UPDATE_CONTRIBUTION
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventAdminDeleteContribution(ev: EventAdminDeleteContribution): Event {
|
||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
||||
this.type = EventProtocolType.ADMIN_DELETE_CONTRIBUTION
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventCreateContributionLink(ev: EventCreateContributionLink): Event {
|
||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
||||
this.type = EventProtocolType.CREATE_CONTRIBUTION_LINK
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventDeleteContributionLink(ev: EventDeleteContributionLink): Event {
|
||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
||||
this.type = EventProtocolType.DELETE_CONTRIBUTION_LINK
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
public setEventUpdateContributionLink(ev: EventUpdateContributionLink): Event {
|
||||
this.setByBasicCt(ev.userId, ev.contributionId, ev.amount)
|
||||
this.type = EventProtocolType.UPDATE_CONTRIBUTION_LINK
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setByBasicUser(userId: number): Event {
|
||||
this.setEventBasic()
|
||||
this.userId = userId
|
||||
|
||||
@ -33,6 +33,17 @@ export enum EventProtocolType {
|
||||
CONTRIBUTION_LINK_ACTIVATE_REDEEM = 'CONTRIBUTION_LINK_ACTIVATE_REDEEM',
|
||||
CONTRIBUTION_DELETE = 'CONTRIBUTION_DELETE',
|
||||
CONTRIBUTION_UPDATE = 'CONTRIBUTION_UPDATE',
|
||||
ADMIN_CONTRIBUTION_CREATE = 'ADMIN_CONTRIBUTION_CREATE',
|
||||
ADMIN_CONTRIBUTION_DELETE = 'ADMIN_CONTRIBUTION_DELETE',
|
||||
ADMIN_CONTRIBUTION_UPDATE = 'ADMIN_CONTRIBUTION_UPDATE',
|
||||
USER_CREATE_CONTRIBUTION_MESSAGE = 'USER_CREATE_CONTRIBUTION_MESSAGE',
|
||||
ADMIN_CREATE_CONTRIBUTION_MESSAGE = 'ADMIN_CREATE_CONTRIBUTION_MESSAGE',
|
||||
DELETE_USER = 'DELETE_USER',
|
||||
UNDELETE_USER = 'UNDELETE_USER',
|
||||
CHANGE_USER_ROLE = 'CHANGE_USER_ROLE',
|
||||
ADMIN_UPDATE_CONTRIBUTION = 'ADMIN_UPDATE_CONTRIBUTION',
|
||||
ADMIN_DELETE_CONTRIBUTION = 'ADMIN_DELETE_CONTRIBUTION',
|
||||
CREATE_CONTRIBUTION_LINK = 'CREATE_CONTRIBUTION_LINK',
|
||||
DELETE_CONTRIBUTION_LINK = 'DELETE_CONTRIBUTION_LINK',
|
||||
UPDATE_CONTRIBUTION_LINK = 'UPDATE_CONTRIBUTION_LINK',
|
||||
}
|
||||
|
||||
@ -42,6 +42,9 @@ import { Contribution } from '@entity/Contribution'
|
||||
import { Transaction as DbTransaction } from '@entity/Transaction'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
|
||||
import { EventProtocol } from '@entity/EventProtocol'
|
||||
import { EventProtocolType } from '@/event/EventProtocolType'
|
||||
import { logger } from '@test/testSetup'
|
||||
|
||||
// mock account activation email to avoid console spam
|
||||
jest.mock('@/mailer/sendAccountActivationEmail', () => {
|
||||
@ -144,6 +147,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('change role with success', () => {
|
||||
@ -196,6 +203,9 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Administrator can not change his own role!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('user has already role to be set', () => {
|
||||
@ -213,6 +223,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User is already admin!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('to usual user', () => {
|
||||
@ -229,6 +243,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User is already a usual user!')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -297,6 +315,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete self', () => {
|
||||
@ -309,6 +331,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Moderator can not delete his own account!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('delete with success', () => {
|
||||
@ -338,6 +364,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${user.id}`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -405,6 +435,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(`Could not find user with userId: ${admin.id + 1}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user to undelete is not deleted', () => {
|
||||
@ -422,6 +456,10 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User is not deleted')
|
||||
})
|
||||
|
||||
describe('undelete deleted user', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({ mutation: deleteUser, variables: { userId: user.id } })
|
||||
@ -909,6 +947,12 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Could not find user with email: bibi@bloxberg.de',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user to create for is deleted', () => {
|
||||
@ -928,6 +972,12 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'This user was deleted. Cannot create a contribution.',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user to create for has email not confirmed', () => {
|
||||
@ -947,6 +997,12 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Contribution could not be saved, Email is not activated',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid user to create for', () => {
|
||||
@ -967,6 +1023,13 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No information for available creations with the given creationDate=',
|
||||
'Invalid Date',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('date of creation is four months ago', () => {
|
||||
@ -987,6 +1050,13 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No information for available creations with the given creationDate=',
|
||||
variables.creationDate,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('date of creation is in the future', () => {
|
||||
@ -1007,6 +1077,13 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No information for available creations with the given creationDate=',
|
||||
variables.creationDate,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('amount of creation is too high', () => {
|
||||
@ -1024,6 +1101,12 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The amount (2000 GDD) to be created exceeds the amount (1000 GDD) still available for this month.',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('creation is valid', () => {
|
||||
@ -1039,6 +1122,15 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the admin create contribution event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_CREATE,
|
||||
userId: admin.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('second creation surpasses the available amount ', () => {
|
||||
@ -1056,6 +1148,12 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The amount (1000 GDD) to be created exceeds the amount (800 GDD) still available for this month.',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -1134,6 +1232,12 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Could not find UserContact with email: bob@baumeister.de',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user for creation to update is deleted', () => {
|
||||
@ -1155,6 +1259,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User was deleted (stephen@hawking.uk)')
|
||||
})
|
||||
})
|
||||
|
||||
describe('creation does not exist', () => {
|
||||
@ -1176,6 +1284,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('No contribution found to given id.')
|
||||
})
|
||||
})
|
||||
|
||||
describe('user email does not match creation user', () => {
|
||||
@ -1188,7 +1300,9 @@ describe('AdminResolver', () => {
|
||||
email: 'bibi@bloxberg.de',
|
||||
amount: new Decimal(300),
|
||||
memo: 'Danke Bibi!',
|
||||
creationDate: new Date().toString(),
|
||||
creationDate: creation
|
||||
? creation.contributionDate.toString()
|
||||
: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
@ -1201,11 +1315,17 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'user of the pending contribution and send user does not correspond',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('creation update is not valid', () => {
|
||||
// as this test has not clearly defined that date, it is a false positive
|
||||
it.skip('throws an error', async () => {
|
||||
it('throws an error', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminUpdateContribution,
|
||||
@ -1214,24 +1334,32 @@ describe('AdminResolver', () => {
|
||||
email: 'peter@lustig.de',
|
||||
amount: new Decimal(1900),
|
||||
memo: 'Danke Peter!',
|
||||
creationDate: new Date().toString(),
|
||||
creationDate: creation
|
||||
? creation.contributionDate.toString()
|
||||
: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'The amount (1900 GDD) to be created exceeds the amount (500 GDD) still available for this month.',
|
||||
'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.',
|
||||
),
|
||||
],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('creation update is successful changing month', () => {
|
||||
describe.skip('creation update is successful changing month', () => {
|
||||
// skipped as changing the month is currently disable
|
||||
it.skip('returns update creation object', async () => {
|
||||
it('returns update creation object', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminUpdateContribution,
|
||||
@ -1240,7 +1368,9 @@ describe('AdminResolver', () => {
|
||||
email: 'peter@lustig.de',
|
||||
amount: new Decimal(300),
|
||||
memo: 'Danke Peter!',
|
||||
creationDate: new Date().toString(),
|
||||
creationDate: creation
|
||||
? creation.contributionDate.toString()
|
||||
: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
@ -1250,17 +1380,26 @@ describe('AdminResolver', () => {
|
||||
date: expect.any(String),
|
||||
memo: 'Danke Peter!',
|
||||
amount: '300',
|
||||
creation: ['1000', '1000', '200'],
|
||||
creation: ['1000', '700', '500'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the admin update contribution event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
|
||||
userId: admin.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('creation update is successful without changing month', () => {
|
||||
// actually this mutation IS changing the month
|
||||
it.skip('returns update creation object', async () => {
|
||||
it('returns update creation object', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: adminUpdateContribution,
|
||||
@ -1269,7 +1408,9 @@ describe('AdminResolver', () => {
|
||||
email: 'peter@lustig.de',
|
||||
amount: new Decimal(200),
|
||||
memo: 'Das war leider zu Viel!',
|
||||
creationDate: new Date().toString(),
|
||||
creationDate: creation
|
||||
? creation.contributionDate.toString()
|
||||
: new Date().toString(),
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
@ -1279,12 +1420,21 @@ describe('AdminResolver', () => {
|
||||
date: expect.any(String),
|
||||
memo: 'Das war leider zu Viel!',
|
||||
amount: '200',
|
||||
creation: ['1000', '1000', '300'],
|
||||
creation: ['1000', '800', '500'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the admin update contribution event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_UPDATE,
|
||||
userId: admin.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1304,10 +1454,10 @@ describe('AdminResolver', () => {
|
||||
lastName: 'Lustig',
|
||||
email: 'peter@lustig.de',
|
||||
date: expect.any(String),
|
||||
memo: 'Herzlich Willkommen bei Gradido!',
|
||||
amount: '400',
|
||||
memo: 'Das war leider zu Viel!',
|
||||
amount: '200',
|
||||
moderator: admin.id,
|
||||
creation: ['1000', '600', '500'],
|
||||
creation: ['1000', '800', '500'],
|
||||
},
|
||||
{
|
||||
id: expect.any(Number),
|
||||
@ -1318,7 +1468,7 @@ describe('AdminResolver', () => {
|
||||
memo: 'Grundeinkommen',
|
||||
amount: '500',
|
||||
moderator: admin.id,
|
||||
creation: ['1000', '600', '500'],
|
||||
creation: ['1000', '800', '500'],
|
||||
},
|
||||
{
|
||||
id: expect.any(Number),
|
||||
@ -1365,6 +1515,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution not found for given id: -1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('admin deletes own user contribution', () => {
|
||||
@ -1414,6 +1568,15 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the admin delete contribution event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.ADMIN_CONTRIBUTION_DELETE,
|
||||
userId: admin.id,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1433,6 +1596,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution not found for given id: -1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm own creation', () => {
|
||||
@ -1460,6 +1627,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Moderator can not confirm own contribution')
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm creation for other user', () => {
|
||||
@ -1488,6 +1659,14 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the contribution confirm event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.CONTRIBUTION_CONFIRM,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('creates a transaction', async () => {
|
||||
const transaction = await DbTransaction.find()
|
||||
expect(transaction[0].amount.toString()).toBe('450')
|
||||
@ -1512,6 +1691,14 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('stores the send confirmation email event in the database', async () => {
|
||||
await expect(EventProtocol.find()).resolves.toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: EventProtocolType.SEND_CONFIRMATION_EMAIL,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('confirm two creations one after the other quickly', () => {
|
||||
@ -2052,6 +2239,12 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2068,6 +2261,12 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2087,6 +2286,12 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2103,6 +2308,10 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2123,6 +2332,12 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2143,6 +2358,12 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2159,6 +2380,10 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2179,6 +2404,12 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2199,6 +2430,12 @@ describe('AdminResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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({
|
||||
@ -2216,6 +2453,12 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The amount=0 must be initialized with a positiv value!',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('listContributionLinks', () => {
|
||||
@ -2271,6 +2514,10 @@ describe('AdminResolver', () => {
|
||||
})
|
||||
})
|
||||
|
||||
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 () => {
|
||||
@ -2336,6 +2583,10 @@ describe('AdminResolver', () => {
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution Link not found to given id: -1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('valid id', () => {
|
||||
|
||||
@ -64,6 +64,15 @@ import { ContributionMessageType } from '@enum/MessageType'
|
||||
import { ContributionMessage } from '@model/ContributionMessage'
|
||||
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
|
||||
import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail'
|
||||
import { eventProtocol } from '@/event/EventProtocolEmitter'
|
||||
import {
|
||||
Event,
|
||||
EventAdminContributionCreate,
|
||||
EventAdminContributionDelete,
|
||||
EventAdminContributionUpdate,
|
||||
EventContributionConfirm,
|
||||
EventSendConfirmationEmail,
|
||||
} from '@/event/Event'
|
||||
import { ContributionListResult } from '../model/Contribution'
|
||||
|
||||
// const EMAIL_OPT_IN_REGISTER = 1
|
||||
@ -145,11 +154,13 @@ export class AdminResolver {
|
||||
const user = await dbUser.findOne({ id: userId })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
}
|
||||
// administrator user changes own role?
|
||||
const moderatorUser = getUser(context)
|
||||
if (moderatorUser.id === userId) {
|
||||
logger.error('Administrator can not change his own role!')
|
||||
throw new Error('Administrator can not change his own role!')
|
||||
}
|
||||
// change isAdmin
|
||||
@ -158,6 +169,7 @@ export class AdminResolver {
|
||||
if (isAdmin === true) {
|
||||
user.isAdmin = new Date()
|
||||
} else {
|
||||
logger.error('User is already a usual user!')
|
||||
throw new Error('User is already a usual user!')
|
||||
}
|
||||
break
|
||||
@ -165,6 +177,7 @@ export class AdminResolver {
|
||||
if (isAdmin === false) {
|
||||
user.isAdmin = null
|
||||
} else {
|
||||
logger.error('User is already admin!')
|
||||
throw new Error('User is already admin!')
|
||||
}
|
||||
break
|
||||
@ -183,11 +196,13 @@ export class AdminResolver {
|
||||
const user = await dbUser.findOne({ id: userId })
|
||||
// user exists ?
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
}
|
||||
// moderator user disabled own account?
|
||||
const moderatorUser = getUser(context)
|
||||
if (moderatorUser.id === userId) {
|
||||
logger.error('Moderator can not delete his own account!')
|
||||
throw new Error('Moderator can not delete his own account!')
|
||||
}
|
||||
// soft-delete user
|
||||
@ -201,9 +216,11 @@ export class AdminResolver {
|
||||
async unDeleteUser(@Arg('userId', () => Int) userId: number): Promise<Date | null> {
|
||||
const user = await dbUser.findOne({ id: userId }, { withDeleted: true })
|
||||
if (!user) {
|
||||
logger.error(`Could not find user with userId: ${userId}`)
|
||||
throw new Error(`Could not find user with userId: ${userId}`)
|
||||
}
|
||||
if (!user.deletedAt) {
|
||||
logger.error('User is not deleted')
|
||||
throw new Error('User is not deleted')
|
||||
}
|
||||
await user.recover()
|
||||
@ -240,6 +257,8 @@ export class AdminResolver {
|
||||
logger.error('Contribution could not be saved, Email is not activated')
|
||||
throw new Error('Contribution could not be saved, Email is not activated')
|
||||
}
|
||||
|
||||
const event = new Event()
|
||||
const moderator = getUser(context)
|
||||
logger.trace('moderator: ', moderator.id)
|
||||
const creations = await getUserCreation(emailContact.userId)
|
||||
@ -258,7 +277,17 @@ export class AdminResolver {
|
||||
contribution.contributionStatus = ContributionStatus.PENDING
|
||||
|
||||
logger.trace('contribution to save', contribution)
|
||||
|
||||
await DbContribution.save(contribution)
|
||||
|
||||
const eventAdminCreateContribution = new EventAdminContributionCreate()
|
||||
eventAdminCreateContribution.userId = moderator.id
|
||||
eventAdminCreateContribution.amount = amount
|
||||
eventAdminCreateContribution.contributionId = contribution.id
|
||||
await eventProtocol.writeEvent(
|
||||
event.setEventAdminContributionCreate(eventAdminCreateContribution),
|
||||
)
|
||||
|
||||
return getUserCreation(emailContact.userId)
|
||||
}
|
||||
|
||||
@ -319,7 +348,6 @@ export class AdminResolver {
|
||||
const contributionToUpdate = await DbContribution.findOne({
|
||||
where: { id, confirmedAt: IsNull() },
|
||||
})
|
||||
|
||||
if (!contributionToUpdate) {
|
||||
logger.error('No contribution found to given id.')
|
||||
throw new Error('No contribution found to given id.')
|
||||
@ -337,6 +365,7 @@ export class AdminResolver {
|
||||
|
||||
const creationDateObj = new Date(creationDate)
|
||||
let creations = await getUserCreation(user.id)
|
||||
|
||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||
creations = updateCreations(creations, contributionToUpdate)
|
||||
} else {
|
||||
@ -353,6 +382,7 @@ export class AdminResolver {
|
||||
contributionToUpdate.contributionStatus = ContributionStatus.PENDING
|
||||
|
||||
await DbContribution.save(contributionToUpdate)
|
||||
|
||||
const result = new AdminUpdateContribution()
|
||||
result.amount = amount
|
||||
result.memo = contributionToUpdate.memo
|
||||
@ -360,6 +390,15 @@ export class AdminResolver {
|
||||
|
||||
result.creation = await getUserCreation(user.id)
|
||||
|
||||
const event = new Event()
|
||||
const eventAdminContributionUpdate = new EventAdminContributionUpdate()
|
||||
eventAdminContributionUpdate.userId = user.id
|
||||
eventAdminContributionUpdate.amount = amount
|
||||
eventAdminContributionUpdate.contributionId = contributionToUpdate.id
|
||||
await eventProtocol.writeEvent(
|
||||
event.setEventAdminContributionUpdate(eventAdminContributionUpdate),
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -420,6 +459,16 @@ export class AdminResolver {
|
||||
contribution.deletedBy = moderator.id
|
||||
await contribution.save()
|
||||
const res = await contribution.softRemove()
|
||||
|
||||
const event = new Event()
|
||||
const eventAdminContributionDelete = new EventAdminContributionDelete()
|
||||
eventAdminContributionDelete.userId = contribution.userId
|
||||
eventAdminContributionDelete.amount = contribution.amount
|
||||
eventAdminContributionDelete.contributionId = contribution.id
|
||||
await eventProtocol.writeEvent(
|
||||
event.setEventAdminContributionDelete(eventAdminContributionDelete),
|
||||
)
|
||||
|
||||
return !!res
|
||||
}
|
||||
|
||||
@ -515,6 +564,13 @@ export class AdminResolver {
|
||||
} finally {
|
||||
await queryRunner.release()
|
||||
}
|
||||
|
||||
const event = new Event()
|
||||
const eventContributionConfirm = new EventContributionConfirm()
|
||||
eventContributionConfirm.userId = user.id
|
||||
eventContributionConfirm.amount = contribution.amount
|
||||
eventContributionConfirm.contributionId = contribution.id
|
||||
await eventProtocol.writeEvent(event.setEventContributionConfirm(eventContributionConfirm))
|
||||
return true
|
||||
}
|
||||
|
||||
@ -576,6 +632,13 @@ export class AdminResolver {
|
||||
// In case EMails are disabled log the activation link for the user
|
||||
if (!emailSent) {
|
||||
logger.info(`Account confirmation link: ${activationLink}`)
|
||||
} else {
|
||||
const event = new Event()
|
||||
const eventSendConfirmationEmail = new EventSendConfirmationEmail()
|
||||
eventSendConfirmationEmail.userId = user.id
|
||||
await eventProtocol.writeEvent(
|
||||
event.setEventSendConfirmationEmail(eventSendConfirmationEmail),
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
@ -768,9 +831,11 @@ export class AdminResolver {
|
||||
relations: ['user'],
|
||||
})
|
||||
if (!contribution) {
|
||||
logger.error('Contribution not found')
|
||||
throw new Error('Contribution not found')
|
||||
}
|
||||
if (contribution.userId === user.id) {
|
||||
logger.error('Admin can not answer on own contribution')
|
||||
throw new Error('Admin can not answer on own contribution')
|
||||
}
|
||||
if (!contribution.user.emailContact) {
|
||||
|
||||
@ -6,8 +6,15 @@ import { bibiBloxberg } from '@/seeds/users/bibi-bloxberg'
|
||||
import { peterLustig } from '@/seeds/users/peter-lustig'
|
||||
import { cleanDB, testEnvironment } from '@test/helpers'
|
||||
import { userFactory } from '@/seeds/factory/user'
|
||||
import { login, createContributionLink, redeemTransactionLink } from '@/seeds/graphql/mutations'
|
||||
import {
|
||||
login,
|
||||
createContributionLink,
|
||||
redeemTransactionLink,
|
||||
createContribution,
|
||||
updateContribution,
|
||||
} from '@/seeds/graphql/mutations'
|
||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||
import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { GraphQLError } from 'graphql'
|
||||
|
||||
@ -32,6 +39,7 @@ describe('TransactionLinkResolver', () => {
|
||||
describe('redeem daily Contribution Link', () => {
|
||||
const now = new Date()
|
||||
let contributionLink: DbContributionLink | undefined
|
||||
let contribution: UnconfirmedContribution | undefined
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
@ -79,56 +87,59 @@ describe('TransactionLinkResolver', () => {
|
||||
)
|
||||
})
|
||||
|
||||
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('user has pending contribution of 1000 GDD', () => {
|
||||
beforeAll(async () => {
|
||||
jest.useFakeTimers()
|
||||
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
|
||||
setTimeout(() => {}, 1000 * 60 * 60 * 24)
|
||||
jest.runAllTimers()
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
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(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('allows the user to redeem the contribution link again', async () => {
|
||||
it('allows the user to redeem the contribution link', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
@ -160,6 +171,56 @@ describe('TransactionLinkResolver', () => {
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
describe('after one day', () => {
|
||||
beforeAll(async () => {
|
||||
jest.useFakeTimers()
|
||||
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
|
||||
setTimeout(() => {}, 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',
|
||||
),
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -258,7 +258,7 @@ export class TransactionLinkResolver {
|
||||
}
|
||||
}
|
||||
|
||||
const creations = await getUserCreation(user.id, false)
|
||||
const creations = await getUserCreation(user.id)
|
||||
logger.info('open creations', creations)
|
||||
validateContribution(creations, contributionLink.amount, now)
|
||||
const contribution = new DbContribution()
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { TransactionTypeId } from '@/graphql/enum/TransactionTypeId'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
@ -50,27 +49,27 @@ export const getUserCreations = async (
|
||||
const dateFilter = 'last_day(curdate() - interval 3 month) + interval 1 day'
|
||||
logger.trace('getUserCreations dateFilter=', dateFilter)
|
||||
|
||||
const unionString = includePending
|
||||
? `
|
||||
UNION
|
||||
SELECT contribution_date AS date, amount AS amount, user_id AS userId FROM contributions
|
||||
WHERE user_id IN (${ids.toString()})
|
||||
AND contribution_date >= ${dateFilter}
|
||||
AND confirmed_at IS NULL AND deleted_at IS NULL`
|
||||
: ''
|
||||
logger.trace('getUserCreations unionString=', unionString)
|
||||
const sumAmountContributionPerUserAndLast3MonthQuery = queryRunner.manager
|
||||
.createQueryBuilder(Contribution, 'c')
|
||||
.select('month(contribution_date)', 'month')
|
||||
.addSelect('user_id', 'userId')
|
||||
.addSelect('sum(amount)', 'sum')
|
||||
.where(`user_id in (${ids.toString()})`)
|
||||
.andWhere(`contribution_date >= ${dateFilter}`)
|
||||
.andWhere('deleted_at IS NULL')
|
||||
.andWhere('denied_at IS NULL')
|
||||
.groupBy('month')
|
||||
.addGroupBy('userId')
|
||||
.orderBy('month', 'DESC')
|
||||
|
||||
const unionQuery = await queryRunner.manager.query(`
|
||||
SELECT MONTH(date) AS month, sum(amount) AS sum, userId AS id FROM
|
||||
(SELECT creation_date AS date, amount AS amount, user_id AS userId FROM transactions
|
||||
WHERE user_id IN (${ids.toString()})
|
||||
AND type_id = ${TransactionTypeId.CREATION}
|
||||
AND creation_date >= ${dateFilter}
|
||||
${unionString}) AS result
|
||||
GROUP BY month, userId
|
||||
ORDER BY date DESC
|
||||
`)
|
||||
logger.trace('getUserCreations unionQuery=', unionQuery)
|
||||
if (!includePending) {
|
||||
sumAmountContributionPerUserAndLast3MonthQuery.andWhere('confirmed_at IS NOT NULL')
|
||||
}
|
||||
|
||||
const sumAmountContributionPerUserAndLast3Month =
|
||||
await sumAmountContributionPerUserAndLast3MonthQuery.getRawMany()
|
||||
|
||||
logger.trace(sumAmountContributionPerUserAndLast3Month)
|
||||
|
||||
await queryRunner.release()
|
||||
|
||||
@ -78,9 +77,9 @@ export const getUserCreations = async (
|
||||
return {
|
||||
id,
|
||||
creations: months.map((month) => {
|
||||
const creation = unionQuery.find(
|
||||
(raw: { month: string; id: string; creation: number[] }) =>
|
||||
parseInt(raw.month) === month && parseInt(raw.id) === id,
|
||||
const creation = sumAmountContributionPerUserAndLast3Month.find(
|
||||
(raw: { month: string; userId: string; creation: number[] }) =>
|
||||
parseInt(raw.month) === month && parseInt(raw.userId) === id,
|
||||
)
|
||||
return MAX_CREATION_AMOUNT.minus(creation ? creation.sum : 0)
|
||||
}),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido-database",
|
||||
"version": "1.13.1",
|
||||
"version": "1.13.2",
|
||||
"description": "Gradido Database Tool to execute database migrations",
|
||||
"main": "src/index.ts",
|
||||
"repository": "https://github.com/gradido/gradido/database",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bootstrap-vue-gradido-wallet",
|
||||
"version": "1.13.1",
|
||||
"version": "1.13.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node run/server.js",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="mt-2">
|
||||
<span v-for="({ type, text }, index) in linkifiedMessage" :key="index">
|
||||
<b-link v-if="type === 'link'" :to="text">{{ text }}</b-link>
|
||||
<b-link v-if="type === 'link'" :href="text" target="_blank">{{ text }}</b-link>
|
||||
<span v-else>{{ text }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gradido",
|
||||
"version": "1.13.1",
|
||||
"version": "1.13.2",
|
||||
"description": "Gradido",
|
||||
"main": "index.js",
|
||||
"repository": "git@github.com:gradido/gradido.git",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user