mirror of
https://github.com/IT4Change/gradido.git
synced 2025-12-13 07:45:54 +00:00
Merge branch 'master' into 2594-contributions-list-frontend
This commit is contained in:
commit
ddbdbe4224
19
.github/workflows/test.yml
vendored
19
.github/workflows/test.yml
vendored
@ -360,6 +360,25 @@ jobs:
|
|||||||
- name: backend | Lint
|
- name: backend | Lint
|
||||||
run: docker run --rm gradido/backend:test yarn run lint
|
run: docker run --rm gradido/backend:test yarn run lint
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# JOB: LOCALES BACKEND #######################################################
|
||||||
|
##############################################################################
|
||||||
|
locales_backend:
|
||||||
|
name: Locales - Backend
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build_test_backend]
|
||||||
|
steps:
|
||||||
|
##########################################################################
|
||||||
|
# CHECKOUT CODE ##########################################################
|
||||||
|
##########################################################################
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
##########################################################################
|
||||||
|
# LOCALES BACKEND #####################################################
|
||||||
|
##########################################################################
|
||||||
|
- name: Backend | Locales
|
||||||
|
run: cd backend && yarn && yarn locales
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# JOB: LINT DATABASE UP ######################################################
|
# JOB: LINT DATABASE UP ######################################################
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|||||||
@ -4,8 +4,15 @@ 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).
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
||||||
|
|
||||||
|
#### [1.18.2](https://github.com/gradido/gradido/compare/1.18.1...1.18.2)
|
||||||
|
|
||||||
|
- fix(admin): deny contribution button to left [`#2699`](https://github.com/gradido/gradido/pull/2699)
|
||||||
|
|
||||||
#### [1.18.1](https://github.com/gradido/gradido/compare/1.18.0...1.18.1)
|
#### [1.18.1](https://github.com/gradido/gradido/compare/1.18.0...1.18.1)
|
||||||
|
|
||||||
|
> 10 February 2023
|
||||||
|
|
||||||
|
- chore(release): version 1.18.1 [`#2698`](https://github.com/gradido/gradido/pull/2698)
|
||||||
- fix(frontend): fix is last month for empty form date [`#2697`](https://github.com/gradido/gradido/pull/2697)
|
- fix(frontend): fix is last month for empty form date [`#2697`](https://github.com/gradido/gradido/pull/2697)
|
||||||
- fix(frontend): community link [`#2696`](https://github.com/gradido/gradido/pull/2696)
|
- fix(frontend): community link [`#2696`](https://github.com/gradido/gradido/pull/2696)
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"description": "Administraion Interface for Gradido",
|
"description": "Administraion Interface for Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Moriz Wahl",
|
"author": "Moriz Wahl",
|
||||||
"version": "1.18.1",
|
"version": "1.18.2",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -88,5 +88,16 @@ describe('CreationTransactionList', () => {
|
|||||||
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
expect(toastErrorSpy).toBeCalledWith('OUCH!')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('watch currentPage', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await wrapper.setData({ currentPage: 2 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the string in normal order if reversed property is not true', () => {
|
||||||
|
expect(wrapper.vm.currentPage).toBe(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -46,39 +46,31 @@ describe('NavBar', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('Navbar Menu', () => {
|
describe('Navbar Menu', () => {
|
||||||
it('has a link to overview', () => {
|
|
||||||
expect(wrapper.findAll('.nav-item').at(0).find('a').attributes('href')).toBe('/')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a link to /user', () => {
|
it('has a link to /user', () => {
|
||||||
expect(wrapper.findAll('.nav-item').at(1).find('a').attributes('href')).toBe('/user')
|
expect(wrapper.findAll('.nav-item').at(0).find('a').attributes('href')).toBe('/user')
|
||||||
})
|
|
||||||
|
|
||||||
it('has a link to /creation', () => {
|
|
||||||
expect(wrapper.findAll('.nav-item').at(2).find('a').attributes('href')).toBe('/creation')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a link to /creation-confirm', () => {
|
it('has a link to /creation-confirm', () => {
|
||||||
expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe(
|
expect(wrapper.findAll('.nav-item').at(1).find('a').attributes('href')).toBe(
|
||||||
'/creation-confirm',
|
'/creation-confirm',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a link to /contribution-links', () => {
|
it('has a link to /contribution-links', () => {
|
||||||
expect(wrapper.findAll('.nav-item').at(4).find('a').attributes('href')).toBe(
|
expect(wrapper.findAll('.nav-item').at(2).find('a').attributes('href')).toBe(
|
||||||
'/contribution-links',
|
'/contribution-links',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has a link to /statistic', () => {
|
it('has a link to /statistic', () => {
|
||||||
expect(wrapper.findAll('.nav-item').at(5).find('a').attributes('href')).toBe('/statistic')
|
expect(wrapper.findAll('.nav-item').at(3).find('a').attributes('href')).toBe('/statistic')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('wallet', () => {
|
describe('wallet', () => {
|
||||||
const assignLocationSpy = jest.fn()
|
const assignLocationSpy = jest.fn()
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.findAll('.nav-item').at(6).find('a').trigger('click')
|
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('changes window location to wallet', () => {
|
it.skip('changes window location to wallet', () => {
|
||||||
@ -97,7 +89,7 @@ describe('NavBar', () => {
|
|||||||
window.location = {
|
window.location = {
|
||||||
assign: windowLocationMock,
|
assign: windowLocationMock,
|
||||||
}
|
}
|
||||||
await wrapper.findAll('.nav-item').at(7).find('a').trigger('click')
|
await wrapper.findAll('.nav-item').at(5).find('a').trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('redirects to /logout', () => {
|
it('redirects to /logout', () => {
|
||||||
|
|||||||
@ -9,9 +9,7 @@
|
|||||||
|
|
||||||
<b-collapse id="nav-collapse" is-nav>
|
<b-collapse id="nav-collapse" is-nav>
|
||||||
<b-navbar-nav>
|
<b-navbar-nav>
|
||||||
<b-nav-item to="/">{{ $t('navbar.overview') }}</b-nav-item>
|
|
||||||
<b-nav-item to="/user">{{ $t('navbar.user_search') }}</b-nav-item>
|
<b-nav-item to="/user">{{ $t('navbar.user_search') }}</b-nav-item>
|
||||||
<b-nav-item to="/creation">{{ $t('navbar.multi_creation') }}</b-nav-item>
|
|
||||||
<b-nav-item class="bg-color-creation p-1" to="/creation-confirm">
|
<b-nav-item class="bg-color-creation p-1" to="/creation-confirm">
|
||||||
{{ $t('creation') }}
|
{{ $t('creation') }}
|
||||||
<b-badge v-show="$store.state.openCreations > 0" variant="danger">
|
<b-badge v-show="$store.state.openCreations > 0" variant="danger">
|
||||||
|
|||||||
@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="component-select-users-table">
|
|
||||||
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
|
|
||||||
<template #cell(bookmark)="row">
|
|
||||||
<div>
|
|
||||||
<b-button
|
|
||||||
v-if="row.item.emailChecked"
|
|
||||||
variant="warning"
|
|
||||||
size="md"
|
|
||||||
@click="$emit('push-item', row.item)"
|
|
||||||
class="mr-2"
|
|
||||||
>
|
|
||||||
<b-icon icon="plus" variant="success"></b-icon>
|
|
||||||
</b-button>
|
|
||||||
<div v-else>{{ $t('e_mail') }}{{ $t('math.exclaim') }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</b-table-lite>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'SelectUsersTable',
|
|
||||||
props: {
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
fields: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="component-selected-users-table">
|
|
||||||
<b-table-lite :items="items" :fields="fields" caption-top striped hover stacked="md">
|
|
||||||
<template #cell(bookmark)="row">
|
|
||||||
<b-button variant="danger" size="md" @click="$emit('remove-item', row.item)" class="mr-2">
|
|
||||||
<b-icon icon="x" variant="light"></b-icon>
|
|
||||||
</b-button>
|
|
||||||
</template>
|
|
||||||
</b-table-lite>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'SelectedUsersTable',
|
|
||||||
props: {
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
fields: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -42,7 +42,6 @@
|
|||||||
"creation": "Schöpfung",
|
"creation": "Schöpfung",
|
||||||
"creationList": "Schöpfungsliste",
|
"creationList": "Schöpfungsliste",
|
||||||
"creation_form": {
|
"creation_form": {
|
||||||
"creation_failed": "Ausstehende Schöpfung für {email} konnte nicht erzeugt werden.",
|
|
||||||
"creation_for": "Aktives Grundeinkommen für",
|
"creation_for": "Aktives Grundeinkommen für",
|
||||||
"enter_text": "Text eintragen",
|
"enter_text": "Text eintragen",
|
||||||
"form": "Schöpfungsformular",
|
"form": "Schöpfungsformular",
|
||||||
@ -96,7 +95,6 @@
|
|||||||
"lastname": "Nachname",
|
"lastname": "Nachname",
|
||||||
"math": {
|
"math": {
|
||||||
"equals": "=",
|
"equals": "=",
|
||||||
"exclaim": "!",
|
|
||||||
"pipe": "|",
|
"pipe": "|",
|
||||||
"plus": "+"
|
"plus": "+"
|
||||||
},
|
},
|
||||||
@ -105,14 +103,12 @@
|
|||||||
},
|
},
|
||||||
"mod": "Mod",
|
"mod": "Mod",
|
||||||
"moderator": "Moderator",
|
"moderator": "Moderator",
|
||||||
"multiple_creation_text": "Bitte wähle ein oder mehrere Mitglieder aus für die du Schöpfen möchtest.",
|
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"automaticContributions": "Automatische Beiträge",
|
"automaticContributions": "Automatische Beiträge",
|
||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"multi_creation": "Mehrfachschöpfung",
|
|
||||||
"my-account": "Mein Konto",
|
"my-account": "Mein Konto",
|
||||||
"overview": "Übersicht",
|
"open_creation": "Offene Schöpfungen",
|
||||||
"statistic": "Statistik",
|
"statistic": "Statistik",
|
||||||
"user_search": "Nutzersuche"
|
"user_search": "Nutzersuche"
|
||||||
},
|
},
|
||||||
@ -141,9 +137,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redeemed": "eingelöst",
|
"redeemed": "eingelöst",
|
||||||
"remove": "Entfernen",
|
|
||||||
"removeNotSelf": "Als Admin/Moderator kannst du dich nicht selber löschen.",
|
"removeNotSelf": "Als Admin/Moderator kannst du dich nicht selber löschen.",
|
||||||
"remove_all": "alle Nutzer entfernen",
|
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"statistic": {
|
"statistic": {
|
||||||
"activeUsers": "Aktive Mitglieder",
|
"activeUsers": "Aktive Mitglieder",
|
||||||
|
|||||||
@ -42,7 +42,6 @@
|
|||||||
"creation": "Creation",
|
"creation": "Creation",
|
||||||
"creationList": "Creation list",
|
"creationList": "Creation list",
|
||||||
"creation_form": {
|
"creation_form": {
|
||||||
"creation_failed": "Could not create pending creation for {email}",
|
|
||||||
"creation_for": "Active Basic Income for",
|
"creation_for": "Active Basic Income for",
|
||||||
"enter_text": "Enter text",
|
"enter_text": "Enter text",
|
||||||
"form": "Creation form",
|
"form": "Creation form",
|
||||||
@ -96,7 +95,6 @@
|
|||||||
"lastname": "Lastname",
|
"lastname": "Lastname",
|
||||||
"math": {
|
"math": {
|
||||||
"equals": "=",
|
"equals": "=",
|
||||||
"exclaim": "!",
|
|
||||||
"pipe": "|",
|
"pipe": "|",
|
||||||
"plus": "+"
|
"plus": "+"
|
||||||
},
|
},
|
||||||
@ -105,14 +103,12 @@
|
|||||||
},
|
},
|
||||||
"mod": "Mod",
|
"mod": "Mod",
|
||||||
"moderator": "Moderator",
|
"moderator": "Moderator",
|
||||||
"multiple_creation_text": "Please select one or more members for which you would like to perform creations.",
|
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"navbar": {
|
"navbar": {
|
||||||
"automaticContributions": "Automatic Contributions",
|
"automaticContributions": "Automatic Contributions",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"multi_creation": "Multiple creation",
|
|
||||||
"my-account": "My Account",
|
"my-account": "My Account",
|
||||||
"overview": "Overview",
|
"open_creation": "Open creations",
|
||||||
"statistic": "Statistic",
|
"statistic": "Statistic",
|
||||||
"user_search": "User search"
|
"user_search": "User search"
|
||||||
},
|
},
|
||||||
@ -141,9 +137,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redeemed": "redeemed",
|
"redeemed": "redeemed",
|
||||||
"remove": "Remove",
|
|
||||||
"removeNotSelf": "As an admin/moderator, you cannot delete yourself.",
|
"removeNotSelf": "As an admin/moderator, you cannot delete yourself.",
|
||||||
"remove_all": "Remove all users",
|
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"statistic": {
|
"statistic": {
|
||||||
"activeUsers": "Active members",
|
"activeUsers": "Active members",
|
||||||
|
|||||||
18
admin/src/locales/index.test.js
Normal file
18
admin/src/locales/index.test.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import locales from './index.js'
|
||||||
|
|
||||||
|
describe('locales', () => {
|
||||||
|
it('should contain 2 locales', () => {
|
||||||
|
expect(locales).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should contain a German locale', () => {
|
||||||
|
expect(locales).toContainEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
name: 'Deutsch',
|
||||||
|
code: 'de',
|
||||||
|
iso: 'de-DE',
|
||||||
|
enabled: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import ContributionLinks from './ContributionLinks.vue'
|
import ContributionLinks from './ContributionLinks.vue'
|
||||||
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
|
import { listContributionLinks } from '@/graphql/listContributionLinks.js'
|
||||||
|
import { toastErrorSpy } from '../../test/testSetup'
|
||||||
|
|
||||||
const localVue = global.localVue
|
const localVue = global.localVue
|
||||||
|
|
||||||
@ -46,13 +47,31 @@ describe('ContributionLinks', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = Wrapper()
|
wrapper = Wrapper()
|
||||||
})
|
})
|
||||||
|
describe('apollo returns', () => {
|
||||||
|
it('calls listContributionLinks', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
query: listContributionLinks,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('calls listContributionLinks', () => {
|
describe.skip('query transaction with error', () => {
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
beforeEach(() => {
|
||||||
expect.objectContaining({
|
apolloQueryMock.mockRejectedValue({ message: 'OUCH!' })
|
||||||
query: listContributionLinks,
|
wrapper = Wrapper()
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
|
it('calls the API', () => {
|
||||||
|
expect(apolloQueryMock).toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toast error', () => {
|
||||||
|
expect(toastErrorSpy).toBeCalledWith(
|
||||||
|
'listContributionLinks has no result, use default data',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,337 +0,0 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import Creation from './Creation.vue'
|
|
||||||
import { toastErrorSpy } from '../../test/testSetup'
|
|
||||||
|
|
||||||
const localVue = global.localVue
|
|
||||||
|
|
||||||
const apolloQueryMock = jest.fn().mockResolvedValue({
|
|
||||||
data: {
|
|
||||||
searchUsers: {
|
|
||||||
userCount: 2,
|
|
||||||
userList: [
|
|
||||||
{
|
|
||||||
userId: 1,
|
|
||||||
firstName: 'Bibi',
|
|
||||||
lastName: 'Bloxberg',
|
|
||||||
email: 'bibi@bloxberg.de',
|
|
||||||
creation: [200, 400, 600],
|
|
||||||
emailChecked: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
userId: 2,
|
|
||||||
firstName: 'Benjamin',
|
|
||||||
lastName: 'Blümchen',
|
|
||||||
email: 'benjamin@bluemchen.de',
|
|
||||||
creation: [800, 600, 400],
|
|
||||||
emailChecked: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const storeCommitMock = jest.fn()
|
|
||||||
|
|
||||||
const mocks = {
|
|
||||||
$t: jest.fn((t, options) => (options ? [t, options] : t)),
|
|
||||||
$d: jest.fn((d) => d),
|
|
||||||
$apollo: {
|
|
||||||
query: apolloQueryMock,
|
|
||||||
},
|
|
||||||
$store: {
|
|
||||||
commit: storeCommitMock,
|
|
||||||
state: {
|
|
||||||
userSelectedInMassCreation: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Creation', () => {
|
|
||||||
let wrapper
|
|
||||||
|
|
||||||
const Wrapper = () => {
|
|
||||||
return mount(Creation, { localVue, mocks })
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('mount', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has a DIV element with the class.creation', () => {
|
|
||||||
expect(wrapper.find('div.creation').exists()).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('apollo returns user array', () => {
|
|
||||||
it('calls the searchUser query', () => {
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
searchText: '',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 25,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has two rows in the left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has nwo rows in the right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has correct data in first row ', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain('Bibi')
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'Bloxberg',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'200 | 400 | 600',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'bibi@bloxberg.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has correct data in second row ', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
|
|
||||||
'Benjamin',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
|
|
||||||
'Blümchen',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
|
|
||||||
'800 | 600 | 400',
|
|
||||||
)
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).text()).toContain(
|
|
||||||
'benjamin@bluemchen.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('push item', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper.findAll('table').at(0).findAll('tbody > tr').at(1).find('button').trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has one item in left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has one item in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has the correct user in left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'bibi@bloxberg.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has the correct user in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'benjamin@bluemchen.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('updates userSelectedInMassCreation in store', () => {
|
|
||||||
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', [
|
|
||||||
{
|
|
||||||
userId: 2,
|
|
||||||
firstName: 'Benjamin',
|
|
||||||
lastName: 'Blümchen',
|
|
||||||
email: 'benjamin@bluemchen.de',
|
|
||||||
creation: [800, 600, 400],
|
|
||||||
showDetails: false,
|
|
||||||
emailChecked: true,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('remove item', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await wrapper
|
|
||||||
.findAll('table')
|
|
||||||
.at(1)
|
|
||||||
.findAll('tbody > tr')
|
|
||||||
.at(0)
|
|
||||||
.find('button')
|
|
||||||
.trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has two items in left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has the removed user in first row', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'benjamin@bluemchen.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has no items in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('commits empty array as userSelectedInMassCreation', () => {
|
|
||||||
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', [])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('remove all bookmarks', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
await wrapper.find('button.btn-light').trigger('click')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has no items in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('commits empty array to userSelectedInMassCreation', () => {
|
|
||||||
expect(storeCommitMock).toBeCalledWith('setUserSelectedInMassCreation', [])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls searchUsers', () => {
|
|
||||||
expect(apolloQueryMock).toBeCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('store has items in userSelectedInMassCreation', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
mocks.$store.state.userSelectedInMassCreation = [
|
|
||||||
{
|
|
||||||
userId: 2,
|
|
||||||
firstName: 'Benjamin',
|
|
||||||
lastName: 'Blümchen',
|
|
||||||
email: 'benjamin@bluemchen.de',
|
|
||||||
creation: [800, 600, 400],
|
|
||||||
showDetails: false,
|
|
||||||
emailChecked: true,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has one item in left table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(0).findAll('tbody > tr')).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has one item in right table', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr')).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('has the stored user in second row', () => {
|
|
||||||
expect(wrapper.findAll('table').at(1).findAll('tbody > tr').at(0).text()).toContain(
|
|
||||||
'benjamin@bluemchen.de',
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('failed creations', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await wrapper
|
|
||||||
.findComponent({ name: 'CreationFormular' })
|
|
||||||
.vm.$emit('toast-failed-creations', ['bibi@bloxberg.de', 'benjamin@bluemchen.de'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('toasts two error messages', () => {
|
|
||||||
expect(toastErrorSpy).toBeCalledWith([
|
|
||||||
'creation_form.creation_failed',
|
|
||||||
{ email: 'bibi@bloxberg.de' },
|
|
||||||
])
|
|
||||||
expect(toastErrorSpy).toBeCalledWith([
|
|
||||||
'creation_form.creation_failed',
|
|
||||||
{ email: 'benjamin@bluemchen.de' },
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('watchers', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('search criteria', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await wrapper.setData({ criteria: 'XX' })
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls API when criteria changes', async () => {
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
searchText: 'XX',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 25,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('reset search criteria', () => {
|
|
||||||
it('calls the API', async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
await wrapper.find('.test-click-clear-criteria').trigger('click')
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
searchText: '',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 25,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('calls API when currentPage changes', async () => {
|
|
||||||
await wrapper.setData({ currentPage: 2 })
|
|
||||||
expect(apolloQueryMock).toBeCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
variables: {
|
|
||||||
searchText: '',
|
|
||||||
currentPage: 2,
|
|
||||||
pageSize: 25,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('apollo returns error', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
apolloQueryMock.mockRejectedValue({
|
|
||||||
message: 'Ouch',
|
|
||||||
})
|
|
||||||
wrapper = Wrapper()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('toasts an error message', () => {
|
|
||||||
expect(toastErrorSpy).toBeCalledWith('Ouch')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="creation">
|
|
||||||
<b-row>
|
|
||||||
<b-col cols="12" lg="6">
|
|
||||||
<label>{{ $t('user_search') }}</label>
|
|
||||||
<b-input-group>
|
|
||||||
<b-form-input
|
|
||||||
type="text"
|
|
||||||
class="test-input-criteria"
|
|
||||||
v-model="criteria"
|
|
||||||
:placeholder="$t('user_search')"
|
|
||||||
></b-form-input>
|
|
||||||
|
|
||||||
<b-input-group-append class="test-click-clear-criteria" @click="criteria = ''">
|
|
||||||
<b-input-group-text class="pointer">
|
|
||||||
<b-icon icon="x" />
|
|
||||||
</b-input-group-text>
|
|
||||||
</b-input-group-append>
|
|
||||||
</b-input-group>
|
|
||||||
<select-users-table
|
|
||||||
v-if="itemsList.length > 0"
|
|
||||||
:items="itemsList"
|
|
||||||
:fields="Searchfields"
|
|
||||||
@push-item="pushItem"
|
|
||||||
/>
|
|
||||||
<b-pagination
|
|
||||||
pills
|
|
||||||
v-model="currentPage"
|
|
||||||
per-page="perPage"
|
|
||||||
:total-rows="rows"
|
|
||||||
align="center"
|
|
||||||
:hide-ellipsis="true"
|
|
||||||
></b-pagination>
|
|
||||||
</b-col>
|
|
||||||
<b-col cols="12" lg="6" class="shadow p-3 mb-5 rounded bg-info">
|
|
||||||
<div v-show="itemsMassCreation.length > 0">
|
|
||||||
<div class="text-right pr-4 mb-1">
|
|
||||||
<b-button @click="removeAllBookmarks()" variant="light">
|
|
||||||
<b-icon icon="x" scale="2" variant="danger"></b-icon>
|
|
||||||
|
|
||||||
{{ $t('remove_all') }}
|
|
||||||
</b-button>
|
|
||||||
</div>
|
|
||||||
<selected-users-table
|
|
||||||
class="shadow p-3 mb-5 bg-white rounded"
|
|
||||||
:items="itemsMassCreation"
|
|
||||||
:fields="fields"
|
|
||||||
@remove-item="removeItem"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="itemsMassCreation.length === 0">
|
|
||||||
{{ $t('multiple_creation_text') }}
|
|
||||||
</div>
|
|
||||||
<creation-formular
|
|
||||||
v-else
|
|
||||||
type="massCreation"
|
|
||||||
:creation="creation"
|
|
||||||
:items="itemsMassCreation"
|
|
||||||
@remove-all-bookmark="removeAllBookmarks"
|
|
||||||
@toast-failed-creations="toastFailedCreations"
|
|
||||||
/>
|
|
||||||
</b-col>
|
|
||||||
</b-row>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import CreationFormular from '../components/CreationFormular.vue'
|
|
||||||
import SelectUsersTable from '../components/Tables/SelectUsersTable.vue'
|
|
||||||
import SelectedUsersTable from '../components/Tables/SelectedUsersTable.vue'
|
|
||||||
import { searchUsers } from '../graphql/searchUsers'
|
|
||||||
import { creationMonths } from '../mixins/creationMonths'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Creation',
|
|
||||||
mixins: [creationMonths],
|
|
||||||
components: {
|
|
||||||
CreationFormular,
|
|
||||||
SelectUsersTable,
|
|
||||||
SelectedUsersTable,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showArrays: false,
|
|
||||||
itemsList: [],
|
|
||||||
itemsMassCreation: this.$store.state.userSelectedInMassCreation,
|
|
||||||
radioSelectedMass: '',
|
|
||||||
criteria: '',
|
|
||||||
rows: 0,
|
|
||||||
currentPage: 1,
|
|
||||||
perPage: 25,
|
|
||||||
now: Date.now(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
await this.getUsers()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async getUsers() {
|
|
||||||
this.$apollo
|
|
||||||
.query({
|
|
||||||
query: searchUsers,
|
|
||||||
variables: {
|
|
||||||
searchText: this.criteria,
|
|
||||||
currentPage: this.currentPage,
|
|
||||||
pageSize: this.perPage,
|
|
||||||
filters: {
|
|
||||||
byActivated: true,
|
|
||||||
byDeleted: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
this.rows = result.data.searchUsers.userCount
|
|
||||||
this.itemsList = result.data.searchUsers.userList.map((user) => {
|
|
||||||
return {
|
|
||||||
...user,
|
|
||||||
showDetails: false,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (this.itemsMassCreation.length !== 0) {
|
|
||||||
const selectedIndices = this.itemsMassCreation.map((item) => item.userId)
|
|
||||||
this.itemsList = this.itemsList.filter((item) => !selectedIndices.includes(item.userId))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toastError(error.message)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
pushItem(selectedItem) {
|
|
||||||
this.itemsMassCreation = [
|
|
||||||
this.itemsList.find((item) => selectedItem.userId === item.userId),
|
|
||||||
...this.itemsMassCreation,
|
|
||||||
]
|
|
||||||
this.itemsList = this.itemsList.filter((item) => selectedItem.userId !== item.userId)
|
|
||||||
this.$store.commit('setUserSelectedInMassCreation', this.itemsMassCreation)
|
|
||||||
},
|
|
||||||
removeItem(selectedItem) {
|
|
||||||
this.itemsList = [
|
|
||||||
this.itemsMassCreation.find((item) => selectedItem.userId === item.userId),
|
|
||||||
...this.itemsList,
|
|
||||||
]
|
|
||||||
this.itemsMassCreation = this.itemsMassCreation.filter(
|
|
||||||
(item) => selectedItem.userId !== item.userId,
|
|
||||||
)
|
|
||||||
this.$store.commit('setUserSelectedInMassCreation', this.itemsMassCreation)
|
|
||||||
},
|
|
||||||
removeAllBookmarks() {
|
|
||||||
this.itemsMassCreation = []
|
|
||||||
this.$store.commit('setUserSelectedInMassCreation', [])
|
|
||||||
this.getUsers()
|
|
||||||
},
|
|
||||||
toastFailedCreations(failedCreations) {
|
|
||||||
failedCreations.forEach((email) =>
|
|
||||||
this.toastError(this.$t('creation_form.creation_failed', { email })),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
Searchfields() {
|
|
||||||
return [
|
|
||||||
{ key: 'bookmark', label: 'bookmark' },
|
|
||||||
{ key: 'firstName', label: this.$t('firstname') },
|
|
||||||
{ key: 'lastName', label: this.$t('lastname') },
|
|
||||||
{
|
|
||||||
key: 'creation',
|
|
||||||
label: this.creationLabel,
|
|
||||||
formatter: (value, key, item) => {
|
|
||||||
return value.join(' | ')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ key: 'email', label: this.$t('e_mail') },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
fields() {
|
|
||||||
return [
|
|
||||||
{ key: 'email', label: this.$t('e_mail') },
|
|
||||||
{ key: 'firstName', label: this.$t('firstname') },
|
|
||||||
{ key: 'lastName', label: this.$t('lastname') },
|
|
||||||
{
|
|
||||||
key: 'creation',
|
|
||||||
label: this.creationLabel,
|
|
||||||
formatter: (value, key, item) => {
|
|
||||||
return value.join(' | ')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ key: 'bookmark', label: this.$t('remove') },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
currentPage() {
|
|
||||||
this.getUsers()
|
|
||||||
},
|
|
||||||
criteria() {
|
|
||||||
this.getUsers()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -271,7 +271,7 @@ describe('CreationConfirm', () => {
|
|||||||
|
|
||||||
describe('deny creation', () => {
|
describe('deny creation', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await wrapper.findAll('tr').at(1).findAll('button').at(2).trigger('click')
|
await wrapper.findAll('tr').at(1).findAll('button').at(1).trigger('click')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('opens the overlay', () => {
|
it('opens the overlay', () => {
|
||||||
|
|||||||
@ -45,7 +45,7 @@ describe('router', () => {
|
|||||||
|
|
||||||
describe('routes', () => {
|
describe('routes', () => {
|
||||||
it('has nine routes defined', () => {
|
it('has nine routes defined', () => {
|
||||||
expect(routes).toHaveLength(9)
|
expect(routes).toHaveLength(8)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('has "/overview" as default', async () => {
|
it('has "/overview" as default', async () => {
|
||||||
@ -67,13 +67,6 @@ describe('router', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('creation', () => {
|
|
||||||
it('loads the "Creation" component', async () => {
|
|
||||||
const component = await routes.find((r) => r.path === '/creation').component()
|
|
||||||
expect(component.default.name).toBe('Creation')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('creation-confirm', () => {
|
describe('creation-confirm', () => {
|
||||||
it('loads the "CreationConfirm" component', async () => {
|
it('loads the "CreationConfirm" component', async () => {
|
||||||
const component = await routes.find((r) => r.path === '/creation-confirm').component()
|
const component = await routes.find((r) => r.path === '/creation-confirm').component()
|
||||||
|
|||||||
@ -19,10 +19,6 @@ const routes = [
|
|||||||
path: '/user',
|
path: '/user',
|
||||||
component: () => import('@/pages/UserSearch.vue'),
|
component: () => import('@/pages/UserSearch.vue'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/creation',
|
|
||||||
component: () => import('@/pages/Creation.vue'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/creation-confirm',
|
path: '/creation-confirm',
|
||||||
component: () => import('@/pages/CreationConfirm.vue'),
|
component: () => import('@/pages/CreationConfirm.vue'),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-backend",
|
"name": "gradido-backend",
|
||||||
"version": "1.18.1",
|
"version": "1.18.2",
|
||||||
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
"description": "Gradido unified backend providing an API-Service for Gradido Transactions",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/backend",
|
"repository": "https://github.com/gradido/gradido/backend",
|
||||||
@ -15,7 +15,8 @@
|
|||||||
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
|
"lint": "eslint --max-warnings=0 --ext .js,.ts .",
|
||||||
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
|
"test": "cross-env TZ=UTC NODE_ENV=development jest --runInBand --coverage --forceExit --detectOpenHandles",
|
||||||
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
|
"seed": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/seeds/index.ts",
|
||||||
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts"
|
"klicktipp": "cross-env TZ=UTC NODE_ENV=development ts-node -r tsconfig-paths/register src/util/klicktipp.ts",
|
||||||
|
"locales": "scripts/sort.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hyperswarm/dht": "^6.2.0",
|
"@hyperswarm/dht": "^6.2.0",
|
||||||
|
|||||||
25
backend/scripts/sort.sh
Executable file
25
backend/scripts/sort.sh
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
ROOT_DIR=$(dirname "$0")/..
|
||||||
|
|
||||||
|
tmp=$(mktemp)
|
||||||
|
exit_code=0
|
||||||
|
|
||||||
|
for locale_file in $ROOT_DIR/src/locales/*.json
|
||||||
|
do
|
||||||
|
jq -f $(dirname "$0")/sort_filter.jq $locale_file > "$tmp"
|
||||||
|
if [ "$*" == "--fix" ]
|
||||||
|
then
|
||||||
|
mv "$tmp" $locale_file
|
||||||
|
else
|
||||||
|
if diff -q "$tmp" $locale_file > /dev/null ;
|
||||||
|
then
|
||||||
|
: # all good
|
||||||
|
else
|
||||||
|
exit_code=$?
|
||||||
|
echo "$(basename -- $locale_file) is not sorted by keys"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
exit $exit_code
|
||||||
13
backend/scripts/sort_filter.jq
Normal file
13
backend/scripts/sort_filter.jq
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
def walk(f):
|
||||||
|
. as $in
|
||||||
|
| if type == "object" then
|
||||||
|
reduce keys_unsorted[] as $key
|
||||||
|
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
|
||||||
|
elif type == "array" then map( walk(f) ) | f
|
||||||
|
else f
|
||||||
|
end;
|
||||||
|
|
||||||
|
def keys_sort_by(f):
|
||||||
|
to_entries | sort_by(.key|f ) | from_entries;
|
||||||
|
|
||||||
|
walk(if type == "object" then keys_sort_by(ascii_upcase) else . end)
|
||||||
@ -42,6 +42,7 @@ import { User } from '@entity/User'
|
|||||||
import { EventProtocolType } from '@/event/EventProtocolType'
|
import { EventProtocolType } from '@/event/EventProtocolType'
|
||||||
import { logger, i18n as localization } from '@test/testSetup'
|
import { logger, i18n as localization } from '@test/testSetup'
|
||||||
import { UserInputError } from 'apollo-server-express'
|
import { UserInputError } from 'apollo-server-express'
|
||||||
|
import { ContributionStatus } from '../enum/ContributionStatus'
|
||||||
|
|
||||||
// mock account activation email to avoid console spam
|
// mock account activation email to avoid console spam
|
||||||
jest.mock('@/emails/sendEmailVariants', () => {
|
jest.mock('@/emails/sendEmailVariants', () => {
|
||||||
@ -127,13 +128,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too short (5 characters minimum)')],
|
errors: [new GraphQLError('Memo text is too short')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith(`memo text is too short: memo.length=4 < 5`)
|
expect(logger.error).toBeCalledWith('Memo text is too short', 4)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws error when memo length greater than 255 chars', async () => {
|
it('throws error when memo length greater than 255 chars', async () => {
|
||||||
@ -150,13 +151,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too long (255 characters maximum)')],
|
errors: [new GraphQLError('Memo text is too long')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith(`memo text is too long: memo.length=259 > 255`)
|
expect(logger.error).toBeCalledWith('Memo text is too long', 259)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws error when creationDate not-valid', async () => {
|
it('throws error when creationDate not-valid', async () => {
|
||||||
@ -417,31 +418,6 @@ describe('ContributionResolver', () => {
|
|||||||
resetToken()
|
resetToken()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('wrong contribution id', () => {
|
|
||||||
it('throws an error', async () => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: updateContribution,
|
|
||||||
variables: {
|
|
||||||
contributionId: -1,
|
|
||||||
amount: 100.0,
|
|
||||||
memo: 'Test env contribution',
|
|
||||||
creationDate: new Date().toString(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
errors: [new GraphQLError('No contribution found to given id.')],
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('logs the error found', () => {
|
|
||||||
expect(logger.error).toBeCalledWith('No contribution found to given id')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Memo length smaller than 5 chars', () => {
|
describe('Memo length smaller than 5 chars', () => {
|
||||||
it('throws error', async () => {
|
it('throws error', async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
@ -458,13 +434,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too short (5 characters minimum)')],
|
errors: [new GraphQLError('Memo text is too short')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('memo text is too short: memo.length=4 < 5')
|
expect(logger.error).toBeCalledWith('Memo text is too short', 4)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -484,13 +460,38 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('memo text is too long (255 characters maximum)')],
|
errors: [new GraphQLError('Memo text is too long')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('memo text is too long: memo.length=259 > 255')
|
expect(logger.error).toBeCalledWith('Memo text is too long', 259)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('wrong contribution id', () => {
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateContribution,
|
||||||
|
variables: {
|
||||||
|
contributionId: -1,
|
||||||
|
amount: 100.0,
|
||||||
|
memo: 'Test env contribution',
|
||||||
|
creationDate: new Date().toString(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error found', () => {
|
||||||
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -516,18 +517,16 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [new GraphQLError('Can not update contribution of another user')],
|
||||||
new GraphQLError(
|
|
||||||
'user of the pending contribution and send user does not correspond',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'user of the pending contribution and send user does not correspond',
|
'Can not update contribution of another user',
|
||||||
|
expect.any(Object),
|
||||||
|
expect.any(Number),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -548,12 +547,64 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('An admin is not allowed to update a user contribution.')],
|
errors: [new GraphQLError('An admin is not allowed to update an user contribution')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO check that the error is logged (need to modify AdminResolver, avoid conflicts)
|
it('logs the error found', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'An admin is not allowed to update an user contribution',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('contribution has wrong status', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
const contribution = await Contribution.findOneOrFail({
|
||||||
|
id: result.data.createContribution.id,
|
||||||
|
})
|
||||||
|
contribution.contributionStatus = ContributionStatus.DELETED
|
||||||
|
contribution.save()
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const contribution = await Contribution.findOneOrFail({
|
||||||
|
id: result.data.createContribution.id,
|
||||||
|
})
|
||||||
|
contribution.contributionStatus = ContributionStatus.PENDING
|
||||||
|
contribution.save()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
await expect(
|
||||||
|
mutate({
|
||||||
|
mutation: updateContribution,
|
||||||
|
variables: {
|
||||||
|
contributionId: result.data.createContribution.id,
|
||||||
|
amount: 10.0,
|
||||||
|
memo: 'Test env contribution',
|
||||||
|
creationDate: new Date().toString(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('Contribution can not be updated due to status')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs the error found', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Contribution can not be updated due to status',
|
||||||
|
ContributionStatus.DELETED,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('update too much so that the limit is exceeded', () => {
|
describe('update too much so that the limit is exceeded', () => {
|
||||||
@ -610,16 +661,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Currently the month of the contribution cannot change.')],
|
errors: [new GraphQLError('Month of contribution can not be changed')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it.skip('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('Month of contribution can not be changed')
|
||||||
'No information for available creations with the given creationDate=',
|
|
||||||
'Invalid Date',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1153,6 +1201,7 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
describe('wrong contribution id', () => {
|
describe('wrong contribution id', () => {
|
||||||
it('returns an error', async () => {
|
it('returns an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: deleteContribution,
|
mutation: deleteContribution,
|
||||||
@ -1162,18 +1211,19 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Contribution not found for given id.')],
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('Contribution not found for given id')
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('other user sends a deleteContribution', () => {
|
describe('other user sends a deleteContribution', () => {
|
||||||
it('returns an error', async () => {
|
it('returns an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: login,
|
mutation: login,
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
@ -1193,7 +1243,11 @@ describe('ContributionResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('Can not delete contribution of another user')
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Can not delete contribution of another user',
|
||||||
|
expect.any(Object),
|
||||||
|
expect.any(Number),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1269,7 +1323,10 @@ describe('ContributionResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error found', () => {
|
it('logs the error found', () => {
|
||||||
expect(logger.error).toBeCalledWith('A confirmed contribution can not be deleted')
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'A confirmed contribution can not be deleted',
|
||||||
|
expect.objectContaining({ contributionStatus: 'CONFIRMED' }),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1535,15 +1592,13 @@ describe('ContributionResolver', () => {
|
|||||||
mutate({ mutation: adminCreateContribution, variables }),
|
mutate({ mutation: adminCreateContribution, variables }),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Could not find user with email: bibi@bloxberg.de')],
|
errors: [new GraphQLError('Could not find user')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('Could not find user', 'bibi@bloxberg.de')
|
||||||
'Could not find user with email: bibi@bloxberg.de',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1563,7 +1618,7 @@ describe('ContributionResolver', () => {
|
|||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
new GraphQLError('This user was deleted. Cannot create a contribution.'),
|
new GraphQLError('Cannot create contribution since the user was deleted'),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -1571,7 +1626,12 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'This user was deleted. Cannot create a contribution.',
|
'Cannot create contribution since the user was deleted',
|
||||||
|
expect.objectContaining({
|
||||||
|
user: expect.objectContaining({
|
||||||
|
deletedAt: new Date('2018-03-14T09:17:52.000Z'),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1592,7 +1652,9 @@ describe('ContributionResolver', () => {
|
|||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
new GraphQLError('Contribution could not be saved, Email is not activated'),
|
new GraphQLError(
|
||||||
|
'Cannot create contribution since the users email is not activated',
|
||||||
|
),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -1600,7 +1662,8 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'Contribution could not be saved, Email is not activated',
|
'Cannot create contribution since the users email is not activated',
|
||||||
|
expect.objectContaining({ emailChecked: false }),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1619,13 +1682,13 @@ describe('ContributionResolver', () => {
|
|||||||
mutate({ mutation: adminCreateContribution, variables }),
|
mutate({ mutation: adminCreateContribution, variables }),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError(`invalid Date for creationDate=invalid-date`)],
|
errors: [new GraphQLError('CreationDate is invalid')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(`invalid Date for creationDate=invalid-date`)
|
expect(logger.error).toBeCalledWith('CreationDate is invalid', 'invalid-date')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1821,17 +1884,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [new GraphQLError('Could not find User')],
|
||||||
new GraphQLError('Could not find UserContact with email: bob@baumeister.de'),
|
|
||||||
],
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith('Could not find User', 'bob@baumeister.de')
|
||||||
'Could not find UserContact with email: bob@baumeister.de',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1851,13 +1910,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('User was deleted (stephen@hawking.uk)')],
|
errors: [new GraphQLError('User was deleted')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('User was deleted (stephen@hawking.uk)')
|
expect(logger.error).toBeCalledWith('User was deleted', 'stephen@hawking.uk')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1877,13 +1936,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('No contribution found to given id.')],
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('No contribution found to given id.')
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1907,7 +1966,7 @@ describe('ContributionResolver', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [
|
errors: [
|
||||||
new GraphQLError(
|
new GraphQLError(
|
||||||
'user of the pending contribution and send user does not correspond',
|
'User of the pending contribution and send user does not correspond',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -1916,7 +1975,7 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith(
|
expect(logger.error).toBeCalledWith(
|
||||||
'user of the pending contribution and send user does not correspond',
|
'User of the pending contribution and send user does not correspond',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -2111,13 +2170,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Contribution not found for given id.')],
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('Contribution not found for given id: -1')
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2237,13 +2296,13 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Contribution not found to given id.')],
|
errors: [new GraphQLError('Contribution not found')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('logs the error thrown', () => {
|
it('logs the error thrown', () => {
|
||||||
expect(logger.error).toBeCalledWith('Contribution not found for given id: -1')
|
expect(logger.error).toBeCalledWith('Contribution not found', -1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2354,6 +2413,7 @@ describe('ContributionResolver', () => {
|
|||||||
|
|
||||||
describe('confirm same contribution again', () => {
|
describe('confirm same contribution again', () => {
|
||||||
it('throws an error', async () => {
|
it('throws an error', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: confirmContribution,
|
mutation: confirmContribution,
|
||||||
@ -2363,11 +2423,18 @@ describe('ContributionResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('Contribution already confirmd.')],
|
errors: [new GraphQLError('Contribution already confirmed')],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Contribution already confirmed',
|
||||||
|
expect.any(Number),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('confirm two creations one after the other quickly', () => {
|
describe('confirm two creations one after the other quickly', () => {
|
||||||
|
|||||||
@ -55,6 +55,7 @@ import {
|
|||||||
sendContributionDeniedEmail,
|
sendContributionDeniedEmail,
|
||||||
} from '@/emails/sendEmailVariants'
|
} from '@/emails/sendEmailVariants'
|
||||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
import { getLastTransaction } from './util/getLastTransaction'
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
@ -67,14 +68,11 @@ export class ContributionResolver {
|
|||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<UnconfirmedContribution> {
|
): Promise<UnconfirmedContribution> {
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (memo.length > MEMO_MAX_CHARS) {
|
|
||||||
logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`)
|
|
||||||
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
if (memo.length < MEMO_MIN_CHARS) {
|
||||||
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
|
throw new LogError('Memo text is too short', memo.length)
|
||||||
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
}
|
||||||
|
if (memo.length > MEMO_MAX_CHARS) {
|
||||||
|
throw new LogError('Memo text is too long', memo.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = new Event()
|
const event = new Event()
|
||||||
@ -116,16 +114,13 @@ export class ContributionResolver {
|
|||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error('Contribution not found for given id')
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found for given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.userId !== user.id) {
|
if (contribution.userId !== user.id) {
|
||||||
logger.error('Can not delete contribution of another user')
|
throw new LogError('Can not delete contribution of another user', contribution, user.id)
|
||||||
throw new Error('Can not delete contribution of another user')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error('A confirmed contribution can not be deleted')
|
throw new LogError('A confirmed contribution can not be deleted', contribution)
|
||||||
throw new Error('A confirmed contribution can not be deleted')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contribution.contributionStatus = ContributionStatus.DELETED
|
contribution.contributionStatus = ContributionStatus.DELETED
|
||||||
@ -220,14 +215,11 @@ export class ContributionResolver {
|
|||||||
@Ctx() context: Context,
|
@Ctx() context: Context,
|
||||||
): Promise<UnconfirmedContribution> {
|
): Promise<UnconfirmedContribution> {
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (memo.length > MEMO_MAX_CHARS) {
|
|
||||||
logger.error(`memo text is too long: memo.length=${memo.length} > ${MEMO_MAX_CHARS}`)
|
|
||||||
throw new Error(`memo text is too long (${MEMO_MAX_CHARS} characters maximum)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memo.length < MEMO_MIN_CHARS) {
|
if (memo.length < MEMO_MIN_CHARS) {
|
||||||
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
|
throw new LogError('Memo text is too short', memo.length)
|
||||||
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
|
}
|
||||||
|
if (memo.length > MEMO_MAX_CHARS) {
|
||||||
|
throw new LogError('Memo text is too long', memo.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = getUser(context)
|
const user = getUser(context)
|
||||||
@ -236,22 +228,22 @@ export class ContributionResolver {
|
|||||||
where: { id: contributionId, confirmedAt: IsNull(), deniedAt: IsNull() },
|
where: { id: contributionId, confirmedAt: IsNull(), deniedAt: IsNull() },
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error('No contribution found to given id')
|
throw new LogError('Contribution not found', contributionId)
|
||||||
throw new Error('No contribution found to given id.')
|
|
||||||
}
|
}
|
||||||
if (contributionToUpdate.userId !== user.id) {
|
if (contributionToUpdate.userId !== user.id) {
|
||||||
logger.error('user of the pending contribution and send user does not correspond')
|
throw new LogError(
|
||||||
throw new Error('user of the pending contribution and send user does not correspond')
|
'Can not update contribution of another user',
|
||||||
|
contributionToUpdate,
|
||||||
|
user.id,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
||||||
) {
|
) {
|
||||||
logger.error(
|
throw new LogError(
|
||||||
`Contribution can not be updated since the state is ${contributionToUpdate.contributionStatus}`,
|
'Contribution can not be updated due to status',
|
||||||
)
|
contributionToUpdate.contributionStatus,
|
||||||
throw new Error(
|
|
||||||
`Contribution can not be updated since the state is ${contributionToUpdate.contributionStatus}`,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const creationDateObj = new Date(creationDate)
|
const creationDateObj = new Date(creationDate)
|
||||||
@ -259,8 +251,7 @@ export class ContributionResolver {
|
|||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
||||||
} else {
|
} else {
|
||||||
logger.error('Currently the month of the contribution cannot change.')
|
throw new LogError('Month of contribution can not be changed')
|
||||||
throw new Error('Currently the month of the contribution cannot change.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all possible cases not to be true are thrown in this function
|
// all possible cases not to be true are thrown in this function
|
||||||
@ -311,29 +302,24 @@ export class ContributionResolver {
|
|||||||
)
|
)
|
||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
if (!isValidDateString(creationDate)) {
|
if (!isValidDateString(creationDate)) {
|
||||||
logger.error(`invalid Date for creationDate=${creationDate}`)
|
throw new LogError('CreationDate is invalid', creationDate)
|
||||||
throw new Error(`invalid Date for creationDate=${creationDate}`)
|
|
||||||
}
|
}
|
||||||
const emailContact = await UserContact.findOne({
|
const emailContact = await UserContact.findOne({
|
||||||
where: { email },
|
where: { email },
|
||||||
withDeleted: true,
|
withDeleted: true,
|
||||||
relations: ['user'],
|
relations: ['user'],
|
||||||
})
|
})
|
||||||
if (!emailContact) {
|
if (!emailContact || !emailContact.user) {
|
||||||
logger.error(`Could not find user with email: ${email}`)
|
throw new LogError('Could not find user', email)
|
||||||
throw new Error(`Could not find user with email: ${email}`)
|
|
||||||
}
|
}
|
||||||
if (emailContact.deletedAt) {
|
if (emailContact.deletedAt || emailContact.user.deletedAt) {
|
||||||
logger.error('This emailContact was deleted. Cannot create a contribution.')
|
throw new LogError('Cannot create contribution since the user was deleted', emailContact)
|
||||||
throw new Error('This emailContact was deleted. Cannot create a contribution.')
|
|
||||||
}
|
|
||||||
if (emailContact.user.deletedAt) {
|
|
||||||
logger.error('This user was deleted. Cannot create a contribution.')
|
|
||||||
throw new Error('This user was deleted. Cannot create a contribution.')
|
|
||||||
}
|
}
|
||||||
if (!emailContact.emailChecked) {
|
if (!emailContact.emailChecked) {
|
||||||
logger.error('Contribution could not be saved, Email is not activated')
|
throw new LogError(
|
||||||
throw new Error('Contribution could not be saved, Email is not activated')
|
'Cannot create contribution since the users email is not activated',
|
||||||
|
emailContact,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = new Event()
|
const event = new Event()
|
||||||
@ -406,18 +392,11 @@ export class ContributionResolver {
|
|||||||
withDeleted: true,
|
withDeleted: true,
|
||||||
relations: ['user'],
|
relations: ['user'],
|
||||||
})
|
})
|
||||||
if (!emailContact) {
|
if (!emailContact || !emailContact.user) {
|
||||||
logger.error(`Could not find UserContact with email: ${email}`)
|
throw new LogError('Could not find User', email)
|
||||||
throw new Error(`Could not find UserContact with email: ${email}`)
|
|
||||||
}
|
}
|
||||||
const user = emailContact.user
|
if (emailContact.deletedAt || emailContact.user.deletedAt) {
|
||||||
if (!user) {
|
throw new LogError('User was deleted', email)
|
||||||
logger.error(`Could not find User to emailContact: ${email}`)
|
|
||||||
throw new Error(`Could not find User to emailContact: ${email}`)
|
|
||||||
}
|
|
||||||
if (user.deletedAt) {
|
|
||||||
logger.error(`User was deleted (${email})`)
|
|
||||||
throw new Error(`User was deleted (${email})`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
@ -426,28 +405,25 @@ export class ContributionResolver {
|
|||||||
where: { id, confirmedAt: IsNull(), deniedAt: IsNull() },
|
where: { id, confirmedAt: IsNull(), deniedAt: IsNull() },
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error('No contribution found to given id.')
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('No contribution found to given id.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contributionToUpdate.userId !== user.id) {
|
if (contributionToUpdate.userId !== emailContact.user.id) {
|
||||||
logger.error('user of the pending contribution and send user does not correspond')
|
throw new LogError('User of the pending contribution and send user does not correspond')
|
||||||
throw new Error('user of the pending contribution and send user does not correspond')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contributionToUpdate.moderatorId === null) {
|
if (contributionToUpdate.moderatorId === null) {
|
||||||
logger.error('An admin is not allowed to update a user contribution.')
|
throw new LogError('An admin is not allowed to update an user contribution')
|
||||||
throw new Error('An admin is not allowed to update a user contribution.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const creationDateObj = new Date(creationDate)
|
const creationDateObj = new Date(creationDate)
|
||||||
let creations = await getUserCreation(user.id, clientTimezoneOffset)
|
let creations = await getUserCreation(emailContact.user.id, clientTimezoneOffset)
|
||||||
|
|
||||||
|
// TODO: remove this restriction
|
||||||
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
if (contributionToUpdate.contributionDate.getMonth() === creationDateObj.getMonth()) {
|
||||||
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
creations = updateCreations(creations, contributionToUpdate, clientTimezoneOffset)
|
||||||
} else {
|
} else {
|
||||||
logger.error('Currently the month of the contribution cannot change.')
|
throw new LogError('Month of contribution can not be changed')
|
||||||
throw new Error('Currently the month of the contribution cannot change.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all possible cases not to be true are thrown in this function
|
// all possible cases not to be true are thrown in this function
|
||||||
@ -465,11 +441,11 @@ export class ContributionResolver {
|
|||||||
result.memo = contributionToUpdate.memo
|
result.memo = contributionToUpdate.memo
|
||||||
result.date = contributionToUpdate.contributionDate
|
result.date = contributionToUpdate.contributionDate
|
||||||
|
|
||||||
result.creation = await getUserCreation(user.id, clientTimezoneOffset)
|
result.creation = await getUserCreation(emailContact.user.id, clientTimezoneOffset)
|
||||||
|
|
||||||
const event = new Event()
|
const event = new Event()
|
||||||
const eventAdminContributionUpdate = new EventAdminContributionUpdate()
|
const eventAdminContributionUpdate = new EventAdminContributionUpdate()
|
||||||
eventAdminContributionUpdate.userId = user.id
|
eventAdminContributionUpdate.userId = emailContact.user.id
|
||||||
eventAdminContributionUpdate.amount = amount
|
eventAdminContributionUpdate.amount = amount
|
||||||
eventAdminContributionUpdate.contributionId = contributionToUpdate.id
|
eventAdminContributionUpdate.contributionId = contributionToUpdate.id
|
||||||
await writeEvent(event.setEventAdminContributionUpdate(eventAdminContributionUpdate))
|
await writeEvent(event.setEventAdminContributionUpdate(eventAdminContributionUpdate))
|
||||||
@ -522,19 +498,17 @@ export class ContributionResolver {
|
|||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found for given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error('A confirmed contribution can not be deleted')
|
throw new LogError('A confirmed contribution can not be deleted')
|
||||||
throw new Error('A confirmed contribution can not be deleted')
|
|
||||||
}
|
}
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
if (
|
if (
|
||||||
contribution.contributionType === ContributionType.USER &&
|
contribution.contributionType === ContributionType.USER &&
|
||||||
contribution.userId === moderator.id
|
contribution.userId === moderator.id
|
||||||
) {
|
) {
|
||||||
throw new Error('Own contribution can not be deleted as admin')
|
throw new LogError('Own contribution can not be deleted as admin')
|
||||||
}
|
}
|
||||||
const user = await DbUser.findOneOrFail(
|
const user = await DbUser.findOneOrFail(
|
||||||
{ id: contribution.userId },
|
{ id: contribution.userId },
|
||||||
@ -576,29 +550,24 @@ export class ContributionResolver {
|
|||||||
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
const clientTimezoneOffset = getClientTimezoneOffset(context)
|
||||||
const contribution = await DbContribution.findOne(id)
|
const contribution = await DbContribution.findOne(id)
|
||||||
if (!contribution) {
|
if (!contribution) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error('Contribution not found to given id.')
|
|
||||||
}
|
}
|
||||||
if (contribution.confirmedAt) {
|
if (contribution.confirmedAt) {
|
||||||
logger.error(`Contribution already confirmd: ${id}`)
|
throw new LogError('Contribution already confirmed', id)
|
||||||
throw new Error('Contribution already confirmd.')
|
|
||||||
}
|
}
|
||||||
if (contribution.contributionStatus === 'DENIED') {
|
if (contribution.contributionStatus === 'DENIED') {
|
||||||
logger.error(`Contribution already denied: ${id}`)
|
throw new LogError('Contribution already denied', id)
|
||||||
throw new Error('Contribution already denied.')
|
|
||||||
}
|
}
|
||||||
const moderatorUser = getUser(context)
|
const moderatorUser = getUser(context)
|
||||||
if (moderatorUser.id === contribution.userId) {
|
if (moderatorUser.id === contribution.userId) {
|
||||||
logger.error('Moderator can not confirm own contribution')
|
throw new LogError('Moderator can not confirm own contribution')
|
||||||
throw new Error('Moderator can not confirm own contribution')
|
|
||||||
}
|
}
|
||||||
const user = await DbUser.findOneOrFail(
|
const user = await DbUser.findOneOrFail(
|
||||||
{ id: contribution.userId },
|
{ id: contribution.userId },
|
||||||
{ withDeleted: true, relations: ['emailContact'] },
|
{ withDeleted: true, relations: ['emailContact'] },
|
||||||
)
|
)
|
||||||
if (user.deletedAt) {
|
if (user.deletedAt) {
|
||||||
logger.error('This user was deleted. Cannot confirm a contribution.')
|
throw new LogError('Can not confirm contribution since the user was deleted')
|
||||||
throw new Error('This user was deleted. Cannot confirm a contribution.')
|
|
||||||
}
|
}
|
||||||
const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false)
|
const creations = await getUserCreation(contribution.userId, clientTimezoneOffset, false)
|
||||||
validateContribution(
|
validateContribution(
|
||||||
@ -662,8 +631,7 @@ export class ContributionResolver {
|
|||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
logger.error('Creation was not successful', e)
|
throw new LogError('Creation was not successful', e)
|
||||||
throw new Error('Creation was not successful.')
|
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
@ -738,17 +706,16 @@ export class ContributionResolver {
|
|||||||
deniedBy: IsNull(),
|
deniedBy: IsNull(),
|
||||||
})
|
})
|
||||||
if (!contributionToUpdate) {
|
if (!contributionToUpdate) {
|
||||||
logger.error(`Contribution not found for given id: ${id}`)
|
throw new LogError('Contribution not found', id)
|
||||||
throw new Error(`Contribution not found for given id.`)
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
contributionToUpdate.contributionStatus !== ContributionStatus.IN_PROGRESS &&
|
||||||
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
contributionToUpdate.contributionStatus !== ContributionStatus.PENDING
|
||||||
) {
|
) {
|
||||||
logger.error(
|
throw new LogError(
|
||||||
`Contribution state (${contributionToUpdate.contributionStatus}) is not allowed.`,
|
'Status of the contribution is not allowed',
|
||||||
|
contributionToUpdate.contributionStatus,
|
||||||
)
|
)
|
||||||
throw new Error(`State of the contribution is not allowed.`)
|
|
||||||
}
|
}
|
||||||
const moderator = getUser(context)
|
const moderator = getUser(context)
|
||||||
const user = await DbUser.findOne(
|
const user = await DbUser.findOne(
|
||||||
@ -756,10 +723,7 @@ export class ContributionResolver {
|
|||||||
{ relations: ['emailContact'] },
|
{ relations: ['emailContact'] },
|
||||||
)
|
)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.error(
|
throw new LogError('Could not find User of the Contribution', contributionToUpdate.userId)
|
||||||
`Could not find User for the Contribution (userId: ${contributionToUpdate.userId}).`,
|
|
||||||
)
|
|
||||||
throw new Error('Could not find User for the Contribution.')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contributionToUpdate.contributionStatus = ContributionStatus.DENIED
|
contributionToUpdate.contributionStatus = ContributionStatus.DENIED
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
redeemTransactionLink,
|
redeemTransactionLink,
|
||||||
createContribution,
|
createContribution,
|
||||||
updateContribution,
|
updateContribution,
|
||||||
|
createTransactionLink,
|
||||||
} from '@/seeds/graphql/mutations'
|
} from '@/seeds/graphql/mutations'
|
||||||
import { listTransactionLinksAdmin } from '@/seeds/graphql/queries'
|
import { listTransactionLinksAdmin } from '@/seeds/graphql/queries'
|
||||||
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
import { ContributionLink as DbContributionLink } from '@entity/ContributionLink'
|
||||||
@ -24,6 +25,7 @@ import { UnconfirmedContribution } from '@model/UnconfirmedContribution'
|
|||||||
import Decimal from 'decimal.js-light'
|
import Decimal from 'decimal.js-light'
|
||||||
import { GraphQLError } from 'graphql'
|
import { GraphQLError } from 'graphql'
|
||||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
import { logger } from '@test/testSetup'
|
||||||
|
|
||||||
// mock semaphore to allow use fake timers
|
// mock semaphore to allow use fake timers
|
||||||
jest.mock('@/util/TRANSACTIONS_LOCK')
|
jest.mock('@/util/TRANSACTIONS_LOCK')
|
||||||
@ -50,7 +52,75 @@ afterAll(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('TransactionLinkResolver', () => {
|
describe('TransactionLinkResolver', () => {
|
||||||
|
describe('createTransactionLink', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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('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))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('redeemTransactionLink', () => {
|
describe('redeemTransactionLink', () => {
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
describe('contributionLink', () => {
|
describe('contributionLink', () => {
|
||||||
describe('input not valid', () => {
|
describe('input not valid', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@ -61,6 +131,7 @@ describe('TransactionLinkResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('throws error when link does not exists', async () => {
|
it('throws error when link does not exists', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: redeemTransactionLink,
|
mutation: redeemTransactionLink,
|
||||||
@ -69,16 +140,26 @@ describe('TransactionLinkResolver', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchObject({
|
).resolves.toMatchObject({
|
||||||
errors: [
|
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||||
new GraphQLError(
|
|
||||||
'Creation from contribution link was not successful. Error: No contribution link found to given code: CL-123456',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 () => {
|
it('throws error when link is not valid yet', async () => {
|
||||||
const now = new Date()
|
jest.clearAllMocks()
|
||||||
const {
|
const {
|
||||||
data: { createContributionLink: contributionLink },
|
data: { createContributionLink: contributionLink },
|
||||||
} = await mutate({
|
} = await mutate({
|
||||||
@ -88,7 +169,7 @@ describe('TransactionLinkResolver', () => {
|
|||||||
name: 'Daily Contribution Link',
|
name: 'Daily Contribution Link',
|
||||||
memo: 'Thank you for contribute daily to the community',
|
memo: 'Thank you for contribute daily to the community',
|
||||||
cycle: 'DAILY',
|
cycle: 'DAILY',
|
||||||
validFrom: new Date(now.getFullYear() + 1, 0, 1).toISOString(),
|
validFrom: validFrom.toISOString(),
|
||||||
validTo: new Date(now.getFullYear() + 1, 11, 31, 23, 59, 59, 999).toISOString(),
|
validTo: new Date(now.getFullYear() + 1, 11, 31, 23, 59, 59, 999).toISOString(),
|
||||||
maxAmountPerMonth: new Decimal(200),
|
maxAmountPerMonth: new Decimal(200),
|
||||||
maxPerCycle: 1,
|
maxPerCycle: 1,
|
||||||
@ -102,16 +183,21 @@ describe('TransactionLinkResolver', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchObject({
|
).resolves.toMatchObject({
|
||||||
errors: [
|
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||||
new GraphQLError(
|
|
||||||
'Creation from contribution link was not successful. Error: Contribution link not valid yet',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
await resetEntity(DbContributionLink)
|
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 () => {
|
it('throws error when contributionLink cycle is invalid', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const {
|
const {
|
||||||
data: { createContributionLink: contributionLink },
|
data: { createContributionLink: contributionLink },
|
||||||
@ -136,17 +222,22 @@ describe('TransactionLinkResolver', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchObject({
|
).resolves.toMatchObject({
|
||||||
errors: [
|
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||||
new GraphQLError(
|
|
||||||
'Creation from contribution link was not successful. Error: Contribution link has unknown cycle',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
await resetEntity(DbContributionLink)
|
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 () => {
|
it('throws error when link is no longer valid', async () => {
|
||||||
const now = new Date()
|
jest.clearAllMocks()
|
||||||
const {
|
const {
|
||||||
data: { createContributionLink: contributionLink },
|
data: { createContributionLink: contributionLink },
|
||||||
} = await mutate({
|
} = await mutate({
|
||||||
@ -157,7 +248,7 @@ describe('TransactionLinkResolver', () => {
|
|||||||
memo: 'Thank you for contribute daily to the community',
|
memo: 'Thank you for contribute daily to the community',
|
||||||
cycle: 'DAILY',
|
cycle: 'DAILY',
|
||||||
validFrom: new Date(now.getFullYear() - 1, 0, 1).toISOString(),
|
validFrom: new Date(now.getFullYear() - 1, 0, 1).toISOString(),
|
||||||
validTo: new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 999).toISOString(),
|
validTo: validTo.toISOString(),
|
||||||
maxAmountPerMonth: new Decimal(200),
|
maxAmountPerMonth: new Decimal(200),
|
||||||
maxPerCycle: 1,
|
maxPerCycle: 1,
|
||||||
},
|
},
|
||||||
@ -170,14 +261,18 @@ describe('TransactionLinkResolver', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchObject({
|
).resolves.toMatchObject({
|
||||||
errors: [
|
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||||
new GraphQLError(
|
|
||||||
'Creation from contribution link was not successful. Error: Contribution link is no longer valid',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
await resetEntity(DbContributionLink)
|
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
|
// TODO: have this test separated into a transactionLink and a contributionLink part
|
||||||
@ -250,6 +345,7 @@ describe('TransactionLinkResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('does not allow the user to redeem the contribution link', async () => {
|
it('does not allow the user to redeem the contribution link', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: redeemTransactionLink,
|
mutation: redeemTransactionLink,
|
||||||
@ -258,13 +354,18 @@ describe('TransactionLinkResolver', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchObject({
|
).resolves.toMatchObject({
|
||||||
errors: [
|
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||||
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.',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
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.',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('user has no pending contributions that would not allow to redeem the link', () => {
|
describe('user has no pending contributions that would not allow to redeem the link', () => {
|
||||||
@ -301,6 +402,7 @@ describe('TransactionLinkResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('does not allow the user to redeem the contribution link a second time on the same day', async () => {
|
it('does not allow the user to redeem the contribution link a second time on the same day', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: redeemTransactionLink,
|
mutation: redeemTransactionLink,
|
||||||
@ -309,14 +411,17 @@ describe('TransactionLinkResolver', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchObject({
|
).resolves.toMatchObject({
|
||||||
errors: [
|
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||||
new GraphQLError(
|
|
||||||
'Creation from contribution link was not successful. Error: Contribution link already redeemed today',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Creation from contribution link was not successful',
|
||||||
|
new Error('Contribution link already redeemed today'),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
describe('after one day', () => {
|
describe('after one day', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
jest.useFakeTimers()
|
jest.useFakeTimers()
|
||||||
@ -349,6 +454,7 @@ describe('TransactionLinkResolver', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('does not allow the user to redeem the contribution link a second time on the same day', async () => {
|
it('does not allow the user to redeem the contribution link a second time on the same day', async () => {
|
||||||
|
jest.clearAllMocks()
|
||||||
await expect(
|
await expect(
|
||||||
mutate({
|
mutate({
|
||||||
mutation: redeemTransactionLink,
|
mutation: redeemTransactionLink,
|
||||||
@ -357,33 +463,65 @@ describe('TransactionLinkResolver', () => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
).resolves.toMatchObject({
|
).resolves.toMatchObject({
|
||||||
errors: [
|
errors: [new GraphQLError('Creation from contribution link was not successful')],
|
||||||
new GraphQLError(
|
|
||||||
'Creation from contribution link was not successful. Error: Contribution link already redeemed today',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('logs the error thrown', () => {
|
||||||
|
expect(logger.error).toBeCalledWith(
|
||||||
|
'Creation from contribution link was not successful',
|
||||||
|
new Error('Contribution link already redeemed today'),
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('transaction links list', () => {
|
describe('listTransactionLinksAdmin', () => {
|
||||||
const variables = {
|
const variables = {
|
||||||
userId: 1, // dummy, may be replaced
|
userId: 1, // dummy, may be replaced
|
||||||
filters: null,
|
filters: null,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: 5,
|
pageSize: 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: there is a test not cleaning up after itself! Fix it!
|
afterAll(async () => {
|
||||||
beforeAll(async () => {
|
await cleanDB()
|
||||||
await cleanDB()
|
resetToken()
|
||||||
resetToken()
|
})
|
||||||
|
|
||||||
|
describe('unauthenticated', () => {
|
||||||
|
it('returns an error', async () => {
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: listTransactionLinksAdmin,
|
||||||
|
variables,
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
errors: [new GraphQLError('401 Unauthorized')],
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('authenticated', () => {
|
||||||
|
describe('without admin rights', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
describe('unauthenticated', () => {
|
|
||||||
it('returns an error', async () => {
|
it('returns an error', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
@ -398,22 +536,40 @@ describe('TransactionLinkResolver', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('authenticated', () => {
|
describe('with admin rights', () => {
|
||||||
describe('without admin rights', () => {
|
beforeAll(async () => {
|
||||||
beforeAll(async () => {
|
// admin 'peter@lustig.de' has to exists for 'creationFactory'
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
await userFactory(testEnv, peterLustig)
|
||||||
await mutate({
|
|
||||||
mutation: login,
|
|
||||||
variables: { email: 'bibi@bloxberg.de', password: 'Aa12345_' },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
user = await userFactory(testEnv, bibiBloxberg)
|
||||||
await cleanDB()
|
variables.userId = user.id
|
||||||
resetToken()
|
variables.pageSize = 25
|
||||||
})
|
// bibi needs GDDs
|
||||||
|
const bibisCreation = creations.find((creation) => creation.email === 'bibi@bloxberg.de')
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
await creationFactory(testEnv, bibisCreation!)
|
||||||
|
// bibis transaktion links
|
||||||
|
const bibisTransaktionLinks = transactionLinks.filter(
|
||||||
|
(transactionLink) => transactionLink.email === 'bibi@bloxberg.de',
|
||||||
|
)
|
||||||
|
for (let i = 0; i < bibisTransaktionLinks.length; i++) {
|
||||||
|
await transactionLinkFactory(testEnv, bibisTransaktionLinks[i])
|
||||||
|
}
|
||||||
|
|
||||||
it('returns an error', async () => {
|
// admin: only now log in
|
||||||
|
await mutate({
|
||||||
|
mutation: login,
|
||||||
|
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await cleanDB()
|
||||||
|
resetToken()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('without any filters', () => {
|
||||||
|
it('finds 6 open transaction links and no deleted or redeemed', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: listTransactionLinksAdmin,
|
query: listTransactionLinksAdmin,
|
||||||
@ -421,219 +577,169 @@ describe('TransactionLinkResolver', () => {
|
|||||||
}),
|
}),
|
||||||
).resolves.toEqual(
|
).resolves.toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
errors: [new GraphQLError('401 Unauthorized')],
|
data: {
|
||||||
|
listTransactionLinksAdmin: {
|
||||||
|
linkCount: 6,
|
||||||
|
linkList: expect.not.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
memo: 'Da habe ich mich wohl etwas übernommen.',
|
||||||
|
deletedAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with admin rights', () => {
|
describe('all filters are null', () => {
|
||||||
beforeAll(async () => {
|
it('finds 6 open transaction links and no deleted or redeemed', async () => {
|
||||||
// admin 'peter@lustig.de' has to exists for 'creationFactory'
|
await expect(
|
||||||
await userFactory(testEnv, peterLustig)
|
query({
|
||||||
|
query: listTransactionLinksAdmin,
|
||||||
user = await userFactory(testEnv, bibiBloxberg)
|
variables: {
|
||||||
variables.userId = user.id
|
...variables,
|
||||||
variables.pageSize = 25
|
filters: {
|
||||||
// bibi needs GDDs
|
withDeleted: null,
|
||||||
const bibisCreation = creations.find(
|
withExpired: null,
|
||||||
(creation) => creation.email === 'bibi@bloxberg.de',
|
withRedeemed: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
listTransactionLinksAdmin: {
|
||||||
|
linkCount: 6,
|
||||||
|
linkList: expect.not.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
memo: 'Da habe ich mich wohl etwas übernommen.',
|
||||||
|
deletedAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
})
|
||||||
await creationFactory(testEnv, bibisCreation!)
|
})
|
||||||
// bibis transaktion links
|
|
||||||
const bibisTransaktionLinks = transactionLinks.filter(
|
describe('filter with deleted', () => {
|
||||||
(transactionLink) => transactionLink.email === 'bibi@bloxberg.de',
|
it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => {
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: listTransactionLinksAdmin,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
filters: {
|
||||||
|
withDeleted: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
listTransactionLinksAdmin: {
|
||||||
|
linkCount: 7,
|
||||||
|
linkList: expect.arrayContaining([
|
||||||
|
expect.not.objectContaining({
|
||||||
|
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
memo: 'Da habe ich mich wohl etwas übernommen.',
|
||||||
|
deletedAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
for (let i = 0; i < bibisTransaktionLinks.length; i++) {
|
|
||||||
await transactionLinkFactory(testEnv, bibisTransaktionLinks[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// admin: only now log in
|
|
||||||
await mutate({
|
|
||||||
mutation: login,
|
|
||||||
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
describe('filter by expired', () => {
|
||||||
await cleanDB()
|
it('finds 5 open transaction links, 1 expired, and no redeemed', async () => {
|
||||||
resetToken()
|
await expect(
|
||||||
|
query({
|
||||||
|
query: listTransactionLinksAdmin,
|
||||||
|
variables: {
|
||||||
|
...variables,
|
||||||
|
filters: {
|
||||||
|
withExpired: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
listTransactionLinksAdmin: {
|
||||||
|
linkCount: 7,
|
||||||
|
linkList: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
expect.not.objectContaining({
|
||||||
|
memo: 'Da habe ich mich wohl etwas übernommen.',
|
||||||
|
deletedAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('without any filters', () => {
|
// TODO: works not as expected, because 'redeemedAt' and 'redeemedBy' have to be added to the transaktion link factory
|
||||||
it('finds 6 open transaction links and no deleted or redeemed', async () => {
|
describe.skip('filter by redeemed', () => {
|
||||||
await expect(
|
it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => {
|
||||||
query({
|
await expect(
|
||||||
query: listTransactionLinksAdmin,
|
query({
|
||||||
variables,
|
query: listTransactionLinksAdmin,
|
||||||
}),
|
variables: {
|
||||||
).resolves.toEqual(
|
...variables,
|
||||||
expect.objectContaining({
|
filters: {
|
||||||
data: {
|
withDeleted: null,
|
||||||
listTransactionLinksAdmin: {
|
withExpired: null,
|
||||||
linkCount: 6,
|
withRedeemed: true,
|
||||||
linkList: expect.not.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
memo: 'Da habe ich mich wohl etwas übernommen.',
|
|
||||||
deletedAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
)
|
}),
|
||||||
})
|
).resolves.toEqual(
|
||||||
})
|
expect.objectContaining({
|
||||||
|
data: {
|
||||||
describe('all filters are null', () => {
|
listTransactionLinksAdmin: {
|
||||||
it('finds 6 open transaction links and no deleted or redeemed', async () => {
|
linkCount: 6,
|
||||||
await expect(
|
linkList: expect.arrayContaining([
|
||||||
query({
|
expect.not.objectContaining({
|
||||||
query: listTransactionLinksAdmin,
|
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
||||||
variables: {
|
createdAt: expect.any(String),
|
||||||
...variables,
|
}),
|
||||||
filters: {
|
expect.objectContaining({
|
||||||
withDeleted: null,
|
memo: 'Yeah, eingelöst!',
|
||||||
withExpired: null,
|
redeemedAt: expect.any(String),
|
||||||
withRedeemed: null,
|
redeemedBy: expect.any(Number),
|
||||||
},
|
}),
|
||||||
|
expect.not.objectContaining({
|
||||||
|
memo: 'Da habe ich mich wohl etwas übernommen.',
|
||||||
|
deletedAt: expect.any(String),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
).resolves.toEqual(
|
}),
|
||||||
expect.objectContaining({
|
)
|
||||||
data: {
|
|
||||||
listTransactionLinksAdmin: {
|
|
||||||
linkCount: 6,
|
|
||||||
linkList: expect.not.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
memo: 'Da habe ich mich wohl etwas übernommen.',
|
|
||||||
deletedAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('filter with deleted', () => {
|
|
||||||
it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => {
|
|
||||||
await expect(
|
|
||||||
query({
|
|
||||||
query: listTransactionLinksAdmin,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
filters: {
|
|
||||||
withDeleted: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
listTransactionLinksAdmin: {
|
|
||||||
linkCount: 7,
|
|
||||||
linkList: expect.arrayContaining([
|
|
||||||
expect.not.objectContaining({
|
|
||||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
memo: 'Da habe ich mich wohl etwas übernommen.',
|
|
||||||
deletedAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('filter by expired', () => {
|
|
||||||
it('finds 5 open transaction links, 1 expired, and no redeemed', async () => {
|
|
||||||
await expect(
|
|
||||||
query({
|
|
||||||
query: listTransactionLinksAdmin,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
filters: {
|
|
||||||
withExpired: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
listTransactionLinksAdmin: {
|
|
||||||
linkCount: 7,
|
|
||||||
linkList: expect.arrayContaining([
|
|
||||||
expect.objectContaining({
|
|
||||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
expect.not.objectContaining({
|
|
||||||
memo: 'Da habe ich mich wohl etwas übernommen.',
|
|
||||||
deletedAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: works not as expected, because 'redeemedAt' and 'redeemedBy' have to be added to the transaktion link factory
|
|
||||||
describe.skip('filter by redeemed', () => {
|
|
||||||
it('finds 6 open transaction links, 1 deleted, and no redeemed', async () => {
|
|
||||||
await expect(
|
|
||||||
query({
|
|
||||||
query: listTransactionLinksAdmin,
|
|
||||||
variables: {
|
|
||||||
...variables,
|
|
||||||
filters: {
|
|
||||||
withDeleted: null,
|
|
||||||
withExpired: null,
|
|
||||||
withRedeemed: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toEqual(
|
|
||||||
expect.objectContaining({
|
|
||||||
data: {
|
|
||||||
listTransactionLinksAdmin: {
|
|
||||||
linkCount: 6,
|
|
||||||
linkList: expect.arrayContaining([
|
|
||||||
expect.not.objectContaining({
|
|
||||||
memo: 'Leider wollte niemand meine Gradidos zum Neujahr haben :(',
|
|
||||||
createdAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
expect.objectContaining({
|
|
||||||
memo: 'Yeah, eingelöst!',
|
|
||||||
redeemedAt: expect.any(String),
|
|
||||||
redeemedBy: expect.any(Number),
|
|
||||||
}),
|
|
||||||
expect.not.objectContaining({
|
|
||||||
memo: 'Da habe ich mich wohl etwas übernommen.',
|
|
||||||
deletedAt: expect.any(String),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import { getUserCreation, validateContribution } from './util/creations'
|
|||||||
import { executeTransaction } from './TransactionResolver'
|
import { executeTransaction } from './TransactionResolver'
|
||||||
import QueryLinkResult from '@union/QueryLinkResult'
|
import QueryLinkResult from '@union/QueryLinkResult'
|
||||||
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
import { TRANSACTIONS_LOCK } from '@/util/TRANSACTIONS_LOCK'
|
||||||
|
import LogError from '@/server/LogError'
|
||||||
|
|
||||||
import { getLastTransaction } from './util/getLastTransaction'
|
import { getLastTransaction } from './util/getLastTransaction'
|
||||||
|
|
||||||
@ -65,12 +66,16 @@ export class TransactionLinkResolver {
|
|||||||
const createdDate = new Date()
|
const createdDate = new Date()
|
||||||
const validUntil = transactionLinkExpireDate(createdDate)
|
const validUntil = transactionLinkExpireDate(createdDate)
|
||||||
|
|
||||||
|
if (amount.lessThanOrEqualTo(0)) {
|
||||||
|
throw new LogError('Amount must be a positive number', amount)
|
||||||
|
}
|
||||||
|
|
||||||
const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay)
|
const holdAvailableAmount = amount.minus(calculateDecay(amount, createdDate, validUntil).decay)
|
||||||
|
|
||||||
// validate amount
|
// validate amount
|
||||||
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
const sendBalance = await calculateBalance(user.id, holdAvailableAmount.mul(-1), createdDate)
|
||||||
if (!sendBalance) {
|
if (!sendBalance) {
|
||||||
throw new Error("user hasn't enough GDD or amount is < 0")
|
throw new LogError('User has not enough GDD', user.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
const transactionLink = DbTransactionLink.create()
|
const transactionLink = DbTransactionLink.create()
|
||||||
@ -186,24 +191,15 @@ export class TransactionLinkResolver {
|
|||||||
.where('contributionLink.code = :code', { code: code.replace('CL-', '') })
|
.where('contributionLink.code = :code', { code: code.replace('CL-', '') })
|
||||||
.getOne()
|
.getOne()
|
||||||
if (!contributionLink) {
|
if (!contributionLink) {
|
||||||
logger.error('no contribution link found to given code:', code)
|
throw new LogError('No contribution link found to given code', code)
|
||||||
throw new Error(`No contribution link found to given code: ${code}`)
|
|
||||||
}
|
}
|
||||||
logger.info('...contribution link found with id', contributionLink.id)
|
logger.info('...contribution link found with id', contributionLink.id)
|
||||||
if (new Date(contributionLink.validFrom).getTime() > now.getTime()) {
|
if (new Date(contributionLink.validFrom).getTime() > now.getTime()) {
|
||||||
logger.error(
|
throw new LogError('Contribution link is not valid yet', contributionLink.validFrom)
|
||||||
'contribution link is not valid yet. Valid from: ',
|
|
||||||
contributionLink.validFrom,
|
|
||||||
)
|
|
||||||
throw new Error('Contribution link not valid yet')
|
|
||||||
}
|
}
|
||||||
if (contributionLink.validTo) {
|
if (contributionLink.validTo) {
|
||||||
if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) {
|
if (new Date(contributionLink.validTo).setHours(23, 59, 59) < now.getTime()) {
|
||||||
logger.error(
|
throw new LogError('Contribution link is no longer valid', contributionLink.validTo)
|
||||||
'contribution link is no longer valid. Valid to: ',
|
|
||||||
contributionLink.validTo,
|
|
||||||
)
|
|
||||||
throw new Error('Contribution link is no longer valid')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let alreadyRedeemed: DbContribution | undefined
|
let alreadyRedeemed: DbContribution | undefined
|
||||||
@ -219,11 +215,7 @@ export class TransactionLinkResolver {
|
|||||||
})
|
})
|
||||||
.getOne()
|
.getOne()
|
||||||
if (alreadyRedeemed) {
|
if (alreadyRedeemed) {
|
||||||
logger.error(
|
throw new LogError('Contribution link already redeemed', user.id)
|
||||||
'contribution link with rule ONCE already redeemed by user with id',
|
|
||||||
user.id,
|
|
||||||
)
|
|
||||||
throw new Error('Contribution link already redeemed')
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -248,17 +240,12 @@ export class TransactionLinkResolver {
|
|||||||
)
|
)
|
||||||
.getOne()
|
.getOne()
|
||||||
if (alreadyRedeemed) {
|
if (alreadyRedeemed) {
|
||||||
logger.error(
|
throw new LogError('Contribution link already redeemed today', user.id)
|
||||||
'contribution link with rule DAILY already redeemed by user with id',
|
|
||||||
user.id,
|
|
||||||
)
|
|
||||||
throw new Error('Contribution link already redeemed today')
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
logger.error('contribution link has unknown cycle', contributionLink.cycle)
|
throw new LogError('Contribution link has unknown cycle', contributionLink.cycle)
|
||||||
throw new Error('Contribution link has unknown cycle')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,8 +295,7 @@ export class TransactionLinkResolver {
|
|||||||
logger.info('creation from contribution link commited successfuly.')
|
logger.info('creation from contribution link commited successfuly.')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await queryRunner.rollbackTransaction()
|
await queryRunner.rollbackTransaction()
|
||||||
logger.error(`Creation from contribution link was not successful: ${e}`)
|
throw new LogError('Creation from contribution link was not successful', e)
|
||||||
throw new Error(`Creation from contribution link was not successful. ${e}`)
|
|
||||||
} finally {
|
} finally {
|
||||||
await queryRunner.release()
|
await queryRunner.release()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
{
|
{
|
||||||
"emails": {
|
"emails": {
|
||||||
"addedContributionMessage": {
|
|
||||||
"commonGoodContributionMessage": "du hast zu deinem Gemeinwohl-Beitrag „{contributionMemo}“ eine Nachricht von {senderFirstName} {senderLastName} erhalten.",
|
|
||||||
"subject": "Gradido: Nachricht zu deinem Gemeinwohl-Beitrag",
|
|
||||||
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
|
||||||
},
|
|
||||||
"accountActivation": {
|
"accountActivation": {
|
||||||
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
|
"duration": "Der Link hat eine Gültigkeit von {hours} Stunden und {minutes} Minuten. Sollte die Gültigkeit des Links bereits abgelaufen sein, kannst du dir hier einen neuen Link schicken lassen:",
|
||||||
"emailRegistered": "deine E-Mail-Adresse wurde soeben bei Gradido registriert.",
|
"emailRegistered": "deine E-Mail-Adresse wurde soeben bei Gradido registriert.",
|
||||||
@ -19,6 +14,11 @@
|
|||||||
"onForgottenPasswordCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
|
"onForgottenPasswordCopyLink": "oder kopiere den obigen Link in dein Browserfenster.",
|
||||||
"subject": "Gradido: Erneuter Registrierungsversuch mit deiner E-Mail"
|
"subject": "Gradido: Erneuter Registrierungsversuch mit deiner E-Mail"
|
||||||
},
|
},
|
||||||
|
"addedContributionMessage": {
|
||||||
|
"commonGoodContributionMessage": "du hast zu deinem Gemeinwohl-Beitrag „{contributionMemo}“ eine Nachricht von {senderFirstName} {senderLastName} erhalten.",
|
||||||
|
"subject": "Gradido: Nachricht zu deinem Gemeinwohl-Beitrag",
|
||||||
|
"toSeeAndAnswerMessage": "Um die Nachricht zu sehen und darauf zu antworten, gehe in deinem Gradido-Konto ins Menü „Gemeinschaft“ auf den Tab „Meine Beiträge zum Gemeinwohl“!"
|
||||||
|
},
|
||||||
"contributionConfirmed": {
|
"contributionConfirmed": {
|
||||||
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.",
|
"commonGoodContributionConfirmed": "dein Gemeinwohl-Beitrag „{contributionMemo}“ wurde soeben von {senderFirstName} {senderLastName} bestätigt und in deinem Gradido-Konto gutgeschrieben.",
|
||||||
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde bestätigt"
|
"subject": "Gradido: Dein Gemeinwohl-Beitrag wurde bestätigt"
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
{
|
{
|
||||||
"emails": {
|
"emails": {
|
||||||
"addedContributionMessage": {
|
|
||||||
"commonGoodContributionMessage": "you have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
|
|
||||||
"subject": "Gradido: Message about your common good contribution",
|
|
||||||
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
|
||||||
},
|
|
||||||
"accountActivation": {
|
"accountActivation": {
|
||||||
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
|
"duration": "The link has a validity of {hours} hours and {minutes} minutes. If the validity of the link has already expired, you can have a new link sent to you here:",
|
||||||
"emailRegistered": "Your email address has just been registered with Gradido.",
|
"emailRegistered": "Your email address has just been registered with Gradido.",
|
||||||
@ -19,6 +14,11 @@
|
|||||||
"onForgottenPasswordCopyLink": "or copy the link above into your browser window.",
|
"onForgottenPasswordCopyLink": "or copy the link above into your browser window.",
|
||||||
"subject": "Gradido: Try To Register Again With Your Email"
|
"subject": "Gradido: Try To Register Again With Your Email"
|
||||||
},
|
},
|
||||||
|
"addedContributionMessage": {
|
||||||
|
"commonGoodContributionMessage": "you have received a message from {senderFirstName} {senderLastName} regarding your common good contribution “{contributionMemo}”.",
|
||||||
|
"subject": "Gradido: Message about your common good contribution",
|
||||||
|
"toSeeAndAnswerMessage": "To view and reply to the message, go to the “Community” menu in your Gradido account and click on the “My contributions to the common good” tab!"
|
||||||
|
},
|
||||||
"contributionConfirmed": {
|
"contributionConfirmed": {
|
||||||
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
|
"commonGoodContributionConfirmed": "Your public good contribution “{contributionMemo}” has just been confirmed by {senderFirstName} {senderLastName} and credited to your Gradido account.",
|
||||||
"subject": "Gradido: Your contribution to the common good was confirmed"
|
"subject": "Gradido: Your contribution to the common good was confirmed"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido-database",
|
"name": "gradido-database",
|
||||||
"version": "1.18.1",
|
"version": "1.18.2",
|
||||||
"description": "Gradido Database Tool to execute database migrations",
|
"description": "Gradido Database Tool to execute database migrations",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": "https://github.com/gradido/gradido/database",
|
"repository": "https://github.com/gradido/gradido/database",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "bootstrap-vue-gradido-wallet",
|
"name": "bootstrap-vue-gradido-wallet",
|
||||||
"version": "1.18.1",
|
"version": "1.18.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node run/server.js",
|
"start": "node run/server.js",
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="decayinformation-startblock">
|
<div class="decayinformation-startblock">
|
||||||
|
<div class="my-4">
|
||||||
|
<div class="font-weight-bold pb-2">{{ $t('form.memo') }}</div>
|
||||||
|
<div>{{ memo }}</div>
|
||||||
|
</div>
|
||||||
<div class="mt-3 mb-3 text-center">
|
<div class="mt-3 mb-3 text-center">
|
||||||
<b>{{ $t('decay.before_startblock_transaction') }}</b>
|
<b>{{ $t('decay.before_startblock_transaction') }}</b>
|
||||||
</div>
|
</div>
|
||||||
@ -8,5 +12,11 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'DecayInformation-StartBlock',
|
name: 'DecayInformation-StartBlock',
|
||||||
|
props: {
|
||||||
|
memo: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="decay-information-box">
|
<div class="decay-information-box">
|
||||||
<decay-information-before-startblock v-if="decay.start === null" />
|
<decay-information-before-startblock v-if="decay.start === null" :memo="memo" />
|
||||||
<decay-information-decay-startblock
|
<decay-information-decay-startblock
|
||||||
v-else-if="isStartBlock"
|
v-else-if="isStartBlock"
|
||||||
:amount="amount"
|
:amount="amount"
|
||||||
|
|||||||
@ -179,6 +179,17 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
disabled() {
|
||||||
|
if (
|
||||||
|
this.form.email.length > 5 &&
|
||||||
|
parseInt(this.form.amount) <= parseInt(this.balance) &&
|
||||||
|
this.form.memo.length > 5 &&
|
||||||
|
this.form.memo.length <= 255
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
isBalanceDisabled() {
|
isBalanceDisabled() {
|
||||||
return this.balance <= 0 ? 'disabled' : false
|
return this.balance <= 0 ? 'disabled' : false
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "gradido",
|
"name": "gradido",
|
||||||
"version": "1.18.1",
|
"version": "1.18.2",
|
||||||
"description": "Gradido",
|
"description": "Gradido",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": "git@github.com:gradido/gradido.git",
|
"repository": "git@github.com:gradido/gradido.git",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user