diff --git a/backend/src/middleware/permissionsMiddleware.js b/backend/src/middleware/permissionsMiddleware.js index 6f8ff8c2d..b40e7bc99 100644 --- a/backend/src/middleware/permissionsMiddleware.js +++ b/backend/src/middleware/permissionsMiddleware.js @@ -197,7 +197,7 @@ const permissions = shield( RemovePostEmotions: isAuthenticated, block: isAuthenticated, unblock: isAuthenticated, - markAsRead: belongsToMe + markAsRead: belongsToMe, }, User: { email: isMyOwn, diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js index 17b8e7532..6711f6fdd 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -1,36 +1,33 @@ -import { neo4jgraphql } from 'neo4j-graphql-js' - export default { Query: { notifications: async (parent, args, context, resolveInfo) => { - const { user, driver } = context - let session + const { user } = context + const session = context.driver.session() let notifications let whereClause let orderByClause - switch(args.read) { + switch (args.read) { case true: whereClause = 'WHERE notification.read = TRUE' - break; + break case false: whereClause = 'WHERE notification.read = FALSE' - break; + break default: whereClause = '' } - switch(args.orderBy) { + switch (args.orderBy) { case 'createdAt_asc': orderByClause = 'ORDER BY notification.createdAt ASC' - break; + break case 'createdAt_desc': orderByClause = 'ORDER BY notification.createdAt DESC' - break; + break default: orderByClause = '' } try { - session = context.driver.session() const cypher = ` MATCH (resource)-[notification:NOTIFIED]->(user:User {id:$id}) ${whereClause} @@ -44,12 +41,12 @@ export default { ...record.get('notification').properties, from: { __typename: record.get('resource').labels.find(l => resourceTypes.includes(l)), - ...record.get('resource').properties + ...record.get('resource').properties, }, to: { __typename: 'User', ...record.get('user').properties, - } + }, } }) } finally { @@ -61,6 +58,6 @@ export default { Mutation: { markAsRead: async (parent, params, context, resolveInfo) => { return null - } + }, }, } diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index c173eeef1..72a9a8355 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -1,25 +1,41 @@ import { GraphQLClient } from 'graphql-request' import Factory from '../../seed/factories' import { host, login, gql } from '../../jest/helpers' -import { neode } from '../../bootstrap/neo4j' +import { neode as getNeode, getDriver } from '../../bootstrap/neo4j' +import { createTestClient } from 'apollo-server-testing' +import createServer from '../.././server' let client const factory = Factory() -const instance = neode() +const neode = getNeode() +const driver = getDriver() const userParams = { id: 'you', email: 'test@example.org', password: '1234', } -const categoryIds = ['cat9'] + +let authenticatedUser +let variables +let query +let mutate + +beforeAll(() => { + const { server } = createServer({ + context: () => { + return { + driver, + user: authenticatedUser, + } + }, + }) + query = createTestClient(server).query + mutate = createTestClient(server).mutate +}) beforeEach(async () => { - await factory.create('User', userParams) - await instance.create('Category', { - id: 'cat9', - name: 'Democracy & Politics', - icon: 'university', - }) + authenticatedUser = null + variables = { orderBy: 'createdAt_asc' } }) afterEach(async () => { @@ -28,129 +44,132 @@ afterEach(async () => { describe('notifications', () => { const notificationQuery = gql` - query { - notifications { - id + query($read: Boolean, $orderBy: NOTIFIEDOrdering) { + notifications(read: $read, orderBy: $orderBy) { + from { + __typename + ... on Post { + content + } + ... on Comment { + content + } + } + read + createdAt } } ` + 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-31T17: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('unauthenticated', () => { it('throws authorization error', async () => { - client = new GraphQLClient(host) - await expect(client.request(notificationQuery)).rejects.toThrow('Not Authorised') + const result = await query({ query: notificationQuery }) + expect(result.errors[0]).toHaveProperty('message', 'Not Authorised!') }) }) describe('authenticated', () => { - let headers beforeEach(async () => { - headers = await login({ - email: 'test@example.org', - password: '1234', - }) - client = new GraphQLClient(host, { - headers, - }) + const user = await factory.create('User', userParams) + authenticatedUser = await user.toJson() }) describe('given some notifications', () => { beforeEach(async () => { - const neighborParams = { - email: 'neighbor@example.org', - password: '1234', - id: 'neighbor', - } - await Promise.all([ - factory.create('User', neighborParams), - factory.create('Notification', { - id: 'post-mention-not-for-you', - reason: 'mentioned_in_post', - }), - factory.create('Notification', { - id: 'post-mention-already-seen', - read: true, - reason: 'mentioned_in_post', - }), - factory.create('Notification', { - id: 'post-mention-unseen', - reason: 'mentioned_in_post', - }), - factory.create('Notification', { - id: 'comment-mention-not-for-you', - reason: 'mentioned_in_comment', - }), - factory.create('Notification', { - id: 'comment-mention-already-seen', - read: true, - reason: 'mentioned_in_comment', - }), - factory.create('Notification', { - id: 'comment-mention-unseen', - reason: 'mentioned_in_comment', - }), - ]) - await factory.authenticateAs(neighborParams) - await factory.create('Post', { id: 'p1', categoryIds }) - await Promise.all([ - factory.relate('Notification', 'User', { - from: 'post-mention-not-for-you', - to: 'neighbor', - }), - factory.relate('Notification', 'Post', { - from: 'p1', - to: 'post-mention-not-for-you', - }), - factory.relate('Notification', 'User', { - from: 'post-mention-unseen', - to: 'you', - }), - factory.relate('Notification', 'Post', { - from: 'p1', - to: 'post-mention-unseen', - }), - factory.relate('Notification', 'User', { - from: 'post-mention-already-seen', - to: 'you', - }), - factory.relate('Notification', 'Post', { - from: 'p1', - to: 'post-mention-already-seen', - }), - ]) - // Comment and its notifications - await Promise.all([ - factory.create('Comment', { - id: 'c1', - postId: 'p1', - }), - ]) - await Promise.all([ - factory.relate('Notification', 'User', { - from: 'comment-mention-not-for-you', - to: 'neighbor', - }), - factory.relate('Notification', 'Comment', { - from: 'c1', - to: 'comment-mention-not-for-you', - }), - factory.relate('Notification', 'User', { - from: 'comment-mention-unseen', - to: 'you', - }), - factory.relate('Notification', 'Comment', { - from: 'c1', - to: 'comment-mention-unseen', - }), - factory.relate('Notification', 'User', { - from: 'comment-mention-already-seen', - to: 'you', - }), - factory.relate('Notification', 'Comment', { - from: 'c1', - to: 'comment-mention-already-seen', - }), - ]) + await factory.create('User', { id: 'neighbor' }) + await Promise.all(setupNotifications.map(s => neode.cypher(s))) + }) + + describe('no filters', () => { + it('returns all notifications of current user', async () => { + const expected = expect.objectContaining({ + data: { + notifications: [ + { + from: { + __typename: 'Comment', + content: 'You have seen this comment mentioning already', + }, + read: true, + createdAt: '2019-08-30T15:33:48.651Z', + }, + { + from: { + __typename: 'Post', + content: 'Already seen post mentioning', + }, + read: true, + createdAt: '2019-08-30T17:33:48.651Z', + }, + { + from: { + __typename: 'Comment', + content: 'You have been mentioned in a comment', + }, + read: false, + createdAt: '2019-08-31T17:33:48.651Z', + }, + { + from: { + __typename: 'Post', + content: 'You have been mentioned in a post', + }, + read: false, + createdAt: '2019-08-31T17:33:48.651Z', + }, + ], + }, + }) + await expect(query({ query: notificationQuery, variables })).resolves.toEqual(expected) + }) }) describe('filter for read: false', () => { @@ -167,7 +186,7 @@ describe('notifications', () => { } } ` - const variables = { read: false } + it('returns only unread notifications of current user', async () => { const expected = { currentUser: { @@ -194,61 +213,6 @@ describe('notifications', () => { ).resolves.toEqual(expected) }) }) - - describe('no filters', () => { - const queryCurrentUserNotifications = gql` - query { - notifications(orderBy: createdAt_desc) { - id - post { - id - } - comment { - id - } - } - } - ` - it('returns all notifications of current user', async () => { - const expected = { - currentUser: { - notifications: expect.arrayContaining([ - { - id: 'post-mention-unseen', - post: { - id: 'p1', - }, - comment: null, - }, - { - id: 'post-mention-already-seen', - post: { - id: 'p1', - }, - comment: null, - }, - { - id: 'comment-mention-unseen', - comment: { - id: 'c1', - }, - post: null, - }, - { - id: 'comment-mention-already-seen', - comment: { - id: 'c1', - }, - post: null, - }, - ]), - }, - } - await expect(client.request(queryCurrentUserNotifications, variables)).resolves.toEqual( - expected, - ) - }) - }) }) }) })