diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index b169e10fb..8486288ec 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -39,9 +39,9 @@ export const createGroupMutation = gql` } ` -export const enterGroupMutation = gql` +export const joinGroupMutation = gql` mutation ($id: ID!, $userId: ID!) { - EnterGroup(id: $id, userId: $userId) { + JoinGroup(id: $id, userId: $userId) { id name slug diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index ebe8a6020..f9b2d05da 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -5,7 +5,7 @@ import createServer from '../server' import faker from '@faker-js/faker' import Factory from '../db/factories' import { getNeode, getDriver } from '../db/neo4j' -import { createGroupMutation, enterGroupMutation } from './graphql/groups' +import { createGroupMutation, joinGroupMutation } from './graphql/groups' import { createPostMutation } from './graphql/posts' import { createCommentMutation } from './graphql/comments' @@ -402,14 +402,14 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g0', userId: 'u2', }, }), mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g0', userId: 'u3', @@ -434,28 +434,28 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g1', userId: 'u1', }, }), mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g1', userId: 'u5', }, }), mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g1', userId: 'u6', }, }), mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g1', userId: 'u7', @@ -480,28 +480,28 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g2', userId: 'u4', }, }), mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g2', userId: 'u5', }, }), mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g2', userId: 'u6', }, }), mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'g2', userId: 'u7', diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index dc54d5a29..afdc5501e 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -64,22 +64,26 @@ const isAllowedSeeingMembersOfGroup = rule({ const transactionResponse = await transaction.run( ` MATCH (group:Group {id: $groupId}) - OPTIONAL MATCH (admin:User {id: $userId})-[membership:MEMBER_OF]->(group) - WHERE membership.role IN ['admin', 'owner'] - RETURN group {.*}, admin {.*, myRoleInGroup: membership.role} + OPTIONAL MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group) + RETURN group {.*}, member {.*, myRoleInGroup: membership.role} `, { groupId, userId: user.id }, ) return { - admin: transactionResponse.records.map((record) => record.get('admin'))[0], + member: transactionResponse.records.map((record) => record.get('member'))[0], group: transactionResponse.records.map((record) => record.get('group'))[0], } }) try { - const { admin, group } = await readTxPromise - // Wolle: console.log('admin: ', admin) + const { member, group } = await readTxPromise + // Wolle: console.log('member: ', member) // console.log('group: ', group) - return group.groupType === 'public' || !!admin + return ( + group.groupType === 'public' || + (['closed', 'hidden'].includes(group.groupType) && + !!member && + ['usual', 'admin', 'owner'].includes(member.myRoleInGroup)) + ) } catch (error) { // Wolle: console.log('error: ', error) throw new Error(error) @@ -179,7 +183,7 @@ export default shield( SignupVerification: allow, UpdateUser: onlyYourself, CreateGroup: isAuthenticated, - EnterGroup: isAuthenticated, + JoinGroup: isAuthenticated, CreatePost: isAuthenticated, UpdatePost: isAuthor, DeletePost: isAuthor, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 91135a1db..c9a31fdc3 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -131,11 +131,11 @@ export default { session.close() } }, - EnterGroup: async (_parent, params, context, _resolveInfo) => { + JoinGroup: async (_parent, params, context, _resolveInfo) => { const { id: groupId, userId } = params const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { - const enterGroupCypher = ` + const joinGroupCypher = ` MATCH (member:User {id: $userId}), (group:Group {id: $groupId}) MERGE (member)-[membership:MEMBER_OF]->(group) ON CREATE SET @@ -148,7 +148,7 @@ export default { END RETURN member {.*, myRoleInGroup: membership.role} ` - const result = await transaction.run(enterGroupCypher, { groupId, userId }) + const result = await transaction.run(joinGroupCypher, { groupId, userId }) const [member] = await result.records.map((record) => record.get('member')) return member }) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 33aeaf2bd..87eb02dc0 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -2,7 +2,7 @@ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '../../db/factories' import { createGroupMutation, - enterGroupMutation, + joinGroupMutation, groupMemberQuery, groupQuery, } from '../../db/graphql/groups' @@ -90,6 +90,118 @@ afterEach(async () => { await cleanDatabase() }) +describe('CreateGroup', () => { + beforeEach(() => { + variables = { + ...variables, + id: 'g589', + name: 'The Best Group', + slug: 'the-group', + about: 'We will change the world!', + description: 'Some description' + descriptionAdditional100, + groupType: 'public', + actionRadius: 'regional', + categoryIds, + } + }) + + describe('unauthenticated', () => { + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: createGroupMutation, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('creates a group', async () => { + const expected = { + data: { + CreateGroup: { + name: 'The Best Group', + slug: 'the-group', + about: 'We will change the world!', + }, + }, + errors: undefined, + } + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + + it('assigns the authenticated user as owner', async () => { + const expected = { + data: { + CreateGroup: { + name: 'The Best Group', + myRole: 'owner', + }, + }, + errors: undefined, + } + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + + it('has "disabled" and "deleted" default to "false"', async () => { + const expected = { data: { CreateGroup: { disabled: false, deleted: false } } } + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + + describe('description', () => { + describe('length without HTML', () => { + describe('less then 100 chars', () => { + it('throws error: "Too view categories!"', async () => { + const { errors } = await mutate({ + mutation: createGroupMutation, + variables: { + ...variables, + description: + '0123456789' + + '0123456789', + }, + }) + expect(errors[0]).toHaveProperty('message', 'Description too short!') + }) + }) + }) + }) + + describe('categories', () => { + beforeEach(() => { + CONFIG.CATEGORIES_ACTIVE = true + }) + + describe('not even one', () => { + it('throws error: "Too view categories!"', async () => { + const { errors } = await mutate({ + mutation: createGroupMutation, + variables: { ...variables, categoryIds: null }, + }) + expect(errors[0]).toHaveProperty('message', 'Too view categories!') + }) + }) + + describe('four', () => { + it('throws error: "Too many categories!"', async () => { + const { errors } = await mutate({ + mutation: createGroupMutation, + variables: { ...variables, categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'] }, + }) + expect(errors[0]).toHaveProperty('message', 'Too many categories!') + }) + }) + }) + }) +}) + describe('Group', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { @@ -208,6 +320,244 @@ describe('Group', () => { }) }) +describe('JoinGroup', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + variables = { + id: 'not-existing-group', + userId: 'current-user', + } + const { errors } = await mutate({ mutation: joinGroupMutation, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('authenticated', () => { + let ownerOfClosedGroupUser + let ownerOfHiddenGroupUser + + beforeEach(async () => { + ownerOfClosedGroupUser = await Factory.build( + 'user', + { + id: 'owner-of-closed-group', + name: 'Owner Of Closed Group', + }, + { + email: 'owner-of-closed-group@example.org', + password: '1234', + }, + ) + ownerOfHiddenGroupUser = await Factory.build( + 'user', + { + id: 'owner-of-hidden-group', + name: 'Owner Of Hidden Group', + }, + { + email: 'owner-of-hidden-group@example.org', + password: '1234', + }, + ) + authenticatedUser = await ownerOfClosedGroupUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'closed-group', + name: 'Uninteresting Group', + about: 'We will change nothing!', + description: 'We love it like it is!?' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + categoryIds, + }, + }) + authenticatedUser = await ownerOfHiddenGroupUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'hidden-group', + name: 'Investigative Journalism Group', + about: 'We will change all.', + description: 'We research …' + descriptionAdditional100, + groupType: 'hidden', + actionRadius: 'global', + categoryIds, + }, + }) + authenticatedUser = await user.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'public-group', + name: 'The Best Group', + about: 'We will change the world!', + description: 'Some description' + descriptionAdditional100, + groupType: 'public', + actionRadius: 'regional', + categoryIds, + }, + }) + }) + + describe('public group', () => { + describe('entered by "owner-of-closed-group"', () => { + it('has "usual" as membership role', async () => { + variables = { + id: 'public-group', + userId: 'owner-of-closed-group', + } + const expected = { + data: { + JoinGroup: { + id: 'owner-of-closed-group', + myRoleInGroup: 'usual', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: joinGroupMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('entered by its owner', () => { + describe('does not create additional "MEMBER_OF" relation and therefore', () => { + it('has still "owner" as membership role', async () => { + variables = { + id: 'public-group', + userId: 'current-user', + } + const expected = { + data: { + JoinGroup: { + id: 'current-user', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: joinGroupMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + }) + + describe('closed group', () => { + describe('entered by "current-user"', () => { + it('has "pending" as membership role', async () => { + variables = { + id: 'closed-group', + userId: 'current-user', + } + const expected = { + data: { + JoinGroup: { + id: 'current-user', + myRoleInGroup: 'pending', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: joinGroupMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('entered by its owner', () => { + describe('does not create additional "MEMBER_OF" relation and therefore', () => { + it('has still "owner" as membership role', async () => { + variables = { + id: 'closed-group', + userId: 'owner-of-closed-group', + } + const expected = { + data: { + JoinGroup: { + id: 'owner-of-closed-group', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: joinGroupMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + }) + + describe('hidden group', () => { + describe('entered by "owner-of-closed-group"', () => { + it('has "pending" as membership role', async () => { + variables = { + id: 'hidden-group', + userId: 'owner-of-closed-group', + } + const expected = { + data: { + JoinGroup: { + id: 'owner-of-closed-group', + myRoleInGroup: 'pending', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: joinGroupMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('entered by its owner', () => { + describe('does not create additional "MEMBER_OF" relation and therefore', () => { + it('has still "owner" as membership role', async () => { + variables = { + id: 'hidden-group', + userId: 'owner-of-hidden-group', + } + const expected = { + data: { + JoinGroup: { + id: 'owner-of-hidden-group', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: joinGroupMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + }) + }) +}) + describe('GroupMember', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { @@ -301,28 +651,28 @@ describe('GroupMember', () => { }) // create additional memberships await mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'public-group', userId: 'owner-of-closed-group', }, }) await mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'public-group', userId: 'owner-of-hidden-group', }, }) await mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'closed-group', userId: 'current-user', }, }) await mutate({ - mutation: enterGroupMutation, + mutation: joinGroupMutation, variables: { id: 'hidden-group', userId: 'owner-of-closed-group', @@ -338,7 +688,11 @@ describe('GroupMember', () => { }) describe('query group members', () => { - describe('by owner', () => { + describe('by owner "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + it('finds all members', async () => { const expected = { data: { @@ -368,493 +722,265 @@ describe('GroupMember', () => { }) }) - describe('by "other-user"', () => { - it.only('throws authorization error', async () => { - authenticatedUser = await otherUser.toJson() - const result = await query({ query: groupMemberQuery, variables }) - console.log('result: ', result) - // Wolle: const { errors } = await query({ query: groupMemberQuery, variables }) - // expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + describe('by usual member "owner-of-closed-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfClosedGroupUser.toJson() }) - }) - }) - describe('entered by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { - variables = { - id: 'public-group', - userId: 'current-user', - } + it('finds all members', async () => { const expected = { data: { - EnterGroup: { - id: 'current-user', - myRoleInGroup: 'owner', - }, + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'owner', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'usual', + }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'usual', + }), + ]), }, errors: undefined, } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) - }) - }) - }) - }) - - describe('closed group', () => { - describe('entered by "current-user"', () => { - it('has "pending" as membership role', async () => { - variables = { - id: 'closed-group', - userId: 'current-user', - } - const expected = { - data: { - EnterGroup: { - id: 'current-user', - myRoleInGroup: 'pending', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: enterGroupMutation, + const result = await mutate({ + mutation: groupMemberQuery, variables, - }), - ).resolves.toMatchObject(expected) - }) - }) - - describe('entered by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { - variables = { - id: 'closed-group', - userId: 'owner-of-closed-group', - } - const expected = { - data: { - EnterGroup: { - id: 'owner-of-closed-group', - myRoleInGroup: 'owner', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) - }) - }) - }) - }) - - describe('hidden group', () => { - describe('entered by "owner-of-closed-group"', () => { - it('has "pending" as membership role', async () => { - variables = { - id: 'hidden-group', - userId: 'owner-of-closed-group', - } - const expected = { - data: { - EnterGroup: { - id: 'owner-of-closed-group', - myRoleInGroup: 'pending', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) - }) - }) - - describe('entered by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { - variables = { - id: 'hidden-group', - userId: 'owner-of-hidden-group', - } - const expected = { - data: { - EnterGroup: { - id: 'owner-of-hidden-group', - myRoleInGroup: 'owner', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) - }) - }) - }) - }) - }) -}) - -describe('CreateGroup', () => { - beforeEach(() => { - variables = { - ...variables, - id: 'g589', - name: 'The Best Group', - slug: 'the-group', - about: 'We will change the world!', - description: 'Some description' + descriptionAdditional100, - groupType: 'public', - actionRadius: 'regional', - categoryIds, - } - }) - - describe('unauthenticated', () => { - it('throws authorization error', async () => { - const { errors } = await mutate({ mutation: createGroupMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('authenticated', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - it('creates a group', async () => { - const expected = { - data: { - CreateGroup: { - name: 'The Best Group', - slug: 'the-group', - about: 'We will change the world!', - }, - }, - errors: undefined, - } - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( - expected, - ) - }) - - it('assigns the authenticated user as owner', async () => { - const expected = { - data: { - CreateGroup: { - name: 'The Best Group', - myRole: 'owner', - }, - }, - errors: undefined, - } - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( - expected, - ) - }) - - it('has "disabled" and "deleted" default to "false"', async () => { - const expected = { data: { CreateGroup: { disabled: false, deleted: false } } } - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( - expected, - ) - }) - - describe('description', () => { - describe('length without HTML', () => { - describe('less then 100 chars', () => { - it('throws error: "Too view categories!"', async () => { - const { errors } = await mutate({ - mutation: createGroupMutation, - variables: { - ...variables, - description: - '0123456789' + - '0123456789', - }, }) - expect(errors[0]).toHaveProperty('message', 'Description too short!') + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(3) }) }) - }) - }) - describe('categories', () => { - beforeEach(() => { - CONFIG.CATEGORIES_ACTIVE = true - }) - - describe('not even one', () => { - it('throws error: "Too view categories!"', async () => { - const { errors } = await mutate({ - mutation: createGroupMutation, - variables: { ...variables, categoryIds: null }, + describe('by none member "other-user"', () => { + beforeEach(async () => { + authenticatedUser = await otherUser.toJson() }) - expect(errors[0]).toHaveProperty('message', 'Too view categories!') - }) - }) - describe('four', () => { - it('throws error: "Too many categories!"', async () => { - const { errors } = await mutate({ - mutation: createGroupMutation, - variables: { ...variables, categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'] }, - }) - expect(errors[0]).toHaveProperty('message', 'Too many categories!') - }) - }) - }) - }) -}) - -describe('EnterGroup', () => { - describe('unauthenticated', () => { - it('throws authorization error', async () => { - variables = { - id: 'not-existing-group', - userId: 'current-user', - } - const { errors } = await mutate({ mutation: enterGroupMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('authenticated', () => { - let ownerOfClosedGroupUser - let ownerOfHiddenGroupUser - - beforeEach(async () => { - ownerOfClosedGroupUser = await Factory.build( - 'user', - { - id: 'owner-of-closed-group', - name: 'Owner Of Closed Group', - }, - { - email: 'owner-of-closed-group@example.org', - password: '1234', - }, - ) - ownerOfHiddenGroupUser = await Factory.build( - 'user', - { - id: 'owner-of-hidden-group', - name: 'Owner Of Hidden Group', - }, - { - email: 'owner-of-hidden-group@example.org', - password: '1234', - }, - ) - authenticatedUser = await ownerOfClosedGroupUser.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'closed-group', - name: 'Uninteresting Group', - about: 'We will change nothing!', - description: 'We love it like it is!?' + descriptionAdditional100, - groupType: 'closed', - actionRadius: 'national', - categoryIds, - }, - }) - authenticatedUser = await ownerOfHiddenGroupUser.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'hidden-group', - name: 'Investigative Journalism Group', - about: 'We will change all.', - description: 'We research …' + descriptionAdditional100, - groupType: 'hidden', - actionRadius: 'global', - categoryIds, - }, - }) - authenticatedUser = await user.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'public-group', - name: 'The Best Group', - about: 'We will change the world!', - description: 'Some description' + descriptionAdditional100, - groupType: 'public', - actionRadius: 'regional', - categoryIds, - }, - }) - }) - - describe('public group', () => { - describe('entered by "owner-of-closed-group"', () => { - it('has "usual" as membership role', async () => { - variables = { - id: 'public-group', - userId: 'owner-of-closed-group', - } - const expected = { - data: { - EnterGroup: { - id: 'owner-of-closed-group', - myRoleInGroup: 'usual', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) - }) - }) - - describe('entered by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { - variables = { - id: 'public-group', - userId: 'current-user', - } + it('finds all members', async () => { const expected = { data: { - EnterGroup: { - id: 'current-user', - myRoleInGroup: 'owner', - }, + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'owner', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'usual', + }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'usual', + }), + ]), }, errors: undefined, } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(3) }) }) }) }) describe('closed group', () => { - describe('entered by "current-user"', () => { - it('has "pending" as membership role', async () => { - variables = { - id: 'closed-group', - userId: 'current-user', - } - const expected = { - data: { - EnterGroup: { - id: 'current-user', - myRoleInGroup: 'pending', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) - }) + beforeEach(async () => { + variables = { + id: 'closed-group', + } }) - describe('entered by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { - variables = { - id: 'closed-group', - userId: 'owner-of-closed-group', - } + describe('query group members', () => { + describe('by owner "owner-of-closed-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfClosedGroupUser.toJson() + }) + + it('finds all members', async () => { const expected = { data: { - EnterGroup: { - id: 'owner-of-closed-group', - myRoleInGroup: 'owner', - }, + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'owner', + }), + ]), }, errors: undefined, } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(2) + }) + }) + + // needs 'SwitchGroupMemberRole' + describe.skip('by usual member "owner-of-hidden-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfHiddenGroupUser.toJson() + }) + + it('finds all members', async () => { + const expected = { + data: { + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'owner', + }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'usual', + }), + ]), + }, + errors: undefined, + } + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(3) + }) + }) + + describe('by pending member "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('throws authorization error', async () => { + const { errors } = await query({ query: groupMemberQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('by none member "other-user"', () => { + beforeEach(async () => { + authenticatedUser = await otherUser.toJson() + }) + + it('throws authorization error', async () => { + const { errors } = await query({ query: groupMemberQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) }) }) describe('hidden group', () => { - describe('entered by "owner-of-closed-group"', () => { - it('has "pending" as membership role', async () => { - variables = { - id: 'hidden-group', - userId: 'owner-of-closed-group', - } - const expected = { - data: { - EnterGroup: { - id: 'owner-of-closed-group', - myRoleInGroup: 'pending', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) - }) + beforeEach(async () => { + variables = { + id: 'hidden-group', + } }) - describe('entered by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { - variables = { - id: 'hidden-group', - userId: 'owner-of-hidden-group', - } + describe('query group members', () => { + describe('by owner "owner-of-hidden-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfHiddenGroupUser.toJson() + }) + + it('finds all members', async () => { const expected = { data: { - EnterGroup: { - id: 'owner-of-hidden-group', - myRoleInGroup: 'owner', - }, + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'owner', + }), + ]), }, errors: undefined, } - await expect( - mutate({ - mutation: enterGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(2) + }) + }) + + // needs 'SwitchGroupMemberRole' + describe.skip('by usual member "owner-of-closed-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfClosedGroupUser.toJson() + }) + + it('finds all members', async () => { + const expected = { + data: { + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'usual', + }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'owner', + }), + ]), + }, + errors: undefined, + } + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(3) + }) + }) + + describe('by pending member "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('throws authorization error', async () => { + const { errors } = await query({ query: groupMemberQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('by none member "other-user"', () => { + beforeEach(async () => { + authenticatedUser = await otherUser.toJson() + }) + + it('throws authorization error', async () => { + const { errors } = await query({ query: groupMemberQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) }) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index cf44894db..fad1b7e58 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -114,7 +114,7 @@ type Mutation { DeleteGroup(id: ID!): Group - EnterGroup( + JoinGroup( id: ID! userId: ID! ): User