From 5e592f666b78ffe12581639c55635426a6d4bb85 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Thu, 28 Feb 2019 15:00:45 -0300 Subject: [PATCH 1/6] Write unit test badge creation - for unauthenticated - for authenticated admin Co-authored-by: Wolfgang Huss --- src/middleware/permissionsMiddleware.js | 3 +- src/resolvers/badges.spec.js | 85 +++++++++++++++++++++++++ src/seed/factories/badges.js | 18 +++--- 3 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 src/resolvers/badges.spec.js diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 0c6723b4b..928137847 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -32,7 +32,8 @@ const permissions = shield({ CreatePost: isAuthenticated, // TODO UpdatePost: isOwner, // TODO DeletePost: isOwner, - report: isAuthenticated + report: isAuthenticated, + CreateBadge: isAuthenticated // addFruitToBasket: isAuthenticated // CreateUser: allow, }, diff --git a/src/resolvers/badges.spec.js b/src/resolvers/badges.spec.js new file mode 100644 index 000000000..abaa85995 --- /dev/null +++ b/src/resolvers/badges.spec.js @@ -0,0 +1,85 @@ +import Factory from '../seed/factories' +import { GraphQLClient } from 'graphql-request' +import { host, login } from '../jest/helpers' + +const factory = Factory() + +describe('report', () => { + beforeEach(async () => { + await factory.create('User', { + email: 'user@example.org', + password: '1234' + }) + await factory.create('User', { + id: 'u2', + name: 'moderator', + role: 'moderator', + email: 'moderator@example.org' + }) + await factory.create('User', { + id: 'u3', + name: 'admin', + role: 'moderator', + email: 'admin@example.org' + }) + }) + + afterEach(async () => { + await factory.cleanDatabase() + }) + + const params = { + id: 'b1', + key: 'indiegogo_en_racoon', + type: 'crowdfunding', + status: 'permanent', + icon: '/img/badges/indiegogo_en_racoon.svg' + } + + describe('unauthenticated', () => { + let client + + it('throws authorization error', async () => { + client = new GraphQLClient(host) + let { id, key, type, status, icon } = params + await expect( + client.request(`mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + }`) + ).rejects.toThrow('Not Authorised') + }) + + describe('authenticated admin', () => { + let headers + let response + let { id, key, type, status, icon } = params + beforeEach(async () => { + headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + response = await client.request(`mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + }`, + { headers } + ) + }) + it('creates a badge', () => { + let { id } = response.CreateBadge + expect(response).toEqual({ + CreateBadge: { id } + }) + }) + }) + }) +}) diff --git a/src/seed/factories/badges.js b/src/seed/factories/badges.js index b34442521..e24a67c21 100644 --- a/src/seed/factories/badges.js +++ b/src/seed/factories/badges.js @@ -10,14 +10,14 @@ export default function (params) { } = params return ` - mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - } + mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + } ` } From ec648113798b91d8bf96ffcc41c164a468b1ff2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 1 Mar 2019 13:17:07 +0100 Subject: [PATCH 2/6] Add to .gitignore `.DS_Store` --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index b909223f8..81a29c8e6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ coverage.lcov .nyc_output/ public/uploads/* !.gitkeep + +# Apple macOS folder attribute file +.DS_Store \ No newline at end of file From 6937c60ef8de53458a8ea09e0ec311594462b217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 1 Mar 2019 15:49:11 +0100 Subject: [PATCH 3/6] Only admins are allowed to create badges --- src/middleware/permissionsMiddleware.js | 10 +++- src/resolvers/badges.spec.js | 79 ++++++++++++++++--------- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index 928137847..d2dc45094 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -16,8 +16,12 @@ const isModerator = rule()(async (parent, args, ctx, info) => { }) */ -const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, ctx, info) => { - return ctx.user.id === parent.id +const isAdmin = rule()(async (parent, args, { user }, info) => { + return user && (user.role === 'admin') +}) + +const isMyOwn = rule({ cache: 'no_cache' })(async (parent, args, context, info) => { + return context.user.id === parent.id }) // Permissions @@ -33,7 +37,7 @@ const permissions = shield({ // TODO UpdatePost: isOwner, // TODO DeletePost: isOwner, report: isAuthenticated, - CreateBadge: isAuthenticated + CreateBadge: isAdmin // addFruitToBasket: isAuthenticated // CreateUser: allow, }, diff --git a/src/resolvers/badges.spec.js b/src/resolvers/badges.spec.js index abaa85995..3574dae6d 100644 --- a/src/resolvers/badges.spec.js +++ b/src/resolvers/badges.spec.js @@ -4,22 +4,21 @@ import { host, login } from '../jest/helpers' const factory = Factory() -describe('report', () => { +describe('Badge', () => { beforeEach(async () => { await factory.create('User', { email: 'user@example.org', + role: 'user', password: '1234' }) await factory.create('User', { id: 'u2', - name: 'moderator', role: 'moderator', email: 'moderator@example.org' }) await factory.create('User', { id: 'u3', - name: 'admin', - role: 'moderator', + role: 'admin', email: 'admin@example.org' }) }) @@ -54,32 +53,56 @@ describe('report', () => { }`) ).rejects.toThrow('Not Authorised') }) + }) - describe('authenticated admin', () => { - let headers - let response - let { id, key, type, status, icon } = params - beforeEach(async () => { - headers = await login({ email: 'admin@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - response = await client.request(`mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - }`, - { headers } - ) - }) - it('creates a badge', () => { - let { id } = response.CreateBadge - expect(response).toEqual({ - CreateBadge: { id } - }) + describe('authenticated admin', () => { + let client + let headers + let response + let { id, key, type, status, icon } = params + beforeEach(async () => { + headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + response = await client.request(`mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + }`, + { headers } + ) + }) + it('creates a badge', () => { + let { id } = response.CreateBadge + expect(response).toEqual({ + CreateBadge: { id } }) }) }) + + describe('authenticated moderator', () => { + let client + let headers + let { id, key, type, status, icon } = params + beforeEach(async () => { + headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('throws authorization error', async () => { + await expect(client.request(`mutation { + CreateBadge( + id: "${id}", + key: "${key}", + type: ${type}, + status: ${status}, + icon: "${icon}" + ) { id } + }`, + { headers } + )).rejects.toThrow('Not Authorised') + }) + }) }) From f25708875a8c75f36f3b88f20f7e302ee53261ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 3 Mar 2019 14:01:50 +0100 Subject: [PATCH 4/6] Refactor badges test CC @Tirokk @grenzfrequence * the top level block should correspond with the name of the resolver * the block below should be `CreatePost` or `UpdatePost` * the arguments of client.request are `query/mutation`, `variables` but you passed in the `headers` which should go into `new GraphQlClient(host, options)` * re-use the very same mutation to avoid bugs in the tests * use `await expect(someAsyncMethod).resolves.toEqual(expected)` style for extra test assurance --- src/resolvers/badges.spec.js | 128 ++++++++++++++++------------------- 1 file changed, 57 insertions(+), 71 deletions(-) diff --git a/src/resolvers/badges.spec.js b/src/resolvers/badges.spec.js index 3574dae6d..f6d8c06dc 100644 --- a/src/resolvers/badges.spec.js +++ b/src/resolvers/badges.spec.js @@ -4,7 +4,7 @@ import { host, login } from '../jest/helpers' const factory = Factory() -describe('Badge', () => { +describe('badges', () => { beforeEach(async () => { await factory.create('User', { email: 'user@example.org', @@ -27,82 +27,68 @@ describe('Badge', () => { await factory.cleanDatabase() }) - const params = { - id: 'b1', - key: 'indiegogo_en_racoon', - type: 'crowdfunding', - status: 'permanent', - icon: '/img/badges/indiegogo_en_racoon.svg' - } + describe('CreateBadge', () => { + const params = { + id: 'b1', + key: 'indiegogo_en_racoon', + type: 'crowdfunding', + status: 'permanent', + icon: '/img/badges/indiegogo_en_racoon.svg' + } - describe('unauthenticated', () => { - let client + const mutation = ` + mutation( + $id: ID + $key: String! + $type: BadgeTypeEnum! + $status: BadgeStatusEnum! + $icon: String! + ) { + CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) { + id + } + } + ` - it('throws authorization error', async () => { - client = new GraphQLClient(host) - let { id, key, type, status, icon } = params - await expect( - client.request(`mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - }`) - ).rejects.toThrow('Not Authorised') + describe('unauthenticated', () => { + let client + + it('throws authorization error', async () => { + client = new GraphQLClient(host) + await expect( + client.request(mutation, params) + ).rejects.toThrow('Not Authorised') + }) }) - }) - describe('authenticated admin', () => { - let client - let headers - let response - let { id, key, type, status, icon } = params - beforeEach(async () => { - headers = await login({ email: 'admin@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - response = await client.request(`mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - }`, - { headers } - ) + describe('authenticated admin', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('creates a badge', async () => { + const expected = { + CreateBadge: { + id: 'b1' + } + } + await expect(client.request(mutation, params)).resolves.toEqual(expected) + }) }) - it('creates a badge', () => { - let { id } = response.CreateBadge - expect(response).toEqual({ - CreateBadge: { id } + + describe('authenticated moderator', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('throws authorization error', async () => { + await expect( + client.request(mutation, params) + ).rejects.toThrow('Not Authorised') }) }) }) - - describe('authenticated moderator', () => { - let client - let headers - let { id, key, type, status, icon } = params - beforeEach(async () => { - headers = await login({ email: 'moderator@example.org', password: '1234' }) - client = new GraphQLClient(host, { headers }) - }) - it('throws authorization error', async () => { - await expect(client.request(`mutation { - CreateBadge( - id: "${id}", - key: "${key}", - type: ${type}, - status: ${status}, - icon: "${icon}" - ) { id } - }`, - { headers } - )).rejects.toThrow('Not Authorised') - }) - }) }) From fb2b407be029f8e08cfaaf21be72cc6c088aff4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 3 Mar 2019 14:32:29 +0100 Subject: [PATCH 5/6] Extend @Tirokk 's test to Create and Update --- src/resolvers/badges.spec.js | 141 +++++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/src/resolvers/badges.spec.js b/src/resolvers/badges.spec.js index f6d8c06dc..e38f54381 100644 --- a/src/resolvers/badges.spec.js +++ b/src/resolvers/badges.spec.js @@ -28,7 +28,7 @@ describe('badges', () => { }) describe('CreateBadge', () => { - const params = { + const variables = { id: 'b1', key: 'indiegogo_en_racoon', type: 'crowdfunding', @@ -45,7 +45,11 @@ describe('badges', () => { $icon: String! ) { CreateBadge(id: $id, key: $key, type: $type, status: $status, icon: $icon) { - id + id, + key, + type, + status, + icon } } ` @@ -56,7 +60,7 @@ describe('badges', () => { it('throws authorization error', async () => { client = new GraphQLClient(host) await expect( - client.request(mutation, params) + client.request(mutation, variables) ).rejects.toThrow('Not Authorised') }) }) @@ -70,10 +74,14 @@ describe('badges', () => { it('creates a badge', async () => { const expected = { CreateBadge: { - id: 'b1' + icon: '/img/badges/indiegogo_en_racoon.svg', + id: 'b1', + key: 'indiegogo_en_racoon', + status: 'permanent', + type: 'crowdfunding' } } - await expect(client.request(mutation, params)).resolves.toEqual(expected) + await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) }) @@ -86,9 +94,130 @@ describe('badges', () => { it('throws authorization error', async () => { await expect( - client.request(mutation, params) + client.request(mutation, variables) ).rejects.toThrow('Not Authorised') }) }) }) + + describe('UpdateBadge', () => { + beforeEach(async () => { + await factory.authenticateAs({ email: 'admin@example.org', password: '1234' }) + await factory.create('Badge', { id: 'b1' }) + }) + const variables = { + id: 'b1', + key: 'whatever' + } + + const mutation = ` + mutation($id: ID!, $key: String!) { + UpdateBadge(id: $id, key: $key) { + id + key + } + } + ` + + describe('unauthenticated', () => { + let client + + it('throws authorization error', async () => { + client = new GraphQLClient(host) + await expect( + client.request(mutation, variables) + ).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated moderator', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('throws authorization error', async () => { + await expect( + client.request(mutation, variables) + ).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated admin', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('updates a badge', async () => { + const expected = { + UpdateBadge: { + id: 'b1', + key: 'whatever' + } + } + await expect(client.request(mutation, variables)).resolves.toEqual(expected) + }) + }) + }) + + describe('DeleteBadge', () => { + beforeEach(async () => { + await factory.authenticateAs({ email: 'admin@example.org', password: '1234' }) + await factory.create('Badge', { id: 'b1' }) + }) + const variables = { + id: 'b1' + } + + const mutation = ` + mutation($id: ID!) { + DeleteBadge(id: $id) { + id + } + } + ` + + describe('unauthenticated', () => { + let client + + it('throws authorization error', async () => { + client = new GraphQLClient(host) + await expect( + client.request(mutation, variables) + ).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated moderator', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'moderator@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + + it('throws authorization error', async () => { + await expect( + client.request(mutation, variables) + ).rejects.toThrow('Not Authorised') + }) + }) + + describe('authenticated admin', () => { + let client + beforeEach(async () => { + const headers = await login({ email: 'admin@example.org', password: '1234' }) + client = new GraphQLClient(host, { headers }) + }) + it('deletes a badge', async () => { + const expected = { + DeleteBadge: { + id: 'b1' + } + } + await expect(client.request(mutation, variables)).resolves.toEqual(expected) + }) + }) + }) }) From 8d1eb6026a8b34ad82c22f6ab8d0c29dae8b4c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 3 Mar 2019 14:35:08 +0100 Subject: [PATCH 6/6] Let all tests pass :green_heart: --- src/middleware/permissionsMiddleware.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/middleware/permissionsMiddleware.js b/src/middleware/permissionsMiddleware.js index d2dc45094..f10b30cb6 100644 --- a/src/middleware/permissionsMiddleware.js +++ b/src/middleware/permissionsMiddleware.js @@ -37,7 +37,9 @@ const permissions = shield({ // TODO UpdatePost: isOwner, // TODO DeletePost: isOwner, report: isAuthenticated, - CreateBadge: isAdmin + CreateBadge: isAdmin, + UpdateBadge: isAdmin, + DeleteBadge: isAdmin // addFruitToBasket: isAuthenticated // CreateUser: allow, },