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.
This commit is contained in:
Robert Schäfer 2019-02-15 01:46:33 +01:00 committed by Matt Rider
parent 5230099e6b
commit 5a995f9f86
4 changed files with 96 additions and 86 deletions

View File

@ -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

View File

@ -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'}]})
})

View File

@ -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;
}

View File

@ -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 } }
}
`
`;
}