diff --git a/backend/src/middleware/slugifyMiddleware.spec.js b/backend/src/middleware/slugifyMiddleware.spec.js index 0f42def85..d11757bc7 100644 --- a/backend/src/middleware/slugifyMiddleware.spec.js +++ b/backend/src/middleware/slugifyMiddleware.spec.js @@ -89,8 +89,10 @@ describe('slugify', () => { }) describe('SignupVerification', () => { - const mutation = `mutation($password: String!, $email: String!, $name: String!, $slug: String, $nonce: String!) { - SignupVerification(email: $email, password: $password, name: $name, slug: $slug, nonce: $nonce) { slug } + const mutation = `mutation($password: String!, $email: String!, $name: String!, $slug: String, $nonce: String!, $termsAndConditionsAgreedVersion: String!) { + SignupVerification(email: $email, password: $password, name: $name, slug: $slug, nonce: $nonce, termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion) { + slug + } } ` @@ -98,7 +100,12 @@ describe('slugify', () => { // required for SignupVerification await instance.create('EmailAddress', { email: '123@example.org', nonce: '123456' }) - const defaultVariables = { nonce: '123456', password: 'yo', email: '123@example.org' } + const defaultVariables = { + nonce: '123456', + password: 'yo', + email: '123@example.org', + termsAndConditionsAgreedVersion: '0.0.1', + } return authenticatedClient.request(mutation, { ...defaultVariables, ...variables }) } diff --git a/backend/src/models/User.js b/backend/src/models/User.js index 28ab46d3c..c2749ce6a 100644 --- a/backend/src/models/User.js +++ b/backend/src/models/User.js @@ -80,9 +80,19 @@ module.exports = { notifications: { type: 'relationship', relationship: 'NOTIFIED', - target: 'Notification', + target: 'User', direction: 'in', }, + termsAndConditionsAgreedVersion: { + type: 'string', + allow: [null], + }, + /* termsAndConditionsAgreedAt: { + type: 'string', + isoDate: true, + allow: [null], + // required: true, TODO + }, */ shouted: { type: 'relationship', relationship: 'SHOUTED', diff --git a/backend/src/schema/resolvers/registration.js b/backend/src/schema/resolvers/registration.js index 20c54a49b..423ce7580 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,12 @@ export default { } }, SignupVerification: async (object, args, context, resolveInfo) => { + const { 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 9f9a171f7..8e33bf314 100644 --- a/backend/src/schema/resolvers/registration.spec.js +++ b/backend/src/schema/resolvers/registration.spec.js @@ -34,6 +34,7 @@ describe('CreateInvitationCode', () => { name: 'Inviter', email: 'inviter@example.org', password: '1234', + termsAndConditionsAgreedVersion: '0.0.1', } action = async () => { const factory = Factory() @@ -293,19 +294,25 @@ describe('Signup', () => { describe('SignupVerification', () => { const mutation = ` - mutation($name: String!, $password: String!, $email: String!, $nonce: String!) { - SignupVerification(name: $name, password: $password, email: $email, nonce: $nonce) { + 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', - } + let variables + + beforeEach(async () => { + variables = { + nonce: '123456', + name: 'John Doe', + password: '123', + email: 'john@example.org', + termsAndConditionsAgreedVersion: '0.0.1', + } + }) describe('unauthenticated', () => { beforeEach(async () => { @@ -349,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) }) @@ -385,6 +392,24 @@ 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 () => { + 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/user_management.js b/backend/src/schema/resolvers/user_management.js index a634eaf85..e1528cc9e 100644 --- a/backend/src/schema/resolvers/user_management.js +++ b/backend/src/schema/resolvers/user_management.js @@ -1,7 +1,6 @@ import encode from '../../jwt/encode' import bcrypt from 'bcryptjs' import { AuthenticationError } from 'apollo-server' -import { neo4jgraphql } from 'neo4j-graphql-js' import { neode } from '../../bootstrap/neo4j' const instance = neode() @@ -12,9 +11,9 @@ export default { return Boolean(user && user.id) }, currentUser: async (object, params, ctx, resolveInfo) => { - const { user } = ctx - if (!user) return null - return neo4jgraphql(object, { id: user.id }, ctx, resolveInfo, false) + if (!ctx.user) return null + const user = await instance.find('User', ctx.user.id) + return user.toJson() }, }, Mutation: { diff --git a/backend/src/schema/resolvers/users.js b/backend/src/schema/resolvers/users.js index 44d4cff50..93076e334 100644 --- a/backend/src/schema/resolvers/users.js +++ b/backend/src/schema/resolvers/users.js @@ -1,7 +1,7 @@ import { neo4jgraphql } from 'neo4j-graphql-js' import fileUpload from './fileUpload' import { neode } from '../../bootstrap/neo4j' -import { UserInputError } from 'apollo-server' +import { UserInputError, ForbiddenError } from 'apollo-server' import Resolver from './helpers/Resolver' const instance = neode() @@ -88,6 +88,13 @@ export default { return blockedUser.toJson() }, UpdateUser: async (object, args, context, resolveInfo) => { + const { 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 e8e6205ca..50f413157 100644 --- a/backend/src/schema/resolvers/users.spec.js +++ b/backend/src/schema/resolvers/users.spec.js @@ -75,22 +75,33 @@ describe('User', () => { }) 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 + + 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) { - UpdateUser(id: $id, name: $name) { + mutation($id: ID!, $name: String, $termsAndConditionsAgreedVersion: String) { + UpdateUser( + id: $id + name: $name + termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion + ) { id name + termsAndConditionsAgreedVersion } } ` @@ -159,6 +170,30 @@ describe('UpdateUser', () => { 'child "name" fails because ["name" length must be at least 3 characters long]', ) }) + + it('given a new agreed version of terms and conditions', async () => { + variables = { ...variables, termsAndConditionsAgreedVersion: '0.0.2' } + const expected = { + data: { + UpdateUser: expect.objectContaining({ + termsAndConditionsAgreedVersion: '0.0.2', + }), + }, + } + + await expect(mutate({ mutation: updateUserMutation, variables })).resolves.toMatchObject( + expected, + ) + }) + + it('rejects if version of terms and conditions has wrong format', async () => { + variables = { + ...variables, + termsAndConditionsAgreedVersion: 'invalid version format', + } + const { errors } = await mutate({ mutation: updateUserMutation, variables }) + expect(errors[0]).toHaveProperty('message', 'Invalid version format!') + }) }) }) diff --git a/backend/src/schema/types/type/EmailAddress.gql b/backend/src/schema/types/type/EmailAddress.gql index 63b39d457..fe7e4cffb 100644 --- a/backend/src/schema/types/type/EmailAddress.gql +++ b/backend/src/schema/types/type/EmailAddress.gql @@ -1,6 +1,5 @@ type EmailAddress { - id: ID! - email: String! + email: ID! verifiedAt: String createdAt: String } @@ -19,5 +18,6 @@ type Mutation { avatarUpload: Upload locationName: String about: String + termsAndConditionsAgreedVersion: String ): User } diff --git a/backend/src/schema/types/type/User.gql b/backend/src/schema/types/type/User.gql index 6151d0708..83d5343b2 100644 --- a/backend/src/schema/types/type/User.gql +++ b/backend/src/schema/types/type/User.gql @@ -1,173 +1,177 @@ type User { - id: ID! - actorId: String - name: String - email: String! @cypher(statement: "MATCH (this)-[:PRIMARY_EMAIL]->(e:EmailAddress) RETURN e.email") - slug: String! - avatar: String - coverImg: String - deleted: Boolean - disabled: Boolean - disabledBy: User @relation(name: "DISABLED", direction: "IN") - role: UserGroup! - publicKey: String - invitedBy: User @relation(name: "INVITED", direction: "IN") - invited: [User] @relation(name: "INVITED", direction: "OUT") + id: ID! + actorId: String + name: String + email: String! @cypher(statement: "MATCH (this)-[: PRIMARY_EMAIL]->(e: EmailAddress) RETURN e.email") + slug: String! + avatar: String + coverImg: String + deleted: Boolean + disabled: Boolean + disabledBy: User @relation(name: "DISABLED", direction: "IN") + role: UserGroup! + publicKey: String + invitedBy: User @relation(name: "INVITED", direction: "IN") + invited: [User] @relation(name: "INVITED", direction: "OUT") - location: Location @cypher(statement: "MATCH (this)-[:IS_IN]->(l:Location) RETURN l") - locationName: String - about: String - socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN") + location: Location @cypher(statement: "MATCH (this)-[: IS_IN]->(l: Location) RETURN l") + locationName: String + about: String + socialMedia: [SocialMedia]! @relation(name: "OWNED_BY", direction: "IN") - #createdAt: DateTime - #updatedAt: DateTime - createdAt: String - updatedAt: String + # createdAt: DateTime + # updatedAt: DateTime + createdAt: String + updatedAt: String - friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") - friendsCount: Int! @cypher(statement: "MATCH (this)<-[:FRIENDS]->(r:User) RETURN COUNT(DISTINCT r)") + termsAndConditionsAgreedVersion: String - following: [User]! @relation(name: "FOLLOWS", direction: "OUT") - followingCount: Int! @cypher(statement: "MATCH (this)-[:FOLLOWS]->(r:User) RETURN COUNT(DISTINCT r)") + friends: [User]! @relation(name: "FRIENDS", direction: "BOTH") + friendsCount: Int! @cypher(statement: "MATCH (this)<-[: FRIENDS]->(r: User) RETURN COUNT(DISTINCT r)") - followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN") - followedByCount: Int! @cypher(statement: "MATCH (this)<-[:FOLLOWS]-(r:User) RETURN COUNT(DISTINCT r)") + following: [User]! @relation(name: "FOLLOWS", direction: "OUT") + followingCount: Int! @cypher(statement: "MATCH (this)-[: FOLLOWS]->(r: User) RETURN COUNT(DISTINCT r)") - # Is the currently logged in user following that user? - followedByCurrentUser: Boolean! @cypher( - statement: """ - MATCH (this)<-[:FOLLOWS]-(u:User {id: $cypherParams.currentUserId}) - RETURN COUNT(u) >= 1 - """ - ) - isBlocked: Boolean! @cypher( - statement: """ - MATCH (this)<-[:BLOCKED]-(u:User {id: $cypherParams.currentUserId}) - RETURN COUNT(u) >= 1 - """ - ) + followedBy: [User]! @relation(name: "FOLLOWS", direction: "IN") + followedByCount: Int! @cypher(statement: "MATCH (this)<-[: FOLLOWS]-(r: User) RETURN COUNT(DISTINCT r)") - #contributions: [WrittenPost]! - #contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]! - # @cypher( - # statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp" - # ) - contributions: [Post]! @relation(name: "WROTE", direction: "OUT") - contributionsCount: Int! @cypher( - statement: """ - MATCH (this)-[:WROTE]->(r:Post) - WHERE NOT r.deleted = true AND NOT r.disabled = true - RETURN COUNT(r) - """ - ) + # Is the currently logged in user following that user? + followedByCurrentUser: Boolean! @cypher( + statement: """ + MATCH (this)<-[: FOLLOWS]-(u: User { id: $cypherParams.currentUserId}) + RETURN COUNT(u) >= 1 + """ + ) + isBlocked: Boolean! @cypher( + statement: """ + MATCH (this)<-[: BLOCKED]-(u: User { id: $cypherParams.currentUserId}) + RETURN COUNT(u) >= 1 + """ + ) - comments: [Comment]! @relation(name: "WROTE", direction: "OUT") - commentedCount: Int! @cypher(statement: "MATCH (this)-[:WROTE]->(:Comment)-[:COMMENTS]->(p:Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))") + # contributions: [WrittenPost]! + # contributions2(first: Int = 10, offset: Int = 0): [WrittenPost2]! + # @cypher( + # statement: "MATCH (this)-[w:WROTE]->(p:Post) RETURN p as Post, w.timestamp as timestamp" + # ) + contributions: [Post]! @relation(name: "WROTE", direction: "OUT") + contributionsCount: Int! @cypher( + statement: """ + MATCH (this)-[: WROTE]->(r: Post) + WHERE NOT r.deleted = true AND NOT r.disabled = true + RETURN COUNT(r) + """ + ) - shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT") - shoutedCount: Int! @cypher(statement: "MATCH (this)-[:SHOUTED]->(r:Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)") + comments: [Comment]! @relation(name: "WROTE", direction: "OUT") + commentedCount: Int! @cypher(statement: "MATCH (this)-[: WROTE]->(: Comment)-[: COMMENTS]->(p: Post) WHERE NOT p.deleted = true AND NOT p.disabled = true RETURN COUNT(DISTINCT(p))") - organizationsCreated: [Organization] @relation(name: "CREATED_ORGA", direction: "OUT") - organizationsOwned: [Organization] @relation(name: "OWNING_ORGA", direction: "OUT") + shouted: [Post]! @relation(name: "SHOUTED", direction: "OUT") + shoutedCount: Int! @cypher(statement: "MATCH (this)-[: SHOUTED]->(r: Post) WHERE NOT r.deleted = true AND NOT r.disabled = true RETURN COUNT(DISTINCT r)") - categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") + organizationsCreated: [Organization] @relation(name: "CREATED_ORGA", direction: "OUT") + organizationsOwned: [Organization] @relation(name: "OWNING_ORGA", direction: "OUT") - badges: [Badge]! @relation(name: "REWARDED", direction: "IN") - badgesCount: Int! @cypher(statement: "MATCH (this)<-[:REWARDED]-(r:Badge) RETURN COUNT(r)") + categories: [Category]! @relation(name: "CATEGORIZED", direction: "OUT") - emotions: [EMOTED] + badges: [Badge]! @relation(name: "REWARDED", direction: "IN") + badgesCount: Int! @cypher(statement: "MATCH (this)<-[: REWARDED]-(r: Badge) RETURN COUNT(r)") + + emotions: [EMOTED] } input _UserFilter { - AND: [_UserFilter!] - OR: [_UserFilter!] - name_contains: String - about_contains: String - slug_contains: String - id: ID - id_not: ID - id_in: [ID!] - id_not_in: [ID!] - id_contains: ID - id_not_contains: ID - id_starts_with: ID - id_not_starts_with: ID - id_ends_with: ID - id_not_ends_with: ID - friends: _UserFilter - friends_not: _UserFilter - friends_in: [_UserFilter!] - friends_not_in: [_UserFilter!] - friends_some: _UserFilter - friends_none: _UserFilter - friends_single: _UserFilter - friends_every: _UserFilter - following: _UserFilter - following_not: _UserFilter - following_in: [_UserFilter!] - following_not_in: [_UserFilter!] - following_some: _UserFilter - following_none: _UserFilter - following_single: _UserFilter - following_every: _UserFilter - followedBy: _UserFilter - followedBy_not: _UserFilter - followedBy_in: [_UserFilter!] - followedBy_not_in: [_UserFilter!] - followedBy_some: _UserFilter - followedBy_none: _UserFilter - followedBy_single: _UserFilter - followedBy_every: _UserFilter + AND: [_UserFilter!] + OR: [_UserFilter!] + name_contains: String + about_contains: String + slug_contains: String + id: ID + id_not: ID + id_in: [ID!] + id_not_in: [ID!] + id_contains: ID + id_not_contains: ID + id_starts_with: ID + id_not_starts_with: ID + id_ends_with: ID + id_not_ends_with: ID + friends: _UserFilter + friends_not: _UserFilter + friends_in: [_UserFilter!] + friends_not_in: [_UserFilter!] + friends_some: _UserFilter + friends_none: _UserFilter + friends_single: _UserFilter + friends_every: _UserFilter + following: _UserFilter + following_not: _UserFilter + following_in: [_UserFilter!] + following_not_in: [_UserFilter!] + following_some: _UserFilter + following_none: _UserFilter + following_single: _UserFilter + following_every: _UserFilter + followedBy: _UserFilter + followedBy_not: _UserFilter + followedBy_in: [_UserFilter!] + followedBy_not_in: [_UserFilter!] + followedBy_some: _UserFilter + followedBy_none: _UserFilter + followedBy_single: _UserFilter + followedBy_every: _UserFilter } type Query { - User( - id: ID - email: String - actorId: String - name: String - slug: String - avatar: String - coverImg: String - role: UserGroup - locationName: String - about: String - createdAt: String - updatedAt: String - friendsCount: Int - followingCount: Int - followedByCount: Int - followedByCurrentUser: Boolean - contributionsCount: Int - commentedCount: Int - shoutedCount: Int - badgesCount: Int - first: Int - offset: Int - orderBy: [_UserOrdering] - filter: _UserFilter - ): [User] + User( + id: ID + email: String + actorId: String + name: String + slug: String + avatar: String + coverImg: String + role: UserGroup + locationName: String + about: String + createdAt: String + updatedAt: String + friendsCount: Int + followingCount: Int + followedByCount: Int + followedByCurrentUser: Boolean + contributionsCount: Int + commentedCount: Int + shoutedCount: Int + badgesCount: Int + first: Int + offset: Int + orderBy: [_UserOrdering] + filter: _UserFilter + ): [User] - blockedUsers: [User] + blockedUsers: [User] + currentUser: User } type Mutation { - UpdateUser ( - id: ID! - name: String - email: String - slug: String - avatar: String - coverImg: String - avatarUpload: Upload - locationName: String - about: String - ): User + UpdateUser ( + id: ID! + name: String + email: String + slug: String + avatar: String + coverImg: String + avatarUpload: Upload + locationName: String + about: String + termsAndConditionsAgreedVersion: String + ): User - DeleteUser(id: ID!, resource: [Deletable]): User + DeleteUser(id: ID!, resource: [Deletable]): User - block(id: ID!): User - unblock(id: ID!): User -} + block(id: ID!): User + unblock(id: ID!): User +} \ No newline at end of file diff --git a/backend/src/seed/factories/users.js b/backend/src/seed/factories/users.js index af1699253..0ed1d4bc5 100644 --- a/backend/src/seed/factories/users.js +++ b/backend/src/seed/factories/users.js @@ -14,6 +14,8 @@ export default function create() { role: 'user', avatar: faker.internet.avatar(), about: faker.lorem.paragraph(), + // termsAndConditionsAgreedAt: new Date().toISOString(), + termsAndConditionsAgreedVersion: '0.0.1', } defaults.slug = slugify(defaults.name, { lower: true }) args = { diff --git a/cypress.env.template.json b/cypress.env.template.json index bd03f6381..f5916bb89 100644 --- a/cypress.env.template.json +++ b/cypress.env.template.json @@ -3,4 +3,4 @@ "NEO4J_URI": "bolt://localhost:7687", "NEO4J_USERNAME": "neo4j", "NEO4J_PASSWORD": "letmein" -} +} \ No newline at end of file diff --git a/cypress/integration/common/report.js b/cypress/integration/common/report.js index 180353328..fb395d361 100644 --- a/cypress/integration/common/report.js +++ b/cypress/integration/common/report.js @@ -31,6 +31,7 @@ Given('I am logged in with a {string} role', role => { cy.factory().create('User', { email: `${role}@example.org`, password: '1234', + termsAndConditionsAgreedVersion: "0.0.2", role }) cy.login({ diff --git a/cypress/integration/common/steps.js b/cypress/integration/common/steps.js index f7ab18707..d712ee9b1 100644 --- a/cypress/integration/common/steps.js +++ b/cypress/integration/common/steps.js @@ -1,4 +1,8 @@ -import { Given, When, Then } from "cypress-cucumber-preprocessor/steps"; +import { + Given, + When, + Then +} from "cypress-cucumber-preprocessor/steps"; import helpers from "../../support/helpers"; /* global cy */ @@ -9,12 +13,16 @@ let loginCredentials = { email: "peterpan@example.org", password: "1234" }; +const termsAndConditionsAgreedVersion = { + termsAndConditionsAgreedVersion: "0.0.2" +}; const narratorParams = { id: 'id-of-peter-pan', name: "Peter Pan", slug: "peter-pan", avatar: "https://s3.amazonaws.com/uifaces/faces/twitter/nerrsoft/128.jpg", - ...loginCredentials + ...loginCredentials, + ...termsAndConditionsAgreedVersion, }; Given("I am logged in", () => { @@ -28,25 +36,54 @@ Given("we have a selection of categories", () => { Given("we have a selection of tags and categories as well as posts", () => { cy.createCategories("cat12") .factory() - .create("Tag", { id: "Ecology" }) - .create("Tag", { id: "Nature" }) - .create("Tag", { id: "Democracy" }); + .create("Tag", { + id: "Ecology" + }) + .create("Tag", { + id: "Nature" + }) + .create("Tag", { + id: "Democracy" + }); cy.factory() - .create("User", { id: 'a1' }) - .create("Post", {authorId: 'a1', tagIds: [ "Ecology", "Nature", "Democracy" ], categoryIds: ["cat12"] }) - .create("Post", {authorId: 'a1', tagIds: [ "Nature", "Democracy" ], categoryIds: ["cat121"] }); + .create("User", { + id: 'a1' + }) + .create("Post", { + authorId: 'a1', + tagIds: ["Ecology", "Nature", "Democracy"], + categoryIds: ["cat12"] + }) + .create("Post", { + authorId: 'a1', + tagIds: ["Nature", "Democracy"], + categoryIds: ["cat121"] + }); cy.factory() - .create("User", { id: 'a2'}) - .create("Post", { authorId: 'a2', tagIds: ['Nature', 'Democracy'], categoryIds: ["cat12"] }); + .create("User", { + id: 'a2' + }) + .create("Post", { + authorId: 'a2', + tagIds: ['Nature', 'Democracy'], + categoryIds: ["cat12"] + }); cy.factory() - .create("Post", { authorId: narratorParams.id, tagIds: ['Democracy'], categoryIds: ["cat122"] }) + .create("Post", { + authorId: narratorParams.id, + tagIds: ['Democracy'], + categoryIds: ["cat122"] + }) }); Given("we have the following user accounts:", table => { table.hashes().forEach(params => { - cy.factory().create("User", params); + cy.factory().create("User", { + ...params, + ...termsAndConditionsAgreedVersion + }); }); }); @@ -57,7 +94,8 @@ Given("I have a user account", () => { Given("my user account has the role {string}", role => { cy.factory().create("User", { role, - ...loginCredentials + ...loginCredentials, + ...termsAndConditionsAgreedVersion, }); }); @@ -117,7 +155,9 @@ Given("I previously switched the language to {string}", name => { }); Then("the whole user interface appears in {string}", name => { - const { code } = helpers.getLangByName(name); + const { + code + } = helpers.getLangByName(name); cy.get(`html[lang=${code}]`); cy.getCookie("locale").should("have.property", "value", code); }); @@ -151,7 +191,9 @@ Given("we have the following posts in our database:", table => { icon: "smile" }) - table.hashes().forEach(({ ...postAttributes }, i) => { + table.hashes().forEach(({ + ...postAttributes + }, i) => { postAttributes = { ...postAttributes, deleted: Boolean(postAttributes.deleted), @@ -229,10 +271,15 @@ Then("the first post on the landing page has the title:", title => { Then( "the page {string} returns a 404 error with a message:", (route, message) => { - cy.request({ url: route, failOnStatusCode: false }) + cy.request({ + url: route, + failOnStatusCode: false + }) .its("status") .should("eq", 404); - cy.visit(route, { failOnStatusCode: false }); + cy.visit(route, { + failOnStatusCode: false + }); cy.get(".error").should("contain", message); } ); @@ -240,7 +287,10 @@ Then( Given("my user account has the following login credentials:", table => { loginCredentials = table.hashes()[0]; cy.debug(); - cy.factory().create("User", loginCredentials); + cy.factory().create("User", { + ...termsAndConditionsAgreedVersion, + ...loginCredentials + }); }); When("I fill the password form with:", table => { @@ -259,7 +309,9 @@ When("submit the form", () => { Then("I cannot login anymore with password {string}", password => { cy.reload(); - const { email } = loginCredentials; + const { + email + } = loginCredentials; cy.visit(`/login`); cy.get("input[name=email]") .trigger("focus") @@ -280,14 +332,22 @@ Then("I can login successfully with password {string}", password => { cy.reload(); cy.login({ ...loginCredentials, - ...{ password } + ...{ + password + } }); cy.get(".iziToast-wrapper").should("contain", "You are logged in!"); }); When("I log in with the following credentials:", table => { - const { email, password } = table.hashes()[0]; - cy.login({ email, password }); + const { + email, + password + } = table.hashes()[0]; + cy.login({ + email, + password + }); }); When("open the notification menu and click on the first item", () => { @@ -337,12 +397,14 @@ Then("there are no notifications in the top menu", () => { Given("there is an annoying user called {string}", name => { const annoyingParams = { email: "spammy-spammer@example.org", - password: "1234" + password: "1234", + ...termsAndConditionsAgreedVersion }; cy.factory().create("User", { ...annoyingParams, id: "annoying-user", - name + name, + ...termsAndConditionsAgreedVersion, }); }); @@ -364,7 +426,9 @@ When( cy.get(".user-content-menu .content-menu-trigger").click(); cy.get(".popover .ds-menu-item-link") .contains(button) - .click({ force: true }); + .click({ + force: true + }); } ); @@ -379,10 +443,14 @@ When("I navigate to my {string} settings page", settingsPage => { Given("I follow the user {string}", name => { cy.neode() - .first("User", { name }) + .first("User", { + name + }) .then(followed => { cy.neode() - .first("User", { name: narratorParams.name }) + .first("User", { + name: narratorParams.name + }) .relateTo(followed, "following"); }); }); @@ -390,7 +458,11 @@ Given("I follow the user {string}", name => { Given('"Spammy Spammer" wrote a post {string}', title => { cy.createCategories("cat21") .factory() - .create("Post", { authorId: 'annoying-user', title, categoryIds: ["cat21"] }); + .create("Post", { + authorId: 'annoying-user', + title, + categoryIds: ["cat21"] + }); }); Then("the list of posts of this user is empty", () => { @@ -409,23 +481,37 @@ Then("nobody is following the user profile anymore", () => { Given("I wrote a post {string}", title => { cy.createCategories(`cat213`, title) .factory() - .create("Post", { authorId: narratorParams.id, title, categoryIds: ["cat213"] }); + .create("Post", { + authorId: narratorParams.id, + title, + categoryIds: ["cat213"] + }); }); When("I block the user {string}", name => { cy.neode() - .first("User", { name }) + .first("User", { + name + }) .then(blocked => { cy.neode() - .first("User", { name: narratorParams.name }) + .first("User", { + name: narratorParams.name + }) .relateTo(blocked, "blocked"); }); }); When("I log in with:", table => { const [firstRow] = table.hashes(); - const { Email, Password } = firstRow; - cy.login({ email: Email, password: Password }); + const { + Email, + Password + } = firstRow; + cy.login({ + email: Email, + password: Password + }); }); Then("I see only one post with the title {string}", title => { @@ -433,4 +519,4 @@ Then("I see only one post with the title {string}", title => { .find(".post-link") .should("have.length", 1); cy.get(".main-container").contains(".post-link", title); -}); +}); \ No newline at end of file diff --git a/webapp/components/Registration/CreateUserAccount.spec.js b/webapp/components/Registration/CreateUserAccount.spec.js index a8d63193f..3ee358db6 100644 --- a/webapp/components/Registration/CreateUserAccount.spec.js +++ b/webapp/components/Registration/CreateUserAccount.spec.js @@ -77,6 +77,7 @@ describe('CreateUserAccount', () => { email: 'sixseven@example.org', nonce: '666777', password: 'hellopassword', + termsAndConditionsAgreedVersion: '0.0.2', }, }) expect(mocks.$apollo.mutate).toHaveBeenCalledWith(expected) diff --git a/webapp/components/Registration/CreateUserAccount.vue b/webapp/components/Registration/CreateUserAccount.vue index e5b1ee38f..e1ff98a5c 100644 --- a/webapp/components/Registration/CreateUserAccount.vue +++ b/webapp/components/Registration/CreateUserAccount.vue @@ -55,7 +55,10 @@ v-model="termsAndConditionsConfirmed" :checked="termsAndConditionsConfirmed" /> - +