From 3d2df56141aa67af3172f21fdf28e02c994b825d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 21 Aug 2019 14:27:32 +0200 Subject: [PATCH] Backend tests --- .../handleNotificationsMiddleware.js | 65 +++-- .../handleNotificationsMiddleware.spec.js | 257 ++++++++++++++---- backend/src/models/Notification.js | 3 +- .../schema/types/enum/ReasonNotification.gql | 5 + .../src/schema/types/type/Notification.gql | 2 +- .../Notification/Notification.vue | 2 +- 6 files changed, 254 insertions(+), 80 deletions(-) create mode 100644 backend/src/schema/types/enum/ReasonNotification.gql diff --git a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js index d04eeeece..723dbdc05 100644 --- a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js @@ -6,33 +6,56 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => { if (!idsOfUsers.length) return // Done here, because Neode validation is not working. - const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_your_post'] + const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_post'] if (!reasonsAllowed.includes(reason)) { throw new UserInputError('Notification reason is not allowed!') } + if ( + (label === 'Post' && reason !== 'mentioned_in_post') || + (label === 'Comment' && !['mentioned_in_comment', 'comment_on_post'].includes(reason)) + ) { + throw new UserInputError('Notification fits not to reason!') + } const session = context.driver.session() const createdAt = new Date().toISOString() let cypher - if (label === 'Post') { - cypher = ` - MATCH (post: Post { id: $id })<-[:WROTE]-(author: User) - MATCH (user: User) - WHERE user.id in $idsOfUsers - AND NOT (user)<-[:BLOCKED]-(author) - CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) - MERGE (post)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) - ` - } else { - cypher = ` - MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) - MATCH (user: User) - WHERE user.id in $idsOfUsers - AND NOT (user)<-[:BLOCKED]-(author) - AND NOT (user)<-[:BLOCKED]-(postAuthor) - CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) - MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) - ` + switch (reason) { + case 'mentioned_in_post': { + cypher = ` + MATCH (post: Post { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfUsers + AND NOT (user)<-[:BLOCKED]-(author) + CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) + MERGE (post)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) + ` + break + } + case 'mentioned_in_comment': { + cypher = ` + MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfUsers + AND NOT (user)<-[:BLOCKED]-(author) + AND NOT (user)<-[:BLOCKED]-(postAuthor) + CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) + MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) + ` + break + } + case 'comment_on_post': { + cypher = ` + MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfUsers + AND NOT (user)<-[:BLOCKED]-(author) + AND NOT (author)<-[:BLOCKED]-(user) + CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) + MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) + ` + break + } } await session.run(cypher, { label, @@ -116,7 +139,7 @@ const handleCreateComment = async (resolve, root, args, context, resolveInfo) => return record.get('user') }) if (context.user.id !== postAuthor.id) { - await notifyUsers('Comment', comment.id, [postAuthor.id], 'comment_on_your_post', context) + await notifyUsers('Comment', comment.id, [postAuthor.id], 'comment_on_post', context) } } diff --git a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js index cb6aa9844..f5d2c61c8 100644 --- a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js @@ -63,16 +63,17 @@ describe('notifications', () => { describe('authenticated', () => { beforeEach(async () => { - authenticatedUser = user + authenticatedUser = await user.toJson() }) describe('given another user', () => { - let title - let content + let postTitle + let postContent + let postAuthor const createPostAction = async () => { const createPostMutation = gql` - mutation($id: ID, $title: String!, $content: String!) { - CreatePost(id: $id, title: $title, content: $content) { + mutation($id: ID, $postTitle: String!, $postContent: String!) { + CreatePost(id: $id, title: $postTitle, content: $postContent) { id title content @@ -84,38 +85,154 @@ describe('notifications', () => { mutation: createPostMutation, variables: { id: 'p47', - title, - content, + postTitle, + postContent, }, }) authenticatedUser = await user.toJson() } - let postAuthor - beforeEach(async () => { - postAuthor = await instance.create('User', { - email: 'post-author@example.org', - password: '1234', - id: 'postAuthor', + let commentContent + let commentAuthor + const createCommentOnPostAction = async () => { + await createPostAction() + const createCommentMutation = gql` + mutation($id: ID, $postId: ID!, $commentContent: String!) { + CreateComment(id: $id, postId: $postId, content: $commentContent) { + id + content + } + } + ` + authenticatedUser = await commentAuthor.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'c47', + postId: 'p47', + commentContent, + }, + }) + authenticatedUser = await user.toJson() + } + + describe('comments on my post', () => { + beforeEach(async () => { + postTitle = 'My post' + postContent = 'My post content.' + postAuthor = user + }) + + describe('commenter is not me', () => { + beforeEach(async () => { + commentContent = 'Commenters comment.' + commentAuthor = await instance.create('User', { + id: 'commentAuthor', + name: 'Mrs Comment', + slug: 'mrs-comment', + email: 'commentauthor@example.org', + password: '1234', + }) + }) + + it('sends me a notification', async () => { + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [ + { + read: false, + reason: 'comment_on_post', + post: null, + comment: { + content: commentContent, + }, + }, + ], + }, + }, + }) + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + + it('sends me no notification if I block the comment author', async () => { + await user.relateTo(commentAuthor, 'blocked') + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [], + }, + }, + }) + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + }) + + describe('commenter is me', () => { + beforeEach(async () => { + commentContent = 'My comment.' + commentAuthor = user + }) + + it('sends me no notification', async () => { + await user.relateTo(commentAuthor, 'blocked') + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [], + }, + }, + }) + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) }) }) - describe('comments on my post', () => { - title = 'My post' - content = 'My content' - - // it('sends me a notification', async () => { - // await createPostAction() - // XXX - // }) + beforeEach(async () => { + postAuthor = await instance.create('User', { + id: 'postAuthor', + name: 'Mrs Post', + slug: 'mrs-post', + email: 'post-author@example.org', + password: '1234', + }) }) describe('mentions me in a post', () => { - title = 'Mentioning Al Capone' - content = - 'Hey @al-capone how do you do?' + beforeEach(async () => { + postTitle = 'Mentioning Al Capone' + postContent = + 'Hey @al-capone how do you do?' + }) - it('sends you a notification', async () => { + it('sends me a notification', async () => { await createPostAction() const expectedContent = 'Hey @al-capone how do you do?' @@ -163,8 +280,8 @@ describe('notifications', () => { ` const updatePostMutation = gql` - mutation($id: ID!, $title: String!, $content: String!) { - UpdatePost(id: $id, content: $content, title: $title) { + mutation($id: ID!, $postTitle: String!, $postContent: String!) { + UpdatePost(id: $id, content: $postContent, title: $postTitle) { title content } @@ -175,8 +292,8 @@ describe('notifications', () => { mutation: updatePostMutation, variables: { id: 'p47', - title, - content: updatedContent, + postTitle, + postContent: updatedContent, }, }) authenticatedUser = await user.toJson() @@ -247,39 +364,67 @@ describe('notifications', () => { ).resolves.toEqual(expected) }) }) + }) - describe('but the author of the post blocked me and a mentioner mentions me in a comment', () => { - const createCommentOnPostAction = async () => { - await createPostAction() - const createCommentMutation = gql` - mutation($id: ID, $postId: ID!, $commentContent: String!) { - CreateComment(id: $id, postId: $postId, content: $commentContent) { - id - content - } - } - ` - authenticatedUser = await commentMentioner.toJson() - await mutate({ - mutation: createCommentMutation, - variables: { - id: 'c47', - postId: 'p47', - commentContent: - 'One mention of me with .', + describe('mentions me in a comment', () => { + beforeEach(async () => { + postTitle = 'Post where I get mentioned in a comment' + postContent = 'Content of post where I get mentioned in a comment.' + }) + + describe('I am not blocked at all', () => { + beforeEach(async () => { + commentContent = + 'One mention about me with @al-capone.' + commentAuthor = await instance.create('User', { + id: 'commentAuthor', + name: 'Mrs Comment', + slug: 'mrs-comment', + email: 'comment-author@example.org', + password: '1234', + }) + }) + + it('sends a notification', async () => { + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [ + { + read: false, + reason: 'mentioned_in_comment', + post: null, + comment: { + content: commentContent, + }, + }, + ], + }, }, }) - authenticatedUser = await user.toJson() - } - let commentMentioner + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + }) + describe('but the author of the post blocked me', () => { beforeEach(async () => { await postAuthor.relateTo(user, 'blocked') - commentMentioner = await instance.create('User', { - id: 'mentioner', - name: 'Mr Mentioner', - slug: 'mr-mentioner', - email: 'mentioner@example.org', + commentContent = + 'One mention about me with @al-capone.' + commentAuthor = await instance.create('User', { + id: 'commentAuthor', + name: 'Mrs Comment', + slug: 'mrs-comment', + email: 'comment-author@example.org', password: '1234', }) }) diff --git a/backend/src/models/Notification.js b/backend/src/models/Notification.js index 93c4cd6cb..b54a99574 100644 --- a/backend/src/models/Notification.js +++ b/backend/src/models/Notification.js @@ -12,7 +12,8 @@ module.exports = { }, reason: { type: 'string', - valid: ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_your_post'], + valid: ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_post'], + invalid: [null], default: 'mentioned_in_post', }, createdAt: { diff --git a/backend/src/schema/types/enum/ReasonNotification.gql b/backend/src/schema/types/enum/ReasonNotification.gql new file mode 100644 index 000000000..a66c446be --- /dev/null +++ b/backend/src/schema/types/enum/ReasonNotification.gql @@ -0,0 +1,5 @@ +enum ReasonNotification { + mentioned_in_post + mentioned_in_comment + comment_on_post +} \ No newline at end of file diff --git a/backend/src/schema/types/type/Notification.gql b/backend/src/schema/types/type/Notification.gql index a03b86769..a3543445f 100644 --- a/backend/src/schema/types/type/Notification.gql +++ b/backend/src/schema/types/type/Notification.gql @@ -1,7 +1,7 @@ type Notification { id: ID! read: Boolean - reason: String + reason: ReasonNotification createdAt: String user: User @relation(name: "NOTIFIED", direction: "OUT") post: Post @relation(name: "NOTIFIED", direction: "IN") diff --git a/webapp/components/notifications/Notification/Notification.vue b/webapp/components/notifications/Notification/Notification.vue index 1aa930fc7..8afde5793 100644 --- a/webapp/components/notifications/Notification/Notification.vue +++ b/webapp/components/notifications/Notification/Notification.vue @@ -56,7 +56,7 @@ export default { notificationTextIdents: { mentioned_in_post: 'notifications.menu.mentionedInPost', mentioned_in_comment: 'notifications.menu.mentionedInComment', - comment_on_your_post: 'notifications.menu.commentedOnPost', + comment_on_post: 'notifications.menu.commentedOnPost', }, } },