require 'httpclient' require_relative 'base_test' class SearchIndexTest < BaseTest describe 'pass request options' do def before_all super @index = @@search_client.init_index(get_test_index_name('options')) end def test_with_wrong_credentials exception = assert_raises Algolia::AlgoliaHttpError do @index.save_object(generate_object('111'), { headers: { 'X-Algolia-Application-Id' => 'XXXXX', 'X-Algolia-API-Key' => 'XXXXX' } }) end assert_equal 'Invalid Application-ID or API key', exception.message end end describe 'save objects' do def before_all super @index = @@search_client.init_index(get_test_index_name('indexing')) end def retrieve_last_object_ids(responses) responses.last.raw_response[:objectIDs] end def test_save_objects responses = Algolia::MultipleResponse.new object_ids = [] obj1 = generate_object('obj1') responses.push(@index.save_object(obj1)) object_ids.push(retrieve_last_object_ids(responses)) obj2 = generate_object response = @index.save_object(obj2, { auto_generate_object_id_if_not_exist: true }) responses.push(response) object_ids.push(retrieve_last_object_ids(responses)) responses.push(@index.save_objects([])) object_ids.push(retrieve_last_object_ids(responses)) obj3 = generate_object('obj3') obj4 = generate_object('obj4') responses.push(@index.save_objects([obj3, obj4])) object_ids.push(retrieve_last_object_ids(responses)) obj5 = generate_object obj6 = generate_object responses.push(@index.save_objects([obj5, obj6], { auto_generate_object_id_if_not_exist: true })) object_ids.push(retrieve_last_object_ids(responses)) object_ids.flatten! objects = 1.upto(1000).map do |i| generate_object(i.to_s) end @index.config.batch_size = 100 responses.push(@index.save_objects(objects)) responses.wait assert_equal obj1[:property], @index.get_object(object_ids[0])[:property] assert_equal obj2[:property], @index.get_object(object_ids[1])[:property] assert_equal obj3[:property], @index.get_object(object_ids[2])[:property] assert_equal obj4[:property], @index.get_object(object_ids[3])[:property] assert_equal obj5[:property], @index.get_object(object_ids[4])[:property] assert_equal obj6[:property], @index.get_object(object_ids[5])[:property] results = @index.get_objects((1..1000).to_a)[:results] results.each do |obj| assert_includes(objects, obj) end assert_equal objects.length, results.length browsed_objects = [] @index.browse_objects do |hit| browsed_objects.push(hit) end assert_equal 1006, browsed_objects.length objects.each do |obj| assert_includes(browsed_objects, obj) end [obj1, obj3, obj4].each do |obj| assert_includes(browsed_objects, obj) end responses = Algolia::MultipleResponse.new obj1[:property] = 'new property' responses.push(@index.partial_update_object(obj1)) obj3[:property] = 'new property 3' obj4[:property] = 'new property 4' responses.push(@index.partial_update_objects([obj3, obj4])) responses.wait assert_equal obj1[:property], @index.get_object(object_ids[0])[:property] assert_equal obj3[:property], @index.get_object(object_ids[2])[:property] assert_equal obj4[:property], @index.get_object(object_ids[3])[:property] delete_by_obj = { objectID: 'obj_del_by', _tags: 'algolia', property: 'property' } @index.save_object!(delete_by_obj) responses = Algolia::MultipleResponse.new responses.push(@index.delete_object(object_ids.shift)) responses.push(@index.delete_by({ tagFilters: ['algolia'] })) responses.push(@index.delete_objects(object_ids)) responses.push(@index.clear_objects) responses.wait browsed_objects = [] @index.browse_objects do |hit| browsed_objects.push(hit) end assert_equal 0, browsed_objects.length end def test_save_object_without_object_id_and_fail exception = assert_raises Algolia::AlgoliaError do @index.save_object(generate_object) end assert_equal "Missing 'objectID'", exception.message end def test_save_objects_with_single_object_and_fail exception = assert_raises Algolia::AlgoliaError do @index.save_objects(generate_object) end assert_equal 'argument must be an array of objects', exception.message end def test_save_objects_with_array_of_integers_and_fail exception = assert_raises Algolia::AlgoliaError do @index.save_objects([2222, 3333]) end assert_equal 'argument must be an array of object, got: 2222', exception.message end end describe 'settings' do def before_all super @index_name = get_test_index_name('settings') @index = @@search_client.init_index(@index_name) end def test_settings @index.save_object!(generate_object('obj1')) settings = { searchableAttributes: %w(attribute1 attribute2 attribute3 ordered(attribute4) unordered(attribute5)), attributesForFaceting: %w(attribute1 filterOnly(attribute2) searchable(attribute3)), unretrievableAttributes: %w( attribute1 attribute2 ), attributesToRetrieve: %w( attribute3 attribute4 ), ranking: %w(asc(attribute1) desc(attribute2) attribute custom exact filters geo proximity typo words), customRanking: %w(asc(attribute1) desc(attribute1)), replicas: [ @index_name + '_replica1', @index_name + '_replica2' ], maxValuesPerFacet: 100, sortFacetValuesBy: 'count', attributesToHighlight: %w( attribute1 attribute2 ), attributesToSnippet: %w(attribute1:10 attribute2:8), highlightPreTag: '<strong>', highlightPostTag: '</strong>', snippetEllipsisText: ' and so on.', restrictHighlightAndSnippetArrays: true, hitsPerPage: 42, paginationLimitedTo: 43, minWordSizefor1Typo: 2, minWordSizefor2Typos: 6, typoTolerance: 'false', allowTyposOnNumericTokens: false, ignorePlurals: true, disableTypoToleranceOnAttributes: %w( attribute1 attribute2 ), disableTypoToleranceOnWords: %w( word1 word2 ), separatorsToIndex: '()[]', queryType: 'prefixNone', removeWordsIfNoResults: 'allOptional', advancedSyntax: true, optionalWords: %w( word1 word2 ), removeStopWords: true, disablePrefixOnAttributes: %w( attribute1 attribute2 ), disableExactOnAttributes: %w( attribute1 attribute2 ), exactOnSingleWordQuery: 'word', enableRules: false, numericAttributesForFiltering: %w( attribute1 attribute2 ), allowCompressionOfIntegerArray: true, attributeForDistinct: 'attribute1', distinct: 2, replaceSynonymsInHighlight: false, minProximity: 7, responseFields: %w( hits hitsPerPage ), maxFacetHits: 100, camelCaseAttributes: %w( attribute1 attribute2 ), decompoundedAttributes: { de: %w(attribute1 attribute2), fi: ['attribute3'] }, keepDiacriticsOnCharacters: 'øé', queryLanguages: %w( en fr ), alternativesAsExact: ['ignorePlurals'], advancedSyntaxFeatures: ['exactPhrase'], userData: { customUserData: 42.0 }, indexLanguages: ['ja'] } @index.set_settings!(settings) # Because the response settings dict contains the extra version key, we # also add it to the expected settings dict to prevent the test to fail # for a missing key. settings[:version] = 2 assert_equal @index.get_settings, settings settings[:typoTolerance] = 'min' settings[:ignorePlurals] = %w(en fr) settings[:removeStopWords] = %w(en fr) settings[:distinct] = true @index.set_settings!(settings) assert_equal @index.get_settings, settings end end describe 'search' do def before_all super @index = @@search_client.init_index(get_test_index_name('search')) @index.save_objects!(create_employee_records, { auto_generate_object_id_if_not_exist: true }) @index.set_settings!(attributesForFaceting: ['searchable(company)']) end def test_search_objects response = @index.search('algolia') assert_equal 2, response[:nbHits] assert_equal 0, Algolia::Search::Index.get_object_position(response, 'nicolas-dessaigne') assert_equal 1, Algolia::Search::Index.get_object_position(response, 'julien-lemoine') assert_equal(-1, Algolia::Search::Index.get_object_position(response, '')) end def test_find_objects exception = assert_raises Algolia::AlgoliaHttpError do @index.find_object(-> (_hit) { false }, { query: '', paginate: false }) end assert_equal 'Object not found', exception.message response = @index.find_object(-> (_hit) { true }, { query: '', paginate: false }) assert_equal 0, response[:position] assert_equal 0, response[:page] condition = -> (obj) do obj.has_key?(:company) && obj[:company] == 'Apple' end exception = assert_raises Algolia::AlgoliaHttpError do @index.find_object(condition, { query: 'algolia', paginate: false }) end assert_equal 'Object not found', exception.message exception = assert_raises Algolia::AlgoliaHttpError do @index.find_object(condition, { query: '', paginate: false, hitsPerPage: 5 }) end assert_equal 'Object not found', exception.message response = @index.find_object(condition, { query: '', paginate: true, hitsPerPage: 5 }) assert_equal 0, response[:position] assert_equal 2, response[:page] response = @index.search('elon', { clickAnalytics: true }) refute_nil response[:queryID] response = @index.search('elon', { facets: '*', facetFilters: ['company:tesla'] }) assert_equal 1, response[:nbHits] response = @index.search('elon', { facets: '*', filters: '(company:tesla OR company:spacex)' }) assert_equal 2, response[:nbHits] response = @index.search_for_facet_values('company', 'a') assert(response[:facetHits].any? { |hit| hit[:value] == 'Algolia' }) assert(response[:facetHits].any? { |hit| hit[:value] == 'Amazon' }) assert(response[:facetHits].any? { |hit| hit[:value] == 'Apple' }) assert(response[:facetHits].any? { |hit| hit[:value] == 'Arista Networks' }) end end describe 'synonyms' do def before_all super @index = @@search_client.init_index(get_test_index_name('synonyms')) end def test_synonyms responses = Algolia::MultipleResponse.new responses.push(@index.save_objects([ { console: 'Sony PlayStation <PLAYSTATIONVERSION>' }, { console: 'Nintendo Switch' }, { console: 'Nintendo Wii U' }, { console: 'Nintendo Game Boy Advance' }, { console: 'Microsoft Xbox' }, { console: 'Microsoft Xbox 360' }, { console: 'Microsoft Xbox One' } ], { auto_generate_object_id_if_not_exist: true })) synonym1 = { objectID: 'gba', type: 'synonym', synonyms: ['gba', 'gameboy advance', 'game boy advance'] } responses.push(@index.save_synonym(synonym1)) synonym2 = { objectID: 'wii_to_wii_u', type: 'onewaysynonym', input: 'wii', synonyms: ['wii U'] } synonym3 = { objectID: 'playstation_version_placeholder', type: 'placeholder', placeholder: '<PLAYSTATIONVERSION>', replacements: ['1', 'One', '2', '3', '4', '4 Pro'] } synonym4 = { objectID: 'ps4', type: 'altcorrection1', word: 'ps4', corrections: ['playstation4'] } synonym5 = { objectID: 'psone', type: 'altcorrection2', word: 'psone', corrections: ['playstationone'] } responses.push(@index.save_synonyms([synonym2, synonym3, synonym4, synonym5])) responses.wait assert_equal synonym1, @index.get_synonym(synonym1[:objectID]) assert_equal synonym2, @index.get_synonym(synonym2[:objectID]) assert_equal synonym3, @index.get_synonym(synonym3[:objectID]) assert_equal synonym4, @index.get_synonym(synonym4[:objectID]) assert_equal synonym5, @index.get_synonym(synonym5[:objectID]) res = @index.search_synonyms('') assert_equal 5, res[:hits].length results = [] @index.browse_synonyms do |synonym| results.push(synonym) end synonyms = [ synonym1, synonym2, synonym3, synonym4, synonym5 ] synonyms.each do |synonym| assert_includes results, synonym end @index.delete_synonym!('gba') exception = assert_raises Algolia::AlgoliaHttpError do @index.get_synonym('gba') end assert_equal 'Synonym set does not exist', exception.message @index.clear_synonyms! res = @index.search_synonyms('') assert_equal 0, res[:nbHits] end describe 'query rules' do def before_all super @index = @@search_client.init_index(get_test_index_name('rules')) end def test_rules responses = Algolia::MultipleResponse.new responses.push(@index.save_objects([ { objectID: 'iphone_7', brand: 'Apple', model: '7' }, { objectID: 'iphone_8', brand: 'Apple', model: '8' }, { objectID: 'iphone_x', brand: 'Apple', model: 'X' }, { objectID: 'one_plus_one', brand: 'OnePlus', model: 'One' }, { objectID: 'one_plus_two', brand: 'OnePlus', model: 'Two' } ], { auto_generate_object_id_if_not_exist: true })) responses.push(@index.set_settings({ attributesForFaceting: %w(brand model) })) rule1 = { objectID: 'brand_automatic_faceting', enabled: false, condition: { anchoring: 'is', pattern: '{facet:brand}' }, consequence: { params: { automaticFacetFilters: [ { facet: 'brand', disjunctive: true, score: 42 } ] } }, validity: [ { from: 1532439300, # 07/24/2018 13:35:00 UTC until: 1532525700 # 07/25/2018 13:35:00 UTC }, { from: 1532612100, # 07/26/2018 13:35:00 UTC until: 1532698500 # 07/27/2018 13:35:00 UTC } ], description: 'Automatic apply the faceting on `brand` if a brand value is found in the query' } responses.push(@index.save_rule(rule1)) rule2 = { objectID: 'query_edits', conditions: [{ anchoring: 'is', pattern: 'mobile phone', alternatives: true }], consequence: { filterPromotes: false, params: { query: { edits: [ { type: 'remove', delete: 'mobile' }, { type: 'replace', delete: 'phone', insert: 'iphone' } ] } } } } rule3 = { objectID: 'query_promo', consequence: { params: { filters: 'brand:OnePlus' } } } rule4 = { objectID: 'query_promo_summer', condition: { context: 'summer' }, consequence: { params: { filters: 'model:One' } } } responses.push(@index.save_rules([rule2, rule3, rule4])) responses.wait assert_equal 1, @index.search('', { ruleContexts: ['summer'] })[:nbHits] assert_equal rule1, rule_without_metadata(@index.get_rule(rule1[:objectID])) assert_equal rule2, rule_without_metadata(@index.get_rule(rule2[:objectID])) assert_equal rule3, rule_without_metadata(@index.get_rule(rule3[:objectID])) assert_equal rule4, rule_without_metadata(@index.get_rule(rule4[:objectID])) assert_equal 4, @index.search_rules('')[:nbHits] results = [] @index.browse_rules do |rule| results.push(rule) end rules = [ rule1, rule2, rule3, rule4 ] results.each do |rule| assert_includes rules, rule_without_metadata(rule) end @index.delete_rule!(rule1[:objectID]) exception = assert_raises Algolia::AlgoliaHttpError do @index.get_rule(rule1[:objectID]) end assert_equal 'ObjectID does not exist', exception.message @index.clear_rules! res = @index.search_rules('') assert_equal 0, res[:nbHits] end end describe 'batching' do def before_all super @index = @@search_client.init_index(get_test_index_name('index_batching')) end def test_index_batching @index.save_objects!([ { objectID: 'one', key: 'value' }, { objectID: 'two', key: 'value' }, { objectID: 'three', key: 'value' }, { objectID: 'four', key: 'value' }, { objectID: 'five', key: 'value' } ]) @index.batch!([ { action: 'addObject', body: { objectID: 'zero', key: 'value' } }, { action: 'updateObject', body: { objectID: 'one', k: 'v' } }, { action: 'partialUpdateObject', body: { objectID: 'two', k: 'v' } }, { action: 'partialUpdateObject', body: { objectID: 'two_bis', key: 'value' } }, { action: 'partialUpdateObjectNoCreate', body: { objectID: 'three', k: 'v' } }, { action: 'deleteObject', body: { objectID: 'four' } } ]) objects = [ { objectID: 'zero', key: 'value' }, { objectID: 'one', k: 'v' }, { objectID: 'two', key: 'value', k: 'v' }, { objectID: 'two_bis', key: 'value' }, { objectID: 'three', key: 'value', k: 'v' }, { objectID: 'five', key: 'value' } ] @index.browse_objects do |object| assert_includes objects, object end end end describe 'replacing' do def before_all super @index = @@search_client.init_index(get_test_index_name('replacing')) end def test_replacing responses = Algolia::MultipleResponse.new responses.push(@index.save_object({ objectID: 'one' })) responses.push(@index.save_rule({ objectID: 'one', condition: { anchoring: 'is', pattern: 'pattern' }, consequence: { params: { query: { edits: [ { type: 'remove', delete: 'pattern' } ] } } } })) responses.push(@index.save_synonym({ objectID: 'one', type: 'synonym', synonyms: %w(one two) })) responses.wait @index.replace_all_objects!([{ objectID: 'two' }]) responses.push(@index.replace_all_rules([{ objectID: 'two', condition: { anchoring: 'is', pattern: 'pattern' }, consequence: { params: { query: { edits: [ { type: 'remove', delete: 'pattern' } ] } } } }])) responses.push(@index.replace_all_synonyms([{ objectID: 'two', type: 'synonym', synonyms: %w(one two) }])) responses.wait exception = assert_raises Algolia::AlgoliaHttpError do @index.get_object('one') end assert_equal 'ObjectID does not exist', exception.message assert_equal 'two', @index.get_object('two')[:objectID] exception = assert_raises Algolia::AlgoliaHttpError do @index.get_rule('one') end assert_equal 'ObjectID does not exist', exception.message assert_equal 'two', @index.get_rule('two')[:objectID] exception = assert_raises Algolia::AlgoliaHttpError do @index.get_synonym('one') end assert_equal 'Synonym set does not exist', exception.message assert_equal 'two', @index.get_synonym('two')[:objectID] end end describe 'exists' do def before_all super @index = @@search_client.init_index(get_test_index_name('exists')) end def test_exists refute @index.exists? @index.save_object!(generate_object('111')) assert @index.exists? @index.delete! refute @index.exists? end end end end