search spec starts doing what it should

This commit is contained in:
Moriz Wahl 2020-03-04 04:00:29 +01:00 committed by mattwr18
parent f9b6fb95ab
commit a6a2ac4fbe
2 changed files with 202 additions and 115 deletions

View File

@ -1,22 +1,18 @@
import log from './helpers/databaseLogger'
// see http://lucene.apache.org/core/8_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description
export default {
Query: {
findResources: async (_parent, args, context, _resolveInfo) => {
const { query, limit } = args
const { id: thisUserId } = context.user
// see http://lucene.apache.org/core/8_3_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#package.description
const myQuery = query
.replace(/\s+/g, ' ')
.replace(/[[@#:*~\\$|^\]?/"'(){}+?!,.-;]/g, '')
.split(' ')
.map(s => (s.toLowerCase().match(/^(not|and|or)$/) ? '"' + s + '"' : s + '*'))
.join(' ')
const postCypher = `
CALL db.index.fulltext.queryNodes('post_fulltext_search', $query)
YIELD node as resource, score
MATCH (resource)<-[:WROTE]-(author:User)
WHERE score >= 0.5
WHERE score >= 0.2
AND NOT (
author.deleted = true OR author.disabled = true
OR resource.deleted = true OR resource.disabled = true
@ -53,7 +49,7 @@ export default {
thisUserId,
})
const userTransactionResponse = transaction.run(userCypher, {
query: myQuery,
query: createUserQuery(query),
limit,
thisUserId,
})
@ -64,6 +60,8 @@ export default {
const [postResults, userResults] = await searchResultPromise
log(postResults)
log(userResults)
// console.log(postResults.summary.query.parameters)
// console.log(userResults)
return [...postResults.records, ...userResults.records].map(r => r.get('resource'))
} finally {
session.close()
@ -72,16 +70,32 @@ export default {
},
}
function createUserQuery(str) {
// match the whole text
const normalizedString = normalizeWhitespace(str)
const escapedString = escapeSpecialCharacters(normalizedString)
const result = normalizedString.includes(' ') ? quoteString(escapedString) : escapedString
// console.log('"' + + '"')
return result
}
function createPostQuery(str) {
// match the whole text
// console.log('"' + escapeSpecialCharacters(normalizeWhitespace(str)) + '"')
return '"' + escapeSpecialCharacters(normalizeWhitespace(str)) + '"'
const normalizedString = normalizeWhitespace(str)
const escapedString = escapeSpecialCharacters(normalizedString)
const result = normalizedString.includes(' ') ? quoteString(escapedString) : escapedString
// console.log('"' + + '"')
return result
}
function normalizeWhitespace(str) {
return str.replace(/\s+/g, ' ')
}
function quoteString(str) {
return '"' + str + '"'
}
function escapeSpecialCharacters(str) {
return str.replace(/(["[\]&|\\{}+!()^~*?:/-])/g, '\\$1')
}

View File

@ -26,7 +26,7 @@ beforeAll(async () => {
})
afterAll(async () => {
await cleanDatabase()
// await cleanDatabase()
})
const searchQuery = gql`
@ -47,73 +47,149 @@ const searchQuery = gql`
}
`
const nothingFound = { data: { findResources: [] } }
const createExpectedObject = array => {
return { data: { findResources: array } }
}
const addPostToDB = post => {
return Factory.build('post', {
id: post.id,
title: post.title,
content: post.content,
})
}
const addUserToDB = user => {
return Factory.build('user', {
id: user.id,
name: user.name,
slug: user.slug,
})
}
const createDataObject = (obj, type) => {
return { __typename: type, ...obj }
}
const createPostObject = post => {
return createDataObject(post, 'Post')
}
const createUserObject = user => {
return createDataObject(user, 'User')
}
// see data at the end of the file
let user
describe('resolvers', () => {
describe('searches', () => {
beforeAll(async () => {
const user = await Factory.build('user', {
id: 'a-user',
name: 'John Doe',
slug: 'john-doe',
})
await Factory.build('post', {
id: 'a-post',
title: 'Beitrag',
content: 'Ein erster Beitrag',
})
user = await addUserToDB(aUser)
await addPostToDB(aPost)
authenticatedUser = await user.toJson()
})
let variables
describe('basic searches', () => {
it('finds the post', async () => {
variables = { query: 'Beitrag' }
const res = await query({ query: searchQuery, variables })
// console.log(res)
expect(res.data.findResources).toHaveLength(1)
const expected = createExpectedObject([aPost])
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject(expected)
})
it('does not find the post', async () => {
variables = { query: 'Unfug' }
const res = await query({ query: searchQuery, variables })
expect(res.data.findResources).toHaveLength(0)
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject(nothingFound)
})
it('finds the user', async () => {
variables = { query: 'John' }
const res = await query({ query: searchQuery, variables })
// console.log(res)
expect(res.data.findResources).toHaveLength(1)
const expected = createExpectedObject([aUser])
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject(expected)
})
it('does not find the user', async () => {
variables = { query: 'Unfug' }
const res = await query({ query: searchQuery, variables })
expect(res.data.findResources).toHaveLength(0)
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject(nothingFound)
})
})
describe('more data added', () => {
beforeAll(async () => {
await Promise.all([
Factory.build('post', {
id: 'b-post',
title: 'Aufruf',
content: 'Jeder sollte seinen Beitrag leisten.',
}),
Factory.build('post', {
id: 'c-post',
title: 'Die binomischen Formeln',
content: `
1. binomische Formel: (a + b)² = + 2ab +
addPostToDB(bPost),
addPostToDB(cPost),
addPostToDB(dPost),
addPostToDB(ePost),
addPostToDB(fPost),
addPostToDB(gPost),
addUserToDB(bUser),
addUserToDB(cUser),
addUserToDB(dUser),
addUserToDB(eUser),
addUserToDB(fUser),
addUserToDB(gUser),
])
})
it('finds the AK-47', async () => {
variables = { query: 'AK-47' }
const expected = createExpectedObject([gPost])
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject(expected)
})
it('finds more than one post', async () => {
variables = { query: 'Beitrag' }
const expected = createExpectedObject([aPost, bPost])
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject(expected)
})
it('finds more than one user by slug', async () => {
variables = { query: '-maria-' }
const expected = createExpectedObject([dUser, cUser])
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject(expected)
})
it('finds Russian text', async () => {
variables = { query: 'Калашникова' }
const expected = createExpectedObject([gPost])
await expect(query({ query: searchQuery, variables })).resolves.toMatchObject(expected)
})
})
})
})
// data section
const aPost = createPostObject({
id: 'a-post',
title: 'Beitrag',
content: 'Ein erster Beitrag',
})
const bPost = createPostObject({
id: 'b-post',
title: 'Aufruf',
content: 'Jeder sollte seinen Beitrag leisten.',
})
const cPost = createPostObject({
id: 'c-post',
title: 'Die binomischen Formeln',
content: `1. binomische Formel: (a + b)² = a² + 2ab + b²
2. binomische Formel: (a - b)² = - 2ab +
3. binomische Formel: (a + b)(a - b) = -
`,
}),
Factory.build('post', {
id: 'd-post',
title: 'Der Panther',
content: `
Sein Blick ist vom Vorübergehn der Stäbe
3. binomische Formel: (a + b)(a - b) = - `,
})
const dPost = createPostObject({
id: 'd-post',
title: 'Der Panther',
content: `Sein Blick ist vom Vorübergehn der Stäbe
so müd geworden, daß er nichts mehr hält.
Ihm ist, als ob es tausend Stäbe gäbe
und hinter tausend Stäben keine Welt.
@ -126,69 +202,66 @@ in der betäubt ein großer Wille steht.
Nur manchmal schiebt der Vorhang der Pupille
sich lautlos auf . Dann geht ein Bild hinein,
geht durch der Glieder angespannte Stille
und hört im Herzen auf zu sein.
`,
}),
Factory.build('post', {
id: 'e-post',
title: 'Typographie',
content: `
Gelegentlich können sowohl der angeführte Text als auch der Begleitsatz mit Frage- oder Ausrufezeichen enden (§ 91):
Gefällt dir der Roman Quo vadis?? Lass doch dieses ewige Ich will nicht!!
`,
}),
Factory.build('post', {
id: 'f-post',
title: 'Typographie II',
content: `
Der Gedankenstrich kann als Auslassungszeichen (Auslassungsstrich) eine längere Pause oder eine Ellipse darstellen: Du willst doch wohl nicht etwa , Mein Gott, woher nehm ich bloß ?
`,
}),
Factory.build('post', {
id: 'g-post',
title: 'AK-47',
content: `
Vom AK-47 Typ I existiert eine Version mit unter die Waffe klappbarer Schulterstütze, das AKS-47 (russisch Автомат Калашникова складной образца 1947 года, transkr.: Avtomat Kalašnikova skladnoj obrazca 1947 goda, dt. Automat Kalaschnikow klappbar Modell 1947tes Jahr) genannt wird, seltener auch AK-47s.
`,
}),
Factory.build('user', {
id: 'b-user',
name: 'Johnannes der Täufer',
slug: 'johnannes-der-taufer',
}),
Factory.build('user', {
id: 'c-user',
name: 'Rainer Maria Rilke',
slug: 'rainer-maria-rilke',
}),
Factory.build('user', {
id: 'd-user',
name: 'Erich Maria Remarque',
slug: 'erich-maria-remarque',
}),
Factory.build('user', {
id: 'e-user',
name: 'Klaus Dieter',
slug: 'kd',
}),
Factory.build('user', {
id: 'f-user',
name: 'Sluggy',
slug: '_',
}),
Factory.build('user', {
id: 'g-user',
name: 'AKK',
slug: 'akk',
}),
])
})
it('finds the AK-47', async () => {
variables = { query: 'AK-47' }
const res = await query({ query: searchQuery, variables })
expect(res.data.findResources).toHaveLength(1)
})
})
})
und hört im Herzen auf zu sein.`,
})
const ePost = createPostObject({
id: 'e-post',
title: 'Typographie',
content: `Gelegentlich können sowohl der angeführte Text als auch der Begleitsatz mit Frage- oder Ausrufezeichen enden (§ 91):
Gefällt dir der Roman Quo vadis?? Lass doch dieses ewige Ich will nicht!!`,
})
const fPost = createPostObject({
id: 'f-post',
title: 'Typographie II',
content: `Der Gedankenstrich kann als Auslassungszeichen (Auslassungsstrich) eine längere Pause oder eine Ellipse darstellen: „Du willst doch wohl nicht etwa –“, „Mein Gott, woher nehm ich bloß ?“`,
})
const gPost = createPostObject({
id: 'g-post',
title: 'AK-47',
content: `Vom AK-47 Typ I existiert eine Version mit unter die Waffe klappbarer Schulterstütze, das AKS-47 (russisch Автомат Калашникова складной образца 1947 года, transkr.: Avtomat Kalašnikova skladnoj obrazca 1947 goda, dt. Automat Kalaschnikow klappbar Modell 1947tes Jahr) genannt wird, seltener auch AK-47s.`,
})
const aUser = createUserObject({
id: 'a-user',
name: 'John Doe',
slug: 'john-doe',
})
const bUser = createUserObject({
id: 'b-user',
name: 'Johnannes der Täufer',
slug: 'johnannes-der-taufer',
})
const cUser = createUserObject({
id: 'c-user',
name: 'Rainer Maria Rilke',
slug: 'rainer-maria-rilke',
})
const dUser = createUserObject({
id: 'd-user',
name: 'Erich Maria Remarque',
slug: 'erich-maria-remarque',
})
const eUser = createUserObject({
id: 'e-user',
name: 'Klaus Dieter',
slug: 'kd',
})
const fUser = createUserObject({
id: 'f-user',
name: 'Sluggy',
slug: '_',
})
const gUser = createUserObject({
id: 'g-user',
name: 'AKK',
slug: 'akk',
})