emails spec with correct context, clean up

This commit is contained in:
Moriz Wahl 2025-06-26 17:54:19 +02:00
parent 78238606d9
commit 33b06f61c5
4 changed files with 70 additions and 50 deletions

View File

@ -165,7 +165,7 @@ describe('Badges', () => {
errors: [ errors: [
{ {
message: message:
'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', 'UserInputError: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
}, },
], ],
}) })
@ -184,7 +184,7 @@ describe('Badges', () => {
errors: [ errors: [
{ {
message: message:
'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', 'UserInputError: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
}, },
], ],
}) })
@ -203,7 +203,7 @@ describe('Badges', () => {
errors: [ errors: [
{ {
message: message:
'Error: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', 'UserInputError: Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
}, },
], ],
}) })

View File

@ -4,6 +4,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { UserInputError } from 'apollo-server'
import { neo4jgraphql } from 'neo4j-graphql-js' import { neo4jgraphql } from 'neo4j-graphql-js'
import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges' import { TROPHY_BADGES_SELECTED_MAX } from '@constants/badges'
@ -62,10 +63,7 @@ export default {
try { try {
const { relation, user } = await writeTxResultPromise const { relation, user } = await writeTxResultPromise
if (!relation) { if (!relation) {
context.logger.error( throw new UserInputError(
'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
)
throw new Error(
'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', 'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
) )
} }
@ -129,10 +127,7 @@ export default {
).records.map((record) => record.get('user')) ).records.map((record) => record.get('user'))
if (users.length !== 1) { if (users.length !== 1) {
context.logger.error( throw new UserInputError(
'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
)
throw new Error(
'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.', 'Could not reward badge! Ensure the user and the badge exist and the badge is of the correct type.',
) )
} }
@ -140,7 +135,7 @@ export default {
return users[0] return users[0]
}, },
revokeBadge: async (_object, args, context, _resolveInfo) => { revokeBadge: async (_object, args, context: Context, _resolveInfo) => {
const { badgeId, userId } = args const { badgeId, userId } = args
const session = context.driver.session() const session = context.driver.session()
@ -167,7 +162,7 @@ export default {
context.logger.error('revokeBadge', error) context.logger.error('revokeBadge', error)
throw new Error(error) throw new Error(error)
} finally { } finally {
session.close() await session.close()
} }
}, },
}, },

View File

