mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into event_refactor_database
This commit is contained in:
commit
53f684d865
158
.github/workflows/test.yml
vendored
158
.github/workflows/test.yml
vendored
@ -163,7 +163,6 @@ jobs:
|
||||
locales_frontend:
|
||||
name: Locales - Frontend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_frontend]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -171,20 +170,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Frontend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-frontend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/frontend.tar
|
||||
##########################################################################
|
||||
# LOCALES FRONTEND #######################################################
|
||||
##########################################################################
|
||||
- name: Frontend | Locales
|
||||
run: docker run --rm gradido/frontend:test yarn run locales
|
||||
run: cd frontend && yarn && yarn run locales
|
||||
|
||||
##############################################################################
|
||||
# JOB: LINT FRONTEND #########################################################
|
||||
@ -192,7 +181,6 @@ jobs:
|
||||
lint_frontend:
|
||||
name: Lint - Frontend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_frontend]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -200,20 +188,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Frontend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-frontend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/frontend.tar
|
||||
##########################################################################
|
||||
# LINT FRONTEND ##########################################################
|
||||
##########################################################################
|
||||
- name: Frontend | Lint
|
||||
run: docker run --rm gradido/frontend:test yarn run lint
|
||||
run: cd frontend && yarn && yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: STYLELINT FRONTEND ####################################################
|
||||
@ -221,7 +199,6 @@ jobs:
|
||||
stylelint_frontend:
|
||||
name: Stylelint - Frontend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_frontend]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -229,20 +206,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Frontend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-frontend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/frontend.tar
|
||||
##########################################################################
|
||||
# STYLELINT FRONTEND #####################################################
|
||||
##########################################################################
|
||||
- name: Frontend | Stylelint
|
||||
run: docker run --rm gradido/frontend:test yarn run stylelint
|
||||
run: cd frontend && yarn && yarn run stylelint
|
||||
|
||||
##############################################################################
|
||||
# JOB: LINT ADMIN INTERFACE ##################################################
|
||||
@ -250,7 +217,6 @@ jobs:
|
||||
lint_admin:
|
||||
name: Lint - Admin Interface
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_admin]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -258,20 +224,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Admin Interface)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-admin-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/admin.tar
|
||||
##########################################################################
|
||||
# LINT ADMIN INTERFACE ###################################################
|
||||
##########################################################################
|
||||
- name: Admin Interface | Lint
|
||||
run: docker run --rm gradido/admin:test yarn run lint
|
||||
run: cd admin && yarn && yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: STYLELINT ADMIN INTERFACE #############################################
|
||||
@ -279,7 +235,6 @@ jobs:
|
||||
stylelint_admin:
|
||||
name: Stylelint - Admin Interface
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_admin]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -287,20 +242,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Admin Interface)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-admin-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/admin.tar
|
||||
##########################################################################
|
||||
# STYLELINT ADMIN INTERFACE ##############################################
|
||||
##########################################################################
|
||||
- name: Admin Interface | Stylelint
|
||||
run: docker run --rm gradido/admin:test yarn run stylelint
|
||||
run: cd admin && yarn && yarn run stylelint
|
||||
|
||||
##############################################################################
|
||||
# JOB: LOCALES ADMIN #########################################################
|
||||
@ -308,7 +253,6 @@ jobs:
|
||||
locales_admin:
|
||||
name: Locales - Admin Interface
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_admin]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -316,20 +260,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Admin Interface)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-admin-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/admin.tar
|
||||
##########################################################################
|
||||
# LOCALES FRONTEND #######################################################
|
||||
##########################################################################
|
||||
- name: admin | Locales
|
||||
run: docker run --rm gradido/admin:test yarn run locales
|
||||
- name: Admin | Locales
|
||||
run: cd admin && yarn && yarn run locales
|
||||
|
||||
##############################################################################
|
||||
# JOB: LINT BACKEND ##########################################################
|
||||
@ -337,7 +271,6 @@ jobs:
|
||||
lint_backend:
|
||||
name: Lint - Backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_backend]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -345,20 +278,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-backend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/backend.tar
|
||||
##########################################################################
|
||||
# LINT BACKEND ###########################################################
|
||||
##########################################################################
|
||||
- name: backend | Lint
|
||||
run: docker run --rm gradido/backend:test yarn run lint
|
||||
run: cd backend && yarn && yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: LOCALES BACKEND #######################################################
|
||||
@ -366,7 +289,6 @@ jobs:
|
||||
locales_backend:
|
||||
name: Locales - Backend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_backend]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -385,7 +307,6 @@ jobs:
|
||||
lint_database_up:
|
||||
name: Lint - Database Up
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_database_up]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -393,20 +314,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGE ##################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Backend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-database-test_up
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/database_up.tar
|
||||
##########################################################################
|
||||
# LINT DATABASE ##########################################################
|
||||
##########################################################################
|
||||
- name: database | Lint
|
||||
run: docker run --rm gradido/database:test_up yarn run lint
|
||||
- name: Database | Lint
|
||||
run: cd database && yarn && yarn run lint
|
||||
|
||||
##############################################################################
|
||||
# JOB: UNIT TEST FRONTEND ###################################################
|
||||
@ -414,7 +325,6 @@ jobs:
|
||||
unit_test_frontend:
|
||||
name: Unit tests - Frontend
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_frontend]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -422,30 +332,12 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGES #################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Frontend)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-frontend-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/frontend.tar
|
||||
##########################################################################
|
||||
# UNIT TESTS FRONTEND ####################################################
|
||||
##########################################################################
|
||||
- name: frontend | Unit tests
|
||||
- name: Frontend | Unit tests
|
||||
run: |
|
||||
docker run --env NODE_ENV=test -v ~/coverage:/app/coverage --rm gradido/frontend:test yarn run test
|
||||
cp -r ~/coverage ./coverage
|
||||
##########################################################################
|
||||
# COVERAGE REPORT FRONTEND ###############################################
|
||||
##########################################################################
|
||||
#- name: frontend | Coverage report
|
||||
# uses: romeovs/lcov-reporter-action@v0.2.21
|
||||
# with:
|
||||
# github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# lcov-file: ./coverage/lcov.info
|
||||
cd frontend && yarn && yarn run test
|
||||
cp -r ./coverage ../
|
||||
##########################################################################
|
||||
# COVERAGE CHECK FRONTEND ################################################
|
||||
##########################################################################
|
||||
@ -454,7 +346,7 @@ jobs:
|
||||
with:
|
||||
report_name: Coverage Frontend
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
result_path: ./frontend/coverage/lcov.info
|
||||
min_coverage: 95
|
||||
token: ${{ github.token }}
|
||||
|
||||
@ -464,7 +356,6 @@ jobs:
|
||||
unit_test_admin:
|
||||
name: Unit tests - Admin Interface
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build_test_admin]
|
||||
steps:
|
||||
##########################################################################
|
||||
# CHECKOUT CODE ##########################################################
|
||||
@ -472,22 +363,12 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
##########################################################################
|
||||
# DOWNLOAD DOCKER IMAGES #################################################
|
||||
##########################################################################
|
||||
- name: Download Docker Image (Admin Interface)
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: docker-admin-test
|
||||
path: /tmp
|
||||
- name: Load Docker Image
|
||||
run: docker load < /tmp/admin.tar
|
||||
##########################################################################
|
||||
# UNIT TESTS ADMIN INTERFACE #############################################
|
||||
##########################################################################
|
||||
- name: Admin Interface | Unit tests
|
||||
run: |
|
||||
docker run -v ~/coverage:/app/coverage --rm gradido/admin:test yarn run test
|
||||
cp -r ~/coverage ./coverage
|
||||
cd admin && yarn && yarn run test
|
||||
cp -r ./coverage ../
|
||||
##########################################################################
|
||||
# COVERAGE CHECK ADMIN INTERFACE #########################################
|
||||
##########################################################################
|
||||
@ -496,7 +377,7 @@ jobs:
|
||||
with:
|
||||
report_name: Coverage Admin Interface
|
||||
type: lcov
|
||||
result_path: ./coverage/lcov.info
|
||||
result_path: ./admin/coverage/lcov.info
|
||||
min_coverage: 97
|
||||
token: ${{ github.token }}
|
||||
|
||||
@ -534,8 +415,9 @@ jobs:
|
||||
- name: backend | docker-compose database
|
||||
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps database
|
||||
- name: backend Unit tests | test
|
||||
run: cd database && yarn && yarn build && cd ../backend && yarn && yarn test
|
||||
# run: docker-compose -f docker-compose.yml -f docker-compose.test.yml exec -T backend yarn test
|
||||
run: |
|
||||
cd database && yarn && yarn build && cd ../backend && yarn && yarn test
|
||||
cp -r ./coverage ../
|
||||
##########################################################################
|
||||
# COVERAGE CHECK BACKEND #################################################
|
||||
##########################################################################
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import jwt from 'jsonwebtoken'
|
||||
import CONFIG from '@/config/'
|
||||
import { CustomJwtPayload } from './CustomJwtPayload'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
export const decode = (token: string): CustomJwtPayload | null => {
|
||||
if (!token) throw new Error('401 Unauthorized')
|
||||
if (!token) throw new LogError('401 Unauthorized')
|
||||
try {
|
||||
return <CustomJwtPayload>jwt.verify(token, CONFIG.JWT_SECRET)
|
||||
} catch (err) {
|
||||
|
||||
@ -7,6 +7,7 @@ import { ROLE_UNAUTHORIZED, ROLE_USER, ROLE_ADMIN } from '@/auth/ROLES'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS'
|
||||
import { User } from '@entity/User'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||
context.role = ROLE_UNAUTHORIZED // unauthorized user
|
||||
@ -17,13 +18,13 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||
|
||||
// Do we have a token?
|
||||
if (!context.token) {
|
||||
throw new Error('401 Unauthorized')
|
||||
throw new LogError('401 Unauthorized')
|
||||
}
|
||||
|
||||
// Decode the token
|
||||
const decoded = decode(context.token)
|
||||
if (!decoded) {
|
||||
throw new Error('403.13 - Client certificate revoked')
|
||||
throw new LogError('403.13 - Client certificate revoked')
|
||||
}
|
||||
// Set context gradidoID
|
||||
context.gradidoID = decoded.gradidoID
|
||||
@ -39,13 +40,13 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
|
||||
context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER
|
||||
} catch {
|
||||
// in case the database query fails (user deleted)
|
||||
throw new Error('401 Unauthorized')
|
||||
throw new LogError('401 Unauthorized')
|
||||
}
|
||||
|
||||
// check for correct rights
|
||||
const missingRights = (<RIGHTS[]>rights).filter((right) => !context.role.hasRight(right))
|
||||
if (missingRights.length !== 0) {
|
||||
throw new Error('401 Unauthorized')
|
||||
throw new LogError('401 Unauthorized')
|
||||
}
|
||||
|
||||
// set new header token
|
||||
|
||||
@ -257,17 +257,13 @@ describe('Contribution Links', () => {
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError('Start-Date is not initialized. A Start-Date must be set!'),
|
||||
],
|
||||
errors: [new GraphQLError('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!',
|
||||
)
|
||||
expect(logger.error).toBeCalledWith('A Start-Date must be set')
|
||||
})
|
||||
|
||||
it('returns an error if missing endDate', async () => {
|
||||
@ -282,15 +278,13 @@ describe('Contribution Links', () => {
|
||||
}),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('End-Date is not initialized. An End-Date must be set!')],
|
||||
errors: [new GraphQLError('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!',
|
||||
)
|
||||
expect(logger.error).toBeCalledWith('An End-Date must be set')
|
||||
})
|
||||
|
||||
it('returns an error if endDate is before startDate', async () => {
|
||||
@ -307,7 +301,7 @@ describe('Contribution Links', () => {
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(`The value of validFrom must before or equals the validTo!`),
|
||||
new GraphQLError(`The value of validFrom must before or equals the validTo`),
|
||||
],
|
||||
}),
|
||||
)
|
||||
@ -315,7 +309,7 @@ describe('Contribution Links', () => {
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
`The value of validFrom must before or equals the validTo!`,
|
||||
`The value of validFrom must before or equals the validTo`,
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@ -33,10 +33,14 @@ export class ContributionMessageResolver {
|
||||
try {
|
||||
const contribution = await DbContribution.findOne({ id: contributionId })
|
||||
if (!contribution) {
|
||||
throw new Error('Contribution not found')
|
||||
throw new LogError('Contribution not found', contributionId)
|
||||
}
|
||||
if (contribution.userId !== user.id) {
|
||||
throw new Error('Can not send message to contribution of another user')
|
||||
throw new LogError(
|
||||
'Can not send message to contribution of another user',
|
||||
contribution.userId,
|
||||
user.id,
|
||||
)
|
||||
}
|
||||
|
||||
contributionMessage.contributionId = contributionId
|
||||
|
||||
@ -245,8 +245,8 @@ describe('ContributionResolver', () => {
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No information for available creations with the given creationDate=',
|
||||
'Invalid Date',
|
||||
'No information for available creations for the given date',
|
||||
expect.any(Date),
|
||||
)
|
||||
})
|
||||
|
||||
@ -268,8 +268,8 @@ describe('ContributionResolver', () => {
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No information for available creations with the given creationDate=',
|
||||
'Invalid Date',
|
||||
'No information for available creations for the given date',
|
||||
expect.any(Date),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -526,14 +526,16 @@ describe('ContributionResolver', () => {
|
||||
})
|
||||
expect(errorObjects).toEqual([
|
||||
new GraphQLError(
|
||||
'The amount (1019 GDD) to be created exceeds the amount (600 GDD) still available for this month.',
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
),
|
||||
])
|
||||
})
|
||||
|
||||
it('logs the error found', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The amount (1019 GDD) to be created exceeds the amount (600 GDD) still available for this month.',
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
new Decimal(1019),
|
||||
new Decimal(600),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -2008,8 +2010,8 @@ describe('ContributionResolver', () => {
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No information for available creations with the given creationDate=',
|
||||
new Date(variables.creationDate).toString(),
|
||||
'No information for available creations for the given date',
|
||||
new Date(variables.creationDate),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -2033,8 +2035,8 @@ describe('ContributionResolver', () => {
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No information for available creations with the given creationDate=',
|
||||
new Date(variables.creationDate).toString(),
|
||||
'No information for available creations for the given date',
|
||||
new Date(variables.creationDate),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -2049,7 +2051,7 @@ describe('ContributionResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'The amount (2000 GDD) to be created exceeds the amount (790 GDD) still available for this month.',
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -2058,7 +2060,9 @@ describe('ContributionResolver', () => {
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The amount (2000 GDD) to be created exceeds the amount (790 GDD) still available for this month.',
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
new Decimal(2000),
|
||||
new Decimal(790),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -2098,7 +2102,7 @@ describe('ContributionResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'The amount (1000 GDD) to be created exceeds the amount (590 GDD) still available for this month.',
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -2107,7 +2111,9 @@ describe('ContributionResolver', () => {
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'The amount (1000 GDD) to be created exceeds the amount (590 GDD) still available for this month.',
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
new Decimal(1000),
|
||||
new Decimal(590),
|
||||
)
|
||||
})
|
||||
})
|
||||
@ -2300,7 +2306,7 @@ describe('ContributionResolver', () => {
|
||||
expect.objectContaining({
|
||||
errors: [
|
||||
new GraphQLError(
|
||||
'The amount (1900 GDD) to be created exceeds the amount (1000 GDD) still available for this month.',
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
),
|
||||
],
|
||||
}),
|
||||
@ -2309,7 +2315,9 @@ describe('ContributionResolver', () => {
|
||||
|
||||
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.',
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
new Decimal(1900),
|
||||
new Decimal(1000),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,6 +8,7 @@ import { Context, getUser } from '@/server/context'
|
||||
import CONFIG from '@/config'
|
||||
import { apiGet, apiPost } from '@/apis/HttpRequest'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
@Resolver()
|
||||
export class GdtResolver {
|
||||
@ -25,11 +26,11 @@ export class GdtResolver {
|
||||
`${CONFIG.GDT_API_URL}/GdtEntries/listPerEmailApi/${userEntity.emailContact.email}/${currentPage}/${pageSize}/${order}`,
|
||||
)
|
||||
if (!resultGDT.success) {
|
||||
throw new Error(resultGDT.data)
|
||||
throw new LogError(resultGDT.data)
|
||||
}
|
||||
return new GdtEntryList(resultGDT.data)
|
||||
} catch (err) {
|
||||
throw new Error('GDT Server is not reachable.')
|
||||
throw new LogError('GDT Server is not reachable')
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ export class GdtResolver {
|
||||
email: user.emailContact.email,
|
||||
})
|
||||
if (!resultGDTSum.success) {
|
||||
throw new Error('Call not successful')
|
||||
throw new LogError('Call not successful')
|
||||
}
|
||||
return Number(resultGDTSum.data.sum) || 0
|
||||
} catch (err) {
|
||||
@ -59,7 +60,7 @@ export class GdtResolver {
|
||||
// load user
|
||||
const resultPID = await apiGet(`${CONFIG.GDT_API_URL}/publishers/checkPidApi/${pid}`)
|
||||
if (!resultPID.success) {
|
||||
throw new Error(resultPID.data)
|
||||
throw new LogError(resultPID.data)
|
||||
}
|
||||
return resultPID.data.pid
|
||||
}
|
||||
|
||||
@ -53,65 +53,81 @@ afterAll(async () => {
|
||||
|
||||
describe('TransactionLinkResolver', () => {
|
||||
describe('createTransactionLink', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
describe('unauthenticated', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
resetToken()
|
||||
await expect(
|
||||
mutate({ mutation: createTransactionLink, variables: { amount: 0, memo: 'Test' } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('throws error when amount is zero', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createTransactionLink,
|
||||
variables: {
|
||||
amount: 0,
|
||||
memo: 'Test',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Amount must be a positive number')],
|
||||
describe('authenticated', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(0))
|
||||
})
|
||||
|
||||
it('throws error when amount is negative', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createTransactionLink,
|
||||
variables: {
|
||||
amount: -10,
|
||||
memo: 'Test',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Amount must be a positive number')],
|
||||
it('throws error when amount is zero', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createTransactionLink,
|
||||
variables: {
|
||||
amount: 0,
|
||||
memo: 'Test',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Amount must be a positive number')],
|
||||
})
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(0))
|
||||
})
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(-10))
|
||||
})
|
||||
|
||||
it('throws error when user has not enough GDD', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createTransactionLink,
|
||||
variables: {
|
||||
amount: 1001,
|
||||
memo: 'Test',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('User has not enough GDD')],
|
||||
it('throws error when amount is negative', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createTransactionLink,
|
||||
variables: {
|
||||
amount: -10,
|
||||
memo: 'Test',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Amount must be a positive number')],
|
||||
})
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Amount must be a positive number', new Decimal(-10))
|
||||
})
|
||||
|
||||
it('throws error when user has not enough GDD', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: createTransactionLink,
|
||||
variables: {
|
||||
amount: 1001,
|
||||
memo: 'Test',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('User has not enough GDD')],
|
||||
})
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User has not enough GDD', expect.any(Number))
|
||||
})
|
||||
})
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('User has not enough GDD', expect.any(Number))
|
||||
})
|
||||
})
|
||||
|
||||
@ -121,236 +137,37 @@ describe('TransactionLinkResolver', () => {
|
||||
resetToken()
|
||||
})
|
||||
|
||||
describe('contributionLink', () => {
|
||||
describe('input not valid', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
it('throws error when link does not exists', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-123456',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No contribution link found to given code',
|
||||
'CL-123456',
|
||||
)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error('No contribution link found to given code'),
|
||||
)
|
||||
})
|
||||
|
||||
const now = new Date()
|
||||
const validFrom = new Date(now.getFullYear() + 1, 0, 1)
|
||||
|
||||
it('throws error when link is not valid yet', async () => {
|
||||
jest.clearAllMocks()
|
||||
const {
|
||||
data: { createContributionLink: contributionLink },
|
||||
} = await mutate({
|
||||
mutation: createContributionLink,
|
||||
variables: {
|
||||
amount: new Decimal(5),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
cycle: 'DAILY',
|
||||
validFrom: validFrom.toISOString(),
|
||||
validTo: new Date(now.getFullYear() + 1, 11, 31, 23, 59, 59, 999).toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + contributionLink.code,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
await resetEntity(DbContributionLink)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution link is not valid yet', validFrom)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error('Contribution link is not valid yet'),
|
||||
)
|
||||
})
|
||||
|
||||
it('throws error when contributionLink cycle is invalid', async () => {
|
||||
jest.clearAllMocks()
|
||||
const now = new Date()
|
||||
const {
|
||||
data: { createContributionLink: contributionLink },
|
||||
} = await mutate({
|
||||
mutation: createContributionLink,
|
||||
variables: {
|
||||
amount: new Decimal(5),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
cycle: 'INVALID',
|
||||
validFrom: new Date(now.getFullYear(), 0, 1).toISOString(),
|
||||
validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + contributionLink.code,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
await resetEntity(DbContributionLink)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution link has unknown cycle', 'INVALID')
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error('Contribution link has unknown cycle'),
|
||||
)
|
||||
})
|
||||
|
||||
const validTo = new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 0)
|
||||
it('throws error when link is no longer valid', async () => {
|
||||
jest.clearAllMocks()
|
||||
const {
|
||||
data: { createContributionLink: contributionLink },
|
||||
} = await mutate({
|
||||
mutation: createContributionLink,
|
||||
variables: {
|
||||
amount: new Decimal(5),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
cycle: 'DAILY',
|
||||
validFrom: new Date(now.getFullYear() - 1, 0, 1).toISOString(),
|
||||
validTo: validTo.toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + contributionLink.code,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
await resetEntity(DbContributionLink)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution link is no longer valid', validTo)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error('Contribution link is no longer valid'),
|
||||
)
|
||||
})
|
||||
describe('unauthenticated', () => {
|
||||
it('throws an error', async () => {
|
||||
jest.clearAllMocks()
|
||||
resetToken()
|
||||
await expect(
|
||||
mutate({ mutation: redeemTransactionLink, variables: { code: 'CL-123456' } }),
|
||||
).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
errors: [new GraphQLError('401 Unauthorized')],
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: have this test separated into a transactionLink and a contributionLink part
|
||||
describe('redeem daily Contribution Link', () => {
|
||||
const now = new Date()
|
||||
let contributionLink: DbContributionLink | undefined
|
||||
let contribution: UnconfirmedContribution | undefined
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
await mutate({
|
||||
mutation: createContributionLink,
|
||||
variables: {
|
||||
amount: new Decimal(5),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
cycle: 'DAILY',
|
||||
validFrom: new Date(now.getFullYear(), 0, 1).toISOString(),
|
||||
validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('has a daily contribution link in the database', async () => {
|
||||
const cls = await DbContributionLink.find()
|
||||
expect(cls).toHaveLength(1)
|
||||
contributionLink = cls[0]
|
||||
expect(contributionLink).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
validFrom: new Date(now.getFullYear(), 0, 1),
|
||||
validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 0),
|
||||
cycle: 'DAILY',
|
||||
maxPerCycle: 1,
|
||||
totalMaxCountOfContribution: null,
|
||||
maxAccountBalance: null,
|
||||
minGapHours: null,
|
||||
createdAt: expect.any(Date),
|
||||
deletedAt: null,
|
||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||
linkEnabled: true,
|
||||
amount: expect.decimalEqual(5),
|
||||
maxAmountPerMonth: expect.decimalEqual(200),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('user has pending contribution of 1000 GDD', () => {
|
||||
describe('authenticated', () => {
|
||||
describe('contributionLink', () => {
|
||||
describe('input not valid', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
variables: { email: 'peter@lustig.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 () => {
|
||||
it('throws error when link does not exists', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + (contributionLink ? contributionLink.code : ''),
|
||||
code: 'CL-123456',
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
@ -359,85 +176,247 @@ describe('TransactionLinkResolver', () => {
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'No contribution link found to given code',
|
||||
'CL-123456',
|
||||
)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error(
|
||||
'The amount (5 GDD) to be created exceeds the amount (0 GDD) still available for this month.',
|
||||
),
|
||||
new Error('No contribution link found to given code'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
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(),
|
||||
},
|
||||
})
|
||||
})
|
||||
const now = new Date()
|
||||
const validFrom = new Date(now.getFullYear() + 1, 0, 1)
|
||||
|
||||
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 () => {
|
||||
it('throws error when link is not valid yet', async () => {
|
||||
jest.clearAllMocks()
|
||||
const {
|
||||
data: { createContributionLink: contributionLink },
|
||||
} = await mutate({
|
||||
mutation: createContributionLink,
|
||||
variables: {
|
||||
amount: new Decimal(5),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
cycle: 'DAILY',
|
||||
validFrom: validFrom.toISOString(),
|
||||
validTo: new Date(now.getFullYear() + 1, 11, 31, 23, 59, 59, 999).toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + (contributionLink ? contributionLink.code : ''),
|
||||
code: 'CL-' + contributionLink.code,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
await resetEntity(DbContributionLink)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution link is not valid yet', validFrom)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error('Contribution link already redeemed today'),
|
||||
new Error('Contribution link is not valid yet'),
|
||||
)
|
||||
})
|
||||
|
||||
describe('after one day', () => {
|
||||
it('throws error when contributionLink cycle is invalid', async () => {
|
||||
jest.clearAllMocks()
|
||||
const now = new Date()
|
||||
const {
|
||||
data: { createContributionLink: contributionLink },
|
||||
} = await mutate({
|
||||
mutation: createContributionLink,
|
||||
variables: {
|
||||
amount: new Decimal(5),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
cycle: 'INVALID',
|
||||
validFrom: new Date(now.getFullYear(), 0, 1).toISOString(),
|
||||
validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + contributionLink.code,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
await resetEntity(DbContributionLink)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution link has unknown cycle', 'INVALID')
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error('Contribution link has unknown cycle'),
|
||||
)
|
||||
})
|
||||
|
||||
const validTo = new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 0)
|
||||
it('throws error when link is no longer valid', async () => {
|
||||
jest.clearAllMocks()
|
||||
const {
|
||||
data: { createContributionLink: contributionLink },
|
||||
} = await mutate({
|
||||
mutation: createContributionLink,
|
||||
variables: {
|
||||
amount: new Decimal(5),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
cycle: 'DAILY',
|
||||
validFrom: new Date(now.getFullYear() - 1, 0, 1).toISOString(),
|
||||
validTo: validTo.toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + contributionLink.code,
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
await resetEntity(DbContributionLink)
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith('Contribution link is no longer valid', validTo)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error('Contribution link is no longer valid'),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: have this test separated into a transactionLink and a contributionLink part
|
||||
describe('redeem daily Contribution Link', () => {
|
||||
const now = new Date()
|
||||
let contributionLink: DbContributionLink | undefined
|
||||
let contribution: UnconfirmedContribution | undefined
|
||||
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||
})
|
||||
await mutate({
|
||||
mutation: createContributionLink,
|
||||
variables: {
|
||||
amount: new Decimal(5),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
cycle: 'DAILY',
|
||||
validFrom: new Date(now.getFullYear(), 0, 1).toISOString(),
|
||||
validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999).toISOString(),
|
||||
maxAmountPerMonth: new Decimal(200),
|
||||
maxPerCycle: 1,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('has a daily contribution link in the database', async () => {
|
||||
const cls = await DbContributionLink.find()
|
||||
expect(cls).toHaveLength(1)
|
||||
contributionLink = cls[0]
|
||||
expect(contributionLink).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(Number),
|
||||
name: 'Daily Contribution Link',
|
||||
memo: 'Thank you for contribute daily to the community',
|
||||
validFrom: new Date(now.getFullYear(), 0, 1),
|
||||
validTo: new Date(now.getFullYear(), 11, 31, 23, 59, 59, 0),
|
||||
cycle: 'DAILY',
|
||||
maxPerCycle: 1,
|
||||
totalMaxCountOfContribution: null,
|
||||
maxAccountBalance: null,
|
||||
minGapHours: null,
|
||||
createdAt: expect.any(Date),
|
||||
deletedAt: null,
|
||||
code: expect.stringMatching(/^[0-9a-f]{24,24}$/),
|
||||
linkEnabled: true,
|
||||
amount: expect.decimalEqual(5),
|
||||
maxAmountPerMonth: expect.decimalEqual(200),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
describe('user has pending contribution of 1000 GDD', () => {
|
||||
beforeAll(async () => {
|
||||
jest.useFakeTimers()
|
||||
setTimeout(jest.fn(), 1000 * 60 * 60 * 24)
|
||||
jest.runAllTimers()
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
const result = await mutate({
|
||||
mutation: createContribution,
|
||||
variables: {
|
||||
amount: new Decimal(1000),
|
||||
memo: 'I was brewing potions for the community the whole month',
|
||||
creationDate: now.toISOString(),
|
||||
},
|
||||
})
|
||||
contribution = result.data.createContribution
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers()
|
||||
it('does not allow the user to redeem the contribution link', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + (contributionLink ? contributionLink.code : ''),
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
})
|
||||
|
||||
it('allows the user to redeem the contribution link again', async () => {
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error(
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('user has no pending contributions that would not allow to redeem the link', () => {
|
||||
beforeAll(async () => {
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
await mutate({
|
||||
mutation: updateContribution,
|
||||
variables: {
|
||||
contributionId: contribution ? contribution.id : -1,
|
||||
amount: new Decimal(800),
|
||||
memo: 'I was brewing potions for the community the whole month',
|
||||
creationDate: now.toISOString(),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('allows the user to redeem the contribution link', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
@ -473,6 +452,59 @@ describe('TransactionLinkResolver', () => {
|
||||
new Error('Contribution link already redeemed today'),
|
||||
)
|
||||
})
|
||||
|
||||
describe('after one day', () => {
|
||||
beforeAll(async () => {
|
||||
jest.useFakeTimers()
|
||||
setTimeout(jest.fn(), 1000 * 60 * 60 * 24)
|
||||
jest.runAllTimers()
|
||||
await mutate({
|
||||
mutation: login,
|
||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('allows the user to redeem the contribution link again', async () => {
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + (contributionLink ? contributionLink.code : ''),
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
redeemTransactionLink: true,
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('does not allow the user to redeem the contribution link a second time on the same day', async () => {
|
||||
jest.clearAllMocks()
|
||||
await expect(
|
||||
mutate({
|
||||
mutation: redeemTransactionLink,
|
||||
variables: {
|
||||
code: 'CL-' + (contributionLink ? contributionLink.code : ''),
|
||||
},
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||
})
|
||||
})
|
||||
|
||||
it('logs the error thrown', () => {
|
||||
expect(logger.error).toBeCalledWith(
|
||||
'Creation from contribution link was not successful',
|
||||
new Error('Contribution link already redeemed today'),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -86,8 +86,8 @@ export class TransactionLinkResolver {
|
||||
transactionLink.code = transactionLinkCode(createdDate)
|
||||
transactionLink.createdAt = createdDate
|
||||
transactionLink.validUntil = validUntil
|
||||
await DbTransactionLink.save(transactionLink).catch(() => {
|
||||
throw new Error('Unable to save transaction link')
|
||||
await DbTransactionLink.save(transactionLink).catch((e) => {
|
||||
throw new LogError('Unable to save transaction link', e)
|
||||
})
|
||||
|
||||
return new TransactionLink(transactionLink, new User(user))
|
||||
@ -103,19 +103,23 @@ export class TransactionLinkResolver {
|
||||
|
||||
const transactionLink = await DbTransactionLink.findOne({ id })
|
||||
if (!transactionLink) {
|
||||
throw new Error('Transaction Link not found!')
|
||||
throw new LogError('Transaction link not found', id)
|
||||
}
|
||||
|
||||
if (transactionLink.userId !== user.id) {
|
||||
throw new Error('Transaction Link cannot be deleted!')
|
||||
throw new LogError(
|
||||
'Transaction link cannot be deleted by another user',
|
||||
transactionLink.userId,
|
||||
user.id,
|
||||
)
|
||||
}
|
||||
|
||||
if (transactionLink.redeemedBy) {
|
||||
throw new Error('Transaction Link already redeemed!')
|
||||
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
||||
}
|
||||
|
||||
await transactionLink.softRemove().catch(() => {
|
||||
throw new Error('Transaction Link could not be deleted!')
|
||||
await transactionLink.softRemove().catch((e) => {
|
||||
throw new LogError('Transaction link could not be deleted', e)
|
||||
})
|
||||
|
||||
return true
|
||||
@ -312,18 +316,18 @@ export class TransactionLinkResolver {
|
||||
)
|
||||
|
||||
if (user.id === linkedUser.id) {
|
||||
throw new Error('Cannot redeem own transaction link.')
|
||||
throw new LogError('Cannot redeem own transaction link', user.id)
|
||||
}
|
||||
|
||||
// TODO: The now check should be done within the semaphore lock,
|
||||
// since the program might wait a while till it is ready to proceed
|
||||
// writing the transaction.
|
||||
if (transactionLink.validUntil.getTime() < now.getTime()) {
|
||||
throw new Error('Transaction Link is not valid anymore.')
|
||||
throw new LogError('Transaction link is not valid anymore', transactionLink.validUntil)
|
||||
}
|
||||
|
||||
if (transactionLink.redeemedBy) {
|
||||
throw new Error('Transaction Link already redeemed.')
|
||||
throw new LogError('Transaction link already redeemed', transactionLink.redeemedBy)
|
||||
}
|
||||
|
||||
await executeTransaction(
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import LogError from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { getConnection } from '@dbTools/typeorm'
|
||||
import { Contribution } from '@entity/Contribution'
|
||||
@ -19,19 +20,14 @@ export const validateContribution = (
|
||||
const index = getCreationIndex(creationDate.getMonth(), timezoneOffset)
|
||||
|
||||
if (index < 0) {
|
||||
logger.error(
|
||||
'No information for available creations with the given creationDate=',
|
||||
creationDate.toString(),
|
||||
)
|
||||
throw new Error('No information for available creations for the given date')
|
||||
throw new LogError('No information for available creations for the given date', creationDate)
|
||||
}
|
||||
|
||||
if (amount.greaterThan(creations[index].toString())) {
|
||||
logger.error(
|
||||
`The amount (${amount} GDD) to be created exceeds the amount (${creations[index]} GDD) still available for this month.`,
|
||||
)
|
||||
throw new Error(
|
||||
`The amount (${amount} GDD) to be created exceeds the amount (${creations[index]} GDD) still available for this month.`,
|
||||
throw new LogError(
|
||||
'The amount to be created exceeds the amount still available for this month',
|
||||
amount,
|
||||
creations[index],
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -126,19 +122,16 @@ export const isStartEndDateValid = (
|
||||
endDate: string | null | undefined,
|
||||
): void => {
|
||||
if (!startDate) {
|
||||
logger.error('Start-Date is not initialized. A Start-Date must be set!')
|
||||
throw new Error('Start-Date is not initialized. A Start-Date must be set!')
|
||||
throw new LogError('A Start-Date must be set')
|
||||
}
|
||||
|
||||
if (!endDate) {
|
||||
logger.error('End-Date is not initialized. An End-Date must be set!')
|
||||
throw new Error('End-Date is not initialized. An End-Date must be set!')
|
||||
throw new LogError('An End-Date must be set')
|
||||
}
|
||||
|
||||
// check if endDate is before startDate
|
||||
if (new Date(endDate).getTime() - new Date(startDate).getTime() < 0) {
|
||||
logger.error(`The value of validFrom must before or equals the validTo!`)
|
||||
throw new Error(`The value of validFrom must before or equals the validTo!`)
|
||||
throw new LogError(`The value of validFrom must before or equals the validTo`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,7 +143,7 @@ export const updateCreations = (
|
||||
const index = getCreationIndex(contribution.contributionDate.getMonth(), timezoneOffset)
|
||||
|
||||
if (index < 0) {
|
||||
throw new Error('You cannot create GDD for a month older than the last three months.')
|
||||
throw new LogError('You cannot create GDD for a month older than the last three months')
|
||||
}
|
||||
creations[index] = creations[index].plus(contribution.amount.toString())
|
||||
return creations
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import CONFIG from '@/config'
|
||||
import LogError from '@/server/LogError'
|
||||
import { backendLogger as logger } from '@/server/logger'
|
||||
import { User } from '@entity/User'
|
||||
import { PasswordEncryptionType } from '@enum/PasswordEncryptionType'
|
||||
@ -16,11 +17,10 @@ export const SecretKeyCryptographyCreateKey = (salt: string, password: string):
|
||||
const configLoginAppSecret = Buffer.from(CONFIG.LOGIN_APP_SECRET, 'hex')
|
||||
const configLoginServerKey = Buffer.from(CONFIG.LOGIN_SERVER_KEY, 'hex')
|
||||
if (configLoginServerKey.length !== sodium.crypto_shorthash_KEYBYTES) {
|
||||
logger.error(
|
||||
`ServerKey has an invalid size. The size must be ${sodium.crypto_shorthash_KEYBYTES} bytes.`,
|
||||
)
|
||||
throw new Error(
|
||||
`ServerKey has an invalid size. The size must be ${sodium.crypto_shorthash_KEYBYTES} bytes.`,
|
||||
throw new LogError(
|
||||
'ServerKey has an invalid size',
|
||||
configLoginServerKey.length,
|
||||
sodium.crypto_shorthash_KEYBYTES,
|
||||
)
|
||||
}
|
||||
|
||||
@ -52,20 +52,13 @@ export const SecretKeyCryptographyCreateKey = (salt: string, password: string):
|
||||
|
||||
export const getUserCryptographicSalt = (dbUser: User): string => {
|
||||
switch (dbUser.passwordEncryptionType) {
|
||||
case PasswordEncryptionType.NO_PASSWORD: {
|
||||
logger.error('Password not set for user ' + dbUser.id)
|
||||
throw new Error('Password not set for user ' + dbUser.id) // user has no password
|
||||
}
|
||||
case PasswordEncryptionType.EMAIL: {
|
||||
case PasswordEncryptionType.NO_PASSWORD:
|
||||
throw new LogError('User has no password set', dbUser.id)
|
||||
case PasswordEncryptionType.EMAIL:
|
||||
return dbUser.emailContact.email
|
||||
break
|
||||
}
|
||||
case PasswordEncryptionType.GRADIDO_ID: {
|
||||
case PasswordEncryptionType.GRADIDO_ID:
|
||||
return dbUser.gradidoID
|
||||
break
|
||||
}
|
||||
default:
|
||||
logger.error(`Unknown password encryption type: ${dbUser.passwordEncryptionType}`)
|
||||
throw new Error(`Unknown password encryption type: ${dbUser.passwordEncryptionType}`)
|
||||
throw new LogError('Unknown password encryption type', dbUser.passwordEncryptionType)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { User as dbUser } from '@entity/User'
|
||||
import { Transaction as dbTransaction } from '@entity/Transaction'
|
||||
import Decimal from 'decimal.js-light'
|
||||
import { ExpressContext } from 'apollo-server-express'
|
||||
import LogError from './LogError'
|
||||
|
||||
export interface Context {
|
||||
token: string | null
|
||||
@ -35,7 +36,7 @@ const context = (args: ExpressContext): Context => {
|
||||
|
||||
export const getUser = (context: Context): dbUser => {
|
||||
if (context.user) return context.user
|
||||
throw new Error('No user given in context!')
|
||||
throw new LogError('No user given in context')
|
||||
}
|
||||
|
||||
export const getClientTimezoneOffset = (context: Context): number => {
|
||||
@ -45,7 +46,7 @@ export const getClientTimezoneOffset = (context: Context): number => {
|
||||
) {
|
||||
return context.clientTimezoneOffset
|
||||
}
|
||||
throw new Error('No valid client time zone offset in context!')
|
||||
throw new LogError('No valid client time zone offset in context')
|
||||
}
|
||||
|
||||
export default context
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import Decimal from 'decimal.js-light'
|
||||
import CONFIG from '@/config'
|
||||
import { Decay } from '@model/Decay'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
// TODO: externalize all those definitions and functions into an external decay library
|
||||
|
||||
@ -22,7 +23,7 @@ function calculateDecay(
|
||||
const startBlockMs = startBlock.getTime()
|
||||
|
||||
if (toMs < fromMs) {
|
||||
throw new Error('to < from, reverse decay calculation is invalid')
|
||||
throw new LogError('calculateDecay: to < from, reverse decay calculation is invalid')
|
||||
}
|
||||
|
||||
// Initialize with no decay
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import connection from '@/typeorm/connection'
|
||||
import { getKlickTippUser } from '@/apis/KlicktippController'
|
||||
import { User } from '@entity/User'
|
||||
import LogError from '@/server/LogError'
|
||||
|
||||
export async function retrieveNotRegisteredEmails(): Promise<string[]> {
|
||||
const con = await connection()
|
||||
if (!con) {
|
||||
throw new Error('No connection to database')
|
||||
throw new LogError('No connection to database')
|
||||
}
|
||||
const users = await User.find({ relations: ['emailContact'] })
|
||||
const notRegisteredUser = []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user