From 1b9303ceaff99435ae8b0ef06bf0204f95facb93 Mon Sep 17 00:00:00 2001 From: ogerly Date: Tue, 3 Sep 2019 09:46:18 +0200 Subject: [PATCH] Jest tests for the Terms and Conditions in frontend and backend --- backend/src/models/User.js | 6 +- backend/src/schema/resolvers/registration.js | 9 ++- .../src/schema/resolvers/registration.spec.js | 43 +++++++--- backend/src/schema/resolvers/users.js | 13 ++- backend/src/schema/resolvers/users.spec.js | 79 +++++++++++++------ .../users/termsAndConditions.spec.js | 24 ------ .../src/schema/types/type/EmailAddress.gql | 2 +- backend/src/seed/factories/users.js | 2 +- webapp/locales/de.json | 2 +- webapp/locales/en.json | 2 +- webapp/pages/terms-and-conditions-confirm.vue | 2 +- 11 files changed, 118 insertions(+), 66 deletions(-) delete mode 100644 backend/src/schema/resolvers/users/termsAndConditions.spec.js diff --git a/backend/src/models/User.js b/backend/src/models/User.js index c082a761e..5b239b5e8 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -87,10 +87,10 @@ module.exports = { type: 'string', allow: [null], }, - termsAndConditionsAgreedAt: { + /*termsAndConditionsAgreedAt: { type: 'string', isoDate: true, allow: [null], - /* required: true, TODO */ - }, + // required: true, TODO + },*/ } diff --git a/backend/src/schema/resolvers/registration.js b/backend/src/schema/resolvers/registration.js index 20c54a49b..c4fd73889 100644 --- a/backend/src/schema/resolvers/registration.js +++ b/backend/src/schema/resolvers/registration.js @@ -1,4 +1,4 @@ -import { UserInputError } from 'apollo-server' +import { ForbiddenError, UserInputError } from 'apollo-server' import uuid from 'uuid/v4' import { neode } from '../../bootstrap/neo4j' import fileUpload from './fileUpload' @@ -77,6 +77,13 @@ export default { } }, SignupVerification: async (object, args, context, resolveInfo) => { + + let { termsAndConditionsAgreedVersion } = args + const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g) + if (!regEx.test(termsAndConditionsAgreedVersion)) { + throw new ForbiddenError('Invalid version format!') + } + let { nonce, email } = args email = email.toLowerCase() const result = await instance.cypher( diff --git a/backend/src/schema/resolvers/registration.spec.js b/backend/src/schema/resolvers/registration.spec.js index c68f38382..7e4b5e57a 100644 --- a/backend/src/schema/resolvers/registration.spec.js +++ b/backend/src/schema/resolvers/registration.spec.js @@ -297,17 +297,22 @@ describe('SignupVerification', () => { mutation($name: String!, $password: String!, $email: String!, $nonce: String!, $termsAndConditionsAgreedVersion: String!) { SignupVerification(name: $name, password: $password, email: $email, nonce: $nonce, termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion) { id + termsAndConditionsAgreedVersion } } ` describe('given valid password and email', () => { - const variables = { - nonce: '123456', - name: 'John Doe', - password: '123', - email: 'john@example.org', - termsAndConditionsAgreedVersion: '0.0.1', - } + let variables + + beforeEach(async () => { + variables = { + nonce: '123456', + name: 'John Doe', + password: '123', + email: 'john@example.org', + termsAndConditionsAgreedVersion: '0.0.1', + } + }) describe('unauthenticated', () => { beforeEach(async () => { @@ -351,9 +356,9 @@ describe('SignupVerification', () => { describe('sending a valid nonce', () => { it('creates a user account', async () => { const expected = { - SignupVerification: { + SignupVerification: expect.objectContaining({ id: expect.any(String), - }, + }), } await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) @@ -387,6 +392,26 @@ describe('SignupVerification', () => { const { records: emails } = await instance.cypher(cypher, { name: 'John Doe' }) expect(emails).toHaveLength(1) }) + + it('is version of terms and conditions saved correctly', async () => { + const expected = { + SignupVerification: expect.objectContaining({ + termsAndConditionsAgreedVersion: '0.0.1', + }), + } + await expect(client.request(mutation, variables)).resolves.toEqual(expected) + }) + + it('rejects if version of terms and conditions has wrong format', async () => { + const expected = { + SignupVerification: expect.objectContaining({ + termsAndConditionsAgreedVersion: 'invalid version format', + }), + } + await expect(client.request(mutation, {...variables, termsAndConditionsAgreedVersion: 'invalid version format'})).rejects.toThrow( + 'Invalid version format!', + ) + }) }) describe('sending invalid nonce', () => { diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 8e8a604f2..8d9b194de 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -3,7 +3,8 @@ import bcrypt from 'bcryptjs' import { neo4jgraphql } from 'neo4j-graphql-js' import fileUpload from './fileUpload' import { neode } from '../../bootstrap/neo4j' -import { AuthenticationError, UserInputError } from 'apollo-server' +import { AuthenticationError, UserInputError, ForbiddenError } from 'apollo-server' + import Resolver from './helpers/Resolver' const instance = neode() @@ -148,6 +149,16 @@ export default { return blockedUser.toJson() }, UpdateUser: async (object, args, context, resolveInfo) => { + + let { termsAndConditionsAgreedVersion } = args + if (!! termsAndConditionsAgreedVersion ){ + const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g) + if (!regEx.test(termsAndConditionsAgreedVersion)) { + throw new ForbiddenError('Invalid version format!') + } + } + + args = await fileUpload(args, { file: 'avatarUpload', url: 'avatar' }) try { const user = await instance.find('User', args.id) diff --git a/backend/src/schema/resolvers/users.spec.js b/backend/src/schema/resolvers/users.spec.js index 454b457e6..32614090f 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -52,26 +52,35 @@ describe('users', () => { }) describe('UpdateUser', () => { - const userParams = { - email: 'user@example.org', - password: '1234', - id: 'u47', - name: 'John Doe', - } - const variables = { - id: 'u47', - name: 'John Doughnut', - } + let userParams + let variables - const mutation = ` - mutation($id: ID!, $name: String) { - UpdateUser(id: $id, name: $name) { + beforeEach(async () => { + userParams = { + email: 'user@example.org', + password: '1234', + id: 'u47', + name: 'John Doe', + } + + variables = { + id: 'u47', + name: 'John Doughnut', + } + }) + + + const UpdateUserMutation = gql` + mutation($id: ID!, $name: String, $termsAndConditionsAgreedVersion: String) { + UpdateUser(id: $id, name: $name, termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion) { id name + termsAndConditionsAgreedVersion } } ` + beforeEach(async () => { await factory.create('User', userParams) }) @@ -90,7 +99,7 @@ describe('users', () => { }) it('is not allowed to change other user accounts', async () => { - await expect(client.request(mutation, variables)).rejects.toThrow('Not Authorised') + await expect(client.request(UpdateUserMutation, variables)).rejects.toThrow('Not Authorised') }) }) @@ -102,21 +111,18 @@ describe('users', () => { it('name within specifications', async () => { const expected = { - UpdateUser: { + UpdateUser: expect.objectContaining({ id: 'u47', name: 'John Doughnut', - }, + }), } - await expect(client.request(mutation, variables)).resolves.toEqual(expected) + await expect(client.request(UpdateUserMutation, variables)).resolves.toEqual(expected) }) it('with `null` as name', async () => { - const variables = { - id: 'u47', - name: null, - } + variables.name = null const expected = '"name" must be a string' - await expect(client.request(mutation, variables)).rejects.toThrow(expected) + await expect(client.request(UpdateUserMutation, variables)).rejects.toThrow(expected) }) it('with too short name', async () => { @@ -125,8 +131,33 @@ describe('users', () => { name: ' ', } const expected = '"name" length must be at least 3 characters long' - await expect(client.request(mutation, variables)).rejects.toThrow(expected) + await expect(client.request(UpdateUserMutation, variables)).rejects.toThrow(expected) }) + + + + it ('given a new agreed version of terms and conditions', async () => { + variables = { ...variables, termsAndConditionsAgreedVersion: '0.0.2' } + const expected = { + UpdateUser: expect.objectContaining({ + termsAndConditionsAgreedVersion: '0.0.2' + }) + } + await expect(client.request(UpdateUserMutation, variables)).resolves.toEqual(expected) + }) + + it('rejects if version of terms and conditions has wrong format', async () => { + const expected = { + UpdateUser: expect.objectContaining({ + termsAndConditionsAgreedVersion: 'invalid version format' + }) + } + await expect(client.request(UpdateUserMutation, {...variables, termsAndConditionsAgreedVersion: 'invalid version format'})).rejects.toThrow( + 'Invalid version format!', + ) + }) + + }) }) @@ -181,6 +212,8 @@ describe('users', () => { client = new GraphQLClient(host, { headers }) }) + + describe("attempting to delete another user's account", () => { it('throws an authorization error', async () => { deleteUserVariables = { id: 'u565' } diff --git a/backend/src/schema/resolvers/users/termsAndConditions.spec.js b/backend/src/schema/resolvers/users/termsAndConditions.spec.js deleted file mode 100644 index 95b078677..000000000 --- a/backend/src/schema/resolvers/users/termsAndConditions.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -describe('SignupVerification', () => { - describe('given a valid version', () => { - // const version = '1.2.3' - - it.todo('saves the version with the new created user account') - it.todo('saves the current datetime in `termsAndConditionsAgreedAt`') - }) - - describe('given an invalid version string', () => { - // const version = 'this string does not follow semantic versioning' - - it.todo('rejects') - }) -}) - -describe('UpdateUser', () => { - describe('given terms and conditions are not updated', () => { - it.todo('does not update `termsAndConditionsAgreedAt`') - }) - describe('given a new agreed version of terms and conditions', () => { - it.todo('updates `termsAndConditionsAgreedAt`') - it.todo('updates `termsAndConditionsAgreedVersion`') - }) -}) diff --git a/backend/src/schema/types/type/EmailAddress.gql b/backend/src/schema/types/type/EmailAddress.gql index 4bf8ff724..fe7e4cffb 100644 --- a/backend/src/schema/types/type/EmailAddress.gql +++ b/backend/src/schema/types/type/EmailAddress.gql @@ -18,6 +18,6 @@ type Mutation { avatarUpload: Upload locationName: String about: String - termsAndConditionsAgreedVersion: String! + termsAndConditionsAgreedVersion: String ): User } diff --git a/backend/src/seed/factories/users.js b/backend/src/seed/factories/users.js index dc3d56cb6..e6a40b7bb 100644 --- a/backend/src/seed/factories/users.js +++ b/backend/src/seed/factories/users.js @@ -14,7 +14,7 @@ export default function create() { role: 'user', avatar: faker.internet.avatar(), about: faker.lorem.paragraph(), - termsAndConditionsAgreedAt: new Date().toISOString(), + // termsAndConditionsAgreedAt: new Date().toISOString(), termsAndConditionsAgreedVersion: '0.0.1', } defaults.slug = slugify(defaults.name, { lower: true }) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 255212e7d..f05bba887 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -21,7 +21,7 @@ } }, "site": { - "thx": "Danke!", + "thanks": "Danke!", "made": "Mit ❤ gemacht", "imprint": "Impressum", "data-privacy": "Datenschutz", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index a24161288..f4ae9c755 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -21,7 +21,7 @@ } }, "site": { - "thx": "Thanks!", + "thanks": "Thanks!", "made": "Made with ❤", "imprint": "Imprint", "termsAndConditions": "Terms and conditions", diff --git a/webapp/pages/terms-and-conditions-confirm.vue b/webapp/pages/terms-and-conditions-confirm.vue index a516bd134..5d5d605a7 100644 --- a/webapp/pages/terms-and-conditions-confirm.vue +++ b/webapp/pages/terms-and-conditions-confirm.vue @@ -87,7 +87,7 @@ export default { }) }, }) - this.$toast.success(this.$t('site.thx')) + this.$toast.success(this.$t('site.thanks')) this.$router.replace(this.$route.query.path || '/') } catch (err) { this.$toast.error(err.message)