UNPKG

@asymmetrik/elastic-querybuilder

Version:
857 lines (774 loc) 18.4 kB
const QueryBuilder = require('../src/index'); const { ERRORS, DEFAULTS } = require('../src/constants'); const mocks = { dis_max_queries: [ { term: { age: 31 }}, { term: { age: 32 }}, { term: { age: 33 }} ], multi_match: { query: 'The Coon', fields: ['superhero', 'name', 'alias'] }, functions: [ { field_value_factor: { field: 'number_of_detentions', factor: 1, modifier: 'ln2p' } } ] }; describe('QueryBuilder', () => { test('should use default values if none are provided in the constructor', () => { const builder = new QueryBuilder(); // Check it's private properties to make sure they equal the defaults expect(builder._query.from).toEqual(DEFAULTS.FROM); expect(builder._query.size).toEqual(DEFAULTS.SIZE); }); test('should be able to update the from, and size settings', () => { const builder = new QueryBuilder(); const newFrom = 15; const newSize = 45; // Check it's private properties to make sure they equal the defaults expect(builder._query.from).toEqual(DEFAULTS.FROM); expect(builder._query.size).toEqual(DEFAULTS.SIZE); // Update them with valid values and make sure they changed builder.from(newFrom).size(newSize); expect(builder._query.from).toEqual(newFrom); expect(builder._query.size).toEqual(newSize); // Update them without a value and make sure they do not get set to undefined builder.from().size(); expect(builder._query.from).toEqual(newFrom); expect(builder._query.size).toEqual(newSize); }); test('should be able set the track_score settings', () => { const builder = new QueryBuilder(); const trackScores = true; // Set the trackScore builder.trackScores(trackScores); expect(builder._query.track_scores).toEqual(trackScores); // Update them without a value and make sure they do not get set to undefined builder.trackScores(); expect(builder._query.track_scores).toEqual(trackScores); }); describe('clone', () => { test('should be able to clone the builder', () => { const builder = new QueryBuilder(); const clone = builder.clone(); expect(clone).toBeInstanceOf(QueryBuilder); expect(clone).not.toBe(builder); }); test('should have the same output on the original and clone but not be the same object', () => { const builder = new QueryBuilder() .should('match', 'alias', 'Mysterion') .from(15) .size(15); const clone = builder.clone(); expect(clone.build()).toEqual(builder.build()); }); test('should be able to modify them separately after cloning them', () => { const builder = new QueryBuilder().from(15).size(15); const clone = builder.clone(); expect(clone).not.toBe(builder); expect(clone.build()).toEqual(builder.build()); builder.should('match', 'alias', 'Mysterion'); expect(clone.build()).not.toEqual(builder.build()); }); }); describe('build', () => { test('should allow me to add raw parameters to the final query', () => { const query = new QueryBuilder() .raw('min_score', 0) .raw('query.boost', 1.2) .raw('query.minimum_should_match', 1) .build(); expect(query).toEqual({ from: 0, size: 15, min_score: 0, query: { match_all: {}, boost: 1.2, minimum_should_match: 1 } }); }); test('should handle a simple match_none query', () => { const query = new QueryBuilder() .query('match_none') .build(); expect(query).toEqual({ from: 0, size: 15, query: { match_none: {} } }); }); test('should handle building query with track_scores', () => { const query = new QueryBuilder() .trackScores(true) .query('match_none') .build(); expect(query).toEqual({ from: 0, size: 15, track_scores: true, query: { match_none: {} } }); }); test('should be able to build a boolean query', () => { const query = new QueryBuilder() .raw('query.bool.boost', 1.2) .must('match', 'name', 'Kenny') .must('match', 'alias', 'Mysterion') .build(); expect(query).toEqual({ from: 0, size: 15, query: { bool: { boost: 1.2, must: [ { match: { name: 'Kenny' }}, { match: { alias: 'Mysterion' }} ] } } }); }); test('should place should filters inside a filter query if there is a must', () => { const query = new QueryBuilder() .raw('query.bool.boost', 1.2) .must('match', 'city', 'South Park') .should('match', 'name', 'Kenny') .should('match', 'alias', 'Mysterion') .build(); expect(query).toEqual({ from: 0, size: 15, query: { bool: { boost: 1.2, filter: { bool: { must: { match: { city: 'South Park' } }, should: [ { match: { name: 'Kenny' }}, { match: { alias: 'Mysterion' }} ] } } } } }); }); test('should place should filters at the top level if there are no other queries', () => { const query = new QueryBuilder() .raw('query.bool.boost', 1.2) .should('match', 'name', 'Kenny') .should('match', 'alias', 'Mysterion') .build(); expect(query).toEqual({ from: 0, size: 15, query: { bool: { boost: 1.2, should: [ { match: { name: 'Kenny' }}, { match: { alias: 'Mysterion' }} ] } } }); }); test('should be able to build a compound boolean query', () => { const query = new QueryBuilder() .raw('query.bool.boost', 1.2) .must('match', 'name', 'Kenny') .must('match', 'alias', 'Mysterion') .should('match_phrase', 'most_common_question', 'Who is Mysterion?') .build(); expect(query).toEqual({ from: 0, size: 15, query: { bool: { boost: 1.2, filter: { bool: { must: [ { match: { name: 'Kenny' }}, { match: { alias: 'Mysterion' }} ], should: { match_phrase: { most_common_question: 'Who is Mysterion?' } } } } } } }); }); test('should build a simple query with sorting options', () => { const query = new QueryBuilder() .must('match', 'grade', '4th') .sort('gpa', { order: 'desc', mode: 'avg' }) .build(); expect(query).toEqual({ from: 0, size: 15, query: { match: { grade: '4th' } }, sort: [{ gpa: { order: 'desc', mode: 'avg' } }] }); }); test('should build a function score query with filters, functions, and settings', () => { const query = new QueryBuilder() .raw('query.function_score.functions', mocks.functions) .raw('query.function_score.score_mode', 'sum') .raw('query.function_score.boost_mode', 'sum') .query('function_score', builder => builder .query('dis_max', { tie_breaker: 1, queries: mocks.dis_max_queries }) ) .build(); expect(query).toEqual({ from: 0, size: 15, query: { function_score: { query: { dis_max: { tie_breaker: 1, queries: [ { term: { age: 31 }}, { term: { age: 32 }}, { term: { age: 33 }} ] } }, functions: [{ field_value_factor: { field: 'number_of_detentions', factor: 1, modifier: 'ln2p' } }], score_mode: 'sum', boost_mode: 'sum' } } }); }); }); describe('buildDisMax', () => { test('should throw an error if no queries are provided', () => { const builder = new QueryBuilder(); function perform_check () { builder.buildDisMax(); } expect(perform_check).toThrowError(ERRORS.NOT_AN_ARRAY); }); test('should include default query params from and to', () => { const query = new QueryBuilder() .buildDisMax({ queries: mocks.dis_max_queries }); expect(query.from).toEqual(DEFAULTS.FROM); expect(query.size).toEqual(DEFAULTS.SIZE); }); test('should be able to add raw parameters', () => { const query = new QueryBuilder() .raw('query.dis_max.tie_breaker', 0.5) .buildDisMax({ queries: mocks.dis_max_queries }); expect(query.query.dis_max.tie_breaker).toEqual(0.5); }); test('should build a dis_max query with options', () => { const query = new QueryBuilder() .buildDisMax({ queries: mocks.dis_max_queries, tie_breaker: 1.2, boost: 2 }); expect(query).toEqual({ from: 0, size: 15, query: { dis_max: { queries: [ { term: { age: 31 }}, { term: { age: 32 }}, { term: { age: 33 }} ], tie_breaker: 1.2, boost: 2 } } }); }); test('should build a dis_max query with sort and options', () => { const query = new QueryBuilder() .sort('number_detentions', { order: 'desc' }) .buildDisMax({ queries: mocks.dis_max_queries, tie_breaker: 1.2, boost: 2 }); expect(query).toEqual({ from: 0, size: 15, query: { dis_max: { queries: [ { term: { age: 31 }}, { term: { age: 32 }}, { term: { age: 33 }} ], tie_breaker: 1.2, boost: 2 } }, sort: [{ number_detentions: { order: 'desc' } }] }); }); test('should build a dis_max query with filters and options', () => { const query = new QueryBuilder() .must('match', 'enemy', 'Cartman') .buildDisMax({ queries: mocks.dis_max_queries, tie_breaker: 1.2, boost: 2 }); expect(query).toEqual({ from: 0, size: 15, query: { bool: { filter: [ { match: { enemy: 'Cartman' } }, { dis_max: { queries: [ { term: { age: 31 }}, { term: { age: 32 }}, { term: { age: 33 }} ], tie_breaker: 1.2, boost: 2 } } ] } } }); }); }); describe('buildMultiMatch', () => { test('should throw an error if no arguments at all are provided', () => { const builder = new QueryBuilder(); function perform_check () { builder.buildMultiMatch(); } expect(perform_check).toThrowError(ERRORS.MULTI_MATCH_ARGS); }); test('should throw an error if no query is provided', () => { const builder = new QueryBuilder(); function perform_check () { builder.buildMultiMatch({ fields: ['name', 'alias'] }); } expect(perform_check).toThrowError(ERRORS.MULTI_MATCH_ARGS); }); test('should throw an error if no fields are provided', () => { const builder = new QueryBuilder(); function perform_check () { builder.buildMultiMatch({ query: 'Heyy, I\'m not fat I\'m big boned.' }); } expect(perform_check).toThrowError(ERRORS.MULTI_MATCH_ARGS); }); test('should include default query params from and to', () => { const query = new QueryBuilder() .buildMultiMatch({ query: mocks.multi_match.query, fields: mocks.multi_match.fields }); expect(query.from).toEqual(DEFAULTS.FROM); expect(query.size).toEqual(DEFAULTS.SIZE); }); test('should be able to add raw parameters', () => { const query = new QueryBuilder() .raw('query.multi_match.tie_breaker', 0.5) .buildMultiMatch({ query: mocks.multi_match.query, fields: mocks.multi_match.fields }); expect(query.query.multi_match.tie_breaker).toEqual(0.5); }); test('should build a multi_match query with options', () => { const query = new QueryBuilder() .buildMultiMatch({ query: mocks.multi_match.query, fields: mocks.multi_match.fields, type: 'best_fields', tie_breaker: 0.3, minimum_should_match: '30%' }); expect(query).toEqual({ from: 0, size: 15, query: { multi_match: { query: 'The Coon', fields: ['superhero', 'name', 'alias'], type: 'best_fields', tie_breaker: 0.3, minimum_should_match: '30%' } } }); }); test('should build a multi_match with filters and options', () => { const query = new QueryBuilder() .must('match', 'grade', '4th') .buildMultiMatch({ query: mocks.multi_match.query, fields: mocks.multi_match.fields, type: 'best_fields', tie_breaker: 0.3, minimum_should_match: '30%' }); expect(query).toEqual({ from: 0, size: 15, query: { bool: { filter: [ { match: { grade: '4th' } }, { multi_match: { query: 'The Coon', fields: ['superhero', 'name', 'alias'], type: 'best_fields', tie_breaker: 0.3, minimum_should_match: '30%' } } ] } } }); }); test('should build simple multi_match with sort options', () => { const query = new QueryBuilder() .sort('_geo_distance', { coordinates: [ -70, 40 ], distance_type: 'arc', order: 'asc', unit: 'mi', mode: 'min' }) .buildMultiMatch({ query: mocks.multi_match.query, fields: mocks.multi_match.fields, type: 'best_fields', tie_breaker: 0.3, minimum_should_match: '30%' }); expect(query).toEqual({ from: 0, size: 15, query: { multi_match: { query: 'The Coon', fields: ['superhero', 'name', 'alias'], type: 'best_fields', tie_breaker: 0.3, minimum_should_match: '30%' } }, sort: [{ _geo_distance: { coordinates: [ -70, 40 ], distance_type: 'arc', order: 'asc', unit: 'mi', mode: 'min' } }] }); }); }); describe('buildFunctionScore', () => { test('should build a function_score query with no query', () => { const query = new QueryBuilder() .buildFunctionScore(); expect(query).toEqual({ from: 0, size: 15, query: { function_score: { query: { match_all: {}}, functions: [] } } }); }); test('should add sorts to a functino query', () => { const query = new QueryBuilder() .sort('_score', {}) .buildFunctionScore(); expect(query).toEqual({ from: 0, size: 15, query: { function_score: { query: { match_all: {}}, functions: [] } }, sort: [{ _score: {} }] }); }); test('should build a function_score query with a query and some functions', () => { const query = new QueryBuilder() .func('field_value_factor', { field: 'number_of_detentions', modifier: 'ln2p', factor: 1 }) .must('dis_max', { tie_breaker: 1, queries: [{ match: { alias: 'The Coon' } }] }) .buildFunctionScore(); expect(query).toEqual({ from: 0, size: 15, query: { function_score: { query: { dis_max: { tie_breaker: 1, queries: [{ match: { alias: 'The Coon' } }] } }, functions: [{ field_value_factor: { field: 'number_of_detentions', modifier: 'ln2p', factor: 1 } }] } } }); }); test('should incorporate raw parameters', () => { const query = new QueryBuilder() .raw('query.function_score.score_mode', 'sum') .must('match', 'city', 'South Park') .func({ filter: { match: { state: 'Colorado' } }, weight: 100 }) .buildFunctionScore(); expect(query).toEqual({ from: 0, size: 15, query: { function_score: { query: { match: { city: 'South Park' } }, functions: [{ filter: { match: { state: 'Colorado' } }, weight: 100 }], score_mode: 'sum' } } }); }); test('should include unfiltered aggregations in the query', () => { const query = new QueryBuilder() .query('match_all') .func('field_value_factor', { field: 'state' }) .aggs('terms', 'grade') .buildFunctionScore(); expect(query).toEqual({ from: 0, size: 15, query: { function_score: { query: { match_all: {} }, functions: [{ field_value_factor: { field: 'state' } }] } }, aggs: { grade: { terms: { field: 'grade' } } } }); }); test('should include filtered aggregations in the query', () => { const query = new QueryBuilder() .query('match', 'grade', '4th') .query('match', 'state', 'Colorado') .func('field_value_factor', { field: 'state' }) .aggs('terms', 'grade', { size: 12 }) .buildFunctionScore({ filterAggs: true }); expect(query).toEqual({ from: 0, size: 15, query: { function_score: { query: { bool: { must: [{ match: { grade: '4th' } }, { match: { state: 'Colorado' } }] } }, functions: [{ field_value_factor: { field: 'state' } }] } }, aggs: { all: { global: {}, aggs: { grade: { aggs: { grade: { terms: { field: 'grade', size: 12 } } }, filter: { bool: { must: { match: { state: 'Colorado' } } } } } } } } }); }); test('should place should filters inside a filter query if there is a must', () => { const query = new QueryBuilder() .must('match', 'city', 'South Park') .should('match', 'name', 'Kenny') .should('match', 'alias', 'Mysterion') .func('field_value_factor', { field: 'state' }) .buildFunctionScore(); expect(query).toEqual({ from: 0, size: 15, query: { function_score: { query: { bool: { filter: { bool: { must: { match: { city: 'South Park' } }, should: [{ match: { name: 'Kenny' } }, { match: { alias: 'Mysterion' } }] } } } }, functions: [{ field_value_factor: { field: 'state' } }] } } }); }); }); });