diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 66903aa63..cce45ae6e 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -6,8 +6,6 @@ import { createTestClient } from 'apollo-server-testing' const categoryIds = ['cat9'] let user -let anotherUser -let moderator let admin let authenticatedUser @@ -18,6 +16,35 @@ let variables const driver = getDriver() const neode = getNeode() +const deleteUserMutation = gql` + mutation($id: ID!, $resource: [Deletable]) { + DeleteUser(id: $id, resource: $resource) { + id + name + about + deleted + contributions { + id + content + contentExcerpt + deleted + comments { + id + content + contentExcerpt + deleted + } + } + comments { + id + content + contentExcerpt + deleted + } + } + } +` + beforeAll(() => { const { server } = createServer({ context: () => { @@ -51,7 +78,6 @@ describe('User', () => { variables = { email: 'any-email-address@example.org', } - await Factory.build('user', { name: 'Johnny' }, { email: 'any-email-address@example.org' }) }) @@ -248,38 +274,8 @@ describe('UpdateUser', () => { }) }) -describe('Delete a user', () => { - let deleteUserMutation - +describe('Delete a User as admin', () => { beforeEach(async () => { - deleteUserMutation = gql` - mutation($id: ID!, $resource: [Deletable]) { - DeleteUser(id: $id, resource: $resource) { - id - name - about - deleted - contributions { - id - content - contentExcerpt - deleted - comments { - id - content - contentExcerpt - deleted - } - } - comments { - id - content - contentExcerpt - deleted - } - } - } - ` variables = { id: ' u343', resource: [] } user = await Factory.build('user', { @@ -289,112 +285,113 @@ describe('Delete a user', () => { }) }) - describe('as another user', () => { + describe('authenticated as Admin', () => { beforeEach(async () => { - anotherUser = await Factory.build( + admin = await Factory.build( 'user', { - role: 'user', + role: 'admin', }, { - email: 'user@example.org', + email: 'admin@example.org', password: '1234', }, ) - - authenticatedUser = await anotherUser.toJson() + authenticatedUser = await admin.toJson() }) - it("an ordinary user has no authorization to delete another user's account", async () => { - const { errors } = await mutate({ mutation: deleteUserMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('as moderator', () => { - beforeEach(async () => { - moderator = await Factory.build( - 'user', - { - role: 'moderator', - }, - { - email: 'moderator@example.org', - password: '1234', - }, - ) - - authenticatedUser = await moderator.toJson() - }) - - it('moderator is not allowed to delete other user accounts', async () => { - const { errors } = await mutate({ mutation: deleteUserMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('as admin', () => { - describe('authenticated as Admin', () => { - beforeEach(async () => { - admin = await Factory.build( - 'user', - { - role: 'admin', - }, - { - email: 'admin@example.org', - password: '1234', - }, - ) - authenticatedUser = await admin.toJson() + describe('deleting a user account', () => { + beforeEach(() => { + variables = { ...variables, id: 'u343' } }) - describe('deleting a user account', () => { - beforeEach(() => { - variables = { id: 'u343' } + describe('given posts and comments', () => { + beforeEach(async () => { + await Factory.build('category', { + id: 'cat9', + name: 'Democracy & Politics', + icon: 'university', + }) + await Factory.build( + 'post', + { + id: 'p139', + content: 'Post by user u343', + }, + { + author: user, + categoryIds, + }, + ) + await Factory.build( + 'comment', + { + id: 'c155', + content: 'Comment by user u343', + }, + { + author: user, + }, + ) + await Factory.build( + 'comment', + { + id: 'c156', + content: "A comment by someone else on user u343's post", + }, + { + postId: 'p139', + }, + ) }) - describe('given posts and comments', () => { - beforeEach(async () => { - await Factory.build('category', { - id: 'cat9', - name: 'Democracy & Politics', - icon: 'university', - }) - await Factory.build( - 'post', - { - id: 'p139', - content: 'Post by user u343', + it("deletes account, but doesn't delete posts or comments by default", async () => { + const expectedResponse = { + data: { + DeleteUser: { + id: 'u343', + name: 'UNAVAILABLE', + about: 'UNAVAILABLE', + deleted: true, + contributions: [ + { + id: 'p139', + content: 'Post by user u343', + contentExcerpt: 'Post by user u343', + deleted: false, + comments: [ + { + id: 'c156', + content: "A comment by someone else on user u343's post", + contentExcerpt: "A comment by someone else on user u343's post", + deleted: false, + }, + ], + }, + ], + comments: [ + { + id: 'c155', + content: 'Comment by user u343', + contentExcerpt: 'Comment by user u343', + deleted: false, + }, + ], }, - { - author: user, - categoryIds, - }, - ) - await Factory.build( - 'comment', - { - id: 'c155', - content: 'Comment by user u343', - }, - { - author: user, - }, - ) - await Factory.build( - 'comment', - { - id: 'c156', - content: "A comment by someone else on user u343's post", - }, - { - postId: 'p139', - }, - ) + }, + errors: undefined, + } + await expect(mutate({ mutation: deleteUserMutation, variables })).resolves.toMatchObject( + expectedResponse, + ) + }) + + describe('deletion of all posts and comments requested', () => { + beforeEach(() => { + variables = { ...variables, resource: ['Comment', 'Post'] } }) - it("deletes account, but doesn't delete posts or comments by default", async () => { + it('marks posts and comments as deleted', async () => { const expectedResponse = { data: { DeleteUser: { @@ -405,15 +402,15 @@ describe('Delete a user', () => { contributions: [ { id: 'p139', - content: 'Post by user u343', - contentExcerpt: 'Post by user u343', - deleted: false, + content: 'UNAVAILABLE', + contentExcerpt: 'UNAVAILABLE', + deleted: true, comments: [ { id: 'c156', - content: "A comment by someone else on user u343's post", - contentExcerpt: "A comment by someone else on user u343's post", - deleted: false, + content: 'UNAVAILABLE', + contentExcerpt: 'UNAVAILABLE', + deleted: true, }, ], }, @@ -421,9 +418,9 @@ describe('Delete a user', () => { comments: [ { id: 'c155', - content: 'Comment by user u343', - contentExcerpt: 'Comment by user u343', - deleted: false, + content: 'UNAVAILABLE', + contentExcerpt: 'UNAVAILABLE', + deleted: true, }, ], }, @@ -434,175 +431,28 @@ describe('Delete a user', () => { mutate({ mutation: deleteUserMutation, variables }), ).resolves.toMatchObject(expectedResponse) }) + }) + }) - describe('deletion of all post requested', () => { - beforeEach(() => { - variables = { ...variables, resource: ['Post'] } - }) + describe('connected `EmailAddress` nodes', () => { + it('will be removed completely', async () => { + await expect(neode.all('EmailAddress')).resolves.toHaveLength(2) + await mutate({ mutation: deleteUserMutation, variables }) - it('on request', async () => { - const expectedResponse = { - data: { - DeleteUser: { - id: 'u343', - name: 'UNAVAILABLE', - about: 'UNAVAILABLE', - deleted: true, - contributions: [ - { - id: 'p139', - content: 'UNAVAILABLE', - contentExcerpt: 'UNAVAILABLE', - deleted: true, - comments: [ - { - id: 'c156', - content: 'UNAVAILABLE', - contentExcerpt: 'UNAVAILABLE', - deleted: true, - }, - ], - }, - ], - comments: [ - { - id: 'c155', - content: 'Comment by user u343', - contentExcerpt: 'Comment by user u343', - deleted: false, - }, - ], - }, - }, - errors: undefined, - } - await expect( - mutate({ mutation: deleteUserMutation, variables }), - ).resolves.toMatchObject(expectedResponse) - }) + await expect(neode.all('EmailAddress')).resolves.toHaveLength(1) + }) + }) - it('deletes user avatar and post hero images', async () => { - await expect(neode.all('Image')).resolves.toHaveLength(22) - await mutate({ mutation: deleteUserMutation, variables }) - await expect(neode.all('Image')).resolves.toHaveLength(20) - }) - }) - - describe('deletion of all comments requested', () => { - beforeEach(() => { - variables = { ...variables, resource: ['Comment'] } - }) - - it('marks comments as deleted', async () => { - const expectedResponse = { - data: { - DeleteUser: { - id: 'u343', - name: 'UNAVAILABLE', - about: 'UNAVAILABLE', - deleted: true, - contributions: [ - { - id: 'p139', - content: 'Post by user u343', - contentExcerpt: 'Post by user u343', - deleted: false, - comments: [ - { - id: 'c156', - content: "A comment by someone else on user u343's post", - contentExcerpt: "A comment by someone else on user u343's post", - deleted: false, - }, - ], - }, - ], - comments: [ - { - id: 'c155', - content: 'UNAVAILABLE', - contentExcerpt: 'UNAVAILABLE', - deleted: true, - }, - ], - }, - }, - errors: undefined, - } - await expect( - mutate({ mutation: deleteUserMutation, variables }), - ).resolves.toMatchObject(expectedResponse) - }) - }) - - describe('deletion of all posts and comments requested', () => { - beforeEach(() => { - variables = { ...variables, resource: ['Comment', 'Post'] } - }) - - it('marks posts and comments as deleted', async () => { - const expectedResponse = { - data: { - DeleteUser: { - id: 'u343', - name: 'UNAVAILABLE', - about: 'UNAVAILABLE', - deleted: true, - contributions: [ - { - id: 'p139', - content: 'UNAVAILABLE', - contentExcerpt: 'UNAVAILABLE', - deleted: true, - comments: [ - { - id: 'c156', - content: 'UNAVAILABLE', - contentExcerpt: 'UNAVAILABLE', - deleted: true, - }, - ], - }, - ], - comments: [ - { - id: 'c155', - content: 'UNAVAILABLE', - contentExcerpt: 'UNAVAILABLE', - deleted: true, - }, - ], - }, - }, - errors: undefined, - } - await expect( - mutate({ mutation: deleteUserMutation, variables }), - ).resolves.toMatchObject(expectedResponse) - }) - }) + describe('connected `SocialMedia` nodes', () => { + beforeEach(async () => { + const socialMedia = await Factory.build('socialMedia') + await socialMedia.relateTo(user, 'ownedBy') }) - describe('connected `EmailAddress` nodes', () => { - it('will be removed completely', async () => { - await expect(neode.all('EmailAddress')).resolves.toHaveLength(2) - await mutate({ mutation: deleteUserMutation, variables }) - - await expect(neode.all('EmailAddress')).resolves.toHaveLength(1) - }) - }) - - describe('connected `SocialMedia` nodes', () => { - beforeEach(async () => { - const socialMedia = await Factory.build('socialMedia') - await socialMedia.relateTo(user, 'ownedBy') - }) - - it('will be removed completely', async () => { - await expect(neode.all('SocialMedia')).resolves.toHaveLength(1) - await mutate({ mutation: deleteUserMutation, variables }) - await expect(neode.all('SocialMedia')).resolves.toHaveLength(0) - }) + it('will be removed completely', async () => { + await expect(neode.all('SocialMedia')).resolves.toHaveLength(1) + await mutate({ mutation: deleteUserMutation, variables }) + await expect(neode.all('SocialMedia')).resolves.toHaveLength(0) }) }) }) diff --git a/backend/src/schema/resolvers/users/location.spec.js b/backend/src/schema/resolvers/users/location.spec.js index 59442a9ca..3044e4b6f 100644 --- a/backend/src/schema/resolvers/users/location.spec.js +++ b/backend/src/schema/resolvers/users/location.spec.js @@ -51,8 +51,8 @@ const newlyCreatedNodesWithLocales = [ country: { id: expect.stringContaining('country'), type: 'country', - name: 'United States of America', - nameEN: 'United States of America', + name: 'United States', + nameEN: 'United States', nameDE: 'Vereinigte Staaten', namePT: 'Estados Unidos', nameES: 'Estados Unidos', diff --git a/backend/src/schema/types/schema.gql b/backend/src/schema/types/schema.gql index 23c2ded4d..b15179443 100644 --- a/backend/src/schema/types/schema.gql +++ b/backend/src/schema/types/schema.gql @@ -12,11 +12,6 @@ type Mutation { unfollowUser(id: ID!): User } -enum Deletable { - Post - Comment -} - enum ShoutTypeEnum { Post } diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index af525396b..e6e7191c5 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -185,6 +185,11 @@ type Query { ) } +enum Deletable { + Post + Comment +} + type Mutation { UpdateUser ( id: ID! diff --git a/webapp/components/ContentMenu/ContentMenu.spec.js b/webapp/components/ContentMenu/ContentMenu.spec.js index b49a1158a..37bef14f6 100644 --- a/webapp/components/ContentMenu/ContentMenu.spec.js +++ b/webapp/components/ContentMenu/ContentMenu.spec.js @@ -134,6 +134,47 @@ describe('ContentMenu.vue', () => { ], ]) }) + + it('can delete another user', () => { + getters['auth/user'] = () => { + return { id: 'some-user', slug: 'some-user' } + } + const wrapper = openContentMenu({ + resourceType: 'user', + resource: { + id: 'another-user', + slug: 'another-user', + }, + }) + wrapper + .findAll('.ds-menu-item') + .filter((item) => item.text() === 'settings.deleteUserAccount.name') + .at(0) + .trigger('click') + expect(wrapper.emitted('delete')).toEqual([ + [ + { + id: 'another-user', + slug: 'another-user', + }, + ], + ]) + }) + + it('can not delete the own account', () => { + const wrapper = openContentMenu({ + resourceType: 'user', + resource: { + id: 'some-user', + slug: 'some-user', + }, + }) + expect( + wrapper + .findAll('.ds-menu-item') + .filter((item) => item.text() === 'settings.deleteUserAccount.name'), + ).toEqual({}) + }) }) describe('owner of comment can', () => { diff --git a/webapp/components/ContentMenu/ContentMenu.vue b/webapp/components/ContentMenu/ContentMenu.vue index ef2df822b..88d83d059 100644 --- a/webapp/components/ContentMenu/ContentMenu.vue +++ b/webapp/components/ContentMenu/ContentMenu.vue @@ -191,6 +191,15 @@ export default { icon: 'user-times', }) } + if (this.isAdmin === true) { + routes.push({ + label: this.$t(`settings.deleteUserAccount.name`), + callback: () => { + this.$emit('delete', this.resource) + }, + icon: 'trash', + }) + } } } diff --git a/webapp/components/DeleteData/DeleteData.spec.js b/webapp/components/DeleteData/DeleteData.spec.js index 81a6c10c7..70f98424a 100644 --- a/webapp/components/DeleteData/DeleteData.spec.js +++ b/webapp/components/DeleteData/DeleteData.spec.js @@ -50,12 +50,20 @@ describe('DeleteData.vue', () => { }) describe('mount', () => { + const data = () => { + return { + currentUserCounts: { + contributionsCount: 4, + commentedCount: 2, + }, + } + } const Wrapper = () => { const store = new Vuex.Store({ getters, actions, }) - return mount(DeleteData, { mocks, localVue, store }) + return mount(DeleteData, { mocks, localVue, store, data }) } beforeEach(() => { diff --git a/webapp/components/DeleteData/DeleteData.vue b/webapp/components/DeleteData/DeleteData.vue index 36d166207..21b842fe6 100644 --- a/webapp/components/DeleteData/DeleteData.vue +++ b/webapp/components/DeleteData/DeleteData.vue @@ -8,7 +8,9 @@ {{ $t('settings.deleteUserAccount.pleaseConfirm', { confirm: currentUser.name }) }} -

{{ $t('settings.deleteUserAccount.accountDescription') }}

+

+ {{ $t('settings.deleteUserAccount.accountDescription') }} +