diff --git a/.github/dependabot_backend.yml b/.github/dependabot_backend.yml new file mode 100644 index 000000000..eadeec7af --- /dev/null +++ b/.github/dependabot_backend.yml @@ -0,0 +1,37 @@ +version: 2 + +updates: +- package-ecosystem: npm + directory: "/backend" + rebase-strategy: "disabled" + schedule: + interval: weekly + day: "saturday" + timezone: "Europe/Berlin" + time: "03:00" + labels: + - "devops" + - "service:backend" + +- package-ecosystem: docker + directory: "/backend" + rebase-strategy: "disabled" + schedule: + interval: weekly + day: "saturday" + timezone: "Europe/Berlin" + time: "03:00" + labels: + - "devops" + - "service:docker" + +- package-ecosystem: "github-actions" + directory: "/" + rebase-strategy: "disabled" + schedule: + interval: weekly + day: "saturday" + timezone: "Europe/Berlin" + time: "03:00" + labels: + - "devops" diff --git a/.github/file-filters.yml b/.github/file-filters.yml index f0d38b75b..1559d2354 100644 --- a/.github/file-filters.yml +++ b/.github/file-filters.yml @@ -36,6 +36,9 @@ backend: &backend dht_node: &dht_node - 'dht-node/**/*' +dlt_connector: &dlt_connector + - 'dlt-connector/**/*' + docker-compose: &docker-compose - 'docker-compose.*' diff --git a/.github/workflows/lint_pr.yml b/.github/workflows/lint_pr.yml index defaa7b08..132d5861d 100644 --- a/.github/workflows/lint_pr.yml +++ b/.github/workflows/lint_pr.yml @@ -29,6 +29,8 @@ jobs: database release federation + dht + dlt workflow docker other diff --git a/.github/workflows/test_dht_node.yml b/.github/workflows/test_dht_node.yml index b63d1fc0d..e81ed33af 100644 --- a/.github/workflows/test_dht_node.yml +++ b/.github/workflows/test_dht_node.yml @@ -51,7 +51,7 @@ jobs: uses: actions/checkout@v3 - name: Lint - run: cd dht-node && yarn && yarn run lint + run: cd database && yarn && cd ../dht-node && yarn && yarn run lint unit_test: name: Unit Tests - DHT Node diff --git a/.github/workflows/test_dlt_connector.yml b/.github/workflows/test_dlt_connector.yml new file mode 100644 index 000000000..8628f9f37 --- /dev/null +++ b/.github/workflows/test_dlt_connector.yml @@ -0,0 +1,74 @@ +name: Gradido DLT Connector Test CI + +on: push + +jobs: + files-changed: + name: Detect File Changes - DLT Connector + runs-on: ubuntu-latest + outputs: + dlt_connector: ${{ steps.changes.outputs.dlt_connector }} + docker-compose: ${{ steps.changes.outputs.docker-compose }} + steps: + - uses: actions/checkout@v3.3.0 + + - name: Check for frontend file changes + uses: dorny/paths-filter@v2.11.1 + id: changes + with: + token: ${{ github.token }} + filters: .github/file-filters.yml + list-files: shell + + build: + name: Docker Build Test - DLT Connector + if: needs.files-changed.outputs.dlt_connector == 'true' + needs: files-changed + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Build 'test' image + run: | + docker build --target test -t "gradido/dlt-connector:test" -f dlt-connector/Dockerfile . + docker save "gradido/dlt-connector:test" > /tmp/dlt-connector.tar + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: docker-dlt-connector-test + path: /tmp/dlt-connector.tar + + lint: + name: Lint - DLT Connector + if: needs.files-changed.outputs.dlt_connector == 'true' + needs: files-changed + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Lint + run: cd dlt-connector && yarn && yarn run lint + + unit_test: + name: Unit Tests - DLT Connector + if: needs.files-changed.outputs.dlt_connector == 'true' || needs.files-changed.outputs.docker-compose == 'true' + needs: [files-changed, build] + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Download Docker Image + uses: actions/download-artifact@v3 + with: + name: docker-dlt-connector-test + path: /tmp + + - name: Load Docker Image + run: docker load < /tmp/dlt-connector.tar + + - name: Unit tests + run: docker run --env NODE_ENV=test --rm gradido/dlt-connector:test yarn run test diff --git a/.github/workflows/test_e2e.yml b/.github/workflows/test_e2e.yml index d1dd2851a..617097a53 100644 --- a/.github/workflows/test_e2e.yml +++ b/.github/workflows/test_e2e.yml @@ -33,7 +33,6 @@ jobs: yarn && yarn dev_reset cd ../backend yarn && yarn seed - cd .. - name: Boot up test system | docker-compose frontends run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps frontend admin nginx @@ -41,18 +40,35 @@ jobs: - name: Boot up test system | docker-compose mailserver run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver - - name: Sleep for 15 seconds - run: sleep 15s + - name: End-to-end tests | prepare + run: | + wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386" + chmod +x /opt/cucumber-json-formatter + sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter + cd e2e-tests/ + yarn - name: End-to-end tests | run tests id: e2e-tests run: | cd e2e-tests/ - yarn yarn run cypress run - - name: End-to-end tests | if tests failed, upload screenshots + + - name: End-to-end tests | if tests failed, compile html report + if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} + run: | + cd e2e-tests/ + node create-cucumber-html-report.js + + - name: End-to-end tests | if tests failed, get pr number + id: pr + if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} + uses: 8BitJonny/gh-get-current-pr@2.2.0 + + - name: End-to-end tests | if tests failed, upload report + id: e2e-report if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }} uses: actions/upload-artifact@v3 with: - name: cypress-screenshots - path: /home/runner/work/gradido/gradido/e2e-tests/cypress/screenshots/ + name: cypress-report-pr-#${{ steps.pr.outputs.number }} + path: /home/runner/work/gradido/gradido/e2e-tests/cypress/reports/cucumber_html_report diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e04d4c21..3bea722dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,129 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.23.2](https://github.com/gradido/gradido/compare/1.23.1...1.23.2) + +- feat(admin): contribution filtering by memo [`#3174`](https://github.com/gradido/gradido/pull/3174) +- feat(backend): after transaction creations trigger to send them to dlt-connector [`#3152`](https://github.com/gradido/gradido/pull/3152) + +#### [1.23.1](https://github.com/gradido/gradido/compare/1.23.0...1.23.1) + +> 10 August 2023 + +- chore(release): v1.23.1 [`#3173`](https://github.com/gradido/gradido/pull/3173) +- feat(dlt): add dlt-connector to release-script [`#3170`](https://github.com/gradido/gradido/pull/3170) +- fix(backend): update logfiles clearance-script [`#3168`](https://github.com/gradido/gradido/pull/3168) +- fix(backend): too much logoutput on production [`#3160`](https://github.com/gradido/gradido/pull/3160) +- perf(backend): enable logfile compression [`#3158`](https://github.com/gradido/gradido/pull/3158) +- feat(other): setup dependabot for backend package updates [`#3156`](https://github.com/gradido/gradido/pull/3156) + +#### [1.23.0](https://github.com/gradido/gradido/compare/1.22.3...1.23.0) + +> 25 July 2023 + +- chore(release): v1.23.0 [`#3157`](https://github.com/gradido/gradido/pull/3157) +- refactor(frontend): add contribution by link information to locales [`#3144`](https://github.com/gradido/gradido/pull/3144) +- fix(admin): user role in admin interface [`#3153`](https://github.com/gradido/gradido/pull/3153) +- feat(backend): 3030 feature role administration backend [`#3074`](https://github.com/gradido/gradido/pull/3074) +- feat(other): iota-tangle-connector sending transaction [`#3132`](https://github.com/gradido/gradido/pull/3132) +- feat(other): proper reporting for failing end-to-end tests [`#3096`](https://github.com/gradido/gradido/pull/3096) +- fix(other): add missing volume for dlt-connector dev docker [`#3134`](https://github.com/gradido/gradido/pull/3134) +- fix(backend): semaphore parallel redeemTransactionLink test [`#3133`](https://github.com/gradido/gradido/pull/3133) +- feat(other): iota-tangle-connector mit Hello World Message als separates Modul [`#3118`](https://github.com/gradido/gradido/pull/3118) +- refactor(database): fix database public key lengths [`#3026`](https://github.com/gradido/gradido/pull/3026) +- refactor(dht): eslint dht import [`#3044`](https://github.com/gradido/gradido/pull/3044) + +#### [1.22.3](https://github.com/gradido/gradido/compare/1.22.2...1.22.3) + +> 6 July 2023 + +- chore(release): v1.22.3 [`#3131`](https://github.com/gradido/gradido/pull/3131) +- fix(backend): corrected email-link [`#3129`](https://github.com/gradido/gradido/pull/3129) + +#### [1.22.2](https://github.com/gradido/gradido/compare/1.22.1...1.22.2) + +> 6 July 2023 + +- chore(release): v1.22.2 [`#3127`](https://github.com/gradido/gradido/pull/3127) +- fix(backend): moderation message are completely hidden from the user [`#3123`](https://github.com/gradido/gradido/pull/3123) +- fix(frontend): properly save username, do not allow to edit it again [`#3124`](https://github.com/gradido/gradido/pull/3124) +- fix(frontend): fix German "Speichern" to have capital letter [`#3122`](https://github.com/gradido/gradido/pull/3122) + +#### [1.22.1](https://github.com/gradido/gradido/compare/1.22.0...1.22.1) + +> 4 July 2023 + +- chore(release): v1.22.1 [`#3117`](https://github.com/gradido/gradido/pull/3117) +- fix(backend): use base url from config in email templates [`#3114`](https://github.com/gradido/gradido/pull/3114) +- feat(frontend): test right side layout template [`#3052`](https://github.com/gradido/gradido/pull/3052) +- feat(backend): remove iota from backend [`#3115`](https://github.com/gradido/gradido/pull/3115) +- refactor(frontend): text juni to juli [`#3116`](https://github.com/gradido/gradido/pull/3116) +- refactor(frontend): refactor changes incorporated [`#3113`](https://github.com/gradido/gradido/pull/3113) +- refactor(frontend): date from deploy changed to infotext [`#3108`](https://github.com/gradido/gradido/pull/3108) +- fix(frontend): add alias to the verifyLogin GraphQL answer. [`#3107`](https://github.com/gradido/gradido/pull/3107) +- fix(backend): moderator message don't send email to user [`#3106`](https://github.com/gradido/gradido/pull/3106) + +#### [1.22.0](https://github.com/gradido/gradido/compare/1.21.0...1.22.0) + +> 30 June 2023 + +- chore(release): v1.22.0 [`#3101`](https://github.com/gradido/gradido/pull/3101) +- fix(backend): yarn.lock after typeorm update [`#3097`](https://github.com/gradido/gradido/pull/3097) +- feat(frontend): new style for settings page [`#3040`](https://github.com/gradido/gradido/pull/3040) +- feat(admin): query users on contributions [`#3094`](https://github.com/gradido/gradido/pull/3094) +- feat(backend): user query on find contributions [`#3091`](https://github.com/gradido/gradido/pull/3091) +- fix(backend): double redeem transaction link [`#3093`](https://github.com/gradido/gradido/pull/3093) +- feat(admin): message type admin frontend [`#3073`](https://github.com/gradido/gradido/pull/3073) +- feat(other): end-to-end test scenarios for deleted and not registered user [`#3077`](https://github.com/gradido/gradido/pull/3077) +- feat(database): update typeorm [`#3078`](https://github.com/gradido/gradido/pull/3078) +- refactor(other): disable cypress test retries [`#3092`](https://github.com/gradido/gradido/pull/3092) +- test(other): update cypress [`#3056`](https://github.com/gradido/gradido/pull/3056) +- feat(other): add definition of cron-job for klicktipp export. [`#3051`](https://github.com/gradido/gradido/pull/3051) +- fix(backend): forget password not for deleted users [`#3090`](https://github.com/gradido/gradido/pull/3090) +- fix(backend): gdt server error & gdt refetch policy [`#3086`](https://github.com/gradido/gradido/pull/3086) +- feat(backend): contribution message type moderator [`#3072`](https://github.com/gradido/gradido/pull/3072) +- fix(other): linting in e2e tests directory does not work [`#3089`](https://github.com/gradido/gradido/pull/3089) +- feat(other): end-to-end test feature send coins [`#3070`](https://github.com/gradido/gradido/pull/3070) +- refactor(dht): move typescript related packages in dev Dependencies [`#3085`](https://github.com/gradido/gradido/pull/3085) +- fix(backend): jest environment [`#3084`](https://github.com/gradido/gradido/pull/3084) +- feat(frontend): transaction list page change style for small device [`#3081`](https://github.com/gradido/gradido/pull/3081) +- refactor(other): refactor state to status [`#3082`](https://github.com/gradido/gradido/pull/3082) +- feat(admin): change deny contribution btn-warning color and background-color to #e1a908 [`#3080`](https://github.com/gradido/gradido/pull/3080) +- feat(backend): bootstrap and Hello World Test [`#3041`](https://github.com/gradido/gradido/pull/3041) +- feat(frontend): change the info text on the start page [`#3049`](https://github.com/gradido/gradido/pull/3049) +- refactor(dht): eslint dht n [`#3045`](https://github.com/gradido/gradido/pull/3045) +- refactor(dht): eslint dht comments [`#3046`](https://github.com/gradido/gradido/pull/3046) +- fix(frontend): auto logout messages autohide time 5000 [`#2959`](https://github.com/gradido/gradido/pull/2959) +- feat(federation): introduce private key in community table [`#3024`](https://github.com/gradido/gradido/pull/3024) +- refactor(backend): removed klicktipp middleware and replace it with a function [`#3035`](https://github.com/gradido/gradido/pull/3035) +- feat(admin): order user search desc, fix pagination [`#3059`](https://github.com/gradido/gradido/pull/3059) +- refactor(database): eslint database eslint comments [`#3039`](https://github.com/gradido/gradido/pull/3039) +- refactor(database): eslint database import [`#3038`](https://github.com/gradido/gradido/pull/3038) +- feat(backend): apply design template to HTML e-mails [`#2938`](https://github.com/gradido/gradido/pull/2938) +- refactor(database): eslint database n [`#3037`](https://github.com/gradido/gradido/pull/3037) +- refactor(backend): sodium native with types [`#3032`](https://github.com/gradido/gradido/pull/3032) +- refactor(federation): expose private key to write home community [`#3023`](https://github.com/gradido/gradido/pull/3023) +- refactor(dht): eslint dht base configuration [`#3042`](https://github.com/gradido/gradido/pull/3042) +- refactor(federation): eslint federation base configuration [`#3047`](https://github.com/gradido/gradido/pull/3047) +- refactor(database): eslint database base configuration [`#3036`](https://github.com/gradido/gradido/pull/3036) +- feat(workflow): dht as package for lint pr workflow [`#3043`](https://github.com/gradido/gradido/pull/3043) +- refactor(federation): removed (potential) duplicate db query [`#3019`](https://github.com/gradido/gradido/pull/3019) +- refactor(federation): remove function getSeed [`#3022`](https://github.com/gradido/gradido/pull/3022) +- refactor(federation): refactor federation to use inheritance [`#2992`](https://github.com/gradido/gradido/pull/2992) +- refactor(backend): random-bigint types [`#3033`](https://github.com/gradido/gradido/pull/3033) +- fix(frontend): incorrect errormessage for wrong contribution link [`#2958`](https://github.com/gradido/gradido/pull/2958) +- refactor(federation): remove export from CommunityApi [`#3021`](https://github.com/gradido/gradido/pull/3021) +- refactor(federation): simplify newCommunityUuid [`#3020`](https://github.com/gradido/gradido/pull/3020) +- refactor(database): remove duplicate comments [`#3025`](https://github.com/gradido/gradido/pull/3025) +- feat(federation): x com 3 introduce business communities [`#2955`](https://github.com/gradido/gradido/pull/2955) +- refactor(backend): remove to do after creating issue [`#3000`](https://github.com/gradido/gradido/pull/3000) +- refactor(backend): camelcase exception for FederationClient_XX_X [`#3016`](https://github.com/gradido/gradido/pull/3016) + #### [1.21.0](https://github.com/gradido/gradido/compare/1.20.0...1.21.0) +> 19 May 2023 + +- chore(release): v1.21.0 [`#2998`](https://github.com/gradido/gradido/pull/2998) - feat(frontend): preserve email after login [`#2994`](https://github.com/gradido/gradido/pull/2994) - feat(frontend): send coins via identifier [`#2989`](https://github.com/gradido/gradido/pull/2989) - feat(backend): export user events to klicktipp [`#2916`](https://github.com/gradido/gradido/pull/2916) diff --git a/admin/package.json b/admin/package.json index 823201b7d..e5a3e5e3c 100644 --- a/admin/package.json +++ b/admin/package.json @@ -3,7 +3,7 @@ "description": "Administraion Interface for Gradido", "main": "index.js", "author": "Moriz Wahl", - "version": "1.21.0", + "version": "1.23.2", "license": "Apache-2.0", "private": false, "scripts": { diff --git a/admin/src/components/ChangeUserRoleFormular.spec.js b/admin/src/components/ChangeUserRoleFormular.spec.js index 381d2ce43..5af22d257 100644 --- a/admin/src/components/ChangeUserRoleFormular.spec.js +++ b/admin/src/components/ChangeUserRoleFormular.spec.js @@ -21,6 +21,7 @@ const mocks = { moderator: { id: 0, name: 'test moderator', + roles: ['ADMIN'], }, }, }, @@ -45,7 +46,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - isAdmin: null, + roles: [], }, } wrapper = Wrapper() @@ -61,7 +62,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 0, - isAdmin: null, + roles: ['ADMIN'], }, } wrapper = Wrapper() @@ -88,7 +89,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - isAdmin: null, + roles: [], }, } wrapper = Wrapper() @@ -120,13 +121,13 @@ describe('ChangeUserRoleFormular', () => { beforeEach(() => { apolloMutateMock.mockResolvedValue({ data: { - setUserRole: new Date(), + setUserRole: 'ADMIN', }, }) propsData = { item: { userId: 1, - isAdmin: null, + roles: ['USER'], }, } wrapper = Wrapper() @@ -134,7 +135,7 @@ describe('ChangeUserRoleFormular', () => { }) it('has selected option set to "usual user"', () => { - expect(wrapper.find('select.role-select').element.value).toBe('user') + expect(wrapper.find('select.role-select').element.value).toBe('USER') }) describe('change select to', () => { @@ -149,7 +150,7 @@ describe('ChangeUserRoleFormular', () => { }) }) - describe('new role', () => { + describe('new role "MODERATOR"', () => { beforeEach(() => { rolesToSelect.at(1).setSelected() }) @@ -181,19 +182,267 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - isAdmin: true, + role: 'MODERATOR', }, }), ) }) - it('emits "updateIsAdmin"', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( + it('emits "updateRoles" with role moderator', () => { + expect(wrapper.emitted('updateRoles')).toEqual( expect.arrayContaining([ expect.arrayContaining([ { userId: 1, - isAdmin: expect.any(Date), + roles: ['MODERATOR'], + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + }) + + describe('new role "ADMIN"', () => { + beforeEach(() => { + rolesToSelect.at(2).setSelected() + }) + + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, + ) + }) + + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + role: 'ADMIN', + }, + }), + ) + }) + + it('emits "updateRoles" with role moderator', () => { + expect(wrapper.emitted('updateRoles')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + roles: ['ADMIN'], + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + }) + }) + }) + + describe('user has role "moderator"', () => { + beforeEach(() => { + apolloMutateMock.mockResolvedValue({ + data: { + setUserRole: null, + }, + }) + propsData = { + item: { + userId: 1, + roles: ['MODERATOR'], + }, + } + wrapper = Wrapper() + rolesToSelect = wrapper.find('select.role-select').findAll('option') + }) + + it('has selected option set to "MODERATOR"', () => { + expect(wrapper.find('select.role-select').element.value).toBe('MODERATOR') + }) + + describe('change select to', () => { + describe('same role', () => { + it('has "change_user_role" button disabled', () => { + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe(true) + }) + + it('does not call the API', () => { + rolesToSelect.at(1).setSelected() + expect(apolloMutateMock).not.toHaveBeenCalled() + }) + }) + + describe('new role "USER"', () => { + beforeEach(() => { + rolesToSelect.at(0).setSelected() + }) + + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, + ) + }) + + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + role: 'USER', + }, + }), + ) + }) + + it('emits "updateRoles"', () => { + expect(wrapper.emitted('updateRoles')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + roles: [], + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + }) + + describe('new role "ADMIN"', () => { + beforeEach(() => { + rolesToSelect.at(2).setSelected() + }) + + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, + ) + }) + + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + role: 'ADMIN', + }, + }), + ) + }) + + it('emits "updateRoles"', () => { + expect(wrapper.emitted('updateRoles')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + roles: ['ADMIN'], }, ]), ]), @@ -232,7 +481,7 @@ describe('ChangeUserRoleFormular', () => { propsData = { item: { userId: 1, - isAdmin: new Date(), + roles: ['ADMIN'], }, } wrapper = Wrapper() @@ -240,7 +489,7 @@ describe('ChangeUserRoleFormular', () => { }) it('has selected option set to "admin"', () => { - expect(wrapper.find('select.role-select').element.value).toBe('admin') + expect(wrapper.find('select.role-select').element.value).toBe('ADMIN') }) describe('change select to', () => { @@ -251,11 +500,12 @@ describe('ChangeUserRoleFormular', () => { it('does not call the API', () => { rolesToSelect.at(1).setSelected() + // TODO: Fix this expect(apolloMutateMock).not.toHaveBeenCalled() }) }) - describe('new role', () => { + describe('new role "USER"', () => { beforeEach(() => { rolesToSelect.at(0).setSelected() }) @@ -287,19 +537,90 @@ describe('ChangeUserRoleFormular', () => { mutation: setUserRole, variables: { userId: 1, - isAdmin: false, + role: 'USER', }, }), ) }) - it('emits "updateIsAdmin"', () => { - expect(wrapper.emitted('updateIsAdmin')).toEqual( + it('emits "updateRoles"', () => { + expect(wrapper.emitted('updateRoles')).toEqual( expect.arrayContaining([ expect.arrayContaining([ { userId: 1, - isAdmin: null, + roles: [], + }, + ]), + ]), + ) + }) + + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('userRole.successfullyChangedTo') + }) + }) + + describe('confirm role change with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + apolloMutateMock.mockRejectedValue({ message: 'Oh no!' }) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Oh no!') + }) + }) + }) + }) + + describe('new role "MODERATOR"', () => { + beforeEach(() => { + rolesToSelect.at(1).setSelected() + }) + + it('has "change_user_role" button enabled', () => { + expect(wrapper.find('button.btn.btn-danger').exists()).toBe(true) + expect(wrapper.find('button.btn.btn-danger[disabled="disabled"]').exists()).toBe( + false, + ) + }) + + describe('clicking the "change_user_role" button', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(true)) + await wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + }) + + it('calls the modal', () => { + expect(wrapper.emitted('showModal')) + expect(spy).toHaveBeenCalled() + }) + + describe('confirm role change with success', () => { + it('calls the API', () => { + expect(apolloMutateMock).toBeCalledWith( + expect.objectContaining({ + mutation: setUserRole, + variables: { + userId: 1, + role: 'MODERATOR', + }, + }), + ) + }) + + it('emits "updateRoles"', () => { + expect(wrapper.emitted('updateRoles')).toEqual( + expect.arrayContaining([ + expect.arrayContaining([ + { + userId: 1, + roles: ['MODERATOR'], }, ]), ]), @@ -328,5 +649,23 @@ describe('ChangeUserRoleFormular', () => { }) }) }) + + describe('authenticated user is MODERATOR', () => { + beforeEach(() => { + mocks.$store.state.moderator.roles = ['MODERATOR'] + }) + + it('displays text with role', () => { + expect(wrapper.text()).toBe('userRole.selectRoles.admin') + }) + + it('has no role select', () => { + expect(wrapper.find('select.role-select').exists()).toBe(false) + }) + + it('has no button', () => { + expect(wrapper.find('button.btn.btn-dange').exists()).toBe(false) + }) + }) }) }) diff --git a/admin/src/components/ChangeUserRoleFormular.vue b/admin/src/components/ChangeUserRoleFormular.vue index 677a12f56..7f048d0e2 100644 --- a/admin/src/components/ChangeUserRoleFormular.vue +++ b/admin/src/components/ChangeUserRoleFormular.vue @@ -1,7 +1,10 @@