Merge branch 'master' into 2343-feature-gradido-is-free-worldwide-on-startpage-slider

This commit is contained in:
mahula 2022-11-10 11:42:08 +01:00 committed by GitHub
commit 26284aaef8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 526 additions and 152 deletions

View File

@ -20,10 +20,10 @@ ENV PORT="8080"
# Labels
LABEL org.label-schema.build-date="${BUILD_DATE}"
LABEL org.label-schema.name="gradido:admin"
LABEL org.label-schema.description="Gradido Vue Admin Interface"
LABEL org.label-schema.usage="https://github.com/gradido/gradido/admin/README.md"
LABEL org.label-schema.description="Gradido Admin Interface"
LABEL org.label-schema.usage="https://github.com/gradido/gradido/blob/master/README.md"
LABEL org.label-schema.url="https://gradido.net"
LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido/backend"
LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido/tree/master/admin"
LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}"
LABEL org.label-schema.vendor="gradido Community"
LABEL org.label-schema.version="${BUILD_VERSION}"

View File

@ -53,6 +53,7 @@
"vuex-persistedstate": "^4.1.0"
},
"devDependencies": {
"@apollo/client": "^3.7.1",
"@babel/eslint-parser": "^7.15.8",
"@intlify/eslint-plugin-vue-i18n": "^1.4.0",
"@vue/cli-plugin-babel": "~4.5.0",
@ -71,6 +72,7 @@
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-vue": "^7.20.0",
"mock-apollo-client": "^1.2.1",
"postcss": "^8.4.8",
"postcss-html": "^1.3.0",
"postcss-scss": "^4.0.3",

View File

@ -46,5 +46,10 @@ describe('ContributionLink', () => {
wrapper.vm.editContributionLinkData()
expect(wrapper.vm.$root.$emit('bv::toggle::collapse', 'newContribution')).toBeTruthy()
})
it('emits toggle::collapse close Contribution-Form ', async () => {
wrapper.vm.closeContributionForm()
expect(wrapper.vm.$root.$emit('bv::toggle::collapse', 'newContribution')).toBeTruthy()
})
})
})

View File

@ -8,7 +8,11 @@
header-class="text-center"
class="mt-5"
>
<b-button v-b-toggle.newContribution class="my-3 d-flex justify-content-left">
<b-button
v-if="!editContributionLink"
v-b-toggle.newContribution
class="my-3 d-flex justify-content-left"
>
{{ $t('math.plus') }} {{ $t('contributionLink.newContributionLink') }}
</b-button>
@ -17,7 +21,9 @@
<p class="h2 ml-5">{{ $t('contributionLink.contributionLinks') }}</p>
<contribution-link-form
:contributionLinkData="contributionLinkData"
:editContributionLink="editContributionLink"
@get-contribution-links="$emit('get-contribution-links')"
@closeContributionForm="closeContributionForm"
/>
</b-card>
</b-collapse>
@ -58,12 +64,23 @@ export default {
return {
visible: false,
contributionLinkData: {},
editContributionLink: false,
}
},
methods: {
closeContributionForm() {
if (this.visible) {
this.$root.$emit('bv::toggle::collapse', 'newContribution')
this.editContributionLink = false
this.contributionLinkData = {}
}
},
editContributionLinkData(data) {
if (!this.visible) this.$root.$emit('bv::toggle::collapse', 'newContribution')
if (!this.visible) {
this.$root.$emit('bv::toggle::collapse', 'newContribution')
}
this.contributionLinkData = data
this.editContributionLink = true
},
},
}

View File

@ -9,6 +9,7 @@ global.alert = jest.fn()
const propsData = {
contributionLinkData: {},
editContributionLink: false,
}
const apolloMutateMock = jest.fn().mockResolvedValue()
@ -108,6 +109,7 @@ describe('ContributionLinkForm', () => {
cycle: 'ONCE',
maxPerCycle: 1,
maxAmountPerMonth: '0',
id: null,
},
})
})

View File

