mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
Merge branch 'master' into 100_import_mongodb_to_neo4j
This commit is contained in:
commit
f5611a6316
@ -23,7 +23,7 @@ install:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- docker-compose exec backend yarn run lint
|
- docker-compose exec backend yarn run lint
|
||||||
- docker-compose exec backend yarn run test
|
- docker-compose exec backend yarn run test --ci
|
||||||
- docker-compose exec backend yarn run test:coverage
|
- docker-compose exec backend yarn run test:coverage
|
||||||
- docker-compose exec backend yarn run db:reset
|
- docker-compose exec backend yarn run db:reset
|
||||||
- docker-compose exec backend yarn run db:seed
|
- docker-compose exec backend yarn run db:seed
|
||||||
|
|||||||
@ -137,6 +137,7 @@ npm run db:reset
|
|||||||
|
|
||||||
**Beware**: We have no multiple database setup at the moment. We clean the database after each test, running the tests will wipe out all your data!
|
**Beware**: We have no multiple database setup at the moment. We clean the database after each test, running the tests will wipe out all your data!
|
||||||
|
|
||||||
|
Run the tests:
|
||||||
```bash
|
```bash
|
||||||
yarn run test
|
yarn run test
|
||||||
# -or-
|
# -or-
|
||||||
|
|||||||
0
dist/.gitkeep
vendored
0
dist/.gitkeep
vendored
14
package.json
14
package.json
@ -9,12 +9,16 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "babel src/ -d dist/ --copy-files",
|
"build": "babel src/ -d dist/ --copy-files",
|
||||||
"start": "node dist/",
|
"start": "node dist/",
|
||||||
"dev": "nodemon --exec babel-node src/index.js",
|
"dev": "nodemon --exec babel-node src/",
|
||||||
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js",
|
"dev:debug": "nodemon --exec babel-node --inspect=0.0.0.0:9229 src/index.js",
|
||||||
"lint": "eslint src --config .eslintrc.js",
|
"lint": "eslint src --config .eslintrc.js",
|
||||||
"test": "nyc --reporter=text-lcov yarn run test:jest",
|
"test": "nyc --reporter=text-lcov yarn run test:jest",
|
||||||
"test:jest": "$npm_package_config_no_auth run-p --race start test:cmd:jest",
|
"test:before:server": "cross-env GRAPHQL_URI=http://localhost:4123 GRAPHQL_PORT=4123 babel-node src/ 2> /dev/null",
|
||||||
"test:cmd:jest": "jest --forceExit",
|
"test:before:seeder": "cross-env GRAPHQL_URI=http://localhost:4001 GRAPHQL_PORT=4001 PERMISSIONS=disabled babel-node src/ 2> /dev/null",
|
||||||
|
"test:jest:cmd": "wait-on tcp:4001 tcp:4123 && jest --forceExit --detectOpenHandles --runInBand",
|
||||||
|
"test:jest:cmd:debug": "wait-on tcp:4001 tcp:4123 && node --inspect-brk ./node_modules/.bin/jest -i --forceExit --detectOpenHandles --runInBand",
|
||||||
|
"test:jest": "run-p --race test:before:* 'test:jest:cmd {@}' --",
|
||||||
|
"test:jest:debug": "run-p --race test:before:* 'test:jest:cmd:debug {@}' --",
|
||||||
"test:coverage": "nyc report --reporter=text-lcov > coverage.lcov",
|
"test:coverage": "nyc report --reporter=text-lcov > coverage.lcov",
|
||||||
"db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js",
|
"db:script:seed": "wait-on tcp:4001 && babel-node src/seed/seed-db.js",
|
||||||
"db:script:reset": "wait-on tcp:4001 && babel-node src/seed/reset-db.js",
|
"db:script:reset": "wait-on tcp:4001 && babel-node src/seed/reset-db.js",
|
||||||
@ -25,7 +29,9 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"jest": {
|
"jest": {
|
||||||
"verbose": true,
|
"verbose": true,
|
||||||
"testMatch": ["**/src/**/?(*.)+(spec|test).js?(x)" ]
|
"testMatch": [
|
||||||
|
"**/src/**/?(*.)+(spec|test).js?(x)"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"apollo-cache-inmemory": "~1.4.0",
|
"apollo-cache-inmemory": "~1.4.0",
|
||||||
|
|||||||
1
scripts/test.sh
Executable file
1
scripts/test.sh
Executable file
@ -0,0 +1 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
@ -1,23 +1,7 @@
|
|||||||
import { request } from 'graphql-request'
|
import { request } from 'graphql-request'
|
||||||
import createServer from './server'
|
|
||||||
import mocks from './mocks'
|
|
||||||
import { create, cleanDatabase } from './seed/factories'
|
import { create, cleanDatabase } from './seed/factories'
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
|
import { host } from './jest/helpers'
|
||||||
let getHost
|
|
||||||
let app
|
|
||||||
let port
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const server = createServer({ mocks })
|
|
||||||
app = await server.start({ port: 0 })
|
|
||||||
port = app.address().port
|
|
||||||
getHost = () => `http://127.0.0.1:${port}`
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await app.close()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('login', () => {
|
describe('login', () => {
|
||||||
const mutation = (params) => {
|
const mutation = (params) => {
|
||||||
@ -45,7 +29,7 @@ describe('login', () => {
|
|||||||
describe('asking for a `token`', () => {
|
describe('asking for a `token`', () => {
|
||||||
describe('with valid email/password combination', () => {
|
describe('with valid email/password combination', () => {
|
||||||
it('responds with a JWT token', async () => {
|
it('responds with a JWT token', async () => {
|
||||||
const data = await request(getHost(), mutation({ email: 'test@example.org', password: '1234' }))
|
const data = await request(host, mutation({ email: 'test@example.org', password: '1234' }))
|
||||||
const { token } = data.login
|
const { token } = data.login
|
||||||
jwt.verify(token, process.env.JWT_SECRET, (err, data) => {
|
jwt.verify(token, process.env.JWT_SECRET, (err, data) => {
|
||||||
expect(data.email).toEqual('test@example.org')
|
expect(data.email).toEqual('test@example.org')
|
||||||
@ -57,7 +41,7 @@ describe('login', () => {
|
|||||||
describe('with a valid email but incorrect password', () => {
|
describe('with a valid email but incorrect password', () => {
|
||||||
it('responds with "Incorrect email address or password."', async () => {
|
it('responds with "Incorrect email address or password."', async () => {
|
||||||
try {
|
try {
|
||||||
await request(getHost(), mutation({ email: 'test@example.org', password: 'wrong' }))
|
await request(host, mutation({ email: 'test@example.org', password: 'wrong' }))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error.response.errors[0].message).toEqual('Incorrect email address or password.')
|
expect(error.response.errors[0].message).toEqual('Incorrect email address or password.')
|
||||||
}
|
}
|
||||||
@ -67,7 +51,7 @@ describe('login', () => {
|
|||||||
describe('with a non-existing email', () => {
|
describe('with a non-existing email', () => {
|
||||||
it('responds with "Incorrect email address or password."', async () => {
|
it('responds with "Incorrect email address or password."', async () => {
|
||||||
try {
|
try {
|
||||||
await request(getHost(), mutation({ email: 'non-existent@example.org', password: 'wrong' }))
|
await request(host, mutation({ email: 'non-existent@example.org', password: 'wrong' }))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error.response.errors[0].message).toEqual('Incorrect email address or password.')
|
expect(error.response.errors[0].message).toEqual('Incorrect email address or password.')
|
||||||
}
|
}
|
||||||
16
src/jest/helpers.js
Normal file
16
src/jest/helpers.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { request } from 'graphql-request'
|
||||||
|
|
||||||
|
export const host = 'http://127.0.0.1:4123'
|
||||||
|
|
||||||
|
export async function authenticatedHeaders ({ email, password }) {
|
||||||
|
const mutation = `
|
||||||
|
mutation {
|
||||||
|
login(email:"${email}", password:"${password}"){
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
const response = await request(host, mutation)
|
||||||
|
return {
|
||||||
|
authorization: `Bearer ${response.login.token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,7 +13,7 @@ export default {
|
|||||||
Query: async (resolve, root, args, context, info) => {
|
Query: async (resolve, root, args, context, info) => {
|
||||||
const result = await resolve(root, args, context, info)
|
const result = await resolve(root, args, context, info)
|
||||||
return walkRecursive(result, ['password'], () => {
|
return walkRecursive(result, ['password'], () => {
|
||||||
// replace password with asterix
|
// replace password with asterisk
|
||||||
return '*****'
|
return '*****'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/middleware/permissionsMiddleware.spec.js
Normal file
72
src/middleware/permissionsMiddleware.spec.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { create, cleanDatabase } from '../seed/factories'
|
||||||
|
import { host, authenticatedHeaders } from '../jest/helpers'
|
||||||
|
import { GraphQLClient } from 'graphql-request'
|
||||||
|
|
||||||
|
describe('authorization', () => {
|
||||||
|
describe('given two existing users', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await create('user', {
|
||||||
|
email: 'owner@example.org',
|
||||||
|
name: 'Owner',
|
||||||
|
password: 'iamtheowner'
|
||||||
|
})
|
||||||
|
await create('user', {
|
||||||
|
email: 'someone@example.org',
|
||||||
|
name: 'Someone else',
|
||||||
|
password: 'else'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await cleanDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('access email address', () => {
|
||||||
|
let headers = {}
|
||||||
|
const action = async (headers) => {
|
||||||
|
const graphQLClient = new GraphQLClient(host, { headers })
|
||||||
|
return graphQLClient.request(`{
|
||||||
|
User(name: "Owner") {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('not logged in', async () => {
|
||||||
|
it('does not expose the owner\'s email address', async () => {
|
||||||
|
try {
|
||||||
|
await action(headers)
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.response.errors[0].message).toEqual('Not Authorised!')
|
||||||
|
expect(error.response.data).toEqual({ User: [ { email: null } ] })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('as owner', () => {
|
||||||
|
it('exposes the owner\'s email address', async () => {
|
||||||
|
headers = await authenticatedHeaders({
|
||||||
|
email: 'owner@example.org',
|
||||||
|
password: 'iamtheowner'
|
||||||
|
})
|
||||||
|
expect(await action(headers)).toEqual({ User: [ { email: 'owner@example.org' } ] })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('as someone else', () => {
|
||||||
|
it('does not expose the owner\'s email address', async () => {
|
||||||
|
headers = await authenticatedHeaders({
|
||||||
|
email: 'someone@example.org',
|
||||||
|
password: 'else'
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await action(headers)
|
||||||
|
} catch (error) {
|
||||||
|
expect(error.response.errors[0].message).toEqual('Not Authorised!')
|
||||||
|
expect(error.response.data).toEqual({ User: [ { email: null } ] })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,15 +0,0 @@
|
|||||||
describe('query', () => {
|
|
||||||
describe('statistics', () => {
|
|
||||||
describe('authenticated user', () => {
|
|
||||||
describe('read', () => {
|
|
||||||
xit('is forbidden', () => {})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('admin', () => {
|
|
||||||
describe('read', () => {
|
|
||||||
xit('is permitted', () => {})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import createOrUpdateLocations from './nodes/locations'
|
import createOrUpdateLocations from './nodes/locations'
|
||||||
|
import find from 'lodash/find'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Mutation: {
|
Mutation: {
|
||||||
@ -12,5 +13,28 @@ export default {
|
|||||||
await createOrUpdateLocations(args.id, args.locationName, context.driver)
|
await createOrUpdateLocations(args.id, args.locationName, context.driver)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Query: {
|
||||||
|
User: async (resolve, root, args, context, info) => {
|
||||||
|
let isIdPresent
|
||||||
|
let removeIdFromResult
|
||||||
|
try {
|
||||||
|
isIdPresent = find(info.fieldNodes[0].selectionSet.selections, item => item.name.value === 'id')
|
||||||
|
if (!isIdPresent) {
|
||||||
|
// add id to request as the user did not ask but we need it
|
||||||
|
info.fieldNodes[0].selectionSet.selections.unshift({
|
||||||
|
kind: 'Field',
|
||||||
|
name: { kind: 'Name', value: 'id' }
|
||||||
|
})
|
||||||
|
removeIdFromResult = true
|
||||||
|
}
|
||||||
|
} catch (err) {}
|
||||||
|
const result = await resolve(root, args, context, info)
|
||||||
|
if (!isIdPresent && removeIdFromResult) {
|
||||||
|
// remove id if the user did not ask for it
|
||||||
|
info.fieldNodes[0].selectionSet.selections.shift()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ export default async function (client) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
/* eslint-disable-next-line no-console */
|
/* eslint-disable-next-line no-console */
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
process.exit(1)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
/* eslint-disable-next-line no-console */
|
/* eslint-disable-next-line no-console */
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import dotenv from 'dotenv'
|
|||||||
import { HttpLink } from 'apollo-link-http'
|
import { HttpLink } from 'apollo-link-http'
|
||||||
import { InMemoryCache } from 'apollo-cache-inmemory'
|
import { InMemoryCache } from 'apollo-cache-inmemory'
|
||||||
import neo4j from '../../bootstrap/neo4j'
|
import neo4j from '../../bootstrap/neo4j'
|
||||||
import { query } from '../../graphql-schema'
|
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
@ -14,12 +13,11 @@ if (process.env.NODE_ENV === 'production') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = new ApolloClient({
|
const client = new ApolloClient({
|
||||||
link: new HttpLink({ uri: process.env.GRAPHQL_URI, fetch }),
|
link: new HttpLink({ uri: 'http://localhost:4001', fetch }),
|
||||||
cache: new InMemoryCache()
|
cache: new InMemoryCache()
|
||||||
})
|
})
|
||||||
|
|
||||||
const driver = neo4j().getDriver()
|
const driver = neo4j().getDriver()
|
||||||
const session = driver.session()
|
|
||||||
|
|
||||||
const builders = {
|
const builders = {
|
||||||
'user': require('./users.js').default
|
'user': require('./users.js').default
|
||||||
@ -29,12 +27,20 @@ const buildMutation = (model, parameters) => {
|
|||||||
return builders[model](parameters)
|
return builders[model](parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
const create = async (model, parameters) => {
|
const create = (model, parameters) => {
|
||||||
await client.mutate({ mutation: gql(buildMutation(model, parameters)) })
|
return client.mutate({ mutation: gql(buildMutation(model, parameters)) })
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleanDatabase = async () => {
|
const cleanDatabase = async () => {
|
||||||
await query('MATCH (n) DETACH DELETE n', session)
|
const session = driver.session()
|
||||||
|
const cypher = 'MATCH (n) DETACH DELETE n'
|
||||||
|
try {
|
||||||
|
const result = await session.run(cypher)
|
||||||
|
session.close()
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user