From 5a995f9f86f2d484794faa883de23461d85b8014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 15 Feb 2019 01:46:33 +0100 Subject: [PATCH] Implement test for search @appinteractive could you have a look if sanitization of search queries work? I created a test and I see "unterminated string" exceptions. This is not what we want! All user input should be escaped. --- src/schema.graphql | 3 +- src/schema.graphql.spec.js | 18 ++++- src/seed/factories/index.js | 141 ++++++++++++++++++------------------ src/seed/factories/posts.js | 20 ++--- 4 files changed, 96 insertions(+), 86 deletions(-) diff --git a/src/schema.graphql b/src/schema.graphql index 472b345d7..ee519fff3 100644 --- a/src/schema.graphql +++ b/src/schema.graphql @@ -3,8 +3,7 @@ type Query { statistics: Statistics! findPosts(filter: String!, limit: Int = 10): [Post]! @cypher( statement: """ - CALL db.index.fulltext.queryNodes( - 'full_text_search', $filter+'~') + CALL db.index.fulltext.queryNodes('full_text_search', $filter+'~') YIELD node AS node RETURN node ORDER BY node.createdAt DESC diff --git a/src/schema.graphql.spec.js b/src/schema.graphql.spec.js index 304fdf557..5fdc67d0c 100644 --- a/src/schema.graphql.spec.js +++ b/src/schema.graphql.spec.js @@ -21,7 +21,7 @@ describe('filter for searchQuery', () => { }) await create('post', { title: 'Threepenny Opera', - content: 'And the shark, it has teeth, And it wears them in the face.' + content: 'And the shark, it has teeth, And it wears them in the face.' }) }) @@ -29,6 +29,18 @@ describe('filter for searchQuery', () => { await cleanDatabase() }) + describe('sanitization', () => { + it('escapes cypher statement', async () => { + await request(host, query(`''); + MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r; + CALL db.index.fulltext.queryNodes('full_text_search', '' + `)) + console.log(data) + const data = await request(host, query('the')) + expect(data).toEqual({findPosts: [{title: 'Hamlet'}, {title: 'Threepenny Opera'}]}) + }) + }) + describe('result set', () => { describe('includes posts if search term', () => { it('matches title', async () => { @@ -36,8 +48,8 @@ describe('filter for searchQuery', () => { expect(data).toEqual({findPosts: [{title: 'Hamlet'}]}) }) - it('matches a part of the title', async () => { - const data = await request(host, query('let')) + it('matches mistyped title', async () => { + const data = await request(host, query('amlet')) expect(data).toEqual({findPosts: [{title: 'Hamlet'}]}) }) diff --git a/src/seed/factories/index.js b/src/seed/factories/index.js index a107fc6b7..fb59281fc 100644 --- a/src/seed/factories/index.js +++ b/src/seed/factories/index.js @@ -1,7 +1,16 @@ -import { GraphQLClient, request } from 'graphql-request' -import { getDriver } from '../../bootstrap/neo4j' +import { GraphQLClient, request } from "graphql-request"; +import { getDriver } from "../../bootstrap/neo4j"; -export const seedServerHost = 'http://127.0.0.1:4001' +import createBadge from "./badges.js"; +import createUser from "./users.js"; +import createOrganization from "./organizations.js"; +import createPost from "./posts.js"; +import createComment from "./comments.js"; +import createCategory from "./categories.js"; +import createTag from "./tags.js"; +import createReport from "./reports.js"; + +export const seedServerHost = "http://127.0.0.1:4001"; const authenticatedHeaders = async ({ email, password }, host) => { const mutation = ` @@ -9,95 +18,85 @@ const authenticatedHeaders = async ({ email, password }, host) => { login(email:"${email}", password:"${password}"){ token } - }` - const response = await request(host, mutation) + }`; + const response = await request(host, mutation); return { authorization: `Bearer ${response.login.token}` - } -} - + }; +}; const factories = { - 'badge': require('./badges.js').default, - 'user': require('./users.js').default, - 'organization': require('./organizations.js').default, - 'post': require('./posts.js').default, - 'comment': require('./comments.js').default, - 'category': require('./categories.js').default, - 'tag': require('./tags.js').default, - 'report': require('./reports.js').default -} - -const relationFactories = { - 'user': require('./users.js').relate, - 'organization': require('./organizations.js').relate, - 'post': require('./posts.js').relate, - 'comment': require('./comments.js').relate -} - -export const create = (model, parameters, options) => { - const graphQLClient = new GraphQLClient(seedServerHost, options) - const mutation = factories[model](parameters) - return graphQLClient.request(mutation) -} - -export const relate = (model, type, parameters, options) => { - const graphQLClient = new GraphQLClient(seedServerHost, options) - const mutation = relationFactories[model](type, parameters) - return graphQLClient.request(mutation) -} + Badge: createBadge, + User: createUser, + Organization: createOrganization, + Post: createPost, + Comment: createComment, + Category: createCategory, + Tag: createTag, + Report: createReport +}; export const cleanDatabase = async (options = {}) => { - const { - driver = getDriver() - } = options - const session = driver.session() - const cypher = 'MATCH (n) DETACH DELETE n' + const { driver = getDriver() } = options; + const session = driver.session(); + const cypher = "MATCH (n) DETACH DELETE n"; try { - return await session.run(cypher) + return await session.run(cypher); } catch (error) { - throw (error) + throw error; } finally { - session.close() + session.close(); } -} +}; -export default function Factory (options = {}) { +export default function Factory(options = {}) { const { neo4jDriver = getDriver(), - seedServerHost = 'http://127.0.0.1:4001' - } = options + seedServerHost = "http://127.0.0.1:4001" + } = options; - const graphQLClient = new GraphQLClient(seedServerHost) + const graphQLClient = new GraphQLClient(seedServerHost); const result = { neo4jDriver, seedServerHost, graphQLClient, + factories, 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 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 create(node, properties) { + const mutation = this.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 relate(node, relationship, properties) { + const { from, to } = properties; + const mutation = ` + mutation { + Add${node}${relationship}( + from: { id: "${from}" }, + to: { id: "${to}" } + ) { from { id } } + } + `; + this.lastResponse = await this.graphQLClient.request(mutation); + return this; }, - async cleanDatabase () { - this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver }) - 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 + }; + result.authenticateAs.bind(result); + result.create.bind(result); + result.relate.bind(result); + result.cleanDatabase.bind(result); + return result; } diff --git a/src/seed/factories/posts.js b/src/seed/factories/posts.js index 80f5e289d..179f17a9e 100644 --- a/src/seed/factories/posts.js +++ b/src/seed/factories/posts.js @@ -1,7 +1,7 @@ -import faker from 'faker' -import uuid from 'uuid/v4' +import faker from "faker"; +import uuid from "uuid/v4"; -export default function (params) { +export default function(params) { const { id = uuid(), title = faker.lorem.sentence(), @@ -11,12 +11,12 @@ export default function (params) { faker.lorem.sentence(), faker.lorem.sentence(), faker.lorem.sentence() - ].join('. '), + ].join(". "), image = faker.image.image(), - visibility = 'public', + visibility = "public", disabled = false, deleted = false - } = params + } = params; return ` mutation { @@ -30,11 +30,11 @@ export default function (params) { deleted: ${deleted} ) { title, content } } - ` + `; } -export function relate (type, params) { - const { from, to } = params +export function relate(type, params) { + const { from, to } = params; return ` mutation { ${from}_${type}_${to}: AddPost${type}( @@ -42,5 +42,5 @@ export function relate (type, params) { to: { id: "${to}" } ) { from { id } } } - ` + `; }