From 0007533b8c8df92534e6bd75c6c2ce326fccfd78 Mon Sep 17 00:00:00 2001 From: mattwr18 Date: Wed, 16 Oct 2019 01:20:17 +0200 Subject: [PATCH] Add tests that admin can pin anyone's post/limit 1 pinned post --- .../src/middleware/permissionsMiddleware.js | 4 +- backend/src/schema/resolvers/posts.js | 42 ++-- backend/src/schema/resolvers/posts.spec.js | 180 ++++++++++++++---- 3 files changed, 169 insertions(+), 57 deletions(-) diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 9eda4da3b..690835d91 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -148,8 +148,8 @@ const permissions = shield( SignupVerification: allow, CreateInvitationCode: and(isAuthenticated, or(not(invitationLimitReached), isAdmin)), UpdateUser: onlyYourself, - CreatePost: or(and(isAuthenticated, not(pinnedPost)), isAdmin), - UpdatePost: isAuthor, + CreatePost: isAuthenticated, + UpdatePost: or(and(isAuthor, not(pinnedPost)), isAdmin), DeletePost: isAuthor, report: isAuthenticated, CreateSocialMedia: isAuthenticated, diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index 79d3105af..4e7f21c40 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -75,29 +75,19 @@ export default { }, Mutation: { CreatePost: async (_parent, params, context, _resolveInfo) => { - const { categoryIds, pinned } = params - delete params.pinned + const { categoryIds } = params delete params.categoryIds params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) params.id = params.id || uuid() let post - let createPostCypher = `CREATE (post:Post {params}) + const createPostCypher = `CREATE (post:Post {params}) SET post.createdAt = toString(datetime()) SET post.updatedAt = toString(datetime()) WITH post MATCH (author:User {id: $userId}) MERGE (post)<-[:WROTE]-(author) - WITH post, author` - - if (pinned) { - createPostCypher += ` - MERGE (post)<-[:PINNED]-(author) - WITH post - ` - } - - createPostCypher += ` + WITH post, author UNWIND $categoryIds AS categoryId MATCH (category:Category {id: categoryId}) MERGE (post)-[:CATEGORIZED]->(category) @@ -123,14 +113,16 @@ export default { return post }, UpdatePost: async (_parent, params, context, _resolveInfo) => { - const { categoryIds } = params + const { categoryIds, pinned } = params + const { id: userId } = context.user + delete params.pinned delete params.categoryIds params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) const session = context.driver.session() - let updatePostCypher = `MATCH (post:Post {id: $params.id}) SET post += $params SET post.updatedAt = toString(datetime()) + WITH post ` if (categoryIds && categoryIds.length) { @@ -143,15 +135,31 @@ export default { await session.run(cypherDeletePreviousRelations, { params }) updatePostCypher += ` - WITH post UNWIND $categoryIds AS categoryId MATCH (category:Category {id: categoryId}) MERGE (post)-[:CATEGORIZED]->(category) + WITH post ` } + if (pinned) { + const cypherDeletePreviousRelations = ` + MATCH ()-[previousRelations:PINNED]->(post:Post) + DELETE previousRelations + RETURN post + ` + + await session.run(cypherDeletePreviousRelations) + + updatePostCypher += ` + MATCH (user:User {id: $userId}) WHERE user.role = 'admin' + MERGE (user)-[:PINNED]->(post) + WITH post + ` + } + updatePostCypher += `RETURN post` - const updatePostVariables = { categoryIds, params } + const updatePostVariables = { categoryIds, params, userId } const transactionRes = await session.run(updatePostCypher, updatePostVariables) const [post] = transactionRes.records.map(record => { diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js index fd970c1b1..f41d442a4 100644 --- a/backend/src/schema/resolvers/posts.spec.js +++ b/backend/src/schema/resolvers/posts.spec.js @@ -17,13 +17,7 @@ const categoryIds = ['cat9', 'cat4', 'cat15'] let variables const createPostMutation = gql` - mutation( - $id: ID - $title: String! - $content: String! - $language: String - $categoryIds: [ID] - ) { + mutation($id: ID, $title: String!, $content: String!, $language: String, $categoryIds: [ID]) { CreatePost( id: $id title: $title @@ -380,9 +374,25 @@ describe('UpdatePost', () => { let author, newlyCreatedPost const updatePostMutation = gql` mutation($id: ID!, $title: String!, $content: String!, $categoryIds: [ID], $pinned: Boolean) { - UpdatePost(id: $id, title: $title, content: $content, categoryIds: $categoryIds, pinned: $pinned) { + UpdatePost( + id: $id + title: $title + content: $content + categoryIds: $categoryIds + pinned: $pinned + ) { id + title content + author { + name + slug + } + pinnedBy { + id + name + role + } categories { id } @@ -568,13 +578,13 @@ describe('UpdatePost', () => { }) }) }) - + describe('pinned posts', () => { beforeEach(async () => { - variables = { ...variables, categoryIds: ['cat9', 'cat27', 'cat15'], pinned: true } + variables = { ...variables, pinned: true } }) - describe('users cannot pin posts on update', () => { - it.only('throws authorization error', async () => { + describe('users cannot pin posts', () => { + it('throws authorization error', async () => { await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ errors: [{ message: 'Not Authorised!' }], data: { UpdatePost: null }, @@ -582,7 +592,7 @@ describe('UpdatePost', () => { }) }) - describe('moderator cannot create pinned posts', () => { + describe('moderator cannot pin posts', () => { let moderator beforeEach(async () => { moderator = await user.update({ role: 'moderator', updatedAt: new Date().toISOString() }) @@ -590,14 +600,14 @@ describe('UpdatePost', () => { }) it('throws authorization error', async () => { - await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject({ + await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject({ errors: [{ message: 'Not Authorised!' }], - data: { CreatePost: null }, + data: { UpdatePost: null }, }) }) }) - describe('admin can create pinned posts', () => { + describe('admin can pin posts', () => { let admin beforeEach(async () => { admin = await user.update({ @@ -605,37 +615,131 @@ describe('UpdatePost', () => { name: 'Admin', updatedAt: new Date().toISOString(), }) - authenticatedUser = await admin.toJson() variables = { ...variables, title: 'pinned post', - content: 'this is super important for the community and we promise not to have too many', + content: 'this is super important for the community', } + authenticatedUser = await admin.toJson() }) - it('throws authorization error', async () => { - const expected = { - data: { - CreatePost: { - title: 'pinned post', - content: - 'this is super important for the community and we promise not to have too many', - author: { - name: 'Admin', - }, - pinnedBy: { - id: 'current-user', - name: 'Admin', - role: 'admin', + describe('post created by them', () => { + beforeEach(async () => { + await factory.create('Post', { + id: 'created-and-pinned-by-same-admin', + author: admin, + }) + }) + + it('responds with the updated Post', async () => { + variables = { ...variables, id: 'created-and-pinned-by-same-admin' } + const expected = { + data: { + UpdatePost: { + title: 'pinned post', + content: 'this is super important for the community', + author: { + name: 'Admin', + }, + pinnedBy: { + id: 'current-user', + name: 'Admin', + role: 'admin', + }, }, }, - }, - errors: undefined, - } + errors: undefined, + } - await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject( - expected, - ) + await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + }) + + describe('post created by another admin', () => { + let otherAdmin + beforeEach(async () => { + otherAdmin = await factory.create('User', { + role: 'admin', + name: 'otherAdmin', + }) + authenticatedUser = await otherAdmin.toJson() + await factory.create('Post', { + id: 'created-by-one-admin-pinned-by-different-one', + author: otherAdmin, + }) + }) + + it('responds with the updated Post', async () => { + authenticatedUser = await admin.toJson() + variables = { ...variables, id: 'created-by-one-admin-pinned-by-different-one' } + const expected = { + data: { + UpdatePost: { + title: 'pinned post', + content: 'this is super important for the community', + author: { + name: 'otherAdmin', + }, + pinnedBy: { + id: 'current-user', + name: 'Admin', + role: 'admin', + }, + }, + }, + errors: undefined, + } + + await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + }) + + describe('post created by another user', () => { + it('responds with the updated Post', async () => { + const expected = { + data: { + UpdatePost: { + title: 'pinned post', + content: 'this is super important for the community', + author: { + slug: 'the-author', + }, + pinnedBy: { + id: 'current-user', + name: 'Admin', + role: 'admin', + }, + }, + }, + errors: undefined, + } + + await expect(mutate({ mutation: updatePostMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + }) + + describe('removes other pinned post', () => { + beforeEach(async () => { + await factory.create('Post', { + id: 'only-pinned-post', + author: admin, + }) + }) + + it('leaves only one pinned post at a time', async () => { + expect.assertions(1) + await mutate({ mutation: updatePostMutation, variables }) + variables = { ...variables, id: 'only-pinned-post' } + await mutate({ mutation: updatePostMutation, variables }) + const pinnedPosts = await neode.cypher(`MATCH ()-[:PINNED]->(post:Post) RETURN post`) + expect(pinnedPosts.records).toHaveLength(1) + }) }) }) })