From 1377455cda0f178d345696f4f09ae2e26c064908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 7 Mar 2019 18:31:54 +0100 Subject: [PATCH 01/12] Disable/enable should return the Resource --- src/resolvers/moderation.spec.js | 20 ++++++++++---------- src/schema.graphql | 9 +++++++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/resolvers/moderation.spec.js b/src/resolvers/moderation.spec.js index c1d4a75fe..005996ec7 100644 --- a/src/resolvers/moderation.spec.js +++ b/src/resolvers/moderation.spec.js @@ -37,7 +37,7 @@ afterEach(async () => { describe('disable', () => { const mutation = ` mutation($id: ID!, $type: ResourceEnum!) { - disable(resource: { id: $id, type: $type }) + disable(resource: { id: $id, type: $type }) { id type } } ` let variables @@ -103,8 +103,8 @@ describe('disable', () => { } }) - it('returns true', async () => { - const expected = { disable: true } + it('returns disabled Resource', async () => { + const expected = { disable: { id: 'c47', type: 'comment' } } await runSetup() await expect(action()).resolves.toEqual(expected) }) @@ -154,8 +154,8 @@ describe('disable', () => { } }) - it('returns true', async () => { - const expected = { disable: true } + it('returns disabled Resource', async () => { + const expected = { disable: { id: 'p9', type: 'contribution' } } await runSetup() await expect(action()).resolves.toEqual(expected) }) @@ -195,7 +195,7 @@ describe('disable', () => { describe('enable', () => { const mutation = ` mutation($id: ID!, $type: ResourceEnum!) { - enable(resource: { id: $id, type: $type }) + enable(resource: { id: $id, type: $type }) { id type } } ` let variables @@ -270,8 +270,8 @@ describe('enable', () => { } }) - it('returns true', async () => { - const expected = { enable: true } + it('returns disabled Resource', async () => { + const expected = { enable: { id: 'c456', type: 'comment' } } await runSetup() await expect(action()).resolves.toEqual(expected) }) @@ -331,8 +331,8 @@ describe('enable', () => { } }) - it('returns true', async () => { - const expected = { enable: true } + it('returns disabled Resource', async () => { + const expected = { enable: { id: 'p9', type: 'contribution' } } await runSetup() await expect(action()).resolves.toEqual(expected) }) diff --git a/src/schema.graphql b/src/schema.graphql index 5e58d5f1d..e284b6c52 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -7,8 +7,8 @@ type Mutation { login(email: String!, password: String!): String! signup(email: String!, password: String!): Boolean! report(resource: Resource!, description: String): Report - disable(resource: Resource!): Boolean! - enable(resource: Resource!): Boolean! + disable(resource: Resource!): ResourcePayload! + enable(resource: Resource!): ResourcePayload! } type Statistics { @@ -32,6 +32,11 @@ input Resource { type: ResourceEnum! } +type ResourcePayload { + id: ID!, + type: ResourceEnum! +} + enum ResourceEnum { contribution comment From 4596041186704065d2c5d5ff332e30b89825e211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 7 Mar 2019 19:21:47 +0100 Subject: [PATCH 02/12] Refactor mutations without Resource type --- src/middleware/softDeleteMiddleware.spec.js | 5 +- src/resolvers/moderation.js | 26 +++++++---- src/resolvers/moderation.spec.js | 52 ++++++++------------- src/schema.graphql | 23 ++------- src/seed/seed-db.js | 9 +--- 5 files changed, 43 insertions(+), 72 deletions(-) diff --git a/src/middleware/softDeleteMiddleware.spec.js b/src/middleware/softDeleteMiddleware.spec.js index e9bc461f1..ed132104e 100644 --- a/src/middleware/softDeleteMiddleware.spec.js +++ b/src/middleware/softDeleteMiddleware.spec.js @@ -22,10 +22,9 @@ beforeEach(async () => { await moderatorFactory.authenticateAs({ email: 'moderator@example.org', password: '1234' }) const disableMutation = ` mutation { - disable(resource: { + disable( id: "p2" - type: contribution - }) + ) } ` await moderatorFactory.mutate(disableMutation) diff --git a/src/resolvers/moderation.js b/src/resolvers/moderation.js index db44790b9..33af83bb6 100644 --- a/src/resolvers/moderation.js +++ b/src/resolvers/moderation.js @@ -1,30 +1,38 @@ export default { Mutation: { disable: async (object, params, { user, driver }) => { - const { resource: { id } } = params + const { id } = params const { id: userId } = user const cypher = ` MATCH (u:User {id: $userId}) - MATCH (r {id: $id}) - SET r.disabled = true - MERGE (r)<-[:DISABLED]-(u) + MATCH (resource {id: $id}) + SET resource.disabled = true + MERGE (resource)<-[:DISABLED]-(u) + RETURN resource {.id} ` const session = driver.session() const res = await session.run(cypher, { id, userId }) + const [resource] = res.records.map((record) => { + return record.get('resource') + }) session.close() - return Boolean(res) + return resource.id }, enable: async (object, params, { user, driver }) => { - const { resource: { id } } = params + const { id } = params const cypher = ` - MATCH (r {id: $id})<-[d:DISABLED]-() - SET r.disabled = false + MATCH (resource {id: $id})<-[d:DISABLED]-() + SET resource.disabled = false DELETE d + RETURN resource {.id} ` const session = driver.session() const res = await session.run(cypher, { id }) + const [resource] = res.records.map((record) => { + return record.get('resource') + }) session.close() - return Boolean(res) + return resource.id } } } diff --git a/src/resolvers/moderation.spec.js b/src/resolvers/moderation.spec.js index 005996ec7..3b2ace1cc 100644 --- a/src/resolvers/moderation.spec.js +++ b/src/resolvers/moderation.spec.js @@ -36,8 +36,8 @@ afterEach(async () => { describe('disable', () => { const mutation = ` - mutation($id: ID!, $type: ResourceEnum!) { - disable(resource: { id: $id, type: $type }) { id type } + mutation($id: ID!) { + disable(id: $id) } ` let variables @@ -45,8 +45,7 @@ describe('disable', () => { beforeEach(() => { // our defaul set of variables variables = { - id: 'blabla', - type: 'contribution' + id: 'blabla' } }) @@ -85,8 +84,7 @@ describe('disable', () => { describe('on a comment', () => { beforeEach(async () => { variables = { - id: 'c47', - type: 'comment' + id: 'c47' } setup.createResource = async () => { @@ -103,8 +101,8 @@ describe('disable', () => { } }) - it('returns disabled Resource', async () => { - const expected = { disable: { id: 'c47', type: 'comment' } } + it('returns disabled resource id', async () => { + const expected = { disable: 'c47' } await runSetup() await expect(action()).resolves.toEqual(expected) }) @@ -141,8 +139,7 @@ describe('disable', () => { describe('on a post', () => { beforeEach(async () => { variables = { - id: 'p9', - type: 'contribution' + id: 'p9' } setup.createResource = async () => { @@ -154,8 +151,8 @@ describe('disable', () => { } }) - it('returns disabled Resource', async () => { - const expected = { disable: { id: 'p9', type: 'contribution' } } + it('returns disabled resource id', async () => { + const expected = { disable: 'p9' } await runSetup() await expect(action()).resolves.toEqual(expected) }) @@ -194,8 +191,8 @@ describe('disable', () => { describe('enable', () => { const mutation = ` - mutation($id: ID!, $type: ResourceEnum!) { - enable(resource: { id: $id, type: $type }) { id type } + mutation($id: ID!) { + enable(id: $id) } ` let variables @@ -207,8 +204,7 @@ describe('enable', () => { beforeEach(() => { // our defaul set of variables variables = { - id: 'blabla', - type: 'contribution' + id: 'blabla' } }) @@ -242,8 +238,7 @@ describe('enable', () => { describe('on a comment', () => { beforeEach(async () => { variables = { - id: 'c456', - type: 'comment' + id: 'c456' } setup.createResource = async () => { @@ -260,18 +255,15 @@ describe('enable', () => { const disableMutation = ` mutation { - disable(resource: { - id: "c456" - type: comment - }) + disable(id: "c456") } ` await factory.mutate(disableMutation) // that's we want to delete } }) - it('returns disabled Resource', async () => { - const expected = { enable: { id: 'c456', type: 'comment' } } + it('returns disabled resource id', async () => { + const expected = { enable: 'c456' } await runSetup() await expect(action()).resolves.toEqual(expected) }) @@ -308,8 +300,7 @@ describe('enable', () => { describe('on a post', () => { beforeEach(async () => { variables = { - id: 'p9', - type: 'contribution' + id: 'p9' } setup.createResource = async () => { @@ -321,18 +312,15 @@ describe('enable', () => { const disableMutation = ` mutation { - disable(resource: { - id: "p9" - type: contribution - }) + disable(id: "p9") } ` await factory.mutate(disableMutation) // that's we want to delete } }) - it('returns disabled Resource', async () => { - const expected = { enable: { id: 'p9', type: 'contribution' } } + it('returns disabled resource id', async () => { + const expected = { enable: 'p9' } await runSetup() await expect(action()).resolves.toEqual(expected) }) diff --git a/src/schema.graphql b/src/schema.graphql index e284b6c52..74d042933 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -6,9 +6,9 @@ type Query { type Mutation { login(email: String!, password: String!): String! signup(email: String!, password: String!): Boolean! - report(resource: Resource!, description: String): Report - disable(resource: Resource!): ResourcePayload! - enable(resource: Resource!): ResourcePayload! + report(id: ID!, description: String): Report + disable(id: ID!): ID! + enable(id: ID!): ID! } type Statistics { @@ -27,22 +27,6 @@ scalar Date scalar Time scalar DateTime -input Resource { - id: ID!, - type: ResourceEnum! -} - -type ResourcePayload { - id: ID!, - type: ResourceEnum! -} - -enum ResourceEnum { - contribution - comment - user -} - enum VisibilityEnum { public friends @@ -178,7 +162,6 @@ type Report { id: ID! reporter: User @relation(name: "REPORTED", direction: "IN") description: String - type: ResourceEnum! createdAt: String comment: Comment @relation(name: "REPORTED", direction: "OUT") contribution: Post @relation(name: "REPORTED", direction: "OUT") diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 310089ef7..5f5b764a4 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -98,14 +98,7 @@ import Factory from './factories' asTick.create('Post', { id: 'p15' }) ]) - const disableMutation = ` - mutation { - disable(resource: { - id: "p11" - type: contribution - }) - } - ` + const disableMutation = 'mutation { disable( id: "p11") }' await asModerator.mutate(disableMutation) await Promise.all([ From 95e9c14cc693bfaaf6cb4830d263c0b92b5b1a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 7 Mar 2019 20:13:39 +0100 Subject: [PATCH 03/12] Refactor report mutation/type to reduce redundancy We don't need to save the type of the reported resource. We can derive it. --- src/resolvers/reports.js | 53 +++++++++++---------------------- src/resolvers/reports.spec.js | 55 +++++++++++++++-------------------- src/schema.graphql | 1 + src/seed/factories/reports.js | 7 ++--- src/seed/seed-db.js | 6 ++-- 5 files changed, 47 insertions(+), 75 deletions(-) diff --git a/src/resolvers/reports.js b/src/resolvers/reports.js index c471d7b7a..79bab6646 100644 --- a/src/resolvers/reports.js +++ b/src/resolvers/reports.js @@ -2,50 +2,31 @@ import uuid from 'uuid/v4' export default { Mutation: { - report: async (parent, { resource, description }, { driver, req, user }, resolveInfo) => { - const contextId = uuid() + report: async (parent, { id, description }, { driver, req, user }, resolveInfo) => { + const reportId = uuid() const session = driver.session() - const data = { - id: contextId, - type: resource.type, + const reportData = { + id: reportId, createdAt: (new Date()).toISOString(), - description: resource.description + description: description } - await session.run( - 'CREATE (r:Report $report) ' + - 'RETURN r.id, r.type, r.description', { - report: data - } - ) - let contentType - - switch (resource.type) { - case 'post': - case 'contribution': - contentType = 'Post' - break - case 'comment': - contentType = 'Comment' - break - case 'user': - contentType = 'User' - break + await session.run(` + MATCH (author:User {id: $userId}) + MATCH (resource {id: $resourceId}) + CREATE (report:Report $reportData) + MERGE (resource)<-[:REPORTED]-(report) + MERGE (report)<-[:REPORTED]-(author) + RETURN report + `, { + resourceId: id, + userId: user.id, + reportData } - - await session.run( - `MATCH (author:User {id: $userId}), (context:${contentType} {id: $resourceId}), (report:Report {id: $contextId}) ` + - 'MERGE (report)<-[:REPORTED]-(author) ' + - 'MERGE (context)<-[:REPORTED]-(report) ' + - 'RETURN context', { - resourceId: resource.id, - userId: user.id, - contextId: contextId - } ) session.close() // TODO: output Report compatible object - return data + return reportData } } } diff --git a/src/resolvers/reports.spec.js b/src/resolvers/reports.spec.js index 253cdadcc..07cfc92eb 100644 --- a/src/resolvers/reports.spec.js +++ b/src/resolvers/reports.spec.js @@ -22,45 +22,38 @@ describe('report', () => { await factory.cleanDatabase() }) + const mutation = ` + mutation { + report( + id: "u2", + description: "I don't like this user" + ) { description } + } + ` + let headers + beforeEach(async () => { + headers = {} + }) + + let client + const action = () => { + client = new GraphQLClient(host, { headers }) + return client.request(mutation) + } + describe('unauthenticated', () => { - let client it('throws authorization error', async () => { - client = new GraphQLClient(host) - await expect( - client.request(`mutation { - report( - description: "I don't like this user", - resource: { - id: "u2", - type: user - } - ) { id, createdAt } - }`) - ).rejects.toThrow('Not Authorised') + await expect(action()).rejects.toThrow('Not Authorised') }) describe('authenticated', () => { - let headers - let response beforeEach(async () => { headers = await login({ email: 'test@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - response = await client.request(`mutation { - report( - description: "I don't like this user", - resource: { - id: "u2", - type: user - } - ) { id, createdAt } - }`, - { headers } - ) }) - it('creates a report', () => { - let { id, createdAt } = response.report - expect(response).toEqual({ - report: { id, createdAt } + + it('creates a report', async () => { + await expect(action()).resolves.toEqual({ + report: { description: 'I don\'t like this user' } }) }) }) diff --git a/src/schema.graphql b/src/schema.graphql index 74d042933..65e11fe19 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -162,6 +162,7 @@ type Report { id: ID! reporter: User @relation(name: "REPORTED", direction: "IN") description: String + type: String! @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]") createdAt: String comment: Comment @relation(name: "REPORTED", direction: "OUT") contribution: Post @relation(name: "REPORTED", direction: "OUT") diff --git a/src/seed/factories/reports.js b/src/seed/factories/reports.js index 4dcd479f1..ccf5299bd 100644 --- a/src/seed/factories/reports.js +++ b/src/seed/factories/reports.js @@ -3,17 +3,14 @@ import faker from 'faker' export default function create (params) { const { description = faker.lorem.sentence(), - resource: { id: resourceId, type } + id } = params return ` mutation { report( description: "${description}", - resource: { - id: "${resourceId}", - type: ${type} - } + id: "${id}", ) { id, createdAt diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 5f5b764a4..b4ecd6d7f 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -173,9 +173,9 @@ import Factory from './factories' ]) await Promise.all([ - asTick.create('Report', { description: 'I don\'t like this comment', resource: { id: 'c1', type: 'comment' } }), - asTrick.create('Report', { description: 'I don\'t like this post', resource: { id: 'p1', type: 'contribution' } }), - asTrack.create('Report', { description: 'I don\'t like this user', resource: { id: 'u1', type: 'user' } }) + asTick.create('Report', { description: 'I don\'t like this comment', id: 'c1' }), + asTrick.create('Report', { description: 'I don\'t like this post', id: 'p1' }), + asTrack.create('Report', { description: 'I don\'t like this user', id: 'u1' }) ]) await Promise.all([ From 61a45d39eafbadddd9642473567dc0b74f280989 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 7 Mar 2019 17:43:00 -0300 Subject: [PATCH 04/12] Respond with reporter, resource --- src/resolvers/reports.js | 21 +++++++++-- src/resolvers/reports.spec.js | 71 ++++++++++++++++++++++++++++------- 2 files changed, 76 insertions(+), 16 deletions(-) diff --git a/src/resolvers/reports.js b/src/resolvers/reports.js index 79bab6646..7c60bb88f 100644 --- a/src/resolvers/reports.js +++ b/src/resolvers/reports.js @@ -10,23 +10,38 @@ export default { createdAt: (new Date()).toISOString(), description: description } - await session.run(` + + const res = await session.run(` MATCH (author:User {id: $userId}) MATCH (resource {id: $resourceId}) CREATE (report:Report $reportData) MERGE (resource)<-[:REPORTED]-(report) MERGE (report)<-[:REPORTED]-(author) - RETURN report + RETURN report, author, resource `, { resourceId: id, userId: user.id, reportData } ) + const [{ report, author, resource }] = res.records.map(r => { + return { + report: r.get('report'), + author: r.get('author'), + resource: r.get('resource') + } + }) session.close() + console.log(report) + console.log(author) // TODO: output Report compatible object - return reportData + return { + ...report.properties, + reporter: author.properties, + user: resource.properties, + type: 'blablabla' + } } } } diff --git a/src/resolvers/reports.spec.js b/src/resolvers/reports.spec.js index 07cfc92eb..eebc335bc 100644 --- a/src/resolvers/reports.spec.js +++ b/src/resolvers/reports.spec.js @@ -5,8 +5,12 @@ import { host, login } from '../jest/helpers' const factory = Factory() describe('report', () => { + let mutation + let headers beforeEach(async () => { + headers = {} await factory.create('User', { + id: 'u1', email: 'test@example.org', password: '1234' }) @@ -16,25 +20,20 @@ describe('report', () => { role: 'user', email: 'abusive-user@example.org' }) + mutation = ` + mutation { + report( + id: "u2", + description: "I don't like this user" + ) { description } + } + ` }) afterEach(async () => { await factory.cleanDatabase() }) - const mutation = ` - mutation { - report( - id: "u2", - description: "I don't like this user" - ) { description } - } - ` - let headers - beforeEach(async () => { - headers = {} - }) - let client const action = () => { client = new GraphQLClient(host, { headers }) @@ -56,6 +55,52 @@ describe('report', () => { report: { description: 'I don\'t like this user' } }) }) + + it('returns the reporter', async () => { + mutation = ` + mutation { + report( + id: "u2", + description: "I don't like this user" + ) { reporter { + email + } } + } + ` + await expect(action()).resolves.toEqual({ + report: { reporter: { email: 'test@example.org' } } + }) + }) + + it('returns type', async () => { + mutation = ` + mutation { + report( + id: "u2", + description: "I don't like this user" + ) { type } + } + ` + await expect(action()).resolves.toEqual({ + report: { type: 'User' } + }) + }) + + it('returns user', async () => { + mutation = ` + mutation { + report( + id: "u2", + description: "I don't like this user" + ) { user { + name + } } + } + ` + await expect(action()).resolves.toEqual({ + report: { user: { name: 'abusive-user' } } + }) + }) }) }) }) From 0529e2b2b06aa2ad98e57d5be2b77d78be6063d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 7 Mar 2019 22:42:32 +0100 Subject: [PATCH 05/12] Refactor `report` mutation, return a proper Report --- src/resolvers/reports.js | 43 ++++++---- src/resolvers/reports.spec.js | 148 ++++++++++++++++++++++------------ src/schema.graphql | 4 +- 3 files changed, 128 insertions(+), 67 deletions(-) diff --git a/src/resolvers/reports.js b/src/resolvers/reports.js index 7c60bb88f..86d2af394 100644 --- a/src/resolvers/reports.js +++ b/src/resolvers/reports.js @@ -12,36 +12,51 @@ export default { } const res = await session.run(` - MATCH (author:User {id: $userId}) + MATCH (submitter:User {id: $userId}) MATCH (resource {id: $resourceId}) CREATE (report:Report $reportData) MERGE (resource)<-[:REPORTED]-(report) - MERGE (report)<-[:REPORTED]-(author) - RETURN report, author, resource + MERGE (report)<-[:REPORTED]-(submitter) + RETURN report, submitter, resource, labels(resource)[0] as type `, { resourceId: id, userId: user.id, reportData } ) - const [{ report, author, resource }] = res.records.map(r => { + session.close() + + const [dbResponse] = res.records.map(r => { return { report: r.get('report'), - author: r.get('author'), - resource: r.get('resource') + submitter: r.get('submitter'), + resource: r.get('resource'), + type: r.get('type') } }) - session.close() - console.log(report) - console.log(author) + if(!dbResponse) return null + const { report, submitter, resource, type } = dbResponse - // TODO: output Report compatible object - return { + let response = { ...report.properties, - reporter: author.properties, - user: resource.properties, - type: 'blablabla' + post: null, + comment: null, + user: null, + submitter: submitter.properties, + type } + switch(type){ + case "Post": + response.post = resource.properties + break; + case "Comment": + response.comment = resource.properties + break; + case "User": + response.user = resource.properties + break; + } + return response } } } diff --git a/src/resolvers/reports.spec.js b/src/resolvers/reports.spec.js index eebc335bc..7e9a55d1e 100644 --- a/src/resolvers/reports.spec.js +++ b/src/resolvers/reports.spec.js @@ -7,6 +7,9 @@ const factory = Factory() describe('report', () => { let mutation let headers + let returnedObject + let variables + beforeEach(async () => { headers = {} await factory.create('User', { @@ -20,14 +23,6 @@ describe('report', () => { role: 'user', email: 'abusive-user@example.org' }) - mutation = ` - mutation { - report( - id: "u2", - description: "I don't like this user" - ) { description } - } - ` }) afterEach(async () => { @@ -36,8 +31,17 @@ describe('report', () => { let client const action = () => { + mutation = ` + mutation($id: ID!) { + report( + id: $id, + description: "Violates code of conduct" + ) ${returnedObject || '{ description }'} + } + ` + variables = variables || { id: 'whatever' } client = new GraphQLClient(host, { headers }) - return client.request(mutation) + return client.request(mutation, variables) } describe('unauthenticated', () => { @@ -50,55 +54,97 @@ describe('report', () => { headers = await login({ email: 'test@example.org', password: '1234' }) }) - it('creates a report', async () => { - await expect(action()).resolves.toEqual({ - report: { description: 'I don\'t like this user' } + describe('invalid resource id', () => { + it('returns null', async () => { + await expect(action()).resolves.toEqual({ + report: null + }) }) }) - it('returns the reporter', async () => { - mutation = ` - mutation { - report( - id: "u2", - description: "I don't like this user" - ) { reporter { - email - } } - } - ` - await expect(action()).resolves.toEqual({ - report: { reporter: { email: 'test@example.org' } } + describe('valid resource id', () => { + beforeEach(async () => { + variables = {id: 'u2'} }) - }) - it('returns type', async () => { - mutation = ` - mutation { - report( - id: "u2", - description: "I don't like this user" - ) { type } - } - ` - await expect(action()).resolves.toEqual({ - report: { type: 'User' } + it('creates a report', async () => { + await expect(action()).resolves.toEqual({ + report: { description: 'Violates code of conduct' } + }) }) - }) - it('returns user', async () => { - mutation = ` - mutation { - report( - id: "u2", - description: "I don't like this user" - ) { user { - name - } } - } - ` - await expect(action()).resolves.toEqual({ - report: { user: { name: 'abusive-user' } } + it('returns the submitter', async () => { + returnedObject = '{ submitter { email } }' + await expect(action()).resolves.toEqual({ + report: { submitter: { email: 'test@example.org' } } + }) + }) + + describe('reported resource is a user', () => { + it('returns type "User"', async () => { + returnedObject = '{ type }' + await expect(action()).resolves.toEqual({ + report: { type: 'User' } + }) + }) + + it('returns resource in user attribute', async () => { + returnedObject = '{ user { name } }' + await expect(action()).resolves.toEqual({ + report: { user: { name: 'abusive-user' } } + }) + }) + }) + + describe('reported resource is a post', () => { + beforeEach(async () => { + await factory.authenticateAs({email: 'test@example.org', password: '1234'}) + await factory.create('Post', {id: 'p23', title: 'Matt and Robert having a pair-programming' }) + variables = { id: 'p23' } + }) + + it('returns type "Post"', async () => { + returnedObject = '{ type }' + await expect(action()).resolves.toEqual({ + report: { type: 'Post' } + }) + }) + + it('returns resource in post attribute', async () => { + returnedObject = '{ post { title } }' + await expect(action()).resolves.toEqual({ + report: { post: { title: 'Matt and Robert having a pair-programming' } } + }) + }) + + it('returns null in user attribute', async () => { + returnedObject = '{ user { name } }' + await expect(action()).resolves.toEqual({ + report: { user: null } + }) + }) + }) + + describe('reported resource is a comment', () => { + beforeEach(async () => { + await factory.authenticateAs({email: 'test@example.org', password: '1234'}) + await factory.create('Comment', {id: 'c34', content: 'Robert getting tired.' }) + variables = { id: 'c34' } + }) + + it('returns type "Comment"', async () => { + returnedObject = '{ type }' + await expect(action()).resolves.toEqual({ + report: { type: 'Comment' } + }) + }) + + it('returns resource in comment attribute', async () => { + returnedObject = '{ comment { content } }' + await expect(action()).resolves.toEqual({ + report: { comment: { content: 'Robert getting tired.' } } + }) + }) }) }) }) diff --git a/src/schema.graphql b/src/schema.graphql index 65e11fe19..a00afd53b 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -160,12 +160,12 @@ type Comment { type Report { id: ID! - reporter: User @relation(name: "REPORTED", direction: "IN") + submitter: User @relation(name: "REPORTED", direction: "IN") description: String type: String! @cypher(statement: "MATCH (resource)<-[:REPORTED]-(this) RETURN labels(resource)[0]") createdAt: String comment: Comment @relation(name: "REPORTED", direction: "OUT") - contribution: Post @relation(name: "REPORTED", direction: "OUT") + post: Post @relation(name: "REPORTED", direction: "OUT") user: User @relation(name: "REPORTED", direction: "OUT") } From f68e289016a903aac53c40ff1c2d60f750b48454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Thu, 7 Mar 2019 22:44:43 +0100 Subject: [PATCH 06/12] Fix lint --- src/resolvers/reports.js | 22 +++++++++++----------- src/resolvers/reports.spec.js | 10 +++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/resolvers/reports.js b/src/resolvers/reports.js index 86d2af394..acac516e0 100644 --- a/src/resolvers/reports.js +++ b/src/resolvers/reports.js @@ -34,7 +34,7 @@ export default { type: r.get('type') } }) - if(!dbResponse) return null + if (!dbResponse) return null const { report, submitter, resource, type } = dbResponse let response = { @@ -45,16 +45,16 @@ export default { submitter: submitter.properties, type } - switch(type){ - case "Post": - response.post = resource.properties - break; - case "Comment": - response.comment = resource.properties - break; - case "User": - response.user = resource.properties - break; + switch (type) { + case 'Post': + response.post = resource.properties + break + case 'Comment': + response.comment = resource.properties + break + case 'User': + response.user = resource.properties + break } return response } diff --git a/src/resolvers/reports.spec.js b/src/resolvers/reports.spec.js index 7e9a55d1e..ac45235ac 100644 --- a/src/resolvers/reports.spec.js +++ b/src/resolvers/reports.spec.js @@ -64,7 +64,7 @@ describe('report', () => { describe('valid resource id', () => { beforeEach(async () => { - variables = {id: 'u2'} + variables = { id: 'u2' } }) it('creates a report', async () => { @@ -98,8 +98,8 @@ describe('report', () => { describe('reported resource is a post', () => { beforeEach(async () => { - await factory.authenticateAs({email: 'test@example.org', password: '1234'}) - await factory.create('Post', {id: 'p23', title: 'Matt and Robert having a pair-programming' }) + await factory.authenticateAs({ email: 'test@example.org', password: '1234' }) + await factory.create('Post', { id: 'p23', title: 'Matt and Robert having a pair-programming' }) variables = { id: 'p23' } }) @@ -127,8 +127,8 @@ describe('report', () => { describe('reported resource is a comment', () => { beforeEach(async () => { - await factory.authenticateAs({email: 'test@example.org', password: '1234'}) - await factory.create('Comment', {id: 'c34', content: 'Robert getting tired.' }) + await factory.authenticateAs({ email: 'test@example.org', password: '1234' }) + await factory.create('Comment', { id: 'c34', content: 'Robert getting tired.' }) variables = { id: 'c34' } }) From 261edef72bb6bdd002e4f6d78d8398b4cbeb4c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 8 Mar 2019 20:55:46 +0100 Subject: [PATCH 07/12] Remove duplicate mutations --- src/schema.graphql | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/schema.graphql b/src/schema.graphql index 16db72819..5d641c0b8 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -12,7 +12,6 @@ type Mutation { report(id: ID!, description: String): Report disable(id: ID!): ID! enable(id: ID!): ID! - report(resource: Resource!, description: String): Report "Shout the given Type and ID" shout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """ MATCH (n {id: $id})<-[:WROTE]-(wu:User), (u:User {id: $cypherParams.currentUserId}) @@ -42,8 +41,6 @@ type Mutation { DELETE r RETURN COUNT(r) > 0 """) - disable(resource: Resource!): Boolean! - enable(resource: Resource!): Boolean! } type Statistics { From cae33301052e37461b099a423836d02958ad4d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 10 Mar 2019 18:29:25 +0100 Subject: [PATCH 08/12] Fix deprecation: async function passed to describe --- src/middleware/permissionsMiddleware.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/permissionsMiddleware.spec.js b/src/middleware/permissionsMiddleware.spec.js index 78766aad2..89904a7bf 100644 --- a/src/middleware/permissionsMiddleware.spec.js +++ b/src/middleware/permissionsMiddleware.spec.js @@ -34,7 +34,7 @@ describe('authorization', () => { return graphQLClient.request('{User(name: "Owner") { email } }') } - describe('not logged in', async () => { + describe('not logged in', () => { it('rejects', async () => { await expect(action()).rejects.toThrow('Not Authorised!') }) From f14c903d2a406a7a1038e0084b1d7fd3f4cc6a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 10 Mar 2019 18:33:51 +0100 Subject: [PATCH 09/12] Refactor: we don't need a setup object --- src/resolvers/moderation.spec.js | 69 ++++++++++++++++---------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/resolvers/moderation.spec.js b/src/resolvers/moderation.spec.js index 3b2ace1cc..4791fd0ee 100644 --- a/src/resolvers/moderation.spec.js +++ b/src/resolvers/moderation.spec.js @@ -14,22 +14,21 @@ const setupAuthenticateClient = (params) => { return authenticateClient } -let setup -const runSetup = async () => { - await setup.createResource() - await setup.authenticateClient() -} +let createResource +let authenticateClient beforeEach(() => { - setup = { - createResource: () => { - }, - authenticateClient: () => { - client = new GraphQLClient(host) - } + createResource = () => {} + authenticateClient = () => { + client = new GraphQLClient(host) } }) +const setup = async () => { + await createResource() + await authenticateClient() +} + afterEach(async () => { await factory.cleanDatabase() }) @@ -54,26 +53,26 @@ describe('disable', () => { } it('throws authorization error', async () => { - await runSetup() + await setup() await expect(action()).rejects.toThrow('Not Authorised') }) describe('authenticated', () => { beforeEach(() => { - setup.authenticateClient = setupAuthenticateClient({ + authenticateClient = setupAuthenticateClient({ email: 'user@example.org', password: '1234' }) }) it('throws authorization error', async () => { - await runSetup() + await setup() await expect(action()).rejects.toThrow('Not Authorised') }) describe('as moderator', () => { beforeEach(() => { - setup.authenticateClient = setupAuthenticateClient({ + authenticateClient = setupAuthenticateClient({ id: 'u7', email: 'moderator@example.org', password: '1234', @@ -87,7 +86,7 @@ describe('disable', () => { id: 'c47' } - setup.createResource = async () => { + createResource = async () => { await factory.create('User', { id: 'u45', email: 'commenter@example.org', password: '1234' }) await factory.authenticateAs({ email: 'commenter@example.org', password: '1234' }) await Promise.all([ @@ -103,7 +102,7 @@ describe('disable', () => { it('returns disabled resource id', async () => { const expected = { disable: 'c47' } - await runSetup() + await setup() await expect(action()).resolves.toEqual(expected) }) @@ -111,7 +110,7 @@ describe('disable', () => { const before = { Comment: [{ id: 'c47', disabledBy: null }] } const expected = { Comment: [{ id: 'c47', disabledBy: { id: 'u7' } }] } - await runSetup() + await setup() await expect(client.request( '{ Comment { id, disabledBy { id } } }' )).resolves.toEqual(before) @@ -125,7 +124,7 @@ describe('disable', () => { const before = { Comment: [ { id: 'c47', disabled: false } ] } const expected = { Comment: [ { id: 'c47', disabled: true } ] } - await runSetup() + await setup() await expect(client.request( '{ Comment { id disabled } }' )).resolves.toEqual(before) @@ -142,7 +141,7 @@ describe('disable', () => { id: 'p9' } - setup.createResource = async () => { + createResource = async () => { await factory.create('User', { email: 'author@example.org', password: '1234' }) await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) await factory.create('Post', { @@ -153,7 +152,7 @@ describe('disable', () => { it('returns disabled resource id', async () => { const expected = { disable: 'p9' } - await runSetup() + await setup() await expect(action()).resolves.toEqual(expected) }) @@ -161,7 +160,7 @@ describe('disable', () => { const before = { Post: [{ id: 'p9', disabledBy: null }] } const expected = { Post: [{ id: 'p9', disabledBy: { id: 'u7' } }] } - await runSetup() + await setup() await expect(client.request( '{ Post { id, disabledBy { id } } }' )).resolves.toEqual(before) @@ -175,7 +174,7 @@ describe('disable', () => { const before = { Post: [ { id: 'p9', disabled: false } ] } const expected = { Post: [ { id: 'p9', disabled: true } ] } - await runSetup() + await setup() await expect(client.request( '{ Post { id disabled } }' )).resolves.toEqual(before) @@ -209,26 +208,26 @@ describe('enable', () => { }) it('throws authorization error', async () => { - await runSetup() + await setup() await expect(action()).rejects.toThrow('Not Authorised') }) describe('authenticated', () => { beforeEach(() => { - setup.authenticateClient = setupAuthenticateClient({ + authenticateClient = setupAuthenticateClient({ email: 'user@example.org', password: '1234' }) }) it('throws authorization error', async () => { - await runSetup() + await setup() await expect(action()).rejects.toThrow('Not Authorised') }) describe('as moderator', () => { beforeEach(async () => { - setup.authenticateClient = setupAuthenticateClient({ + authenticateClient = setupAuthenticateClient({ role: 'moderator', email: 'someUser@example.org', password: '1234' @@ -241,7 +240,7 @@ describe('enable', () => { id: 'c456' } - setup.createResource = async () => { + createResource = async () => { await factory.create('User', { id: 'u123', email: 'author@example.org', password: '1234' }) await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) await Promise.all([ @@ -264,7 +263,7 @@ describe('enable', () => { it('returns disabled resource id', async () => { const expected = { enable: 'c456' } - await runSetup() + await setup() await expect(action()).resolves.toEqual(expected) }) @@ -272,7 +271,7 @@ describe('enable', () => { const before = { Comment: [{ id: 'c456', disabledBy: { id: 'u123' } }] } const expected = { Comment: [{ id: 'c456', disabledBy: null }] } - await runSetup() + await setup() await expect(client.request( '{ Comment(disabled: true) { id, disabledBy { id } } }' )).resolves.toEqual(before) @@ -286,7 +285,7 @@ describe('enable', () => { const before = { Comment: [ { id: 'c456', disabled: true } ] } const expected = { Comment: [ { id: 'c456', disabled: false } ] } - await runSetup() + await setup() await expect(client.request( '{ Comment(disabled: true) { id disabled } }' )).resolves.toEqual(before) @@ -303,7 +302,7 @@ describe('enable', () => { id: 'p9' } - setup.createResource = async () => { + createResource = async () => { await factory.create('User', { id: 'u123', email: 'author@example.org', password: '1234' }) await factory.authenticateAs({ email: 'author@example.org', password: '1234' }) await factory.create('Post', { @@ -321,7 +320,7 @@ describe('enable', () => { it('returns disabled resource id', async () => { const expected = { enable: 'p9' } - await runSetup() + await setup() await expect(action()).resolves.toEqual(expected) }) @@ -329,7 +328,7 @@ describe('enable', () => { const before = { Post: [{ id: 'p9', disabledBy: { id: 'u123' } }] } const expected = { Post: [{ id: 'p9', disabledBy: null }] } - await runSetup() + await setup() await expect(client.request( '{ Post(disabled: true) { id, disabledBy { id } } }' )).resolves.toEqual(before) @@ -343,7 +342,7 @@ describe('enable', () => { const before = { Post: [ { id: 'p9', disabled: true } ] } const expected = { Post: [ { id: 'p9', disabled: false } ] } - await runSetup() + await setup() await expect(client.request( '{ Post(disabled: true) { id disabled } }' )).resolves.toEqual(before) From ee5c4127e6eb50bf605432db77d35b896548b17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 10 Mar 2019 18:49:04 +0100 Subject: [PATCH 10/12] Prevent disabling any type Resource must have a label Post|Comment|User --- src/resolvers/moderation.js | 7 ++++-- src/resolvers/moderation.spec.js | 39 ++++++++++++++++++++++++++++++++ src/schema.graphql | 4 ++-- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/resolvers/moderation.js b/src/resolvers/moderation.js index 33af83bb6..97fe22e9a 100644 --- a/src/resolvers/moderation.js +++ b/src/resolvers/moderation.js @@ -6,16 +6,18 @@ export default { const cypher = ` MATCH (u:User {id: $userId}) MATCH (resource {id: $id}) + WHERE resource:User OR resource:Comment OR resource:Post SET resource.disabled = true MERGE (resource)<-[:DISABLED]-(u) RETURN resource {.id} ` const session = driver.session() const res = await session.run(cypher, { id, userId }) + session.close() const [resource] = res.records.map((record) => { return record.get('resource') }) - session.close() + if(!resource) return null return resource.id }, enable: async (object, params, { user, driver }) => { @@ -28,10 +30,11 @@ export default { ` const session = driver.session() const res = await session.run(cypher, { id }) + session.close() const [resource] = res.records.map((record) => { return record.get('resource') }) - session.close() + if(!resource) return null return resource.id } } diff --git a/src/resolvers/moderation.spec.js b/src/resolvers/moderation.spec.js index 4791fd0ee..0b74287b4 100644 --- a/src/resolvers/moderation.spec.js +++ b/src/resolvers/moderation.spec.js @@ -80,6 +80,25 @@ describe('disable', () => { }) }) + describe('on something that is not a (Comment|Post|User) ', () => { + beforeEach(async () => { + variables = { + id: 't23' + } + createResource = () => { + return Promise.all([ + factory.create('Tag', { id: 't23' }), + ]) + } + }) + + it('returns null', async () => { + const expected = { disable: null } + await setup() + await expect(action()).resolves.toEqual(expected) + }) + }) + describe('on a comment', () => { beforeEach(async () => { variables = { @@ -234,6 +253,26 @@ describe('enable', () => { }) }) + describe('on something that is not a (Comment|Post|User) ', () => { + beforeEach(async () => { + variables = { + id: 't23' + } + createResource = () => { + // we cannot create a :DISABLED relationship here + return Promise.all([ + factory.create('Tag', { id: 't23' }), + ]) + } + }) + + it('returns null', async () => { + const expected = { enable: null } + await setup() + await expect(action()).resolves.toEqual(expected) + }) + }) + describe('on a comment', () => { beforeEach(async () => { variables = { diff --git a/src/schema.graphql b/src/schema.graphql index 5d641c0b8..a542e1229 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -10,8 +10,8 @@ type Mutation { login(email: String!, password: String!): String! signup(email: String!, password: String!): Boolean! report(id: ID!, description: String): Report - disable(id: ID!): ID! - enable(id: ID!): ID! + disable(id: ID!): ID + enable(id: ID!): ID "Shout the given Type and ID" shout(id: ID!, type: ShoutTypeEnum): Boolean! @cypher(statement: """ MATCH (n {id: $id})<-[:WROTE]-(wu:User), (u:User {id: $cypherParams.currentUserId}) From 60d711bb0a6544fd4460b458396a9c56e53d8784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 10 Mar 2019 18:57:45 +0100 Subject: [PATCH 11/12] Prevent to report any type Resouce must have a label (Post|Comment|User) --- src/resolvers/moderation.js | 4 ++-- src/resolvers/moderation.spec.js | 5 ++--- src/resolvers/reports.js | 1 + src/resolvers/reports.spec.js | 16 ++++++++++++++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/resolvers/moderation.js b/src/resolvers/moderation.js index 97fe22e9a..7bc1227ff 100644 --- a/src/resolvers/moderation.js +++ b/src/resolvers/moderation.js @@ -17,7 +17,7 @@ export default { const [resource] = res.records.map((record) => { return record.get('resource') }) - if(!resource) return null + if (!resource) return null return resource.id }, enable: async (object, params, { user, driver }) => { @@ -34,7 +34,7 @@ export default { const [resource] = res.records.map((record) => { return record.get('resource') }) - if(!resource) return null + if (!resource) return null return resource.id } } diff --git a/src/resolvers/moderation.spec.js b/src/resolvers/moderation.spec.js index 0b74287b4..dfbcac80f 100644 --- a/src/resolvers/moderation.spec.js +++ b/src/resolvers/moderation.spec.js @@ -14,7 +14,6 @@ const setupAuthenticateClient = (params) => { return authenticateClient } - let createResource let authenticateClient beforeEach(() => { @@ -87,7 +86,7 @@ describe('disable', () => { } createResource = () => { return Promise.all([ - factory.create('Tag', { id: 't23' }), + factory.create('Tag', { id: 't23' }) ]) } }) @@ -261,7 +260,7 @@ describe('enable', () => { createResource = () => { // we cannot create a :DISABLED relationship here return Promise.all([ - factory.create('Tag', { id: 't23' }), + factory.create('Tag', { id: 't23' }) ]) } }) diff --git a/src/resolvers/reports.js b/src/resolvers/reports.js index acac516e0..fb912a557 100644 --- a/src/resolvers/reports.js +++ b/src/resolvers/reports.js @@ -14,6 +14,7 @@ export default { const res = await session.run(` MATCH (submitter:User {id: $userId}) MATCH (resource {id: $resourceId}) + WHERE resource:User OR resource:Comment OR resource:Post CREATE (report:Report $reportData) MERGE (resource)<-[:REPORTED]-(report) MERGE (report)<-[:REPORTED]-(submitter) diff --git a/src/resolvers/reports.spec.js b/src/resolvers/reports.spec.js index ac45235ac..ae8894572 100644 --- a/src/resolvers/reports.spec.js +++ b/src/resolvers/reports.spec.js @@ -11,6 +11,8 @@ describe('report', () => { let variables beforeEach(async () => { + returnedObject = '{ description }' + variables = { id: 'whatever' } headers = {} await factory.create('User', { id: 'u1', @@ -36,10 +38,9 @@ describe('report', () => { report( id: $id, description: "Violates code of conduct" - ) ${returnedObject || '{ description }'} + ) ${returnedObject} } ` - variables = variables || { id: 'whatever' } client = new GraphQLClient(host, { headers }) return client.request(mutation, variables) } @@ -146,6 +147,17 @@ describe('report', () => { }) }) }) + + describe('reported resource is a tag', () => { + beforeEach(async () => { + await factory.create('Tag', { id: 't23' }) + variables = { id: 't23' } + }) + + it('returns null', async () => { + await expect(action()).resolves.toEqual({ report: null }) + }) + }) }) }) }) From 211693606284f727871d79624278dcc7ca5fecdc Mon Sep 17 00:00:00 2001 From: Grzegorz Leoniec Date: Fri, 8 Mar 2019 16:49:43 +0100 Subject: [PATCH 12/12] Fixed shout and follow seeding --- src/seed/seed-db.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/seed/seed-db.js b/src/seed/seed-db.js index 860ff6707..e8c757db8 100644 --- a/src/seed/seed-db.js +++ b/src/seed/seed-db.js @@ -146,6 +146,26 @@ import Factory from './factories' f.relate('Post', 'Tags', { from: 'p15', to: 't3' }) ]) + await Promise.all([ + asAdmin + .shout({ id: 'p2', type: 'Post' }), + asAdmin + .shout({ id: 'p6', type: 'Post' }), + asModerator + .shout({ id: 'p0', type: 'Post' }), + asModerator + .shout({ id: 'p6', type: 'Post' }), + asUser + .shout({ id: 'p6', type: 'Post' }), + asUser + .shout({ id: 'p7', type: 'Post' }), + asTick + .shout({ id: 'p8', type: 'Post' }), + asTick + .shout({ id: 'p9', type: 'Post' }), + asTrack + .shout({ id: 'p10', type: 'Post' }) + ]) await Promise.all([ asAdmin .shout({ id: 'p2', type: 'Post' }),