@ -6,6 +6,7 @@
<b-col>
<b-form-group :label="$t('contributionLink.validFrom')">
<b-form-datepicker
reset-button
v-model="form.validFrom"
size="lg"
:min="min"
@ -19,6 +20,7 @@
<b-col>
<b-form-group :label="$t('contributionLink.validTo')">
<b-form-datepicker
reset-button
v-model="form.validTo"
size="lg"
:min="form.validFrom ? form.validFrom : min"
@ -102,16 +104,25 @@
</b-form-group>
-->
<div class="mt-6">
<b-button type="submit" variant="primary">{{ $t('contributionLink.create') }}</b-button>
<b-button type="submit" variant="primary">
{{
editContributionLink ? $t('contributionLink.saveChange') : $t('contributionLink.create')
}}
</b-button>
<b-button type="reset" variant="danger" @click.prevent="onReset">
{{ $t('contributionLink.clear') }}
</b-button>
<b-button @click.prevent="$emit('closeContributionForm')">
{{ $t('contributionLink.close') }}
</b-button>
</div>
</b-form>
</div>
</template>
<script>
import { createContributionLink } from '@/graphql/createContributionLink.js'
import { updateContributionLink } from '@/graphql/updateContributionLink.js'
export default {
name: 'ContributionLinkForm',
props: {
@ -121,6 +132,7 @@ export default {
return {}
},
},
editContributionLink: { type: Boolean, required: true },
},
data() {
return {
@ -157,23 +169,24 @@ export default {
if (this.form.validFrom === null)
return this.toastError(this.$t('contributionLink.noStartDate'))
if (this.form.validTo === null) return this.toastError(this.$t('contributionLink.noEndDate'))
const variables = {
...this.form,
id: this.contributionLinkData.id ? this.contributionLinkData.id : null,
}
this.$apollo
.mutate({
mutation: createContributionLink,
variables: {
validFrom: this.form.validFrom,
validTo: this.form.validTo,
name: this.form.name,
amount: this.form.amount,
memo: this.form.memo,
cycle: this.form.cycle,
maxPerCycle: this.form.maxPerCycle,
maxAmountPerMonth: this.form.maxAmountPerMonth,
},
mutation: this.editContributionLink ? updateContributionLink : createContributionLink,
variables: variables,
})
.then((result) => {
this.link = result.data.createContributionLink.link
this.toastSuccess(this.link)
const link = this.editContributionLink
? result.data.updateContributionLink.link
: result.data.createContributionLink.link
this.toastSuccess(
this.editContributionLink ? this.$t('contributionLink.changeSaved') : link,
)
this.onReset()
this.$root.$emit('bv::toggle::collapse', 'newContribution')
this.$emit('get-contribution-links')
@ -195,14 +208,7 @@ export default {
},
watch: {
contributionLinkData() {
this.form.name = this.contributionLinkData.name
this.form.memo = this.contributionLinkData.memo
this.form.amount = this.contributionLinkData.amount
this.form.validFrom = this.contributionLinkData.validFrom
this.form.validTo = this.contributionLinkData.validTo
this.form.cycle = this.contributionLinkData.cycle
this.form.maxPerCycle = this.contributionLinkData.maxPerCycle
this.form.maxAmountPerMonth = this.contributionLinkData.maxAmountPerMonth
this.form = this.contributionLinkData
},
},
}

View File

@ -118,12 +118,11 @@ export default {
},
methods: {
updateCreationData(data) {
this.creationUserData = data
// this.creationUserData.amount = data.amount
// this.creationUserData.date = data.date
// this.creationUserData.memo = data.memo
// this.creationUserData.moderator = data.moderator
data.row.toggleDetails()
const row = data.row
this.$emit('update-contributions', data)
delete data.row
this.creationUserData = { ...this.creationUserData, ...data }
row.toggleDetails()
},
updateUserData(rowItem, newCreation) {
rowItem.creation = newCreation

View File

@ -0,0 +1,40 @@
import gql from 'graphql-tag'
export const updateContributionLink = gql`
mutation (
$amount: Decimal!
$name: String!
$memo: String!
$cycle: String!
$validFrom: String
$validTo: String
$maxAmountPerMonth: Decimal
$maxPerCycle: Int! = 1
$id: Int!
) {
updateContributionLink(
amount: $amount
name: $name
memo: $memo
cycle: $cycle
validFrom: $validFrom
validTo: $validTo
maxAmountPerMonth: $maxAmountPerMonth
maxPerCycle: $maxPerCycle
id: $id
) {
id
amount
name
memo
code
link
createdAt
validFrom
validTo
maxAmountPerMonth
cycle
maxPerCycle
}
}
`

View File

@ -3,7 +3,9 @@
"back": "zurück",
"contributionLink": {
"amount": "Betrag",
"changeSaved": "Änderungen gespeichert",
"clear": "Löschen",
"close": "Schließen",
"contributionLinks": "Beitragslinks",
"create": "Anlegen",
"cycle": "Zyklus",
@ -23,6 +25,7 @@
"once": "einmalig"
}
},
"saveChange": "Änderungen speichern",
"validFrom": "Startdatum",
"validTo": "Enddatum"
},

View File

@ -3,7 +3,9 @@
"back": "back",
"contributionLink": {
"amount": "Amount",
"changeSaved": "Changes saved",
"clear": "Clear",
"close": "Close",
"contributionLinks": "Contribution Links",
"create": "Create",
"cycle": "Cycle",
@ -23,6 +25,7 @@
"once": "once"
}
},
"saveChange": "Save Changes",
"validFrom": "Start-date",
"validTo": "End-Date"
},

View File

@ -1,42 +1,22 @@
import { mount } from '@vue/test-utils'
import CreationConfirm from './CreationConfirm.vue'
import { adminDeleteContribution } from '../graphql/adminDeleteContribution'
import { listUnconfirmedContributions } from '../graphql/listUnconfirmedContributions'
import { confirmContribution } from '../graphql/confirmContribution'
import { toastErrorSpy, toastSuccessSpy } from '../../test/testSetup'
import VueApollo from 'vue-apollo'
import { createMockClient } from 'mock-apollo-client'
const mockClient = createMockClient()
const apolloProvider = new VueApollo({
defaultClient: mockClient,
})
const localVue = global.localVue
const storeCommitMock = jest.fn()
const apolloQueryMock = jest.fn().mockResolvedValue({
data: {
listUnconfirmedContributions: [
{
id: 1,
firstName: 'Bibi',
lastName: 'Bloxberg',
userId: 99,
email: 'bibi@bloxberg.de',
amount: 500,
memo: 'Danke für alles',
date: new Date(),
moderator: 1,
},
{
id: 2,
firstName: 'Räuber',
lastName: 'Hotzenplotz',
userId: 100,
email: 'raeuber@hotzenplotz.de',
amount: 1000000,
memo: 'Gut Ergattert',
date: new Date(),
moderator: 1,
},
],
},
})
localVue.use(VueApollo)
const apolloMutateMock = jest.fn().mockResolvedValue({})
const storeCommitMock = jest.fn()
const mocks = {
$t: jest.fn((t) => t),
@ -53,17 +33,69 @@ const mocks = {
},
},
},
$apollo: {
query: apolloQueryMock,
mutate: apolloMutateMock,
},
}
const defaultData = () => {
return {
listUnconfirmedContributions: [
{
id: 1,
firstName: 'Bibi',
lastName: 'Bloxberg',
userId: 99,
email: 'bibi@bloxberg.de',
amount: 500,
memo: 'Danke für alles',
date: new Date(),
moderator: 1,
state: 'PENDING',
creation: [500, 500, 500],
messageCount: 0,
},
{
id: 2,
firstName: 'Räuber',
lastName: 'Hotzenplotz',
userId: 100,
email: 'raeuber@hotzenplotz.de',
amount: 1000000,
memo: 'Gut Ergattert',
date: new Date(),
moderator: 1,
state: 'PENDING',
creation: [500, 500, 500],
messageCount: 0,
},
],
}
}
describe('CreationConfirm', () => {
let wrapper
const listUnconfirmedContributionsMock = jest.fn()
const adminDeleteContributionMock = jest.fn()
const confirmContributionMock = jest.fn()
mockClient.setRequestHandler(
listUnconfirmedContributions,
listUnconfirmedContributionsMock
.mockRejectedValueOnce({ message: 'Ouch!' })
.mockResolvedValue({ data: defaultData() }),
)
mockClient.setRequestHandler(
adminDeleteContribution,
adminDeleteContributionMock.mockResolvedValue({ data: { adminDeleteContribution: true } }),
)
mockClient.setRequestHandler(
confirmContribution,
confirmContributionMock.mockResolvedValue({ data: { confirmContribution: true } }),
)
const Wrapper = () => {
return mount(CreationConfirm, { localVue, mocks })
return mount(CreationConfirm, { localVue, mocks, apolloProvider })
}
describe('mount', () => {
@ -72,12 +104,20 @@ describe('CreationConfirm', () => {
wrapper = Wrapper()
})
it('has a DIV element with the class.creation-confirm', () => {
expect(wrapper.find('div.creation-confirm').exists()).toBeTruthy()
describe('server response for get pending creations is error', () => {
it('toast an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Ouch!')
})
})
it('has two pending creations', () => {
expect(wrapper.vm.pendingCreations).toHaveLength(2)
describe('server response is succes', () => {
it('has a DIV element with the class.creation-confirm', () => {
expect(wrapper.find('div.creation-confirm').exists()).toBeTruthy()
})
it('has two pending creations', () => {
expect(wrapper.vm.pendingCreations).toHaveLength(2)
})
})
describe('store', () => {
@ -105,10 +145,7 @@ describe('CreationConfirm', () => {
})
it('calls the adminDeleteContribution mutation', () => {
expect(apolloMutateMock).toBeCalledWith({
mutation: adminDeleteContribution,
variables: { id: 1 },
})
expect(adminDeleteContributionMock).toBeCalledWith({ id: 1 })
})
it('commits openCreationsMinus to store', () => {
@ -128,7 +165,7 @@ describe('CreationConfirm', () => {
})
it('does not call the adminDeleteContribution mutation', () => {
expect(apolloMutateMock).not.toBeCalled()
expect(adminDeleteContributionMock).not.toBeCalled()
})
})
})
@ -139,7 +176,7 @@ describe('CreationConfirm', () => {
beforeEach(async () => {
spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm')
spy.mockImplementation(() => Promise.resolve('some value'))
apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' })
adminDeleteContributionMock.mockRejectedValue({ message: 'Ouchhh!' })
await wrapper.findAll('tr').at(1).findAll('button').at(0).trigger('click')
})
@ -150,7 +187,6 @@ describe('CreationConfirm', () => {
describe('confirm creation with success', () => {
beforeEach(async () => {
apolloMutateMock.mockResolvedValue({})
await wrapper.findAll('tr').at(2).findAll('button').at(2).trigger('click')
})
@ -179,10 +215,7 @@ describe('CreationConfirm', () => {
})
it('calls the confirmContribution mutation', () => {
expect(apolloMutateMock).toBeCalledWith({
mutation: confirmContribution,
variables: { id: 2 },
})
expect(confirmContributionMock).toBeCalledWith({ id: 2 })
})
it('commits openCreationsMinus to store', () => {
@ -200,7 +233,7 @@ describe('CreationConfirm', () => {
describe('confirm creation with error', () => {
beforeEach(async () => {
apolloMutateMock.mockRejectedValue({ message: 'Ouchhh!' })
confirmContributionMock.mockRejectedValue({ message: 'Ouchhh!' })
await wrapper.find('#overlay').findAll('button').at(1).trigger('click')
})
@ -210,19 +243,5 @@ describe('CreationConfirm', () => {
})
})
})
describe('server response for get pending creations is error', () => {
beforeEach(() => {
jest.clearAllMocks()
apolloQueryMock.mockRejectedValue({
message: 'Ouch!',
})
wrapper = Wrapper()
})
it('toast an error message', () => {
expect(toastErrorSpy).toBeCalledWith('Ouch!')
})
})
})
})

View File

@ -10,6 +10,7 @@
@remove-creation="removeCreation"
@show-overlay="showOverlay"
@update-state="updateState"
@update-contributions="$apollo.queries.PendingContributions.refetch()"
/>
</div>
</template>
@ -71,21 +72,6 @@ export default {
this.toastError(error.message)
})
},
getPendingCreations() {
this.$apollo
.query({
query: listUnconfirmedContributions,
fetchPolicy: 'network-only',
})
.then((result) => {
this.$store.commit('resetOpenCreations')
this.pendingCreations = result.data.listUnconfirmedContributions
this.$store.commit('setOpenCreations', result.data.listUnconfirmedContributions.length)
})
.catch((error) => {
this.toastError(error.message)
})
},
updatePendingCreations(id) {
this.pendingCreations = this.pendingCreations.filter((obj) => obj.id !== id)
this.$store.commit('openCreationsMinus', 1)
@ -127,8 +113,24 @@ export default {
]
},
},
async created() {
await this.getPendingCreations()
apollo: {
PendingContributions: {
query() {
return listUnconfirmedContributions
},
variables() {
// may be at some point we need a pagination here
return {}
},
update({ listUnconfirmedContributions }) {
this.$store.commit('resetOpenCreations')
this.pendingCreations = listUnconfirmedContributions
this.$store.commit('setOpenCreations', listUnconfirmedContributions.length)
},
error({ message }) {
this.toastError(message)
},
},
},
}
</script>

View File

@ -2,6 +2,25 @@
# yarn lockfile v1
"@apollo/client@^3.7.1":
version "3.7.1"
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.7.1.tgz#86ce47c18a0714e229231148b0306562550c2248"
integrity sha512-xu5M/l7p9gT9Fx7nF3AQivp0XukjB7TM7tOd5wifIpI8RskYveL4I+rpTijzWrnqCPZabkbzJKH7WEAKdctt9w==
dependencies:
"@graphql-typed-document-node/core" "^3.1.1"
"@wry/context" "^0.7.0"
"@wry/equality" "^0.5.0"
"@wry/trie" "^0.3.0"
graphql-tag "^2.12.6"
hoist-non-react-statics "^3.3.2"
optimism "^0.16.1"
prop-types "^15.7.2"
response-iterator "^0.2.6"
symbol-observable "^4.0.0"
ts-invariant "^0.10.3"
tslib "^2.3.0"
zen-observable-ts "^1.2.5"
"@babel/code-frame@7.12.11":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
@ -1030,6 +1049,11 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@graphql-typed-document-node/core@^3.1.1":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.1.tgz#076d78ce99822258cf813ecc1e7fa460fa74d052"
integrity sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@ -2419,6 +2443,20 @@
"@types/node" ">=6"
tslib "^1.9.3"
"@wry/context@^0.6.0":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.6.1.tgz#c3c29c0ad622adb00f6a53303c4f965ee06ebeb2"
integrity sha512-LOmVnY1iTU2D8tv4Xf6MVMZZ+juIJ87Kt/plMijjN20NMAXGmH4u8bS1t0uT74cZ5gwpocYueV58YwyI8y+GKw==
dependencies:
tslib "^2.3.0"
"@wry/context@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.7.0.tgz#be88e22c0ddf62aeb0ae9f95c3d90932c619a5c8"
integrity sha512-LcDAiYWRtwAoSOArfk7cuYvFXytxfVrdX7yxoUmK7pPITLk5jYh2F8knCwS7LjgYL8u1eidPlKKV6Ikqq0ODqQ==
dependencies:
tslib "^2.3.0"
"@wry/equality@^0.1.2":
version "0.1.11"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.11.tgz#35cb156e4a96695aa81a9ecc4d03787bc17f1790"
@ -2426,6 +2464,20 @@
dependencies:
tslib "^1.9.3"
"@wry/equality@^0.5.0":
version "0.5.3"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.5.3.tgz#fafebc69561aa2d40340da89fa7dc4b1f6fb7831"
integrity sha512-avR+UXdSrsF2v8vIqIgmeTY0UR91UT+IyablCyKe/uk22uOJ8fusKZnH9JH9e1/EtLeNJBtagNmL3eJdnOV53g==
dependencies:
tslib "^2.3.0"
"@wry/trie@^0.3.0":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.3.2.tgz#a06f235dc184bd26396ba456711f69f8c35097e6"
integrity sha512-yRTyhWSls2OY/pYLfwff867r8ekooZ4UI+/gxot5Wj8EFwSf2rG+n+Mo/6LoLQm1TKA4GRj2+LCpbfS937dClQ==
dependencies:
tslib "^2.3.0"
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@ -6704,6 +6756,13 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graphql-tag@^2.12.6:
version "2.12.6"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==
dependencies:
tslib "^2.1.0"
graphql-tag@^2.4.2:
version "2.12.5"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.5.tgz#5cff974a67b417747d05c8d9f5f3cb4495d0db8f"
@ -6880,6 +6939,13 @@ hmac-drbg@^1.0.1:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.1"
hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
dependencies:
react-is "^16.7.0"
homedir-polyfill@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
@ -9174,7 +9240,7 @@ lolex@^5.0.0:
dependencies:
"@sinonjs/commons" "^1.7.0"
loose-envify@^1.0.0:
loose-envify@^1.0.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -9498,6 +9564,11 @@ mkdirp@0.x, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
mock-apollo-client@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/mock-apollo-client/-/mock-apollo-client-1.2.1.tgz#e3bfdc3ff73b1fea28fa7e91ec82e43ba8cbfa39"
integrity sha512-QYQ6Hxo+t7hard1bcHHbsHxlNQYTQsaMNsm2Psh/NbwLMi2R4tGzplJKt97MUWuARHMq3GHB4PTLj/gxej4Caw==
moo-color@^1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.3.tgz#d56435f8359c8284d83ac58016df7427febece74"
@ -9987,6 +10058,14 @@ optimism@^0.10.0:
dependencies:
"@wry/context" "^0.4.0"
optimism@^0.16.1:
version "0.16.1"
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.1.tgz#7c8efc1f3179f18307b887e18c15c5b7133f6e7d"
integrity sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg==
dependencies:
"@wry/context" "^0.6.0"
"@wry/trie" "^0.3.0"
optionator@^0.8.1:
version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
@ -10901,6 +10980,15 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.7.2:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.13.1"
proto-list@~1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
@ -11080,7 +11168,7 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
react-is@^16.8.4:
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.4:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -11423,6 +11511,11 @@ resolve@1.x, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.14.2,
is-core-module "^2.2.0"
path-parse "^1.0.6"
response-iterator@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/response-iterator/-/response-iterator-0.2.6.tgz#249005fb14d2e4eeb478a3f735a28fd8b4c9f3da"
integrity sha512-pVzEEzrsg23Sh053rmDUvLSkGXluZio0qu8VT6ukrYuvtjVfCbDZH9d6PGXb8HZfzdNZt8feXv/jvUzlhRgLnw==
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@ -12446,6 +12539,11 @@ symbol-observable@^1.0.2:
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
symbol-observable@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205"
integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==
symbol-tree@^3.2.2, symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
@ -12727,6 +12825,13 @@ tryer@^1.0.1:
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
ts-invariant@^0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.10.3.tgz#3e048ff96e91459ffca01304dbc7f61c1f642f6c"
integrity sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==
dependencies:
tslib "^2.1.0"
ts-invariant@^0.4.0:
version "0.4.4"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86"
@ -12785,6 +12890,11 @@ tslib@^2.1.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tslib@^2.3.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
@ -13844,7 +13954,14 @@ zen-observable-ts@^0.8.21:
tslib "^1.9.3"
zen-observable "^0.8.0"
zen-observable@^0.8.0:
zen-observable-ts@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-1.2.5.tgz#6c6d9ea3d3a842812c6e9519209365a122ba8b58"
integrity sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==
dependencies:
zen-observable "0.8.15"
zen-observable@0.8.15, zen-observable@^0.8.0:
version "0.8.15"
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15"
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==

View File

@ -139,6 +139,7 @@ describe('AdminResolver', () => {
describe('user to get a new role does not exist', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: setUserRole, variables: { userId: admin.id + 1, isAdmin: true } }),
).resolves.toEqual(
@ -195,6 +196,7 @@ describe('AdminResolver', () => {
describe('change role with error', () => {
describe('is own role', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: setUserRole, variables: { userId: admin.id, isAdmin: false } }),
).resolves.toEqual(
@ -211,6 +213,7 @@ describe('AdminResolver', () => {
describe('user has already role to be set', () => {
describe('to admin', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await mutate({
mutation: setUserRole,
variables: { userId: user.id, isAdmin: true },
@ -231,6 +234,7 @@ describe('AdminResolver', () => {
describe('to usual user', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await mutate({
mutation: setUserRole,
variables: { userId: user.id, isAdmin: false },
@ -307,6 +311,7 @@ describe('AdminResolver', () => {
describe('user to be deleted does not exist', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: deleteUser, variables: { userId: admin.id + 1 } }),
).resolves.toEqual(
@ -323,6 +328,7 @@ describe('AdminResolver', () => {
describe('delete self', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: deleteUser, variables: { userId: admin.id } }),
).resolves.toEqual(
@ -356,6 +362,7 @@ describe('AdminResolver', () => {
describe('delete deleted user', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: deleteUser, variables: { userId: user.id } }),
).resolves.toEqual(
@ -427,6 +434,7 @@ describe('AdminResolver', () => {
describe('user to be undelete does not exist', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: unDeleteUser, variables: { userId: admin.id + 1 } }),
).resolves.toEqual(
@ -447,6 +455,7 @@ describe('AdminResolver', () => {
})
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: unDeleteUser, variables: { userId: user.id } }),
).resolves.toEqual(
@ -939,6 +948,7 @@ describe('AdminResolver', () => {
describe('user to create for does not exist', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: adminCreateContribution, variables }),
).resolves.toEqual(
@ -962,6 +972,7 @@ describe('AdminResolver', () => {
})
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: adminCreateContribution, variables }),
).resolves.toEqual(
@ -987,6 +998,7 @@ describe('AdminResolver', () => {
})
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: adminCreateContribution, variables }),
).resolves.toEqual(
@ -1013,6 +1025,7 @@ describe('AdminResolver', () => {
describe('date of creation is not a date string', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({ mutation: adminCreateContribution, variables }),
).resolves.toEqual(
@ -1034,6 +1047,7 @@ describe('AdminResolver', () => {
describe('date of creation is four months ago', () => {
it('throws an error', async () => {
jest.clearAllMocks()
const now = new Date()
variables.creationDate = new Date(
now.getFullYear(),
@ -1061,6 +1075,7 @@ describe('AdminResolver', () => {
describe('date of creation is in the future', () => {
it('throws an error', async () => {
jest.clearAllMocks()
const now = new Date()
variables.creationDate = new Date(
now.getFullYear(),
@ -1088,6 +1103,7 @@ describe('AdminResolver', () => {
describe('amount of creation is too high', () => {
it('throws an error', async () => {
jest.clearAllMocks()
variables.creationDate = new Date().toString()
await expect(
mutate({ mutation: adminCreateContribution, variables }),
@ -1213,6 +1229,7 @@ describe('AdminResolver', () => {
describe('user for creation to update does not exist', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminUpdateContribution,
@ -1242,6 +1259,7 @@ describe('AdminResolver', () => {
describe('user for creation to update is deleted', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminUpdateContribution,
@ -1267,6 +1285,7 @@ describe('AdminResolver', () => {
describe('creation does not exist', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminUpdateContribution,
@ -1292,6 +1311,7 @@ describe('AdminResolver', () => {
describe('user email does not match creation user', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminUpdateContribution,
@ -1326,6 +1346,7 @@ describe('AdminResolver', () => {
describe('creation update is not valid', () => {
// as this test has not clearly defined that date, it is a false positive
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminUpdateContribution,
@ -1502,6 +1523,7 @@ describe('AdminResolver', () => {
describe('adminDeleteContribution', () => {
describe('creation id does not exist', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminDeleteContribution,
@ -1538,6 +1560,7 @@ describe('AdminResolver', () => {
})
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminDeleteContribution,
@ -1583,6 +1606,7 @@ describe('AdminResolver', () => {
describe('confirmContribution', () => {
describe('creation does not exits', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: confirmContribution,

View File

@ -63,6 +63,7 @@ import ContributionMessageArgs from '@arg/ContributionMessageArgs'
import { ContributionMessageType } from '@enum/MessageType'
import { ContributionMessage } from '@model/ContributionMessage'
import { sendContributionConfirmedEmail } from '@/mailer/sendContributionConfirmedEmail'
import { sendContributionRejectedEmail } from '@/mailer/sendContributionRejectedEmail'
import { sendAddedContributionMessageEmail } from '@/mailer/sendAddedContributionMessageEmail'
import { eventProtocol } from '@/event/EventProtocolEmitter'
import {
@ -455,6 +456,10 @@ export class AdminResolver {
) {
throw new Error('Own contribution can not be deleted as admin')
}
const user = await dbUser.findOneOrFail(
{ id: contribution.userId },
{ relations: ['emailContact'] },
)
contribution.contributionStatus = ContributionStatus.DELETED
contribution.deletedBy = moderator.id
await contribution.save()
@ -468,6 +473,16 @@ export class AdminResolver {
await eventProtocol.writeEvent(
event.setEventAdminContributionDelete(eventAdminContributionDelete),
)
sendContributionRejectedEmail({
senderFirstName: moderator.firstName,
senderLastName: moderator.lastName,
recipientEmail: user.emailContact.email,
recipientFirstName: user.firstName,
recipientLastName: user.lastName,
contributionMemo: contribution.memo,
contributionAmount: contribution.amount,
overviewURL: CONFIG.EMAIL_LINK_OVERVIEW,
})
return !!res
}

View File

@ -74,6 +74,7 @@ describe('ContributionResolver', () => {
describe('input not valid', () => {
it('throws error when memo length smaller than 5 chars', async () => {
jest.clearAllMocks()
const date = new Date()
await expect(
mutate({
@ -92,10 +93,11 @@ describe('ContributionResolver', () => {
})
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: memo.length=4 < 5`)
})
it('throws error when memo length greater than 255 chars', async () => {
jest.clearAllMocks()
const date = new Date()
await expect(
mutate({
@ -114,10 +116,11 @@ describe('ContributionResolver', () => {
})
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: memo.length=259 > 255`)
})
it('throws error when creationDate not-valid', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: createContribution,
@ -144,6 +147,7 @@ describe('ContributionResolver', () => {
})
it('throws error when creationDate 3 month behind', async () => {
jest.clearAllMocks()
const date = new Date()
await expect(
mutate({
@ -375,6 +379,7 @@ describe('ContributionResolver', () => {
describe('wrong contribution id', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: updateContribution,
@ -399,6 +404,7 @@ describe('ContributionResolver', () => {
describe('Memo length smaller than 5 chars', () => {
it('throws error', async () => {
jest.clearAllMocks()
const date = new Date()
await expect(
mutate({
@ -418,12 +424,13 @@ describe('ContributionResolver', () => {
})
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: memo.length=4 < 5')
})
})
describe('Memo length greater than 255 chars', () => {
it('throws error', async () => {
jest.clearAllMocks()
const date = new Date()
await expect(
mutate({
@ -443,7 +450,7 @@ describe('ContributionResolver', () => {
})
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: memo.length=259 > 255')
})
})
@ -456,6 +463,7 @@ describe('ContributionResolver', () => {
})
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: updateContribution,
@ -486,6 +494,7 @@ describe('ContributionResolver', () => {
describe('admin tries to update a user contribution', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: adminUpdateContribution,
@ -516,6 +525,7 @@ describe('ContributionResolver', () => {
})
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: updateContribution,
@ -546,6 +556,7 @@ describe('ContributionResolver', () => {
describe('update creation to a date that is older than 3 months', () => {
it('throws an error', async () => {
jest.clearAllMocks()
const date = new Date()
await expect(
mutate({
@ -564,7 +575,7 @@ describe('ContributionResolver', () => {
)
})
it('logs the error found', () => {
it.skip('logs the error found', () => {
expect(logger.error).toBeCalledWith(
'No information for available creations with the given creationDate=',
'Invalid Date',
@ -830,6 +841,7 @@ describe('ContributionResolver', () => {
describe('User deletes already confirmed contribution', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await mutate({
mutation: login,
variables: { email: 'peter@lustig.de', password: 'Aa12345_' },

View File

@ -32,12 +32,12 @@ export class ContributionResolver {
@Ctx() context: Context,
): Promise<UnconfirmedContribution> {
if (memo.length > MEMO_MAX_CHARS) {
logger.error(`memo text is too long: memo.length=${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) {
logger.error(`memo text is too short: memo.length=${memo.length} < (${MEMO_MIN_CHARS})`)
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
}
@ -172,12 +172,12 @@ export class ContributionResolver {
@Ctx() context: Context,
): Promise<UnconfirmedContribution> {
if (memo.length > MEMO_MAX_CHARS) {
logger.error(`memo text is too long: memo.length=${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) {
logger.error(`memo text is too short: memo.length=${memo.length} < (${MEMO_MIN_CHARS}`)
logger.error(`memo text is too short: memo.length=${memo.length} < ${MEMO_MIN_CHARS}`)
throw new Error(`memo text is too short (${MEMO_MIN_CHARS} characters minimum)`)
}

View File

@ -67,6 +67,7 @@ describe('send coins', () => {
describe('unknown recipient', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await mutate({
mutation: login,
variables: bobData,
@ -93,6 +94,7 @@ describe('send coins', () => {
describe('deleted recipient', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await mutate({
mutation: login,
variables: peterData,
@ -125,6 +127,7 @@ describe('send coins', () => {
describe('recipient account not activated', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await mutate({
mutation: login,
variables: peterData,
@ -166,6 +169,7 @@ describe('send coins', () => {
describe('sender and recipient are the same', () => {
it('throws an error', async () => {
jest.clearAllMocks()
expect(
await mutate({
mutation: sendCoins,
@ -189,6 +193,7 @@ describe('send coins', () => {
describe('memo text is too long', () => {
it('throws an error', async () => {
jest.clearAllMocks()
expect(
await mutate({
mutation: sendCoins,
@ -212,6 +217,7 @@ describe('send coins', () => {
describe('memo text is too short', () => {
it('throws an error', async () => {
jest.clearAllMocks()
expect(
await mutate({
mutation: sendCoins,
@ -235,6 +241,7 @@ describe('send coins', () => {
describe('user has not enough GDD', () => {
it('throws an error', async () => {
jest.clearAllMocks()
expect(
await mutate({
mutation: sendCoins,
@ -260,6 +267,7 @@ describe('send coins', () => {
describe('sending negative amount', () => {
it('throws an error', async () => {
jest.clearAllMocks()
expect(
await mutate({
mutation: sendCoins,

View File

@ -514,18 +514,20 @@ describe('UserResolver', () => {
await mutate({ mutation: createUser, variables: createUserVariables })
const emailContact = await UserContact.findOneOrFail({ email: createUserVariables.email })
emailVerificationCode = emailContact.emailVerificationCode.toString()
result = await mutate({
mutation: setPassword,
variables: { code: emailVerificationCode, password: 'not-valid' },
})
})
afterAll(async () => {
await cleanDB()
})
it('throws an error', () => {
expect(result).toEqual(
it('throws an error', async () => {
jest.clearAllMocks()
expect(
await mutate({
mutation: setPassword,
variables: { code: emailVerificationCode, password: 'not-valid' },
}),
).toEqual(
expect.objectContaining({
errors: [
new GraphQLError(
@ -544,18 +546,20 @@ describe('UserResolver', () => {
describe('no valid optin code', () => {
beforeAll(async () => {
await mutate({ mutation: createUser, variables: createUserVariables })
result = await mutate({
mutation: setPassword,
variables: { code: 'not valid', password: 'Aa12345_' },
})
})
afterAll(async () => {
await cleanDB()
})
it('throws an error', () => {
expect(result).toEqual(
it('throws an error', async () => {
jest.clearAllMocks()
expect(
await mutate({
mutation: setPassword,
variables: { code: 'not valid', password: 'Aa12345_' },
}),
).toEqual(
expect.objectContaining({
errors: [new GraphQLError('Could not login with emailVerificationCode')],
}),
@ -582,13 +586,9 @@ describe('UserResolver', () => {
})
describe('no users in database', () => {
beforeAll(async () => {
it('throws an error', async () => {
jest.clearAllMocks()
result = await mutate({ mutation: login, variables })
})
it('throws an error', () => {
expect(result).toEqual(
expect(await mutate({ mutation: login, variables })).toEqual(
expect.objectContaining({
errors: [new GraphQLError('No user with this credentials')],
}),
@ -666,6 +666,7 @@ describe('UserResolver', () => {
describe('logout', () => {
describe('unauthenticated', () => {
it('throws an error', async () => {
jest.clearAllMocks()
resetToken()
await expect(mutate({ mutation: logout })).resolves.toEqual(
expect.objectContaining({
@ -704,6 +705,7 @@ describe('UserResolver', () => {
describe('verifyLogin', () => {
describe('unauthenticated', () => {
it('throws an error', async () => {
jest.clearAllMocks()
resetToken()
await expect(query({ query: verifyLogin })).resolves.toEqual(
expect.objectContaining({
@ -723,6 +725,7 @@ describe('UserResolver', () => {
})
it('throws an error', async () => {
jest.clearAllMocks()
resetToken()
await expect(query({ query: verifyLogin })).resolves.toEqual(
expect.objectContaining({
@ -883,6 +886,7 @@ describe('UserResolver', () => {
describe('wrong optin code', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
query({ query: queryOptIn, variables: { optIn: 'not-valid' } }),
).resolves.toEqual(
@ -919,6 +923,7 @@ describe('UserResolver', () => {
describe('updateUserInfos', () => {
describe('unauthenticated', () => {
it('throws an error', async () => {
jest.clearAllMocks()
resetToken()
await expect(mutate({ mutation: updateUserInfos })).resolves.toEqual(
expect.objectContaining({
@ -976,6 +981,7 @@ describe('UserResolver', () => {
describe('language is not valid', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: updateUserInfos,
@ -998,6 +1004,7 @@ describe('UserResolver', () => {
describe('password', () => {
describe('wrong old password', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: updateUserInfos,
@ -1020,6 +1027,7 @@ describe('UserResolver', () => {
describe('invalid new password', () => {
it('throws an error', async () => {
jest.clearAllMocks()
await expect(
mutate({
mutation: updateUserInfos,
@ -1108,6 +1116,7 @@ describe('UserResolver', () => {
describe('searchAdminUsers', () => {
describe('unauthenticated', () => {
it('throws an error', async () => {
jest.clearAllMocks()
resetToken()
await expect(mutate({ mutation: searchAdminUsers })).resolves.toEqual(
expect.objectContaining({

View File

@ -0,0 +1,38 @@
import Decimal from 'decimal.js-light'
import { sendContributionRejectedEmail } from './sendContributionRejectedEmail'
import { sendEMail } from './sendEMail'
jest.mock('./sendEMail', () => {
return {
__esModule: true,
sendEMail: jest.fn(),
}
})
describe('sendContributionConfirmedEmail', () => {
beforeEach(async () => {
await sendContributionRejectedEmail({
senderFirstName: 'Peter',
senderLastName: 'Lustig',
recipientFirstName: 'Bibi',
recipientLastName: 'Bloxberg',
recipientEmail: 'bibi@bloxberg.de',
contributionMemo: 'Vielen herzlichen Dank für den neuen Hexenbesen!',
contributionAmount: new Decimal(200.0),
overviewURL: 'http://localhost/overview',
})
})
it('calls sendEMail', () => {
expect(sendEMail).toBeCalledWith({
to: 'Bibi Bloxberg <bibi@bloxberg.de>',
subject: 'Schöpfung wurde abgelehnt',
text:
expect.stringContaining('Hallo Bibi Bloxberg') &&
expect.stringContaining(
'Dein Gradido Schöpfungsantrag "Vielen herzlichen Dank für den neuen Hexenbesen!" wurde soeben von Peter Lustig abgelehnt.',
) &&
expect.stringContaining('Link zu deinem Konto: http://localhost/overview'),
})
})
})

View File

@ -0,0 +1,26 @@
import { backendLogger as logger } from '@/server/logger'
import Decimal from 'decimal.js-light'
import { sendEMail } from './sendEMail'
import { contributionRejected } from './text/contributionRejected'
export const sendContributionRejectedEmail = (data: {
senderFirstName: string
senderLastName: string
recipientFirstName: string
recipientLastName: string
recipientEmail: string
contributionMemo: string
contributionAmount: Decimal
overviewURL: string
}): Promise<boolean> => {
logger.info(
`sendEmail(): to=${data.recipientFirstName} ${data.recipientLastName} <${data.recipientEmail}>,
subject=${contributionRejected.de.subject},
text=${contributionRejected.de.text(data)}`,
)
return sendEMail({
to: `${data.recipientFirstName} ${data.recipientLastName} <${data.recipientEmail}>`,
subject: contributionRejected.de.subject,
text: contributionRejected.de.text(data),
})
}

View File

@ -0,0 +1,27 @@
import Decimal from 'decimal.js-light'
export const contributionRejected = {
de: {
subject: 'Schöpfung wurde abgelehnt',
text: (data: {
senderFirstName: string
senderLastName: string
recipientFirstName: string
recipientLastName: string
contributionMemo: string
contributionAmount: Decimal
overviewURL: string
}): string =>
`Hallo ${data.recipientFirstName} ${data.recipientLastName},
Dein eingereichter Gemeinwohl-Beitrag "${data.contributionMemo}" wurde soeben von ${data.senderFirstName} ${data.senderLastName} abgelehnt.
Bitte antworte nicht auf diese E-Mail!
Mit freundlichen Grüßen,
dein Gradido-Team
Link zu deinem Konto: ${data.overviewURL}`,
},
}

View File

@ -18,7 +18,7 @@ ENV NODE_ENV="production"
# Labels
LABEL org.label-schema.build-date="${BUILD_DATE}"
LABEL org.label-schema.name="gradido:database"
LABEL org.label-schema.description="Gradido GraphQL Backend"
LABEL org.label-schema.description="Gradido Database Migration Service"
LABEL org.label-schema.usage="https://github.com/gradido/gradido/blob/master/README.md"
LABEL org.label-schema.url="https://gradido.net"
LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido/tree/master/database"

View File

@ -20,10 +20,10 @@ ENV PORT="3000"
# Labels
LABEL org.label-schema.build-date="${BUILD_DATE}"
LABEL org.label-schema.name="gradido:frontend"
LABEL org.label-schema.description="Gradido Vue Webwallet"
LABEL org.label-schema.usage="https://github.com/gradido/gradido_vue_wallet/blob/master/README.md"
LABEL org.label-schema.description="Gradido Wallet Interface"
LABEL org.label-schema.usage="https://github.com/gradido/gradido/blob/master/README.md"
LABEL org.label-schema.url="https://gradido.net"
LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido_vue_wallet/tree/master/backend"
LABEL org.label-schema.vcs-url="https://github.com/gradido/gradido/tree/master/frontend"
LABEL org.label-schema.vcs-ref="${BUILD_COMMIT}"
LABEL org.label-schema.vendor="gradido Community"
LABEL org.label-schema.version="${BUILD_VERSION}"

View File

@ -18,7 +18,7 @@
<div class="m-1 mt-2">{{ $t('GDD') }}</div>
</b-input-group-prepend>
<div class="p-3">{{ (amount * -1) | GDD }}</div>
<div class="p-3">{{ amount | GDD }}</div>
</b-input-group>
<br />

View File

@ -207,7 +207,7 @@
},
"language": "Sprache",
"link-load": "den letzten Link nachladen | die letzten {n} Links nachladen | weitere {n} Links nachladen",
"login": "Anmeldung",
"login": "Anmelden",
"math": {
"aprox": "~",
"asterisk": "*",

View File

@ -207,7 +207,7 @@
},
"language": "Language",
"link-load": "Load the last link | Load the last {n} links | Load more {n} links",
"login": "Login",
"login": "Sign in",
"math": {
"aprox": "~",
"asterisk": "*",