@ -1,22 +1,25 @@
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/await-thenable */ /* eslint-disable @typescript-eslint/await-thenable */
/* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ApolloServer } from 'apollo-server-express'
import { createTestClient } from 'apollo-server-testing' import { createTestClient } from 'apollo-server-testing'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import databaseContext from '@context/database'
import Factory, { cleanDatabase } from '@db/factories' import Factory, { cleanDatabase } from '@db/factories'
import { getDriver, getNeode } from '@db/neo4j' import createServer, { getContext } from '@src/server'
import createServer from '@src/server'
const neode = getNeode()
let mutate, query let mutate, query
let authenticatedUser let authenticatedUser
let user let user
let variables let variables
const driver = getDriver()
const database = databaseContext()
let server: ApolloServer
const loggerErrorMock: (e) => void = jest.fn() const loggerErrorMock: (e) => void = jest.fn()
@ -27,22 +30,21 @@ jest.mock('@src/logger', () => ({
beforeAll(async () => { beforeAll(async () => {
await cleanDatabase() await cleanDatabase()
const { server } = createServer({ const contextUser = async (_req) => authenticatedUser
context: () => { const context = getContext({ user: contextUser, database })
return {
driver, server = createServer({ context }).server
neode,
user: authenticatedUser, const createTestClientResult = createTestClient(server)
} query = createTestClientResult.query
}, mutate = createTestClientResult.mutate
})
mutate = createTestClient(server).mutate
query = createTestClient(server).query
}) })
afterAll(async () => { afterAll(async () => {
await cleanDatabase() await cleanDatabase()
await driver.close() void server.stop()
void database.driver.close()
database.neode.close()
}) })
beforeEach(async () => { beforeEach(async () => {
@ -116,7 +118,7 @@ describe('AddEmailAddress', () => {
it('connects `UnverifiedEmailAddress` to the authenticated user', async () => { it('connects `UnverifiedEmailAddress` to the authenticated user', async () => {
await mutate({ mutation, variables }) await mutate({ mutation, variables })
const result = await neode.cypher( const result = await database.neode.cypher(
` `
MATCH(u:User)-[:PRIMARY_EMAIL]->(:EmailAddress {email: "user@example.org"}) MATCH(u:User)-[:PRIMARY_EMAIL]->(:EmailAddress {email: "user@example.org"})
MATCH(u:User)<-[:BELONGS_TO]-(e:UnverifiedEmailAddress {email: "new-email@example.org"}) MATCH(u:User)<-[:BELONGS_TO]-(e:UnverifiedEmailAddress {email: "new-email@example.org"})
@ -124,7 +126,11 @@ describe('AddEmailAddress', () => {
`, `,
{}, {},
) )
const email = neode.hydrateFirst(result, 'e', neode.model('UnverifiedEmailAddress')) const email = database.neode.hydrateFirst(
result,
'e',
database.neode.model('UnverifiedEmailAddress'),
)
await expect(email.toJson()).resolves.toMatchObject({ await expect(email.toJson()).resolves.toMatchObject({
email: 'new-email@example.org', email: 'new-email@example.org',
nonce: expect.any(String), nonce: expect.any(String),
@ -266,14 +272,18 @@ describe('VerifyEmailAddress', () => {
it('connects the new `EmailAddress` as PRIMARY', async () => { it('connects the new `EmailAddress` as PRIMARY', async () => {
await mutate({ mutation, variables }) await mutate({ mutation, variables })
const result = await neode.cypher( const result = await database.neode.cypher(
` `
MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"}) MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"})
RETURN e RETURN e
`, `,
{}, {},
) )
const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) const email = database.neode.hydrateFirst(
result,
'e',
database.neode.model('EmailAddress'),
)
await expect(email.toJson()).resolves.toMatchObject({ await expect(email.toJson()).resolves.toMatchObject({
email: 'to-be-verified@example.org', email: 'to-be-verified@example.org',
}) })
@ -284,14 +294,18 @@ describe('VerifyEmailAddress', () => {
MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "user@example.org"}) MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "user@example.org"})
RETURN e RETURN e
` `
let result = await neode.cypher(cypherStatement, {}) let result = await database.neode.cypher(cypherStatement, {})
let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) let email = database.neode.hydrateFirst(
result,
'e',
database.neode.model('EmailAddress'),
)
await expect(email.toJson()).resolves.toMatchObject({ await expect(email.toJson()).resolves.toMatchObject({
email: 'user@example.org', email: 'user@example.org',
}) })
await mutate({ mutation, variables }) await mutate({ mutation, variables })
result = await neode.cypher(cypherStatement, {}) result = await database.neode.cypher(cypherStatement, {})
email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) email = database.neode.hydrateFirst(result, 'e', database.neode.model('EmailAddress'))
await expect(email).toBe(false) await expect(email).toBe(false)
}) })
@ -300,14 +314,18 @@ describe('VerifyEmailAddress', () => {
MATCH(u:User {id: "567"})<-[:BELONGS_TO]-(e:EmailAddress {email: "user@example.org"}) MATCH(u:User {id: "567"})<-[:BELONGS_TO]-(e:EmailAddress {email: "user@example.org"})
RETURN e RETURN e
` `
let result = await neode.cypher(cypherStatement, {}) let result = await database.neode.cypher(cypherStatement, {})
let email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) let email = database.neode.hydrateFirst(
result,
'e',
database.neode.model('EmailAddress'),
)
await expect(email.toJson()).resolves.toMatchObject({ await expect(email.toJson()).resolves.toMatchObject({
email: 'user@example.org', email: 'user@example.org',
}) })
await mutate({ mutation, variables }) await mutate({ mutation, variables })
result = await neode.cypher(cypherStatement, {}) result = await database.neode.cypher(cypherStatement, {})
email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) email = database.neode.hydrateFirst(result, 'e', database.neode.model('EmailAddress'))
await expect(email).toBe(false) await expect(email).toBe(false)
}) })
@ -331,14 +349,18 @@ describe('VerifyEmailAddress', () => {
it('connects the new `EmailAddress` as PRIMARY', async () => { it('connects the new `EmailAddress` as PRIMARY', async () => {
await mutate({ mutation, variables }) await mutate({ mutation, variables })
const result = await neode.cypher( const result = await database.neode.cypher(
` `
MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"}) MATCH(u:User {id: "567"})-[:PRIMARY_EMAIL]->(e:EmailAddress {email: "to-be-verified@example.org"})
RETURN e RETURN e
`, `,
{}, {},
) )
const email = neode.hydrateFirst(result, 'e', neode.model('EmailAddress')) const email = database.neode.hydrateFirst(
result,
'e',
database.neode.model('EmailAddress'),
)
await expect(email.toJson()).resolves.toMatchObject({ await expect(email.toJson()).resolves.toMatchObject({
email: 'to-be-verified@example.org', email: 'to-be-verified@example.org',
}) })

View File

@ -16,7 +16,7 @@ import Resolver from './helpers/Resolver'
export default { export default {
Query: { Query: {
VerifyNonce: async (_parent, args, context, _resolveInfo) => { VerifyNonce: async (_parent, args, context: Context, _resolveInfo) => {
args.email = normalizeEmail(args.email) args.email = normalizeEmail(args.email)
const session = context.driver.session() const session = context.driver.session()
const readTxResultPromise = session.readTransaction(async (txc) => { const readTxResultPromise = session.readTransaction(async (txc) => {
@ -32,8 +32,11 @@ export default {
try { try {
const txResult = await readTxResultPromise const txResult = await readTxResultPromise
return txResult.records[0].get('result') return txResult.records[0].get('result')
} catch (e) {
context.logger.error('VerifyNonce query', e)
throw new Error(e.message)
} finally { } finally {
session.close() await session.close()
} }
}, },
}, },
@ -45,7 +48,6 @@ export default {
const { neode } = context const { neode } = context
await new Validator(neode, neode.model('UnverifiedEmailAddress'), args) await new Validator(neode, neode.model('UnverifiedEmailAddress'), args)
} catch (e) { } catch (e) {
// context.logger.error('must be a valid email')
throw new UserInputError('must be a valid email') throw new UserInputError('must be a valid email')
} }
@ -79,6 +81,9 @@ export default {
try { try {
const txResult = await writeTxResultPromise const txResult = await writeTxResultPromise
response = txResult[0] response = txResult[0]
} catch (e) {
context.logger.error('AddEmailAddress mutation', e)
throw new Error(e.message)
} finally { } finally {
await session.close() await session.close()
} }
@ -115,16 +120,14 @@ export default {
response = txResult[0] response = txResult[0]
} catch (e) { } catch (e) {
if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') { if (e.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') {
// context.logger.error('A user account with this email already exists.')
throw new UserInputError('A user account with this email already exists.') throw new UserInputError('A user account with this email already exists.')
} }
// context.logger.error('VerifyEmailAddress', e) context.logger.error('VerifyEmailAddress', e)
throw new Error(e) throw new Error(e)
} finally { } finally {
await session.close() await session.close()
} }
if (!response) { if (!response) {
// context.logger.error('Invalid nonce or no email address found.')
throw new UserInputError('Invalid nonce or no email address found.') throw new UserInputError('Invalid nonce or no email address found.')
} }
return response return response