From 27b74eb9e1ba5e41c43f4eea9c40db59e5cdb76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 17 Aug 2022 12:11:52 +0200 Subject: [PATCH 01/58] Implement 'GroupMember' resolver, a first step --- .../src/middleware/permissionsMiddleware.js | 32 +++++ backend/src/schema/resolvers/groups.js | 22 ++++ backend/src/schema/resolvers/groups.spec.js | 122 ++++++++++++++++++ backend/src/schema/types/type/Group.gql | 8 ++ backend/src/schema/types/type/User.gql | 2 + 5 files changed, 186 insertions(+) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 99dcfc0cd..4c73624b0 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -52,6 +52,37 @@ const isMySocialMedia = rule({ return socialMedia.ownedBy.node.id === user.id }) +const isAllowSeeingMembersOfGroup = rule({ + cache: 'no_cache', +})(async (_parent, args, { user, driver }) => { + if (!user) return false + const { id: groupId } = args + const session = driver.session() + const readTxPromise = session.readTransaction(async (transaction) => { + const transactionResponse = await transaction.run( + ` + MATCH (group:Group {id: $groupId}) + OPTIONAL MATCH (admin {id:User $userId})-[membership:MEMBER_OF]->(group) + WHERE membership.role IN ['admin', 'owner'] + RETURN group, admin + `, + { groupId, userId: user.id }, + ) + return { + admin: transactionResponse.records.map((record) => record.get('admin')), + group: transactionResponse.records.map((record) => record.get('group')), + } + }) + try { + const [{ admin, group }] = await readTxPromise + return group.groupType === 'public' || !!admin + } catch (error) { + throw new Error(error) + } finally { + session.close() + } +}) + const isAuthor = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { @@ -115,6 +146,7 @@ export default shield( statistics: allow, currentUser: allow, Group: isAuthenticated, + GroupMember: isAllowSeeingMembersOfGroup, Post: allow, profilePagePosts: allow, Comment: allow, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 5737f5505..d27d9b5e4 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -46,6 +46,28 @@ export default { session.close() } }, + GroupMember: async (_object, params, context, _resolveInfo) => { + const { id: groupId } = params + const session = context.driver.session() + const readTxResultPromise = session.readTransaction(async (txc) => { + const groupMemberCypher = ` + MATCH (user:User {id: $userId})-[membership:MEMBER_OF]->(:Group {id: $groupId}) + RETURN user {.*, myRoleInGroup: membership.role} + ` + const result = await txc.run(groupMemberCypher, { + groupId, + userId: context.user.id, + }) + return result.records.map((record) => record.get('user')) + }) + try { + return await readTxResultPromise + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, }, Mutation: { CreateGroup: async (_parent, params, context, _resolveInfo) => { diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 707558a06..706e27748 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -207,6 +207,128 @@ describe('Group', () => { }) }) +describe('GroupMember', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + const { errors } = await query({ query: groupQuery, variables: {} }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('authenticated', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + let otherUser + + beforeEach(async () => { + otherUser = await Factory.build( + 'user', + { + id: 'other-user', + name: 'Other TestUser', + }, + { + email: 'test2@example.org', + password: '1234', + }, + ) + authenticatedUser = await otherUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'others-group', + name: 'Uninteresting Group', + about: 'We will change nothing!', + description: 'We love it like it is!?' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'global', + categoryIds, + }, + }) + authenticatedUser = await user.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'my-group', + name: 'The Best Group', + about: 'We will change the world!', + description: 'Some description' + descriptionAdditional100, + groupType: 'public', + actionRadius: 'regional', + categoryIds, + }, + }) + }) + + describe('query group members', () => { + describe('by owner', () => { + it.only('finds all members', async () => { + const expected = { + data: { + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }), + // Wolle: expect.objectContaining({ + // id: 'others-group', + // slug: 'uninteresting-group', + // myRole: null, + // }), + ]), + }, + errors: undefined, + } + await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected) + }) + }) + + describe('isMember = true', () => { + it('finds only groups where user is member', async () => { + const expected = { + data: { + Group: [ + { + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }, + ], + }, + errors: undefined, + } + await expect( + query({ query: groupQuery, variables: { isMember: true } }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('isMember = false', () => { + it('finds only groups where user is not(!) member', async () => { + const expected = { + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), + }, + errors: undefined, + } + await expect( + query({ query: groupQuery, variables: { isMember: false } }), + ).resolves.toMatchObject(expected) + }) + }) + }) + }) +}) + describe('CreateGroup', () => { beforeEach(() => { variables = { diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index 3165b4a44..fd53d48b3 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -74,6 +74,14 @@ type Query { filter: _GroupFilter ): [Group] + GroupMember( + id: ID + first: Int + offset: Int + orderBy: [_GroupOrdering] + filter: _GroupFilter + ): [User] + AvailableGroupTypes: [GroupType]! AvailableGroupActionRadii: [GroupActionRadius]! diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index a25e51079..4219cd00e 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -114,6 +114,8 @@ type User { badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") emotions: [EMOTED] + + myRoleInGroup: GroupMemberRole } From 25ed30dba168f901f4f09a6c3d03c27c2692d3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Aug 2022 10:12:11 +0200 Subject: [PATCH 02/58] Implement 'EnterGroup' resolver --- backend/src/db/graphql/groups.js | 22 + .../src/middleware/permissionsMiddleware.js | 5 +- backend/src/schema/resolvers/groups.js | 34 +- backend/src/schema/resolvers/groups.spec.js | 479 +++++++++++++----- backend/src/schema/types/type/Group.gql | 12 +- 5 files changed, 423 insertions(+), 129 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 2a611f324..b169e10fb 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -39,6 +39,17 @@ export const createGroupMutation = gql` } ` +export const enterGroupMutation = gql` + mutation ($id: ID!, $userId: ID!) { + EnterGroup(id: $id, userId: $userId) { + id + name + slug + myRoleInGroup + } + } +` + // ------ queries export const groupQuery = gql` @@ -93,3 +104,14 @@ export const groupQuery = gql` } } ` + +export const groupMemberQuery = gql` + query ($id: ID!, $first: Int, $offset: Int, $orderBy: [_UserOrdering], $filter: _UserFilter) { + GroupMember(id: $id, first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) { + id + name + slug + myRoleInGroup + } + } +` diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 4c73624b0..9dcf35476 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -62,9 +62,9 @@ const isAllowSeeingMembersOfGroup = rule({ const transactionResponse = await transaction.run( ` MATCH (group:Group {id: $groupId}) - OPTIONAL MATCH (admin {id:User $userId})-[membership:MEMBER_OF]->(group) + OPTIONAL MATCH (admin:User {id: $userId})-[membership:MEMBER_OF]->(group) WHERE membership.role IN ['admin', 'owner'] - RETURN group, admin + RETURN group, admin {.*, myRoleInGroup: membership.role} `, { groupId, userId: user.id }, ) @@ -174,6 +174,7 @@ export default shield( SignupVerification: allow, UpdateUser: onlyYourself, CreateGroup: isAuthenticated, + EnterGroup: isAuthenticated, CreatePost: isAuthenticated, UpdatePost: isAuthor, DeletePost: isAuthor, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index d27d9b5e4..b64484d37 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -109,7 +109,7 @@ export default { MERGE (owner)-[:CREATED]->(group) MERGE (owner)-[membership:MEMBER_OF]->(group) SET membership.createdAt = toString(datetime()) - SET membership.updatedAt = toString(datetime()) + SET membership.updatedAt = membership.createdAt SET membership.role = 'owner' ${categoriesCypher} RETURN group {.*, myRole: membership.role} @@ -122,8 +122,7 @@ export default { return group }) try { - const group = await writeTxResultPromise - return group + return await writeTxResultPromise } catch (error) { if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') throw new UserInputError('Group with this slug already exists!') @@ -132,6 +131,35 @@ export default { session.close() } }, + EnterGroup: async (_parent, params, context, _resolveInfo) => { + const { id: groupId, userId } = params + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const enterGroupCypher = ` + MATCH (member:User {id: $userId}), (group:Group {id: $groupId}) + MERGE (member)-[membership:MEMBER_OF]->(group) + ON CREATE SET + membership.createdAt = toString(datetime()), + membership.updatedAt = membership.createdAt, + membership.role = + CASE WHEN group.groupType = 'public' + THEN 'usual' + ELSE 'pending' + END + RETURN member {.*, myRoleInGroup: membership.role} + ` + const result = await transaction.run(enterGroupCypher, { groupId, userId }) + const [member] = await result.records.map((record) => record.get('member')) + return member + }) + try { + return await writeTxResultPromise + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, }, Group: { ...Resolver('Group', { diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 706e27748..81223a584 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -1,6 +1,11 @@ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '../../db/factories' -import { createGroupMutation, groupQuery } from '../../db/graphql/groups' +import { + createGroupMutation, + enterGroupMutation, + groupMemberQuery, + groupQuery, +} from '../../db/graphql/groups' import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' import CONFIG from '../../config' @@ -94,10 +99,6 @@ describe('Group', () => { }) describe('authenticated', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - let otherUser beforeEach(async () => { @@ -207,127 +208,127 @@ describe('Group', () => { }) }) -describe('GroupMember', () => { - describe('unauthenticated', () => { - it('throws authorization error', async () => { - const { errors } = await query({ query: groupQuery, variables: {} }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) +// describe('GroupMember', () => { +// describe('unauthenticated', () => { +// it('throws authorization error', async () => { +// const { errors } = await query({ query: groupMemberQuery, variables: {} }) +// expect(errors[0]).toHaveProperty('message', 'Not Authorised!') +// }) +// }) - describe('authenticated', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) +// describe('authenticated', () => { +// beforeEach(async () => { +// authenticatedUser = await user.toJson() +// }) - let otherUser +// let otherUser - beforeEach(async () => { - otherUser = await Factory.build( - 'user', - { - id: 'other-user', - name: 'Other TestUser', - }, - { - email: 'test2@example.org', - password: '1234', - }, - ) - authenticatedUser = await otherUser.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'others-group', - name: 'Uninteresting Group', - about: 'We will change nothing!', - description: 'We love it like it is!?' + descriptionAdditional100, - groupType: 'closed', - actionRadius: 'global', - categoryIds, - }, - }) - authenticatedUser = await user.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'my-group', - name: 'The Best Group', - about: 'We will change the world!', - description: 'Some description' + descriptionAdditional100, - groupType: 'public', - actionRadius: 'regional', - categoryIds, - }, - }) - }) +// beforeEach(async () => { +// otherUser = await Factory.build( +// 'user', +// { +// id: 'other-user', +// name: 'Other TestUser', +// }, +// { +// email: 'test2@example.org', +// password: '1234', +// }, +// ) +// authenticatedUser = await otherUser.toJson() +// await mutate({ +// mutation: createGroupMutation, +// variables: { +// id: 'others-group', +// name: 'Uninteresting Group', +// about: 'We will change nothing!', +// description: 'We love it like it is!?' + descriptionAdditional100, +// groupType: 'closed', +// actionRadius: 'global', +// categoryIds, +// }, +// }) +// authenticatedUser = await user.toJson() +// await mutate({ +// mutation: createGroupMutation, +// variables: { +// id: 'my-group', +// name: 'The Best Group', +// about: 'We will change the world!', +// description: 'Some description' + descriptionAdditional100, +// groupType: 'public', +// actionRadius: 'regional', +// categoryIds, +// }, +// }) +// }) - describe('query group members', () => { - describe('by owner', () => { - it.only('finds all members', async () => { - const expected = { - data: { - GroupMember: expect.arrayContaining([ - expect.objectContaining({ - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }), - // Wolle: expect.objectContaining({ - // id: 'others-group', - // slug: 'uninteresting-group', - // myRole: null, - // }), - ]), - }, - errors: undefined, - } - await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected) - }) - }) +// describe('query group members', () => { +// describe('by owner', () => { +// it.only('finds all members', async () => { +// const expected = { +// data: { +// GroupMember: expect.arrayContaining([ +// expect.objectContaining({ +// id: 'my-group', +// slug: 'the-best-group', +// myRole: 'owner', +// }), +// // Wolle: expect.objectContaining({ +// // id: 'others-group', +// // slug: 'uninteresting-group', +// // myRole: null, +// // }), +// ]), +// }, +// errors: undefined, +// } +// await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected) +// }) +// }) - describe('isMember = true', () => { - it('finds only groups where user is member', async () => { - const expected = { - data: { - Group: [ - { - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }, - ], - }, - errors: undefined, - } - await expect( - query({ query: groupQuery, variables: { isMember: true } }), - ).resolves.toMatchObject(expected) - }) - }) +// describe('isMember = true', () => { +// it('finds only groups where user is member', async () => { +// const expected = { +// data: { +// Group: [ +// { +// id: 'my-group', +// slug: 'the-best-group', +// myRole: 'owner', +// }, +// ], +// }, +// errors: undefined, +// } +// await expect( +// query({ query: groupQuery, variables: { isMember: true } }), +// ).resolves.toMatchObject(expected) +// }) +// }) - describe('isMember = false', () => { - it('finds only groups where user is not(!) member', async () => { - const expected = { - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), - }, - errors: undefined, - } - await expect( - query({ query: groupQuery, variables: { isMember: false } }), - ).resolves.toMatchObject(expected) - }) - }) - }) - }) -}) +// describe('isMember = false', () => { +// it('finds only groups where user is not(!) member', async () => { +// const expected = { +// data: { +// Group: expect.arrayContaining([ +// expect.objectContaining({ +// id: 'others-group', +// slug: 'uninteresting-group', +// myRole: null, +// }), +// ]), +// }, +// errors: undefined, +// } +// await expect( +// query({ query: groupQuery, variables: { isMember: false } }), +// ).resolves.toMatchObject(expected) +// }) +// }) +// }) +// }) +// }) describe('CreateGroup', () => { beforeEach(() => { @@ -440,3 +441,241 @@ describe('CreateGroup', () => { }) }) }) + +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', + } + const expected = { + data: { + EnterGroup: { + id: 'current-user', + myRoleInGroup: 'owner', + }, + }, + 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, + 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) + }) + }) + }) + }) + }) +}) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index fd53d48b3..cf44894db 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -71,15 +71,14 @@ type Query { first: Int offset: Int orderBy: [_GroupOrdering] - filter: _GroupFilter ): [Group] GroupMember( - id: ID + id: ID! first: Int offset: Int - orderBy: [_GroupOrdering] - filter: _GroupFilter + orderBy: [_UserOrdering] + filter: _UserFilter ): [User] AvailableGroupTypes: [GroupType]! @@ -114,4 +113,9 @@ type Mutation { ): Group DeleteGroup(id: ID!): Group + + EnterGroup( + id: ID! + userId: ID! + ): User } From 613f91b0371825a6b093e1fc0bb48c680709235a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Aug 2022 11:47:27 +0200 Subject: [PATCH 03/58] Add seedings for group memberships --- backend/src/db/seed.js | 78 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index e41ef1abc..ebe8a6020 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 } from './graphql/groups' +import { createGroupMutation, enterGroupMutation } from './graphql/groups' import { createPostMutation } from './graphql/posts' import { createCommentMutation } from './graphql/comments' @@ -400,6 +400,22 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), ]) + await Promise.all([ + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g0', + userId: 'u2', + }, + }), + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g0', + userId: 'u3', + }, + }), + ]) authenticatedUser = await jennyRostock.toJson() await Promise.all([ @@ -416,6 +432,36 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), ]) + await Promise.all([ + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g1', + userId: 'u1', + }, + }), + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g1', + userId: 'u5', + }, + }), + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g1', + userId: 'u6', + }, + }), + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g1', + userId: 'u7', + }, + }), + ]) authenticatedUser = await bobDerBaumeister.toJson() await Promise.all([ @@ -432,6 +478,36 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), ]) + await Promise.all([ + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g2', + userId: 'u4', + }, + }), + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g2', + userId: 'u5', + }, + }), + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g2', + userId: 'u6', + }, + }), + mutate({ + mutation: enterGroupMutation, + variables: { + id: 'g2', + userId: 'u7', + }, + }), + ]) // Create Posts From d36f0eab57887569d439680e3770ba9ea0e41209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 18 Aug 2022 11:48:26 +0200 Subject: [PATCH 04/58] Add tests for 'GroupMember' resolver, a start --- .../src/middleware/permissionsMiddleware.js | 17 +- backend/src/schema/resolvers/groups.js | 4 +- backend/src/schema/resolvers/groups.spec.js | 412 +++++++++++++----- 3 files changed, 310 insertions(+), 123 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 9dcf35476..dc54d5a29 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -52,11 +52,13 @@ const isMySocialMedia = rule({ return socialMedia.ownedBy.node.id === user.id }) -const isAllowSeeingMembersOfGroup = rule({ +const isAllowedSeeingMembersOfGroup = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { if (!user) return false const { id: groupId } = args + // Wolle: console.log('groupId: ', groupId) + // console.log('user.id: ', user.id) const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { const transactionResponse = await transaction.run( @@ -64,19 +66,22 @@ const isAllowSeeingMembersOfGroup = rule({ 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} + RETURN group {.*}, admin {.*, myRoleInGroup: membership.role} `, { groupId, userId: user.id }, ) return { - admin: transactionResponse.records.map((record) => record.get('admin')), - group: transactionResponse.records.map((record) => record.get('group')), + admin: transactionResponse.records.map((record) => record.get('admin'))[0], + group: transactionResponse.records.map((record) => record.get('group'))[0], } }) try { - const [{ admin, group }] = await readTxPromise + const { admin, group } = await readTxPromise + // Wolle: console.log('admin: ', admin) + // console.log('group: ', group) return group.groupType === 'public' || !!admin } catch (error) { + // Wolle: console.log('error: ', error) throw new Error(error) } finally { session.close() @@ -146,7 +151,7 @@ export default shield( statistics: allow, currentUser: allow, Group: isAuthenticated, - GroupMember: isAllowSeeingMembersOfGroup, + GroupMember: isAllowedSeeingMembersOfGroup, Post: allow, profilePagePosts: allow, Comment: allow, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index b64484d37..91135a1db 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -48,15 +48,15 @@ export default { }, GroupMember: async (_object, params, context, _resolveInfo) => { const { id: groupId } = params + // Wolle: console.log('groupId: ', groupId) const session = context.driver.session() const readTxResultPromise = session.readTransaction(async (txc) => { const groupMemberCypher = ` - MATCH (user:User {id: $userId})-[membership:MEMBER_OF]->(:Group {id: $groupId}) + MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId}) RETURN user {.*, myRoleInGroup: membership.role} ` const result = await txc.run(groupMemberCypher, { groupId, - userId: context.user.id, }) return result.records.map((record) => record.get('user')) }) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 81223a584..33aeaf2bd 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -208,127 +208,309 @@ describe('Group', () => { }) }) -// describe('GroupMember', () => { -// describe('unauthenticated', () => { -// it('throws authorization error', async () => { -// const { errors } = await query({ query: groupMemberQuery, variables: {} }) -// expect(errors[0]).toHaveProperty('message', 'Not Authorised!') -// }) -// }) +describe('GroupMember', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + variables = { + id: 'not-existing-group', + } + const { errors } = await query({ query: groupMemberQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) -// describe('authenticated', () => { -// beforeEach(async () => { -// authenticatedUser = await user.toJson() -// }) + describe('authenticated', () => { + let otherUser + let ownerOfClosedGroupUser + let ownerOfHiddenGroupUser -// let otherUser + beforeEach(async () => { + // create users + otherUser = await Factory.build( + 'user', + { + id: 'other-user', + name: 'Other TestUser', + }, + { + email: 'test2@example.org', + password: '1234', + }, + ) + 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', + }, + ) + // create groups + 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, + }, + }) + // create additional memberships + await mutate({ + mutation: enterGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-closed-group', + }, + }) + await mutate({ + mutation: enterGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-hidden-group', + }, + }) + await mutate({ + mutation: enterGroupMutation, + variables: { + id: 'closed-group', + userId: 'current-user', + }, + }) + await mutate({ + mutation: enterGroupMutation, + variables: { + id: 'hidden-group', + userId: 'owner-of-closed-group', + }, + }) + }) -// beforeEach(async () => { -// otherUser = await Factory.build( -// 'user', -// { -// id: 'other-user', -// name: 'Other TestUser', -// }, -// { -// email: 'test2@example.org', -// password: '1234', -// }, -// ) -// authenticatedUser = await otherUser.toJson() -// await mutate({ -// mutation: createGroupMutation, -// variables: { -// id: 'others-group', -// name: 'Uninteresting Group', -// about: 'We will change nothing!', -// description: 'We love it like it is!?' + descriptionAdditional100, -// groupType: 'closed', -// actionRadius: 'global', -// categoryIds, -// }, -// }) -// authenticatedUser = await user.toJson() -// await mutate({ -// mutation: createGroupMutation, -// variables: { -// id: 'my-group', -// name: 'The Best Group', -// about: 'We will change the world!', -// description: 'Some description' + descriptionAdditional100, -// groupType: 'public', -// actionRadius: 'regional', -// categoryIds, -// }, -// }) -// }) + describe('public group', () => { + beforeEach(async () => { + variables = { + id: 'public-group', + } + }) -// describe('query group members', () => { -// describe('by owner', () => { -// it.only('finds all members', async () => { -// const expected = { -// data: { -// GroupMember: expect.arrayContaining([ -// expect.objectContaining({ -// id: 'my-group', -// slug: 'the-best-group', -// myRole: 'owner', -// }), -// // Wolle: expect.objectContaining({ -// // id: 'others-group', -// // slug: 'uninteresting-group', -// // myRole: null, -// // }), -// ]), -// }, -// errors: undefined, -// } -// await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected) -// }) -// }) + describe('query group members', () => { + describe('by owner', () => { + it('finds all members', async () => { + const expected = { + data: { + 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, + } + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(3) + }) + }) -// describe('isMember = true', () => { -// it('finds only groups where user is member', async () => { -// const expected = { -// data: { -// Group: [ -// { -// id: 'my-group', -// slug: 'the-best-group', -// myRole: 'owner', -// }, -// ], -// }, -// errors: undefined, -// } -// await expect( -// query({ query: groupQuery, variables: { isMember: true } }), -// ).resolves.toMatchObject(expected) -// }) -// }) + 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('isMember = false', () => { -// it('finds only groups where user is not(!) member', async () => { -// const expected = { -// data: { -// Group: expect.arrayContaining([ -// expect.objectContaining({ -// id: 'others-group', -// slug: 'uninteresting-group', -// myRole: null, -// }), -// ]), -// }, -// errors: undefined, -// } -// await expect( -// query({ query: groupQuery, variables: { isMember: false } }), -// ).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: { + EnterGroup: { + id: 'current-user', + myRoleInGroup: 'owner', + }, + }, + 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, + 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(() => { From 14620b00eb1e63b23c3d83399de6a4e735307c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 19 Aug 2022 10:47:17 +0200 Subject: [PATCH 05/58] Add tests for 'JoinGroup' and 'GroupMember' resolver --- backend/src/db/graphql/groups.js | 4 +- backend/src/db/seed.js | 22 +- .../src/middleware/permissionsMiddleware.js | 20 +- backend/src/schema/resolvers/groups.js | 6 +- backend/src/schema/resolvers/groups.spec.js | 1022 +++++++++-------- backend/src/schema/types/type/Group.gql | 2 +- 6 files changed, 603 insertions(+), 473 deletions(-) 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 From 695a71bf2574c02ed0c8a5ed78503b375318eb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Sat, 20 Aug 2022 09:43:37 +0200 Subject: [PATCH 06/58] Implement 'SwitchGroupMemberRole' resolver and write the beginning to test it --- backend/src/db/graphql/groups.js | 11 + .../src/middleware/permissionsMiddleware.js | 85 +++- backend/src/schema/resolvers/groups.js | 27 ++ backend/src/schema/resolvers/groups.spec.js | 438 +++++++++++++++--- backend/src/schema/types/type/Group.gql | 6 + 5 files changed, 507 insertions(+), 60 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 8486288ec..41780f7cd 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -50,6 +50,17 @@ export const joinGroupMutation = gql` } ` +export const switchGroupMemberRoleMutation = gql` + mutation ($id: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { + SwitchGroupMemberRole(id: $id, userId: $userId, roleInGroup: $roleInGroup) { + id + name + slug + myRoleInGroup + } + } +` + // ------ queries export const groupQuery = gql` diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index afdc5501e..11ca956b6 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -79,10 +79,82 @@ const isAllowedSeeingMembersOfGroup = rule({ // Wolle: console.log('member: ', member) // console.log('group: ', group) return ( - group.groupType === 'public' || - (['closed', 'hidden'].includes(group.groupType) && - !!member && - ['usual', 'admin', 'owner'].includes(member.myRoleInGroup)) + !!group && + (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) + } finally { + session.close() + } +}) + +const isAllowedToSwitchGroupMemberRole = rule({ + cache: 'no_cache', +})(async (_parent, args, { user, driver }) => { + if (!user) return false + const adminId = user.id + const { id: groupId, userId, roleInGroup } = args + // Wolle: + // console.log('adminId: ', adminId) + // console.log('groupId: ', groupId) + // console.log('userId: ', userId) + // console.log('roleInGroup: ', roleInGroup) + const session = driver.session() + const readTxPromise = session.readTransaction(async (transaction) => { + const transactionResponse = await transaction.run( + ` + MATCH (admin:User {id: $adminId})-[adminMembership:MEMBER_OF]->(group:Group {id: $groupId})<-[userMembership:MEMBER_OF]-(member:User {id: $userId}) + RETURN group {.*}, admin {.*, myRoleInGroup: adminMembership.role}, member {.*, myRoleInGroup: userMembership.role} + `, + { groupId, adminId, userId }, + ) + // Wolle: + // console.log( + // 'transactionResponse: ', + // transactionResponse, + // ) + // console.log( + // 'transaction admins: ', + // transactionResponse.records.map((record) => record.get('admin')), + // ) + // console.log( + // 'transaction groups: ', + // transactionResponse.records.map((record) => record.get('group')), + // ) + // console.log( + // 'transaction members: ', + // transactionResponse.records.map((record) => record.get('member')), + // ) + return { + admin: transactionResponse.records.map((record) => record.get('admin'))[0], + group: transactionResponse.records.map((record) => record.get('group'))[0], + member: transactionResponse.records.map((record) => record.get('member'))[0], + } + }) + try { + // Wolle: + // console.log('enter try !!!') + const { admin, group, member } = await readTxPromise + // Wolle: + // console.log('after !!!') + // console.log('admin: ', admin) + // console.log('group: ', group) + // console.log('member: ', member) + return ( + !!group && + !!admin && + !!member && + adminId !== userId && + ((['admin'].includes(admin.myRoleInGroup) && + !['owner'].includes(member.myRoleInGroup) && + ['pending', 'usual', 'admin'].includes(roleInGroup)) || + (['owner'].includes(admin.myRoleInGroup) && + ['pending', 'usual', 'admin', 'owner'].includes(roleInGroup))) ) } catch (error) { // Wolle: console.log('error: ', error) @@ -118,7 +190,7 @@ const isAuthor = rule({ const isDeletingOwnAccount = rule({ cache: 'no_cache', -})(async (parent, args, context, info) => { +})(async (parent, args, context, _info) => { return context.user.id === args.id }) @@ -183,7 +255,8 @@ export default shield( SignupVerification: allow, UpdateUser: onlyYourself, CreateGroup: isAuthenticated, - JoinGroup: isAuthenticated, + JoinGroup: isAuthenticated, // Wolle: can not be correct + SwitchGroupMemberRole: isAllowedToSwitchGroupMemberRole, CreatePost: isAuthenticated, UpdatePost: isAuthor, DeletePost: isAuthor, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index c9a31fdc3..34959908d 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -160,6 +160,33 @@ export default { session.close() } }, + SwitchGroupMemberRole: async (_parent, params, context, _resolveInfo) => { + const { id: groupId, userId, roleInGroup } = params + // Wolle + // console.log('groupId: ', groupId) + // console.log('userId: ', groupId) + // console.log('roleInGroup: ', groupId) + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const joinGroupCypher = ` + MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId}) + SET + membership.updatedAt = toString(datetime()), + membership.role = $roleInGroup + RETURN member {.*, myRoleInGroup: membership.role} + ` + const result = await transaction.run(joinGroupCypher, { groupId, userId, roleInGroup }) + const [member] = await result.records.map((record) => record.get('member')) + return member + }) + try { + return await writeTxResultPromise + } catch (error) { + throw new Error(error) + } finally { + session.close() + } + }, }, Group: { ...Resolver('Group', { diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 87eb02dc0..e17e4827a 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -3,6 +3,7 @@ import Factory, { cleanDatabase } from '../../db/factories' import { createGroupMutation, joinGroupMutation, + switchGroupMemberRoleMutation, groupMemberQuery, groupQuery, } from '../../db/graphql/groups' @@ -13,8 +14,8 @@ import CONFIG from '../../config' const driver = getDriver() const neode = getNeode() -let query -let mutate +let isCleanDbAfterEach = true +let isSeedDb = true let authenticatedUser let user @@ -23,20 +24,20 @@ const descriptionAdditional100 = ' 123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789-123456789' let variables = {} +const { server } = createServer({ + context: () => { + return { + driver, + neode, + user: authenticatedUser, + } + }, +}) +const { query } = createTestClient(server) +const { mutate } = createTestClient(server) + beforeAll(async () => { await cleanDatabase() - - const { server } = createServer({ - context: () => { - return { - driver, - neode, - user: authenticatedUser, - } - }, - }) - query = createTestClient(server).query - mutate = createTestClient(server).mutate }) afterAll(async () => { @@ -44,50 +45,55 @@ afterAll(async () => { }) beforeEach(async () => { - variables = {} - user = await Factory.build( - 'user', - { - id: 'current-user', - name: 'TestUser', - }, - { - email: 'test@example.org', - password: '1234', - }, - ) - await Promise.all([ - neode.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - slug: 'democracy-politics', - icon: 'university', - }), - neode.create('Category', { - id: 'cat4', - name: 'Environment & Nature', - slug: 'environment-nature', - icon: 'tree', - }), - neode.create('Category', { - id: 'cat15', - name: 'Consumption & Sustainability', - slug: 'consumption-sustainability', - icon: 'shopping-cart', - }), - neode.create('Category', { - id: 'cat27', - name: 'Animal Protection', - slug: 'animal-protection', - icon: 'paw', - }), - ]) - authenticatedUser = null + // Wolle: find a better solution + if (isSeedDb) { + variables = {} + user = await Factory.build( + 'user', + { + id: 'current-user', + name: 'TestUser', + }, + { + email: 'test@example.org', + password: '1234', + }, + ) + await Promise.all([ + neode.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + slug: 'democracy-politics', + icon: 'university', + }), + neode.create('Category', { + id: 'cat4', + name: 'Environment & Nature', + slug: 'environment-nature', + icon: 'tree', + }), + neode.create('Category', { + id: 'cat15', + name: 'Consumption & Sustainability', + slug: 'consumption-sustainability', + icon: 'shopping-cart', + }), + neode.create('Category', { + id: 'cat27', + name: 'Animal Protection', + slug: 'animal-protection', + icon: 'paw', + }), + ]) + authenticatedUser = null + } }) // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 afterEach(async () => { - await cleanDatabase() + if (isCleanDbAfterEach) { + await cleanDatabase() + } }) describe('CreateGroup', () => { @@ -558,6 +564,330 @@ describe('JoinGroup', () => { }) }) +describe('SwitchGroupMemberRole', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + variables = { + id: 'not-existing-group', + userId: 'current-user', + roleInGroup: 'pending', + } + const { errors } = await mutate({ mutation: switchGroupMemberRoleMutation, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('authenticated', () => { + describe('in building up mode', () => { + let usualMemberUser + let adminMemberUser + let ownerMemberUser + // let secondOwnerMemberUser + + beforeEach(async () => { + // Wolle: change this to beforeAll? + if (isSeedDb) { + // create users + usualMemberUser = await Factory.build( + 'user', + { + id: 'usual-member-user', + name: 'Usual Member TestUser', + }, + { + email: 'usual-member-user@example.org', + password: '1234', + }, + ) + adminMemberUser = await Factory.build( + 'user', + { + id: 'admin-member-user', + name: 'Admin Member TestUser', + }, + { + email: 'admin-member-user@example.org', + password: '1234', + }, + ) + ownerMemberUser = await Factory.build( + 'user', + { + id: 'owner-member-user', + name: 'Owner Member TestUser', + }, + { + email: 'owner-member-user@example.org', + password: '1234', + }, + ) + // secondOwnerMemberUser = + await Factory.build( + 'user', + { + id: 'second-owner-member-user', + name: 'Second Owner Member TestUser', + }, + { + email: 'second-owner-member-user@example.org', + password: '1234', + }, + ) + // create groups + authenticatedUser = await usualMemberUser.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, + }, + }) + authenticatedUser = await ownerMemberUser.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 adminMemberUser.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, + }, + }) + // create additional memberships + // public-group + authenticatedUser = await usualMemberUser.toJson() + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-closed-group', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-hidden-group', + }, + }) + // closed-group + authenticatedUser = await ownerMemberUser.toJson() + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'usual-member-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'admin-member-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'second-owner-member-user', + }, + }) + // hidden-group + authenticatedUser = await adminMemberUser.toJson() + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'hidden-group', + userId: 'admin-member-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'hidden-group', + userId: 'second-owner-member-user', + }, + }) + // Wolle + // function sleep(ms) { + // return new Promise(resolve => setTimeout(resolve, ms)); + // } + // await sleep(4 * 1000) + isCleanDbAfterEach = false + isSeedDb = false + } + }) + afterAll(async () => { + // Wolle: find a better solution + await cleanDatabase() + isCleanDbAfterEach = true + isSeedDb = true + }) + + describe('in all group types – here "closed-group" for example', () => { + beforeEach(async () => { + variables = { + id: 'closed-group', + } + }) + + describe('switch role', () => { + describe('of owner member "owner-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'owner-member-user', + } + }) + + describe('by owner themself "owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await ownerMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + }) + + describe('of prospective admin member "admin-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'admin-member-user', + } + }) + + describe('by owner "owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await ownerMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('has role admin', async () => { + // Wolle: + // const groups = await query({ query: groupQuery, variables: {} }) + // console.log('groups.data.Group: ', groups.data.Group) + // const groupMemberOfClosedGroup = await mutate({ + // mutation: groupMemberQuery, + // variables: { + // id: 'closed-group', + // }, + // }) + // console.log('groupMemberOfClosedGroup.data.GroupMember: ', groupMemberOfClosedGroup.data.GroupMember) + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'admin-member-user', + myRoleInGroup: 'admin', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + + describe('by still pending member "usual-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await usualMemberUser.toJson() + }) + + describe('degrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by none member "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + describe('degrade to pending again', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + }) + }) + }) + }) + }) +}) + describe('GroupMember', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index fad1b7e58..270f5c844 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -118,4 +118,10 @@ type Mutation { id: ID! userId: ID! ): User + + SwitchGroupMemberRole( + id: ID! + userId: ID! + roleInGroup: GroupMemberRole! + ): User } From 0fe609e29452a5f8e3634e3f8e08dbc0d9c6f8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Aug 2022 07:19:07 +0200 Subject: [PATCH 07/58] Add test for find all member of closed and hidden groups if you are usual member --- .../src/middleware/permissionsMiddleware.js | 2 + backend/src/schema/resolvers/groups.spec.js | 81 ++++++++++++++----- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 11ca956b6..728385902 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -55,6 +55,7 @@ const isMySocialMedia = rule({ const isAllowedSeeingMembersOfGroup = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { + // Wolle: may have a look to 'isAuthenticated' if (!user) return false const { id: groupId } = args // Wolle: console.log('groupId: ', groupId) @@ -96,6 +97,7 @@ const isAllowedSeeingMembersOfGroup = rule({ const isAllowedToSwitchGroupMemberRole = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { + // Wolle: may have a look to 'isAuthenticated' if (!user) return false const adminId = user.id const { id: groupId, userId, roleInGroup } = args diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index e17e4827a..1721e3e00 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -940,6 +940,19 @@ describe('GroupMember', () => { }, ) // create groups + 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, + }, + }) authenticatedUser = await ownerOfClosedGroupUser.toJson() await mutate({ mutation: createGroupMutation, @@ -966,20 +979,8 @@ describe('GroupMember', () => { 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, - }, - }) // create additional memberships + // public-group await mutate({ mutation: joinGroupMutation, variables: { @@ -994,6 +995,7 @@ describe('GroupMember', () => { userId: 'owner-of-hidden-group', }, }) + // closed-group await mutate({ mutation: joinGroupMutation, variables: { @@ -1001,6 +1003,21 @@ describe('GroupMember', () => { userId: 'current-user', }, }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'owner-of-hidden-group', + }, + }) + // hidden-group + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'hidden-group', + userId: 'current-user', + }, + }) await mutate({ mutation: joinGroupMutation, variables: { @@ -1008,6 +1025,8 @@ describe('GroupMember', () => { userId: 'owner-of-closed-group', }, }) + + authenticatedUser = await user.toJson() }) describe('public group', () => { @@ -1147,6 +1166,10 @@ describe('GroupMember', () => { id: 'owner-of-closed-group', myRoleInGroup: 'owner', }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'pending', + }), ]), }, errors: undefined, @@ -1156,13 +1179,21 @@ describe('GroupMember', () => { variables, }) expect(result).toMatchObject(expected) - expect(result.data.GroupMember.length).toBe(2) + expect(result.data.GroupMember.length).toBe(3) }) }) - // needs 'SwitchGroupMemberRole' - describe.skip('by usual member "owner-of-hidden-group"', () => { + describe('by usual member "owner-of-hidden-group"', () => { beforeEach(async () => { + authenticatedUser = await ownerOfClosedGroupUser.toJson() + await mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'closed-group', + userId: 'owner-of-hidden-group', + roleInGroup: 'usual', + }, + }) authenticatedUser = await ownerOfHiddenGroupUser.toJson() }) @@ -1236,6 +1267,10 @@ describe('GroupMember', () => { const expected = { data: { GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'pending', + }), expect.objectContaining({ id: 'owner-of-closed-group', myRoleInGroup: 'pending', @@ -1253,13 +1288,21 @@ describe('GroupMember', () => { variables, }) expect(result).toMatchObject(expected) - expect(result.data.GroupMember.length).toBe(2) + expect(result.data.GroupMember.length).toBe(3) }) }) - // needs 'SwitchGroupMemberRole' - describe.skip('by usual member "owner-of-closed-group"', () => { + describe('by usual member "owner-of-closed-group"', () => { beforeEach(async () => { + authenticatedUser = await ownerOfHiddenGroupUser.toJson() + await mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'hidden-group', + userId: 'owner-of-closed-group', + roleInGroup: 'usual', + }, + }) authenticatedUser = await ownerOfClosedGroupUser.toJson() }) From 74589d4d3f0a71c50a35ed285429803e614626f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Aug 2022 07:27:46 +0200 Subject: [PATCH 08/58] Resort creation of groups and its connected joins of the groups --- backend/src/schema/resolvers/groups.spec.js | 113 ++++++++++---------- 1 file changed, 56 insertions(+), 57 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 1721e3e00..ea3904d0a 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -343,6 +343,7 @@ describe('JoinGroup', () => { let ownerOfHiddenGroupUser beforeEach(async () => { + // create users ownerOfClosedGroupUser = await Factory.build( 'user', { @@ -365,6 +366,8 @@ describe('JoinGroup', () => { password: '1234', }, ) + // create groups + // public-group authenticatedUser = await ownerOfClosedGroupUser.toJson() await mutate({ mutation: createGroupMutation, @@ -634,6 +637,7 @@ describe('SwitchGroupMemberRole', () => { }, ) // create groups + // public-group authenticatedUser = await usualMemberUser.toJson() await mutate({ mutation: createGroupMutation, @@ -647,35 +651,6 @@ describe('SwitchGroupMemberRole', () => { categoryIds, }, }) - authenticatedUser = await ownerMemberUser.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 adminMemberUser.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, - }, - }) - // create additional memberships - // public-group - authenticatedUser = await usualMemberUser.toJson() await mutate({ mutation: joinGroupMutation, variables: { @@ -692,6 +667,18 @@ describe('SwitchGroupMemberRole', () => { }) // closed-group authenticatedUser = await ownerMemberUser.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, + }, + }) await mutate({ mutation: joinGroupMutation, variables: { @@ -715,6 +702,18 @@ describe('SwitchGroupMemberRole', () => { }) // hidden-group authenticatedUser = await adminMemberUser.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, + }, + }) await mutate({ mutation: joinGroupMutation, variables: { @@ -729,6 +728,7 @@ describe('SwitchGroupMemberRole', () => { userId: 'second-owner-member-user', }, }) + // Wolle // function sleep(ms) { // return new Promise(resolve => setTimeout(resolve, ms)); @@ -940,6 +940,7 @@ describe('GroupMember', () => { }, ) // create groups + // public-group authenticatedUser = await user.toJson() await mutate({ mutation: createGroupMutation, @@ -953,34 +954,6 @@ describe('GroupMember', () => { categoryIds, }, }) - 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, - }, - }) - // create additional memberships - // public-group await mutate({ mutation: joinGroupMutation, variables: { @@ -996,6 +969,19 @@ describe('GroupMember', () => { }, }) // closed-group + 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, + }, + }) await mutate({ mutation: joinGroupMutation, variables: { @@ -1011,6 +997,19 @@ describe('GroupMember', () => { }, }) // hidden-group + 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, + }, + }) await mutate({ mutation: joinGroupMutation, variables: { From e91394948cda44717edeb1dac7e62fbedef391ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Aug 2022 10:08:58 +0200 Subject: [PATCH 09/58] Add tests for 'SwitchGroupMemberRole' resolver --- .../src/middleware/permissionsMiddleware.js | 1 + backend/src/schema/resolvers/groups.spec.js | 765 +++++++++++++++++- 2 files changed, 735 insertions(+), 31 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 728385902..e3e3b3eb0 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -152,6 +152,7 @@ const isAllowedToSwitchGroupMemberRole = rule({ !!admin && !!member && adminId !== userId && + // Wolle: member.myRoleInGroup === roleInGroup && ((['admin'].includes(admin.myRoleInGroup) && !['owner'].includes(member.myRoleInGroup) && ['pending', 'usual', 'admin'].includes(roleInGroup)) || diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index ea3904d0a..a06191baa 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -582,15 +582,27 @@ describe('SwitchGroupMemberRole', () => { describe('authenticated', () => { describe('in building up mode', () => { + let pendingMemberUser let usualMemberUser let adminMemberUser let ownerMemberUser - // let secondOwnerMemberUser + let secondOwnerMemberUser beforeEach(async () => { // Wolle: change this to beforeAll? if (isSeedDb) { // create users + pendingMemberUser = await Factory.build( + 'user', + { + id: 'pending-member-user', + name: 'Pending Member TestUser', + }, + { + email: 'pending-member-user@example.org', + password: '1234', + }, + ) usualMemberUser = await Factory.build( 'user', { @@ -624,8 +636,7 @@ describe('SwitchGroupMemberRole', () => { password: '1234', }, ) - // secondOwnerMemberUser = - await Factory.build( + secondOwnerMemberUser = await Factory.build( 'user', { id: 'second-owner-member-user', @@ -679,6 +690,13 @@ describe('SwitchGroupMemberRole', () => { categoryIds, }, }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'pending-member-user', + }, + }) await mutate({ mutation: joinGroupMutation, variables: { @@ -752,50 +770,54 @@ describe('SwitchGroupMemberRole', () => { } }) - describe('switch role', () => { - describe('of owner member "owner-member-user"', () => { + describe('give the members their prospective roles', () => { + describe('by owner "owner-member-user"', () => { beforeEach(async () => { - variables = { - ...variables, - userId: 'owner-member-user', - } + authenticatedUser = await ownerMemberUser.toJson() }) - describe('by owner themself "owner-member-user"', () => { + describe('switch role of "usual-member-user"', () => { beforeEach(async () => { - authenticatedUser = await ownerMemberUser.toJson() + variables = { + ...variables, + userId: 'usual-member-user', + } }) - describe('to admin', () => { + describe('to usual', () => { beforeEach(async () => { variables = { ...variables, - roleInGroup: 'admin', + roleInGroup: 'usual', } }) - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + it('has role usual', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'usual-member-user', + myRoleInGroup: 'usual', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) }) }) }) - }) - describe('of prospective admin member "admin-member-user"', () => { - beforeEach(async () => { - variables = { - ...variables, - userId: 'admin-member-user', - } - }) - - describe('by owner "owner-member-user"', () => { + describe('switch role of "admin-member-user"', () => { beforeEach(async () => { - authenticatedUser = await ownerMemberUser.toJson() + variables = { + ...variables, + userId: 'admin-member-user', + } }) describe('to admin', () => { @@ -836,11 +858,348 @@ describe('SwitchGroupMemberRole', () => { }) }) - describe('by still pending member "usual-member-user"', () => { + describe('switch role of "second-owner-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'second-owner-member-user', + } + }) + + describe('to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('has role owner', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'second-owner-member-user', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + // Wolle: + // const groups = await query({ query: groupQuery, variables: {} }) + // console.log('groups.data.Group: ', groups.data.Group) + // const groupMemberOfClosedGroup = await mutate({ + // mutation: groupMemberQuery, + // variables: { + // id: 'closed-group', + // }, + // }) + // console.log('groupMemberOfClosedGroup.data.GroupMember: ', groupMemberOfClosedGroup.data.GroupMember) + }) + }) + }) + }) + }) + + describe('switch role', () => { + describe('of owner "owner-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'owner-member-user', + } + }) + + describe('by owner themself "owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await ownerMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + // Wolle: shall this be possible for now? + // or shall only an owner who gave the second owner the owner role downgrade themself? + describe('by second owner "second-owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await secondOwnerMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('has role admin', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'owner-member-user', + myRoleInGroup: 'admin', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('back to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('has role owner again', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'owner-member-user', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + + describe('by admin "admin-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await adminMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by usual member "usual-member-user"', () => { beforeEach(async () => { authenticatedUser = await usualMemberUser.toJson() }) + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by still pending member "pending-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await pendingMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + }) + + describe('of admin "admin-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'admin-member-user', + } + }) + + describe('by owner "owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await ownerMemberUser.toJson() + }) + + describe('to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('has role owner', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'admin-member-user', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('back to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('has role admin again', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'admin-member-user', + myRoleInGroup: 'admin', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + + describe('by usual member "usual-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await usualMemberUser.toJson() + }) + + describe('upgrade to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('degrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by still pending member "pending-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await pendingMemberUser.toJson() + }) + + describe('upgrade to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + describe('degrade to usual', () => { beforeEach(async () => { variables = { @@ -864,6 +1223,23 @@ describe('SwitchGroupMemberRole', () => { authenticatedUser = await user.toJson() }) + describe('upgrade to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + describe('degrade to pending again', () => { beforeEach(async () => { variables = { @@ -882,6 +1258,333 @@ describe('SwitchGroupMemberRole', () => { }) }) }) + + describe('of usual member "usual-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'usual-member-user', + } + }) + + describe('by owner "owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await ownerMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('has role admin', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'usual-member-user', + myRoleInGroup: 'admin', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('back to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('has role usual again', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'usual-member-user', + myRoleInGroup: 'usual', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + + describe('by usual member "usual-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await usualMemberUser.toJson() + }) + + describe('upgrade to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('degrade to pending', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by still pending member "pending-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await pendingMemberUser.toJson() + }) + + describe('upgrade to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('degrade to pending', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by none member "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + describe('upgrade to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('degrade to pending again', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + }) + + describe('of still pending member "pending-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'pending-member-user', + } + }) + + describe('by owner "owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await ownerMemberUser.toJson() + }) + + describe('to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('has role usual', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'pending-member-user', + myRoleInGroup: 'usual', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('back to pending', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } + }) + + it('has role usual again', async () => { + const expected = { + data: { + SwitchGroupMemberRole: { + id: 'pending-member-user', + myRoleInGroup: 'pending', + }, + }, + errors: undefined, + } + await expect( + mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject(expected) + }) + }) + }) + + describe('by usual member "usual-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await usualMemberUser.toJson() + }) + + describe('upgrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by still pending member "pending-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await pendingMemberUser.toJson() + }) + + describe('upgrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by none member "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + describe('upgrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: switchGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + }) }) }) }) From 29874e54a146e4174d17a09f6e2f4d451efe2954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Aug 2022 10:40:16 +0200 Subject: [PATCH 10/58] Add jest dev helper function for sleeping --- backend/src/helpers/jest.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backend/src/helpers/jest.js b/backend/src/helpers/jest.js index ecfc1a042..bb0ab9d7c 100644 --- a/backend/src/helpers/jest.js +++ b/backend/src/helpers/jest.js @@ -7,3 +7,11 @@ export function gql(strings) { return strings.join('') } + +// sometime we have to wait to check a db state by having a look into the db in a certain moment +// or we wait a bit to check if we missed to set an await somewhere +export function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} +// usage – 4 seconds for example +// await sleep(4 * 1000) From 41bf1f7d3977b40e9d5b33d7b72fa738ad43e2a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Aug 2022 10:41:31 +0200 Subject: [PATCH 11/58] Refactor tests in 'groups.spec.js' to have modes 'clean db' and 'building up' --- backend/src/schema/resolvers/groups.spec.js | 2228 +++++++++---------- 1 file changed, 1105 insertions(+), 1123 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index a06191baa..1a1386118 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -14,8 +14,6 @@ import CONFIG from '../../config' const driver = getDriver() const neode = getNeode() -let isCleanDbAfterEach = true -let isSeedDb = true let authenticatedUser let user @@ -36,6 +34,48 @@ const { server } = createServer({ const { query } = createTestClient(server) const { mutate } = createTestClient(server) +const seedBasicsAndClearAuthentication = async () => { + variables = {} + user = await Factory.build( + 'user', + { + id: 'current-user', + name: 'TestUser', + }, + { + email: 'test@example.org', + password: '1234', + }, + ) + await Promise.all([ + neode.create('Category', { + id: 'cat9', + name: 'Democracy & Politics', + slug: 'democracy-politics', + icon: 'university', + }), + neode.create('Category', { + id: 'cat4', + name: 'Environment & Nature', + slug: 'environment-nature', + icon: 'tree', + }), + neode.create('Category', { + id: 'cat15', + name: 'Consumption & Sustainability', + slug: 'consumption-sustainability', + icon: 'shopping-cart', + }), + neode.create('Category', { + id: 'cat27', + name: 'Animal Protection', + slug: 'animal-protection', + icon: 'paw', + }), + ]) + authenticatedUser = null +} + beforeAll(async () => { await cleanDatabase() }) @@ -44,357 +84,1038 @@ afterAll(async () => { await cleanDatabase() }) -beforeEach(async () => { - // Wolle: find a better solution - if (isSeedDb) { - variables = {} - user = await Factory.build( - 'user', - { - id: 'current-user', - name: 'TestUser', - }, - { - email: 'test@example.org', - password: '1234', - }, - ) - await Promise.all([ - neode.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - slug: 'democracy-politics', - icon: 'university', - }), - neode.create('Category', { - id: 'cat4', - name: 'Environment & Nature', - slug: 'environment-nature', - icon: 'tree', - }), - neode.create('Category', { - id: 'cat15', - name: 'Consumption & Sustainability', - slug: 'consumption-sustainability', - icon: 'shopping-cart', - }), - neode.create('Category', { - id: 'cat27', - name: 'Animal Protection', - slug: 'animal-protection', - icon: 'paw', - }), - ]) - authenticatedUser = null - } -}) +describe('in mode: always clean db', () => { + beforeEach(async () => { + await seedBasicsAndClearAuthentication() + }) -// TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 -afterEach(async () => { - if (isCleanDbAfterEach) { + // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 + 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, + 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, } - 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', + 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, - ) - }) + 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, - ) - }) + 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, + ) + }) - describe('description', () => { - describe('length without HTML', () => { - describe('less then 100 chars', () => { + 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, - description: - '0123456789' + - '0123456789', - }, + variables: { ...variables, categoryIds: null }, }) - expect(errors[0]).toHaveProperty('message', 'Description too short!') + expect(errors[0]).toHaveProperty('message', 'Too view categories!') }) }) - }) - }) - 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('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!') }) - 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 () => { - const { errors } = await query({ query: groupQuery, variables: {} }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('authenticated', () => { - let otherUser - - beforeEach(async () => { - otherUser = await Factory.build( - 'user', - { - id: 'other-user', - name: 'Other TestUser', - }, - { - email: 'test2@example.org', - password: '1234', - }, - ) - authenticatedUser = await otherUser.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'others-group', - name: 'Uninteresting Group', - about: 'We will change nothing!', - description: 'We love it like it is!?' + descriptionAdditional100, - groupType: 'closed', - actionRadius: 'global', - categoryIds, - }, - }) - authenticatedUser = await user.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'my-group', - name: 'The Best Group', - about: 'We will change the world!', - description: 'Some description' + descriptionAdditional100, - groupType: 'public', - actionRadius: 'regional', - categoryIds, - }, + describe('Group', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + const { errors } = await query({ query: groupQuery, variables: {} }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - describe('query groups', () => { - describe('without any filters', () => { - it('finds all groups', async () => { - const expected = { - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }), - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), - }, - errors: undefined, - } - await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject(expected) + describe('authenticated', () => { + let otherUser + + beforeEach(async () => { + otherUser = await Factory.build( + 'user', + { + id: 'other-user', + name: 'Other TestUser', + }, + { + email: 'test2@example.org', + password: '1234', + }, + ) + authenticatedUser = await otherUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'others-group', + name: 'Uninteresting Group', + about: 'We will change nothing!', + description: 'We love it like it is!?' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'global', + categoryIds, + }, + }) + authenticatedUser = await user.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'my-group', + name: 'The Best Group', + about: 'We will change the world!', + description: 'Some description' + descriptionAdditional100, + groupType: 'public', + actionRadius: 'regional', + categoryIds, + }, }) }) - describe('isMember = true', () => { - it('finds only groups where user is member', async () => { - const expected = { - data: { - Group: [ - { - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', + describe('query groups', () => { + describe('without any filters', () => { + it('finds all groups', async () => { + const expected = { + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }), + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), + }, + errors: undefined, + } + await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject( + expected, + ) + }) + }) + + describe('isMember = true', () => { + it('finds only groups where user is member', async () => { + const expected = { + data: { + Group: [ + { + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }, + ], + }, + errors: undefined, + } + await expect( + query({ query: groupQuery, variables: { isMember: true } }), + ).resolves.toMatchObject(expected) + }) + }) + + describe('isMember = false', () => { + it('finds only groups where user is not(!) member', async () => { + const expected = { + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), + }, + errors: undefined, + } + await expect( + query({ query: groupQuery, variables: { isMember: false } }), + ).resolves.toMatchObject(expected) + }) + }) + }) + }) + }) + + 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 () => { + // create users + 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', + }, + ) + // create groups + // public-group + 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( - query({ query: groupQuery, variables: { isMember: true } }), - ).resolves.toMatchObject(expected) + }, + 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('isMember = false', () => { - it('finds only groups where user is not(!) member', async () => { - const expected = { - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, + 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, }), - ]), - }, - errors: undefined, + ).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 () => { + variables = { + id: 'not-existing-group', + } + const { errors } = await query({ query: groupMemberQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('authenticated', () => { + let otherUser + let ownerOfClosedGroupUser + let ownerOfHiddenGroupUser + + beforeEach(async () => { + // create users + otherUser = await Factory.build( + 'user', + { + id: 'other-user', + name: 'Other TestUser', + }, + { + email: 'test2@example.org', + password: '1234', + }, + ) + 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', + }, + ) + // create groups + // public-group + 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, + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-closed-group', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-hidden-group', + }, + }) + // closed-group + 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, + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'current-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'owner-of-hidden-group', + }, + }) + // hidden-group + 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, + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'hidden-group', + userId: 'current-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'hidden-group', + userId: 'owner-of-closed-group', + }, + }) + + authenticatedUser = await user.toJson() + }) + + describe('public group', () => { + beforeEach(async () => { + variables = { + id: 'public-group', } - await expect( - query({ query: groupQuery, variables: { isMember: false } }), - ).resolves.toMatchObject(expected) + }) + + describe('query group members', () => { + describe('by owner "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('finds all members', async () => { + const expected = { + data: { + 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, + } + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(3) + }) + }) + + describe('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: 'owner', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'usual', + }), + 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 none member "other-user"', () => { + beforeEach(async () => { + authenticatedUser = await otherUser.toJson() + }) + + it('finds all members', async () => { + const expected = { + data: { + 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, + } + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(3) + }) + }) + }) + }) + + describe('closed group', () => { + beforeEach(async () => { + variables = { + id: '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: { + 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: 'pending', + }), + ]), + }, + errors: undefined, + } + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject(expected) + expect(result.data.GroupMember.length).toBe(3) + }) + }) + + describe('by usual member "owner-of-hidden-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfClosedGroupUser.toJson() + await mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'closed-group', + userId: 'owner-of-hidden-group', + roleInGroup: 'usual', + }, + }) + 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', () => { + beforeEach(async () => { + variables = { + id: '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: { + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'pending', + }), + 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 usual member "owner-of-closed-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfHiddenGroupUser.toJson() + await mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'hidden-group', + userId: 'owner-of-closed-group', + roleInGroup: 'usual', + }, + }) + 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!') + }) + }) }) }) }) }) }) -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('in mode: building up', () => { + beforeAll(async () => { + await seedBasicsAndClearAuthentication() }) - describe('authenticated', () => { - let ownerOfClosedGroupUser - let ownerOfHiddenGroupUser + afterAll(async () => { + await cleanDatabase() + }) - beforeEach(async () => { + describe('SwitchGroupMemberRole', () => { + let pendingMemberUser + let usualMemberUser + let adminMemberUser + let ownerMemberUser + let secondOwnerMemberUser + + beforeAll(async () => { // create users - ownerOfClosedGroupUser = await Factory.build( + pendingMemberUser = await Factory.build( 'user', { - id: 'owner-of-closed-group', - name: 'Owner Of Closed Group', + id: 'pending-member-user', + name: 'Pending Member TestUser', }, { - email: 'owner-of-closed-group@example.org', + email: 'pending-member-user@example.org', password: '1234', }, ) - ownerOfHiddenGroupUser = await Factory.build( + usualMemberUser = await Factory.build( 'user', { - id: 'owner-of-hidden-group', - name: 'Owner Of Hidden Group', + id: 'usual-member-user', + name: 'Usual Member TestUser', }, { - email: 'owner-of-hidden-group@example.org', + email: 'usual-member-user@example.org', + password: '1234', + }, + ) + adminMemberUser = await Factory.build( + 'user', + { + id: 'admin-member-user', + name: 'Admin Member TestUser', + }, + { + email: 'admin-member-user@example.org', + password: '1234', + }, + ) + ownerMemberUser = await Factory.build( + 'user', + { + id: 'owner-member-user', + name: 'Owner Member TestUser', + }, + { + email: 'owner-member-user@example.org', + password: '1234', + }, + ) + secondOwnerMemberUser = await Factory.build( + 'user', + { + id: 'second-owner-member-user', + name: 'Second Owner Member TestUser', + }, + { + email: 'second-owner-member-user@example.org', password: '1234', }, ) // create groups // public-group - 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() + authenticatedUser = await usualMemberUser.toJson() await mutate({ mutation: createGroupMutation, variables: { @@ -407,362 +1128,105 @@ describe('JoinGroup', () => { 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) - }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-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: '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) - }) - }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-hidden-group', + }, + }) + // closed-group + authenticatedUser = await ownerMemberUser.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, + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'pending-member-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'usual-member-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'admin-member-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'second-owner-member-user', + }, + }) + // hidden-group + authenticatedUser = await adminMemberUser.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, + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'hidden-group', + userId: 'admin-member-user', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + id: 'hidden-group', + userId: 'second-owner-member-user', + }, }) }) - 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('SwitchGroupMemberRole', () => { - describe('unauthenticated', () => { - it('throws authorization error', async () => { - variables = { - id: 'not-existing-group', - userId: 'current-user', - roleInGroup: 'pending', - } - const { errors } = await mutate({ mutation: switchGroupMemberRoleMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('authenticated', () => { - describe('in building up mode', () => { - let pendingMemberUser - let usualMemberUser - let adminMemberUser - let ownerMemberUser - let secondOwnerMemberUser - - beforeEach(async () => { - // Wolle: change this to beforeAll? - if (isSeedDb) { - // create users - pendingMemberUser = await Factory.build( - 'user', - { - id: 'pending-member-user', - name: 'Pending Member TestUser', - }, - { - email: 'pending-member-user@example.org', - password: '1234', - }, - ) - usualMemberUser = await Factory.build( - 'user', - { - id: 'usual-member-user', - name: 'Usual Member TestUser', - }, - { - email: 'usual-member-user@example.org', - password: '1234', - }, - ) - adminMemberUser = await Factory.build( - 'user', - { - id: 'admin-member-user', - name: 'Admin Member TestUser', - }, - { - email: 'admin-member-user@example.org', - password: '1234', - }, - ) - ownerMemberUser = await Factory.build( - 'user', - { - id: 'owner-member-user', - name: 'Owner Member TestUser', - }, - { - email: 'owner-member-user@example.org', - password: '1234', - }, - ) - secondOwnerMemberUser = await Factory.build( - 'user', - { - id: 'second-owner-member-user', - name: 'Second Owner Member TestUser', - }, - { - email: 'second-owner-member-user@example.org', - password: '1234', - }, - ) - // create groups - // public-group - authenticatedUser = await usualMemberUser.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, - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'public-group', - userId: 'owner-of-closed-group', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'public-group', - userId: 'owner-of-hidden-group', - }, - }) - // closed-group - authenticatedUser = await ownerMemberUser.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, - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'pending-member-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'usual-member-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'admin-member-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'second-owner-member-user', - }, - }) - // hidden-group - authenticatedUser = await adminMemberUser.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, - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'hidden-group', - userId: 'admin-member-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'hidden-group', - userId: 'second-owner-member-user', - }, - }) - - // Wolle - // function sleep(ms) { - // return new Promise(resolve => setTimeout(resolve, ms)); - // } - // await sleep(4 * 1000) - isCleanDbAfterEach = false - isSeedDb = false + describe('unauthenticated', () => { + it('throws authorization error', async () => { + variables = { + id: 'not-existing-group', + userId: 'current-user', + roleInGroup: 'pending', } + const { errors } = await mutate({ mutation: switchGroupMemberRoleMutation, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) - afterAll(async () => { - // Wolle: find a better solution - await cleanDatabase() - isCleanDbAfterEach = true - isSeedDb = true - }) + }) + describe('authenticated', () => { describe('in all group types – here "closed-group" for example', () => { beforeEach(async () => { variables = { @@ -890,16 +1354,6 @@ describe('SwitchGroupMemberRole', () => { variables, }), ).resolves.toMatchObject(expected) - // Wolle: - // const groups = await query({ query: groupQuery, variables: {} }) - // console.log('groups.data.Group: ', groups.data.Group) - // const groupMemberOfClosedGroup = await mutate({ - // mutation: groupMemberQuery, - // variables: { - // id: 'closed-group', - // }, - // }) - // console.log('groupMemberOfClosedGroup.data.GroupMember: ', groupMemberOfClosedGroup.data.GroupMember) }) }) }) @@ -1590,475 +2044,3 @@ describe('SwitchGroupMemberRole', () => { }) }) }) - -describe('GroupMember', () => { - describe('unauthenticated', () => { - it('throws authorization error', async () => { - variables = { - id: 'not-existing-group', - } - const { errors } = await query({ query: groupMemberQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('authenticated', () => { - let otherUser - let ownerOfClosedGroupUser - let ownerOfHiddenGroupUser - - beforeEach(async () => { - // create users - otherUser = await Factory.build( - 'user', - { - id: 'other-user', - name: 'Other TestUser', - }, - { - email: 'test2@example.org', - password: '1234', - }, - ) - 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', - }, - ) - // create groups - // public-group - 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, - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'public-group', - userId: 'owner-of-closed-group', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'public-group', - userId: 'owner-of-hidden-group', - }, - }) - // closed-group - 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, - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'current-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'owner-of-hidden-group', - }, - }) - // hidden-group - 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, - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'hidden-group', - userId: 'current-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'hidden-group', - userId: 'owner-of-closed-group', - }, - }) - - authenticatedUser = await user.toJson() - }) - - describe('public group', () => { - beforeEach(async () => { - variables = { - id: 'public-group', - } - }) - - describe('query group members', () => { - describe('by owner "current-user"', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - it('finds all members', async () => { - const expected = { - data: { - 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, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, - }) - expect(result).toMatchObject(expected) - expect(result.data.GroupMember.length).toBe(3) - }) - }) - - describe('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: 'owner', - }), - expect.objectContaining({ - id: 'owner-of-closed-group', - myRoleInGroup: 'usual', - }), - 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 none member "other-user"', () => { - beforeEach(async () => { - authenticatedUser = await otherUser.toJson() - }) - - it('finds all members', async () => { - const expected = { - data: { - 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, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, - }) - expect(result).toMatchObject(expected) - expect(result.data.GroupMember.length).toBe(3) - }) - }) - }) - }) - - describe('closed group', () => { - beforeEach(async () => { - variables = { - id: '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: { - 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: 'pending', - }), - ]), - }, - errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, - }) - expect(result).toMatchObject(expected) - expect(result.data.GroupMember.length).toBe(3) - }) - }) - - describe('by usual member "owner-of-hidden-group"', () => { - beforeEach(async () => { - authenticatedUser = await ownerOfClosedGroupUser.toJson() - await mutate({ - mutation: switchGroupMemberRoleMutation, - variables: { - id: 'closed-group', - userId: 'owner-of-hidden-group', - roleInGroup: 'usual', - }, - }) - 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', () => { - beforeEach(async () => { - variables = { - id: '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: { - GroupMember: expect.arrayContaining([ - expect.objectContaining({ - id: 'current-user', - myRoleInGroup: 'pending', - }), - expect.objectContaining({ - id: 'owner-of-closed-group', - myRoleInGroup: 'pending', - }), - 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 usual member "owner-of-closed-group"', () => { - beforeEach(async () => { - authenticatedUser = await ownerOfHiddenGroupUser.toJson() - await mutate({ - mutation: switchGroupMemberRoleMutation, - variables: { - id: 'hidden-group', - userId: 'owner-of-closed-group', - roleInGroup: 'usual', - }, - }) - 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!') - }) - }) - }) - }) - }) -}) From f9c8c9b4be2165f1e1187d0b4b2f87e2a73424a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Aug 2022 11:27:18 +0200 Subject: [PATCH 12/58] Add different group member roles to seeding --- backend/src/db/seed.js | 113 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index f9b2d05da..a2be78553 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -5,7 +5,11 @@ import createServer from '../server' import faker from '@faker-js/faker' import Factory from '../db/factories' import { getNeode, getDriver } from '../db/neo4j' -import { createGroupMutation, joinGroupMutation } from './graphql/groups' +import { + createGroupMutation, + joinGroupMutation, + switchGroupMemberRoleMutation, +} from './graphql/groups' import { createPostMutation } from './graphql/posts' import { createCommentMutation } from './graphql/comments' @@ -415,6 +419,46 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] userId: 'u3', }, }), + mutate({ + mutation: joinGroupMutation, + variables: { + id: 'g0', + userId: 'u4', + }, + }), + mutate({ + mutation: joinGroupMutation, + variables: { + id: 'g0', + userId: 'u6', + }, + }), + ]) + await Promise.all([ + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u2', + roleInGroup: 'usual', + }, + }), + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u4', + roleInGroup: 'admin', + }, + }), + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u3', + roleInGroup: 'owner', + }, + }), ]) authenticatedUser = await jennyRostock.toJson() @@ -440,6 +484,13 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] userId: 'u1', }, }), + mutate({ + mutation: joinGroupMutation, + variables: { + id: 'g1', + userId: 'u2', + }, + }), mutate({ mutation: joinGroupMutation, variables: { @@ -462,6 +513,40 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), ]) + await Promise.all([ + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u1', + roleInGroup: 'usual', + }, + }), + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u2', + roleInGroup: 'usual', + }, + }), + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u5', + roleInGroup: 'admin', + }, + }), + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u6', + roleInGroup: 'owner', + }, + }), + ]) authenticatedUser = await bobDerBaumeister.toJson() await Promise.all([ @@ -508,6 +593,32 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), ]) + await Promise.all([ + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u4', + roleInGroup: 'usual', + }, + }), + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u5', + roleInGroup: 'usual', + }, + }), + mutate({ + mutation: switchGroupMemberRoleMutation, + variables: { + id: 'g0', + userId: 'u6', + roleInGroup: 'usual', + }, + }), + ]) // Create Posts From b3c179011a05d30fab24bd925c0f56c5796e0a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Aug 2022 11:45:51 +0200 Subject: [PATCH 13/58] Add URL comment for sleep --- backend/src/helpers/jest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/helpers/jest.js b/backend/src/helpers/jest.js index bb0ab9d7c..e3f6a3c84 100644 --- a/backend/src/helpers/jest.js +++ b/backend/src/helpers/jest.js @@ -10,6 +10,7 @@ export function gql(strings) { // sometime we have to wait to check a db state by having a look into the db in a certain moment // or we wait a bit to check if we missed to set an await somewhere +// see: https://www.sitepoint.com/delay-sleep-pause-wait/ export function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)) } From dee20c25a9c2a4765173ecba8336cca9603fa920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 22 Aug 2022 11:46:41 +0200 Subject: [PATCH 14/58] Add test todo for 'has "updatedAt" newer as "createdAt"' --- backend/src/schema/resolvers/groups.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 1a1386118..22bac6da7 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -1273,6 +1273,9 @@ describe('in mode: building up', () => { }), ).resolves.toMatchObject(expected) }) + + // the GQL mutation needs this fields in the result for testing + it.todo('has "updatedAt" newer as "createdAt"') }) }) From a22023174d4b9c7afc24530331daf6c7c85fb3fc Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 22 Aug 2022 18:33:12 +0200 Subject: [PATCH 15/58] feat: Show Categories Filter when Categories are Active --- webapp/components/FilterMenu/FilterMenu.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/webapp/components/FilterMenu/FilterMenu.vue b/webapp/components/FilterMenu/FilterMenu.vue index 9e211ccf9..84c7c1f67 100644 --- a/webapp/components/FilterMenu/FilterMenu.vue +++ b/webapp/components/FilterMenu/FilterMenu.vue @@ -14,6 +14,7 @@

{{ $t('filter-menu.filter-by') }}

+

{{ $t('filter-menu.order-by') }}

@@ -28,17 +29,24 @@ import Dropdown from '~/components/Dropdown' import { mapGetters } from 'vuex' import FollowingFilter from './FollowingFilter' import OrderByFilter from './OrderByFilter' +import CategoriesFilter from './CategoriesFilter' export default { components: { Dropdown, FollowingFilter, + CategoriesFilter, OrderByFilter, }, props: { placement: { type: String }, offset: { type: [String, Number] }, }, + data() { + return { + categoriesActive: this.$env.CATEGORIES_ACTIVE, + } + }, computed: { ...mapGetters({ filterActive: 'posts/isActive', From eddaa96d367e2be279eb5dfcbf3b0560b35357cb Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 22 Aug 2022 18:52:42 +0200 Subject: [PATCH 16/58] add movement icon --- webapp/assets/_new/icons/svgs/movement.svg | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 webapp/assets/_new/icons/svgs/movement.svg diff --git a/webapp/assets/_new/icons/svgs/movement.svg b/webapp/assets/_new/icons/svgs/movement.svg new file mode 100644 index 000000000..ac5cd9cc0 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/movement.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + From c1f1408f6c7670f9305643b7440e5c9af0b17beb Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 22 Aug 2022 19:17:50 +0200 Subject: [PATCH 17/58] add yunite categories --- backend/src/constants/categories.js | 98 +++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 backend/src/constants/categories.js diff --git a/backend/src/constants/categories.js b/backend/src/constants/categories.js new file mode 100644 index 000000000..d38f64b1a --- /dev/null +++ b/backend/src/constants/categories.js @@ -0,0 +1,98 @@ +export const categories = [ + { + icon: 'users', + name: 'networking', + description: 'Kooperation, Aktionsbündnisse, Solidarität, Hilfe', + }, + { + icon: 'home', + name: 'home', + description: 'Bauen, Lebensgemeinschaften, Tiny Houses, Gemüsegarten', + }, + { + icon: 'lightbulb', + name: 'energy', + description: 'Öl, Gas, Kohle, Wind, Wasserkraft, Biogas, Atomenergie, ...', + }, + { + icon: 'smile', + name: 'psyche', + description: 'Seele, Gefühle, Glück', + }, + { + icon: 'movement', + name: 'body-and-excercise', + description: 'Sport, Yoga, Massage, Tanzen, Entspannung', + }, + { + icon: 'balance-scale', + name: 'law', + description: 'Menschenrechte, Gesetze, Verordnungen', + }, + { + icon: 'money', + name: 'finance', + description: 'Geld, Finanzsystem, Alternativwährungen, ...', + }, + { + icon: 'child', + name: 'children', + description: 'Familie, Pädagogik, Schule, Prägung', + }, + { + icon: 'suitcase', + name: 'mobility', + description: 'Reise, Verkehr, Elektromobilität', + }, + { + icon: 'shopping-cart', + name: 'economy', + description: 'Handel, Konsum, Marketing, Lebensmittel, Lieferketten, ...', + }, + { + icon: 'angellist', + name: 'peace', + description: 'Krieg, Militär, soziale Verteidigung, Waffen, Cyberattacken', + }, + { + icon: 'university', + name: 'politics', + description: 'Demokratie, Mitbestimmung, Wahlen, Korruption, Parteien', + }, + { + icon: 'tree', + name: 'nature', + description: 'Tiere, Pflanzen, Landwirtschaft, Ökologie, Artenvielfalt', + }, + { + icon: 'graduation-cap', + name: 'science', + description: 'Bildung, Hochschule, Publikationen, ...', + }, + { + icon: 'medkit', + name: 'health', + description: 'Medizin, Ernährung, WHO, Impfungen, Schadstoffe, ...', + }, + { + icon: 'desktop', + name: 'IT-and-media', + description: + 'Nachrichten, Manipulation, Datenschutz, Überwachung, Datenkraken, AI, Software, Apps', + }, + { + icon: 'heart-o', + name: 'spirituality', + description: 'Religion, Werte, Ethik', + }, + { + icon: 'music', + name: 'culture', + description: 'Kunst, Theater, Musik, Fotografie, Film', + }, + { + icon: 'ellipsis-h', + name: 'miscellaneous', + description: '', + }, +] From 6425e5d172fa06020f46d1c3a9d1fcf5266c9cf2 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 22 Aug 2022 19:44:20 +0200 Subject: [PATCH 18/58] seed categories on db:migrate init --- backend/src/db/migrate/store.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js index 377caf0b0..0361e874b 100644 --- a/backend/src/db/migrate/store.js +++ b/backend/src/db/migrate/store.js @@ -1,6 +1,8 @@ import { getDriver, getNeode } from '../../db/neo4j' import { hashSync } from 'bcryptjs' import { v4 as uuid } from 'uuid' +import { categories } from '../../constants/categories' +import CONFIG from '../../config' const defaultAdmin = { email: 'admin@example.org', @@ -10,6 +12,27 @@ const defaultAdmin = { slug: 'admin', } +const createCategories = async (session) => { + const createCategoriesTxResultPromise = session.writeTransaction(async (txc) => { + categories.forEach(({ icon, name }, index) => { + const id = `cat${index + 1}` + txc.run( + `MERGE (c:Category { + icon: "${icon}", + slug: "${name}", + id: "${id}", + createdAt: toString(datetime()) + })`, + ) + }) + }) + try { + await createCategoriesTxResultPromise + } catch (error) { + console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console + } +} + const createDefaultAdminUser = async (session) => { const readTxResultPromise = session.readTransaction(async (txc) => { const result = await txc.run('MATCH (user:User) RETURN count(user) AS userCount') @@ -58,6 +81,7 @@ class Store { const { driver } = neode const session = driver.session() await createDefaultAdminUser(session) + if (CONFIG.CATEGORIES_ACTIVE) await createCategories(session) const writeTxResultPromise = session.writeTransaction(async (txc) => { await txc.run('CALL apoc.schema.assert({},{},true)') // drop all indices return Promise.all( From 5b7bdc02913e7b3aad12dced5725441010e34045 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 22 Aug 2022 19:56:35 +0200 Subject: [PATCH 19/58] seed Yunite categories --- backend/src/db/factories.js | 1 - backend/src/db/seed.js | 106 +++------------------------------ backend/src/models/Category.js | 4 +- 3 files changed, 10 insertions(+), 101 deletions(-) diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js index 3e164d51b..f46ac10ee 100644 --- a/backend/src/db/factories.js +++ b/backend/src/db/factories.js @@ -35,7 +35,6 @@ export const cleanDatabase = async (options = {}) => { Factory.define('category') .attr('id', uuid) .attr('icon', 'globe') - .attr('name', 'Global Peace & Nonviolence') .after((buildObject, options) => { return neode.create('Category', buildObject) }) diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index 46c5870e0..eb79d5aa2 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -6,6 +6,7 @@ import faker from '@faker-js/faker' import Factory from '../db/factories' import { getNeode, getDriver } from '../db/neo4j' import { gql } from '../helpers/jest' +import { categories } from '../constants/categories' if (CONFIG.PRODUCTION && !CONFIG.PRODUCTION_DB_CLEAN_ALLOW) { throw new Error(`You cannot seed the database in a non-staging and real production environment!`) @@ -267,104 +268,15 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] dagobert.relateTo(louie, 'blocked'), ]) - await Promise.all([ - Factory.build('category', { - id: 'cat1', - name: 'Just For Fun', - slug: 'just-for-fun', - icon: 'smile', + await Promise.all( + categories.map(({ icon, name }, index) => { + Factory.build('category', { + id: `cat${index + 1}`, + slug: name, + icon, + }) }), - Factory.build('category', { - id: 'cat2', - name: 'Happiness & Values', - slug: 'happiness-values', - icon: 'heart-o', - }), - Factory.build('category', { - id: 'cat3', - name: 'Health & Wellbeing', - slug: 'health-wellbeing', - icon: 'medkit', - }), - Factory.build('category', { - id: 'cat4', - name: 'Environment & Nature', - slug: 'environment-nature', - icon: 'tree', - }), - Factory.build('category', { - id: 'cat5', - name: 'Animal Protection', - slug: 'animal-protection', - icon: 'paw', - }), - Factory.build('category', { - id: 'cat6', - name: 'Human Rights & Justice', - slug: 'human-rights-justice', - icon: 'balance-scale', - }), - Factory.build('category', { - id: 'cat7', - name: 'Education & Sciences', - slug: 'education-sciences', - icon: 'graduation-cap', - }), - Factory.build('category', { - id: 'cat8', - name: 'Cooperation & Development', - slug: 'cooperation-development', - icon: 'users', - }), - Factory.build('category', { - id: 'cat9', - name: 'Democracy & Politics', - slug: 'democracy-politics', - icon: 'university', - }), - Factory.build('category', { - id: 'cat10', - name: 'Economy & Finances', - slug: 'economy-finances', - icon: 'money', - }), - Factory.build('category', { - id: 'cat11', - name: 'Energy & Technology', - slug: 'energy-technology', - icon: 'flash', - }), - Factory.build('category', { - id: 'cat12', - name: 'IT, Internet & Data Privacy', - slug: 'it-internet-data-privacy', - icon: 'mouse-pointer', - }), - Factory.build('category', { - id: 'cat13', - name: 'Art, Culture & Sport', - slug: 'art-culture-sport', - icon: 'paint-brush', - }), - Factory.build('category', { - id: 'cat14', - name: 'Freedom of Speech', - slug: 'freedom-of-speech', - icon: 'bullhorn', - }), - Factory.build('category', { - id: 'cat15', - name: 'Consumption & Sustainability', - slug: 'consumption-sustainability', - icon: 'shopping-cart', - }), - Factory.build('category', { - id: 'cat16', - name: 'Global Peace & Nonviolence', - slug: 'global-peace-nonviolence', - icon: 'angellist', - }), - ]) + ) const [environment, nature, democracy, freedom] = await Promise.all([ Factory.build('tag', { diff --git a/backend/src/models/Category.js b/backend/src/models/Category.js index ea617adc8..8d87ac0e2 100644 --- a/backend/src/models/Category.js +++ b/backend/src/models/Category.js @@ -2,15 +2,13 @@ import { v4 as uuid } from 'uuid' export default { id: { type: 'string', primary: true, default: uuid }, - name: { type: 'string', required: true, default: false }, slug: { type: 'string', unique: 'true' }, icon: { type: 'string', required: true, default: false }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, updatedAt: { type: 'string', isoDate: true, - required: true, - default: () => new Date().toISOString(), + required: false, }, post: { type: 'relationship', From 25c8fe275f61c886f7634e68565fd7aa87875343 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 22 Aug 2022 20:11:21 +0200 Subject: [PATCH 20/58] add locales for yunite categories --- backend/src/constants/categories.js | 2 +- webapp/locales/de.json | 35 ++++++++++++++++------------- webapp/locales/en.json | 35 ++++++++++++++++------------- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/backend/src/constants/categories.js b/backend/src/constants/categories.js index d38f64b1a..146131453 100644 --- a/backend/src/constants/categories.js +++ b/backend/src/constants/categories.js @@ -76,7 +76,7 @@ export const categories = [ }, { icon: 'desktop', - name: 'IT-and-media', + name: 'it-and-media', description: 'Nachrichten, Manipulation, Datenschutz, Überwachung, Datenkraken, AI, Software, Apps', }, diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 297daa511..dee8d086a 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -218,22 +218,25 @@ }, "category": { "name": { - "animal-protection": "Schutz der Tiere", - "art-culture-sport": "Kunst, Kultur & Sport", - "consumption-sustainability": "Verbrauch & Nachhaltigkeit", - "cooperation-development": "Zusammenarbeit & Entwicklung", - "democracy-politics": "Demokratie & Politik", - "economy-finances": "Wirtschaft & Finanzen", - "education-sciences": "Bildung & Wissenschaft", - "energy-technology": "Energie & Technologie", - "environment-nature": "Umwelt & Natur", - "freedom-of-speech": "Redefreiheit", - "global-peace-nonviolence": "Globaler Frieden & Gewaltlosigkeit", - "happiness-values": "Glück & Werte", - "health-wellbeing": "Gesundheit & Wohlbefinden", - "human-rights-justice": "Menschenrechte & Gerechtigkeit", - "it-internet-data-privacy": "IT, Internet & Datenschutz", - "just-for-fun": "Nur zum Spaß" + "body-and-excercise": "Körper & Bewegung", + "children": "Kinder", + "culture": "Kultur", + "economy": "Wirtschaft", + "energy": "Energie", + "finance": "Finanzen", + "health": "Gesundheit", + "home": "Wohnen", + "it-and-media": "IT & Medien", + "law": "Recht", + "miscellaneous": "Sonstiges", + "mobility": "Mobilität", + "nature": "Natur", + "networking": "Vernetzung", + "peace": "Frieden", + "politics": "Politik", + "psyche": "Psyche", + "science": "Wissenschaft", + "spirituality": "Spiritualität" } }, "emotions-label": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 8499b0290..d6172c257 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -218,22 +218,25 @@ }, "category": { "name": { - "animal-protection": "Animal Protection", - "art-culture-sport": "Art, Culture, & Sport", - "consumption-sustainability": "Consumption & Sustainability", - "cooperation-development": "Cooperation & Development", - "democracy-politics": "Democracy & Politics", - "economy-finances": "Economy & Finances", - "education-sciences": "Education & Sciences", - "energy-technology": "Energy & Technology", - "environment-nature": "Environment & Nature", - "freedom-of-speech": "Freedom of Speech", - "global-peace-nonviolence": "Global Peace & Nonviolence", - "happiness-values": "Happiness & Values", - "health-wellbeing": "Health & Wellbeing", - "human-rights-justice": "Human Rights & Justice", - "it-internet-data-privacy": "IT, Internet & Data Privacy", - "just-for-fun": "Just for Fun" + "body-and-excercise": "Body & Excercise", + "children": "Children", + "culture": "Culture", + "economy": "Economy", + "energy": "Energy", + "finance": "Finance", + "health": "Health", + "home": "Home", + "it-and-media": "IT & Media", + "law": "Law", + "miscellaneous": "Miscellaneous", + "mobility": "Mobility", + "nature": "Nature", + "networking": "Networking", + "peace": "Peace", + "politics": "Politics", + "psyche": "Psyche", + "science": "Science", + "spirituality": "Spirituality" } }, "emotions-label": { From ad4497786ce9b26171846a9fa9881dc67aa9f371 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 22 Aug 2022 20:29:04 +0200 Subject: [PATCH 21/58] add missing items for categories --- webapp/assets/_new/icons/svgs/child.svg | 5 +++++ webapp/assets/_new/icons/svgs/desktop.svg | 5 +++++ webapp/assets/_new/icons/svgs/ellipsis-h.svg | 5 +++++ webapp/assets/_new/icons/svgs/home.svg | 5 +++++ webapp/assets/_new/icons/svgs/lightbulb.svg | 5 +++++ webapp/assets/_new/icons/svgs/music.svg | 5 +++++ webapp/assets/_new/icons/svgs/suitcase.svg | 5 +++++ 7 files changed, 35 insertions(+) create mode 100644 webapp/assets/_new/icons/svgs/child.svg create mode 100644 webapp/assets/_new/icons/svgs/desktop.svg create mode 100644 webapp/assets/_new/icons/svgs/ellipsis-h.svg create mode 100644 webapp/assets/_new/icons/svgs/home.svg create mode 100644 webapp/assets/_new/icons/svgs/lightbulb.svg create mode 100644 webapp/assets/_new/icons/svgs/music.svg create mode 100644 webapp/assets/_new/icons/svgs/suitcase.svg diff --git a/webapp/assets/_new/icons/svgs/child.svg b/webapp/assets/_new/icons/svgs/child.svg new file mode 100644 index 000000000..fcb5651f0 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/child.svg @@ -0,0 +1,5 @@ + + +child + + diff --git a/webapp/assets/_new/icons/svgs/desktop.svg b/webapp/assets/_new/icons/svgs/desktop.svg new file mode 100644 index 000000000..ba1ef8431 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/desktop.svg @@ -0,0 +1,5 @@ + + +desktop + + diff --git a/webapp/assets/_new/icons/svgs/ellipsis-h.svg b/webapp/assets/_new/icons/svgs/ellipsis-h.svg new file mode 100644 index 000000000..eb7deeab0 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/ellipsis-h.svg @@ -0,0 +1,5 @@ + + +ellipsis-h + + diff --git a/webapp/assets/_new/icons/svgs/home.svg b/webapp/assets/_new/icons/svgs/home.svg new file mode 100644 index 000000000..b1a13b06f --- /dev/null +++ b/webapp/assets/_new/icons/svgs/home.svg @@ -0,0 +1,5 @@ + + +home + + diff --git a/webapp/assets/_new/icons/svgs/lightbulb.svg b/webapp/assets/_new/icons/svgs/lightbulb.svg new file mode 100644 index 000000000..1c19c81b1 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/lightbulb.svg @@ -0,0 +1,5 @@ + + +lightbulb-o + + diff --git a/webapp/assets/_new/icons/svgs/music.svg b/webapp/assets/_new/icons/svgs/music.svg new file mode 100644 index 000000000..b84b87800 --- /dev/null +++ b/webapp/assets/_new/icons/svgs/music.svg @@ -0,0 +1,5 @@ + + +music + + diff --git a/webapp/assets/_new/icons/svgs/suitcase.svg b/webapp/assets/_new/icons/svgs/suitcase.svg new file mode 100644 index 000000000..ceca5cbad --- /dev/null +++ b/webapp/assets/_new/icons/svgs/suitcase.svg @@ -0,0 +1,5 @@ + + +suitcase + + From d948067f44a1d6caf492dde222d97564d824940c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 22 Aug 2022 20:47:37 +0200 Subject: [PATCH 22/58] add category name again to seed and init to avoid conflicts with graphql model --- backend/src/db/factories.js | 1 + backend/src/db/migrate/store.js | 1 + backend/src/db/seed.js | 1 + backend/src/models/Category.js | 1 + backend/src/schema/resolvers/posts.js | 2 +- 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/db/factories.js b/backend/src/db/factories.js index f46ac10ee..3e164d51b 100644 --- a/backend/src/db/factories.js +++ b/backend/src/db/factories.js @@ -35,6 +35,7 @@ export const cleanDatabase = async (options = {}) => { Factory.define('category') .attr('id', uuid) .attr('icon', 'globe') + .attr('name', 'Global Peace & Nonviolence') .after((buildObject, options) => { return neode.create('Category', buildObject) }) diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js index 0361e874b..61d591cb7 100644 --- a/backend/src/db/migrate/store.js +++ b/backend/src/db/migrate/store.js @@ -20,6 +20,7 @@ const createCategories = async (session) => { `MERGE (c:Category { icon: "${icon}", slug: "${name}", + name: "${name}", id: "${id}", createdAt: toString(datetime()) })`, diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index eb79d5aa2..482517e69 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -273,6 +273,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] Factory.build('category', { id: `cat${index + 1}`, slug: name, + name, icon, }) }), diff --git a/backend/src/models/Category.js b/backend/src/models/Category.js index 8d87ac0e2..9a3f47fd0 100644 --- a/backend/src/models/Category.js +++ b/backend/src/models/Category.js @@ -2,6 +2,7 @@ import { v4 as uuid } from 'uuid' export default { id: { type: 'string', primary: true, default: uuid }, + name: { type: 'string', required: true, default: false }, slug: { type: 'string', unique: 'true' }, icon: { type: 'string', required: true, default: false }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index b09bb3edd..d9a04732c 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -358,7 +358,7 @@ export default { undefinedToNull: ['activityId', 'objectId', 'language', 'pinnedAt', 'pinned'], hasMany: { tags: '-[:TAGGED]->(related:Tag)', - // categories: '-[:CATEGORIZED]->(related:Category)', + categories: '-[:CATEGORIZED]->(related:Category)', comments: '<-[:COMMENTS]-(related:Comment)', shoutedBy: '<-[:SHOUTED]-(related:User)', emotions: '<-[related:EMOTED]', From e3549d3aa99cefbb21b4e3e5d919613d9a7d56a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 05:11:10 +0200 Subject: [PATCH 23/58] Rename resolver `SwitchGroupMemberRole` to `ChangeGroupMemberRole` Co-Authored-By: Mogge --- backend/src/db/graphql/groups.js | 4 +- backend/src/db/seed.js | 22 ++--- .../src/middleware/permissionsMiddleware.js | 2 +- backend/src/schema/resolvers/groups.js | 2 +- backend/src/schema/resolvers/groups.spec.js | 92 +++++++++---------- backend/src/schema/types/type/Group.gql | 2 +- 6 files changed, 62 insertions(+), 62 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 41780f7cd..2a9647860 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -50,9 +50,9 @@ export const joinGroupMutation = gql` } ` -export const switchGroupMemberRoleMutation = gql` +export const changeGroupMemberRoleMutation = gql` mutation ($id: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { - SwitchGroupMemberRole(id: $id, userId: $userId, roleInGroup: $roleInGroup) { + ChangeGroupMemberRole(id: $id, userId: $userId, roleInGroup: $roleInGroup) { id name slug diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index a2be78553..0010b09ef 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -8,7 +8,7 @@ import { getNeode, getDriver } from '../db/neo4j' import { createGroupMutation, joinGroupMutation, - switchGroupMemberRoleMutation, + changeGroupMemberRoleMutation, } from './graphql/groups' import { createPostMutation } from './graphql/posts' import { createCommentMutation } from './graphql/comments' @@ -436,7 +436,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u2', @@ -444,7 +444,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u4', @@ -452,7 +452,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u3', @@ -515,7 +515,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u1', @@ -523,7 +523,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u2', @@ -531,7 +531,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u5', @@ -539,7 +539,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u6', @@ -595,7 +595,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) await Promise.all([ mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u4', @@ -603,7 +603,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u5', @@ -611,7 +611,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] }, }), mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'g0', userId: 'u6', diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index e3e3b3eb0..a3935872e 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -259,7 +259,7 @@ export default shield( UpdateUser: onlyYourself, CreateGroup: isAuthenticated, JoinGroup: isAuthenticated, // Wolle: can not be correct - SwitchGroupMemberRole: isAllowedToSwitchGroupMemberRole, + ChangeGroupMemberRole: isAllowedToSwitchGroupMemberRole, CreatePost: isAuthenticated, UpdatePost: isAuthor, DeletePost: isAuthor, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 34959908d..ca24ef55f 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -160,7 +160,7 @@ export default { session.close() } }, - SwitchGroupMemberRole: async (_parent, params, context, _resolveInfo) => { + ChangeGroupMemberRole: async (_parent, params, context, _resolveInfo) => { const { id: groupId, userId, roleInGroup } = params // Wolle // console.log('groupId: ', groupId) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 22bac6da7..23fc5646e 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -3,7 +3,7 @@ import Factory, { cleanDatabase } from '../../db/factories' import { createGroupMutation, joinGroupMutation, - switchGroupMemberRoleMutation, + changeGroupMemberRoleMutation, groupMemberQuery, groupQuery, } from '../../db/graphql/groups' @@ -865,7 +865,7 @@ describe('in mode: always clean db', () => { beforeEach(async () => { authenticatedUser = await ownerOfClosedGroupUser.toJson() await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'closed-group', userId: 'owner-of-hidden-group', @@ -974,7 +974,7 @@ describe('in mode: always clean db', () => { beforeEach(async () => { authenticatedUser = await ownerOfHiddenGroupUser.toJson() await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'hidden-group', userId: 'owner-of-closed-group', @@ -1049,7 +1049,7 @@ describe('in mode: building up', () => { await cleanDatabase() }) - describe('SwitchGroupMemberRole', () => { + describe('ChangeGroupMemberRole', () => { let pendingMemberUser let usualMemberUser let adminMemberUser @@ -1221,7 +1221,7 @@ describe('in mode: building up', () => { userId: 'current-user', roleInGroup: 'pending', } - const { errors } = await mutate({ mutation: switchGroupMemberRoleMutation, variables }) + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -1259,7 +1259,7 @@ describe('in mode: building up', () => { it('has role usual', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'usual-member-user', myRoleInGroup: 'usual', }, @@ -1268,7 +1268,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1308,7 +1308,7 @@ describe('in mode: building up', () => { // console.log('groupMemberOfClosedGroup.data.GroupMember: ', groupMemberOfClosedGroup.data.GroupMember) const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'admin-member-user', myRoleInGroup: 'admin', }, @@ -1317,7 +1317,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1344,7 +1344,7 @@ describe('in mode: building up', () => { it('has role owner', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'second-owner-member-user', myRoleInGroup: 'owner', }, @@ -1353,7 +1353,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1387,7 +1387,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1413,7 +1413,7 @@ describe('in mode: building up', () => { it('has role admin', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'owner-member-user', myRoleInGroup: 'admin', }, @@ -1422,7 +1422,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1440,7 +1440,7 @@ describe('in mode: building up', () => { it('has role owner again', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'owner-member-user', myRoleInGroup: 'owner', }, @@ -1449,7 +1449,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1472,7 +1472,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1495,7 +1495,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1518,7 +1518,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1551,7 +1551,7 @@ describe('in mode: building up', () => { it('has role owner', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'admin-member-user', myRoleInGroup: 'owner', }, @@ -1560,7 +1560,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1578,7 +1578,7 @@ describe('in mode: building up', () => { it('has role admin again', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'admin-member-user', myRoleInGroup: 'admin', }, @@ -1587,7 +1587,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1610,7 +1610,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1627,7 +1627,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1650,7 +1650,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1667,7 +1667,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1690,7 +1690,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1707,7 +1707,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1740,7 +1740,7 @@ describe('in mode: building up', () => { it('has role admin', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'usual-member-user', myRoleInGroup: 'admin', }, @@ -1749,7 +1749,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1767,7 +1767,7 @@ describe('in mode: building up', () => { it('has role usual again', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'usual-member-user', myRoleInGroup: 'usual', }, @@ -1776,7 +1776,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1799,7 +1799,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1816,7 +1816,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1839,7 +1839,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1856,7 +1856,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1879,7 +1879,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1896,7 +1896,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -1929,7 +1929,7 @@ describe('in mode: building up', () => { it('has role usual', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'pending-member-user', myRoleInGroup: 'usual', }, @@ -1938,7 +1938,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1956,7 +1956,7 @@ describe('in mode: building up', () => { it('has role usual again', async () => { const expected = { data: { - SwitchGroupMemberRole: { + ChangeGroupMemberRole: { id: 'pending-member-user', myRoleInGroup: 'pending', }, @@ -1965,7 +1965,7 @@ describe('in mode: building up', () => { } await expect( mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }), ).resolves.toMatchObject(expected) @@ -1988,7 +1988,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -2011,7 +2011,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, variables, }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') @@ -2034,7 +2034,7 @@ describe('in mode: building up', () => { it('throws authorization error', async () => { const { errors } = await mutate({ - mutation: switchGroupMemberRoleMutation, + mutation: changeGroupMemberRoleMutation, 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 270f5c844..f12b98c07 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -119,7 +119,7 @@ type Mutation { userId: ID! ): User - SwitchGroupMemberRole( + ChangeGroupMemberRole( id: ID! userId: ID! roleInGroup: GroupMemberRole! From 3ab33a44f19d17940ec09f9d2a40a1d493100ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 05:21:53 +0200 Subject: [PATCH 24/58] Check permission not given for resolver `ChangeGroupMemberRole` if admin will change their own member role in group already at the beginning of 'isAllowedToChangeGroupMemberRole' Co-Authored-By: Mogge --- backend/src/middleware/permissionsMiddleware.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index a3935872e..a92aacbba 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -55,8 +55,7 @@ const isMySocialMedia = rule({ const isAllowedSeeingMembersOfGroup = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - // Wolle: may have a look to 'isAuthenticated' - if (!user) return false + if (!(user && user.id)) return false const { id: groupId } = args // Wolle: console.log('groupId: ', groupId) // console.log('user.id: ', user.id) @@ -94,13 +93,13 @@ const isAllowedSeeingMembersOfGroup = rule({ } }) -const isAllowedToSwitchGroupMemberRole = rule({ +const isAllowedToChangeGroupMemberRole = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - // Wolle: may have a look to 'isAuthenticated' - if (!user) return false + if (!(user && user.id)) return false const adminId = user.id const { id: groupId, userId, roleInGroup } = args + if (adminId === userId) return false // Wolle: // console.log('adminId: ', adminId) // console.log('groupId: ', groupId) @@ -151,7 +150,6 @@ const isAllowedToSwitchGroupMemberRole = rule({ !!group && !!admin && !!member && - adminId !== userId && // Wolle: member.myRoleInGroup === roleInGroup && ((['admin'].includes(admin.myRoleInGroup) && !['owner'].includes(member.myRoleInGroup) && @@ -259,7 +257,7 @@ export default shield( UpdateUser: onlyYourself, CreateGroup: isAuthenticated, JoinGroup: isAuthenticated, // Wolle: can not be correct - ChangeGroupMemberRole: isAllowedToSwitchGroupMemberRole, + ChangeGroupMemberRole: isAllowedToChangeGroupMemberRole, CreatePost: isAuthenticated, UpdatePost: isAuthor, DeletePost: isAuthor, From 95cebd577d9793a75aef18906516a0ab669c5998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 10:18:00 +0200 Subject: [PATCH 25/58] Refactor to group joining by `ChangeGroupMemberRole` and that `JoinGroup` is not possible for hidden groups Co-Authored-By: Mogge --- .../src/middleware/permissionsMiddleware.js | 68 ++++- backend/src/schema/resolvers/groups.js | 16 +- backend/src/schema/resolvers/groups.spec.js | 288 ++++++++++-------- 3 files changed, 237 insertions(+), 135 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index a92aacbba..f0ddd04ca 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -101,6 +101,7 @@ const isAllowedToChangeGroupMemberRole = rule({ const { id: groupId, userId, roleInGroup } = args if (adminId === userId) return false // Wolle: + // console.log('isAllowedToChangeGroupMemberRole !!!') // console.log('adminId: ', adminId) // console.log('groupId: ', groupId) // console.log('userId: ', userId) @@ -109,7 +110,8 @@ const isAllowedToChangeGroupMemberRole = rule({ const readTxPromise = session.readTransaction(async (transaction) => { const transactionResponse = await transaction.run( ` - MATCH (admin:User {id: $adminId})-[adminMembership:MEMBER_OF]->(group:Group {id: $groupId})<-[userMembership:MEMBER_OF]-(member:User {id: $userId}) + MATCH (admin:User {id: $adminId})-[adminMembership:MEMBER_OF]->(group:Group {id: $groupId}) + OPTIONAL MATCH (group)<-[userMembership:MEMBER_OF]-(member:User {id: $userId}) RETURN group {.*}, admin {.*, myRoleInGroup: adminMembership.role}, member {.*, myRoleInGroup: userMembership.role} `, { groupId, adminId, userId }, @@ -149,14 +151,70 @@ const isAllowedToChangeGroupMemberRole = rule({ return ( !!group && !!admin && - !!member && - // Wolle: member.myRoleInGroup === roleInGroup && + (!member || + (!!member && + (member.myRoleInGroup === roleInGroup || !['owner'].includes(member.myRoleInGroup)))) && ((['admin'].includes(admin.myRoleInGroup) && - !['owner'].includes(member.myRoleInGroup) && ['pending', 'usual', 'admin'].includes(roleInGroup)) || (['owner'].includes(admin.myRoleInGroup) && ['pending', 'usual', 'admin', 'owner'].includes(roleInGroup))) ) + } catch (error) { + // Wolle: + // console.log('error: ', error) + throw new Error(error) + } finally { + session.close() + } +}) + +const isAllowedToJoinGroup = rule({ + cache: 'no_cache', +})(async (_parent, args, { user, driver }) => { + if (!(user && user.id)) return false + const { id: groupId, userId } = args + // Wolle: + // console.log('adminId: ', adminId) + // console.log('groupId: ', groupId) + // console.log('userId: ', userId) + // console.log('roleInGroup: ', roleInGroup) + const session = driver.session() + const readTxPromise = session.readTransaction(async (transaction) => { + const transactionResponse = await transaction.run( + ` + MATCH (group:Group {id: $groupId}) + OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(member:User {id: $userId}) + RETURN group {.*}, member {.*, myRoleInGroup: membership.role} + `, + { groupId, userId }, + ) + // Wolle: + // console.log( + // 'transactionResponse: ', + // transactionResponse, + // ) + // console.log( + // 'transaction groups: ', + // transactionResponse.records.map((record) => record.get('group')), + // ) + // console.log( + // 'transaction members: ', + // transactionResponse.records.map((record) => record.get('member')), + // ) + return { + group: transactionResponse.records.map((record) => record.get('group'))[0], + member: transactionResponse.records.map((record) => record.get('member'))[0], + } + }) + try { + // Wolle: + // console.log('enter try !!!') + const { group, member } = await readTxPromise + // Wolle: + // console.log('after !!!') + // console.log('group: ', group) + // console.log('member: ', member) + return !!group && (group.groupType !== 'hidden' || (!!member && !!member.myRoleInGroup)) } catch (error) { // Wolle: console.log('error: ', error) throw new Error(error) @@ -256,7 +314,7 @@ export default shield( SignupVerification: allow, UpdateUser: onlyYourself, CreateGroup: isAuthenticated, - JoinGroup: isAuthenticated, // Wolle: can not be correct + JoinGroup: isAllowedToJoinGroup, ChangeGroupMemberRole: isAllowedToChangeGroupMemberRole, CreatePost: isAuthenticated, UpdatePost: isAuthor, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index ca24ef55f..89c136dc5 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -163,20 +163,28 @@ export default { ChangeGroupMemberRole: async (_parent, params, context, _resolveInfo) => { const { id: groupId, userId, roleInGroup } = params // Wolle + // console.log('ChangeGroupMemberRole !!!') // console.log('groupId: ', groupId) - // console.log('userId: ', groupId) - // console.log('roleInGroup: ', groupId) + // console.log('userId: ', userId) + // console.log('roleInGroup: ', roleInGroup) const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { const joinGroupCypher = ` - MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId}) - SET + MATCH (member:User {id: $userId}), (group:Group {id: $groupId}) + MERGE (member)-[membership:MEMBER_OF]->(group) + ON CREATE SET + membership.createdAt = toString(datetime()), + membership.updatedAt = membership.createdAt, + membership.role = $roleInGroup + ON MATCH SET membership.updatedAt = toString(datetime()), membership.role = $roleInGroup RETURN member {.*, myRoleInGroup: membership.role} ` const result = await transaction.run(joinGroupCypher, { groupId, userId, roleInGroup }) const [member] = await result.records.map((record) => record.get('member')) + // Wolle + // console.log('member: ', member) return member }) try { diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 23fc5646e..d9c2f22e3 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -410,7 +410,7 @@ describe('in mode: always clean db', () => { }) describe('public group', () => { - describe('entered by "owner-of-closed-group"', () => { + describe('joined by "owner-of-closed-group"', () => { it('has "usual" as membership role', async () => { variables = { id: 'public-group', @@ -434,7 +434,7 @@ describe('in mode: always clean db', () => { }) }) - describe('entered by its owner', () => { + describe('joined by its owner', () => { describe('does not create additional "MEMBER_OF" relation and therefore', () => { it('has still "owner" as membership role', async () => { variables = { @@ -462,7 +462,7 @@ describe('in mode: always clean db', () => { }) describe('closed group', () => { - describe('entered by "current-user"', () => { + describe('joined by "current-user"', () => { it('has "pending" as membership role', async () => { variables = { id: 'closed-group', @@ -486,7 +486,7 @@ describe('in mode: always clean db', () => { }) }) - describe('entered by its owner', () => { + describe('joined by its owner', () => { describe('does not create additional "MEMBER_OF" relation and therefore', () => { it('has still "owner" as membership role', async () => { variables = { @@ -514,31 +514,18 @@ describe('in mode: always clean db', () => { }) describe('hidden group', () => { - describe('entered by "owner-of-closed-group"', () => { - it('has "pending" as membership role', async () => { + describe('joined by "owner-of-closed-group"', () => { + it('throws authorization error', 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) + const { errors } = await query({ query: groupMemberQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - describe('entered by its owner', () => { + describe('joined by its owner', () => { describe('does not create additional "MEMBER_OF" relation and therefore', () => { it('has still "owner" as membership role', async () => { variables = { @@ -566,8 +553,18 @@ describe('in mode: always clean db', () => { }) }) }) +}) +describe('in mode: building up – separate for each resolver', () => { describe('GroupMember', () => { + beforeAll(async () => { + await seedBasicsAndClearAuthentication() + }) + + afterAll(async () => { + await cleanDatabase() + }) + describe('unauthenticated', () => { it('throws authorization error', async () => { variables = { @@ -580,10 +577,11 @@ describe('in mode: always clean db', () => { describe('authenticated', () => { let otherUser + let pendingUser let ownerOfClosedGroupUser let ownerOfHiddenGroupUser - beforeEach(async () => { + beforeAll(async () => { // create users otherUser = await Factory.build( 'user', @@ -592,7 +590,18 @@ describe('in mode: always clean db', () => { name: 'Other TestUser', }, { - email: 'test2@example.org', + email: 'other-user@example.org', + password: '1234', + }, + ) + pendingUser = await Factory.build( + 'user', + { + id: 'pending-user', + name: 'Pending TestUser', + }, + { + email: 'pending@example.org', password: '1234', }, ) @@ -689,22 +698,43 @@ describe('in mode: always clean db', () => { categoryIds, }, }) + // 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner await mutate({ - mutation: joinGroupMutation, + mutation: changeGroupMemberRoleMutation, + variables: { + id: 'hidden-group', + userId: 'pending-user', + roleInGroup: 'pending', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, variables: { id: 'hidden-group', userId: 'current-user', + roleInGroup: 'usual', }, }) await mutate({ - mutation: joinGroupMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'hidden-group', userId: 'owner-of-closed-group', + roleInGroup: 'admin', }, }) + // Wolle: + // const groups = await query({ query: groupQuery, variables: {} }) + // console.log('groups.data.Group: ', groups.data.Group) + // const groupMemberOfClosedGroup = await mutate({ + // mutation: groupMemberQuery, + // variables: { + // id: 'hidden-group', + // }, + // }) + // console.log('groupMemberOfClosedGroup.data.GroupMember: ', groupMemberOfClosedGroup.data.GroupMember) - authenticatedUser = await user.toJson() + authenticatedUser = null }) describe('public group', () => { @@ -946,12 +976,16 @@ describe('in mode: always clean db', () => { data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ - id: 'current-user', + id: 'pending-user', myRoleInGroup: 'pending', }), + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'usual', + }), expect.objectContaining({ id: 'owner-of-closed-group', - myRoleInGroup: 'pending', + myRoleInGroup: 'admin', }), expect.objectContaining({ id: 'owner-of-hidden-group', @@ -966,21 +1000,50 @@ describe('in mode: always clean db', () => { variables, }) expect(result).toMatchObject(expected) - expect(result.data.GroupMember.length).toBe(3) + expect(result.data.GroupMember.length).toBe(4) }) }) - describe('by usual member "owner-of-closed-group"', () => { + describe('by usual member "current-user"', () => { beforeEach(async () => { - authenticatedUser = await ownerOfHiddenGroupUser.toJson() - await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - id: 'hidden-group', - userId: 'owner-of-closed-group', - roleInGroup: 'usual', + authenticatedUser = await user.toJson() + }) + + it('finds all members', async () => { + const expected = { + data: { + GroupMember: expect.arrayContaining([ + expect.objectContaining({ + id: 'pending-user', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'usual', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'admin', + }), + 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(4) + }) + }) + + describe('by admin member "owner-of-closed-group"', () => { + beforeEach(async () => { authenticatedUser = await ownerOfClosedGroupUser.toJson() }) @@ -989,13 +1052,17 @@ describe('in mode: always clean db', () => { data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ - id: 'current-user', + id: 'pending-user', myRoleInGroup: 'pending', }), expect.objectContaining({ - id: 'owner-of-closed-group', + id: 'current-user', myRoleInGroup: 'usual', }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'admin', + }), expect.objectContaining({ id: 'owner-of-hidden-group', myRoleInGroup: 'owner', @@ -1009,13 +1076,13 @@ describe('in mode: always clean db', () => { variables, }) expect(result).toMatchObject(expected) - expect(result.data.GroupMember.length).toBe(3) + expect(result.data.GroupMember.length).toBe(4) }) }) - describe('by pending member "current-user"', () => { + describe('by pending member "pending-user"', () => { beforeEach(async () => { - authenticatedUser = await user.toJson() + authenticatedUser = await pendingUser.toJson() }) it('throws authorization error', async () => { @@ -1038,16 +1105,6 @@ describe('in mode: always clean db', () => { }) }) }) -}) - -describe('in mode: building up', () => { - beforeAll(async () => { - await seedBasicsAndClearAuthentication() - }) - - afterAll(async () => { - await cleanDatabase() - }) describe('ChangeGroupMemberRole', () => { let pendingMemberUser @@ -1057,6 +1114,7 @@ describe('in mode: building up', () => { let secondOwnerMemberUser beforeAll(async () => { + await seedBasicsAndClearAuthentication() // create users pendingMemberUser = await Factory.build( 'user', @@ -1156,34 +1214,6 @@ describe('in mode: building up', () => { categoryIds, }, }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'pending-member-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'usual-member-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'admin-member-user', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - id: 'closed-group', - userId: 'second-owner-member-user', - }, - }) // hidden-group authenticatedUser = await adminMemberUser.toJson() await mutate({ @@ -1198,20 +1228,45 @@ describe('in mode: building up', () => { categoryIds, }, }) + // 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner await mutate({ - mutation: joinGroupMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'hidden-group', userId: 'admin-member-user', + roleInGroup: 'usual', }, }) await mutate({ - mutation: joinGroupMutation, + mutation: changeGroupMemberRoleMutation, variables: { id: 'hidden-group', userId: 'second-owner-member-user', + roleInGroup: 'usual', }, }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + id: 'hidden-group', + userId: 'admin-member-user', + roleInGroup: 'usual', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + id: 'hidden-group', + userId: 'second-owner-member-user', + roleInGroup: 'usual', + }, + }) + + authenticatedUser = null + }) + + afterAll(async () => { + await cleanDatabase() }) describe('unauthenticated', () => { @@ -1234,13 +1289,13 @@ describe('in mode: building up', () => { } }) - describe('give the members their prospective roles', () => { + describe('join the members and give them their prospective roles', () => { describe('by owner "owner-member-user"', () => { beforeEach(async () => { authenticatedUser = await ownerMemberUser.toJson() }) - describe('switch role of "usual-member-user"', () => { + describe('for "usual-member-user"', () => { beforeEach(async () => { variables = { ...variables, @@ -1248,7 +1303,7 @@ describe('in mode: building up', () => { } }) - describe('to usual', () => { + describe('as usual', () => { beforeEach(async () => { variables = { ...variables, @@ -1279,7 +1334,7 @@ describe('in mode: building up', () => { }) }) - describe('switch role of "admin-member-user"', () => { + describe('for "admin-member-user"', () => { beforeEach(async () => { variables = { ...variables, @@ -1287,7 +1342,7 @@ describe('in mode: building up', () => { } }) - describe('to admin', () => { + describe('as admin', () => { beforeEach(async () => { variables = { ...variables, @@ -1325,7 +1380,7 @@ describe('in mode: building up', () => { }) }) - describe('switch role of "second-owner-member-user"', () => { + describe('for "second-owner-member-user"', () => { beforeEach(async () => { variables = { ...variables, @@ -1333,7 +1388,7 @@ describe('in mode: building up', () => { } }) - describe('to owner', () => { + describe('as owner', () => { beforeEach(async () => { variables = { ...variables, @@ -1395,8 +1450,9 @@ describe('in mode: building up', () => { }) }) - // Wolle: shall this be possible for now? - // or shall only an owner who gave the second owner the owner role downgrade themself? + // shall this be possible in the future? + // or shall only an owner who gave the second owner the owner role downgrade themself for savety? + // otherwise the first owner who downgrades the other one has the victory over the group! describe('by second owner "second-owner-member-user"', () => { beforeEach(async () => { authenticatedUser = await secondOwnerMemberUser.toJson() @@ -1410,26 +1466,16 @@ describe('in mode: building up', () => { } }) - it('has role admin', async () => { - const expected = { - data: { - ChangeGroupMemberRole: { - id: 'owner-member-user', - myRoleInGroup: 'admin', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - describe('back to owner', () => { + describe('to same role owner', () => { beforeEach(async () => { variables = { ...variables, @@ -1437,7 +1483,7 @@ describe('in mode: building up', () => { } }) - it('has role owner again', async () => { + it('has role owner still', async () => { const expected = { data: { ChangeGroupMemberRole: { @@ -1575,22 +1621,12 @@ describe('in mode: building up', () => { } }) - it('has role admin again', async () => { - const expected = { - data: { - ChangeGroupMemberRole: { - id: 'admin-member-user', - myRoleInGroup: 'admin', - }, - }, - errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) }) From 9dd819f8d23bbed03d1d797a98b465190741a7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 10:25:39 +0200 Subject: [PATCH 26/58] Set not 'membership.updatedAt' on relation creation Co-Authored-By: Mogge --- backend/src/schema/resolvers/groups.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 89c136dc5..04ef6d704 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -109,7 +109,6 @@ export default { MERGE (owner)-[:CREATED]->(group) MERGE (owner)-[membership:MEMBER_OF]->(group) SET membership.createdAt = toString(datetime()) - SET membership.updatedAt = membership.createdAt SET membership.role = 'owner' ${categoriesCypher} RETURN group {.*, myRole: membership.role} @@ -140,7 +139,6 @@ export default { MERGE (member)-[membership:MEMBER_OF]->(group) ON CREATE SET membership.createdAt = toString(datetime()), - membership.updatedAt = membership.createdAt, membership.role = CASE WHEN group.groupType = 'public' THEN 'usual' @@ -174,7 +172,6 @@ export default { MERGE (member)-[membership:MEMBER_OF]->(group) ON CREATE SET membership.createdAt = toString(datetime()), - membership.updatedAt = membership.createdAt, membership.role = $roleInGroup ON MATCH SET membership.updatedAt = toString(datetime()), From 356fac9d948642379dfecce2b0163bb198ba1983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 10:36:37 +0200 Subject: [PATCH 27/58] Destruction of 'mutate' and 'query' in 'groups.spec.js' in one line now Co-Authored-By: Mogge --- backend/src/schema/resolvers/groups.spec.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index d9c2f22e3..6787e866e 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -31,8 +31,7 @@ const { server } = createServer({ } }, }) -const { query } = createTestClient(server) -const { mutate } = createTestClient(server) +const { mutate, query } = createTestClient(server) const seedBasicsAndClearAuthentication = async () => { variables = {} From b703621eb7a8db37cbd4de1aab0be955aea6c462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 10:52:15 +0200 Subject: [PATCH 28/58] Remove local variables 'expected' and some 'variable' and put the values directly in the 'expect' calls in 'groups.spec.js' Co-Authored-By: Mogge --- backend/src/schema/resolvers/groups.spec.js | 379 +++++++++----------- 1 file changed, 170 insertions(+), 209 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 6787e866e..df4c5388b 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -121,7 +121,7 @@ describe('in mode: always clean db', () => { }) it('creates a group', async () => { - const expected = { + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ data: { CreateGroup: { name: 'The Best Group', @@ -130,14 +130,11 @@ describe('in mode: always clean db', () => { }, }, errors: undefined, - } - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( - expected, - ) + }) }) it('assigns the authenticated user as owner', async () => { - const expected = { + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ data: { CreateGroup: { name: 'The Best Group', @@ -145,17 +142,13 @@ describe('in mode: always clean db', () => { }, }, 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, - ) + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ + data: { CreateGroup: { disabled: false, deleted: false } }, + }) }) describe('description', () => { @@ -259,7 +252,7 @@ describe('in mode: always clean db', () => { describe('query groups', () => { describe('without any filters', () => { it('finds all groups', async () => { - const expected = { + await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({ data: { Group: expect.arrayContaining([ expect.objectContaining({ @@ -275,16 +268,15 @@ describe('in mode: always clean db', () => { ]), }, errors: undefined, - } - await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject( - expected, - ) + }) }) }) describe('isMember = true', () => { it('finds only groups where user is member', async () => { - const expected = { + await expect( + query({ query: groupQuery, variables: { isMember: true } }), + ).resolves.toMatchObject({ data: { Group: [ { @@ -295,16 +287,15 @@ describe('in mode: always clean db', () => { ], }, errors: undefined, - } - await expect( - query({ query: groupQuery, variables: { isMember: true } }), - ).resolves.toMatchObject(expected) + }) }) }) describe('isMember = false', () => { it('finds only groups where user is not(!) member', async () => { - const expected = { + await expect( + query({ query: groupQuery, variables: { isMember: false } }), + ).resolves.toMatchObject({ data: { Group: expect.arrayContaining([ expect.objectContaining({ @@ -315,10 +306,7 @@ describe('in mode: always clean db', () => { ]), }, errors: undefined, - } - await expect( - query({ query: groupQuery, variables: { isMember: false } }), - ).resolves.toMatchObject(expected) + }) }) }) }) @@ -411,11 +399,15 @@ describe('in mode: always clean db', () => { describe('public group', () => { describe('joined by "owner-of-closed-group"', () => { it('has "usual" as membership role', async () => { - variables = { - id: 'public-group', - userId: 'owner-of-closed-group', - } - const expected = { + await expect( + mutate({ + mutation: joinGroupMutation, + variables: { + id: 'public-group', + userId: 'owner-of-closed-group', + }, + }), + ).resolves.toMatchObject({ data: { JoinGroup: { id: 'owner-of-closed-group', @@ -423,24 +415,22 @@ describe('in mode: always clean db', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: joinGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) describe('joined 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 = { + await expect( + mutate({ + mutation: joinGroupMutation, + variables: { + id: 'public-group', + userId: 'current-user', + }, + }), + ).resolves.toMatchObject({ data: { JoinGroup: { id: 'current-user', @@ -448,13 +438,7 @@ describe('in mode: always clean db', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: joinGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) }) @@ -463,11 +447,15 @@ describe('in mode: always clean db', () => { describe('closed group', () => { describe('joined by "current-user"', () => { it('has "pending" as membership role', async () => { - variables = { - id: 'closed-group', - userId: 'current-user', - } - const expected = { + await expect( + mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'current-user', + }, + }), + ).resolves.toMatchObject({ data: { JoinGroup: { id: 'current-user', @@ -475,24 +463,22 @@ describe('in mode: always clean db', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: joinGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) describe('joined 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 = { + await expect( + mutate({ + mutation: joinGroupMutation, + variables: { + id: 'closed-group', + userId: 'owner-of-closed-group', + }, + }), + ).resolves.toMatchObject({ data: { JoinGroup: { id: 'owner-of-closed-group', @@ -500,13 +486,7 @@ describe('in mode: always clean db', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: joinGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) }) @@ -527,11 +507,15 @@ describe('in mode: always clean db', () => { describe('joined 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 = { + await expect( + mutate({ + mutation: joinGroupMutation, + variables: { + id: 'hidden-group', + userId: 'owner-of-hidden-group', + }, + }), + ).resolves.toMatchObject({ data: { JoinGroup: { id: 'owner-of-hidden-group', @@ -539,13 +523,7 @@ describe('in mode: always clean db', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: joinGroupMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) }) @@ -750,7 +728,11 @@ describe('in mode: building up – separate for each resolver', () => { }) it('finds all members', async () => { - const expected = { + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject({ data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ @@ -768,12 +750,7 @@ describe('in mode: building up – separate for each resolver', () => { ]), }, errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, }) - expect(result).toMatchObject(expected) expect(result.data.GroupMember.length).toBe(3) }) }) @@ -784,7 +761,11 @@ describe('in mode: building up – separate for each resolver', () => { }) it('finds all members', async () => { - const expected = { + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject({ data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ @@ -802,12 +783,7 @@ describe('in mode: building up – separate for each resolver', () => { ]), }, errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, }) - expect(result).toMatchObject(expected) expect(result.data.GroupMember.length).toBe(3) }) }) @@ -818,7 +794,11 @@ describe('in mode: building up – separate for each resolver', () => { }) it('finds all members', async () => { - const expected = { + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject({ data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ @@ -836,12 +816,7 @@ describe('in mode: building up – separate for each resolver', () => { ]), }, errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, }) - expect(result).toMatchObject(expected) expect(result.data.GroupMember.length).toBe(3) }) }) @@ -862,7 +837,11 @@ describe('in mode: building up – separate for each resolver', () => { }) it('finds all members', async () => { - const expected = { + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject({ data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ @@ -880,12 +859,7 @@ describe('in mode: building up – separate for each resolver', () => { ]), }, errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, }) - expect(result).toMatchObject(expected) expect(result.data.GroupMember.length).toBe(3) }) }) @@ -905,7 +879,11 @@ describe('in mode: building up – separate for each resolver', () => { }) it('finds all members', async () => { - const expected = { + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject({ data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ @@ -923,12 +901,7 @@ describe('in mode: building up – separate for each resolver', () => { ]), }, errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, }) - expect(result).toMatchObject(expected) expect(result.data.GroupMember.length).toBe(3) }) }) @@ -971,7 +944,11 @@ describe('in mode: building up – separate for each resolver', () => { }) it('finds all members', async () => { - const expected = { + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject({ data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ @@ -993,12 +970,7 @@ describe('in mode: building up – separate for each resolver', () => { ]), }, errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, }) - expect(result).toMatchObject(expected) expect(result.data.GroupMember.length).toBe(4) }) }) @@ -1009,7 +981,11 @@ describe('in mode: building up – separate for each resolver', () => { }) it('finds all members', async () => { - const expected = { + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject({ data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ @@ -1031,12 +1007,7 @@ describe('in mode: building up – separate for each resolver', () => { ]), }, errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, }) - expect(result).toMatchObject(expected) expect(result.data.GroupMember.length).toBe(4) }) }) @@ -1047,7 +1018,11 @@ describe('in mode: building up – separate for each resolver', () => { }) it('finds all members', async () => { - const expected = { + const result = await mutate({ + mutation: groupMemberQuery, + variables, + }) + expect(result).toMatchObject({ data: { GroupMember: expect.arrayContaining([ expect.objectContaining({ @@ -1069,12 +1044,7 @@ describe('in mode: building up – separate for each resolver', () => { ]), }, errors: undefined, - } - const result = await mutate({ - mutation: groupMemberQuery, - variables, }) - expect(result).toMatchObject(expected) expect(result.data.GroupMember.length).toBe(4) }) }) @@ -1311,7 +1281,12 @@ describe('in mode: building up – separate for each resolver', () => { }) it('has role usual', async () => { - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'usual-member-user', @@ -1319,13 +1294,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) // the GQL mutation needs this fields in the result for testing @@ -1360,7 +1329,12 @@ describe('in mode: building up – separate for each resolver', () => { // }, // }) // console.log('groupMemberOfClosedGroup.data.GroupMember: ', groupMemberOfClosedGroup.data.GroupMember) - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'admin-member-user', @@ -1368,13 +1342,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) }) @@ -1396,7 +1364,12 @@ describe('in mode: building up – separate for each resolver', () => { }) it('has role owner', async () => { - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'second-owner-member-user', @@ -1404,13 +1377,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) }) @@ -1483,7 +1450,12 @@ describe('in mode: building up – separate for each resolver', () => { }) it('has role owner still', async () => { - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'owner-member-user', @@ -1491,13 +1463,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) }) @@ -1594,7 +1560,12 @@ describe('in mode: building up – separate for each resolver', () => { }) it('has role owner', async () => { - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'admin-member-user', @@ -1602,13 +1573,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) @@ -1773,7 +1738,12 @@ describe('in mode: building up – separate for each resolver', () => { }) it('has role admin', async () => { - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'usual-member-user', @@ -1781,13 +1751,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) @@ -1800,7 +1764,12 @@ describe('in mode: building up – separate for each resolver', () => { }) it('has role usual again', async () => { - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'usual-member-user', @@ -1808,13 +1777,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) }) @@ -1962,7 +1925,12 @@ describe('in mode: building up – separate for each resolver', () => { }) it('has role usual', async () => { - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'pending-member-user', @@ -1970,13 +1938,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) @@ -1989,7 +1951,12 @@ describe('in mode: building up – separate for each resolver', () => { }) it('has role usual again', async () => { - const expected = { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ data: { ChangeGroupMemberRole: { id: 'pending-member-user', @@ -1997,13 +1964,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }, errors: undefined, - } - await expect( - mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }), - ).resolves.toMatchObject(expected) + }) }) }) }) From 826cf5a8e0364845f4c92b7d870e389f02f5a700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 11:03:47 +0200 Subject: [PATCH 29/58] Renamed 'GroupMember' to 'GroupMembers' Co-Authored-By: Mogge --- backend/src/db/graphql/groups.js | 4 +- .../src/middleware/permissionsMiddleware.js | 2 +- backend/src/schema/resolvers/groups.js | 2 +- backend/src/schema/resolvers/groups.spec.js | 72 +++++++++---------- backend/src/schema/types/type/Group.gql | 2 +- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 2a9647860..3a7f047d2 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -116,9 +116,9 @@ export const groupQuery = gql` } ` -export const groupMemberQuery = gql` +export const groupMembersQuery = gql` query ($id: ID!, $first: Int, $offset: Int, $orderBy: [_UserOrdering], $filter: _UserFilter) { - GroupMember(id: $id, first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) { + GroupMembers(id: $id, first: $first, offset: $offset, orderBy: $orderBy, filter: $filter) { id name slug diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index f0ddd04ca..c8f97cd17 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -286,7 +286,7 @@ export default shield( statistics: allow, currentUser: allow, Group: isAuthenticated, - GroupMember: isAllowedSeeingMembersOfGroup, + GroupMembers: isAllowedSeeingMembersOfGroup, Post: allow, profilePagePosts: allow, Comment: allow, diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 04ef6d704..5e401c28e 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -46,7 +46,7 @@ export default { session.close() } }, - GroupMember: async (_object, params, context, _resolveInfo) => { + GroupMembers: async (_object, params, context, _resolveInfo) => { const { id: groupId } = params // Wolle: console.log('groupId: ', groupId) const session = context.driver.session() diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index df4c5388b..c81f976be 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -4,7 +4,7 @@ import { createGroupMutation, joinGroupMutation, changeGroupMemberRoleMutation, - groupMemberQuery, + groupMembersQuery, groupQuery, } from '../../db/graphql/groups' import { getNeode, getDriver } from '../../db/neo4j' @@ -499,7 +499,7 @@ describe('in mode: always clean db', () => { id: 'hidden-group', userId: 'owner-of-closed-group', } - const { errors } = await query({ query: groupMemberQuery, variables }) + const { errors } = await query({ query: groupMembersQuery, variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -533,7 +533,7 @@ describe('in mode: always clean db', () => { }) describe('in mode: building up – separate for each resolver', () => { - describe('GroupMember', () => { + describe('GroupMembers', () => { beforeAll(async () => { await seedBasicsAndClearAuthentication() }) @@ -547,7 +547,7 @@ describe('in mode: building up – separate for each resolver', () => { variables = { id: 'not-existing-group', } - const { errors } = await query({ query: groupMemberQuery, variables }) + const { errors } = await query({ query: groupMembersQuery, variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -704,12 +704,12 @@ describe('in mode: building up – separate for each resolver', () => { // const groups = await query({ query: groupQuery, variables: {} }) // console.log('groups.data.Group: ', groups.data.Group) // const groupMemberOfClosedGroup = await mutate({ - // mutation: groupMemberQuery, + // mutation: groupMembersQuery, // variables: { // id: 'hidden-group', // }, // }) - // console.log('groupMemberOfClosedGroup.data.GroupMember: ', groupMemberOfClosedGroup.data.GroupMember) + // console.log('groupMemberOfClosedGroup.data.GroupMembers: ', groupMemberOfClosedGroup.data.GroupMembers) authenticatedUser = null }) @@ -729,12 +729,12 @@ describe('in mode: building up – separate for each resolver', () => { it('finds all members', async () => { const result = await mutate({ - mutation: groupMemberQuery, + mutation: groupMembersQuery, variables, }) expect(result).toMatchObject({ data: { - GroupMember: expect.arrayContaining([ + GroupMembers: expect.arrayContaining([ expect.objectContaining({ id: 'current-user', myRoleInGroup: 'owner', @@ -751,7 +751,7 @@ describe('in mode: building up – separate for each resolver', () => { }, errors: undefined, }) - expect(result.data.GroupMember.length).toBe(3) + expect(result.data.GroupMembers.length).toBe(3) }) }) @@ -762,12 +762,12 @@ describe('in mode: building up – separate for each resolver', () => { it('finds all members', async () => { const result = await mutate({ - mutation: groupMemberQuery, + mutation: groupMembersQuery, variables, }) expect(result).toMatchObject({ data: { - GroupMember: expect.arrayContaining([ + GroupMembers: expect.arrayContaining([ expect.objectContaining({ id: 'current-user', myRoleInGroup: 'owner', @@ -784,7 +784,7 @@ describe('in mode: building up – separate for each resolver', () => { }, errors: undefined, }) - expect(result.data.GroupMember.length).toBe(3) + expect(result.data.GroupMembers.length).toBe(3) }) }) @@ -795,12 +795,12 @@ describe('in mode: building up – separate for each resolver', () => { it('finds all members', async () => { const result = await mutate({ - mutation: groupMemberQuery, + mutation: groupMembersQuery, variables, }) expect(result).toMatchObject({ data: { - GroupMember: expect.arrayContaining([ + GroupMembers: expect.arrayContaining([ expect.objectContaining({ id: 'current-user', myRoleInGroup: 'owner', @@ -817,7 +817,7 @@ describe('in mode: building up – separate for each resolver', () => { }, errors: undefined, }) - expect(result.data.GroupMember.length).toBe(3) + expect(result.data.GroupMembers.length).toBe(3) }) }) }) @@ -838,12 +838,12 @@ describe('in mode: building up – separate for each resolver', () => { it('finds all members', async () => { const result = await mutate({ - mutation: groupMemberQuery, + mutation: groupMembersQuery, variables, }) expect(result).toMatchObject({ data: { - GroupMember: expect.arrayContaining([ + GroupMembers: expect.arrayContaining([ expect.objectContaining({ id: 'current-user', myRoleInGroup: 'pending', @@ -860,7 +860,7 @@ describe('in mode: building up – separate for each resolver', () => { }, errors: undefined, }) - expect(result.data.GroupMember.length).toBe(3) + expect(result.data.GroupMembers.length).toBe(3) }) }) @@ -880,12 +880,12 @@ describe('in mode: building up – separate for each resolver', () => { it('finds all members', async () => { const result = await mutate({ - mutation: groupMemberQuery, + mutation: groupMembersQuery, variables, }) expect(result).toMatchObject({ data: { - GroupMember: expect.arrayContaining([ + GroupMembers: expect.arrayContaining([ expect.objectContaining({ id: 'current-user', myRoleInGroup: 'pending', @@ -902,7 +902,7 @@ describe('in mode: building up – separate for each resolver', () => { }, errors: undefined, }) - expect(result.data.GroupMember.length).toBe(3) + expect(result.data.GroupMembers.length).toBe(3) }) }) @@ -912,7 +912,7 @@ describe('in mode: building up – separate for each resolver', () => { }) it('throws authorization error', async () => { - const { errors } = await query({ query: groupMemberQuery, variables }) + const { errors } = await query({ query: groupMembersQuery, variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -923,7 +923,7 @@ describe('in mode: building up – separate for each resolver', () => { }) it('throws authorization error', async () => { - const { errors } = await query({ query: groupMemberQuery, variables }) + const { errors } = await query({ query: groupMembersQuery, variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -945,12 +945,12 @@ describe('in mode: building up – separate for each resolver', () => { it('finds all members', async () => { const result = await mutate({ - mutation: groupMemberQuery, + mutation: groupMembersQuery, variables, }) expect(result).toMatchObject({ data: { - GroupMember: expect.arrayContaining([ + GroupMembers: expect.arrayContaining([ expect.objectContaining({ id: 'pending-user', myRoleInGroup: 'pending', @@ -971,7 +971,7 @@ describe('in mode: building up – separate for each resolver', () => { }, errors: undefined, }) - expect(result.data.GroupMember.length).toBe(4) + expect(result.data.GroupMembers.length).toBe(4) }) }) @@ -982,12 +982,12 @@ describe('in mode: building up – separate for each resolver', () => { it('finds all members', async () => { const result = await mutate({ - mutation: groupMemberQuery, + mutation: groupMembersQuery, variables, }) expect(result).toMatchObject({ data: { - GroupMember: expect.arrayContaining([ + GroupMembers: expect.arrayContaining([ expect.objectContaining({ id: 'pending-user', myRoleInGroup: 'pending', @@ -1008,7 +1008,7 @@ describe('in mode: building up – separate for each resolver', () => { }, errors: undefined, }) - expect(result.data.GroupMember.length).toBe(4) + expect(result.data.GroupMembers.length).toBe(4) }) }) @@ -1019,12 +1019,12 @@ describe('in mode: building up – separate for each resolver', () => { it('finds all members', async () => { const result = await mutate({ - mutation: groupMemberQuery, + mutation: groupMembersQuery, variables, }) expect(result).toMatchObject({ data: { - GroupMember: expect.arrayContaining([ + GroupMembers: expect.arrayContaining([ expect.objectContaining({ id: 'pending-user', myRoleInGroup: 'pending', @@ -1045,7 +1045,7 @@ describe('in mode: building up – separate for each resolver', () => { }, errors: undefined, }) - expect(result.data.GroupMember.length).toBe(4) + expect(result.data.GroupMembers.length).toBe(4) }) }) @@ -1055,7 +1055,7 @@ describe('in mode: building up – separate for each resolver', () => { }) it('throws authorization error', async () => { - const { errors } = await query({ query: groupMemberQuery, variables }) + const { errors } = await query({ query: groupMembersQuery, variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -1066,7 +1066,7 @@ describe('in mode: building up – separate for each resolver', () => { }) it('throws authorization error', async () => { - const { errors } = await query({ query: groupMemberQuery, variables }) + const { errors } = await query({ query: groupMembersQuery, variables }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -1323,12 +1323,12 @@ describe('in mode: building up – separate for each resolver', () => { // const groups = await query({ query: groupQuery, variables: {} }) // console.log('groups.data.Group: ', groups.data.Group) // const groupMemberOfClosedGroup = await mutate({ - // mutation: groupMemberQuery, + // mutation: groupMembersQuery, // variables: { // id: 'closed-group', // }, // }) - // console.log('groupMemberOfClosedGroup.data.GroupMember: ', groupMemberOfClosedGroup.data.GroupMember) + // console.log('groupMemberOfClosedGroup.data.GroupMembers: ', groupMemberOfClosedGroup.data.GroupMembers) await expect( mutate({ mutation: changeGroupMemberRoleMutation, diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index f12b98c07..1f59d7cc4 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -73,7 +73,7 @@ type Query { orderBy: [_GroupOrdering] ): [Group] - GroupMember( + GroupMembers( id: ID! first: Int offset: Int From 813c072dc7d0ba9983c780c194ebfacd90185671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 11:32:22 +0200 Subject: [PATCH 30/58] Change GQL parameter for 'JoinGroup' from 'id' to 'groupId' Co-Authored-By: Mogge --- backend/src/db/graphql/groups.js | 4 +- backend/src/db/seed.js | 26 +++++------ .../src/middleware/permissionsMiddleware.js | 2 +- backend/src/schema/resolvers/groups.js | 2 +- backend/src/schema/resolvers/groups.spec.js | 46 ++++++++++--------- backend/src/schema/types/type/Group.gql | 2 +- 6 files changed, 43 insertions(+), 39 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 3a7f047d2..a0aef97b2 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -40,8 +40,8 @@ export const createGroupMutation = gql` ` export const joinGroupMutation = gql` - mutation ($id: ID!, $userId: ID!) { - JoinGroup(id: $id, userId: $userId) { + mutation ($groupId: ID!, $userId: ID!) { + JoinGroup(groupId: $groupId, userId: $userId) { id name slug diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index 0010b09ef..1bd535456 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -408,28 +408,28 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: joinGroupMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u2', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u3', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u4', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u6', }, }), @@ -480,35 +480,35 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: joinGroupMutation, variables: { - id: 'g1', + groupId: 'g1', userId: 'u1', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g1', + groupId: 'g1', userId: 'u2', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g1', + groupId: 'g1', userId: 'u5', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g1', + groupId: 'g1', userId: 'u6', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g1', + groupId: 'g1', userId: 'u7', }, }), @@ -567,28 +567,28 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: joinGroupMutation, variables: { - id: 'g2', + groupId: 'g2', userId: 'u4', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g2', + groupId: 'g2', userId: 'u5', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g2', + groupId: 'g2', userId: 'u6', }, }), mutate({ mutation: joinGroupMutation, variables: { - id: 'g2', + groupId: 'g2', userId: 'u7', }, }), diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index c8f97cd17..bbaff67d9 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -172,7 +172,7 @@ const isAllowedToJoinGroup = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { if (!(user && user.id)) return false - const { id: groupId, userId } = args + const { groupId, userId } = args // Wolle: // console.log('adminId: ', adminId) // console.log('groupId: ', groupId) diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 5e401c28e..d34d62eb7 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -131,7 +131,7 @@ export default { } }, JoinGroup: async (_parent, params, context, _resolveInfo) => { - const { id: groupId, userId } = params + const { groupId, userId } = params const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { const joinGroupCypher = ` diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index c81f976be..74f806b7b 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -316,11 +316,13 @@ describe('in mode: always clean db', () => { describe('JoinGroup', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { - variables = { - id: 'not-existing-group', - userId: 'current-user', - } - const { errors } = await mutate({ mutation: joinGroupMutation, variables }) + const { errors } = await mutate({ + mutation: joinGroupMutation, + variables: { + groupId: 'not-existing-group', + userId: 'current-user', + }, + }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -403,7 +405,7 @@ describe('in mode: always clean db', () => { mutate({ mutation: joinGroupMutation, variables: { - id: 'public-group', + groupId: 'public-group', userId: 'owner-of-closed-group', }, }), @@ -426,7 +428,7 @@ describe('in mode: always clean db', () => { mutate({ mutation: joinGroupMutation, variables: { - id: 'public-group', + groupId: 'public-group', userId: 'current-user', }, }), @@ -451,7 +453,7 @@ describe('in mode: always clean db', () => { mutate({ mutation: joinGroupMutation, variables: { - id: 'closed-group', + groupId: 'closed-group', userId: 'current-user', }, }), @@ -474,7 +476,7 @@ describe('in mode: always clean db', () => { mutate({ mutation: joinGroupMutation, variables: { - id: 'closed-group', + groupId: 'closed-group', userId: 'owner-of-closed-group', }, }), @@ -495,11 +497,13 @@ describe('in mode: always clean db', () => { describe('hidden group', () => { describe('joined by "owner-of-closed-group"', () => { it('throws authorization error', async () => { - variables = { - id: 'hidden-group', - userId: 'owner-of-closed-group', - } - const { errors } = await query({ query: groupMembersQuery, variables }) + const { errors } = await query({ + query: joinGroupMutation, + variables: { + groupId: 'hidden-group', + userId: 'owner-of-closed-group', + }, + }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -511,7 +515,7 @@ describe('in mode: always clean db', () => { mutate({ mutation: joinGroupMutation, variables: { - id: 'hidden-group', + groupId: 'hidden-group', userId: 'owner-of-hidden-group', }, }), @@ -622,14 +626,14 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: joinGroupMutation, variables: { - id: 'public-group', + groupId: 'public-group', userId: 'owner-of-closed-group', }, }) await mutate({ mutation: joinGroupMutation, variables: { - id: 'public-group', + groupId: 'public-group', userId: 'owner-of-hidden-group', }, }) @@ -650,14 +654,14 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: joinGroupMutation, variables: { - id: 'closed-group', + groupId: 'closed-group', userId: 'current-user', }, }) await mutate({ mutation: joinGroupMutation, variables: { - id: 'closed-group', + groupId: 'closed-group', userId: 'owner-of-hidden-group', }, }) @@ -1158,14 +1162,14 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: joinGroupMutation, variables: { - id: 'public-group', + groupId: 'public-group', userId: 'owner-of-closed-group', }, }) await mutate({ mutation: joinGroupMutation, variables: { - id: 'public-group', + groupId: 'public-group', userId: 'owner-of-hidden-group', }, }) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index 1f59d7cc4..9d2210a1a 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -115,7 +115,7 @@ type Mutation { DeleteGroup(id: ID!): Group JoinGroup( - id: ID! + groupId: ID! userId: ID! ): User From fd497a03aae62b0296c7f1ffc5129c2facf25c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 11:58:23 +0200 Subject: [PATCH 31/58] Change GQL parameter for 'ChangeGroupMemberRole' from 'id' to 'groupId' Co-Authored-By: Mogge --- backend/src/db/graphql/groups.js | 4 +- backend/src/db/seed.js | 20 ++++----- .../src/middleware/permissionsMiddleware.js | 2 +- backend/src/schema/resolvers/groups.js | 2 +- backend/src/schema/resolvers/groups.spec.js | 44 ++++++++----------- backend/src/schema/types/type/Group.gql | 2 +- 6 files changed, 34 insertions(+), 40 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index a0aef97b2..c6f110ed1 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -51,8 +51,8 @@ export const joinGroupMutation = gql` ` export const changeGroupMemberRoleMutation = gql` - mutation ($id: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { - ChangeGroupMemberRole(id: $id, userId: $userId, roleInGroup: $roleInGroup) { + mutation ($groupId: ID!, $userId: ID!, $roleInGroup: GroupMemberRole!) { + ChangeGroupMemberRole(groupId: $groupId, userId: $userId, roleInGroup: $roleInGroup) { id name slug diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index 1bd535456..a71b11131 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -438,7 +438,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u2', roleInGroup: 'usual', }, @@ -446,7 +446,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u4', roleInGroup: 'admin', }, @@ -454,7 +454,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u3', roleInGroup: 'owner', }, @@ -517,7 +517,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u1', roleInGroup: 'usual', }, @@ -525,7 +525,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u2', roleInGroup: 'usual', }, @@ -533,7 +533,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u5', roleInGroup: 'admin', }, @@ -541,7 +541,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u6', roleInGroup: 'owner', }, @@ -597,7 +597,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u4', roleInGroup: 'usual', }, @@ -605,7 +605,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u5', roleInGroup: 'usual', }, @@ -613,7 +613,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'g0', + groupId: 'g0', userId: 'u6', roleInGroup: 'usual', }, diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index bbaff67d9..926f824e4 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -98,7 +98,7 @@ const isAllowedToChangeGroupMemberRole = rule({ })(async (_parent, args, { user, driver }) => { if (!(user && user.id)) return false const adminId = user.id - const { id: groupId, userId, roleInGroup } = args + const { groupId, userId, roleInGroup } = args if (adminId === userId) return false // Wolle: // console.log('isAllowedToChangeGroupMemberRole !!!') diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index d34d62eb7..6ca09d72a 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -159,7 +159,7 @@ export default { } }, ChangeGroupMemberRole: async (_parent, params, context, _resolveInfo) => { - const { id: groupId, userId, roleInGroup } = params + const { groupId, userId, roleInGroup } = params // Wolle // console.log('ChangeGroupMemberRole !!!') // console.log('groupId: ', groupId) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 74f806b7b..a660f6ce8 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -659,10 +659,11 @@ describe('in mode: building up – separate for each resolver', () => { }, }) await mutate({ - mutation: joinGroupMutation, + mutation: changeGroupMemberRoleMutation, variables: { groupId: 'closed-group', userId: 'owner-of-hidden-group', + roleInGroup: 'usual', }, }) // hidden-group @@ -683,7 +684,7 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'hidden-group', + groupId: 'hidden-group', userId: 'pending-user', roleInGroup: 'pending', }, @@ -691,7 +692,7 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'hidden-group', + groupId: 'hidden-group', userId: 'current-user', roleInGroup: 'usual', }, @@ -699,7 +700,7 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'hidden-group', + groupId: 'hidden-group', userId: 'owner-of-closed-group', roleInGroup: 'admin', }, @@ -858,7 +859,7 @@ describe('in mode: building up – separate for each resolver', () => { }), expect.objectContaining({ id: 'owner-of-hidden-group', - myRoleInGroup: 'pending', + myRoleInGroup: 'usual', }), ]), }, @@ -870,15 +871,6 @@ describe('in mode: building up – separate for each resolver', () => { describe('by usual member "owner-of-hidden-group"', () => { beforeEach(async () => { - authenticatedUser = await ownerOfClosedGroupUser.toJson() - await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - id: 'closed-group', - userId: 'owner-of-hidden-group', - roleInGroup: 'usual', - }, - }) authenticatedUser = await ownerOfHiddenGroupUser.toJson() }) @@ -1205,7 +1197,7 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'hidden-group', + groupId: 'hidden-group', userId: 'admin-member-user', roleInGroup: 'usual', }, @@ -1213,7 +1205,7 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'hidden-group', + groupId: 'hidden-group', userId: 'second-owner-member-user', roleInGroup: 'usual', }, @@ -1221,7 +1213,7 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'hidden-group', + groupId: 'hidden-group', userId: 'admin-member-user', roleInGroup: 'usual', }, @@ -1229,7 +1221,7 @@ describe('in mode: building up – separate for each resolver', () => { await mutate({ mutation: changeGroupMemberRoleMutation, variables: { - id: 'hidden-group', + groupId: 'hidden-group', userId: 'second-owner-member-user', roleInGroup: 'usual', }, @@ -1244,12 +1236,14 @@ describe('in mode: building up – separate for each resolver', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { - variables = { - id: 'not-existing-group', - userId: 'current-user', - roleInGroup: 'pending', - } - const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables }) + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'not-existing-group', + userId: 'current-user', + roleInGroup: 'pending', + }, + }) expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) @@ -1258,7 +1252,7 @@ describe('in mode: building up – separate for each resolver', () => { describe('in all group types – here "closed-group" for example', () => { beforeEach(async () => { variables = { - id: 'closed-group', + groupId: 'closed-group', } }) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index 9d2210a1a..5c98e49e8 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -120,7 +120,7 @@ type Mutation { ): User ChangeGroupMemberRole( - id: ID! + groupId: ID! userId: ID! roleInGroup: GroupMemberRole! ): User From c7c2ebdeb7544078a3a6bf4441965e775294640d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 12:20:27 +0200 Subject: [PATCH 32/58] Refine clean db mode in 'groups.spec.js' --- backend/src/schema/resolvers/groups.spec.js | 3264 ++++++++++--------- 1 file changed, 1633 insertions(+), 1631 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index a660f6ce8..054e3abb0 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -83,361 +83,228 @@ afterAll(async () => { await cleanDatabase() }) -describe('in mode: always clean db', () => { - beforeEach(async () => { - await seedBasicsAndClearAuthentication() - }) - - // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 - 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('in mode', () => { + describe('clean db after each single test', () => { + beforeEach(async () => { + await seedBasicsAndClearAuthentication() }) - describe('unauthenticated', () => { - it('throws authorization error', async () => { - const { errors } = await mutate({ mutation: createGroupMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) + // TODO: avoid database clean after each test in the future if possible for performance and flakyness reasons by filling the database step by step, see issue https://github.com/Ocelot-Social-Community/Ocelot-Social/issues/4543 + afterEach(async () => { + await cleanDatabase() }) - describe('authenticated', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() + 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, + } }) - it('creates a group', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ - data: { - CreateGroup: { - name: 'The Best Group', - slug: 'the-group', - about: 'We will change the world!', + 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 () => { + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ + data: { + CreateGroup: { + name: 'The Best Group', + slug: 'the-group', + about: 'We will change the world!', + }, }, - }, - errors: undefined, + errors: undefined, + }) }) - }) - it('assigns the authenticated user as owner', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ - data: { - CreateGroup: { - name: 'The Best Group', - myRole: 'owner', + it('assigns the authenticated user as owner', async () => { + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ + data: { + CreateGroup: { + name: 'The Best Group', + myRole: 'owner', + }, }, - }, - errors: undefined, + errors: undefined, + }) }) - }) - it('has "disabled" and "deleted" default to "false"', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ - data: { CreateGroup: { disabled: false, deleted: false } }, + it('has "disabled" and "deleted" default to "false"', async () => { + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ + data: { CreateGroup: { disabled: false, deleted: false } }, + }) }) - }) - describe('description', () => { - describe('length without HTML', () => { - describe('less then 100 chars', () => { + 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, - description: - '0123456789' + - '0123456789', - }, + variables: { ...variables, categoryIds: null }, }) - expect(errors[0]).toHaveProperty('message', 'Description too short!') + expect(errors[0]).toHaveProperty('message', 'Too view categories!') }) }) - }) - }) - 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 () => { - const { errors } = await query({ query: groupQuery, variables: {} }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('authenticated', () => { - let otherUser - - beforeEach(async () => { - otherUser = await Factory.build( - 'user', - { - id: 'other-user', - name: 'Other TestUser', - }, - { - email: 'test2@example.org', - password: '1234', - }, - ) - authenticatedUser = await otherUser.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'others-group', - name: 'Uninteresting Group', - about: 'We will change nothing!', - description: 'We love it like it is!?' + descriptionAdditional100, - groupType: 'closed', - actionRadius: 'global', - categoryIds, - }, - }) - authenticatedUser = await user.toJson() - await mutate({ - mutation: createGroupMutation, - variables: { - id: 'my-group', - name: 'The Best Group', - about: 'We will change the world!', - description: 'Some description' + descriptionAdditional100, - groupType: 'public', - actionRadius: 'regional', - categoryIds, - }, - }) - }) - - describe('query groups', () => { - describe('without any filters', () => { - it('finds all groups', async () => { - await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({ - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }), - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), - }, - errors: undefined, - }) - }) - }) - - describe('isMember = true', () => { - it('finds only groups where user is member', async () => { - await expect( - query({ query: groupQuery, variables: { isMember: true } }), - ).resolves.toMatchObject({ - data: { - Group: [ - { - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }, - ], - }, - errors: undefined, - }) - }) - }) - - describe('isMember = false', () => { - it('finds only groups where user is not(!) member', async () => { - await expect( - query({ query: groupQuery, variables: { isMember: false } }), - ).resolves.toMatchObject({ - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), - }, - errors: undefined, + 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('JoinGroup', () => { - describe('unauthenticated', () => { - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: joinGroupMutation, - variables: { - groupId: 'not-existing-group', - userId: 'current-user', - }, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('authenticated', () => { - let ownerOfClosedGroupUser - let ownerOfHiddenGroupUser - - beforeEach(async () => { - // create users - 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', - }, - ) - // create groups - // public-group - 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('Group', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + const { errors } = await query({ query: groupQuery, variables: {} }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - describe('public group', () => { - describe('joined by "owner-of-closed-group"', () => { - it('has "usual" as membership role', async () => { - await expect( - mutate({ - mutation: joinGroupMutation, - variables: { - groupId: 'public-group', - userId: 'owner-of-closed-group', + describe('authenticated', () => { + let otherUser + + beforeEach(async () => { + otherUser = await Factory.build( + 'user', + { + id: 'other-user', + name: 'Other TestUser', + }, + { + email: 'test2@example.org', + password: '1234', + }, + ) + authenticatedUser = await otherUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'others-group', + name: 'Uninteresting Group', + about: 'We will change nothing!', + description: 'We love it like it is!?' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'global', + categoryIds, + }, + }) + authenticatedUser = await user.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'my-group', + name: 'The Best Group', + about: 'We will change the world!', + description: 'Some description' + descriptionAdditional100, + groupType: 'public', + actionRadius: 'regional', + categoryIds, + }, + }) + }) + + describe('query groups', () => { + describe('without any filters', () => { + it('finds all groups', async () => { + await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({ + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }), + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), }, - }), - ).resolves.toMatchObject({ - data: { - JoinGroup: { - id: 'owner-of-closed-group', - myRoleInGroup: 'usual', - }, - }, - errors: undefined, + errors: undefined, + }) }) }) - }) - describe('joined by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { + describe('isMember = true', () => { + it('finds only groups where user is member', async () => { await expect( - mutate({ - mutation: joinGroupMutation, - variables: { - groupId: 'public-group', - userId: 'current-user', - }, - }), + query({ query: groupQuery, variables: { isMember: true } }), ).resolves.toMatchObject({ data: { - JoinGroup: { - id: 'current-user', - myRoleInGroup: 'owner', - }, + Group: [ + { + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }, + ], + }, + errors: undefined, + }) + }) + }) + + describe('isMember = false', () => { + it('finds only groups where user is not(!) member', async () => { + await expect( + query({ query: groupQuery, variables: { isMember: false } }), + ).resolves.toMatchObject({ + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), }, errors: undefined, }) @@ -445,38 +312,101 @@ describe('in mode: always clean db', () => { }) }) }) + }) - describe('closed group', () => { - describe('joined by "current-user"', () => { - it('has "pending" as membership role', async () => { - await expect( - mutate({ - mutation: joinGroupMutation, - variables: { - groupId: 'closed-group', - userId: 'current-user', - }, - }), - ).resolves.toMatchObject({ - data: { - JoinGroup: { - id: 'current-user', - myRoleInGroup: 'pending', - }, - }, - errors: undefined, - }) + describe('JoinGroup', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: joinGroupMutation, + variables: { + groupId: 'not-existing-group', + userId: 'current-user', + }, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('authenticated', () => { + let ownerOfClosedGroupUser + let ownerOfHiddenGroupUser + + beforeEach(async () => { + // create users + 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', + }, + ) + // create groups + // public-group + 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('joined by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { + describe('public group', () => { + describe('joined by "owner-of-closed-group"', () => { + it('has "usual" as membership role', async () => { await expect( mutate({ mutation: joinGroupMutation, variables: { - groupId: 'closed-group', + groupId: 'public-group', userId: 'owner-of-closed-group', }, }), @@ -484,133 +414,732 @@ describe('in mode: always clean db', () => { data: { JoinGroup: { id: 'owner-of-closed-group', - myRoleInGroup: 'owner', + myRoleInGroup: 'usual', }, }, errors: undefined, }) }) }) - }) - }) - describe('hidden group', () => { - describe('joined by "owner-of-closed-group"', () => { - it('throws authorization error', async () => { - const { errors } = await query({ - query: joinGroupMutation, - variables: { - groupId: 'hidden-group', - userId: 'owner-of-closed-group', - }, + describe('joined by its owner', () => { + describe('does not create additional "MEMBER_OF" relation and therefore', () => { + it('has still "owner" as membership role', async () => { + await expect( + mutate({ + mutation: joinGroupMutation, + variables: { + groupId: 'public-group', + userId: 'current-user', + }, + }), + ).resolves.toMatchObject({ + data: { + JoinGroup: { + id: 'current-user', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + }) + }) }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - describe('joined by its owner', () => { - describe('does not create additional "MEMBER_OF" relation and therefore', () => { - it('has still "owner" as membership role', async () => { + describe('closed group', () => { + describe('joined by "current-user"', () => { + it('has "pending" as membership role', async () => { await expect( mutate({ mutation: joinGroupMutation, variables: { - groupId: 'hidden-group', - userId: 'owner-of-hidden-group', + groupId: 'closed-group', + userId: 'current-user', }, }), ).resolves.toMatchObject({ data: { JoinGroup: { - id: 'owner-of-hidden-group', - myRoleInGroup: 'owner', + id: 'current-user', + myRoleInGroup: 'pending', }, }, errors: undefined, }) }) }) + + describe('joined by its owner', () => { + describe('does not create additional "MEMBER_OF" relation and therefore', () => { + it('has still "owner" as membership role', async () => { + await expect( + mutate({ + mutation: joinGroupMutation, + variables: { + groupId: 'closed-group', + userId: 'owner-of-closed-group', + }, + }), + ).resolves.toMatchObject({ + data: { + JoinGroup: { + id: 'owner-of-closed-group', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + }) + }) + + describe('hidden group', () => { + describe('joined by "owner-of-closed-group"', () => { + it('throws authorization error', async () => { + const { errors } = await query({ + query: joinGroupMutation, + variables: { + groupId: 'hidden-group', + userId: 'owner-of-closed-group', + }, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('joined by its owner', () => { + describe('does not create additional "MEMBER_OF" relation and therefore', () => { + it('has still "owner" as membership role', async () => { + await expect( + mutate({ + mutation: joinGroupMutation, + variables: { + groupId: 'hidden-group', + userId: 'owner-of-hidden-group', + }, + }), + ).resolves.toMatchObject({ + data: { + JoinGroup: { + id: 'owner-of-hidden-group', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + }) }) }) }) }) -}) -describe('in mode: building up – separate for each resolver', () => { - describe('GroupMembers', () => { - beforeAll(async () => { - await seedBasicsAndClearAuthentication() - }) + describe('building up – clean db after each resolver', () => { + describe('GroupMembers', () => { + beforeAll(async () => { + await seedBasicsAndClearAuthentication() + }) - afterAll(async () => { - await cleanDatabase() - }) + afterAll(async () => { + await cleanDatabase() + }) - describe('unauthenticated', () => { - it('throws authorization error', async () => { - variables = { - id: 'not-existing-group', - } - const { errors } = await query({ query: groupMembersQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + describe('unauthenticated', () => { + it('throws authorization error', async () => { + variables = { + id: 'not-existing-group', + } + const { errors } = await query({ query: groupMembersQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('authenticated', () => { + let otherUser + let pendingUser + let ownerOfClosedGroupUser + let ownerOfHiddenGroupUser + + beforeAll(async () => { + // create users + otherUser = await Factory.build( + 'user', + { + id: 'other-user', + name: 'Other TestUser', + }, + { + email: 'other-user@example.org', + password: '1234', + }, + ) + pendingUser = await Factory.build( + 'user', + { + id: 'pending-user', + name: 'Pending TestUser', + }, + { + email: 'pending@example.org', + password: '1234', + }, + ) + 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', + }, + ) + // create groups + // public-group + 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, + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + groupId: 'public-group', + userId: 'owner-of-closed-group', + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + groupId: 'public-group', + userId: 'owner-of-hidden-group', + }, + }) + // closed-group + 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, + }, + }) + await mutate({ + mutation: joinGroupMutation, + variables: { + groupId: 'closed-group', + userId: 'current-user', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'closed-group', + userId: 'owner-of-hidden-group', + roleInGroup: 'usual', + }, + }) + // hidden-group + 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, + }, + }) + // 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'hidden-group', + userId: 'pending-user', + roleInGroup: 'pending', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'hidden-group', + userId: 'current-user', + roleInGroup: 'usual', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'hidden-group', + userId: 'owner-of-closed-group', + roleInGroup: 'admin', + }, + }) + // Wolle: + // const groups = await query({ query: groupQuery, variables: {} }) + // console.log('groups.data.Group: ', groups.data.Group) + // const groupMemberOfClosedGroup = await mutate({ + // mutation: groupMembersQuery, + // variables: { + // id: 'hidden-group', + // }, + // }) + // console.log('groupMemberOfClosedGroup.data.GroupMembers: ', groupMemberOfClosedGroup.data.GroupMembers) + + authenticatedUser = null + }) + + describe('public group', () => { + beforeEach(async () => { + variables = { + id: 'public-group', + } + }) + + describe('query group members', () => { + describe('by owner "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('finds all members', async () => { + const result = await mutate({ + mutation: groupMembersQuery, + variables, + }) + expect(result).toMatchObject({ + data: { + GroupMembers: 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, + }) + expect(result.data.GroupMembers.length).toBe(3) + }) + }) + + describe('by usual member "owner-of-closed-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfClosedGroupUser.toJson() + }) + + it('finds all members', async () => { + const result = await mutate({ + mutation: groupMembersQuery, + variables, + }) + expect(result).toMatchObject({ + data: { + GroupMembers: 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, + }) + expect(result.data.GroupMembers.length).toBe(3) + }) + }) + + describe('by none member "other-user"', () => { + beforeEach(async () => { + authenticatedUser = await otherUser.toJson() + }) + + it('finds all members', async () => { + const result = await mutate({ + mutation: groupMembersQuery, + variables, + }) + expect(result).toMatchObject({ + data: { + GroupMembers: 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, + }) + expect(result.data.GroupMembers.length).toBe(3) + }) + }) + }) + }) + + describe('closed group', () => { + beforeEach(async () => { + variables = { + id: '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 result = await mutate({ + mutation: groupMembersQuery, + variables, + }) + expect(result).toMatchObject({ + data: { + GroupMembers: 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, + }) + expect(result.data.GroupMembers.length).toBe(3) + }) + }) + + describe('by usual member "owner-of-hidden-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfHiddenGroupUser.toJson() + }) + + it('finds all members', async () => { + const result = await mutate({ + mutation: groupMembersQuery, + variables, + }) + expect(result).toMatchObject({ + data: { + GroupMembers: 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, + }) + expect(result.data.GroupMembers.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: groupMembersQuery, 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: groupMembersQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + }) + + describe('hidden group', () => { + beforeEach(async () => { + variables = { + id: '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 result = await mutate({ + mutation: groupMembersQuery, + variables, + }) + expect(result).toMatchObject({ + data: { + GroupMembers: expect.arrayContaining([ + expect.objectContaining({ + id: 'pending-user', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'usual', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'admin', + }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'owner', + }), + ]), + }, + errors: undefined, + }) + expect(result.data.GroupMembers.length).toBe(4) + }) + }) + + describe('by usual member "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('finds all members', async () => { + const result = await mutate({ + mutation: groupMembersQuery, + variables, + }) + expect(result).toMatchObject({ + data: { + GroupMembers: expect.arrayContaining([ + expect.objectContaining({ + id: 'pending-user', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'usual', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'admin', + }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'owner', + }), + ]), + }, + errors: undefined, + }) + expect(result.data.GroupMembers.length).toBe(4) + }) + }) + + describe('by admin member "owner-of-closed-group"', () => { + beforeEach(async () => { + authenticatedUser = await ownerOfClosedGroupUser.toJson() + }) + + it('finds all members', async () => { + const result = await mutate({ + mutation: groupMembersQuery, + variables, + }) + expect(result).toMatchObject({ + data: { + GroupMembers: expect.arrayContaining([ + expect.objectContaining({ + id: 'pending-user', + myRoleInGroup: 'pending', + }), + expect.objectContaining({ + id: 'current-user', + myRoleInGroup: 'usual', + }), + expect.objectContaining({ + id: 'owner-of-closed-group', + myRoleInGroup: 'admin', + }), + expect.objectContaining({ + id: 'owner-of-hidden-group', + myRoleInGroup: 'owner', + }), + ]), + }, + errors: undefined, + }) + expect(result.data.GroupMembers.length).toBe(4) + }) + }) + + describe('by pending member "pending-user"', () => { + beforeEach(async () => { + authenticatedUser = await pendingUser.toJson() + }) + + it('throws authorization error', async () => { + const { errors } = await query({ query: groupMembersQuery, 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: groupMembersQuery, variables }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + }) }) }) - describe('authenticated', () => { - let otherUser - let pendingUser - let ownerOfClosedGroupUser - let ownerOfHiddenGroupUser + describe('ChangeGroupMemberRole', () => { + let pendingMemberUser + let usualMemberUser + let adminMemberUser + let ownerMemberUser + let secondOwnerMemberUser beforeAll(async () => { + await seedBasicsAndClearAuthentication() // create users - otherUser = await Factory.build( + pendingMemberUser = await Factory.build( 'user', { - id: 'other-user', - name: 'Other TestUser', + id: 'pending-member-user', + name: 'Pending Member TestUser', }, { - email: 'other-user@example.org', + email: 'pending-member-user@example.org', password: '1234', }, ) - pendingUser = await Factory.build( + usualMemberUser = await Factory.build( 'user', { - id: 'pending-user', - name: 'Pending TestUser', + id: 'usual-member-user', + name: 'Usual Member TestUser', }, { - email: 'pending@example.org', + email: 'usual-member-user@example.org', password: '1234', }, ) - ownerOfClosedGroupUser = await Factory.build( + adminMemberUser = await Factory.build( 'user', { - id: 'owner-of-closed-group', - name: 'Owner Of Closed Group', + id: 'admin-member-user', + name: 'Admin Member TestUser', }, { - email: 'owner-of-closed-group@example.org', + email: 'admin-member-user@example.org', password: '1234', }, ) - ownerOfHiddenGroupUser = await Factory.build( + ownerMemberUser = await Factory.build( 'user', { - id: 'owner-of-hidden-group', - name: 'Owner Of Hidden Group', + id: 'owner-member-user', + name: 'Owner Member TestUser', }, { - email: 'owner-of-hidden-group@example.org', + email: 'owner-member-user@example.org', + password: '1234', + }, + ) + secondOwnerMemberUser = await Factory.build( + 'user', + { + id: 'second-owner-member-user', + name: 'Second Owner Member TestUser', + }, + { + email: 'second-owner-member-user@example.org', password: '1234', }, ) // create groups // public-group - authenticatedUser = await user.toJson() + authenticatedUser = await usualMemberUser.toJson() await mutate({ mutation: createGroupMutation, variables: { @@ -638,7 +1167,7 @@ describe('in mode: building up – separate for each resolver', () => { }, }) // closed-group - authenticatedUser = await ownerOfClosedGroupUser.toJson() + authenticatedUser = await ownerMemberUser.toJson() await mutate({ mutation: createGroupMutation, variables: { @@ -651,23 +1180,8 @@ describe('in mode: building up – separate for each resolver', () => { categoryIds, }, }) - await mutate({ - mutation: joinGroupMutation, - variables: { - groupId: 'closed-group', - userId: 'current-user', - }, - }) - await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - groupId: 'closed-group', - userId: 'owner-of-hidden-group', - roleInGroup: 'usual', - }, - }) // hidden-group - authenticatedUser = await ownerOfHiddenGroupUser.toJson() + authenticatedUser = await adminMemberUser.toJson() await mutate({ mutation: createGroupMutation, variables: { @@ -685,15 +1199,7 @@ describe('in mode: building up – separate for each resolver', () => { mutation: changeGroupMemberRoleMutation, variables: { groupId: 'hidden-group', - userId: 'pending-user', - roleInGroup: 'pending', - }, - }) - await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - groupId: 'hidden-group', - userId: 'current-user', + userId: 'admin-member-user', roleInGroup: 'usual', }, }) @@ -701,606 +1207,337 @@ describe('in mode: building up – separate for each resolver', () => { mutation: changeGroupMemberRoleMutation, variables: { groupId: 'hidden-group', - userId: 'owner-of-closed-group', - roleInGroup: 'admin', + userId: 'second-owner-member-user', + roleInGroup: 'usual', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'hidden-group', + userId: 'admin-member-user', + roleInGroup: 'usual', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'hidden-group', + userId: 'second-owner-member-user', + roleInGroup: 'usual', }, }) - // Wolle: - // const groups = await query({ query: groupQuery, variables: {} }) - // console.log('groups.data.Group: ', groups.data.Group) - // const groupMemberOfClosedGroup = await mutate({ - // mutation: groupMembersQuery, - // variables: { - // id: 'hidden-group', - // }, - // }) - // console.log('groupMemberOfClosedGroup.data.GroupMembers: ', groupMemberOfClosedGroup.data.GroupMembers) authenticatedUser = null }) - describe('public group', () => { - beforeEach(async () => { - variables = { - id: 'public-group', - } - }) + afterAll(async () => { + await cleanDatabase() + }) - describe('query group members', () => { - describe('by owner "current-user"', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - it('finds all members', async () => { - const result = await mutate({ - mutation: groupMembersQuery, - variables, - }) - expect(result).toMatchObject({ - data: { - GroupMembers: 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, - }) - expect(result.data.GroupMembers.length).toBe(3) - }) - }) - - describe('by usual member "owner-of-closed-group"', () => { - beforeEach(async () => { - authenticatedUser = await ownerOfClosedGroupUser.toJson() - }) - - it('finds all members', async () => { - const result = await mutate({ - mutation: groupMembersQuery, - variables, - }) - expect(result).toMatchObject({ - data: { - GroupMembers: 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, - }) - expect(result.data.GroupMembers.length).toBe(3) - }) - }) - - describe('by none member "other-user"', () => { - beforeEach(async () => { - authenticatedUser = await otherUser.toJson() - }) - - it('finds all members', async () => { - const result = await mutate({ - mutation: groupMembersQuery, - variables, - }) - expect(result).toMatchObject({ - data: { - GroupMembers: 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, - }) - expect(result.data.GroupMembers.length).toBe(3) - }) + describe('unauthenticated', () => { + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'not-existing-group', + userId: 'current-user', + roleInGroup: 'pending', + }, }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - describe('closed group', () => { - beforeEach(async () => { - variables = { - id: '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 result = await mutate({ - mutation: groupMembersQuery, - variables, - }) - expect(result).toMatchObject({ - data: { - GroupMembers: 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, - }) - expect(result.data.GroupMembers.length).toBe(3) - }) + describe('authenticated', () => { + describe('in all group types – here "closed-group" for example', () => { + beforeEach(async () => { + variables = { + groupId: 'closed-group', + } }) - describe('by usual member "owner-of-hidden-group"', () => { - beforeEach(async () => { - authenticatedUser = await ownerOfHiddenGroupUser.toJson() - }) - - it('finds all members', async () => { - const result = await mutate({ - mutation: groupMembersQuery, - variables, - }) - expect(result).toMatchObject({ - data: { - GroupMembers: 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, - }) - expect(result.data.GroupMembers.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: groupMembersQuery, 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: groupMembersQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - }) - - describe('hidden group', () => { - beforeEach(async () => { - variables = { - id: '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 result = await mutate({ - mutation: groupMembersQuery, - variables, - }) - expect(result).toMatchObject({ - data: { - GroupMembers: expect.arrayContaining([ - expect.objectContaining({ - id: 'pending-user', - myRoleInGroup: 'pending', - }), - expect.objectContaining({ - id: 'current-user', - myRoleInGroup: 'usual', - }), - expect.objectContaining({ - id: 'owner-of-closed-group', - myRoleInGroup: 'admin', - }), - expect.objectContaining({ - id: 'owner-of-hidden-group', - myRoleInGroup: 'owner', - }), - ]), - }, - errors: undefined, - }) - expect(result.data.GroupMembers.length).toBe(4) - }) - }) - - describe('by usual member "current-user"', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - it('finds all members', async () => { - const result = await mutate({ - mutation: groupMembersQuery, - variables, - }) - expect(result).toMatchObject({ - data: { - GroupMembers: expect.arrayContaining([ - expect.objectContaining({ - id: 'pending-user', - myRoleInGroup: 'pending', - }), - expect.objectContaining({ - id: 'current-user', - myRoleInGroup: 'usual', - }), - expect.objectContaining({ - id: 'owner-of-closed-group', - myRoleInGroup: 'admin', - }), - expect.objectContaining({ - id: 'owner-of-hidden-group', - myRoleInGroup: 'owner', - }), - ]), - }, - errors: undefined, - }) - expect(result.data.GroupMembers.length).toBe(4) - }) - }) - - describe('by admin member "owner-of-closed-group"', () => { - beforeEach(async () => { - authenticatedUser = await ownerOfClosedGroupUser.toJson() - }) - - it('finds all members', async () => { - const result = await mutate({ - mutation: groupMembersQuery, - variables, - }) - expect(result).toMatchObject({ - data: { - GroupMembers: expect.arrayContaining([ - expect.objectContaining({ - id: 'pending-user', - myRoleInGroup: 'pending', - }), - expect.objectContaining({ - id: 'current-user', - myRoleInGroup: 'usual', - }), - expect.objectContaining({ - id: 'owner-of-closed-group', - myRoleInGroup: 'admin', - }), - expect.objectContaining({ - id: 'owner-of-hidden-group', - myRoleInGroup: 'owner', - }), - ]), - }, - errors: undefined, - }) - expect(result.data.GroupMembers.length).toBe(4) - }) - }) - - describe('by pending member "pending-user"', () => { - beforeEach(async () => { - authenticatedUser = await pendingUser.toJson() - }) - - it('throws authorization error', async () => { - const { errors } = await query({ query: groupMembersQuery, 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: groupMembersQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - }) - }) - }) - - describe('ChangeGroupMemberRole', () => { - let pendingMemberUser - let usualMemberUser - let adminMemberUser - let ownerMemberUser - let secondOwnerMemberUser - - beforeAll(async () => { - await seedBasicsAndClearAuthentication() - // create users - pendingMemberUser = await Factory.build( - 'user', - { - id: 'pending-member-user', - name: 'Pending Member TestUser', - }, - { - email: 'pending-member-user@example.org', - password: '1234', - }, - ) - usualMemberUser = await Factory.build( - 'user', - { - id: 'usual-member-user', - name: 'Usual Member TestUser', - }, - { - email: 'usual-member-user@example.org', - password: '1234', - }, - ) - adminMemberUser = await Factory.build( - 'user', - { - id: 'admin-member-user', - name: 'Admin Member TestUser', - }, - { - email: 'admin-member-user@example.org', - password: '1234', - }, - ) - ownerMemberUser = await Factory.build( - 'user', - { - id: 'owner-member-user', - name: 'Owner Member TestUser', - }, - { - email: 'owner-member-user@example.org', - password: '1234', - }, - ) - secondOwnerMemberUser = await Factory.build( - 'user', - { - id: 'second-owner-member-user', - name: 'Second Owner Member TestUser', - }, - { - email: 'second-owner-member-user@example.org', - password: '1234', - }, - ) - // create groups - // public-group - authenticatedUser = await usualMemberUser.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, - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - groupId: 'public-group', - userId: 'owner-of-closed-group', - }, - }) - await mutate({ - mutation: joinGroupMutation, - variables: { - groupId: 'public-group', - userId: 'owner-of-hidden-group', - }, - }) - // closed-group - authenticatedUser = await ownerMemberUser.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, - }, - }) - // hidden-group - authenticatedUser = await adminMemberUser.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, - }, - }) - // 'JoinGroup' mutation does not work in hidden groups so we join them by 'ChangeGroupMemberRole' through the owner - await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - groupId: 'hidden-group', - userId: 'admin-member-user', - roleInGroup: 'usual', - }, - }) - await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - groupId: 'hidden-group', - userId: 'second-owner-member-user', - roleInGroup: 'usual', - }, - }) - await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - groupId: 'hidden-group', - userId: 'admin-member-user', - roleInGroup: 'usual', - }, - }) - await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - groupId: 'hidden-group', - userId: 'second-owner-member-user', - roleInGroup: 'usual', - }, - }) - - authenticatedUser = null - }) - - afterAll(async () => { - await cleanDatabase() - }) - - describe('unauthenticated', () => { - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables: { - groupId: 'not-existing-group', - userId: 'current-user', - roleInGroup: 'pending', - }, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('authenticated', () => { - describe('in all group types – here "closed-group" for example', () => { - beforeEach(async () => { - variables = { - groupId: 'closed-group', - } - }) - - describe('join the members and give them their prospective roles', () => { - describe('by owner "owner-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await ownerMemberUser.toJson() - }) - - describe('for "usual-member-user"', () => { + describe('join the members and give them their prospective roles', () => { + describe('by owner "owner-member-user"', () => { beforeEach(async () => { - variables = { - ...variables, - userId: 'usual-member-user', - } + authenticatedUser = await ownerMemberUser.toJson() }) - describe('as usual', () => { + describe('for "usual-member-user"', () => { beforeEach(async () => { variables = { ...variables, - roleInGroup: 'usual', + userId: 'usual-member-user', } }) - it('has role usual', async () => { - await expect( - mutate({ + describe('as usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('has role usual', async () => { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'usual-member-user', + myRoleInGroup: 'usual', + }, + }, + errors: undefined, + }) + }) + + // the GQL mutation needs this fields in the result for testing + it.todo('has "updatedAt" newer as "createdAt"') + }) + }) + + describe('for "admin-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'admin-member-user', + } + }) + + describe('as admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('has role admin', async () => { + // Wolle: + // const groups = await query({ query: groupQuery, variables: {} }) + // console.log('groups.data.Group: ', groups.data.Group) + // const groupMemberOfClosedGroup = await mutate({ + // mutation: groupMembersQuery, + // variables: { + // id: 'closed-group', + // }, + // }) + // console.log('groupMemberOfClosedGroup.data.GroupMembers: ', groupMemberOfClosedGroup.data.GroupMembers) + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'admin-member-user', + myRoleInGroup: 'admin', + }, + }, + errors: undefined, + }) + }) + }) + }) + + describe('for "second-owner-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'second-owner-member-user', + } + }) + + describe('as owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('has role owner', async () => { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'second-owner-member-user', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + }) + }) + }) + + describe('switch role', () => { + describe('of owner "owner-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'owner-member-user', + } + }) + + describe('by owner themself "owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await ownerMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'usual-member-user', - myRoleInGroup: 'usual', - }, - }, - errors: undefined, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + // shall this be possible in the future? + // or shall only an owner who gave the second owner the owner role downgrade themself for savety? + // otherwise the first owner who downgrades the other one has the victory over the group! + describe('by second owner "second-owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await secondOwnerMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - // the GQL mutation needs this fields in the result for testing - it.todo('has "updatedAt" newer as "createdAt"') + describe('to same role owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('has role owner still', async () => { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'owner-member-user', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + }) + + describe('by admin "admin-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await adminMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by usual member "usual-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await usualMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by still pending member "pending-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await pendingMemberUser.toJson() + }) + + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) }) }) - describe('for "admin-member-user"', () => { + describe('of admin "admin-member-user"', () => { beforeEach(async () => { variables = { ...variables, @@ -1308,730 +1545,495 @@ describe('in mode: building up – separate for each resolver', () => { } }) - describe('as admin', () => { + describe('by owner "owner-member-user"', () => { beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } + authenticatedUser = await ownerMemberUser.toJson() }) - it('has role admin', async () => { - // Wolle: - // const groups = await query({ query: groupQuery, variables: {} }) - // console.log('groups.data.Group: ', groups.data.Group) - // const groupMemberOfClosedGroup = await mutate({ - // mutation: groupMembersQuery, - // variables: { - // id: 'closed-group', - // }, - // }) - // console.log('groupMemberOfClosedGroup.data.GroupMembers: ', groupMemberOfClosedGroup.data.GroupMembers) - await expect( - mutate({ + describe('to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('has role owner', async () => { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'admin-member-user', + myRoleInGroup: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + + describe('back to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'admin-member-user', - myRoleInGroup: 'admin', - }, - }, - errors: undefined, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by usual member "usual-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await usualMemberUser.toJson() + }) + + describe('upgrade to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('degrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by still pending member "pending-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await pendingMemberUser.toJson() + }) + + describe('upgrade to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('degrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + + describe('by none member "current-user"', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + describe('upgrade to owner', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'owner', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + + describe('degrade to pending again', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) }) }) - describe('for "second-owner-member-user"', () => { + describe('of usual member "usual-member-user"', () => { beforeEach(async () => { variables = { ...variables, - userId: 'second-owner-member-user', + userId: 'usual-member-user', } }) - describe('as owner', () => { + describe('by owner "owner-member-user"', () => { beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'owner', - } + authenticatedUser = await ownerMemberUser.toJson() }) - it('has role owner', async () => { - await expect( - mutate({ + describe('to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('has role admin', async () => { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'usual-member-user', + myRoleInGroup: 'admin', + }, + }, + errors: undefined, + }) + }) + }) + + describe('back to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('has role usual again', async () => { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'usual-member-user', + myRoleInGroup: 'usual', + }, + }, + errors: undefined, + }) + }) + }) + }) + + describe('by usual member "usual-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await usualMemberUser.toJson() + }) + + describe('upgrade to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'second-owner-member-user', - myRoleInGroup: 'owner', - }, - }, - errors: undefined, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - }) - }) - }) - }) - describe('switch role', () => { - describe('of owner "owner-member-user"', () => { - beforeEach(async () => { - variables = { - ...variables, - userId: 'owner-member-user', - } - }) - - describe('by owner themself "owner-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await ownerMemberUser.toJson() - }) - - describe('to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, + describe('degrade to pending', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - // shall this be possible in the future? - // or shall only an owner who gave the second owner the owner role downgrade themself for savety? - // otherwise the first owner who downgrades the other one has the victory over the group! - describe('by second owner "second-owner-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await secondOwnerMemberUser.toJson() - }) - - describe('to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('to same role owner', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'owner', - } - }) - - it('has role owner still', async () => { - await expect( - mutate({ + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'owner-member-user', - myRoleInGroup: 'owner', - }, - }, - errors: undefined, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) }) - }) - describe('by admin "admin-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await adminMemberUser.toJson() - }) - - describe('to admin', () => { + describe('by still pending member "pending-member-user"', () => { beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } + authenticatedUser = await pendingMemberUser.toJson() }) - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, + describe('upgrade to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - describe('by usual member "usual-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await usualMemberUser.toJson() - }) - - describe('to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - - describe('by still pending member "pending-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await pendingMemberUser.toJson() - }) - - describe('to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - }) - - describe('of admin "admin-member-user"', () => { - beforeEach(async () => { - variables = { - ...variables, - userId: 'admin-member-user', - } - }) - - describe('by owner "owner-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await ownerMemberUser.toJson() - }) - - describe('to owner', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'owner', - } - }) - - it('has role owner', async () => { - await expect( - mutate({ + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'admin-member-user', - myRoleInGroup: 'owner', - }, - }, - errors: undefined, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - }) - describe('back to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, + describe('degrade to pending', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - describe('by usual member "usual-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await usualMemberUser.toJson() - }) - - describe('upgrade to owner', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'owner', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('degrade to usual', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'usual', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - - describe('by still pending member "pending-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await pendingMemberUser.toJson() - }) - - describe('upgrade to owner', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'owner', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('degrade to usual', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'usual', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - - describe('by none member "current-user"', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - describe('upgrade to owner', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'owner', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('degrade to pending again', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'pending', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - }) - - describe('of usual member "usual-member-user"', () => { - beforeEach(async () => { - variables = { - ...variables, - userId: 'usual-member-user', - } - }) - - describe('by owner "owner-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await ownerMemberUser.toJson() - }) - - describe('to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('has role admin', async () => { - await expect( - mutate({ + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'usual-member-user', - myRoleInGroup: 'admin', - }, - }, - errors: undefined, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) }) - describe('back to usual', () => { + describe('by none member "current-user"', () => { beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'usual', - } + authenticatedUser = await user.toJson() }) - it('has role usual again', async () => { - await expect( - mutate({ + describe('upgrade to admin', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'admin', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'usual-member-user', - myRoleInGroup: 'usual', - }, - }, - errors: undefined, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - }) - }) - describe('by usual member "usual-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await usualMemberUser.toJson() - }) - - describe('upgrade to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, + describe('degrade to pending again', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - describe('degrade to pending', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'pending', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - - describe('by still pending member "pending-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await pendingMemberUser.toJson() - }) - - describe('upgrade to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('degrade to pending', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'pending', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - - describe('by none member "current-user"', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - describe('upgrade to admin', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'admin', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - - describe('degrade to pending again', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'pending', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, - }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - }) - - describe('of still pending member "pending-member-user"', () => { - beforeEach(async () => { - variables = { - ...variables, - userId: 'pending-member-user', - } - }) - - describe('by owner "owner-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await ownerMemberUser.toJson() - }) - - describe('to usual', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'usual', - } - }) - - it('has role usual', async () => { - await expect( - mutate({ + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'pending-member-user', - myRoleInGroup: 'usual', + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + }) + }) + }) + }) + + describe('of still pending member "pending-member-user"', () => { + beforeEach(async () => { + variables = { + ...variables, + userId: 'pending-member-user', + } + }) + + describe('by owner "owner-member-user"', () => { + beforeEach(async () => { + authenticatedUser = await ownerMemberUser.toJson() + }) + + describe('to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('has role usual', async () => { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'pending-member-user', + myRoleInGroup: 'usual', + }, }, - }, - errors: undefined, + errors: undefined, + }) + }) + }) + + describe('back to pending', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'pending', + } + }) + + it('has role usual again', async () => { + await expect( + mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + ChangeGroupMemberRole: { + id: 'pending-member-user', + myRoleInGroup: 'pending', + }, + }, + errors: undefined, + }) }) }) }) - describe('back to pending', () => { + describe('by usual member "usual-member-user"', () => { beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'pending', - } + authenticatedUser = await usualMemberUser.toJson() }) - it('has role usual again', async () => { - await expect( - mutate({ + describe('upgrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ mutation: changeGroupMemberRoleMutation, variables, - }), - ).resolves.toMatchObject({ - data: { - ChangeGroupMemberRole: { - id: 'pending-member-user', - myRoleInGroup: 'pending', - }, - }, - errors: undefined, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) }) - }) - describe('by usual member "usual-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await usualMemberUser.toJson() - }) - - describe('upgrade to usual', () => { + describe('by still pending member "pending-member-user"', () => { beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'usual', - } + authenticatedUser = await pendingMemberUser.toJson() }) - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, + describe('upgrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } + }) + + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) - }) - describe('by still pending member "pending-member-user"', () => { - beforeEach(async () => { - authenticatedUser = await pendingMemberUser.toJson() - }) - - describe('upgrade to usual', () => { + describe('by none member "current-user"', () => { beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'usual', - } + authenticatedUser = await user.toJson() }) - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, + describe('upgrade to usual', () => { + beforeEach(async () => { + variables = { + ...variables, + roleInGroup: 'usual', + } }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') - }) - }) - }) - describe('by none member "current-user"', () => { - beforeEach(async () => { - authenticatedUser = await user.toJson() - }) - - describe('upgrade to usual', () => { - beforeEach(async () => { - variables = { - ...variables, - roleInGroup: 'usual', - } - }) - - it('throws authorization error', async () => { - const { errors } = await mutate({ - mutation: changeGroupMemberRoleMutation, - variables, + it('throws authorization error', async () => { + const { errors } = await mutate({ + mutation: changeGroupMemberRoleMutation, + variables, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) }) From cd090420cf318f5b9fbd7618ac3a0f8d65d67700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 12:30:01 +0200 Subject: [PATCH 33/58] Move 'JoinGroup' to building up mode in 'groups.spec.js' --- backend/src/schema/resolvers/groups.spec.js | 56 +++++++++++++-------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 054e3abb0..e966506a7 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -122,34 +122,40 @@ describe('in mode', () => { }) it('creates a group', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ - data: { - CreateGroup: { - name: 'The Best Group', - slug: 'the-group', - about: 'We will change the world!', + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( + { + data: { + CreateGroup: { + name: 'The Best Group', + slug: 'the-group', + about: 'We will change the world!', + }, }, + errors: undefined, }, - errors: undefined, - }) + ) }) it('assigns the authenticated user as owner', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ - data: { - CreateGroup: { - name: 'The Best Group', - myRole: 'owner', + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( + { + data: { + CreateGroup: { + name: 'The Best Group', + myRole: 'owner', + }, }, + errors: undefined, }, - errors: undefined, - }) + ) }) it('has "disabled" and "deleted" default to "false"', async () => { - await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject({ - data: { CreateGroup: { disabled: false, deleted: false } }, - }) + await expect(mutate({ mutation: createGroupMutation, variables })).resolves.toMatchObject( + { + data: { CreateGroup: { disabled: false, deleted: false } }, + }, + ) }) describe('description', () => { @@ -313,8 +319,18 @@ describe('in mode', () => { }) }) }) + }) + describe('building up – clean db after each resolver', () => { describe('JoinGroup', () => { + beforeAll(async () => { + await seedBasicsAndClearAuthentication() + }) + + afterAll(async () => { + await cleanDatabase() + }) + describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await mutate({ @@ -332,7 +348,7 @@ describe('in mode', () => { let ownerOfClosedGroupUser let ownerOfHiddenGroupUser - beforeEach(async () => { + beforeAll(async () => { // create users ownerOfClosedGroupUser = await Factory.build( 'user', @@ -535,9 +551,7 @@ describe('in mode', () => { }) }) }) - }) - describe('building up – clean db after each resolver', () => { describe('GroupMembers', () => { beforeAll(async () => { await seedBasicsAndClearAuthentication() From 47027ad86bca4e82b4d314f880d5f78260669a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 12:47:57 +0200 Subject: [PATCH 34/58] Move 'Group' resolver to building up mode in 'groups.spec.js' --- backend/src/schema/resolvers/groups.spec.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index e966506a7..7a5f9504a 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -204,8 +204,18 @@ describe('in mode', () => { }) }) }) + }) + describe('building up – clean db after each resolver', () => { describe('Group', () => { + beforeAll(async () => { + await seedBasicsAndClearAuthentication() + }) + + afterAll(async () => { + await cleanDatabase() + }) + describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await query({ query: groupQuery, variables: {} }) @@ -216,7 +226,7 @@ describe('in mode', () => { describe('authenticated', () => { let otherUser - beforeEach(async () => { + beforeAll(async () => { otherUser = await Factory.build( 'user', { @@ -319,9 +329,7 @@ describe('in mode', () => { }) }) }) - }) - describe('building up – clean db after each resolver', () => { describe('JoinGroup', () => { beforeAll(async () => { await seedBasicsAndClearAuthentication() From 72eedef8d0d5b5f2e53858e6fee9c15f2e17b15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 23 Aug 2022 13:12:38 +0200 Subject: [PATCH 35/58] Cleanup --- .../src/middleware/permissionsMiddleware.js | 62 ------------------- backend/src/schema/resolvers/groups.js | 8 --- backend/src/schema/resolvers/groups.spec.js | 20 ------ 3 files changed, 90 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 926f824e4..bfd10537a 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -57,8 +57,6 @@ const isAllowedSeeingMembersOfGroup = rule({ })(async (_parent, args, { user, driver }) => { if (!(user && user.id)) return false const { id: groupId } = args - // Wolle: console.log('groupId: ', groupId) - // console.log('user.id: ', user.id) const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { const transactionResponse = await transaction.run( @@ -76,8 +74,6 @@ const isAllowedSeeingMembersOfGroup = rule({ }) try { const { member, group } = await readTxPromise - // Wolle: console.log('member: ', member) - // console.log('group: ', group) return ( !!group && (group.groupType === 'public' || @@ -86,7 +82,6 @@ const isAllowedSeeingMembersOfGroup = rule({ ['usual', 'admin', 'owner'].includes(member.myRoleInGroup))) ) } catch (error) { - // Wolle: console.log('error: ', error) throw new Error(error) } finally { session.close() @@ -100,12 +95,6 @@ const isAllowedToChangeGroupMemberRole = rule({ const adminId = user.id const { groupId, userId, roleInGroup } = args if (adminId === userId) return false - // Wolle: - // console.log('isAllowedToChangeGroupMemberRole !!!') - // console.log('adminId: ', adminId) - // console.log('groupId: ', groupId) - // console.log('userId: ', userId) - // console.log('roleInGroup: ', roleInGroup) const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { const transactionResponse = await transaction.run( @@ -116,23 +105,6 @@ const isAllowedToChangeGroupMemberRole = rule({ `, { groupId, adminId, userId }, ) - // Wolle: - // console.log( - // 'transactionResponse: ', - // transactionResponse, - // ) - // console.log( - // 'transaction admins: ', - // transactionResponse.records.map((record) => record.get('admin')), - // ) - // console.log( - // 'transaction groups: ', - // transactionResponse.records.map((record) => record.get('group')), - // ) - // console.log( - // 'transaction members: ', - // transactionResponse.records.map((record) => record.get('member')), - // ) return { admin: transactionResponse.records.map((record) => record.get('admin'))[0], group: transactionResponse.records.map((record) => record.get('group'))[0], @@ -140,14 +112,7 @@ const isAllowedToChangeGroupMemberRole = rule({ } }) try { - // Wolle: - // console.log('enter try !!!') const { admin, group, member } = await readTxPromise - // Wolle: - // console.log('after !!!') - // console.log('admin: ', admin) - // console.log('group: ', group) - // console.log('member: ', member) return ( !!group && !!admin && @@ -160,8 +125,6 @@ const isAllowedToChangeGroupMemberRole = rule({ ['pending', 'usual', 'admin', 'owner'].includes(roleInGroup))) ) } catch (error) { - // Wolle: - // console.log('error: ', error) throw new Error(error) } finally { session.close() @@ -173,11 +136,6 @@ const isAllowedToJoinGroup = rule({ })(async (_parent, args, { user, driver }) => { if (!(user && user.id)) return false const { groupId, userId } = args - // Wolle: - // console.log('adminId: ', adminId) - // console.log('groupId: ', groupId) - // console.log('userId: ', userId) - // console.log('roleInGroup: ', roleInGroup) const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { const transactionResponse = await transaction.run( @@ -188,35 +146,15 @@ const isAllowedToJoinGroup = rule({ `, { groupId, userId }, ) - // Wolle: - // console.log( - // 'transactionResponse: ', - // transactionResponse, - // ) - // console.log( - // 'transaction groups: ', - // transactionResponse.records.map((record) => record.get('group')), - // ) - // console.log( - // 'transaction members: ', - // transactionResponse.records.map((record) => record.get('member')), - // ) return { group: transactionResponse.records.map((record) => record.get('group'))[0], member: transactionResponse.records.map((record) => record.get('member'))[0], } }) try { - // Wolle: - // console.log('enter try !!!') const { group, member } = await readTxPromise - // Wolle: - // console.log('after !!!') - // console.log('group: ', group) - // console.log('member: ', member) return !!group && (group.groupType !== 'hidden' || (!!member && !!member.myRoleInGroup)) } catch (error) { - // Wolle: console.log('error: ', error) throw new Error(error) } finally { session.close() diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 6ca09d72a..c47cf6085 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -48,7 +48,6 @@ export default { }, GroupMembers: async (_object, params, context, _resolveInfo) => { const { id: groupId } = params - // Wolle: console.log('groupId: ', groupId) const session = context.driver.session() const readTxResultPromise = session.readTransaction(async (txc) => { const groupMemberCypher = ` @@ -160,11 +159,6 @@ export default { }, ChangeGroupMemberRole: async (_parent, params, context, _resolveInfo) => { const { groupId, userId, roleInGroup } = params - // Wolle - // console.log('ChangeGroupMemberRole !!!') - // console.log('groupId: ', groupId) - // console.log('userId: ', userId) - // console.log('roleInGroup: ', roleInGroup) const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { const joinGroupCypher = ` @@ -180,8 +174,6 @@ export default { ` const result = await transaction.run(joinGroupCypher, { groupId, userId, roleInGroup }) const [member] = await result.records.map((record) => record.get('member')) - // Wolle - // console.log('member: ', member) return member }) try { diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 7a5f9504a..be9ba4f3d 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -728,16 +728,6 @@ describe('in mode', () => { roleInGroup: 'admin', }, }) - // Wolle: - // const groups = await query({ query: groupQuery, variables: {} }) - // console.log('groups.data.Group: ', groups.data.Group) - // const groupMemberOfClosedGroup = await mutate({ - // mutation: groupMembersQuery, - // variables: { - // id: 'hidden-group', - // }, - // }) - // console.log('groupMemberOfClosedGroup.data.GroupMembers: ', groupMemberOfClosedGroup.data.GroupMembers) authenticatedUser = null }) @@ -1340,16 +1330,6 @@ describe('in mode', () => { }) it('has role admin', async () => { - // Wolle: - // const groups = await query({ query: groupQuery, variables: {} }) - // console.log('groups.data.Group: ', groups.data.Group) - // const groupMemberOfClosedGroup = await mutate({ - // mutation: groupMembersQuery, - // variables: { - // id: 'closed-group', - // }, - // }) - // console.log('groupMemberOfClosedGroup.data.GroupMembers: ', groupMemberOfClosedGroup.data.GroupMembers) await expect( mutate({ mutation: changeGroupMemberRoleMutation, From 407f4f8d2d27605c41fb529c0f514e9fea5e7bd8 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 25 Aug 2022 13:29:08 +0200 Subject: [PATCH 36/58] fix test (mock .CATEGORIES_ACTIVE) --- webapp/components/FilterMenu/FilterMenu.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webapp/components/FilterMenu/FilterMenu.spec.js b/webapp/components/FilterMenu/FilterMenu.spec.js index 9e7ebfec0..6e9741e79 100644 --- a/webapp/components/FilterMenu/FilterMenu.spec.js +++ b/webapp/components/FilterMenu/FilterMenu.spec.js @@ -8,6 +8,9 @@ let wrapper describe('FilterMenu.vue', () => { const mocks = { $t: jest.fn((string) => string), + $env: { + CATEGORIES_ACTIVE: true, + }, } const getters = { From c3ab014d9c7f371bf96cdbc641d918ed681eb1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 25 Aug 2022 13:44:05 +0200 Subject: [PATCH 37/58] Set 'updatedAt' to 'null' on 'MEMBER_OF' creation in all group resolvers --- backend/src/schema/resolvers/groups.js | 8 +++++-- backend/src/schema/types/type/Group.gql | 28 ++++++++++++------------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index c47cf6085..abaa1716f 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -107,8 +107,10 @@ export default { MATCH (owner:User {id: $userId}) MERGE (owner)-[:CREATED]->(group) MERGE (owner)-[membership:MEMBER_OF]->(group) - SET membership.createdAt = toString(datetime()) - SET membership.role = 'owner' + SET + membership.createdAt = toString(datetime()), + membership.updatedAt = null, + membership.role = 'owner' ${categoriesCypher} RETURN group {.*, myRole: membership.role} `, @@ -138,6 +140,7 @@ export default { MERGE (member)-[membership:MEMBER_OF]->(group) ON CREATE SET membership.createdAt = toString(datetime()), + membership.updatedAt = null, membership.role = CASE WHEN group.groupType = 'public' THEN 'usual' @@ -166,6 +169,7 @@ export default { MERGE (member)-[membership:MEMBER_OF]->(group) ON CREATE SET membership.createdAt = toString(datetime()), + membership.updatedAt = null, membership.role = $roleInGroup ON MATCH SET membership.updatedAt = toString(datetime()), diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index 5c98e49e8..e254e5086 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -59,7 +59,7 @@ input _GroupFilter { type Query { Group( - isMember: Boolean # if 'undefined' or 'null' then all groups + isMember: Boolean # if 'undefined' or 'null' then get all groups id: ID name: String slug: String @@ -81,11 +81,11 @@ type Query { filter: _UserFilter ): [User] - AvailableGroupTypes: [GroupType]! + # AvailableGroupTypes: [GroupType]! - AvailableGroupActionRadii: [GroupActionRadius]! + # AvailableGroupActionRadii: [GroupActionRadius]! - AvailableGroupMemberRoles: [GroupMemberRole]! + # AvailableGroupMemberRoles: [GroupMemberRole]! } type Mutation { @@ -102,17 +102,17 @@ type Mutation { locationName: String ): Group - UpdateGroup( - id: ID! - name: String - slug: String - avatar: ImageInput - locationName: String - about: String - description: String - ): Group + # UpdateGroup( + # id: ID! + # name: String + # slug: String + # avatar: ImageInput + # locationName: String + # about: String + # description: String + # ): Group - DeleteGroup(id: ID!): Group + # DeleteGroup(id: ID!): Group JoinGroup( groupId: ID! From 921adb9d8b785d73eb6ec734cc3646dda63368da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 25 Aug 2022 13:47:37 +0200 Subject: [PATCH 38/58] Fixed all tests --- backend/src/schema/resolvers/groups.spec.js | 62 ++++++++++----------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index be9ba4f3d..1d272de2b 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -112,7 +112,7 @@ describe('in mode', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await mutate({ mutation: createGroupMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -219,7 +219,7 @@ describe('in mode', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await query({ query: groupQuery, variables: {} }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -348,7 +348,7 @@ describe('in mode', () => { userId: 'current-user', }, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -529,7 +529,7 @@ describe('in mode', () => { userId: 'owner-of-closed-group', }, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -575,7 +575,7 @@ describe('in mode', () => { id: 'not-existing-group', } const { errors } = await query({ query: groupMembersQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -922,7 +922,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await query({ query: groupMembersQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -933,7 +933,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await query({ query: groupMembersQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1065,7 +1065,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await query({ query: groupMembersQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1076,7 +1076,7 @@ describe('in mode', () => { it('throws authorization error', async () => { const { errors } = await query({ query: groupMembersQuery, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1257,7 +1257,7 @@ describe('in mode', () => { roleInGroup: 'pending', }, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1412,7 +1412,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1438,7 +1438,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1487,7 +1487,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1510,7 +1510,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1533,7 +1533,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1591,7 +1591,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1614,7 +1614,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1631,7 +1631,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1654,7 +1654,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1671,7 +1671,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1694,7 +1694,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1711,7 +1711,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1801,7 +1801,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1818,7 +1818,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1841,7 +1841,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1858,7 +1858,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1881,7 +1881,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -1898,7 +1898,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -1988,7 +1988,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -2011,7 +2011,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) @@ -2034,7 +2034,7 @@ describe('in mode', () => { mutation: changeGroupMemberRoleMutation, variables, }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) From f2aba3e12e284e8a0bc78ac0afe52aed01420711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 25 Aug 2022 18:03:21 +0200 Subject: [PATCH 39/58] Put exclamation mark behind console log text of successfully created default admin user for consistency --- backend/src/db/migrate/store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js index 61d591cb7..3a1b321e4 100644 --- a/backend/src/db/migrate/store.js +++ b/backend/src/db/migrate/store.js @@ -69,7 +69,7 @@ const createDefaultAdminUser = async (session) => { }) try { await createAdminTxResultPromise - console.log('Successfully created default admin user') // eslint-disable-line no-console + console.log('Successfully created default admin user!') // eslint-disable-line no-console } catch (error) { console.log(error) // eslint-disable-line no-console } From 512784e580dee2b995bb5c8f09c0278b247a74f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 25 Aug 2022 20:17:23 +0200 Subject: [PATCH 40/58] Fix group tests --- backend/src/schema/resolvers/groups.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 707558a06..6890e9147 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -89,7 +89,7 @@ describe('Group', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await query({ query: groupQuery, variables: {} }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) @@ -225,7 +225,7 @@ describe('CreateGroup', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { const { errors } = await mutate({ mutation: createGroupMutation, variables }) - expect(errors[0]).toHaveProperty('message', 'Not Authorised!') + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) From d969141fc6d6eb7d641c537ae13116061ddce19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 30 Aug 2022 06:58:39 +0200 Subject: [PATCH 41/58] Implement 'UpdateGroup' resolver, first step --- backend/src/constants/groups.js | 1 + backend/src/db/graphql/groups.js | 38 +++++++++++++++++++ backend/src/middleware/excerptMiddleware.js | 7 +++- .../src/middleware/permissionsMiddleware.js | 31 +++++++++++++++ backend/src/middleware/sluggifyMiddleware.js | 4 ++ 5 files changed, 80 insertions(+), 1 deletion(-) diff --git a/backend/src/constants/groups.js b/backend/src/constants/groups.js index b4a6063f1..64ffeaa59 100644 --- a/backend/src/constants/groups.js +++ b/backend/src/constants/groups.js @@ -1,2 +1,3 @@ // this file is duplicated in `backend/src/constants/group.js` and `webapp/constants/group.js` export const DESCRIPTION_WITHOUT_HTML_LENGTH_MIN = 100 // with removed HTML tags +export const DESCRIPTION_EXCERPT_HTML_LENGTH = 120 // with removed HTML tags diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 2a611f324..4d6bad5fd 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -39,6 +39,44 @@ export const createGroupMutation = gql` } ` +export const updateGroupMutation = gql` + mutation ( + $id: ID! + $name: String + $slug: String + $avatar: ImageInput + $about: String + $description: String + $actionRadius: GroupActionRadius + $categoryIds: [ID] + ) { + UpdateGroup( + id: $id + name: $name + slug: $slug + avatar: $avatar + about: $about + description: $description + actionRadius: $actionRadius + categoryIds: $categoryIds + ) { + id + name + slug + avatar + createdAt + updatedAt + disabled + deleted + about + description + groupType + actionRadius + myRole + } + } +` + // ------ queries export const groupQuery = gql` diff --git a/backend/src/middleware/excerptMiddleware.js b/backend/src/middleware/excerptMiddleware.js index ca061609a..68eea9a74 100644 --- a/backend/src/middleware/excerptMiddleware.js +++ b/backend/src/middleware/excerptMiddleware.js @@ -1,9 +1,14 @@ import trunc from 'trunc-html' +import { DESCRIPTION_EXCERPT_HTML_LENGTH } from '../constants/groups' export default { Mutation: { CreateGroup: async (resolve, root, args, context, info) => { - args.descriptionExcerpt = trunc(args.description, 120).html + args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html + return resolve(root, args, context, info) + }, + UpdateGroup: async (resolve, root, args, context, info) => { + args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html return resolve(root, args, context, info) }, CreatePost: async (resolve, root, args, context, info) => { diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index c81b069d2..9909f542d 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -52,6 +52,36 @@ const isMySocialMedia = rule({ return socialMedia.ownedBy.node.id === user.id }) +const isAllowedToChangeGroupSettings = rule({ + cache: 'no_cache', +})(async (_parent, args, { user, driver }) => { + if (!(user && user.id)) return false + const ownerId = user.id + const { id: groupId } = args + const session = driver.session() + const readTxPromise = session.readTransaction(async (transaction) => { + const transactionResponse = await transaction.run( + ` + MATCH (owner:User {id: $ownerId})-[adminMembership:MEMBER_OF]->(group:Group {id: $groupId}) + RETURN group {.*}, owner {.*, myRoleInGroup: adminMembership.role} + `, + { groupId, ownerId }, + ) + return { + owner: transactionResponse.records.map((record) => record.get('owner'))[0], + group: transactionResponse.records.map((record) => record.get('group'))[0], + } + }) + try { + const { owner, group } = await readTxPromise + return !!group && !!owner && ['owner'].includes(owner.myRoleInGroup) + } catch (error) { + throw new Error(error) + } finally { + session.close() + } +}) + const isAuthor = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { @@ -142,6 +172,7 @@ export default shield( SignupVerification: allow, UpdateUser: onlyYourself, CreateGroup: isAuthenticated, + UpdateGroup: isAllowedToChangeGroupSettings, CreatePost: isAuthenticated, UpdatePost: isAuthor, DeletePost: isAuthor, diff --git a/backend/src/middleware/sluggifyMiddleware.js b/backend/src/middleware/sluggifyMiddleware.js index 2a965c87f..8fd200e8f 100644 --- a/backend/src/middleware/sluggifyMiddleware.js +++ b/backend/src/middleware/sluggifyMiddleware.js @@ -30,6 +30,10 @@ export default { args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Group'))) return resolve(root, args, context, info) }, + UpdateGroup: async (resolve, root, args, context, info) => { + args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Group'))) + return resolve(root, args, context, info) + }, CreatePost: async (resolve, root, args, context, info) => { args.slug = args.slug || (await uniqueSlug(args.title, isUniqueFor(context, 'Post'))) return resolve(root, args, context, info) From fa2e92a363045d8ab630119c4ed0719cad48e19c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Tue, 30 Aug 2022 08:20:28 +0200 Subject: [PATCH 42/58] Refine tests in 'slugifyMiddleware.spec.js' --- .../src/middleware/slugifyMiddleware.spec.js | 190 +++++++++++++++--- 1 file changed, 158 insertions(+), 32 deletions(-) diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 3fea526ee..62bfcf76d 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -8,6 +8,7 @@ import { signupVerificationMutation } from '../db/graphql/authentications' let authenticatedUser let variables +const categoryIds = ['cat9'] const driver = getDriver() const neode = getNeode() @@ -62,8 +63,6 @@ afterEach(async () => { describe('slugifyMiddleware', () => { describe('CreateGroup', () => { - const categoryIds = ['cat9'] - beforeEach(() => { variables = { ...variables, @@ -130,15 +129,14 @@ describe('slugifyMiddleware', () => { }) it('chooses another slug', async () => { - variables = { - ...variables, - name: 'Pre-Existing Group', - about: 'As an about', - } await expect( mutate({ mutation: createGroupMutation, - variables, + variables: { + ...variables, + name: 'Pre-Existing Group', + about: 'As an about', + }, }), ).resolves.toMatchObject({ data: { @@ -151,15 +149,144 @@ describe('slugifyMiddleware', () => { describe('but if the client specifies a slug', () => { it('rejects CreateGroup', async (done) => { - variables = { - ...variables, - name: 'Pre-Existing Group', - about: 'As an about', - slug: 'pre-existing-group', - } try { await expect( - mutate({ mutation: createGroupMutation, variables }), + mutate({ + mutation: createGroupMutation, + variables: { + ...variables, + name: 'Pre-Existing Group', + about: 'As an about', + slug: 'pre-existing-group', + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Group with this slug already exists!', + }, + ], + }) + done() + } catch (error) { + throw new Error(` + ${error} + + Probably your database has no unique constraints! + + To see all constraints go to http://localhost:7474/browser/ and + paste the following: + \`\`\` + CALL db.constraints(); + \`\`\` + + Learn how to setup the database here: + https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/backend/README.md#database-indices-and-constraints + `) + } + }) + }) + }) + }) + + describe('UpdateGroup', () => { + beforeEach(() => { + variables = { + ...variables, + name: 'The Best Group', + about: 'Some about', + description: 'Some description' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + categoryIds, + } + }) + + describe('if slug not exists', () => { + it('generates a slug based on name', async () => { + await expect( + mutate({ + mutation: createGroupMutation, + variables, + }), + ).resolves.toMatchObject({ + data: { + CreateGroup: { + name: 'The Best Group', + slug: 'the-best-group', + about: 'Some about', + description: 'Some description' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + }, + }, + }) + }) + + it('generates a slug based on given slug', async () => { + await expect( + mutate({ + mutation: createGroupMutation, + variables: { + ...variables, + slug: 'the-group', + }, + }), + ).resolves.toMatchObject({ + data: { + CreateGroup: { + slug: 'the-group', + }, + }, + }) + }) + }) + + describe('if slug exists', () => { + beforeEach(async () => { + await mutate({ + mutation: createGroupMutation, + variables: { + ...variables, + name: 'Pre-Existing Group', + slug: 'pre-existing-group', + about: 'As an about', + }, + }) + }) + + it('chooses another slug', async () => { + await expect( + mutate({ + mutation: createGroupMutation, + variables: { + ...variables, + name: 'Pre-Existing Group', + about: 'As an about', + }, + }), + ).resolves.toMatchObject({ + data: { + CreateGroup: { + slug: 'pre-existing-group-1', + }, + }, + }) + }) + + describe('but if the client specifies a slug', () => { + it('rejects CreateGroup', async (done) => { + try { + await expect( + mutate({ + mutation: createGroupMutation, + variables: { + ...variables, + name: 'Pre-Existing Group', + about: 'As an about', + slug: 'pre-existing-group', + }, + }), ).resolves.toMatchObject({ errors: [ { @@ -190,8 +317,6 @@ describe('slugifyMiddleware', () => { }) describe('CreatePost', () => { - const categoryIds = ['cat9'] - beforeEach(() => { variables = { ...variables, @@ -252,16 +377,15 @@ describe('slugifyMiddleware', () => { }) it('chooses another slug', async () => { - variables = { - ...variables, - title: 'Pre-existing post', - content: 'Some content', - categoryIds, - } await expect( mutate({ mutation: createPostMutation, - variables, + variables: { + ...variables, + title: 'Pre-existing post', + content: 'Some content', + categoryIds, + }, }), ).resolves.toMatchObject({ data: { @@ -274,16 +398,18 @@ describe('slugifyMiddleware', () => { describe('but if the client specifies a slug', () => { it('rejects CreatePost', async (done) => { - variables = { - ...variables, - title: 'Pre-existing post', - content: 'Some content', - slug: 'pre-existing-post', - categoryIds, - } try { await expect( - mutate({ mutation: createPostMutation, variables }), + mutate({ + mutation: createPostMutation, + variables: { + ...variables, + title: 'Pre-existing post', + content: 'Some content', + slug: 'pre-existing-post', + categoryIds, + }, + }), ).resolves.toMatchObject({ errors: [ { From 3e6cabaa1c4e8e7cccf11634d4c893f822767ca1 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 30 Aug 2022 10:13:56 +0200 Subject: [PATCH 43/58] Update backend/src/db/migrate/store.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Wolfgang Huß --- backend/src/db/migrate/store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js index 3a1b321e4..d6fd25bd8 100644 --- a/backend/src/db/migrate/store.js +++ b/backend/src/db/migrate/store.js @@ -29,6 +29,7 @@ const createCategories = async (session) => { }) try { await createCategoriesTxResultPromise + console.log('Successfully created categories!') // eslint-disable-line no-console } catch (error) { console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console } From 0d1da0d83d2bb5b183c203e2db69f58978e1cd1d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 30 Aug 2022 10:14:39 +0200 Subject: [PATCH 44/58] Update backend/src/db/migrate/store.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Wolfgang Huß --- backend/src/db/migrate/store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js index d6fd25bd8..98014f05c 100644 --- a/backend/src/db/migrate/store.js +++ b/backend/src/db/migrate/store.js @@ -30,6 +30,7 @@ const createCategories = async (session) => { try { await createCategoriesTxResultPromise console.log('Successfully created categories!') // eslint-disable-line no-console + console.log('Successfully created categories!') // eslint-disable-line no-console } catch (error) { console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console } From c61e983102042b7a6fd568767ee81bf555425f70 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 30 Aug 2022 10:21:15 +0200 Subject: [PATCH 45/58] remove double console log --- backend/src/db/migrate/store.js | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/db/migrate/store.js b/backend/src/db/migrate/store.js index 98014f05c..d6fd25bd8 100644 --- a/backend/src/db/migrate/store.js +++ b/backend/src/db/migrate/store.js @@ -30,7 +30,6 @@ const createCategories = async (session) => { try { await createCategoriesTxResultPromise console.log('Successfully created categories!') // eslint-disable-line no-console - console.log('Successfully created categories!') // eslint-disable-line no-console } catch (error) { console.log(`Error creating categories: ${error}`) // eslint-disable-line no-console } From 422d818133de538d5a6c67ad340319760b4660dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 31 Aug 2022 08:51:01 +0200 Subject: [PATCH 46/58] !!! Temp !!! --- backend/src/middleware/slugifyMiddleware.spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 62bfcf76d..3a8f380d9 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -189,7 +189,7 @@ describe('slugifyMiddleware', () => { }) }) - describe('UpdateGroup', () => { + describe.only('UpdateGroup', () => { beforeEach(() => { variables = { ...variables, @@ -200,6 +200,10 @@ describe('slugifyMiddleware', () => { actionRadius: 'national', categoryIds, } + await mutate({ + mutation: createGroupMutation, + variables, + }) }) describe('if slug not exists', () => { From a5878ab505ba546fbc7ee2c8232f93a43c74140b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 31 Aug 2022 09:22:38 +0200 Subject: [PATCH 47/58] Fix linting --- backend/src/db/seed.js | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index a3ba8c16b..b516ca529 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -5,7 +5,6 @@ import createServer from '../server' import faker from '@faker-js/faker' import Factory from '../db/factories' import { getNeode, getDriver } from '../db/neo4j' -import { gql } from '../helpers/jest' import { createGroupMutation, joinGroupMutation, From 44eb9d0bde3173e127e929367cc2a55f717c9cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 1 Sep 2022 09:31:50 +0200 Subject: [PATCH 48/58] Implement 'UpdateGroup' resolver, not working --- backend/src/middleware/excerptMiddleware.js | 1 + .../src/middleware/permissionsMiddleware.js | 3 + backend/src/middleware/sluggifyMiddleware.js | 1 + .../src/middleware/slugifyMiddleware.spec.js | 111 ++++++------------ backend/src/schema/resolvers/groups.js | 86 ++++++++++++-- backend/src/schema/resolvers/posts.js | 10 +- backend/src/schema/types/type/Group.gql | 18 +-- 7 files changed, 134 insertions(+), 96 deletions(-) diff --git a/backend/src/middleware/excerptMiddleware.js b/backend/src/middleware/excerptMiddleware.js index 68eea9a74..597b97714 100644 --- a/backend/src/middleware/excerptMiddleware.js +++ b/backend/src/middleware/excerptMiddleware.js @@ -8,6 +8,7 @@ export default { return resolve(root, args, context, info) }, UpdateGroup: async (resolve, root, args, context, info) => { + console.log('excerptMiddleware - UpdateGroup !!!') args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html return resolve(root, args, context, info) }, diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 40f336d2f..f0723e50b 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -55,9 +55,12 @@ const isMySocialMedia = rule({ const isAllowedToChangeGroupSettings = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { + console.log('isAllowedToChangeGroupSettings !!!') if (!(user && user.id)) return false const ownerId = user.id const { id: groupId } = args + console.log('ownerId: ', ownerId) + console.log('groupId: ', groupId) const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { const transactionResponse = await transaction.run( diff --git a/backend/src/middleware/sluggifyMiddleware.js b/backend/src/middleware/sluggifyMiddleware.js index 8fd200e8f..d5f75adb0 100644 --- a/backend/src/middleware/sluggifyMiddleware.js +++ b/backend/src/middleware/sluggifyMiddleware.js @@ -31,6 +31,7 @@ export default { return resolve(root, args, context, info) }, UpdateGroup: async (resolve, root, args, context, info) => { + console.log('sluggifyMiddleware - UpdateGroup !!!') args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Group'))) return resolve(root, args, context, info) }, diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 4f0401cdc..9baef1fae 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -2,7 +2,8 @@ import { getNeode, getDriver } from '../db/neo4j' import createServer from '../server' import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '../db/factories' -import { createGroupMutation } from '../db/graphql/groups' +import { sleep } from '../helpers/jest.js' +import { createGroupMutation, updateGroupMutation } from '../db/graphql/groups' import { createPostMutation } from '../db/graphql/posts' import { signupVerificationMutation } from '../db/graphql/authentications' @@ -189,101 +190,61 @@ describe('slugifyMiddleware', () => { }) }) - describe.only('UpdateGroup', () => { + describe('UpdateGroup', () => { beforeEach(async () => { variables = { ...variables, - name: 'The Best Group', + name: 'Pre-Existing Group', + slug: 'pre-existing-group', about: 'Some about', description: 'Some description' + descriptionAdditional100, groupType: 'closed', actionRadius: 'national', categoryIds, } + console.log('createGroupMutation: ', createGroupMutation) await mutate({ mutation: createGroupMutation, variables, }) + // Wolle: console.log('sleep !!!') + // await sleep(4 * 1000) }) - describe('if slug not exists', () => { - it('generates a slug based on name', async () => { - await expect( - mutate({ - mutation: createGroupMutation, - variables, - }), - ).resolves.toMatchObject({ - data: { - CreateGroup: { - name: 'The Best Group', - slug: 'the-best-group', - about: 'Some about', - description: 'Some description' + descriptionAdditional100, - groupType: 'closed', - actionRadius: 'national', + describe('if group exists', () => { + describe('if new slug not(!) exists', () => { + it.only('has the new slug', async () => { + console.log('updateGroupMutation: ', updateGroupMutation) + await expect( + mutate({ + mutation: updateGroupMutation, + variables: { + ...variables, + slug: 'my-best-group', + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + name: 'The Best Group', + slug: 'my-best-group', + about: 'Some about', + description: 'Some description' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + myRole: 'owner', + }, }, - }, + }) }) }) - it('generates a slug based on given slug', async () => { - await expect( - mutate({ - mutation: createGroupMutation, - variables: { - ...variables, - slug: 'the-group', - }, - }), - ).resolves.toMatchObject({ - data: { - CreateGroup: { - slug: 'the-group', - }, - }, - }) - }) - }) - - describe('if slug exists', () => { - beforeEach(async () => { - await mutate({ - mutation: createGroupMutation, - variables: { - ...variables, - name: 'Pre-Existing Group', - slug: 'pre-existing-group', - about: 'As an about', - }, - }) - }) - - it('chooses another slug', async () => { - await expect( - mutate({ - mutation: createGroupMutation, - variables: { - ...variables, - name: 'Pre-Existing Group', - about: 'As an about', - }, - }), - ).resolves.toMatchObject({ - data: { - CreateGroup: { - slug: 'pre-existing-group-1', - }, - }, - }) - }) - - describe('but if the client specifies a slug', () => { - it('rejects CreateGroup', async (done) => { + describe('if new slug exists', () => { + it('rejects UpdateGroup', async (done) => { try { await expect( mutate({ - mutation: createGroupMutation, + mutation: updateGroupMutation, variables: { ...variables, name: 'Pre-Existing Group', @@ -443,6 +404,8 @@ describe('slugifyMiddleware', () => { }) }) + it.todo('UpdatePost') + describe('SignupVerification', () => { beforeEach(() => { variables = { diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index abaa1716f..312c37e69 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -33,10 +33,10 @@ export default { ` } } - const result = await txc.run(groupCypher, { + const transactionResponse = await txc.run(groupCypher, { userId: context.user.id, }) - return result.records.map((record) => record.get('group')) + return transactionResponse.records.map((record) => record.get('group')) }) try { return await readTxResultPromise @@ -54,10 +54,10 @@ export default { MATCH (user:User)-[membership:MEMBER_OF]->(:Group {id: $groupId}) RETURN user {.*, myRoleInGroup: membership.role} ` - const result = await txc.run(groupMemberCypher, { + const transactionResponse = await txc.run(groupMemberCypher, { groupId, }) - return result.records.map((record) => record.get('user')) + return transactionResponse.records.map((record) => record.get('user')) }) try { return await readTxResultPromise @@ -131,6 +131,72 @@ export default { session.close() } }, + UpdateGroup: async (_parent, params, context, _resolveInfo) => { + console.log('UpdateGroup !!!') + const { categoryIds } = params + const { id: groupId } = params + console.log('categoryIds: ', categoryIds) + console.log('groupId: ', groupId) + delete params.categoryIds + if (CONFIG.CATEGORIES_ACTIVE && (!categoryIds || categoryIds.length < CATEGORIES_MIN)) { + throw new UserInputError('Too view categories!') + } + if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length > CATEGORIES_MAX) { + throw new UserInputError('Too many categories!') + } + if ( + params.description === undefined || + params.description === null || + removeHtmlTags(params.description).length < DESCRIPTION_WITHOUT_HTML_LENGTH_MIN + ) { + throw new UserInputError('Description too short!') + } + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + let updateGroupCypher = ` + MATCH (group:Group {id: $groupId}) + SET group += $params + SET group.updatedAt = toString(datetime()) + WITH group + ` + if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) { + const cypherDeletePreviousRelations = ` + MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category) + DELETE previousRelations + RETURN group, category + ` + await session.writeTransaction((transaction) => { + return transaction.run(cypherDeletePreviousRelations, { groupId }) + }) + updateGroupCypher += ` + UNWIND $categoryIds AS categoryId + MATCH (category:Category {id: categoryId}) + MERGE (group)-[:CATEGORIZED]->(category) + WITH group + OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group) + WITH group, membership # Wolle: is not needed in my eyes + ` + } + updateGroupCypher += `RETURN group {.*, myRole: membership.role}` + const transactionResponse = await transaction.run(updateGroupCypher, { + groupId, + userId: context.user.id, + categoryIds, + params, + }) + const [group] = await transactionResponse.records.map((record) => record.get('group')) + return group + }) + try { + return await writeTxResultPromise + } catch (error) { + if (error.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') + throw new UserInputError('Group with this slug already exists!') + throw new Error(error) + } finally { + session.close() + } + }, JoinGroup: async (_parent, params, context, _resolveInfo) => { const { groupId, userId } = params const session = context.driver.session() @@ -148,8 +214,8 @@ export default { END RETURN member {.*, myRoleInGroup: membership.role} ` - const result = await transaction.run(joinGroupCypher, { groupId, userId }) - const [member] = await result.records.map((record) => record.get('member')) + const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId }) + const [member] = await transactionResponse.records.map((record) => record.get('member')) return member }) try { @@ -176,8 +242,12 @@ export default { membership.role = $roleInGroup RETURN member {.*, myRoleInGroup: membership.role} ` - const result = await transaction.run(joinGroupCypher, { groupId, userId, roleInGroup }) - const [member] = await result.records.map((record) => record.get('member')) + const transactionResponse = await transaction.run(joinGroupCypher, { + groupId, + userId, + roleInGroup, + }) + const [member] = await transactionResponse.records.map((record) => record.get('member')) return member }) try { diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index d9a04732c..97230715f 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -131,11 +131,11 @@ export default { delete params.image const session = context.driver.session() let updatePostCypher = ` - MATCH (post:Post {id: $params.id}) - SET post += $params - SET post.updatedAt = toString(datetime()) - WITH post - ` + MATCH (post:Post {id: $params.id}) + SET post += $params + SET post.updatedAt = toString(datetime()) + WITH post + ` if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) { const cypherDeletePreviousRelations = ` diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index e254e5086..aa28eccd1 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -102,15 +102,15 @@ type Mutation { locationName: String ): Group - # UpdateGroup( - # id: ID! - # name: String - # slug: String - # avatar: ImageInput - # locationName: String - # about: String - # description: String - # ): Group + UpdateGroup( + id: ID! + name: String + slug: String + avatar: ImageInput + locationName: String + about: String + description: String + ): Group # DeleteGroup(id: ID!): Group From 4f7ce5a6c1adaa5cc38ca80da890179eb6dbfe44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 1 Sep 2022 15:04:49 +0200 Subject: [PATCH 49/58] Fix slugification tests of 'UpdateGroup' --- backend/src/db/graphql/groups.js | 25 ++++++++- backend/src/middleware/excerptMiddleware.js | 1 - .../src/middleware/permissionsMiddleware.js | 12 +++-- backend/src/middleware/sluggifyMiddleware.js | 1 - .../src/middleware/slugifyMiddleware.spec.js | 52 +++++++++++-------- backend/src/schema/resolvers/groups.js | 31 ++++++----- backend/src/schema/types/type/Group.gql | 6 ++- 7 files changed, 80 insertions(+), 48 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index cbfebe1b2..277ae356f 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -12,6 +12,8 @@ export const createGroupMutation = gql` $groupType: GroupType! $actionRadius: GroupActionRadius! $categoryIds: [ID] + $avatar: ImageInput + $locationName: String ) { CreateGroup( id: $id @@ -22,6 +24,8 @@ export const createGroupMutation = gql` groupType: $groupType actionRadius: $actionRadius categoryIds: $categoryIds + avatar: $avatar + locationName: $locationName ) { id name @@ -34,6 +38,14 @@ export const createGroupMutation = gql` description groupType actionRadius + # categories { + # id + # slug + # name + # icon + # } + # avatar + # locationName myRole } } @@ -44,11 +56,12 @@ export const updateGroupMutation = gql` $id: ID! $name: String $slug: String - $avatar: ImageInput $about: String $description: String $actionRadius: GroupActionRadius $categoryIds: [ID] + $avatar: ImageInput + $locationName: String ) { UpdateGroup( id: $id @@ -59,11 +72,11 @@ export const updateGroupMutation = gql` description: $description actionRadius: $actionRadius categoryIds: $categoryIds + locationName: $locationName ) { id name slug - avatar createdAt updatedAt disabled @@ -72,6 +85,14 @@ export const updateGroupMutation = gql` description groupType actionRadius + # categories { + # id + # slug + # name + # icon + # } + # avatar + # locationName myRole } } diff --git a/backend/src/middleware/excerptMiddleware.js b/backend/src/middleware/excerptMiddleware.js index 597b97714..68eea9a74 100644 --- a/backend/src/middleware/excerptMiddleware.js +++ b/backend/src/middleware/excerptMiddleware.js @@ -8,7 +8,6 @@ export default { return resolve(root, args, context, info) }, UpdateGroup: async (resolve, root, args, context, info) => { - console.log('excerptMiddleware - UpdateGroup !!!') args.descriptionExcerpt = trunc(args.description, DESCRIPTION_EXCERPT_HTML_LENGTH).html return resolve(root, args, context, info) }, diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index f0723e50b..1752d2bdf 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -55,18 +55,18 @@ const isMySocialMedia = rule({ const isAllowedToChangeGroupSettings = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - console.log('isAllowedToChangeGroupSettings !!!') + // Wolle: console.log('isAllowedToChangeGroupSettings !!!') if (!(user && user.id)) return false const ownerId = user.id const { id: groupId } = args - console.log('ownerId: ', ownerId) - console.log('groupId: ', groupId) + // Wolle: console.log('ownerId: ', ownerId) + // Wolle: console.log('groupId: ', groupId) const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { const transactionResponse = await transaction.run( ` - MATCH (owner:User {id: $ownerId})-[adminMembership:MEMBER_OF]->(group:Group {id: $groupId}) - RETURN group {.*}, owner {.*, myRoleInGroup: adminMembership.role} + MATCH (owner:User {id: $ownerId})-[membership:MEMBER_OF]->(group:Group {id: $groupId}) + RETURN group {.*}, owner {.*, myRoleInGroup: membership.role} `, { groupId, ownerId }, ) @@ -77,6 +77,8 @@ const isAllowedToChangeGroupSettings = rule({ }) try { const { owner, group } = await readTxPromise + // Wolle: console.log('owner: ', owner) + // Wolle: console.log('group: ', group) return !!group && !!owner && ['owner'].includes(owner.myRoleInGroup) } catch (error) { throw new Error(error) diff --git a/backend/src/middleware/sluggifyMiddleware.js b/backend/src/middleware/sluggifyMiddleware.js index d5f75adb0..8fd200e8f 100644 --- a/backend/src/middleware/sluggifyMiddleware.js +++ b/backend/src/middleware/sluggifyMiddleware.js @@ -31,7 +31,6 @@ export default { return resolve(root, args, context, info) }, UpdateGroup: async (resolve, root, args, context, info) => { - console.log('sluggifyMiddleware - UpdateGroup !!!') args.slug = args.slug || (await uniqueSlug(args.name, isUniqueFor(context, 'Group'))) return resolve(root, args, context, info) }, diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 9baef1fae..628d20fe6 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -2,7 +2,6 @@ import { getNeode, getDriver } from '../db/neo4j' import createServer from '../server' import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '../db/factories' -import { sleep } from '../helpers/jest.js' import { createGroupMutation, updateGroupMutation } from '../db/graphql/groups' import { createPostMutation } from '../db/graphql/posts' import { signupVerificationMutation } from '../db/graphql/authentications' @@ -191,35 +190,32 @@ describe('slugifyMiddleware', () => { }) describe('UpdateGroup', () => { + let createGroupResult + beforeEach(async () => { - variables = { - ...variables, - name: 'Pre-Existing Group', - slug: 'pre-existing-group', - about: 'Some about', - description: 'Some description' + descriptionAdditional100, - groupType: 'closed', - actionRadius: 'national', - categoryIds, - } - console.log('createGroupMutation: ', createGroupMutation) - await mutate({ + createGroupResult = await mutate({ mutation: createGroupMutation, - variables, + variables: { + name: 'The Best Group', + slug: 'the-best-group', + about: 'Some about', + description: 'Some description' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + categoryIds, + }, }) - // Wolle: console.log('sleep !!!') - // await sleep(4 * 1000) }) describe('if group exists', () => { describe('if new slug not(!) exists', () => { - it.only('has the new slug', async () => { - console.log('updateGroupMutation: ', updateGroupMutation) + it('has the new slug', async () => { + // Wolle: console.log('createGroupResult: ', createGroupResult) await expect( mutate({ mutation: updateGroupMutation, variables: { - ...variables, + id: createGroupResult.data.CreateGroup.id, slug: 'my-best-group', }, }), @@ -239,16 +235,26 @@ describe('slugifyMiddleware', () => { }) }) - describe('if new slug exists', () => { + describe('if new slug exists in another group', () => { it('rejects UpdateGroup', async (done) => { + await mutate({ + mutation: createGroupMutation, + variables: { + name: 'Pre-Existing Group', + slug: 'pre-existing-group', + about: 'Some about', + description: 'Some description' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + categoryIds, + }, + }) try { await expect( mutate({ mutation: updateGroupMutation, variables: { - ...variables, - name: 'Pre-Existing Group', - about: 'As an about', + id: createGroupResult.data.CreateGroup.id, slug: 'pre-existing-group', }, }), diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 312c37e69..c8a59f0cb 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -132,21 +132,22 @@ export default { } }, UpdateGroup: async (_parent, params, context, _resolveInfo) => { - console.log('UpdateGroup !!!') + // Wolle: console.log('UpdateGroup !!!') const { categoryIds } = params const { id: groupId } = params - console.log('categoryIds: ', categoryIds) - console.log('groupId: ', groupId) + // Wolle: console.log('categoryIds: ', categoryIds) + // Wolle: console.log('groupId: ', groupId) delete params.categoryIds - if (CONFIG.CATEGORIES_ACTIVE && (!categoryIds || categoryIds.length < CATEGORIES_MIN)) { - throw new UserInputError('Too view categories!') - } - if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length > CATEGORIES_MAX) { - throw new UserInputError('Too many categories!') + if (CONFIG.CATEGORIES_ACTIVE && categoryIds) { + if (categoryIds.length < CATEGORIES_MIN) { + throw new UserInputError('Too view categories!') + } + if (categoryIds.length > CATEGORIES_MAX) { + throw new UserInputError('Too many categories!') + } } if ( - params.description === undefined || - params.description === null || + params.description && removeHtmlTags(params.description).length < DESCRIPTION_WITHOUT_HTML_LENGTH_MIN ) { throw new UserInputError('Description too short!') @@ -164,7 +165,7 @@ export default { MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category) DELETE previousRelations RETURN group, category - ` + ` await session.writeTransaction((transaction) => { return transaction.run(cypherDeletePreviousRelations, { groupId }) }) @@ -173,11 +174,13 @@ export default { MATCH (category:Category {id: categoryId}) MERGE (group)-[:CATEGORIZED]->(category) WITH group - OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group) - WITH group, membership # Wolle: is not needed in my eyes ` } - updateGroupCypher += `RETURN group {.*, myRole: membership.role}` + updateGroupCypher += ` + OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group) + RETURN group {.*, myRole: membership.role} + ` + // Wolle: console.log('updateGroupCypher: ', updateGroupCypher) const transactionResponse = await transaction.run(updateGroupCypher, { groupId, userId: context.user.id, diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index aa28eccd1..70f9ce16f 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -106,10 +106,12 @@ type Mutation { id: ID! name: String slug: String - avatar: ImageInput - locationName: String about: String description: String + actionRadius: GroupActionRadius + categoryIds: [ID] + avatar: ImageInput + locationName: String ): Group # DeleteGroup(id: ID!): Group From da66aa4852b036fb1e88382f6f66fcf54555fe20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 1 Sep 2022 15:33:28 +0200 Subject: [PATCH 50/58] Test 'UpdateGroup' resolver, a start --- backend/src/schema/resolvers/groups.spec.js | 130 ++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 1d272de2b..ddf003659 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -2,6 +2,7 @@ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '../../db/factories' import { createGroupMutation, + updateGroupMutation, joinGroupMutation, changeGroupMemberRoleMutation, groupMembersQuery, @@ -2043,5 +2044,134 @@ describe('in mode', () => { }) }) }) + + describe('UpdateGroup', () => { + beforeAll(async () => { + await seedBasicsAndClearAuthentication() + }) + + afterAll(async () => { + await cleanDatabase() + }) + + describe('unauthenticated', () => { + it.only('throws authorization error', async () => { + const { errors } = await mutate({ + query: updateGroupMutation, + variables: { + id: 'my-group', + slug: 'my-best-group', + }, + }) + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') + }) + }) + + describe('authenticated', () => { + let otherUser + + beforeAll(async () => { + otherUser = await Factory.build( + 'user', + { + id: 'other-user', + name: 'Other TestUser', + }, + { + email: 'test2@example.org', + password: '1234', + }, + ) + authenticatedUser = await otherUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'others-group', + name: 'Uninteresting Group', + about: 'We will change nothing!', + description: 'We love it like it is!?' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'global', + categoryIds, + }, + }) + authenticatedUser = await user.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'my-group', + name: 'The Best Group', + about: 'We will change the world!', + description: 'Some description' + descriptionAdditional100, + groupType: 'public', + actionRadius: 'regional', + categoryIds, + }, + }) + }) + + describe('query groups', () => { + describe('without any filters', () => { + it('finds all groups', async () => { + await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({ + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }), + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), + }, + errors: undefined, + }) + }) + }) + + describe('isMember = true', () => { + it('finds only groups where user is member', async () => { + await expect( + query({ query: groupQuery, variables: { isMember: true } }), + ).resolves.toMatchObject({ + data: { + Group: [ + { + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }, + ], + }, + errors: undefined, + }) + }) + }) + + describe('isMember = false', () => { + it('finds only groups where user is not(!) member', async () => { + await expect( + query({ query: groupQuery, variables: { isMember: false } }), + ).resolves.toMatchObject({ + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + myRole: null, + }), + ]), + }, + errors: undefined, + }) + }) + }) + }) + }) + }) }) }) From b82dcd45aa6727b5c556975af77d001671231e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 2 Sep 2022 05:35:52 +0200 Subject: [PATCH 51/58] Add a missing await in 'posts.spec.js' --- backend/src/schema/resolvers/posts.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js index 52bd8fcd0..6fc9b5722 100644 --- a/backend/src/schema/resolvers/posts.spec.js +++ b/backend/src/schema/resolvers/posts.spec.js @@ -368,7 +368,7 @@ describe('UpdatePost', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { authenticatedUser = null - expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ + await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ errors: [{ message: 'Not Authorized!' }], data: { UpdatePost: null }, }) From 7ec608691582a0fd4326f520971beb2ea9aa3758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 2 Sep 2022 08:10:46 +0200 Subject: [PATCH 52/58] Test 'UpdateGroup' in general resolver and 'Group' resolver with 'id' --- backend/src/db/graphql/groups.js | 35 +-- backend/src/schema/resolvers/groups.js | 27 ++- backend/src/schema/resolvers/groups.spec.js | 239 +++++++++++++++----- backend/src/schema/types/type/Group.gql | 16 +- 4 files changed, 231 insertions(+), 86 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 277ae356f..fe3153fa2 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -38,14 +38,15 @@ export const createGroupMutation = gql` description groupType actionRadius - # categories { - # id - # slug - # name - # icon - # } - # avatar - # locationName + categories { + # test this as result + id + slug + name + icon + } + # avatar # test this as result + # locationName # test this as result myRole } } @@ -85,14 +86,15 @@ export const updateGroupMutation = gql` description groupType actionRadius - # categories { - # id - # slug - # name - # icon - # } - # avatar - # locationName + categories { + # test this as result + id + slug + name + icon + } + # avatar # test this as result + # locationName # test this as result myRole } } @@ -166,6 +168,7 @@ export const groupQuery = gql` actionRadius myRole categories { + # test this as result id slug name diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index c8a59f0cb..2a3f2fc11 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -9,25 +9,26 @@ import Resolver from './helpers/Resolver' export default { Query: { Group: async (_object, params, context, _resolveInfo) => { - const { isMember } = params + const { id: groupId, isMember } = params const session = context.driver.session() const readTxResultPromise = session.readTransaction(async (txc) => { + const groupIdCypher = groupId ? ` {id: "${groupId}"}` : '' let groupCypher if (isMember === true) { groupCypher = ` - MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group:Group) + MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group:Group${groupIdCypher}) RETURN group {.*, myRole: membership.role} ` } else { if (isMember === false) { groupCypher = ` - MATCH (group:Group) + MATCH (group:Group${groupIdCypher}) WHERE NOT (:User {id: $userId})-[:MEMBER_OF]->(group) RETURN group {.*, myRole: NULL} ` } else { groupCypher = ` - MATCH (group:Group) + MATCH (group:Group${groupIdCypher}) OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group) RETURN group {.*, myRole: membership.role} ` @@ -153,6 +154,16 @@ export default { throw new UserInputError('Description too short!') } const session = context.driver.session() + if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) { + const cypherDeletePreviousRelations = ` + MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category) + DELETE previousRelations + RETURN group, category + ` + await session.writeTransaction((transaction) => { + return transaction.run(cypherDeletePreviousRelations, { groupId }) + }) + } const writeTxResultPromise = session.writeTransaction(async (transaction) => { let updateGroupCypher = ` MATCH (group:Group {id: $groupId}) @@ -161,14 +172,6 @@ export default { WITH group ` if (CONFIG.CATEGORIES_ACTIVE && categoryIds && categoryIds.length) { - const cypherDeletePreviousRelations = ` - MATCH (group:Group {id: $groupId})-[previousRelations:CATEGORIZED]->(category:Category) - DELETE previousRelations - RETURN group, category - ` - await session.writeTransaction((transaction) => { - return transaction.run(cypherDeletePreviousRelations, { groupId }) - }) updateGroupCypher += ` UNWIND $categoryIds AS categoryId MATCH (category:Category {id: categoryId}) diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index ddf003659..2616cd606 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -107,6 +107,8 @@ describe('in mode', () => { groupType: 'public', actionRadius: 'regional', categoryIds, + // avatar, // test this as result + // locationName, // test this as result } }) @@ -183,13 +185,50 @@ describe('in mode', () => { 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('with matching amount of categories', () => { + it('has new categories', async () => { + await expect( + mutate({ + mutation: createGroupMutation, + variables: { + ...variables, + categoryIds: ['cat4', 'cat27'], + }, + }), + ).resolves.toMatchObject({ + data: { + CreateGroup: { + categories: expect.arrayContaining([ + expect.objectContaining({ id: 'cat4' }), + expect.objectContaining({ id: 'cat27' }), + ]), + myRole: 'owner', + }, + }, + errors: undefined, + }) + }) + }) + + describe('not even one', () => { + describe('by "categoryIds: null"', () => { + 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('by "categoryIds: []"', () => { + it('throws error: "Too view categories!"', async () => { + const { errors } = await mutate({ + mutation: createGroupMutation, + variables: { ...variables, categoryIds: [] }, + }) + expect(errors[0]).toHaveProperty('message', 'Too view categories!') }) - expect(errors[0]).toHaveProperty('message', 'Too view categories!') }) }) @@ -290,6 +329,25 @@ describe('in mode', () => { }) }) + describe("id = 'my-group'", () => { + it('finds only the group with this id', async () => { + await expect( + query({ query: groupQuery, variables: { id: 'my-group' } }), + ).resolves.toMatchObject({ + data: { + Group: [ + expect.objectContaining({ + id: 'my-group', + slug: 'the-best-group', + myRole: 'owner', + }), + ], + }, + errors: undefined, + }) + }) + }) + describe('isMember = true', () => { it('finds only groups where user is member', async () => { await expect( @@ -2055,7 +2113,7 @@ describe('in mode', () => { }) describe('unauthenticated', () => { - it.only('throws authorization error', async () => { + it('throws authorization error', async () => { const { errors } = await mutate({ query: updateGroupMutation, variables: { @@ -2110,64 +2168,141 @@ describe('in mode', () => { }) }) - describe('query groups', () => { - describe('without any filters', () => { - it('finds all groups', async () => { - await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({ + describe('change group settings', () => { + describe('as owner', () => { + beforeEach(async () => { + authenticatedUser = await user.toJson() + }) + + it('has set the settings', async () => { + await expect( + mutate({ + mutation: updateGroupMutation, + variables: { + id: 'my-group', + name: 'The New Group For Our Coutry', + about: 'We will change the land!', + description: 'Some country relevant description' + descriptionAdditional100, + actionRadius: 'national', + // avatar, // test this as result + // locationName, // test this as result + }, + }), + ).resolves.toMatchObject({ data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', - }), - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), + UpdateGroup: { + id: 'my-group', + name: 'The New Group For Our Coutry', + slug: 'the-new-group-for-our-coutry', // changing the slug is tested in the slugifyMiddleware + about: 'We will change the land!', + description: 'Some country relevant description' + descriptionAdditional100, + actionRadius: 'national', + // avatar, // test this as result + // locationName, // test this as result + myRole: 'owner', + }, }, errors: undefined, }) }) - }) - describe('isMember = true', () => { - it('finds only groups where user is member', async () => { - await expect( - query({ query: groupQuery, variables: { isMember: true } }), - ).resolves.toMatchObject({ - data: { - Group: [ - { - id: 'my-group', - slug: 'the-best-group', - myRole: 'owner', + describe('description', () => { + describe('length without HTML', () => { + describe('less then 100 chars', () => { + it('throws error: "Too view categories!"', async () => { + const { errors } = await mutate({ + mutation: updateGroupMutation, + variables: { + id: 'my-group', + description: + '0123456789' + + '0123456789', + }, + }) + expect(errors[0]).toHaveProperty('message', 'Description too short!') + }) + }) + }) + }) + + describe('categories', () => { + beforeEach(async () => { + CONFIG.CATEGORIES_ACTIVE = true + }) + + describe('with matching amount of categories', () => { + it('has new categories', async () => { + await expect( + mutate({ + mutation: updateGroupMutation, + variables: { + id: 'my-group', + categoryIds: ['cat4', 'cat27'], + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + id: 'my-group', + categories: expect.arrayContaining([ + expect.objectContaining({ id: 'cat4' }), + expect.objectContaining({ id: 'cat27' }), + ]), + myRole: 'owner', + }, }, - ], - }, - errors: undefined, + errors: undefined, + }) + }) + }) + + describe('not even one', () => { + describe('by "categoryIds: []"', () => { + it('throws error: "Too view categories!"', async () => { + const { errors } = await mutate({ + mutation: updateGroupMutation, + variables: { + id: 'my-group', + categoryIds: [], + }, + }) + expect(errors[0]).toHaveProperty('message', 'Too view categories!') + }) + }) + }) + + describe('four', () => { + it('throws error: "Too many categories!"', async () => { + const { errors } = await mutate({ + mutation: updateGroupMutation, + variables: { + id: 'my-group', + categoryIds: ['cat9', 'cat4', 'cat15', 'cat27'], + }, + }) + expect(errors[0]).toHaveProperty('message', 'Too many categories!') + }) }) }) }) - describe('isMember = false', () => { - it('finds only groups where user is not(!) member', async () => { - await expect( - query({ query: groupQuery, variables: { isMember: false } }), - ).resolves.toMatchObject({ - data: { - Group: expect.arrayContaining([ - expect.objectContaining({ - id: 'others-group', - slug: 'uninteresting-group', - myRole: null, - }), - ]), + describe('as no(!) owner', () => { + it('throws authorization error', async () => { + authenticatedUser = await otherUser.toJson() + const { errors } = await mutate({ + mutation: updateGroupMutation, + variables: { + id: 'my-group', + name: 'The New Group For Our Coutry', + about: 'We will change the land!', + description: 'Some country relevant description' + descriptionAdditional100, + actionRadius: 'national', + categoryIds: ['cat4', 'cat27'], // test this as result + // avatar, // test this as result + // locationName, // test this as result }, - errors: undefined, }) + expect(errors[0]).toHaveProperty('message', 'Not Authorized!') }) }) }) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index 70f9ce16f..4dc466e53 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -67,6 +67,9 @@ type Query { updatedAt: String about: String description: String + # groupType: GroupType # test this + # actionRadius: GroupActionRadius # test this + # avatar: ImageInput # test this locationName: String first: Int offset: Int @@ -93,13 +96,13 @@ type Mutation { id: ID name: String! slug: String - avatar: ImageInput about: String description: String! groupType: GroupType! actionRadius: GroupActionRadius! - categoryIds: [ID] - locationName: String + categoryIds: [ID] # test this as result + avatar: ImageInput # test this as result + locationName: String # test this as result ): Group UpdateGroup( @@ -108,10 +111,11 @@ type Mutation { slug: String about: String description: String + # groupType: GroupType # is not possible at the moment and has to be discussed. may be in the stronger direction: public → closed → hidden actionRadius: GroupActionRadius - categoryIds: [ID] - avatar: ImageInput - locationName: String + categoryIds: [ID] # test this as result + avatar: ImageInput # test this as result + locationName: String # test this as result ): Group # DeleteGroup(id: ID!): Group From 999bbd85c1f72f159f8ba53eb026bfa1fcff41f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 2 Sep 2022 08:11:40 +0200 Subject: [PATCH 53/58] Add more slugification tests for 'CreateGroup' and 'UpdateGroup' --- .../src/middleware/slugifyMiddleware.spec.js | 140 +++++++++++++----- 1 file changed, 99 insertions(+), 41 deletions(-) diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 628d20fe6..edb6b64eb 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -209,34 +209,61 @@ describe('slugifyMiddleware', () => { describe('if group exists', () => { describe('if new slug not(!) exists', () => { - it('has the new slug', async () => { - // Wolle: console.log('createGroupResult: ', createGroupResult) - await expect( - mutate({ - mutation: updateGroupMutation, - variables: { - id: createGroupResult.data.CreateGroup.id, - slug: 'my-best-group', + describe('setting slug by group name', () => { + it('has the new slug', async () => { + await expect( + mutate({ + mutation: updateGroupMutation, + variables: { + id: createGroupResult.data.CreateGroup.id, + name: 'My Best Group', + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + name: 'My Best Group', + slug: 'my-best-group', + about: 'Some about', + description: 'Some description' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + myRole: 'owner', + }, }, - }), - ).resolves.toMatchObject({ - data: { - UpdateGroup: { - name: 'The Best Group', - slug: 'my-best-group', - about: 'Some about', - description: 'Some description' + descriptionAdditional100, - groupType: 'closed', - actionRadius: 'national', - myRole: 'owner', + }) + }) + }) + + describe('setting slug explicitly', () => { + it('has the new slug', async () => { + await expect( + mutate({ + mutation: updateGroupMutation, + variables: { + id: createGroupResult.data.CreateGroup.id, + slug: 'my-best-group', + }, + }), + ).resolves.toMatchObject({ + data: { + UpdateGroup: { + name: 'The Best Group', + slug: 'my-best-group', + about: 'Some about', + description: 'Some description' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + myRole: 'owner', + }, }, - }, + }) }) }) }) describe('if new slug exists in another group', () => { - it('rejects UpdateGroup', async (done) => { + beforeEach(async () => { await mutate({ mutation: createGroupMutation, variables: { @@ -249,39 +276,70 @@ describe('slugifyMiddleware', () => { categoryIds, }, }) - try { + }) + + describe('setting slug by group name', () => { + it('has unique slug "*-1"', async () => { await expect( mutate({ mutation: updateGroupMutation, variables: { id: createGroupResult.data.CreateGroup.id, - slug: 'pre-existing-group', + name: 'Pre-Existing Group', }, }), ).resolves.toMatchObject({ - errors: [ - { - message: 'Group with this slug already exists!', + data: { + UpdateGroup: { + name: 'Pre-Existing Group', + slug: 'pre-existing-group-1', + about: 'Some about', + description: 'Some description' + descriptionAdditional100, + groupType: 'closed', + actionRadius: 'national', + myRole: 'owner', }, - ], + }, }) - done() - } catch (error) { - throw new Error(` - ${error} + }) + }) - Probably your database has no unique constraints! + describe('setting slug explicitly', () => { + it('rejects UpdateGroup', async (done) => { + try { + await expect( + mutate({ + mutation: updateGroupMutation, + variables: { + id: createGroupResult.data.CreateGroup.id, + slug: 'pre-existing-group', + }, + }), + ).resolves.toMatchObject({ + errors: [ + { + message: 'Group with this slug already exists!', + }, + ], + }) + done() + } catch (error) { + throw new Error(` + ${error} - To see all constraints go to http://localhost:7474/browser/ and - paste the following: - \`\`\` - CALL db.constraints(); - \`\`\` + Probably your database has no unique constraints! - Learn how to setup the database here: - https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/backend/README.md#database-indices-and-constraints - `) - } + To see all constraints go to http://localhost:7474/browser/ and + paste the following: + \`\`\` + CALL db.constraints(); + \`\`\` + + Learn how to setup the database here: + https://github.com/Ocelot-Social-Community/Ocelot-Social/blob/master/backend/README.md#database-indices-and-constraints + `) + } + }) }) }) }) From bfc60bcbf051176bb41e9dd666223eb0a4e7bbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 2 Sep 2022 08:13:53 +0200 Subject: [PATCH 54/58] Test 'UpdateGroup' with 'categoryIds', remainders --- backend/src/schema/types/type/Group.gql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index 4dc466e53..c7473d6ea 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -100,7 +100,7 @@ type Mutation { description: String! groupType: GroupType! actionRadius: GroupActionRadius! - categoryIds: [ID] # test this as result + categoryIds: [ID] avatar: ImageInput # test this as result locationName: String # test this as result ): Group @@ -113,7 +113,7 @@ type Mutation { description: String # groupType: GroupType # is not possible at the moment and has to be discussed. may be in the stronger direction: public → closed → hidden actionRadius: GroupActionRadius - categoryIds: [ID] # test this as result + categoryIds: [ID] avatar: ImageInput # test this as result locationName: String # test this as result ): Group From e713f65522da23c52447eef3d44963ee84d65f67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 5 Sep 2022 09:01:42 +0200 Subject: [PATCH 55/58] Refine groups testing --- backend/src/db/graphql/groups.js | 5 +---- backend/src/schema/resolvers/groups.js | 6 +++++- backend/src/schema/resolvers/groups.spec.js | 8 +++++++- backend/src/schema/types/type/Group.gql | 2 +- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index fe3153fa2..ddf179572 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -12,7 +12,6 @@ export const createGroupMutation = gql` $groupType: GroupType! $actionRadius: GroupActionRadius! $categoryIds: [ID] - $avatar: ImageInput $locationName: String ) { CreateGroup( @@ -24,7 +23,6 @@ export const createGroupMutation = gql` groupType: $groupType actionRadius: $actionRadius categoryIds: $categoryIds - avatar: $avatar locationName: $locationName ) { id @@ -45,7 +43,6 @@ export const createGroupMutation = gql` name icon } - # avatar # test this as result # locationName # test this as result myRole } @@ -68,11 +65,11 @@ export const updateGroupMutation = gql` id: $id name: $name slug: $slug - avatar: $avatar about: $about description: $description actionRadius: $actionRadius categoryIds: $categoryIds + avatar: $avatar locationName: $locationName ) { id diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 2a3f2fc11..4d97a2c9f 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -5,6 +5,7 @@ import { CATEGORIES_MIN, CATEGORIES_MAX } from '../../constants/categories' import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '../../constants/groups' import { removeHtmlTags } from '../../middleware/helpers/cleanHtml.js' import Resolver from './helpers/Resolver' +import { mergeImage } from './images/images' export default { Query: { @@ -135,7 +136,7 @@ export default { UpdateGroup: async (_parent, params, context, _resolveInfo) => { // Wolle: console.log('UpdateGroup !!!') const { categoryIds } = params - const { id: groupId } = params + const { id: groupId, avatar: avatarInput } = params // Wolle: console.log('categoryIds: ', categoryIds) // Wolle: console.log('groupId: ', groupId) delete params.categoryIds @@ -191,6 +192,9 @@ export default { params, }) const [group] = await transactionResponse.records.map((record) => record.get('group')) + if (avatarInput) { + await mergeImage(group, 'AVATAR_IMAGE', avatarInput, { transaction }) + } return group }) try { diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 2616cd606..068bf5c73 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -17,6 +17,7 @@ const neode = getNeode() let authenticatedUser let user +// Wolle: let groupAvartar const categoryIds = ['cat9', 'cat4', 'cat15'] const descriptionAdditional100 = @@ -73,6 +74,9 @@ const seedBasicsAndClearAuthentication = async () => { icon: 'paw', }), ]) + // Wolle: groupAvartar = await Factory.build('image', { + // url: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg', + // }) authenticatedUser = null } @@ -107,7 +111,6 @@ describe('in mode', () => { groupType: 'public', actionRadius: 'regional', categoryIds, - // avatar, // test this as result // locationName, // test this as result } }) @@ -132,6 +135,9 @@ describe('in mode', () => { name: 'The Best Group', slug: 'the-group', about: 'We will change the world!', + description: 'Some description' + descriptionAdditional100, + groupType: 'public', + actionRadius: 'regional', }, }, errors: undefined, diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index c7473d6ea..c1b097857 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -101,7 +101,7 @@ type Mutation { groupType: GroupType! actionRadius: GroupActionRadius! categoryIds: [ID] - avatar: ImageInput # test this as result + # avatar: ImageInput # a group can not be created with an avatar locationName: String # test this as result ): Group From 92e6cc3f1ba1c519705d2b3e0b4b06d62a18a5a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 5 Sep 2022 09:13:47 +0200 Subject: [PATCH 56/58] Cleanup --- backend/src/middleware/permissionsMiddleware.js | 5 ----- backend/src/schema/resolvers/groups.js | 4 ---- backend/src/schema/resolvers/groups.spec.js | 4 ---- 3 files changed, 13 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 1752d2bdf..f6f675008 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -55,12 +55,9 @@ const isMySocialMedia = rule({ const isAllowedToChangeGroupSettings = rule({ cache: 'no_cache', })(async (_parent, args, { user, driver }) => { - // Wolle: console.log('isAllowedToChangeGroupSettings !!!') if (!(user && user.id)) return false const ownerId = user.id const { id: groupId } = args - // Wolle: console.log('ownerId: ', ownerId) - // Wolle: console.log('groupId: ', groupId) const session = driver.session() const readTxPromise = session.readTransaction(async (transaction) => { const transactionResponse = await transaction.run( @@ -77,8 +74,6 @@ const isAllowedToChangeGroupSettings = rule({ }) try { const { owner, group } = await readTxPromise - // Wolle: console.log('owner: ', owner) - // Wolle: console.log('group: ', group) return !!group && !!owner && ['owner'].includes(owner.myRoleInGroup) } catch (error) { throw new Error(error) diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 4d97a2c9f..2111aa54a 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -134,11 +134,8 @@ export default { } }, UpdateGroup: async (_parent, params, context, _resolveInfo) => { - // Wolle: console.log('UpdateGroup !!!') const { categoryIds } = params const { id: groupId, avatar: avatarInput } = params - // Wolle: console.log('categoryIds: ', categoryIds) - // Wolle: console.log('groupId: ', groupId) delete params.categoryIds if (CONFIG.CATEGORIES_ACTIVE && categoryIds) { if (categoryIds.length < CATEGORIES_MIN) { @@ -184,7 +181,6 @@ export default { OPTIONAL MATCH (:User {id: $userId})-[membership:MEMBER_OF]->(group) RETURN group {.*, myRole: membership.role} ` - // Wolle: console.log('updateGroupCypher: ', updateGroupCypher) const transactionResponse = await transaction.run(updateGroupCypher, { groupId, userId: context.user.id, diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 068bf5c73..4eb713e4c 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -17,7 +17,6 @@ const neode = getNeode() let authenticatedUser let user -// Wolle: let groupAvartar const categoryIds = ['cat9', 'cat4', 'cat15'] const descriptionAdditional100 = @@ -74,9 +73,6 @@ const seedBasicsAndClearAuthentication = async () => { icon: 'paw', }), ]) - // Wolle: groupAvartar = await Factory.build('image', { - // url: 'https://s3.amazonaws.com/uifaces/faces/twitter/jimmuirhead/128.jpg', - // }) authenticatedUser = null } From 5f081cdb0ddde04200593a53b7127cd0c682b745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 5 Sep 2022 09:19:07 +0200 Subject: [PATCH 57/58] Refine test descriptions for groups - Cleanup. --- backend/src/db/graphql/groups.js | 2 -- backend/src/schema/resolvers/groups.spec.js | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index ddf179572..7d1b984b0 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -37,7 +37,6 @@ export const createGroupMutation = gql` groupType actionRadius categories { - # test this as result id slug name @@ -84,7 +83,6 @@ export const updateGroupMutation = gql` groupType actionRadius categories { - # test this as result id slug name diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 4eb713e4c..7ccac01c8 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -166,7 +166,7 @@ describe('in mode', () => { describe('description', () => { describe('length without HTML', () => { describe('less then 100 chars', () => { - it('throws error: "Too view categories!"', async () => { + it('throws error: "Description too short!"', async () => { const { errors } = await mutate({ mutation: createGroupMutation, variables: { @@ -2211,7 +2211,7 @@ describe('in mode', () => { describe('description', () => { describe('length without HTML', () => { describe('less then 100 chars', () => { - it('throws error: "Too view categories!"', async () => { + it('throws error: "Description too short!"', async () => { const { errors } = await mutate({ mutation: updateGroupMutation, variables: { From 685ef857525966f463b1a88f36789cb9aa3f6a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Mon, 5 Sep 2022 09:30:35 +0200 Subject: [PATCH 58/58] Test 'Group' with 'categories' as result - Cleanup. --- backend/src/db/graphql/groups.js | 3 +- backend/src/schema/resolvers/groups.spec.js | 36 +++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/backend/src/db/graphql/groups.js b/backend/src/db/graphql/groups.js index 7d1b984b0..150bb5e9a 100644 --- a/backend/src/db/graphql/groups.js +++ b/backend/src/db/graphql/groups.js @@ -163,12 +163,13 @@ export const groupQuery = gql` actionRadius myRole categories { - # test this as result id slug name icon } + # avatar # test this as result + # locationName # test this as result } } ` diff --git a/backend/src/schema/resolvers/groups.spec.js b/backend/src/schema/resolvers/groups.spec.js index 7ccac01c8..7a588dd8b 100644 --- a/backend/src/schema/resolvers/groups.spec.js +++ b/backend/src/schema/resolvers/groups.spec.js @@ -329,6 +329,42 @@ describe('in mode', () => { errors: undefined, }) }) + + describe('categories', () => { + beforeEach(() => { + CONFIG.CATEGORIES_ACTIVE = true + }) + + it('has set categories', async () => { + await expect(query({ query: groupQuery, variables: {} })).resolves.toMatchObject({ + data: { + Group: expect.arrayContaining([ + expect.objectContaining({ + id: 'my-group', + slug: 'the-best-group', + categories: expect.arrayContaining([ + expect.objectContaining({ id: 'cat4' }), + expect.objectContaining({ id: 'cat9' }), + expect.objectContaining({ id: 'cat15' }), + ]), + myRole: 'owner', + }), + expect.objectContaining({ + id: 'others-group', + slug: 'uninteresting-group', + categories: expect.arrayContaining([ + expect.objectContaining({ id: 'cat4' }), + expect.objectContaining({ id: 'cat9' }), + expect.objectContaining({ id: 'cat15' }), + ]), + myRole: null, + }), + ]), + }, + errors: undefined, + }) + }) + }) }) describe("id = 'my-group'", () => {