Merge pull request #171 from Human-Connection/123-unit-test-for-report-mutation

[WIP] Start unit testing report feature
This commit is contained in:
Robert Schäfer 2019-02-22 17:58:44 +01:00 committed by GitHub
commit ed5e206dc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 426 additions and 99 deletions

View File

@ -1,20 +1,18 @@
import { v1 as neo4j } from 'neo4j-driver' import { v1 as neo4j } from 'neo4j-driver'
import dotenv from 'dotenv'
dotenv.config()
let driver let driver
export default function () { export function getDriver (options = {}) {
return { const {
getDriver () { uri = process.env.NEO4J_URI || 'bolt://localhost:7687',
username = process.env.NEO4J_USERNAME || 'neo4j',
password = process.env.NEO4J_PASSWORD || 'neo4j'
} = options
if (!driver) { if (!driver) {
driver = neo4j.driver( driver = neo4j.driver(uri, neo4j.auth.basic(username, password))
process.env.NEO4J_URI || 'bolt://localhost:7687',
neo4j.auth.basic(
process.env.NEO4J_USER || 'neo4j',
process.env.NEO4J_PASSWORD || 'neo4j'
)
)
} }
return driver return driver
} }
}
}

View File

@ -1,7 +1,9 @@
import { request } from 'graphql-request' import { GraphQLClient, request } from 'graphql-request'
import { create, cleanDatabase } from './seed/factories' import Factory from './seed/factories'
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import { host } from './jest/helpers' import { host, login } from './jest/helpers'
const factory = Factory()
describe('login', () => { describe('login', () => {
const mutation = (params) => { const mutation = (params) => {
@ -16,14 +18,14 @@ describe('login', () => {
describe('given an existing user', () => { describe('given an existing user', () => {
beforeEach(async () => { beforeEach(async () => {
await create('user', { await factory.create('user', {
email: 'test@example.org', email: 'test@example.org',
password: '1234' password: '1234'
}) })
}) })
afterEach(async () => { afterEach(async () => {
await cleanDatabase() await factory.cleanDatabase()
}) })
describe('asking for a `token`', () => { describe('asking for a `token`', () => {
@ -60,3 +62,66 @@ describe('login', () => {
}) })
}) })
}) })
describe('report', () => {
beforeEach(async () => {
await factory.create('user', {
email: 'test@example.org',
password: '1234'
})
await factory.create('user', {
id: 'u2',
name: 'abusive-user',
role: 'user',
email: 'abusive-user@example.org'
})
})
afterEach(async () => {
await factory.cleanDatabase()
})
describe('unauthenticated', () => {
let client
it('throws authorization error', async () => {
client = new GraphQLClient(host)
await expect(
client.request(`mutation {
report(
description: "I don't like this user",
resource: {
id: "u2",
type: user
}
) { id, createdAt }
}`)
).rejects.toThrow('Not Authorised')
})
describe('authenticated', () => {
let headers
let response
beforeEach(async () => {
headers = await login({ email: 'test@example.org', password: '1234' })
client = new GraphQLClient(host, { headers })
response = await client.request(`mutation {
report(
description: "I don't like this user",
resource: {
id: "u2",
type: user
}
) { id, createdAt }
}`,
{ headers }
)
})
it('creates a report', () => {
let { id, createdAt } = response.report
expect(response).toEqual({
report: { id, createdAt }
})
})
})
})
})

View File

@ -1,8 +1,10 @@
import { request } from 'graphql-request' import { request } from 'graphql-request'
// this is the to-be-tested server host
// not to be confused with the seeder host
export const host = 'http://127.0.0.1:4123' export const host = 'http://127.0.0.1:4123'
export async function authenticatedHeaders ({ email, password }) { export async function login ({ email, password }) {
const mutation = ` const mutation = `
mutation { mutation {
login(email:"${email}", password:"${password}"){ login(email:"${email}", password:"${password}"){

View File

@ -1,5 +1,5 @@
import { create, cleanDatabase } from '../seed/factories' import { create, cleanDatabase } from '../seed/factories'
import { host, authenticatedHeaders } from '../jest/helpers' import { host, login } from '../jest/helpers'
import { GraphQLClient } from 'graphql-request' import { GraphQLClient } from 'graphql-request'
describe('authorization', () => { describe('authorization', () => {
@ -45,7 +45,7 @@ describe('authorization', () => {
describe('as owner', () => { describe('as owner', () => {
it('exposes the owner\'s email address', async () => { it('exposes the owner\'s email address', async () => {
headers = await authenticatedHeaders({ headers = await login({
email: 'owner@example.org', email: 'owner@example.org',
password: 'iamtheowner' password: 'iamtheowner'
}) })
@ -55,7 +55,7 @@ describe('authorization', () => {
describe('as someone else', () => { describe('as someone else', () => {
it('does not expose the owner\'s email address', async () => { it('does not expose the owner\'s email address', async () => {
headers = await authenticatedHeaders({ headers = await login({
email: 'someone@example.org', email: 'someone@example.org',
password: 'else' password: 'else'
}) })

View File

@ -0,0 +1,23 @@
import uuid from 'uuid/v4'
export default function (params) {
const {
id = uuid(),
key,
type = 'crowdfunding',
status = 'permanent',
icon
} = params
return `
mutation {
CreateBadge(
id: "${id}",
key: "${key}",
type: ${type},
status: ${status},
icon: "${icon}"
) { id }
}
`
}

View File

@ -0,0 +1,21 @@
import uuid from 'uuid/v4'
export default function (params) {
const {
id = uuid(),
name,
slug,
icon
} = params
return `
mutation {
CreateCategory(
id: "${id}",
name: "${name}",
slug: "${slug}",
icon: "${icon}"
) { id, name }
}
`
}

View File

@ -0,0 +1,37 @@
import faker from 'faker'
import uuid from 'uuid/v4'
export default function (params) {
const {
id = uuid(),
content = [
faker.lorem.sentence(),
faker.lorem.sentence()
].join('. '),
disabled = false,
deleted = false
} = params
return `
mutation {
CreateComment(
id: "${id}",
content: "${content}",
disabled: ${disabled},
deleted: ${deleted}
) { id }
}
`
}
export function relate (type, params) {
const { from, to } = params
return `
mutation {
${from}_${type}_${to}: AddComment${type}(
from: { id: "${from}" },
to: { id: "${to}" }
) { from { id } }
}
`
}

View File

@ -1,50 +1,103 @@
import ApolloClient from 'apollo-client' import { GraphQLClient, request } from 'graphql-request'
import gql from 'graphql-tag' import { getDriver } from '../../bootstrap/neo4j'
import dotenv from 'dotenv'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import neo4j from '../../bootstrap/neo4j'
import fetch from 'node-fetch'
dotenv.config() export const seedServerHost = 'http://127.0.0.1:4001'
if (process.env.NODE_ENV === 'production') { const authenticatedHeaders = async ({ email, password }, host) => {
throw new Error('YOU CAN`T RUN FACTORIES IN PRODUCTION MODE') const mutation = `
mutation {
login(email:"${email}", password:"${password}"){
token
}
}`
const response = await request(host, mutation)
return {
authorization: `Bearer ${response.login.token}`
}
} }
const client = new ApolloClient({ const factories = {
link: new HttpLink({ uri: 'http://localhost:4001', fetch }), 'badge': require('./badges.js').default,
cache: new InMemoryCache() 'user': require('./users.js').default,
}) 'organization': require('./organizations.js').default,
'post': require('./posts.js').default,
const driver = neo4j().getDriver() 'comment': require('./comments.js').default,
'category': require('./categories.js').default,
const builders = { 'tag': require('./tags.js').default,
'user': require('./users.js').default 'report': require('./reports.js').default
} }
const buildMutation = (model, parameters) => { const relationFactories = {
return builders[model](parameters) 'user': require('./users.js').relate,
'organization': require('./organizations.js').relate,
'post': require('./posts.js').relate,
'comment': require('./comments.js').relate
} }
const create = (model, parameters) => { export const create = (model, parameters, options) => {
return client.mutate({ mutation: gql(buildMutation(model, parameters)) }) const graphQLClient = new GraphQLClient(seedServerHost, options)
const mutation = factories[model](parameters)
return graphQLClient.request(mutation)
} }
const cleanDatabase = async () => { export const relate = (model, type, parameters, options) => {
const graphQLClient = new GraphQLClient(seedServerHost, options)
const mutation = relationFactories[model](type, parameters)
return graphQLClient.request(mutation)
}
export const cleanDatabase = async (options = {}) => {
const {
driver = getDriver()
} = options
const session = driver.session() const session = driver.session()
const cypher = 'MATCH (n) DETACH DELETE n' const cypher = 'MATCH (n) DETACH DELETE n'
try { try {
const result = await session.run(cypher) return await session.run(cypher)
session.close()
return result
} catch (error) { } catch (error) {
console.log(error) throw (error)
} finally {
session.close()
} }
} }
export { export default function Factory (options = {}) {
create, const {
buildMutation, neo4jDriver = getDriver(),
cleanDatabase seedServerHost = 'http://127.0.0.1:4001'
} = options
const graphQLClient = new GraphQLClient(seedServerHost)
const result = {
neo4jDriver,
seedServerHost,
graphQLClient,
lastResponse: null,
async authenticateAs ({ email, password }) {
const headers = await authenticatedHeaders({ email, password }, seedServerHost)
this.lastResponse = headers
this.graphQLClient = new GraphQLClient(seedServerHost, { headers })
return this
},
async create (node, properties) {
const mutation = factories[node](properties)
this.lastResponse = await this.graphQLClient.request(mutation)
return this
},
async relate (node, relationship, properties) {
const mutation = relationFactories[node](relationship, properties)
this.lastResponse = await this.graphQLClient.request(mutation)
return this
},
async cleanDatabase () {
this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver })
return this
}
}
result.authenticateAs.bind(result)
result.create.bind(result)
result.relate.bind(result)
result.cleanDatabase.bind(result)
return result
} }

View File

@ -0,0 +1,36 @@
import faker from 'faker'
import uuid from 'uuid/v4'
export default function create (params) {
const {
id = uuid(),
name = faker.comany.companyName(),
description = faker.company.catchPhrase(),
disabled = false,
deleted = false
} = params
return `
mutation {
CreateOrganization(
id: "${id}",
name: "${name}",
description: "${description}",
disabled: ${disabled},
deleted: ${deleted}
) { name }
}
`
}
export function relate (type, params) {
const { from, to } = params
return `
mutation {
${from}_${type}_${to}: AddOrganization${type}(
from: { id: "${from}" },
to: { id: "${to}" }
) { from { id } }
}
`
}

View File

@ -0,0 +1,46 @@
import faker from 'faker'
import uuid from 'uuid/v4'
export default function (params) {
const {
id = uuid(),
title = faker.lorem.sentence(),
content = [
faker.lorem.sentence(),
faker.lorem.sentence(),
faker.lorem.sentence(),
faker.lorem.sentence(),
faker.lorem.sentence()
].join('. '),
image = faker.image.image(),
visibility = 'public',
disabled = false,
deleted = false
} = params
return `
mutation {
CreatePost(
id: "${id}",
title: "${title}",
content: "${content}",
image: "${image}",
visibility: ${visibility},
disabled: ${disabled},
deleted: ${deleted}
) { title, content }
}
`
}
export function relate (type, params) {
const { from, to } = params
return `
mutation {
${from}_${type}_${to}: AddPost${type}(
from: { id: "${from}" },
to: { id: "${to}" }
) { from { id } }
}
`
}

View File

@ -0,0 +1,23 @@
import faker from 'faker'
export default function create (params) {
const {
description = faker.lorem.sentence(),
resource: { id: resourceId, type }
} = params
return `
mutation {
report(
description: "${description}",
resource: {
id: "${resourceId}",
type: ${type}
}
) {
id,
createdAt
}
}
`
}

View File

@ -0,0 +1,17 @@
import uuid from 'uuid/v4'
export default function (params) {
const {
id = uuid(),
name
} = params
return `
mutation {
CreateTag(
id: "${id}",
name: "${name}",
) { name }
}
`
}

View File

@ -1,25 +1,30 @@
import faker from 'faker' import faker from 'faker'
import uuid from 'uuid/v4'
export default function (params) { export default function create (params) {
const { const {
id = uuid(),
name = faker.name.findName(), name = faker.name.findName(),
email = faker.internet.email(), email = faker.internet.email(),
password = '1234', password = '1234',
avatar = faker.internet.avatar() role = 'user',
avatar = faker.internet.avatar(),
disabled = false,
deleted = false
} = params } = params
return ` return `
mutation { mutation {
u1: CreateUser( CreateUser(
id: "u1", id: "${id}",
name: "${name}", name: "${name}",
password: "${password}", password: "${password}",
email: "${email}", email: "${email}",
avatar: "${avatar}", avatar: "${avatar}",
role: admin, role: ${role},
disabled: false, disabled: ${disabled},
deleted: false) { deleted: ${deleted}
id ) {
name name
email email
avatar avatar
@ -28,3 +33,15 @@ export default function (params) {
} }
` `
} }
export function relate (type, params) {
const { from, to } = params
return `
mutation {
${from}_${type}_${to}: AddUser${type}(
from: { id: "${from}" },
to: { id: "${to}" }
) { from { id } }
}
`
}

View File

@ -1,30 +1,19 @@
import { query } from '../graphql-schema' import { cleanDatabase } from './factories'
import dotenv from 'dotenv' import dotenv from 'dotenv'
import neo4j from '../bootstrap/neo4j'
dotenv.config() dotenv.config()
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
throw new Error('YOU CAN`T UNSEED IN PRODUCTION MODE') throw new Error(`YOU CAN'T CLEAN THE DATABASE WITH NODE_ENV=${process.env.NODE_ENV}`)
} }
const driver = neo4j().getDriver() (async function () {
const session = driver.session() try {
await cleanDatabase()
const deleteAll = `
MATCH (n)
OPTIONAL MATCH (n)-[r]-()
DELETE n,r
`
query(deleteAll, session).then(() => {
/* eslint-disable-next-line no-console */
console.log('Successfully deleted all nodes and relations!') console.log('Successfully deleted all nodes and relations!')
}).catch((err) => {
/* eslint-disable-next-line no-console */
console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`)
}).finally(() => {
if (session) {
session.close()
}
process.exit(0) process.exit(0)
}) } catch (err) {
console.log(`Error occurred deleting the nodes and relations (reset the db)\n\n${err}`)
process.exit(1)
}
})()

View File

@ -7,7 +7,7 @@ import mocks from './mocks'
import middleware from './middleware' import middleware from './middleware'
import applyDirectives from './bootstrap/directives' import applyDirectives from './bootstrap/directives'
import applyScalars from './bootstrap/scalars' import applyScalars from './bootstrap/scalars'
import neo4j from './bootstrap/neo4j' import { getDriver } from './bootstrap/neo4j'
import passport from 'passport' import passport from 'passport'
import jwtStrategy from './jwt/strategy' import jwtStrategy from './jwt/strategy'
@ -22,7 +22,7 @@ requiredEnvVars.forEach(env => {
} }
}) })
const driver = neo4j().getDriver() const driver = getDriver()
const debug = process.env.NODE_ENV !== 'production' && process.env.DEBUG === 'true' const debug = process.env.NODE_ENV !== 'production' && process.env.DEBUG === 'true'
let schema = makeAugmentedSchema({ let schema = makeAugmentedSchema({