From 76bfe484768cf9b20b2dced865d5d3e3eb999235 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 5 Oct 2022 11:20:27 +0200 Subject: [PATCH] implement and test post visibilty when leaving or changing the role in a group --- backend/src/schema/resolvers/groups.js | 17 + .../schema/resolvers/postsInGroups.spec.js | 366 +++++++++++++++++- 2 files changed, 382 insertions(+), 1 deletion(-) diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index 7a6298047..a014449b8 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -260,9 +260,12 @@ export default { const writeTxResultPromise = session.writeTransaction(async (transaction) => { const leaveGroupCypher = ` MATCH (member:User {id: $userId})-[membership:MEMBER_OF]->(group:Group {id: $groupId}) + OPTIONAL MATCH (post:Post)-[:IN]->(group) + MERGE (member)-[:CANNOT_SEE]->(post) DELETE membership RETURN member {.*, myRoleInGroup: NULL} ` + const transactionResponse = await transaction.run(leaveGroupCypher, { groupId, userId }) const [member] = await transactionResponse.records.map((record) => record.get('member')) return member @@ -279,8 +282,21 @@ export default { const { groupId, userId, roleInGroup } = params const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async (transaction) => { + let postRestrictionCypher = '' + if (['owner', 'admin', 'usual'].includes(roleInGroup)) { + postRestrictionCypher = ` + OPTIONAL MATCH (member)-[restriction:CANNOT_SEE]->(post:Post)-[:IN]->(group) + DELETE restriction` + } else { + // user becomes pending member + postRestrictionCypher = ` + OPTIONAL MATCH (post:Post)-[:IN]->(group) + MERGE (member)-[:CANNOT_SEE]->(post)` + } + const joinGroupCypher = ` MATCH (member:User {id: $userId}), (group:Group {id: $groupId}) + ${postRestrictionCypher} MERGE (member)-[membership:MEMBER_OF]->(group) ON CREATE SET membership.createdAt = toString(datetime()), @@ -291,6 +307,7 @@ export default { membership.role = $roleInGroup RETURN member {.*, myRoleInGroup: membership.role} ` + const transactionResponse = await transaction.run(joinGroupCypher, { groupId, userId, diff --git a/backend/src/schema/resolvers/postsInGroups.spec.js b/backend/src/schema/resolvers/postsInGroups.spec.js index 5505feece..5617a13a4 100644 --- a/backend/src/schema/resolvers/postsInGroups.spec.js +++ b/backend/src/schema/resolvers/postsInGroups.spec.js @@ -2,7 +2,11 @@ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '../../db/factories' import { getNeode, getDriver } from '../../db/neo4j' import createServer from '../../server' -import { createGroupMutation, changeGroupMemberRoleMutation } from '../../db/graphql/groups' +import { + createGroupMutation, + changeGroupMemberRoleMutation, + leaveGroupMutation, +} from '../../db/graphql/groups' import { createPostMutation, postQuery, @@ -941,4 +945,364 @@ describe('Posts in Groups', () => { }) }) }) + + describe('changes of group membership', () => { + describe('pending member becomes usual member', () => { + describe('of closed group', () => { + beforeAll(async () => { + authenticatedUser = await closedUser.toJson() + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'closed-group', + userId: 'pending-user', + roleInGroup: 'usual', + }, + }) + }) + + beforeEach(async () => { + authenticatedUser = await pendingUser.toJson() + }) + + it('shows the posts of the closed group', async () => { + const result = await query({ query: filterPosts(), variables: {} }) + expect(result.data.Post).toHaveLength(3) + expect(result).toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + id: 'post-to-public-group', + title: 'A post to a public group', + content: 'I am posting into a public group as a member of the group', + }, + { + id: 'post-without-group', + title: 'A post without a group', + content: 'As a new user, I do not belong to a group yet.', + }, + { + id: 'post-to-closed-group', + title: 'A post to a closed group', + content: 'I am posting into a closed group as a member of the group', + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('of hidden group', () => { + beforeAll(async () => { + authenticatedUser = await hiddenUser.toJson() + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'hidden-group', + userId: 'pending-user', + roleInGroup: 'usual', + }, + }) + }) + + beforeEach(async () => { + authenticatedUser = await pendingUser.toJson() + }) + + it('shows all the posts', async () => { + const result = await query({ query: filterPosts(), variables: {} }) + expect(result.data.Post).toHaveLength(4) + expect(result).toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + id: 'post-to-public-group', + title: 'A post to a public group', + content: 'I am posting into a public group as a member of the group', + }, + { + id: 'post-without-group', + title: 'A post without a group', + content: 'As a new user, I do not belong to a group yet.', + }, + { + id: 'post-to-closed-group', + title: 'A post to a closed group', + content: 'I am posting into a closed group as a member of the group', + }, + { + id: 'post-to-hidden-group', + title: 'A post to a hidden group', + content: 'I am posting into a hidden group as a member of the group', + }, + ]), + }, + errors: undefined, + }) + }) + }) + }) + + describe('usual member becomes pending', () => { + describe('of closed group', () => { + beforeAll(async () => { + authenticatedUser = await closedUser.toJson() + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'closed-group', + userId: 'pending-user', + roleInGroup: 'pending', + }, + }) + }) + + beforeEach(async () => { + authenticatedUser = await pendingUser.toJson() + }) + + it('does not shows the posts of the closed group anymore', async () => { + const result = await query({ query: filterPosts(), variables: {} }) + expect(result.data.Post).toHaveLength(3) + expect(result).toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + id: 'post-to-public-group', + title: 'A post to a public group', + content: 'I am posting into a public group as a member of the group', + }, + { + id: 'post-without-group', + title: 'A post without a group', + content: 'As a new user, I do not belong to a group yet.', + }, + { + id: 'post-to-hidden-group', + title: 'A post to a hidden group', + content: 'I am posting into a hidden group as a member of the group', + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('of hidden group', () => { + beforeAll(async () => { + authenticatedUser = await hiddenUser.toJson() + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'hidden-group', + userId: 'pending-user', + roleInGroup: 'pending', + }, + }) + }) + + beforeEach(async () => { + authenticatedUser = await pendingUser.toJson() + }) + + it('shows only the public posts', async () => { + const result = await query({ query: filterPosts(), variables: {} }) + expect(result.data.Post).toHaveLength(2) + expect(result).toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + id: 'post-to-public-group', + title: 'A post to a public group', + content: 'I am posting into a public group as a member of the group', + }, + { + id: 'post-without-group', + title: 'A post without a group', + content: 'As a new user, I do not belong to a group yet.', + }, + ]), + }, + errors: undefined, + }) + }) + }) + }) + + describe('usual member leaves', () => { + describe('closed group', () => { + beforeAll(async () => { + authenticatedUser = await allGroupsUser.toJson() + await mutate({ + mutation: leaveGroupMutation(), + variables: { + groupId: 'closed-group', + userId: 'all-groups-user', + }, + }) + }) + + it('does not shows the posts of the closed group anymore', async () => { + const result = await query({ query: filterPosts(), variables: {} }) + expect(result.data.Post).toHaveLength(3) + expect(result).toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + id: 'post-to-public-group', + title: 'A post to a public group', + content: 'I am posting into a public group as a member of the group', + }, + { + id: 'post-without-group', + title: 'A post without a group', + content: 'As a new user, I do not belong to a group yet.', + }, + { + id: 'post-to-hidden-group', + title: 'A post to a hidden group', + content: 'I am posting into a hidden group as a member of the group', + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('hidden group', () => { + beforeAll(async () => { + authenticatedUser = await allGroupsUser.toJson() + await mutate({ + mutation: leaveGroupMutation(), + variables: { + groupId: 'hidden-group', + userId: 'all-groups-user', + }, + }) + }) + + it('does only show the public posts', async () => { + const result = await query({ query: filterPosts(), variables: {} }) + expect(result.data.Post).toHaveLength(2) + expect(result).toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + id: 'post-to-public-group', + title: 'A post to a public group', + content: 'I am posting into a public group as a member of the group', + }, + { + id: 'post-without-group', + title: 'A post without a group', + content: 'As a new user, I do not belong to a group yet.', + }, + ]), + }, + errors: undefined, + }) + }) + }) + }) + + describe('any user joins', () => { + describe('closed group', () => { + beforeAll(async () => { + authenticatedUser = await closedUser.toJson() + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'closed-group', + userId: 'all-groups-user', + roleInGroup: 'usual', + }, + }) + }) + + beforeEach(async () => { + authenticatedUser = await allGroupsUser.toJson() + }) + + it('does not shows the posts of the closed group', async () => { + const result = await query({ query: filterPosts(), variables: {} }) + expect(result.data.Post).toHaveLength(3) + expect(result).toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + id: 'post-to-public-group', + title: 'A post to a public group', + content: 'I am posting into a public group as a member of the group', + }, + { + id: 'post-without-group', + title: 'A post without a group', + content: 'As a new user, I do not belong to a group yet.', + }, + { + id: 'post-to-closed-group', + title: 'A post to a closed group', + content: 'I am posting into a closed group as a member of the group', + }, + ]), + }, + errors: undefined, + }) + }) + }) + + describe('hidden group', () => { + beforeAll(async () => { + authenticatedUser = await hiddenUser.toJson() + await mutate({ + mutation: changeGroupMemberRoleMutation(), + variables: { + groupId: 'hidden-group', + userId: 'all-groups-user', + roleInGroup: 'usual', + }, + }) + }) + + beforeEach(async () => { + authenticatedUser = await allGroupsUser.toJson() + }) + + it('shows all posts', async () => { + const result = await query({ query: filterPosts(), variables: {} }) + expect(result.data.Post).toHaveLength(4) + expect(result).toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + id: 'post-to-public-group', + title: 'A post to a public group', + content: 'I am posting into a public group as a member of the group', + }, + { + id: 'post-without-group', + title: 'A post without a group', + content: 'As a new user, I do not belong to a group yet.', + }, + { + id: 'post-to-closed-group', + title: 'A post to a closed group', + content: 'I am posting into a closed group as a member of the group', + }, + { + id: 'post-to-hidden-group', + title: 'A post to a hidden group', + content: 'I am posting into a hidden group as a member of the group', + }, + ]), + }, + errors: undefined, + }) + }) + }) + }) + }) })