Resolvers for EmailAddress implemented

This commit is contained in:
roschaefer 2019-09-24 17:09:15 +02:00
parent 8c13234af9
commit e51124f316
2 changed files with 169 additions and 53 deletions

View File

@ -1,6 +1,7 @@
import generateNonce from './helpers/generateNonce' import generateNonce from './helpers/generateNonce'
import Resolver from './helpers/Resolver' import Resolver from './helpers/Resolver'
import existingEmailAddress from './helpers/existingEmailAddress' import existingEmailAddress from './helpers/existingEmailAddress'
import { UserInputError } from 'apollo-server'
export default { export default {
Mutation: { Mutation: {
@ -34,7 +35,36 @@ export default {
} }
return response return response
}, },
VerifyEmailAddress: async (_parent, args, context, _resolveInfo) => {}, VerifyEmailAddress: async (_parent, args, context, _resolveInfo) => {
let response
const {
user: { id: userId },
} = context
const { nonce, email } = args
const session = context.driver.session()
const writeTxResultPromise = session.writeTransaction(async txc => {
const result = await txc.run(
`
MATCH (user:User {id: $userId})-[previous:PRIMARY_EMAIL]->(:EmailAddress)
MATCH (user)<-[:BELONGS_TO]-(email:EmailAddress {email: $email, nonce: $nonce})
MERGE (user)-[:PRIMARY_EMAIL]->(email)
SET email.verifiedAt = toString(datetime())
DELETE previous
RETURN email
`,
{ userId, email, nonce },
)
return result.records.map(record => record.get('email').properties)
})
try {
const txResult = await writeTxResultPromise
response = txResult[0]
} finally {
session.close()
}
if (!response) throw new UserInputError('Invalid nonce or no email address found.')
return response
},
}, },
EmailAddress: { EmailAddress: {
...Resolver('EmailAddress', { ...Resolver('EmailAddress', {

View File

@ -67,6 +67,11 @@ describe('AddEmailAddress', () => {
authenticatedUser = await user.toJson() authenticatedUser = await user.toJson()
}) })
describe('email attribute is not a valid email', () => {
it.todo('throws UserInputError')
})
describe('email attribute is a valid email', () => {
it('creates a new unverified `EmailAddress` node', async () => { it('creates a new unverified `EmailAddress` node', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({ await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { data: {
@ -83,7 +88,7 @@ describe('AddEmailAddress', () => {
it('connects `EmailAddress` to the authenticated user', async () => { it('connects `EmailAddress` to the authenticated user', async () => {
await mutate({ mutation, variables }) await mutate({ mutation, variables })
const result = await neode.cypher(` const result = await neode.cypher(`
MATCH(u:User)-[:PRIMARY_EMAIL]->(p:EmailAddress {email: "user@example.org"}) MATCH(u:User)-[:PRIMARY_EMAIL]->(:EmailAddress {email: "user@example.org"})
MATCH(u:User)<-[:BELONGS_TO]-(e:EmailAddress {email: "new-email@example.org"}) MATCH(u:User)<-[:BELONGS_TO]-(e:EmailAddress {email: "new-email@example.org"})
RETURN e RETURN e
`) `)
@ -125,6 +130,7 @@ describe('AddEmailAddress', () => {
}) })
}) })
}) })
})
describe('VerifyEmailAddress', () => { describe('VerifyEmailAddress', () => {
const mutation = gql` const mutation = gql`
@ -132,6 +138,7 @@ describe('VerifyEmailAddress', () => {
VerifyEmailAddress(email: $email, nonce: $nonce) { VerifyEmailAddress(email: $email, nonce: $nonce) {
email email
createdAt createdAt
verifiedAt
} }
} }
` `
@ -154,23 +161,102 @@ describe('VerifyEmailAddress', () => {
}) })
describe('authenticated', () => { describe('authenticated', () => {
beforeEach(async () => {
user = await factory.create('User', { email: 'user@example.org' })
authenticatedUser = await user.toJson()
})
describe('if no unverified `EmailAddress` node exists', () => { describe('if no unverified `EmailAddress` node exists', () => {
it.todo('throws UserInputError') it('throws UserInputError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null },
errors: [{ message: 'Invalid nonce or no email address found.' }],
})
})
})
describe('given an unverified `EmailAddress`', () => {
let emailAddress
beforeEach(async () => {
emailAddress = await factory.create('EmailAddress', {
nonce: 'abcdef',
verifiedAt: null,
createdAt: new Date().toISOString(),
email: 'to-be-verified@example.org',
})
}) })
describe('given invalid nonce', () => { describe('given invalid nonce', () => {
it.todo('throws UserInputError') it('throws UserInputError', async () => {
variables.nonce = 'asdfgh'
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null },
errors: [{ message: 'Invalid nonce or no email address found.' }],
})
})
}) })
describe('given valid nonce for unverified `EmailAddress` node', () => { describe('given valid nonce for unverified `EmailAddress` node', () => {
beforeEach(() => {
variables = { ...variables, nonce: 'abcdef' }
})
describe('but the address does not belong to the authenticated user', () => { describe('but the address does not belong to the authenticated user', () => {
it.todo('throws UserInputError') it('throws UserInputError', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: { VerifyEmailAddress: null },
errors: [{ message: 'Invalid nonce or no email address found.' }],
})
})
}) })
describe('and the `EmailAddress` belongs to the authenticated user', () => { describe('and the `EmailAddress` belongs to the authenticated user', () => {
it.todo('adds `verifiedAt`') beforeEach(async () => {
it.todo('connects the new `EmailAddress` as PRIMARY') await emailAddress.relateTo(user, 'belongsTo')
it.todo('removes previous PRIMARY relationship') })
it('adds `verifiedAt`', async () => {
await expect(mutate({ mutation, variables })).resolves.toMatchObject({
data: {
VerifyEmailAddress: {
email: 'to-be-verified@example.org',
verifiedAt: expect.any(String),
createdAt: expect.any(String),
},
},
errors: undefined,
})
})
it('connects the new `EmailAddress` as PRIMARY', async () => {
await mutate({ mutation, variables })
const result = await neode.cypher(`
MATCH(u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"})
MATCH(u:User)<-[:BELONGS_TO]-(:EmailAddress {email: "user@example.org"})
RETURN e
`)
const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
await expect(email.toJson()).resolves.toMatchObject({
email: 'to-be-verified@example.org',
})
})
it('removes previous PRIMARY relationship', async () => {
const cypherStatement = `
MATCH(u:User)-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "user@example.org"})
RETURN e
`
let result = await neode.cypher(cypherStatement)
let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
await expect(email.toJson()).resolves.toMatchObject({
email: 'user@example.org',
})
await mutate({ mutation, variables })
result = await neode.cypher(cypherStatement)
email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress'))
await expect(email).toBe(false)
})
})
}) })
}) })
}) })