Merge pull request #1513 from Human-Connection/bugfix_about_me_not_saved_on_signup_verification

Bugfix: `about me` not saved on signup
This commit is contained in:
mattwr18 2019-09-10 17:37:22 +02:00 committed by GitHub
commit 3712a5d2cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 209 additions and 108 deletions

View File

@ -1,62 +1,81 @@
import { GraphQLClient } from 'graphql-request'
import Factory from '../seed/factories' import Factory from '../seed/factories'
import { host, login, gql } from '../jest/helpers' import { gql } from '../jest/helpers'
import { neode } from '../bootstrap/neo4j' import { neode as getNeode, getDriver } from '../bootstrap/neo4j'
import createServer from '../server'
import { createTestClient } from 'apollo-server-testing'
let authenticatedClient
let headers
const factory = Factory() const factory = Factory()
const instance = neode()
const categoryIds = ['cat9'] let mutate
const createPostMutation = gql` let authenticatedUser
mutation($title: String!, $content: String!, $categoryIds: [ID]!, $slug: String) { let variables
CreatePost(title: $title, content: $content, categoryIds: $categoryIds, slug: $slug) {
slug const driver = getDriver()
} const neode = getNeode()
}
` beforeAll(() => {
let createPostVariables = { const { server } = createServer({
title: 'I am a brand new post', context: () => {
content: 'Some content', return {
categoryIds, driver,
} neode,
user: authenticatedUser,
}
},
})
mutate = createTestClient(server).mutate
})
beforeEach(async () => { beforeEach(async () => {
const adminParams = { role: 'admin', email: 'admin@example.org', password: '1234' } variables = {}
await factory.create('User', adminParams) const admin = await factory.create('User', { role: 'admin' })
await factory.create('User', { await factory.create('User', {
email: 'someone@example.org', email: 'someone@example.org',
password: '1234', password: '1234',
}) })
await instance.create('Category', { await factory.create('Category', {
id: 'cat9', id: 'cat9',
name: 'Democracy & Politics', name: 'Democracy & Politics',
icon: 'university', icon: 'university',
}) })
// we need to be an admin, otherwise we're not authorized to create a user authenticatedUser = await admin.toJson()
headers = await login(adminParams)
authenticatedClient = new GraphQLClient(host, { headers })
}) })
afterEach(async () => { afterEach(async () => {
await factory.cleanDatabase() await factory.cleanDatabase()
}) })
describe('slugify', () => { describe('slugifyMiddleware', () => {
describe('CreatePost', () => { describe('CreatePost', () => {
const categoryIds = ['cat9']
const createPostMutation = gql`
mutation($title: String!, $content: String!, $categoryIds: [ID]!, $slug: String) {
CreatePost(title: $title, content: $content, categoryIds: $categoryIds, slug: $slug) {
slug
}
}
`
beforeEach(() => {
variables = {
...variables,
title: 'I am a brand new post',
content: 'Some content',
categoryIds,
}
})
it('generates a slug based on title', async () => { it('generates a slug based on title', async () => {
const response = await authenticatedClient.request(createPostMutation, createPostVariables) await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject({
expect(response).toEqual({ data: {
CreatePost: { slug: 'i-am-a-brand-new-post' }, CreatePost: { slug: 'i-am-a-brand-new-post' },
},
}) })
}) })
describe('if slug exists', () => { describe('if slug exists', () => {
beforeEach(async () => { beforeEach(async () => {
const asSomeoneElse = await Factory().authenticateAs({ await factory.create('Post', {
email: 'someone@example.org',
password: '1234',
})
await asSomeoneElse.create('Post', {
title: 'Pre-existing post', title: 'Pre-existing post',
slug: 'pre-existing-post', slug: 'pre-existing-post',
content: 'as Someone else content', content: 'as Someone else content',
@ -65,72 +84,110 @@ describe('slugify', () => {
}) })
it('chooses another slug', async () => { it('chooses another slug', async () => {
createPostVariables = { title: 'Pre-existing post', content: 'Some content', categoryIds } variables = {
const response = await authenticatedClient.request(createPostMutation, createPostVariables) ...variables,
expect(response).toEqual({ title: 'Pre-existing post',
CreatePost: { slug: 'pre-existing-post-1' }, content: 'Some content',
categoryIds,
}
await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject({
data: { CreatePost: { slug: 'pre-existing-post-1' } },
}) })
}) })
describe('but if the client specifies a slug', () => { describe('but if the client specifies a slug', () => {
it('rejects CreatePost', async () => { it('rejects CreatePost', async () => {
createPostVariables = { variables = {
...variables,
title: 'Pre-existing post', title: 'Pre-existing post',
content: 'Some content', content: 'Some content',
slug: 'pre-existing-post', slug: 'pre-existing-post',
categoryIds, categoryIds,
} }
await expect( await expect(mutate({ mutation: createPostMutation, variables })).resolves.toMatchObject({
authenticatedClient.request(createPostMutation, createPostVariables), errors: [{ message: 'Post with this slug already exists!' }],
).rejects.toThrow('already exists') })
}) })
}) })
}) })
}) })
describe('SignupVerification', () => { describe('SignupVerification', () => {
const mutation = `mutation($password: String!, $email: String!, $name: String!, $slug: String, $nonce: String!, $termsAndConditionsAgreedVersion: String!) { const mutation = gql`
SignupVerification(email: $email, password: $password, name: $name, slug: $slug, nonce: $nonce, termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion) { mutation(
slug $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
}
} }
}
` `
const action = async variables => { beforeEach(() => {
// required for SignupVerification variables = {
await instance.create('EmailAddress', { email: '123@example.org', nonce: '123456' }) ...variables,
name: 'I am a user',
const defaultVariables = {
nonce: '123456', nonce: '123456',
password: 'yo', password: 'yo',
email: '123@example.org', email: '123@example.org',
termsAndConditionsAgreedVersion: '0.0.1', termsAndConditionsAgreedVersion: '0.0.1',
} }
return authenticatedClient.request(mutation, { ...defaultVariables, ...variables })
}
it('generates a slug based on name', async () => {
await expect(action({ name: 'I am a user' })).resolves.toEqual({
SignupVerification: { slug: 'i-am-a-user' },
})
}) })
describe('if slug exists', () => { describe('given a user has signed up with their email address', () => {
beforeEach(async () => { beforeEach(async () => {
await factory.create('User', { name: 'pre-existing user', slug: 'pre-existing-user' }) await factory.create('EmailAddress', {
}) email: '123@example.org',
nonce: '123456',
it('chooses another slug', async () => { verifiedAt: null,
await expect(action({ name: 'pre-existing-user' })).resolves.toEqual({
SignupVerification: { slug: 'pre-existing-user-1' },
}) })
}) })
describe('but if the client specifies a slug', () => { it('generates a slug based on name', async () => {
it('rejects SignupVerification', async () => { await expect(mutate({ mutation, variables })).resolves.toMatchObject({
await expect( data: { SignupVerification: { slug: 'i-am-a-user' } },
action({ name: 'Pre-existing user', slug: 'pre-existing-user' }), })
).rejects.toThrow('already exists') })
describe('if slug exists', () => {
beforeEach(async () => {
await factory.create('User', { name: 'I am a user', slug: 'i-am-a-user' })
})
it('chooses another slug', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: {
SignupVerification: { slug: 'i-am-a-user-1' },
},
})
})
describe('but if the client specifies a slug', () => {
beforeEach(() => {
variables = { ...variables, slug: 'i-am-a-user' }
})
it('rejects SignupVerification', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
errors: [
{
message: 'User with this slug already exists!',
},
],
})
})
}) })
}) })
}) })

View File

@ -3,6 +3,7 @@ import { neo4jgraphql } from 'neo4j-graphql-js'
import fileUpload from './fileUpload' import fileUpload from './fileUpload'
import { getBlockedUsers, getBlockedByUsers } from './users.js' import { getBlockedUsers, getBlockedByUsers } from './users.js'
import { mergeWith, isArray } from 'lodash' import { mergeWith, isArray } from 'lodash'
import { UserInputError } from 'apollo-server'
import Resolver from './helpers/Resolver' import Resolver from './helpers/Resolver'
const filterForBlockedUsers = async (params, context) => { const filterForBlockedUsers = async (params, context) => {
@ -78,6 +79,7 @@ export default {
delete params.categoryIds delete params.categoryIds
params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) params = await fileUpload(params, { file: 'imageUpload', url: 'image' })
params.id = params.id || uuid() params.id = params.id || uuid()
let post
const createPostCypher = `CREATE (post:Post {params}) const createPostCypher = `CREATE (post:Post {params})
WITH post WITH post
@ -92,15 +94,21 @@ export default {
const createPostVariables = { userId: context.user.id, categoryIds, params } const createPostVariables = { userId: context.user.id, categoryIds, params }
const session = context.driver.session() const session = context.driver.session()
const transactionRes = await session.run(createPostCypher, createPostVariables) try {
const transactionRes = await session.run(createPostCypher, createPostVariables)
const posts = transactionRes.records.map(record => {
return record.get('post').properties
})
post = posts[0]
} catch (e) {
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('Post with this slug already exists!')
throw new Error(e)
} finally {
session.close()
}
const [post] = transactionRes.records.map(record => { return post
return record.get('post')
})
session.close()
return post.properties
}, },
UpdatePost: async (object, params, context, resolveInfo) => { UpdatePost: async (object, params, context, resolveInfo) => {
const { categoryIds } = params const { categoryIds } = params

View File

@ -1,4 +1,4 @@
import { ForbiddenError, UserInputError } from 'apollo-server' import { UserInputError } from 'apollo-server'
import uuid from 'uuid/v4' import uuid from 'uuid/v4'
import { neode } from '../../bootstrap/neo4j' import { neode } from '../../bootstrap/neo4j'
import fileUpload from './fileUpload' import fileUpload from './fileUpload'
@ -80,7 +80,7 @@ export default {
const { termsAndConditionsAgreedVersion } = args const { termsAndConditionsAgreedVersion } = args
const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g) const regEx = new RegExp(/^[0-9]+\.[0-9]+\.[0-9]+$/g)
if (!regEx.test(termsAndConditionsAgreedVersion)) { if (!regEx.test(termsAndConditionsAgreedVersion)) {
throw new ForbiddenError('Invalid version format!') throw new UserInputError('Invalid version format!')
} }
let { nonce, email } = args let { nonce, email } = args
@ -106,6 +106,8 @@ export default {
]) ])
return user.toJson() return user.toJson()
} catch (e) { } catch (e) {
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed')
throw new UserInputError('User with this slug already exists!')
throw new UserInputError(e.message) throw new UserInputError(e.message)
} }
}, },

View File

@ -327,6 +327,7 @@ describe('SignupVerification', () => {
$password: String! $password: String!
$email: String! $email: String!
$nonce: String! $nonce: String!
$about: String
$termsAndConditionsAgreedVersion: String! $termsAndConditionsAgreedVersion: String!
) { ) {
SignupVerification( SignupVerification(
@ -334,6 +335,7 @@ describe('SignupVerification', () => {
password: $password password: $password
email: $email email: $email
nonce: $nonce nonce: $nonce
about: $about
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
) { ) {
id id
@ -423,6 +425,15 @@ describe('SignupVerification', () => {
expect(emails).toHaveLength(1) expect(emails).toHaveLength(1)
}) })
it('sets `about` attribute of User', async () => {
variables = { ...variables, about: 'Find this description in the user profile' }
await mutate({ mutation, variables })
const user = await neode.first('User', { name: 'John Doe' })
await expect(user.toJson()).resolves.toMatchObject({
about: 'Find this description in the user profile',
})
})
it('marks the EmailAddress as primary', async () => { it('marks the EmailAddress as primary', async () => {
const cypher = ` const cypher = `
MATCH(email:EmailAddress)<-[:PRIMARY_EMAIL]-(u:User {name: {name}}) MATCH(email:EmailAddress)<-[:PRIMARY_EMAIL]-(u:User {name: {name}})

View File

@ -0,0 +1,17 @@
import faker from 'faker'
export default function create() {
return {
factory: async ({ args, neodeInstance }) => {
const defaults = {
email: faker.internet.email(),
verifiedAt: new Date().toISOString(),
}
args = {
...defaults,
...args,
}
return neodeInstance.create('EmailAddress', args)
},
}
}

View File

@ -8,6 +8,7 @@ import createCategory from './categories.js'
import createTag from './tags.js' import createTag from './tags.js'
import createSocialMedia from './socialMedia.js' import createSocialMedia from './socialMedia.js'
import createLocation from './locations.js' import createLocation from './locations.js'
import createEmailAddress from './emailAddresses.js'
export const seedServerHost = 'http://127.0.0.1:4001' export const seedServerHost = 'http://127.0.0.1:4001'
@ -30,6 +31,7 @@ const factories = {
Tag: createTag, Tag: createTag,
SocialMedia: createSocialMedia, SocialMedia: createSocialMedia,
Location: createLocation, Location: createLocation,
EmailAddress: createEmailAddress,
} }
export const cleanDatabase = async (options = {}) => { export const cleanDatabase = async (options = {}) => {

View File

@ -5,7 +5,7 @@ import slugify from 'slug'
export default function create() { export default function create() {
return { return {
factory: async ({ args, neodeInstance }) => { factory: async ({ args, neodeInstance, factoryInstance }) => {
const defaults = { const defaults = {
id: uuid(), id: uuid(),
name: faker.name.findName(), name: faker.name.findName(),
@ -24,7 +24,7 @@ export default function create() {
} }
args = await encryptPassword(args) args = await encryptPassword(args)
const user = await neodeInstance.create('User', args) const user = await neodeInstance.create('User', args)
const email = await neodeInstance.create('EmailAddress', { email: args.email }) const email = await factoryInstance.create('EmailAddress', { email: args.email })
await user.relateTo(email, 'primaryEmail') await user.relateTo(email, 'primaryEmail')
await email.relateTo(user, 'belongsTo') await email.relateTo(user, 'belongsTo')
return user return user

View File

@ -1,5 +1,6 @@
import { config, mount, createLocalVue } from '@vue/test-utils' import { config, mount, createLocalVue } from '@vue/test-utils'
import CreateUserAccount, { SignupVerificationMutation } from './CreateUserAccount' import CreateUserAccount from './CreateUserAccount'
import { SignupVerificationMutation } from '~/graphql/Registration.js'
import Styleguide from '@human-connection/styleguide' import Styleguide from '@human-connection/styleguide'
const localVue = createLocalVue() const localVue = createLocalVue()
@ -55,6 +56,7 @@ describe('CreateUserAccount', () => {
wrapper = Wrapper() wrapper = Wrapper()
wrapper.find('input#name').setValue('John Doe') wrapper.find('input#name').setValue('John Doe')
wrapper.find('input#password').setValue('hellopassword') wrapper.find('input#password').setValue('hellopassword')
wrapper.find('textarea#about').setValue('Hello I am the `about` attribute')
wrapper.find('input#passwordConfirmation').setValue('hellopassword') wrapper.find('input#passwordConfirmation').setValue('hellopassword')
wrapper.find('input#checkbox').setChecked() wrapper.find('input#checkbox').setChecked()
await wrapper.find('form').trigger('submit') await wrapper.find('form').trigger('submit')
@ -72,7 +74,7 @@ describe('CreateUserAccount', () => {
await action() await action()
const expected = expect.objectContaining({ const expected = expect.objectContaining({
variables: { variables: {
about: '', about: 'Hello I am the `about` attribute',
name: 'John Doe', name: 'John Doe',
email: 'sixseven@example.org', email: 'sixseven@example.org',
nonce: '666777', nonce: '666777',

View File

@ -25,7 +25,7 @@
:placeholder="$t('settings.data.namePlaceholder')" :placeholder="$t('settings.data.namePlaceholder')"
/> />
<ds-input <ds-input
id="bio" id="about"
model="about" model="about"
type="textarea" type="textarea"
rows="3" rows="3"
@ -83,34 +83,12 @@
</template> </template>
<script> <script>
import gql from 'graphql-tag'
import PasswordStrength from '../Password/Strength' import PasswordStrength from '../Password/Strength'
import { SweetalertIcon } from 'vue-sweetalert-icons' import { SweetalertIcon } from 'vue-sweetalert-icons'
import PasswordForm from '~/components/utils/PasswordFormHelper' import PasswordForm from '~/components/utils/PasswordFormHelper'
import { VERSION } from '~/constants/terms-and-conditions-version.js' import { VERSION } from '~/constants/terms-and-conditions-version.js'
import { SignupVerificationMutation } from '~/graphql/Registration.js'
/* TODO: hier muss die version rein */
export const SignupVerificationMutation = gql`
mutation(
$nonce: String!
$name: String!
$email: String!
$password: String!
$termsAndConditionsAgreedVersion: String!
) {
SignupVerification(
nonce: $nonce
email: $email
name: $name
password: $password
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
) {
id
name
slug
}
}
`
export default { export default {
components: { components: {
PasswordStrength, PasswordStrength,

View File

@ -0,0 +1,24 @@
import gql from 'graphql-tag'
export const SignupVerificationMutation = gql`
mutation(
$nonce: String!
$name: String!
$email: String!
$password: String!
$about: String
$termsAndConditionsAgreedVersion: String!
) {
SignupVerification(
nonce: $nonce
email: $email
name: $name
password: $password
about: $about
termsAndConditionsAgreedVersion: $termsAndConditionsAgreedVersion
) {
id
name
slug
}
}
`