diff --git a/backend/src/middleware/permissionsMiddleware.spec.js b/backend/src/middleware/permissionsMiddleware.spec.js index 3c307348d..7e5245ef9 100644 --- a/backend/src/middleware/permissionsMiddleware.spec.js +++ b/backend/src/middleware/permissionsMiddleware.spec.js @@ -9,14 +9,6 @@ const driver = getDriver() let query, authenticatedUser, owner, anotherRegularUser, administrator, variables, moderator -const userQuery = gql` - query($name: String) { - User(name: $name) { - email - } - } -` - describe('authorization', () => { beforeAll(async () => { await cleanDatabase() @@ -30,7 +22,11 @@ describe('authorization', () => { query = createTestClient(server).query }) - describe('given two existing users', () => { + afterEach(async () => { + await cleanDatabase() + }) + + describe('given an owner, an other user, an admin, a moderator', () => { beforeEach(async () => { ;[owner, anotherRegularUser, administrator, moderator] = await Promise.all([ Factory.build( @@ -79,15 +75,20 @@ describe('authorization', () => { variables = {} }) - afterEach(async () => { - await cleanDatabase() - }) - describe('access email address', () => { + const userQuery = gql` + query($name: String) { + User(name: $name) { + email + } + } + ` + describe('unauthenticated', () => { beforeEach(() => { authenticatedUser = null }) + it("throws an error and does not expose the owner's email address", async () => { await expect( query({ query: userQuery, variables: { name: 'Owner' } }), @@ -143,7 +144,7 @@ describe('authorization', () => { }) }) - describe('administrator', () => { + describe('as an administrator', () => { beforeEach(async () => { authenticatedUser = await administrator.toJson() }) @@ -158,5 +159,97 @@ describe('authorization', () => { }) }) }) + + // Wolle describe('access reports protected propertied', () => { + // const reportsQuery = gql ` + // query { + // reports { + // id + // createdAt + // updatedAt + // rule + // disable + // closed + // filed + // reviewed + // resource + // } + // } + // ` + + // describe('unauthenticated', () => { + // beforeEach(() => { + // authenticatedUser = null + // }) + // it("throws an error and does not expose the owner's email address", async () => { + // await expect( + // query({ query: reportsQuery, variables: { name: 'Owner' } }), + // ).resolves.toMatchObject({ + // errors: [{ message: 'Not Authorised!' }], + // data: { User: [null] }, + // }) + // }) + // }) + + // describe('authenticated', () => { + // describe('as the owner', () => { + // beforeEach(async () => { + // authenticatedUser = await owner.toJson() + // }) + + // it("exposes the owner's email address", async () => { + // variables = { name: 'Owner' } + // await expect(query({ query: reportsQuery, variables })).resolves.toMatchObject({ + // data: { User: [{ email: 'owner@example.org' }] }, + // errors: undefined, + // }) + // }) + // }) + + // describe('as another regular user', () => { + // beforeEach(async () => { + // authenticatedUser = await anotherRegularUser.toJson() + // }) + + // it("throws an error and does not expose the owner's email address", async () => { + // await expect( + // query({ query: reportsQuery, variables: { name: 'Owner' } }), + // ).resolves.toMatchObject({ + // errors: [{ message: 'Not Authorised!' }], + // data: { User: [null] }, + // }) + // }) + // }) + + // describe('as a moderator', () => { + // beforeEach(async () => { + // authenticatedUser = await moderator.toJson() + // }) + + // it("throws an error and does not expose the owner's email address", async () => { + // await expect( + // query({ query: reportsQuery, variables: { name: 'Owner' } }), + // ).resolves.toMatchObject({ + // errors: [{ message: 'Not Authorised!' }], + // data: { User: [null] }, + // }) + // }) + // }) + + // describe('as an administrator', () => { + // beforeEach(async () => { + // authenticatedUser = await administrator.toJson() + // }) + + // it("exposes the owner's email address", async () => { + // variables = { name: 'Owner' } + // await expect(query({ query: reportsQuery, variables })).resolves.toMatchObject({ + // data: { User: [{ email: 'owner@example.org' }] }, + // errors: undefined, + // }) + // }) + // }) + // }) + // }) }) }) diff --git a/backend/src/schema/resolvers/reports.js b/backend/src/schema/resolvers/reports.js index 0565c4d8a..d68f60f36 100644 --- a/backend/src/schema/resolvers/reports.js +++ b/backend/src/schema/resolvers/reports.js @@ -1,14 +1,14 @@ import log from './helpers/databaseLogger' -const transformReturnType = record => { - return { - ...record.get('report').properties, - resource: { - __typename: record.get('type'), - ...record.get('resource').properties, - }, - } -} +// Wolle const transformReturnType = record => { +// return { +// ...record.get('report').properties, +// resource: { +// __typename: record.get('type'), +// ...record.get('resource').properties, +// }, +// } +// } export default { Mutation: { @@ -27,7 +27,8 @@ export default { WITH submitter, resource, report CREATE (report)<-[filed:FILED {createdAt: $createdAt, reasonCategory: $reasonCategory, reasonDescription: $reasonDescription}]-(submitter) - RETURN report, resource, labels(resource)[0] AS type + WITH report, resource {.*, __typename: labels(resource)[0]} AS finalResource + RETURN {reportId: report.id, resource: properties(finalResource)} AS filedReport `, { resourceId, @@ -38,13 +39,18 @@ export default { }, ) log(reportTransactionResponse) - return reportTransactionResponse.records.map(transformReturnType) + // Wolle return reportTransactionResponse.records.map(transformReturnType) + return reportTransactionResponse.records.map(record => + record.get('filedReport'), + ) }) try { const [createdRelationshipWithNestedAttributes] = await reportWriteTxResultPromise + console.log('createdRelationshipWithNestedAttributes: ', createdRelationshipWithNestedAttributes) if (!createdRelationshipWithNestedAttributes) return null return createdRelationshipWithNestedAttributes } finally { + console.log('session.close !!!') session.close() } }, diff --git a/backend/src/schema/resolvers/reports.spec.js b/backend/src/schema/resolvers/reports.spec.js index 0e690c19e..e411a2046 100644 --- a/backend/src/schema/resolvers/reports.spec.js +++ b/backend/src/schema/resolvers/reports.spec.js @@ -10,22 +10,54 @@ const driver = getDriver() describe('file a report on a resource', () => { let authenticatedUser, currentUser, mutate, query, moderator, abusiveUser, otherReportingUser const categoryIds = ['cat9'] - const reportMutation = gql` + // Wolle const reportMutation = gql` + // mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) { + // fileReport( + // resourceId: $resourceId + // reasonCategory: $reasonCategory + // reasonDescription: $reasonDescription + // ) { + // id + // createdAt + // updatedAt + // closed + // rule + // resource { + // __typename + // ... on User { + // name + // } + // ... on Post { + // title + // } + // ... on Comment { + // content + // } + // } + // filed { + // submitter { + // id + // } + // createdAt + // reasonCategory + // reasonDescription + // } + // } + // } + // ` + const fileReportMutation = gql` mutation($resourceId: ID!, $reasonCategory: ReasonCategory!, $reasonDescription: String!) { fileReport( resourceId: $resourceId reasonCategory: $reasonCategory reasonDescription: $reasonDescription ) { - id - createdAt - updatedAt - closed - rule + reportId resource { __typename ... on User { name + # Wolle filedUnclosedReportByCurrentUser } ... on Post { title @@ -34,6 +66,35 @@ describe('file a report on a resource', () => { content } } + } + } + ` + const variables = { + resourceId: 'invalid', + reasonCategory: 'other', + reasonDescription: 'Violates code of conduct !!!', + } + const reportsQuery = gql` + query { + reports(orderBy: createdAt_desc) { + id + createdAt + updatedAt + closed + resource { + __typename + ... on User { + id + # Wolle FOLLOWSfiledUnclosedReportByCurrentUser + followedByCurrentUser + } + ... on Post { + id + } + ... on Comment { + id + } + } filed { submitter { id @@ -45,11 +106,6 @@ describe('file a report on a resource', () => { } } ` - const variables = { - resourceId: 'whatever', - reasonCategory: 'other', - reasonDescription: 'Violates code of conduct !!!', - } beforeAll(async () => { await cleanDatabase() @@ -74,7 +130,7 @@ describe('file a report on a resource', () => { describe('unauthenticated', () => { it('throws authorization error', async () => { authenticatedUser = null - await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({ + await expect(mutate({ mutation: fileReportMutation, variables })).resolves.toMatchObject({ data: { fileReport: null }, errors: [{ message: 'Not Authorised!' }], }) @@ -127,7 +183,7 @@ describe('file a report on a resource', () => { describe('invalid resource id', () => { it('returns null', async () => { - await expect(mutate({ mutation: reportMutation, variables })).resolves.toMatchObject({ + await expect(mutate({ mutation: fileReportMutation, variables })).resolves.toMatchObject({ data: { fileReport: null }, errors: undefined, }) @@ -136,16 +192,20 @@ describe('file a report on a resource', () => { describe('valid resource', () => { describe('creates report', () => { - it('which belongs to resource', async () => { + it.only('which belongs to resource now reported by current user', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id' }, }), ).resolves.toMatchObject({ data: { fileReport: { - id: expect.any(String), + reportId: expect.any(String), + resource: { + name: 'abusive-user', + // Wolle filedUnclosedReportByCurrentUser: true, + }, }, }, errors: undefined, @@ -154,12 +214,12 @@ describe('file a report on a resource', () => { it('creates only one report for multiple reports on the same resource', async () => { const firstReport = await mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id' }, }) authenticatedUser = await otherReportingUser.toJson() const secondReport = await mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id' }, }) expect(firstReport.data.fileReport.id).toEqual(secondReport.data.fileReport.id) @@ -168,7 +228,7 @@ describe('file a report on a resource', () => { it('returns the rule for how the report was decided', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id' }, }), ).resolves.toMatchObject({ @@ -187,7 +247,7 @@ describe('file a report on a resource', () => { it('returns __typename "User"', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id' }, }), ).resolves.toMatchObject({ @@ -205,7 +265,7 @@ describe('file a report on a resource', () => { it('returns user attribute info', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id' }, }), ).resolves.toMatchObject({ @@ -224,7 +284,7 @@ describe('file a report on a resource', () => { it('returns the submitter', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id' }, }), ).resolves.toMatchObject({ @@ -246,7 +306,7 @@ describe('file a report on a resource', () => { it('returns a date', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id' }, }), ).resolves.toMatchObject({ @@ -262,7 +322,7 @@ describe('file a report on a resource', () => { it('returns the reason category', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id', @@ -286,7 +346,7 @@ describe('file a report on a resource', () => { it('gives an error if the reason category is not in enum "ReasonCategory"', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id', @@ -307,7 +367,7 @@ describe('file a report on a resource', () => { it('returns the reason description', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id', @@ -331,7 +391,7 @@ describe('file a report on a resource', () => { it('sanitizes the reason description', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'abusive-user-id', @@ -371,7 +431,7 @@ describe('file a report on a resource', () => { it('returns type "Post"', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'post-to-report-id', @@ -392,7 +452,7 @@ describe('file a report on a resource', () => { it('returns resource in post attribute', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'post-to-report-id', @@ -442,7 +502,7 @@ describe('file a report on a resource', () => { it('returns type "Comment"', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'comment-to-report-id', @@ -463,7 +523,7 @@ describe('file a report on a resource', () => { it('returns resource in comment attribute', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'comment-to-report-id', @@ -493,7 +553,7 @@ describe('file a report on a resource', () => { it('returns null', async () => { await expect( mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { ...variables, resourceId: 'tag-to-report-id', @@ -510,37 +570,6 @@ describe('file a report on a resource', () => { }) describe('query for reported resource', () => { - const reportsQuery = gql` - query { - reports(orderBy: createdAt_desc) { - id - createdAt - updatedAt - closed - resource { - __typename - ... on User { - id - } - ... on Post { - id - } - ... on Comment { - id - } - } - filed { - submitter { - id - } - createdAt - reasonCategory - reasonDescription - } - } - } - ` - beforeEach(async () => { authenticatedUser = null moderator = await Factory.build( @@ -632,7 +661,7 @@ describe('file a report on a resource', () => { authenticatedUser = await currentUser.toJson() await Promise.all([ mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { resourceId: 'abusive-post-1', reasonCategory: 'other', @@ -640,7 +669,7 @@ describe('file a report on a resource', () => { }, }), mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { resourceId: 'abusive-comment-1', reasonCategory: 'discrimination_etc', @@ -648,7 +677,7 @@ describe('file a report on a resource', () => { }, }), mutate({ - mutation: reportMutation, + mutation: fileReportMutation, variables: { resourceId: 'abusive-user-1', reasonCategory: 'doxing', @@ -678,7 +707,7 @@ describe('file a report on a resource', () => { }) }) - it('role "moderator" gets reports', async () => { + it.only('role "moderator" gets reports', async () => { const expected = { reports: expect.arrayContaining([ expect.objectContaining({ @@ -689,6 +718,8 @@ describe('file a report on a resource', () => { resource: { __typename: 'User', id: 'abusive-user-1', + // Wolle filedUnclosedReportByCurrentUser: false, + followedByCurrentUser: false, }, filed: expect.arrayContaining([ expect.objectContaining({ diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index cbdc683e8..6d8041716 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -251,12 +251,14 @@ export default { boolean: { followedByCurrentUser: 'MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', + filedUnclosedReportByCurrentUser: + 'MATCH (this)<-[:BELONGS_TO]-(:Report {closed: false})<-[:FILED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', + isBlocked: + 'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', blocked: 'MATCH (this)-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', isMuted: 'MATCH (this)<-[:MUTED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', - isBlocked: - 'MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1', }, count: { contributionsCount: diff --git a/backend/src/schema/types/type/Report.gql b/backend/src/schema/types/type/Report.gql index ad0015d01..ae8c640fb 100644 --- a/backend/src/schema/types/type/Report.gql +++ b/backend/src/schema/types/type/Report.gql @@ -10,6 +10,11 @@ type Report { resource: ReportedResource } +type FiledReport { + reportId: ID! + resource: ReportedResource +} + union ReportedResource = User | Post | Comment enum ReportRule { @@ -17,7 +22,7 @@ enum ReportRule { } type Mutation { - fileReport(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): Report + fileReport(resourceId: ID!, reasonCategory: ReasonCategory!, reasonDescription: String!): FiledReport } type Query { diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index baefc9d29..4f07a7ebc 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -64,10 +64,19 @@ type User { # Is the currently logged in user following that user? followedByCurrentUser: Boolean! @cypher( statement: """ - MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId}) + MATCH (this)<-[:FOLLOWS]-(u:User { id: $cypherParams.currentUserId}) RETURN COUNT(u) >= 1 """ ) + + # Has the currently logged in user reported that user? + filedUnclosedReportByCurrentUser: Boolean! @cypher( + statement: """ + MATCH (this)<-[:BELONGS_TO]-(:Report {closed: false})<-[:FILED]-(u:User { id: $cypherParams.currentUserId}) + RETURN COUNT(u) >= 1 + """ + ) + isBlocked: Boolean! @cypher( statement: """ MATCH (this)<-[:BLOCKED]-(user:User {id: $cypherParams.currentUserId})