From a9cd661e63181dc5ea207b2e1e656f720e0b10d0 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 22 Sep 2022 08:17:09 +0200 Subject: [PATCH] add tets for posts in groups --- backend/src/schema/resolvers/groups.js | 1 + backend/src/schema/resolvers/posts.js | 14 +- .../schema/resolvers/postsInGroups.spec.js | 465 ++++++++++++++++++ backend/src/schema/types/type/Group.gql | 8 + 4 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 backend/src/schema/resolvers/postsInGroups.spec.js diff --git a/backend/src/schema/resolvers/groups.js b/backend/src/schema/resolvers/groups.js index babef1d51..8d750284d 100644 --- a/backend/src/schema/resolvers/groups.js +++ b/backend/src/schema/resolvers/groups.js @@ -302,6 +302,7 @@ export default { ...Resolver('Group', { hasMany: { categories: '-[:CATEGORIZED]->(related:Category)', + posts: '<-[:IN]-(related:Post)', }, hasOne: { avatar: '-[:AVATAR_IMAGE]->(related:Image)', diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index 74eca02bd..5bdda96ab 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -17,23 +17,27 @@ const maintainPinnedPosts = (params) => { return params } -const postAccessFilter = (params) => { +const postAccessFilter = (params, user) => { + const { id } = user const groupFilter = { group: { - OR: [{ groupType_in: 'public' }, { myRole_in: ['usual', 'admin', 'owner'] }], + OR: [{ groupType_in: 'public' }, { memberIds_includes: id }], }, } if (isEmpty(params.filter)) { - params.filter = { OR: [groupFilter, {}] } + params.filter = groupFilter } else { if (isEmpty(params.filter.group)) { - params.filter = { OR: [groupFilter, { ...params.filter }] } + // console.log(params.filter) + params.filter = { AND: [groupFilter, { ...params.filter }] } + // console.log(params.filter) } else { params.filter.group = { AND: [{ ...groupFilter.group }, { ...params.filter.group }], } } } + // console.log(params.filter.group) return params } @@ -42,7 +46,7 @@ export default { Post: async (object, params, context, resolveInfo) => { params = await filterForMutedUsers(params, context) params = await maintainPinnedPosts(params) - params = await postAccessFilter(params) + params = await postAccessFilter(params, context.user) return neo4jgraphql(object, params, context, resolveInfo) }, findPosts: async (object, params, context, resolveInfo) => { diff --git a/backend/src/schema/resolvers/postsInGroups.spec.js b/backend/src/schema/resolvers/postsInGroups.spec.js new file mode 100644 index 000000000..51248a024 --- /dev/null +++ b/backend/src/schema/resolvers/postsInGroups.spec.js @@ -0,0 +1,465 @@ +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 { createPostMutation, postQuery } from '../../db/graphql/posts' +// eslint-disable-next-line no-unused-vars +import { DESCRIPTION_WITHOUT_HTML_LENGTH_MIN } from '../../constants/groups' +import CONFIG from '../../config' + +CONFIG.CATEGORIES_ACTIVE = false + +jest.mock('../../constants/groups', () => { + return { + __esModule: true, + DESCRIPTION_WITHOUT_HTML_LENGTH_MIN: 5, + } +}) + +const driver = getDriver() +const neode = getNeode() + +let query +let mutate +let anyUser +let allGroupsUser +let pendingUser +let publicUser +let closedUser +let hiddenUser +let authenticatedUser + +beforeAll(async () => { + await cleanDatabase() + + const { server } = createServer({ + context: () => { + return { + driver, + neode, + user: authenticatedUser, + } + }, + }) + query = createTestClient(server).query + mutate = createTestClient(server).mutate +}) + +afterAll(async () => { + // await cleanDatabase() +}) + +describe('Posts in Groups', () => { + beforeAll(async () => { + anyUser = await Factory.build('user', { + id: 'any-user', + name: 'Any User', + about: 'I am just an ordinary user and do not belong to any group.', + }) + + allGroupsUser = await Factory.build('user', { + id: 'all-groups-user', + name: 'All Groups User', + about: 'I am a member of all groups.', + }) + pendingUser = await Factory.build('user', { + id: 'pending-user', + name: 'Pending User', + about: 'I am a pending member of all groups.', + }) + publicUser = await Factory.build('user', { + id: 'public-user', + name: 'Public User', + about: 'I am the owner of the public group.', + }) + + closedUser = await Factory.build('user', { + id: 'closed-user', + name: 'Private User', + about: 'I am the owner of the closed group.', + }) + + hiddenUser = await Factory.build('user', { + id: 'hidden-user', + name: 'Secret User', + about: 'I am the owner of the hidden group.', + }) + + authenticatedUser = await publicUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'public-group', + name: 'The Public Group', + about: 'The public group!', + description: 'Anyone can see the posts of this group.', + groupType: 'public', + actionRadius: 'regional', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'public-group', + userId: 'pending-user', + roleInGroup: 'pending', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'public-group', + userId: 'all-groups-user', + roleInGroup: 'usual', + }, + }) + authenticatedUser = await closedUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'closed-group', + name: 'The Closed Group', + about: 'The closed group!', + description: 'Only members of this group can see the posts of this group.', + groupType: 'closed', + actionRadius: 'regional', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'closed-group', + userId: 'pending-user', + roleInGroup: 'pending', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'closed-group', + userId: 'all-groups-user', + roleInGroup: 'usual', + }, + }) + authenticatedUser = await hiddenUser.toJson() + await mutate({ + mutation: createGroupMutation, + variables: { + id: 'hidden-group', + name: 'The Hidden Group', + about: 'The hidden group!', + description: 'Only members of this group can see the posts of this group.', + groupType: 'hidden', + actionRadius: 'regional', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'hidden-group', + userId: 'pending-user', + roleInGroup: 'pending', + }, + }) + await mutate({ + mutation: changeGroupMemberRoleMutation, + variables: { + groupId: 'hidden-group', + userId: 'all-groups-user', + roleInGroup: 'usual', + }, + }) + }) + + describe('creating posts in groups', () => { + describe('without membership of group', () => { + beforeEach(async () => { + authenticatedUser = await anyUser.toJson() + }) + + it('throws an error for public groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + id: 'p2', + title: 'A post to a pubic group', + content: 'I am posting into a public group without being a member of the group', + groupId: 'public-group', + }, + }), + ).resolves.toMatchObject({ + errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]), + }) + }) + + it('throws an error for closed groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + id: 'p2', + title: 'A post to a closed group', + content: 'I am posting into a closed group without being a member of the group', + groupId: 'closed-group', + }, + }), + ).resolves.toMatchObject({ + errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]), + }) + }) + + it('throws an error for hidden groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + id: 'p2', + title: 'A post to a closed group', + content: 'I am posting into a hidden group without being a member of the group', + groupId: 'hidden-group', + }, + }), + ).resolves.toMatchObject({ + errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]), + }) + }) + }) + + describe('as a pending member of group', () => { + beforeEach(async () => { + authenticatedUser = await pendingUser.toJson() + }) + + it('throws an error for public groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + id: 'p2', + title: 'A post to a pubic group', + content: 'I am posting into a public group with a pending membership', + groupId: 'public-group', + }, + }), + ).resolves.toMatchObject({ + errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]), + }) + }) + + it('throws an error for closed groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + id: 'p2', + title: 'A post to a closed group', + content: 'I am posting into a closed group with a pending membership', + groupId: 'closed-group', + }, + }), + ).resolves.toMatchObject({ + errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]), + }) + }) + + it('throws an error for hidden groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + id: 'p2', + title: 'A post to a closed group', + content: 'I am posting into a hidden group with a pending membership', + groupId: 'hidden-group', + }, + }), + ).resolves.toMatchObject({ + errors: expect.arrayContaining([expect.objectContaining({ message: 'Not Authorized!' })]), + }) + }) + }) + + describe('as a member of group', () => { + beforeEach(async () => { + authenticatedUser = await allGroupsUser.toJson() + }) + + it('creates a post for public groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + 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', + groupId: 'public-group', + }, + }), + ).resolves.toMatchObject({ + data: { + CreatePost: { + 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', + }, + }, + errors: undefined, + }) + }) + + it('creates a post for closed groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + 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', + groupId: 'closed-group', + }, + }), + ).resolves.toMatchObject({ + data: { + CreatePost: { + 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, + }) + }) + + it('creates a post for hidden groups', async () => { + await expect( + mutate({ + mutation: createPostMutation, + variables: { + 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', + groupId: 'hidden-group', + }, + }), + ).resolves.toMatchObject({ + data: { + CreatePost: { + 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('visibility of posts', () => { + describe('query post by ID', () => { + describe('without membership of group', () => { + beforeEach(async () => { + authenticatedUser = await anyUser.toJson() + }) + + it('shows a post of the public group', async () => { + await expect( + query({ query: postQuery(), variables: { id: 'post-to-public-group' } }), + ).resolves.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', + }, + ]), + }, + errors: undefined, + }) + }) + + it('does not show a post of a closed group', async () => { + await expect( + query({ query: postQuery(), variables: { id: 'post-to-closed-group' } }), + ).resolves.toMatchObject({ + data: { + Post: [], + }, + errors: undefined, + }) + }) + + it('does not show a post of a hidden group', async () => { + await expect( + query({ query: postQuery(), variables: { id: 'post-to-hidden-group' } }), + ).resolves.toMatchObject({ + data: { + Post: [], + }, + errors: undefined, + }) + }) + }) + + describe('as member of group', () => { + beforeEach(async () => { + authenticatedUser = await allGroupsUser.toJson() + }) + + it('shows post of the public group', async () => { + await expect( + query({ query: postQuery(), variables: { id: 'post-to-public-group' } }), + ).resolves.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', + }, + ]), + }, + errors: undefined, + }) + }) + + it('shows post of a closed group', async () => { + await expect( + query({ query: postQuery(), variables: { id: 'post-to-closed-group' } }), + ).resolves.toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + 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, + }) + }) + + it('shows post of a hidden group', async () => { + await expect( + query({ query: postQuery(), variables: { id: 'post-to-hidden-group' } }), + ).resolves.toMatchObject({ + data: { + Post: expect.arrayContaining([ + { + 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, + }) + }) + }) + }) + }) +}) diff --git a/backend/src/schema/types/type/Group.gql b/backend/src/schema/types/type/Group.gql index ef94537fb..bab19307e 100644 --- a/backend/src/schema/types/type/Group.gql +++ b/backend/src/schema/types/type/Group.gql @@ -40,6 +40,13 @@ type Group { myRole: GroupMemberRole # if 'null' then the current user is no member posts: [Post] @relation(name: "IN", direction: "IN") + memberIds: [String] @cypher( + statement: """ + MATCH (this)<-[membership:MEMBER_OF]-(member:User) + WHERE membership.role IN ['usual', 'admin', 'owner'] + RETURN collect(member.id) + """ + ) } @@ -53,6 +60,7 @@ input _GroupFilter { groupType_in: [GroupType!] actionRadius_in: [GroupActionRadius!] myRole_in: [GroupMemberRole!] + memberIds_includes: String id: ID id_not: ID id_in: [ID!]