algolia 2.0.0.pre.alpha.2
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 +7 -0
- data/.circleci/config.yml +146 -0
- data/.github/ISSUE_TEMPLATE.md +20 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
- data/.gitignore +38 -0
- data/.rubocop.yml +186 -0
- data/.rubocop_todo.yml +14 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +18 -0
- data/LICENSE +21 -0
- data/README.md +56 -0
- data/Rakefile +45 -0
- data/Steepfile +6 -0
- data/algolia.gemspec +41 -0
- data/bin/console +21 -0
- data/bin/setup +8 -0
- data/lib/algolia.rb +42 -0
- data/lib/algolia/account_client.rb +65 -0
- data/lib/algolia/analytics_client.rb +105 -0
- data/lib/algolia/config/algolia_config.rb +40 -0
- data/lib/algolia/config/analytics_config.rb +20 -0
- data/lib/algolia/config/insights_config.rb +20 -0
- data/lib/algolia/config/recommendation_config.rb +20 -0
- data/lib/algolia/config/search_config.rb +40 -0
- data/lib/algolia/defaults.rb +35 -0
- data/lib/algolia/enums/call_type.rb +4 -0
- data/lib/algolia/enums/retry_outcome_type.rb +5 -0
- data/lib/algolia/error.rb +29 -0
- data/lib/algolia/helpers.rb +83 -0
- data/lib/algolia/http/http_requester.rb +84 -0
- data/lib/algolia/http/response.rb +23 -0
- data/lib/algolia/insights_client.rb +238 -0
- data/lib/algolia/iterators/base_iterator.rb +19 -0
- data/lib/algolia/iterators/object_iterator.rb +27 -0
- data/lib/algolia/iterators/paginator_iterator.rb +44 -0
- data/lib/algolia/iterators/rule_iterator.rb +9 -0
- data/lib/algolia/iterators/synonym_iterator.rb +9 -0
- data/lib/algolia/logger_helper.rb +14 -0
- data/lib/algolia/recommendation_client.rb +60 -0
- data/lib/algolia/responses/add_api_key_response.rb +38 -0
- data/lib/algolia/responses/base_response.rb +9 -0
- data/lib/algolia/responses/delete_api_key_response.rb +40 -0
- data/lib/algolia/responses/indexing_response.rb +28 -0
- data/lib/algolia/responses/multiple_batch_indexing_response.rb +29 -0
- data/lib/algolia/responses/multiple_response.rb +45 -0
- data/lib/algolia/responses/restore_api_key_response.rb +36 -0
- data/lib/algolia/responses/update_api_key_response.rb +39 -0
- data/lib/algolia/search_client.rb +614 -0
- data/lib/algolia/search_index.rb +1094 -0
- data/lib/algolia/transport/request_options.rb +94 -0
- data/lib/algolia/transport/retry_strategy.rb +117 -0
- data/lib/algolia/transport/stateful_host.rb +26 -0
- data/lib/algolia/transport/transport.rb +161 -0
- data/lib/algolia/user_agent.rb +25 -0
- data/lib/algolia/version.rb +3 -0
- data/sig/config/algolia_config.rbs +24 -0
- data/sig/config/analytics_config.rbs +11 -0
- data/sig/config/insights_config.rbs +11 -0
- data/sig/config/recommendation_config.rbs +11 -0
- data/sig/config/search_config.rbs +11 -0
- data/sig/enums/call_type.rbs +5 -0
- data/sig/helpers.rbs +12 -0
- data/sig/http/http_requester.rbs +17 -0
- data/sig/http/response.rbs +14 -0
- data/sig/interfaces/_connection.rbs +16 -0
- data/sig/iterators/base_iterator.rbs +15 -0
- data/sig/iterators/object_iterator.rbs +6 -0
- data/sig/iterators/paginator_iterator.rbs +8 -0
- data/sig/iterators/rule_iterator.rbs +5 -0
- data/sig/iterators/synonym_iterator.rbs +5 -0
- data/sig/transport/request_options.rbs +33 -0
- data/sig/transport/stateful_host.rbs +21 -0
- data/test/algolia/integration/account_client_test.rb +47 -0
- data/test/algolia/integration/analytics_client_test.rb +113 -0
- data/test/algolia/integration/base_test.rb +9 -0
- data/test/algolia/integration/insights_client_test.rb +80 -0
- data/test/algolia/integration/mocks/mock_requester.rb +45 -0
- data/test/algolia/integration/recommendation_client_test.rb +30 -0
- data/test/algolia/integration/search_client_test.rb +361 -0
- data/test/algolia/integration/search_index_test.rb +698 -0
- data/test/algolia/unit/helpers_test.rb +69 -0
- data/test/algolia/unit/retry_strategy_test.rb +139 -0
- data/test/algolia/unit/user_agent_test.rb +16 -0
- data/test/test_helper.rb +89 -0
- data/upgrade_guide.md +595 -0
- metadata +307 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
class MockRequester
|
2
|
+
def initialize
|
3
|
+
@connection = nil
|
4
|
+
end
|
5
|
+
|
6
|
+
def send_request(host, method, path, _body, headers, _timeout, _connect_timeout)
|
7
|
+
connection = get_connection(host)
|
8
|
+
response = {
|
9
|
+
connection: connection,
|
10
|
+
host: host,
|
11
|
+
path: path,
|
12
|
+
headers: headers,
|
13
|
+
method: method,
|
14
|
+
status: 200,
|
15
|
+
body: '{"hits":[],"nbHits":0,"page":0,"nbPages":1,"hitsPerPage":20,"exhaustiveNbHits":true,"query":"test","params":"query=test","processingTimeMS":1}',
|
16
|
+
success: true
|
17
|
+
}
|
18
|
+
|
19
|
+
Algolia::Http::Response.new(
|
20
|
+
status: response[:status],
|
21
|
+
body: response[:body],
|
22
|
+
headers: response[:headers]
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Retrieve the connection from the @connections
|
27
|
+
#
|
28
|
+
# @param host [StatefulHost]
|
29
|
+
#
|
30
|
+
# @return [Faraday::Connection]
|
31
|
+
#
|
32
|
+
def get_connection(host)
|
33
|
+
@connection = host
|
34
|
+
end
|
35
|
+
|
36
|
+
# Build url from host, path and parameters
|
37
|
+
#
|
38
|
+
# @param host [StatefulHost]
|
39
|
+
#
|
40
|
+
# @return [String]
|
41
|
+
#
|
42
|
+
def build_url(host)
|
43
|
+
host.protocol + host.url
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'base_test'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class RecommendationClientTest < BaseTest
|
5
|
+
describe 'Recommendation client' do
|
6
|
+
def test_recommendation_client
|
7
|
+
client = Algolia::Recommendation::Client.create(APPLICATION_ID_1, ADMIN_KEY_1)
|
8
|
+
personalization_strategy = {
|
9
|
+
eventsScoring: [
|
10
|
+
{ eventName: 'Add to cart', eventType: 'conversion', score: 50 },
|
11
|
+
{ eventName: 'Purchase', eventType: 'conversion', score: 100 }
|
12
|
+
],
|
13
|
+
facetsScoring: [
|
14
|
+
{ facetName: 'brand', score: 100 },
|
15
|
+
{ facetName: 'categories', score: 10 }
|
16
|
+
],
|
17
|
+
personalizationImpact: 0
|
18
|
+
}
|
19
|
+
|
20
|
+
begin
|
21
|
+
client.set_personalization_strategy(personalization_strategy)
|
22
|
+
rescue Algolia::AlgoliaHttpError => e
|
23
|
+
raise e unless e.code == 429
|
24
|
+
end
|
25
|
+
response = client.get_personalization_strategy
|
26
|
+
|
27
|
+
assert_equal response, personalization_strategy
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,361 @@
|
|
1
|
+
require_relative 'base_test'
|
2
|
+
|
3
|
+
class SearchClientTest < BaseTest
|
4
|
+
describe 'customize search client' do
|
5
|
+
def test_with_custom_adapter
|
6
|
+
client = Algolia::Search::Client.new(@@search_config, adapter: 'httpclient')
|
7
|
+
index = client.init_index(get_test_index_name('test_custom_adapter'))
|
8
|
+
|
9
|
+
index.save_object!({ name: 'test', data: 10 }, { auto_generate_object_id_if_not_exist: true })
|
10
|
+
response = index.search('test')
|
11
|
+
|
12
|
+
refute_empty response[:hits]
|
13
|
+
assert_equal 'test', response[:hits][0][:name]
|
14
|
+
assert_equal 10, response[:hits][0][:data]
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_with_custom_requester
|
18
|
+
client = Algolia::Search::Client.new(@@search_config, http_requester: MockRequester.new)
|
19
|
+
index = client.init_index(get_test_index_name('test_custom_requester'))
|
20
|
+
|
21
|
+
response = index.search('test')
|
22
|
+
|
23
|
+
refute_nil response[:hits]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_without_providing_config
|
27
|
+
client = Algolia::Search::Client.create(APPLICATION_ID_1, ADMIN_KEY_1)
|
28
|
+
index = client.init_index(get_test_index_name('test_no_config'))
|
29
|
+
index.save_object!({ name: 'test', data: 10 }, { auto_generate_object_id_if_not_exist: true })
|
30
|
+
response = index.search('test')
|
31
|
+
|
32
|
+
refute_empty response[:hits]
|
33
|
+
assert_equal 'test', response[:hits][0][:name]
|
34
|
+
assert_equal 10, response[:hits][0][:data]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'copy and move index' do
|
39
|
+
def before_all
|
40
|
+
super
|
41
|
+
@index_name = get_test_index_name('copy_index')
|
42
|
+
@index = @@search_client.init_index(@index_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_copy_and_move_index
|
46
|
+
responses = Algolia::MultipleResponse.new
|
47
|
+
|
48
|
+
objects = [
|
49
|
+
{ objectID: 'one', company: 'apple' },
|
50
|
+
{ objectID: 'two', company: 'algolia' }
|
51
|
+
]
|
52
|
+
responses.push(@index.save_objects(objects))
|
53
|
+
|
54
|
+
settings = { attributesForFaceting: ['company'] }
|
55
|
+
responses.push(@index.set_settings(settings))
|
56
|
+
|
57
|
+
synonym = {
|
58
|
+
objectID: 'google_placeholder',
|
59
|
+
type: 'placeholder',
|
60
|
+
placeholder: '<GOOG>',
|
61
|
+
replacements: %w(Google GOOG)
|
62
|
+
}
|
63
|
+
responses.push(@index.save_synonym(synonym))
|
64
|
+
|
65
|
+
rule = {
|
66
|
+
objectID: 'company_auto_faceting',
|
67
|
+
condition: {
|
68
|
+
anchoring: 'contains',
|
69
|
+
pattern: '{facet:company}'
|
70
|
+
},
|
71
|
+
consequence: {
|
72
|
+
params: { automaticFacetFilters: ['company'] }
|
73
|
+
}
|
74
|
+
}
|
75
|
+
responses.push(@index.save_rule(rule))
|
76
|
+
|
77
|
+
responses.wait
|
78
|
+
|
79
|
+
copy_settings_index = @@search_client.init_index(get_test_index_name('copy_index_settings'))
|
80
|
+
copy_rules_index = @@search_client.init_index(get_test_index_name('copy_index_rules'))
|
81
|
+
copy_synonyms_index = @@search_client.init_index(get_test_index_name('copy_index_synonyms'))
|
82
|
+
copy_full_copy_index = @@search_client.init_index(get_test_index_name('copy_index_full_copy'))
|
83
|
+
@@search_client.copy_settings!(@index_name, copy_settings_index.index_name)
|
84
|
+
@@search_client.copy_rules!(@index_name, copy_rules_index.index_name)
|
85
|
+
@@search_client.copy_synonyms!(@index_name, copy_synonyms_index.index_name)
|
86
|
+
@@search_client.copy_index!(@index_name, copy_full_copy_index.index_name)
|
87
|
+
|
88
|
+
assert_equal @index.get_settings, copy_settings_index.get_settings
|
89
|
+
assert_equal @index.get_rule(rule[:objectID]), copy_rules_index.get_rule(rule[:objectID])
|
90
|
+
assert_equal @index.get_synonym(synonym[:objectID]), copy_synonyms_index.get_synonym(synonym[:objectID])
|
91
|
+
assert_equal @index.get_settings, copy_full_copy_index.get_settings
|
92
|
+
assert_equal @index.get_rule(rule[:objectID]), copy_full_copy_index.get_rule(rule[:objectID])
|
93
|
+
assert_equal @index.get_synonym(synonym[:objectID]), copy_full_copy_index.get_synonym(synonym[:objectID])
|
94
|
+
|
95
|
+
moved_index = @@search_client.init_index(get_test_index_name('move_index'))
|
96
|
+
@@search_client.move_index!(@index_name, moved_index.index_name)
|
97
|
+
|
98
|
+
moved_index.get_synonym('google_placeholder')
|
99
|
+
moved_index.get_rule('company_auto_faceting')
|
100
|
+
assert_equal moved_index.get_settings[:attributesForFaceting], ['company']
|
101
|
+
|
102
|
+
moved_index.browse_objects.each do |obj|
|
103
|
+
assert_includes objects, obj
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'MCM' do
|
109
|
+
def before_all
|
110
|
+
super
|
111
|
+
@mcm_client = Algolia::Search::Client.create(MCM_APPLICATION_ID, MCM_ADMIN_KEY)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_mcm
|
115
|
+
clusters = @mcm_client.list_clusters
|
116
|
+
assert_equal 2, clusters[:clusters].length
|
117
|
+
|
118
|
+
cluster_name = clusters[:clusters][0][:clusterName]
|
119
|
+
|
120
|
+
mcm_user_id0 = get_mcm_user_name(0)
|
121
|
+
mcm_user_id1 = get_mcm_user_name(1)
|
122
|
+
mcm_user_id2 = get_mcm_user_name(2)
|
123
|
+
|
124
|
+
@mcm_client.assign_user_id(mcm_user_id0, cluster_name)
|
125
|
+
@mcm_client.assign_user_ids([mcm_user_id1, mcm_user_id2], cluster_name)
|
126
|
+
|
127
|
+
0.upto(2) do |i|
|
128
|
+
retrieved_user = retrieve_user_id(i)
|
129
|
+
assert_equal(retrieved_user, {
|
130
|
+
userID: get_mcm_user_name(i),
|
131
|
+
clusterName: cluster_name,
|
132
|
+
nbRecords: 0,
|
133
|
+
dataSize: 0
|
134
|
+
})
|
135
|
+
end
|
136
|
+
|
137
|
+
refute_equal 0, @mcm_client.list_user_ids[:userIDs].length
|
138
|
+
refute_equal 0, @mcm_client.get_top_user_ids[:topUsers].length
|
139
|
+
|
140
|
+
0.upto(2) do |i|
|
141
|
+
remove_user_id(i)
|
142
|
+
end
|
143
|
+
|
144
|
+
0.upto(2) do |i|
|
145
|
+
assert_removed(i)
|
146
|
+
end
|
147
|
+
|
148
|
+
has_pending_mappings = @mcm_client.pending_mappings?({ retrieveMappings: true })
|
149
|
+
refute_nil has_pending_mappings
|
150
|
+
assert has_pending_mappings[:pending]
|
151
|
+
assert has_pending_mappings[:clusters]
|
152
|
+
assert_instance_of Hash, has_pending_mappings[:clusters]
|
153
|
+
|
154
|
+
has_pending_mappings = @mcm_client.pending_mappings?({ retrieveMappings: false })
|
155
|
+
refute_nil has_pending_mappings
|
156
|
+
assert has_pending_mappings[:pending]
|
157
|
+
refute has_pending_mappings[:clusters]
|
158
|
+
end
|
159
|
+
|
160
|
+
def retrieve_user_id(number)
|
161
|
+
loop do
|
162
|
+
begin
|
163
|
+
return @mcm_client.get_user_id(get_mcm_user_name(number))
|
164
|
+
rescue Algolia::AlgoliaHttpError => e
|
165
|
+
if e.code != 404
|
166
|
+
raise StandardError
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def remove_user_id(number)
|
173
|
+
loop do
|
174
|
+
begin
|
175
|
+
return @mcm_client.remove_user_id(get_mcm_user_name(number))
|
176
|
+
rescue Algolia::AlgoliaHttpError => e
|
177
|
+
if e.code != 400
|
178
|
+
raise StandardError
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def assert_removed(number)
|
185
|
+
loop do
|
186
|
+
begin
|
187
|
+
return @mcm_client.get_user_id(get_mcm_user_name(number))
|
188
|
+
rescue Algolia::AlgoliaHttpError => e
|
189
|
+
if e.code == 404
|
190
|
+
return true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe 'API keys' do
|
198
|
+
def before_all
|
199
|
+
super
|
200
|
+
response = @@search_client.add_api_key!(['search'], {
|
201
|
+
description: 'A description',
|
202
|
+
indexes: ['index'],
|
203
|
+
maxHitsPerQuery: 1000,
|
204
|
+
maxQueriesPerIPPerHour: 1000,
|
205
|
+
queryParameters: 'typoTolerance=strict',
|
206
|
+
referers: ['referer'],
|
207
|
+
validity: 600
|
208
|
+
})
|
209
|
+
@api_key = @@search_client.get_api_key(response.raw_response[:key])
|
210
|
+
end
|
211
|
+
|
212
|
+
def teardown
|
213
|
+
@@search_client.delete_api_key!(@api_key[:value])
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_api_keys
|
217
|
+
assert_equal ['search'], @api_key[:acl]
|
218
|
+
|
219
|
+
api_keys = @@search_client.list_api_keys[:keys].map do |key|
|
220
|
+
key[:value]
|
221
|
+
end
|
222
|
+
assert_includes api_keys, @api_key[:value]
|
223
|
+
|
224
|
+
@@search_client.update_api_key!(@api_key[:value], { maxHitsPerQuery: 42 })
|
225
|
+
updated_api_key = @@search_client.get_api_key(@api_key[:value])
|
226
|
+
assert_equal 42, updated_api_key[:maxHitsPerQuery]
|
227
|
+
|
228
|
+
@@search_client.delete_api_key!(@api_key[:value])
|
229
|
+
|
230
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
231
|
+
@@search_client.get_api_key(@api_key[:value])
|
232
|
+
end
|
233
|
+
|
234
|
+
assert_equal 'Key does not exist', exception.message
|
235
|
+
|
236
|
+
@@search_client.restore_api_key!(@api_key[:value])
|
237
|
+
|
238
|
+
restored_key = @@search_client.get_api_key(@api_key[:value])
|
239
|
+
|
240
|
+
refute_nil restored_key
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe 'Get logs' do
|
245
|
+
def test_logs
|
246
|
+
@@search_client.list_indexes
|
247
|
+
@@search_client.list_indexes
|
248
|
+
|
249
|
+
assert_equal 2, @@search_client.get_logs({
|
250
|
+
length: 2,
|
251
|
+
offset: 0,
|
252
|
+
type: 'all'
|
253
|
+
})[:logs].length
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe 'Multiple Operations' do
|
258
|
+
def before_all
|
259
|
+
@index1 = @@search_client.init_index(get_test_index_name('multiple_operations'))
|
260
|
+
@index2 = @@search_client.init_index(get_test_index_name('multiple_operations_dev'))
|
261
|
+
end
|
262
|
+
|
263
|
+
def test_multiple_operations
|
264
|
+
index_name1 = @index1.index_name
|
265
|
+
index_name2 = @index2.index_name
|
266
|
+
|
267
|
+
response = @@search_client.multiple_batch!([
|
268
|
+
{ indexName: index_name1, action: 'addObject', body: { firstname: 'Jimmie' } },
|
269
|
+
{ indexName: index_name1, action: 'addObject', body: { firstname: 'Jimmie' } },
|
270
|
+
{ indexName: index_name2, action: 'addObject', body: { firstname: 'Jimmie' } },
|
271
|
+
{ indexName: index_name2, action: 'addObject', body: { firstname: 'Jimmie' } }
|
272
|
+
])
|
273
|
+
|
274
|
+
object_ids = response.raw_response[:objectIDs]
|
275
|
+
objects = @@search_client.multiple_get_objects([
|
276
|
+
{ indexName: index_name1, objectID: object_ids[0] },
|
277
|
+
{ indexName: index_name1, objectID: object_ids[1] },
|
278
|
+
{ indexName: index_name2, objectID: object_ids[2] },
|
279
|
+
{ indexName: index_name2, objectID: object_ids[3] }
|
280
|
+
])[:results]
|
281
|
+
|
282
|
+
assert_equal object_ids[0], objects[0][:objectID]
|
283
|
+
assert_equal object_ids[1], objects[1][:objectID]
|
284
|
+
assert_equal object_ids[2], objects[2][:objectID]
|
285
|
+
assert_equal object_ids[3], objects[3][:objectID]
|
286
|
+
|
287
|
+
results = @@search_client.multiple_queries([
|
288
|
+
{ indexName: index_name1, params: to_query_string({ query: '', hitsPerPage: 2 }) },
|
289
|
+
{ indexName: index_name2, params: to_query_string({ query: '', hitsPerPage: 2 }) }
|
290
|
+
], { strategy: 'none' })[:results]
|
291
|
+
|
292
|
+
assert_equal 2, results.length
|
293
|
+
assert_equal 2, results[0][:hits].length
|
294
|
+
assert_equal 2, results[0][:nbHits]
|
295
|
+
assert_equal 2, results[1][:hits].length
|
296
|
+
assert_equal 2, results[1][:nbHits]
|
297
|
+
|
298
|
+
results = @@search_client.multiple_queries([
|
299
|
+
{ indexName: index_name1, params: to_query_string({ query: '', hitsPerPage: 2 }) },
|
300
|
+
{ indexName: index_name2, params: to_query_string({ query: '', hitsPerPage: 2 }) }
|
301
|
+
], { strategy: 'stopIfEnoughMatches' })[:results]
|
302
|
+
|
303
|
+
assert_equal 2, results.length
|
304
|
+
assert_equal 2, results[0][:hits].length
|
305
|
+
assert_equal 2, results[0][:nbHits]
|
306
|
+
assert_equal 0, results[1][:hits].length
|
307
|
+
assert_equal 0, results[1][:nbHits]
|
308
|
+
end
|
309
|
+
|
310
|
+
describe 'Secured API keys' do
|
311
|
+
def test_secured_api_keys
|
312
|
+
@index1 = @@search_client.init_index(get_test_index_name('secured_api_keys'))
|
313
|
+
@index2 = @@search_client.init_index(get_test_index_name('secured_api_keys_dev'))
|
314
|
+
@index1.save_object!({ objectID: 'one' })
|
315
|
+
@index2.save_object!({ objectID: 'one' })
|
316
|
+
|
317
|
+
now = Time.now.to_i
|
318
|
+
secured_api_key = Algolia::Search::Client.generate_secured_api_key(SEARCH_KEY_1, {
|
319
|
+
validUntil: now + (10 * 60),
|
320
|
+
restrictIndices: @index1.index_name
|
321
|
+
})
|
322
|
+
|
323
|
+
secured_client = Algolia::Search::Client.create(APPLICATION_ID_1, secured_api_key)
|
324
|
+
secured_index1 = secured_client.init_index(@index1.index_name)
|
325
|
+
secured_index2 = secured_client.init_index(@index2.index_name)
|
326
|
+
|
327
|
+
secured_index1.search('')
|
328
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
329
|
+
secured_index2.search('')
|
330
|
+
end
|
331
|
+
|
332
|
+
assert_equal 403, exception.code
|
333
|
+
assert_equal 'Index not allowed with this API key', exception.message
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
describe 'Expired Secured API keys' do
|
338
|
+
def test_expired_secured_api_keys
|
339
|
+
now = Time.now.to_i
|
340
|
+
secured_api_key = Algolia::Search::Client.generate_secured_api_key('foo', {
|
341
|
+
validUntil: now - (10 * 60)
|
342
|
+
})
|
343
|
+
remaining = Algolia::Search::Client.get_secured_api_key_remaining_validity(secured_api_key)
|
344
|
+
assert remaining < 0
|
345
|
+
|
346
|
+
secured_api_key = Algolia::Search::Client.generate_secured_api_key('foo', {
|
347
|
+
validUntil: now + (10 * 60)
|
348
|
+
})
|
349
|
+
remaining = Algolia::Search::Client.get_secured_api_key_remaining_validity(secured_api_key)
|
350
|
+
assert remaining > 0
|
351
|
+
|
352
|
+
secured_api_key = Algolia::Search::Client.generate_secured_api_key('foo', {})
|
353
|
+
exception = assert_raises Algolia::AlgoliaError do
|
354
|
+
Algolia::Search::Client.get_secured_api_key_remaining_validity(secured_api_key)
|
355
|
+
end
|
356
|
+
|
357
|
+
assert_equal 'The SecuredAPIKey doesn\'t have a validUntil parameter.', exception.message
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
@@ -0,0 +1,698 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
require_relative 'base_test'
|
3
|
+
|
4
|
+
class SearchIndexTest < BaseTest
|
5
|
+
describe 'pass request options' do
|
6
|
+
def before_all
|
7
|
+
super
|
8
|
+
@index = @@search_client.init_index(get_test_index_name('options'))
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_with_wrong_credentials
|
12
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
13
|
+
@index.save_object(generate_object('111'), {
|
14
|
+
headers: {
|
15
|
+
'X-Algolia-Application-Id' => 'XXXXX',
|
16
|
+
'X-Algolia-API-Key' => 'XXXXX'
|
17
|
+
}
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_equal 'Invalid Application-ID or API key', exception.message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'save objects' do
|
26
|
+
def before_all
|
27
|
+
super
|
28
|
+
@index = @@search_client.init_index(get_test_index_name('indexing'))
|
29
|
+
end
|
30
|
+
|
31
|
+
def retrieve_last_object_ids(responses)
|
32
|
+
responses.last.raw_response[:objectIDs]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_save_objects
|
36
|
+
responses = Algolia::MultipleResponse.new
|
37
|
+
object_ids = []
|
38
|
+
|
39
|
+
obj1 = generate_object('obj1')
|
40
|
+
responses.push(@index.save_object(obj1))
|
41
|
+
object_ids.push(retrieve_last_object_ids(responses))
|
42
|
+
obj2 = generate_object
|
43
|
+
response = @index.save_object(obj2, { auto_generate_object_id_if_not_exist: true })
|
44
|
+
responses.push(response)
|
45
|
+
object_ids.push(retrieve_last_object_ids(responses))
|
46
|
+
responses.push(@index.save_objects([]))
|
47
|
+
object_ids.push(retrieve_last_object_ids(responses))
|
48
|
+
obj3 = generate_object('obj3')
|
49
|
+
obj4 = generate_object('obj4')
|
50
|
+
responses.push(@index.save_objects([obj3, obj4]))
|
51
|
+
object_ids.push(retrieve_last_object_ids(responses))
|
52
|
+
obj5 = generate_object
|
53
|
+
obj6 = generate_object
|
54
|
+
responses.push(@index.save_objects([obj5, obj6], { auto_generate_object_id_if_not_exist: true }))
|
55
|
+
object_ids.push(retrieve_last_object_ids(responses))
|
56
|
+
object_ids.flatten!
|
57
|
+
objects = 1.upto(1000).map do |i|
|
58
|
+
generate_object(i.to_s)
|
59
|
+
end
|
60
|
+
|
61
|
+
@index.config.batch_size = 100
|
62
|
+
responses.push(@index.save_objects(objects))
|
63
|
+
responses.wait
|
64
|
+
|
65
|
+
assert_equal obj1[:property], @index.get_object(object_ids[0])[:property]
|
66
|
+
assert_equal obj2[:property], @index.get_object(object_ids[1])[:property]
|
67
|
+
assert_equal obj3[:property], @index.get_object(object_ids[2])[:property]
|
68
|
+
assert_equal obj4[:property], @index.get_object(object_ids[3])[:property]
|
69
|
+
assert_equal obj5[:property], @index.get_object(object_ids[4])[:property]
|
70
|
+
assert_equal obj6[:property], @index.get_object(object_ids[5])[:property]
|
71
|
+
|
72
|
+
results = @index.get_objects((1..1000).to_a)[:results]
|
73
|
+
|
74
|
+
results.each do |obj|
|
75
|
+
assert_includes(objects, obj)
|
76
|
+
end
|
77
|
+
|
78
|
+
assert_equal objects.length, results.length
|
79
|
+
browsed_objects = []
|
80
|
+
@index.browse_objects do |hit|
|
81
|
+
browsed_objects.push(hit)
|
82
|
+
end
|
83
|
+
|
84
|
+
assert_equal 1006, browsed_objects.length
|
85
|
+
objects.each do |obj|
|
86
|
+
assert_includes(browsed_objects, obj)
|
87
|
+
end
|
88
|
+
|
89
|
+
[obj1, obj3, obj4].each do |obj|
|
90
|
+
assert_includes(browsed_objects, obj)
|
91
|
+
end
|
92
|
+
|
93
|
+
responses = Algolia::MultipleResponse.new
|
94
|
+
|
95
|
+
obj1[:property] = 'new property'
|
96
|
+
responses.push(@index.partial_update_object(obj1))
|
97
|
+
|
98
|
+
obj3[:property] = 'new property 3'
|
99
|
+
obj4[:property] = 'new property 4'
|
100
|
+
responses.push(@index.partial_update_objects([obj3, obj4]))
|
101
|
+
|
102
|
+
responses.wait
|
103
|
+
|
104
|
+
assert_equal obj1[:property], @index.get_object(object_ids[0])[:property]
|
105
|
+
assert_equal obj3[:property], @index.get_object(object_ids[2])[:property]
|
106
|
+
assert_equal obj4[:property], @index.get_object(object_ids[3])[:property]
|
107
|
+
|
108
|
+
delete_by_obj = { objectID: 'obj_del_by', _tags: 'algolia', property: 'property' }
|
109
|
+
@index.save_object!(delete_by_obj)
|
110
|
+
|
111
|
+
responses = Algolia::MultipleResponse.new
|
112
|
+
|
113
|
+
responses.push(@index.delete_object(object_ids.shift))
|
114
|
+
responses.push(@index.delete_by({ tagFilters: ['algolia'] }))
|
115
|
+
responses.push(@index.delete_objects(object_ids))
|
116
|
+
responses.push(@index.clear_objects)
|
117
|
+
|
118
|
+
responses.wait
|
119
|
+
|
120
|
+
browsed_objects = []
|
121
|
+
@index.browse_objects do |hit|
|
122
|
+
browsed_objects.push(hit)
|
123
|
+
end
|
124
|
+
|
125
|
+
assert_equal 0, browsed_objects.length
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_save_object_without_object_id_and_fail
|
129
|
+
exception = assert_raises Algolia::AlgoliaError do
|
130
|
+
@index.save_object(generate_object)
|
131
|
+
end
|
132
|
+
|
133
|
+
assert_equal "Missing 'objectID'", exception.message
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_save_objects_with_single_object_and_fail
|
137
|
+
exception = assert_raises Algolia::AlgoliaError do
|
138
|
+
@index.save_objects(generate_object)
|
139
|
+
end
|
140
|
+
|
141
|
+
assert_equal 'argument must be an array of objects', exception.message
|
142
|
+
end
|
143
|
+
|
144
|
+
def test_save_objects_with_array_of_integers_and_fail
|
145
|
+
exception = assert_raises Algolia::AlgoliaError do
|
146
|
+
@index.save_objects([2222, 3333])
|
147
|
+
end
|
148
|
+
|
149
|
+
assert_equal 'argument must be an array of object, got: 2222', exception.message
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe 'settings' do
|
154
|
+
def before_all
|
155
|
+
super
|
156
|
+
@index_name = get_test_index_name('settings')
|
157
|
+
@index = @@search_client.init_index(@index_name)
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_settings
|
161
|
+
@index.save_object!(generate_object('obj1'))
|
162
|
+
|
163
|
+
settings = {
|
164
|
+
searchableAttributes: %w(attribute1 attribute2 attribute3 ordered(attribute4) unordered(attribute5)),
|
165
|
+
attributesForFaceting: %w(attribute1 filterOnly(attribute2) searchable(attribute3)),
|
166
|
+
unretrievableAttributes: %w(
|
167
|
+
attribute1
|
168
|
+
attribute2
|
169
|
+
),
|
170
|
+
attributesToRetrieve: %w(
|
171
|
+
attribute3
|
172
|
+
attribute4
|
173
|
+
),
|
174
|
+
ranking: %w(asc(attribute1) desc(attribute2) attribute custom exact filters geo proximity typo words),
|
175
|
+
customRanking: %w(asc(attribute1) desc(attribute1)),
|
176
|
+
replicas: [
|
177
|
+
@index_name + '_replica1',
|
178
|
+
@index_name + '_replica2'
|
179
|
+
],
|
180
|
+
maxValuesPerFacet: 100,
|
181
|
+
sortFacetValuesBy: 'count',
|
182
|
+
attributesToHighlight: %w(
|
183
|
+
attribute1
|
184
|
+
attribute2
|
185
|
+
),
|
186
|
+
attributesToSnippet: %w(attribute1:10 attribute2:8),
|
187
|
+
highlightPreTag: '<strong>',
|
188
|
+
highlightPostTag: '</strong>',
|
189
|
+
snippetEllipsisText: ' and so on.',
|
190
|
+
restrictHighlightAndSnippetArrays: true,
|
191
|
+
hitsPerPage: 42,
|
192
|
+
paginationLimitedTo: 43,
|
193
|
+
minWordSizefor1Typo: 2,
|
194
|
+
minWordSizefor2Typos: 6,
|
195
|
+
typoTolerance: 'false',
|
196
|
+
allowTyposOnNumericTokens: false,
|
197
|
+
ignorePlurals: true,
|
198
|
+
disableTypoToleranceOnAttributes: %w(
|
199
|
+
attribute1
|
200
|
+
attribute2
|
201
|
+
),
|
202
|
+
disableTypoToleranceOnWords: %w(
|
203
|
+
word1
|
204
|
+
word2
|
205
|
+
),
|
206
|
+
separatorsToIndex: '()[]',
|
207
|
+
queryType: 'prefixNone',
|
208
|
+
removeWordsIfNoResults: 'allOptional',
|
209
|
+
advancedSyntax: true,
|
210
|
+
optionalWords: %w(
|
211
|
+
word1
|
212
|
+
word2
|
213
|
+
),
|
214
|
+
removeStopWords: true,
|
215
|
+
disablePrefixOnAttributes: %w(
|
216
|
+
attribute1
|
217
|
+
attribute2
|
218
|
+
),
|
219
|
+
disableExactOnAttributes: %w(
|
220
|
+
attribute1
|
221
|
+
attribute2
|
222
|
+
),
|
223
|
+
exactOnSingleWordQuery: 'word',
|
224
|
+
enableRules: false,
|
225
|
+
numericAttributesForFiltering: %w(
|
226
|
+
attribute1
|
227
|
+
attribute2
|
228
|
+
),
|
229
|
+
allowCompressionOfIntegerArray: true,
|
230
|
+
attributeForDistinct: 'attribute1',
|
231
|
+
distinct: 2,
|
232
|
+
replaceSynonymsInHighlight: false,
|
233
|
+
minProximity: 7,
|
234
|
+
responseFields: %w(
|
235
|
+
hits
|
236
|
+
hitsPerPage
|
237
|
+
),
|
238
|
+
maxFacetHits: 100,
|
239
|
+
camelCaseAttributes: %w(
|
240
|
+
attribute1
|
241
|
+
attribute2
|
242
|
+
),
|
243
|
+
decompoundedAttributes: {
|
244
|
+
de: %w(attribute1 attribute2),
|
245
|
+
fi: ['attribute3']
|
246
|
+
},
|
247
|
+
keepDiacriticsOnCharacters: 'øé',
|
248
|
+
queryLanguages: %w(
|
249
|
+
en
|
250
|
+
fr
|
251
|
+
),
|
252
|
+
alternativesAsExact: ['ignorePlurals'],
|
253
|
+
advancedSyntaxFeatures: ['exactPhrase'],
|
254
|
+
userData: {
|
255
|
+
customUserData: 42.0
|
256
|
+
},
|
257
|
+
indexLanguages: ['ja']
|
258
|
+
}
|
259
|
+
|
260
|
+
@index.set_settings!(settings)
|
261
|
+
|
262
|
+
# Because the response settings dict contains the extra version key, we
|
263
|
+
# also add it to the expected settings dict to prevent the test to fail
|
264
|
+
# for a missing key.
|
265
|
+
settings[:version] = 2
|
266
|
+
|
267
|
+
assert_equal @index.get_settings, settings
|
268
|
+
|
269
|
+
settings[:typoTolerance] = 'min'
|
270
|
+
settings[:ignorePlurals] = %w(en fr)
|
271
|
+
settings[:removeStopWords] = %w(en fr)
|
272
|
+
settings[:distinct] = true
|
273
|
+
|
274
|
+
@index.set_settings!(settings)
|
275
|
+
|
276
|
+
assert_equal @index.get_settings, settings
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
describe 'search' do
|
281
|
+
def before_all
|
282
|
+
super
|
283
|
+
@index = @@search_client.init_index(get_test_index_name('search'))
|
284
|
+
@index.save_objects!(create_employee_records, { auto_generate_object_id_if_not_exist: true })
|
285
|
+
@index.set_settings!(attributesForFaceting: ['searchable(company)'])
|
286
|
+
end
|
287
|
+
|
288
|
+
def test_search_objects
|
289
|
+
response = @index.search('algolia')
|
290
|
+
|
291
|
+
assert_equal 2, response[:nbHits]
|
292
|
+
assert_equal 0, Algolia::Search::Index.get_object_position(response, 'nicolas-dessaigne')
|
293
|
+
assert_equal 1, Algolia::Search::Index.get_object_position(response, 'julien-lemoine')
|
294
|
+
assert_equal(-1, Algolia::Search::Index.get_object_position(response, ''))
|
295
|
+
end
|
296
|
+
|
297
|
+
def test_find_objects
|
298
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
299
|
+
@index.find_object(-> (_hit) { false }, { query: '', paginate: false })
|
300
|
+
end
|
301
|
+
|
302
|
+
assert_equal 'Object not found', exception.message
|
303
|
+
|
304
|
+
response = @index.find_object(-> (_hit) { true }, { query: '', paginate: false })
|
305
|
+
assert_equal 0, response[:position]
|
306
|
+
assert_equal 0, response[:page]
|
307
|
+
|
308
|
+
condition = -> (obj) do
|
309
|
+
obj.has_key?(:company) && obj[:company] == 'Apple'
|
310
|
+
end
|
311
|
+
|
312
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
313
|
+
@index.find_object(condition, { query: 'algolia', paginate: false })
|
314
|
+
end
|
315
|
+
|
316
|
+
assert_equal 'Object not found', exception.message
|
317
|
+
|
318
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
319
|
+
@index.find_object(condition, { query: '', paginate: false, hitsPerPage: 5 })
|
320
|
+
end
|
321
|
+
|
322
|
+
assert_equal 'Object not found', exception.message
|
323
|
+
|
324
|
+
response = @index.find_object(condition, { query: '', paginate: true, hitsPerPage: 5 })
|
325
|
+
assert_equal 0, response[:position]
|
326
|
+
assert_equal 2, response[:page]
|
327
|
+
|
328
|
+
response = @index.search('elon', { clickAnalytics: true })
|
329
|
+
|
330
|
+
refute_nil response[:queryID]
|
331
|
+
|
332
|
+
response = @index.search('elon', { facets: '*', facetFilters: ['company:tesla'] })
|
333
|
+
|
334
|
+
assert_equal 1, response[:nbHits]
|
335
|
+
|
336
|
+
response = @index.search('elon', { facets: '*', filters: '(company:tesla OR company:spacex)' })
|
337
|
+
|
338
|
+
assert_equal 2, response[:nbHits]
|
339
|
+
|
340
|
+
response = @index.search_for_facet_values('company', 'a')
|
341
|
+
|
342
|
+
assert(response[:facetHits].any? { |hit| hit[:value] == 'Algolia' })
|
343
|
+
assert(response[:facetHits].any? { |hit| hit[:value] == 'Amazon' })
|
344
|
+
assert(response[:facetHits].any? { |hit| hit[:value] == 'Apple' })
|
345
|
+
assert(response[:facetHits].any? { |hit| hit[:value] == 'Arista Networks' })
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
describe 'synonyms' do
|
350
|
+
def before_all
|
351
|
+
super
|
352
|
+
@index = @@search_client.init_index(get_test_index_name('synonyms'))
|
353
|
+
end
|
354
|
+
|
355
|
+
def test_synonyms
|
356
|
+
responses = Algolia::MultipleResponse.new
|
357
|
+
responses.push(@index.save_objects([
|
358
|
+
{ console: 'Sony PlayStation <PLAYSTATIONVERSION>' },
|
359
|
+
{ console: 'Nintendo Switch' },
|
360
|
+
{ console: 'Nintendo Wii U' },
|
361
|
+
{ console: 'Nintendo Game Boy Advance' },
|
362
|
+
{ console: 'Microsoft Xbox' },
|
363
|
+
{ console: 'Microsoft Xbox 360' },
|
364
|
+
{ console: 'Microsoft Xbox One' }
|
365
|
+
], { auto_generate_object_id_if_not_exist: true }))
|
366
|
+
|
367
|
+
synonym1 = {
|
368
|
+
objectID: 'gba',
|
369
|
+
type: 'synonym',
|
370
|
+
synonyms: ['gba', 'gameboy advance', 'game boy advance']
|
371
|
+
}
|
372
|
+
|
373
|
+
responses.push(@index.save_synonym(synonym1))
|
374
|
+
|
375
|
+
synonym2 = {
|
376
|
+
objectID: 'wii_to_wii_u',
|
377
|
+
type: 'onewaysynonym',
|
378
|
+
input: 'wii',
|
379
|
+
synonyms: ['wii U']
|
380
|
+
}
|
381
|
+
|
382
|
+
synonym3 = {
|
383
|
+
objectID: 'playstation_version_placeholder',
|
384
|
+
type: 'placeholder',
|
385
|
+
placeholder: '<PLAYSTATIONVERSION>',
|
386
|
+
replacements: ['1', 'One', '2', '3', '4', '4 Pro']
|
387
|
+
}
|
388
|
+
|
389
|
+
synonym4 = {
|
390
|
+
objectID: 'ps4',
|
391
|
+
type: 'altcorrection1',
|
392
|
+
word: 'ps4',
|
393
|
+
corrections: ['playstation4']
|
394
|
+
}
|
395
|
+
|
396
|
+
synonym5 = {
|
397
|
+
objectID: 'psone',
|
398
|
+
type: 'altcorrection2',
|
399
|
+
word: 'psone',
|
400
|
+
corrections: ['playstationone']
|
401
|
+
}
|
402
|
+
|
403
|
+
responses.push(@index.save_synonyms([synonym2, synonym3, synonym4, synonym5]))
|
404
|
+
|
405
|
+
responses.wait
|
406
|
+
|
407
|
+
assert_equal synonym1, @index.get_synonym(synonym1[:objectID])
|
408
|
+
assert_equal synonym2, @index.get_synonym(synonym2[:objectID])
|
409
|
+
assert_equal synonym3, @index.get_synonym(synonym3[:objectID])
|
410
|
+
assert_equal synonym4, @index.get_synonym(synonym4[:objectID])
|
411
|
+
assert_equal synonym5, @index.get_synonym(synonym5[:objectID])
|
412
|
+
|
413
|
+
res = @index.search_synonyms('')
|
414
|
+
assert_equal 5, res[:hits].length
|
415
|
+
|
416
|
+
results = []
|
417
|
+
@index.browse_synonyms do |synonym|
|
418
|
+
results.push(synonym)
|
419
|
+
end
|
420
|
+
|
421
|
+
synonyms = [
|
422
|
+
synonym1,
|
423
|
+
synonym2,
|
424
|
+
synonym3,
|
425
|
+
synonym4,
|
426
|
+
synonym5
|
427
|
+
]
|
428
|
+
|
429
|
+
synonyms.each do |synonym|
|
430
|
+
assert_includes results, synonym
|
431
|
+
end
|
432
|
+
|
433
|
+
@index.delete_synonym!('gba')
|
434
|
+
|
435
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
436
|
+
@index.get_synonym('gba')
|
437
|
+
end
|
438
|
+
|
439
|
+
assert_equal 'Synonym set does not exist', exception.message
|
440
|
+
|
441
|
+
@index.clear_synonyms!
|
442
|
+
|
443
|
+
res = @index.search_synonyms('')
|
444
|
+
assert_equal 0, res[:nbHits]
|
445
|
+
end
|
446
|
+
|
447
|
+
describe 'query rules' do
|
448
|
+
def before_all
|
449
|
+
super
|
450
|
+
@index = @@search_client.init_index(get_test_index_name('rules'))
|
451
|
+
end
|
452
|
+
|
453
|
+
def test_rules
|
454
|
+
responses = Algolia::MultipleResponse.new
|
455
|
+
responses.push(@index.save_objects([
|
456
|
+
{ objectID: 'iphone_7', brand: 'Apple', model: '7' },
|
457
|
+
{ objectID: 'iphone_8', brand: 'Apple', model: '8' },
|
458
|
+
{ objectID: 'iphone_x', brand: 'Apple', model: 'X' },
|
459
|
+
{ objectID: 'one_plus_one', brand: 'OnePlus',
|
460
|
+
model: 'One' },
|
461
|
+
{ objectID: 'one_plus_two', brand: 'OnePlus',
|
462
|
+
model: 'Two' }
|
463
|
+
], { auto_generate_object_id_if_not_exist: true }))
|
464
|
+
|
465
|
+
responses.push(@index.set_settings({ attributesForFaceting: %w(brand model) }))
|
466
|
+
|
467
|
+
rule1 = {
|
468
|
+
objectID: 'brand_automatic_faceting',
|
469
|
+
enabled: false,
|
470
|
+
condition: { anchoring: 'is', pattern: '{facet:brand}' },
|
471
|
+
consequence: {
|
472
|
+
params: {
|
473
|
+
automaticFacetFilters: [
|
474
|
+
{ facet: 'brand', disjunctive: true, score: 42 }
|
475
|
+
]
|
476
|
+
}
|
477
|
+
},
|
478
|
+
validity: [
|
479
|
+
{
|
480
|
+
from: 1532439300, # 07/24/2018 13:35:00 UTC
|
481
|
+
until: 1532525700 # 07/25/2018 13:35:00 UTC
|
482
|
+
},
|
483
|
+
{
|
484
|
+
from: 1532612100, # 07/26/2018 13:35:00 UTC
|
485
|
+
until: 1532698500 # 07/27/2018 13:35:00 UTC
|
486
|
+
}
|
487
|
+
],
|
488
|
+
description: 'Automatic apply the faceting on `brand` if a brand value is found in the query'
|
489
|
+
}
|
490
|
+
|
491
|
+
responses.push(@index.save_rule(rule1))
|
492
|
+
|
493
|
+
rule2 = {
|
494
|
+
objectID: 'query_edits',
|
495
|
+
conditions: [{ anchoring: 'is', pattern: 'mobile phone', alternatives: true }],
|
496
|
+
consequence: {
|
497
|
+
filterPromotes: false,
|
498
|
+
params: {
|
499
|
+
query: {
|
500
|
+
edits: [
|
501
|
+
{ type: 'remove', delete: 'mobile' },
|
502
|
+
{ type: 'replace', delete: 'phone', insert: 'iphone' }
|
503
|
+
]
|
504
|
+
}
|
505
|
+
}
|
506
|
+
}
|
507
|
+
}
|
508
|
+
|
509
|
+
rule3 = {
|
510
|
+
objectID: 'query_promo',
|
511
|
+
consequence: {
|
512
|
+
params: {
|
513
|
+
filters: 'brand:OnePlus'
|
514
|
+
}
|
515
|
+
}
|
516
|
+
}
|
517
|
+
|
518
|
+
rule4 = {
|
519
|
+
objectID: 'query_promo_summer',
|
520
|
+
condition: {
|
521
|
+
context: 'summer'
|
522
|
+
},
|
523
|
+
consequence: {
|
524
|
+
params: {
|
525
|
+
filters: 'model:One'
|
526
|
+
}
|
527
|
+
}
|
528
|
+
}
|
529
|
+
|
530
|
+
responses.push(@index.save_rules([rule2, rule3, rule4]))
|
531
|
+
|
532
|
+
responses.wait
|
533
|
+
|
534
|
+
assert_equal 1, @index.search('', { ruleContexts: ['summer'] })[:nbHits]
|
535
|
+
|
536
|
+
assert_equal rule1, rule_without_metadata(@index.get_rule(rule1[:objectID]))
|
537
|
+
assert_equal rule2, rule_without_metadata(@index.get_rule(rule2[:objectID]))
|
538
|
+
assert_equal rule3, rule_without_metadata(@index.get_rule(rule3[:objectID]))
|
539
|
+
assert_equal rule4, rule_without_metadata(@index.get_rule(rule4[:objectID]))
|
540
|
+
|
541
|
+
assert_equal 4, @index.search_rules('')[:nbHits]
|
542
|
+
|
543
|
+
results = []
|
544
|
+
@index.browse_rules do |rule|
|
545
|
+
results.push(rule)
|
546
|
+
end
|
547
|
+
|
548
|
+
rules = [
|
549
|
+
rule1,
|
550
|
+
rule2,
|
551
|
+
rule3,
|
552
|
+
rule4
|
553
|
+
]
|
554
|
+
|
555
|
+
results.each do |rule|
|
556
|
+
assert_includes rules, rule_without_metadata(rule)
|
557
|
+
end
|
558
|
+
|
559
|
+
@index.delete_rule!(rule1[:objectID])
|
560
|
+
|
561
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
562
|
+
@index.get_rule(rule1[:objectID])
|
563
|
+
end
|
564
|
+
|
565
|
+
assert_equal 'ObjectID does not exist', exception.message
|
566
|
+
|
567
|
+
@index.clear_rules!
|
568
|
+
|
569
|
+
res = @index.search_rules('')
|
570
|
+
assert_equal 0, res[:nbHits]
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
describe 'batching' do
|
575
|
+
def before_all
|
576
|
+
super
|
577
|
+
@index = @@search_client.init_index(get_test_index_name('index_batching'))
|
578
|
+
end
|
579
|
+
|
580
|
+
def test_index_batching
|
581
|
+
@index.save_objects!([
|
582
|
+
{ objectID: 'one', key: 'value' },
|
583
|
+
{ objectID: 'two', key: 'value' },
|
584
|
+
{ objectID: 'three', key: 'value' },
|
585
|
+
{ objectID: 'four', key: 'value' },
|
586
|
+
{ objectID: 'five', key: 'value' }
|
587
|
+
])
|
588
|
+
|
589
|
+
@index.batch!([
|
590
|
+
{ action: 'addObject', body: { objectID: 'zero', key: 'value' } },
|
591
|
+
{ action: 'updateObject', body: { objectID: 'one', k: 'v' } },
|
592
|
+
{ action: 'partialUpdateObject', body: { objectID: 'two', k: 'v' } },
|
593
|
+
{ action: 'partialUpdateObject', body: { objectID: 'two_bis', key: 'value' } },
|
594
|
+
{ action: 'partialUpdateObjectNoCreate', body: { objectID: 'three', k: 'v' } },
|
595
|
+
{ action: 'deleteObject', body: { objectID: 'four' } }
|
596
|
+
])
|
597
|
+
|
598
|
+
objects = [
|
599
|
+
{ objectID: 'zero', key: 'value' },
|
600
|
+
{ objectID: 'one', k: 'v' },
|
601
|
+
{ objectID: 'two', key: 'value', k: 'v' },
|
602
|
+
{ objectID: 'two_bis', key: 'value' },
|
603
|
+
{ objectID: 'three', key: 'value', k: 'v' },
|
604
|
+
{ objectID: 'five', key: 'value' }
|
605
|
+
]
|
606
|
+
|
607
|
+
@index.browse_objects do |object|
|
608
|
+
assert_includes objects, object
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
describe 'replacing' do
|
614
|
+
def before_all
|
615
|
+
super
|
616
|
+
@index = @@search_client.init_index(get_test_index_name('replacing'))
|
617
|
+
end
|
618
|
+
|
619
|
+
def test_replacing
|
620
|
+
responses = Algolia::MultipleResponse.new
|
621
|
+
responses.push(@index.save_object({ objectID: 'one' }))
|
622
|
+
responses.push(@index.save_rule({
|
623
|
+
objectID: 'one',
|
624
|
+
condition: { anchoring: 'is', pattern: 'pattern' },
|
625
|
+
consequence: {
|
626
|
+
params: {
|
627
|
+
query: {
|
628
|
+
edits: [
|
629
|
+
{ type: 'remove', delete: 'pattern' }
|
630
|
+
]
|
631
|
+
}
|
632
|
+
}
|
633
|
+
}
|
634
|
+
}))
|
635
|
+
responses.push(@index.save_synonym({ objectID: 'one', type: 'synonym', synonyms: %w(one two) }))
|
636
|
+
responses.wait
|
637
|
+
|
638
|
+
@index.replace_all_objects!([{ objectID: 'two' }])
|
639
|
+
responses.push(@index.replace_all_rules([{
|
640
|
+
objectID: 'two',
|
641
|
+
condition: { anchoring: 'is', pattern: 'pattern' },
|
642
|
+
consequence: {
|
643
|
+
params: {
|
644
|
+
query: {
|
645
|
+
edits: [
|
646
|
+
{ type: 'remove', delete: 'pattern' }
|
647
|
+
]
|
648
|
+
}
|
649
|
+
}
|
650
|
+
}
|
651
|
+
}]))
|
652
|
+
|
653
|
+
responses.push(@index.replace_all_synonyms([{ objectID: 'two', type: 'synonym', synonyms: %w(one two) }]))
|
654
|
+
|
655
|
+
responses.wait
|
656
|
+
|
657
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
658
|
+
@index.get_object('one')
|
659
|
+
end
|
660
|
+
|
661
|
+
assert_equal 'ObjectID does not exist', exception.message
|
662
|
+
|
663
|
+
assert_equal 'two', @index.get_object('two')[:objectID]
|
664
|
+
|
665
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
666
|
+
@index.get_rule('one')
|
667
|
+
end
|
668
|
+
|
669
|
+
assert_equal 'ObjectID does not exist', exception.message
|
670
|
+
|
671
|
+
assert_equal 'two', @index.get_rule('two')[:objectID]
|
672
|
+
|
673
|
+
exception = assert_raises Algolia::AlgoliaHttpError do
|
674
|
+
@index.get_synonym('one')
|
675
|
+
end
|
676
|
+
|
677
|
+
assert_equal 'Synonym set does not exist', exception.message
|
678
|
+
|
679
|
+
assert_equal 'two', @index.get_synonym('two')[:objectID]
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
683
|
+
describe 'exists' do
|
684
|
+
def before_all
|
685
|
+
super
|
686
|
+
@index = @@search_client.init_index(get_test_index_name('exists'))
|
687
|
+
end
|
688
|
+
|
689
|
+
def test_exists
|
690
|
+
refute @index.exists?
|
691
|
+
@index.save_object!(generate_object('111'))
|
692
|
+
assert @index.exists?
|
693
|
+
@index.delete!
|
694
|
+
refute @index.exists?
|
695
|
+
end
|
696
|
+
end
|
697
|
+
end
|
698
|
+
end
|