From 76acebbb738c5510ac73f089e89e9760ad315b85 Mon Sep 17 00:00:00 2001 From: roschaefer Date: Mon, 2 Sep 2019 12:34:21 +0200 Subject: [PATCH] Notifications query filters for deleted resources This should finally fix #1414 --- backend/src/models/Comment.js | 14 ++ backend/src/models/Post.js | 14 ++ backend/src/schema/resolvers/notifications.js | 2 +- .../schema/resolvers/notifications.spec.js | 163 ++++++++++++------ backend/src/seed/factories/categories.js | 23 +-- 5 files changed, 148 insertions(+), 68 deletions(-) diff --git a/backend/src/models/Comment.js b/backend/src/models/Comment.js index 7b130cc79..face77882 100644 --- a/backend/src/models/Comment.js +++ b/backend/src/models/Comment.js @@ -31,4 +31,18 @@ module.exports = { target: 'User', direction: 'in', }, + notified: { + type: 'relationship', + relationship: 'NOTIFIED', + target: 'User', + direction: 'out', + properties: { + read: { type: 'boolean', default: false }, + reason: { + type: 'string', + valid: ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post'], + }, + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + }, + }, } diff --git a/backend/src/models/Post.js b/backend/src/models/Post.js index 7dd62287d..5ac8378c2 100644 --- a/backend/src/models/Post.js +++ b/backend/src/models/Post.js @@ -23,6 +23,20 @@ module.exports = { target: 'User', direction: 'in', }, + notified: { + type: 'relationship', + relationship: 'NOTIFIED', + target: 'User', + direction: 'out', + properties: { + read: { type: 'boolean', default: false }, + reason: { + type: 'string', + valid: ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post'], + }, + createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, + }, + }, createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, updatedAt: { type: 'string', diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js index 65c92b4be..0219df02c 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -44,7 +44,7 @@ export default { try { const cypher = ` - MATCH (resource)-[notification:NOTIFIED]->(user:User {id:$id}) + MATCH (resource {deleted: false, disabled: false})-[notification:NOTIFIED]->(user:User {id:$id}) ${whereClause} RETURN resource, notification, user ${orderByClause} diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index b2ff1c378..280544852 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -1,20 +1,14 @@ import Factory from '../../seed/factories' import { gql } from '../../jest/helpers' -import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' +import { getDriver } from '../../bootstrap/neo4j' import { createTestClient } from 'apollo-server-testing' import createServer from '../.././server' const factory = Factory() -const neode = getNeode() const driver = getDriver() -const userParams = { - id: 'you', - email: 'test@example.org', - password: '1234', -} - let authenticatedUser let user +let author let variables let query let mutate @@ -43,51 +37,81 @@ afterEach(async () => { describe('given some notifications', () => { beforeEach(async () => { - user = await factory.create('User', userParams) - await factory.create('User', { id: 'neighbor' }) - await Promise.all(setupNotifications.map(s => neode.cypher(s))) + const categoryIds = ['cat1'] + author = await factory.create('User', { id: 'author' }) + user = await factory.create('User', { id: 'you' }) + const [neighbor] = await Promise.all([ + factory.create('User', { id: 'neighbor' }), + factory.create('Category', { id: 'cat1' }), + ]) + const [post1, post2, post3] = await Promise.all([ + factory.create('Post', { author, id: 'p1', categoryIds, content: 'Not for you' }), + factory.create('Post', { + author, + id: 'p2', + categoryIds, + content: 'Already seen post mention', + }), + factory.create('Post', { + author, + id: 'p3', + categoryIds, + content: 'You have been mentioned in a post', + }), + ]) + const [comment1, comment2, comment3] = await Promise.all([ + factory.create('Comment', { + author, + postId: 'p3', + id: 'c1', + content: 'You have seen this comment mentioning already', + }), + factory.create('Comment', { + author, + postId: 'p3', + id: 'c2', + content: 'You have been mentioned in a comment', + }), + factory.create('Comment', { + author, + postId: 'p3', + id: 'c3', + content: 'Somebody else was mentioned in a comment', + }), + ]) + await Promise.all([ + post1.relateTo(neighbor, 'notified', { + createdAt: '2019-08-29T17:33:48.651Z', + read: false, + reason: 'mentioned_in_post', + }), + post2.relateTo(user, 'notified', { + createdAt: '2019-08-30T17:33:48.651Z', + read: true, + reason: 'mentioned_in_post', + }), + post3.relateTo(user, 'notified', { + createdAt: '2019-08-31T17:33:48.651Z', + read: false, + reason: 'mentioned_in_post', + }), + comment1.relateTo(user, 'notified', { + createdAt: '2019-08-30T15:33:48.651Z', + read: true, + reason: 'mentioned_in_comment', + }), + comment2.relateTo(user, 'notified', { + createdAt: '2019-08-30T19:33:48.651Z', + read: false, + reason: 'mentioned_in_comment', + }), + comment3.relateTo(neighbor, 'notified', { + createdAt: '2019-09-01T17:33:48.651Z', + read: false, + reason: 'mentioned_in_comment', + }), + ]) }) - const setupNotifications = [ - `MATCH(user:User {id: 'neighbor'}) - MERGE (:Post {id: 'p1', content: 'Not for you'}) - -[:NOTIFIED {createdAt: "2019-08-29T17:33:48.651Z", read: false, reason: "mentioned_in_post"}] - ->(user); - `, - `MATCH(user:User {id: 'you'}) - MERGE (:Post {id: 'p2', content: 'Already seen post mentioning'}) - -[:NOTIFIED {createdAt: "2019-08-30T17:33:48.651Z", read: true, reason: "mentioned_in_post"}] - ->(user); - `, - `MATCH(user:User {id: 'you'}) - MERGE (:Post {id: 'p3', content: 'You have been mentioned in a post'}) - -[:NOTIFIED {createdAt: "2019-08-31T17:33:48.651Z", read: false, reason: "mentioned_in_post"}] - ->(user); - `, - `MATCH(user:User {id: 'you'}) - MATCH(post:Post {id: 'p3'}) - CREATE (comment:Comment {id: 'c1', content: 'You have seen this comment mentioning already'}) - MERGE (comment)-[:COMMENTS]->(post) - MERGE (comment) - -[:NOTIFIED {createdAt: "2019-08-30T15:33:48.651Z", read: true, reason: "mentioned_in_comment"}] - ->(user); - `, - `MATCH(user:User {id: 'you'}) - MATCH(post:Post {id: 'p3'}) - CREATE (comment:Comment {id: 'c2', content: 'You have been mentioned in a comment'}) - MERGE (comment)-[:COMMENTS]->(post) - MERGE (comment) - -[:NOTIFIED {createdAt: "2019-08-30T19:33:48.651Z", read: false, reason: "mentioned_in_comment"}] - ->(user); - `, - `MATCH(user:User {id: 'neighbor'}) - MATCH(post:Post {id: 'p3'}) - CREATE (comment:Comment {id: 'c3', content: 'Somebody else was mentioned in a comment'}) - MERGE (comment)-[:COMMENTS]->(post) - MERGE (comment) - -[:NOTIFIED {createdAt: "2019-09-01T17:33:48.651Z", read: false, reason: "mentioned_in_comment"}] - ->(user); - `, - ] describe('notifications', () => { const notificationQuery = gql` @@ -121,7 +145,7 @@ describe('given some notifications', () => { describe('no filters', () => { it('returns all notifications of current user', async () => { - const expected = expect.objectContaining({ + const expected = { data: { notifications: [ { @@ -135,7 +159,7 @@ describe('given some notifications', () => { { from: { __typename: 'Post', - content: 'Already seen post mentioning', + content: 'Already seen post mention', }, read: true, createdAt: '2019-08-30T17:33:48.651Z', @@ -158,8 +182,10 @@ describe('given some notifications', () => { }, ], }, - }) - await expect(query({ query: notificationQuery, variables })).resolves.toEqual(expected) + } + await expect(query({ query: notificationQuery, variables })).resolves.toMatchObject( + expected, + ) }) }) @@ -191,6 +217,31 @@ describe('given some notifications', () => { query({ query: notificationQuery, variables: { ...variables, read: false } }), ).resolves.toEqual(expected) }) + + describe('if a resource gets deleted', () => { + beforeEach(async () => { + authenticatedUser = await author.toJson() + const deletePostMutation = gql` + mutation($id: ID!) { + DeletePost(id: $id) { + id + deleted + } + } + ` + await expect( + mutate({ mutation: deletePostMutation, variables: { id: 'p3' } }), + ).resolves.toMatchObject({ data: { DeletePost: { id: 'p3', deleted: true } } }) + authenticatedUser = await user.toJson() + }) + + it('reduces notifications list', async () => { + const expected = expect.objectContaining({ data: { notifications: [] } }) + await expect( + query({ query: notificationQuery, variables: { ...variables, read: false } }), + ).resolves.toEqual(expected) + }) + }) }) }) }) diff --git a/backend/src/seed/factories/categories.js b/backend/src/seed/factories/categories.js index 341f1b1fd..d3f5fed21 100644 --- a/backend/src/seed/factories/categories.js +++ b/backend/src/seed/factories/categories.js @@ -1,17 +1,18 @@ import uuid from 'uuid/v4' -export default function(params) { - const { id = uuid(), name, slug, icon } = params - +export default function create() { return { - mutation: ` - mutation($id: ID, $name: String!, $slug: String, $icon: String!) { - CreateCategory(id: $id, name: $name, slug: $slug, icon: $icon) { - id - name + factory: async ({ args, neodeInstance }) => { + const defaults = { + id: uuid(), + icon: 'img/badges/fundraisingbox_de_airship.svg', + name: 'Some category name', } - } - `, - variables: { id, name, slug, icon }, + args = { + ...defaults, + ...args, + } + return neodeInstance.create('Category', args) + }, } }