algolia 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.circleci/config.yml +10 -1
- data/.dockerignore +38 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +36 -6
- data/CONTRIBUTING.MD +184 -0
- data/DOCKER_README.MD +89 -0
- data/Dockerfile +7 -0
- data/README.md +1 -1
- data/lib/algolia/config/recommend_config.rb +6 -0
- data/lib/algolia/helpers.rb +49 -0
- data/lib/algolia/http/http_requester.rb +4 -4
- data/lib/algolia/recommend_client.rb +134 -0
- data/lib/algolia/responses/dictionary_response.rb +33 -0
- data/lib/algolia/search_client.rb +177 -3
- data/lib/algolia/search_index.rb +9 -50
- data/lib/algolia/version.rb +1 -1
- data/lib/algolia.rb +3 -0
- data/test/algolia/integration/analytics_client_test.rb +6 -2
- data/test/algolia/integration/mocks/mock_requester.rb +13 -11
- data/test/algolia/integration/recommend_client_test.rb +70 -0
- data/test/algolia/integration/search_client_test.rb +107 -12
- data/test/algolia/integration/search_index_test.rb +3 -0
- data/test/test_helper.rb +28 -0
- metadata +12 -3
@@ -0,0 +1,33 @@
|
|
1
|
+
module Algolia
|
2
|
+
class DictionaryResponse < BaseResponse
|
3
|
+
include CallType
|
4
|
+
|
5
|
+
attr_reader :raw_response
|
6
|
+
|
7
|
+
# @param client [Search::Client] Algolia Search Client used for verification
|
8
|
+
# @param response [Hash] Raw response from the client
|
9
|
+
#
|
10
|
+
def initialize(client, response)
|
11
|
+
@client = client
|
12
|
+
@raw_response = response
|
13
|
+
@done = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Wait for the task to complete
|
17
|
+
#
|
18
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
19
|
+
#
|
20
|
+
def wait(_opts = {})
|
21
|
+
until @done
|
22
|
+
res = @client.custom_request({}, path_encode('/1/task/%s', @raw_response[:taskID]), :GET, READ)
|
23
|
+
status = get_option(res, 'status')
|
24
|
+
if status == 'published'
|
25
|
+
@done = true
|
26
|
+
end
|
27
|
+
sleep(Defaults::WAIT_TASK_DEFAULT_TIME_BEFORE_RETRY / 1000)
|
28
|
+
end
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -80,7 +80,7 @@ module Algolia
|
|
80
80
|
end
|
81
81
|
|
82
82
|
# # # # # # # # # # # # # # # # # # # # #
|
83
|
-
#
|
83
|
+
# INDEX METHODS
|
84
84
|
# # # # # # # # # # # # # # # # # # # # #
|
85
85
|
|
86
86
|
# Initialize an index with a given name
|
@@ -300,7 +300,7 @@ module Algolia
|
|
300
300
|
# @return [AddApiKeyResponse]
|
301
301
|
#
|
302
302
|
def add_api_key!(acl, opts = {})
|
303
|
-
response = add_api_key(acl)
|
303
|
+
response = add_api_key(acl, opts)
|
304
304
|
|
305
305
|
response.wait(opts)
|
306
306
|
end
|
@@ -480,6 +480,7 @@ module Algolia
|
|
480
480
|
def multiple_queries(queries, opts = {})
|
481
481
|
@transporter.read(:POST, '/1/indexes/*/queries', { requests: queries }, opts)
|
482
482
|
end
|
483
|
+
alias_method :search, :multiple_queries
|
483
484
|
|
484
485
|
# # # # # # # # # # # # # # # # # # # # #
|
485
486
|
# MCM METHODS
|
@@ -594,12 +595,185 @@ module Algolia
|
|
594
595
|
@transporter.read(:GET, '/1/clusters/mapping/pending' + handle_params({ getClusters: retrieve_mappings }), {}, request_options)
|
595
596
|
end
|
596
597
|
|
597
|
-
#
|
598
598
|
# Aliases the pending_mappings? method
|
599
599
|
#
|
600
600
|
alias_method :has_pending_mappings, :pending_mappings?
|
601
601
|
|
602
|
+
# # # # # # # # # # # # # # # # # # # # #
|
603
|
+
# CUSTOM DICTIONARIES METHODS
|
604
|
+
# # # # # # # # # # # # # # # # # # # # #
|
605
|
+
|
606
|
+
# Save entries for a given dictionary
|
607
|
+
#
|
608
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
609
|
+
# @param dictionary_entries [Array<Hash>] array of dictionary entries
|
610
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
611
|
+
#
|
612
|
+
# @return DictionaryResponse
|
613
|
+
#
|
614
|
+
def save_dictionary_entries(dictionary, dictionary_entries, opts = {})
|
615
|
+
response = @transporter.write(
|
616
|
+
:POST,
|
617
|
+
path_encode('/1/dictionaries/%s/batch', dictionary),
|
618
|
+
{ clearExistingDictionaryEntries: false, requests: chunk('addEntry', dictionary_entries) },
|
619
|
+
opts
|
620
|
+
)
|
621
|
+
|
622
|
+
DictionaryResponse.new(self, response)
|
623
|
+
end
|
624
|
+
|
625
|
+
# Save entries for a given dictionary and wait for the task to finish
|
626
|
+
#
|
627
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
628
|
+
# @param dictionary_entries [Array<Hash>] array of dictionary entries
|
629
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
630
|
+
#
|
631
|
+
def save_dictionary_entries!(dictionary, dictionary_entries, opts = {})
|
632
|
+
response = save_dictionary_entries(dictionary, dictionary_entries, opts)
|
633
|
+
|
634
|
+
response.wait(opts)
|
635
|
+
end
|
636
|
+
|
637
|
+
# Replace entries for a given dictionary
|
638
|
+
#
|
639
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
640
|
+
# @param dictionary_entries [Array<Hash>] array of dictionary entries
|
641
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
642
|
+
#
|
643
|
+
# @return DictionaryResponse
|
644
|
+
#
|
645
|
+
def replace_dictionary_entries(dictionary, dictionary_entries, opts = {})
|
646
|
+
response = @transporter.write(
|
647
|
+
:POST,
|
648
|
+
path_encode('/1/dictionaries/%s/batch', dictionary),
|
649
|
+
{ clearExistingDictionaryEntries: true, requests: chunk('addEntry', dictionary_entries) },
|
650
|
+
opts
|
651
|
+
)
|
652
|
+
|
653
|
+
DictionaryResponse.new(self, response)
|
654
|
+
end
|
655
|
+
|
656
|
+
# Replace entries for a given dictionary and wait for the task to finish
|
657
|
+
#
|
658
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
659
|
+
# @param dictionary_entries [Array<Hash>] array of dictionary entries
|
660
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
661
|
+
#
|
662
|
+
def replace_dictionary_entries!(dictionary, dictionary_entries, opts = {})
|
663
|
+
response = replace_dictionary_entries(dictionary, dictionary_entries, opts)
|
664
|
+
|
665
|
+
response.wait(opts)
|
666
|
+
end
|
667
|
+
|
668
|
+
# Delete entries for a given dictionary
|
669
|
+
#
|
670
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
671
|
+
# @param object_ids [Array<Hash>] array of object ids
|
672
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
673
|
+
#
|
674
|
+
# @return DictionaryResponse
|
675
|
+
#
|
676
|
+
def delete_dictionary_entries(dictionary, object_ids, opts = {})
|
677
|
+
request = object_ids.map do |object_id|
|
678
|
+
{ objectID: object_id }
|
679
|
+
end
|
680
|
+
response = @transporter.write(
|
681
|
+
:POST,
|
682
|
+
path_encode('/1/dictionaries/%s/batch', dictionary),
|
683
|
+
{ clearExistingDictionaryEntries: false, requests: chunk('deleteEntry', request) },
|
684
|
+
opts
|
685
|
+
)
|
686
|
+
|
687
|
+
DictionaryResponse.new(self, response)
|
688
|
+
end
|
689
|
+
|
690
|
+
# Delete entries for a given dictionary and wait for the task to finish
|
691
|
+
#
|
692
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
693
|
+
# @param object_ids [Array<Hash>] array of object ids
|
694
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
602
695
|
#
|
696
|
+
def delete_dictionary_entries!(dictionary, object_ids, opts = {})
|
697
|
+
response = delete_dictionary_entries(dictionary, object_ids, opts)
|
698
|
+
|
699
|
+
response.wait(opts)
|
700
|
+
end
|
701
|
+
|
702
|
+
# Clear all entries for a given dictionary
|
703
|
+
#
|
704
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
705
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
706
|
+
#
|
707
|
+
# @return DictionaryResponse
|
708
|
+
#
|
709
|
+
def clear_dictionary_entries(dictionary, opts = {})
|
710
|
+
replace_dictionary_entries(dictionary, [], opts)
|
711
|
+
end
|
712
|
+
|
713
|
+
# Clear all entries for a given dictionary and wait for the task to finish
|
714
|
+
#
|
715
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
716
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
717
|
+
#
|
718
|
+
def clear_dictionary_entries!(dictionary, opts = {})
|
719
|
+
response = replace_dictionary_entries(dictionary, [], opts)
|
720
|
+
|
721
|
+
response.wait(opts)
|
722
|
+
end
|
723
|
+
|
724
|
+
# Search entries for a given dictionary
|
725
|
+
#
|
726
|
+
# @param dictionary [String] dictionary name. Can be either 'stopwords', 'plurals' or 'compounds'
|
727
|
+
# @param query [String] query to send
|
728
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
729
|
+
#
|
730
|
+
def search_dictionary_entries(dictionary, query, opts = {})
|
731
|
+
@transporter.read(
|
732
|
+
:POST,
|
733
|
+
path_encode('/1/dictionaries/%s/search', dictionary),
|
734
|
+
{ query: query },
|
735
|
+
opts
|
736
|
+
)
|
737
|
+
end
|
738
|
+
|
739
|
+
# Set settings for all the dictionaries
|
740
|
+
#
|
741
|
+
# @param dictionary_settings [Hash]
|
742
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
743
|
+
#
|
744
|
+
# @return DictionaryResponse
|
745
|
+
#
|
746
|
+
def set_dictionary_settings(dictionary_settings, opts = {})
|
747
|
+
response = @transporter.write(:PUT, '/1/dictionaries/*/settings', dictionary_settings, opts)
|
748
|
+
|
749
|
+
DictionaryResponse.new(self, response)
|
750
|
+
end
|
751
|
+
|
752
|
+
# Set settings for all the dictionaries and wait for the task to finish
|
753
|
+
#
|
754
|
+
# @param dictionary_settings [Hash]
|
755
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
756
|
+
#
|
757
|
+
# @return DictionaryResponse
|
758
|
+
#
|
759
|
+
def set_dictionary_settings!(dictionary_settings, opts = {})
|
760
|
+
response = set_dictionary_settings(dictionary_settings, opts)
|
761
|
+
|
762
|
+
response.wait(opts)
|
763
|
+
end
|
764
|
+
|
765
|
+
# Retrieve settings for all the dictionaries
|
766
|
+
#
|
767
|
+
# @param opts [Hash] contains extra parameters to send with your query
|
768
|
+
#
|
769
|
+
def get_dictionary_settings(opts = {})
|
770
|
+
@transporter.read(:GET, '/1/dictionaries/*/settings', {}, opts)
|
771
|
+
end
|
772
|
+
|
773
|
+
# # # # # # # # # # # # # # # # # # # # #
|
774
|
+
# MISC METHODS
|
775
|
+
# # # # # # # # # # # # # # # # # # # # #
|
776
|
+
|
603
777
|
# Method available to make custom requests to the API
|
604
778
|
#
|
605
779
|
def custom_request(data, uri, method, call_type, opts = {})
|
data/lib/algolia/search_index.rb
CHANGED
@@ -987,7 +987,15 @@ module Algolia
|
|
987
987
|
# @return [IndexingResponse]
|
988
988
|
#
|
989
989
|
def set_settings(settings, opts = {})
|
990
|
-
|
990
|
+
request_options = symbolize_hash(opts)
|
991
|
+
forward_to_replicas = request_options.delete(:forwardToReplicas) || false
|
992
|
+
|
993
|
+
response = @transporter.write(
|
994
|
+
:PUT,
|
995
|
+
path_encode('/1/indexes/%s/settings', @name) + handle_params({ forwardToReplicas: forward_to_replicas }),
|
996
|
+
settings,
|
997
|
+
request_options
|
998
|
+
)
|
991
999
|
|
992
1000
|
IndexingResponse.new(self, response)
|
993
1001
|
end
|
@@ -1037,55 +1045,6 @@ module Algolia
|
|
1037
1045
|
|
1038
1046
|
private
|
1039
1047
|
|
1040
|
-
# Check the passed object to determine if it's an array
|
1041
|
-
#
|
1042
|
-
# @param object [Object]
|
1043
|
-
#
|
1044
|
-
def check_array(object)
|
1045
|
-
raise AlgoliaError, 'argument must be an array of objects' unless object.is_a?(Array)
|
1046
|
-
end
|
1047
|
-
|
1048
|
-
# Check the passed object
|
1049
|
-
#
|
1050
|
-
# @param object [Object]
|
1051
|
-
# @param in_array [Boolean] whether the object is an array or not
|
1052
|
-
#
|
1053
|
-
def check_object(object, in_array = false)
|
1054
|
-
case object
|
1055
|
-
when Array
|
1056
|
-
raise AlgoliaError, in_array ? 'argument must be an array of objects' : 'argument must not be an array'
|
1057
|
-
when String, Integer, Float, TrueClass, FalseClass, NilClass
|
1058
|
-
raise AlgoliaError, "argument must be an #{'array of' if in_array} object, got: #{object.inspect}"
|
1059
|
-
end
|
1060
|
-
end
|
1061
|
-
|
1062
|
-
# Check if passed object has a objectID
|
1063
|
-
#
|
1064
|
-
# @param object [Object]
|
1065
|
-
# @param object_id [String]
|
1066
|
-
#
|
1067
|
-
def get_object_id(object, object_id = nil)
|
1068
|
-
check_object(object)
|
1069
|
-
object_id ||= object[:objectID] || object['objectID']
|
1070
|
-
raise AlgoliaError, "Missing 'objectID'" if object_id.nil?
|
1071
|
-
object_id
|
1072
|
-
end
|
1073
|
-
|
1074
|
-
# Build a batch request
|
1075
|
-
#
|
1076
|
-
# @param action [String] action to perform on the engine
|
1077
|
-
# @param objects [Array] objects on which build the action
|
1078
|
-
# @param with_object_id [Boolean] if set to true, check if each object has an objectID set
|
1079
|
-
#
|
1080
|
-
def chunk(action, objects, with_object_id = false)
|
1081
|
-
objects.map do |object|
|
1082
|
-
check_object(object, true)
|
1083
|
-
request = { action: action, body: object }
|
1084
|
-
request[:objectID] = get_object_id(object).to_s if with_object_id
|
1085
|
-
request
|
1086
|
-
end
|
1087
|
-
end
|
1088
|
-
|
1089
1048
|
def raw_batch(requests, opts)
|
1090
1049
|
@transporter.write(:POST, path_encode('/1/indexes/%s/batch', @name), { requests: requests }, opts)
|
1091
1050
|
end
|
data/lib/algolia/version.rb
CHANGED
data/lib/algolia.rb
CHANGED
@@ -7,6 +7,7 @@ require 'algolia/config/base_config'
|
|
7
7
|
require 'algolia/config/search_config'
|
8
8
|
require 'algolia/config/analytics_config'
|
9
9
|
require 'algolia/config/insights_config'
|
10
|
+
require 'algolia/config/recommend_config'
|
10
11
|
require 'algolia/config/recommendation_config'
|
11
12
|
require 'algolia/enums/call_type'
|
12
13
|
require 'algolia/enums/retry_outcome_type'
|
@@ -20,6 +21,7 @@ require 'algolia/responses/indexing_response'
|
|
20
21
|
require 'algolia/responses/add_api_key_response'
|
21
22
|
require 'algolia/responses/update_api_key_response'
|
22
23
|
require 'algolia/responses/delete_api_key_response'
|
24
|
+
require 'algolia/responses/dictionary_response'
|
23
25
|
require 'algolia/responses/restore_api_key_response'
|
24
26
|
require 'algolia/responses/multiple_batch_indexing_response'
|
25
27
|
require 'algolia/responses/multiple_response'
|
@@ -32,6 +34,7 @@ require 'algolia/account_client'
|
|
32
34
|
require 'algolia/search_client'
|
33
35
|
require 'algolia/analytics_client'
|
34
36
|
require 'algolia/insights_client'
|
37
|
+
require 'algolia/recommend_client'
|
35
38
|
require 'algolia/recommendation_client'
|
36
39
|
require 'algolia/error'
|
37
40
|
require 'algolia/search_index'
|
@@ -23,7 +23,9 @@ class AnalyticsClientTest < BaseTest
|
|
23
23
|
endAt: tomorrow.strftime('%Y-%m-%dT%H:%M:%SZ')
|
24
24
|
}
|
25
25
|
|
26
|
-
response =
|
26
|
+
response = retry_test do
|
27
|
+
client.add_ab_test(ab_test)
|
28
|
+
end
|
27
29
|
ab_test_id = response[:abTestID]
|
28
30
|
|
29
31
|
index1.wait_task(response[:taskID])
|
@@ -86,7 +88,9 @@ class AnalyticsClientTest < BaseTest
|
|
86
88
|
endAt: tomorrow.strftime('%Y-%m-%dT%H:%M:%SZ')
|
87
89
|
}
|
88
90
|
|
89
|
-
response =
|
91
|
+
response = retry_test do
|
92
|
+
client.add_ab_test(ab_test)
|
93
|
+
end
|
90
94
|
ab_test_id = response[:abTestID]
|
91
95
|
|
92
96
|
index.wait_task(response[:taskID])
|
@@ -1,25 +1,27 @@
|
|
1
1
|
class MockRequester
|
2
|
+
attr_accessor :requests
|
2
3
|
def initialize
|
3
4
|
@connection = nil
|
5
|
+
@requests = []
|
4
6
|
end
|
5
7
|
|
6
|
-
def send_request(host, method, path,
|
7
|
-
|
8
|
-
response = {
|
9
|
-
connection: connection,
|
8
|
+
def send_request(host, method, path, body, headers, timeout, connect_timeout)
|
9
|
+
request = {
|
10
10
|
host: host,
|
11
|
+
method: method,
|
11
12
|
path: path,
|
13
|
+
body: body,
|
12
14
|
headers: headers,
|
13
|
-
|
14
|
-
|
15
|
-
body: '{"hits":[],"nbHits":0,"page":0,"nbPages":1,"hitsPerPage":20,"exhaustiveNbHits":true,"query":"test","params":"query=test","processingTimeMS":1}',
|
16
|
-
success: true
|
15
|
+
timeout: timeout,
|
16
|
+
connect_timeout: connect_timeout
|
17
17
|
}
|
18
18
|
|
19
|
+
@requests.push(request)
|
20
|
+
|
19
21
|
Algolia::Http::Response.new(
|
20
|
-
status:
|
21
|
-
body:
|
22
|
-
headers:
|
22
|
+
status: 200,
|
23
|
+
body: '{"hits": []}',
|
24
|
+
headers: {}
|
23
25
|
)
|
24
26
|
end
|
25
27
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require_relative 'base_test'
|
3
|
+
|
4
|
+
class RecommendClientTest < BaseTest
|
5
|
+
describe 'Recommendations' do
|
6
|
+
def test_get_recommendations
|
7
|
+
requester = MockRequester.new
|
8
|
+
client = Algolia::Recommend::Client.new(@@search_config, http_requester: requester)
|
9
|
+
|
10
|
+
# It correctly formats queries using the 'bought-together' model
|
11
|
+
client.get_recommendations([{ indexName: 'products', objectID: 'B018APC4LE', model: Algolia::Recommend::Model::BOUGHT_TOGETHER }])
|
12
|
+
|
13
|
+
# It correctly formats queries using the 'related-products' model
|
14
|
+
client.get_recommendations([{ indexName: 'products', objectID: 'B018APC4LE', model: Algolia::Recommend::Model::RELATED_PRODUCTS }])
|
15
|
+
|
16
|
+
# It correctly formats multiple queries.
|
17
|
+
client.get_recommendations(
|
18
|
+
[
|
19
|
+
{ indexName: 'products', objectID: 'B018APC4LE-1', model: Algolia::Recommend::Model::RELATED_PRODUCTS, threshold: 0 },
|
20
|
+
{ indexName: 'products', objectID: 'B018APC4LE-2', model: Algolia::Recommend::Model::RELATED_PRODUCTS, threshold: 0 }
|
21
|
+
]
|
22
|
+
)
|
23
|
+
|
24
|
+
# It resets the threshold to 0 if it's not numeric.
|
25
|
+
client.get_recommendations([{ indexName: 'products', objectID: 'B018APC4LE', model: Algolia::Recommend::Model::BOUGHT_TOGETHER, threshold: nil }])
|
26
|
+
|
27
|
+
# It passes the threshold correctly if it's numeric.
|
28
|
+
client.get_recommendations([{ indexName: 'products', objectID: 'B018APC4LE', model: Algolia::Recommend::Model::BOUGHT_TOGETHER, threshold: 42 }])
|
29
|
+
|
30
|
+
assert_requests(
|
31
|
+
requester,
|
32
|
+
[
|
33
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":0}]}' },
|
34
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"related-products","threshold":0}]}' },
|
35
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE-1","model":"related-products","threshold":0},{"indexName":"products","objectID":"B018APC4LE-2","model":"related-products","threshold":0}]}' },
|
36
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":0}]}' },
|
37
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":42}]}' }
|
38
|
+
]
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_get_related_products
|
43
|
+
requester = MockRequester.new
|
44
|
+
client = Algolia::Recommend::Client.new(@@search_config, http_requester: requester)
|
45
|
+
|
46
|
+
client.get_related_products([{ indexName: 'products', objectID: 'B018APC4LE' }])
|
47
|
+
|
48
|
+
assert_requests(
|
49
|
+
requester,
|
50
|
+
[{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"related-products","threshold":0}]}' }]
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_get_frequently_bought_together
|
55
|
+
requester = MockRequester.new
|
56
|
+
client = Algolia::Recommend::Client.new(@@search_config, http_requester: requester)
|
57
|
+
|
58
|
+
client.get_frequently_bought_together([{ indexName: 'products', objectID: 'B018APC4LE' }])
|
59
|
+
client.get_frequently_bought_together([{ indexName: 'products', objectID: 'B018APC4LE', fallbackParameters: {} }])
|
60
|
+
|
61
|
+
assert_requests(
|
62
|
+
requester,
|
63
|
+
[
|
64
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":0}]}' },
|
65
|
+
{ method: :post, path: '/1/indexes/*/recommendations', body: '{"requests":[{"indexName":"products","objectID":"B018APC4LE","model":"bought-together","threshold":0}]}' }
|
66
|
+
]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'securerandom'
|
1
2
|
require_relative 'base_test'
|
2
3
|
|
3
4
|
class SearchClientTest < BaseTest
|
@@ -215,6 +216,7 @@ class SearchClientTest < BaseTest
|
|
215
216
|
|
216
217
|
def test_api_keys
|
217
218
|
assert_equal ['search'], @api_key[:acl]
|
219
|
+
assert_equal 'A description', @api_key[:description]
|
218
220
|
|
219
221
|
api_keys = @@search_client.list_api_keys[:keys].map do |key|
|
220
222
|
key[:value]
|
@@ -222,7 +224,9 @@ class SearchClientTest < BaseTest
|
|
222
224
|
assert_includes api_keys, @api_key[:value]
|
223
225
|
|
224
226
|
@@search_client.update_api_key!(@api_key[:value], { maxHitsPerQuery: 42 })
|
225
|
-
updated_api_key =
|
227
|
+
updated_api_key = retry_test do
|
228
|
+
@@search_client.get_api_key(@api_key[:value], test: 'test')
|
229
|
+
end
|
226
230
|
assert_equal 42, updated_api_key[:maxHitsPerQuery]
|
227
231
|
|
228
232
|
@@search_client.delete_api_key!(@api_key[:value])
|
@@ -233,18 +237,13 @@ class SearchClientTest < BaseTest
|
|
233
237
|
|
234
238
|
assert_equal 'Key does not exist', exception.message
|
235
239
|
|
236
|
-
|
237
|
-
|
238
|
-
@@search_client.restore_api_key!(@api_key[:value])
|
239
|
-
break
|
240
|
-
rescue Algolia::AlgoliaHttpError => e
|
241
|
-
if e.code != 404
|
242
|
-
raise StandardError
|
243
|
-
end
|
244
|
-
end
|
240
|
+
retry_test do
|
241
|
+
@@search_client.restore_api_key!(@api_key[:value])
|
245
242
|
end
|
246
243
|
|
247
|
-
restored_key =
|
244
|
+
restored_key = retry_test do
|
245
|
+
@@search_client.get_api_key(@api_key[:value])
|
246
|
+
end
|
248
247
|
|
249
248
|
refute_nil restored_key
|
250
249
|
end
|
@@ -333,7 +332,12 @@ class SearchClientTest < BaseTest
|
|
333
332
|
secured_index1 = secured_client.init_index(@index1.name)
|
334
333
|
secured_index2 = secured_client.init_index(@index2.name)
|
335
334
|
|
336
|
-
|
335
|
+
res = retry_test do
|
336
|
+
secured_index1.search('')
|
337
|
+
end
|
338
|
+
|
339
|
+
assert_equal 1, res[:hits].length
|
340
|
+
|
337
341
|
exception = assert_raises Algolia::AlgoliaHttpError do
|
338
342
|
secured_index2.search('')
|
339
343
|
end
|
@@ -366,5 +370,96 @@ class SearchClientTest < BaseTest
|
|
366
370
|
assert_equal 'The SecuredAPIKey doesn\'t have a validUntil parameter.', exception.message
|
367
371
|
end
|
368
372
|
end
|
373
|
+
|
374
|
+
describe 'Custom Dictionaries' do
|
375
|
+
def before_all
|
376
|
+
@client = Algolia::Search::Client.create(APPLICATION_ID_2, ADMIN_KEY_2)
|
377
|
+
end
|
378
|
+
|
379
|
+
def test_stopwords_dictionaries
|
380
|
+
entry_id = SecureRandom.hex
|
381
|
+
assert_equal 0, @client.search_dictionary_entries('stopwords', entry_id)[:nbHits]
|
382
|
+
|
383
|
+
entry = {
|
384
|
+
objectID: entry_id,
|
385
|
+
language: 'en',
|
386
|
+
word: 'down'
|
387
|
+
}
|
388
|
+
@client.save_dictionary_entries!('stopwords', [entry])
|
389
|
+
|
390
|
+
stopwords = @client.search_dictionary_entries('stopwords', entry_id)
|
391
|
+
assert_equal 1, stopwords[:nbHits]
|
392
|
+
assert_equal stopwords[:hits][0][:objectID], entry[:objectID]
|
393
|
+
assert_equal stopwords[:hits][0][:word], entry[:word]
|
394
|
+
|
395
|
+
@client.delete_dictionary_entries!('stopwords', [entry_id])
|
396
|
+
assert_equal 0, @client.search_dictionary_entries('stopwords', entry_id)[:nbHits]
|
397
|
+
|
398
|
+
old_dictionary_state = @client.search_dictionary_entries('stopwords', '')
|
399
|
+
old_dictionary_entries = old_dictionary_state[:hits].map do |hit|
|
400
|
+
hit.reject { |key| key == :type }
|
401
|
+
end
|
402
|
+
|
403
|
+
@client.save_dictionary_entries!('stopwords', [entry])
|
404
|
+
assert_equal 1, @client.search_dictionary_entries('stopwords', entry_id)[:nbHits]
|
405
|
+
|
406
|
+
@client.replace_dictionary_entries!('stopwords', old_dictionary_entries)
|
407
|
+
assert_equal 0, @client.search_dictionary_entries('stopwords', entry_id)[:nbHits]
|
408
|
+
|
409
|
+
stopwords_settings = {
|
410
|
+
disableStandardEntries: {
|
411
|
+
stopwords: {
|
412
|
+
en: true
|
413
|
+
}
|
414
|
+
}
|
415
|
+
}
|
416
|
+
|
417
|
+
@client.set_dictionary_settings!(stopwords_settings)
|
418
|
+
|
419
|
+
assert_equal @client.get_dictionary_settings, stopwords_settings
|
420
|
+
end
|
421
|
+
|
422
|
+
def test_plurals_dictionaries
|
423
|
+
entry_id = SecureRandom.hex
|
424
|
+
assert_equal 0, @client.search_dictionary_entries('plurals', entry_id)[:nbHits]
|
425
|
+
|
426
|
+
entry = {
|
427
|
+
objectID: entry_id,
|
428
|
+
language: 'fr',
|
429
|
+
words: %w(cheval chevaux)
|
430
|
+
}
|
431
|
+
@client.save_dictionary_entries!('plurals', [entry])
|
432
|
+
|
433
|
+
plurals = @client.search_dictionary_entries('plurals', entry_id)
|
434
|
+
assert_equal 1, plurals[:nbHits]
|
435
|
+
assert_equal plurals[:hits][0][:objectID], entry[:objectID]
|
436
|
+
assert_equal plurals[:hits][0][:words], entry[:words]
|
437
|
+
|
438
|
+
@client.delete_dictionary_entries!('plurals', [entry_id])
|
439
|
+
assert_equal 0, @client.search_dictionary_entries('plurals', entry_id)[:nbHits]
|
440
|
+
end
|
441
|
+
|
442
|
+
def test_compounds_dictionaries
|
443
|
+
entry_id = SecureRandom.hex
|
444
|
+
assert_equal 0, @client.search_dictionary_entries('compounds', entry_id)[:nbHits]
|
445
|
+
|
446
|
+
entry = {
|
447
|
+
objectID: entry_id,
|
448
|
+
language: 'de',
|
449
|
+
word: 'kopfschmerztablette',
|
450
|
+
decomposition: %w(kopf schmerz tablette)
|
451
|
+
}
|
452
|
+
@client.save_dictionary_entries!('compounds', [entry])
|
453
|
+
|
454
|
+
compounds = @client.search_dictionary_entries('compounds', entry_id)
|
455
|
+
assert_equal 1, compounds[:nbHits]
|
456
|
+
assert_equal compounds[:hits][0][:objectID], entry[:objectID]
|
457
|
+
assert_equal compounds[:hits][0][:word], entry[:word]
|
458
|
+
assert_equal compounds[:hits][0][:decomposition], entry[:decomposition]
|
459
|
+
|
460
|
+
@client.delete_dictionary_entries!('compounds', [entry_id])
|
461
|
+
assert_equal 0, @client.search_dictionary_entries('compounds', entry_id)[:nbHits]
|
462
|
+
end
|
463
|
+
end
|
369
464
|
end
|
370
465
|
end
|
@@ -274,6 +274,9 @@ class SearchIndexTest < BaseTest
|
|
274
274
|
@index.set_settings!(settings)
|
275
275
|
|
276
276
|
assert_equal @index.get_settings, settings
|
277
|
+
|
278
|
+
# check that the forwardToReplicas parameter is passed correctly
|
279
|
+
assert @index.set_settings!(settings, { forwardToReplicas: true })
|
277
280
|
end
|
278
281
|
end
|
279
282
|
|