supplejack_client 1.0.1

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +16 -0
  5. data/Guardfile +24 -0
  6. data/LICENSE.txt +674 -0
  7. data/README.md +153 -0
  8. data/Rakefile +9 -0
  9. data/lib/generators/locales/en.yml +11 -0
  10. data/lib/generators/supplejack/install_generator.rb +28 -0
  11. data/lib/generators/templates/README +19 -0
  12. data/lib/generators/templates/supplejack_client.rb +120 -0
  13. data/lib/supplejack/config.rb +116 -0
  14. data/lib/supplejack/controllers/helpers.rb +172 -0
  15. data/lib/supplejack/engine.rb +20 -0
  16. data/lib/supplejack/exceptions.rb +17 -0
  17. data/lib/supplejack/facet.rb +33 -0
  18. data/lib/supplejack/item.rb +94 -0
  19. data/lib/supplejack/item_relation.rb +73 -0
  20. data/lib/supplejack/log_subscriber.rb +58 -0
  21. data/lib/supplejack/paginated_collection.rb +61 -0
  22. data/lib/supplejack/record.rb +147 -0
  23. data/lib/supplejack/request.rb +95 -0
  24. data/lib/supplejack/search.rb +346 -0
  25. data/lib/supplejack/url_formats/item_hash.rb +208 -0
  26. data/lib/supplejack/user.rb +132 -0
  27. data/lib/supplejack/user_set.rb +349 -0
  28. data/lib/supplejack/user_set_relation.rb +143 -0
  29. data/lib/supplejack/util.rb +120 -0
  30. data/lib/supplejack/version.rb +10 -0
  31. data/lib/supplejack_client.rb +29 -0
  32. data/spec/spec_helper.rb +23 -0
  33. data/spec/supplejack/controllers/helpers_spec.rb +277 -0
  34. data/spec/supplejack/facet_spec.rb +44 -0
  35. data/spec/supplejack/item_relation_spec.rb +111 -0
  36. data/spec/supplejack/item_spec.rb +115 -0
  37. data/spec/supplejack/log_subscriber_spec.rb +40 -0
  38. data/spec/supplejack/paginated_collection_spec.rb +43 -0
  39. data/spec/supplejack/record_spec.rb +255 -0
  40. data/spec/supplejack/request_spec.rb +195 -0
  41. data/spec/supplejack/search_spec.rb +727 -0
  42. data/spec/supplejack/url_formats/item_hash_spec.rb +341 -0
  43. data/spec/supplejack/user_set_relation_spec.rb +149 -0
  44. data/spec/supplejack/user_set_spec.rb +465 -0
  45. data/spec/supplejack/user_spec.rb +159 -0
  46. data/supplejack_client.gemspec +30 -0
  47. metadata +159 -0
@@ -0,0 +1,195 @@
1
+ # The Supplejack Client code is Crown copyright (C) 2014, New Zealand Government,
2
+ # and is licensed under the GNU General Public License, version 3.
3
+ # See https://github.com/DigitalNZ/supplejack_client for details.
4
+ #
5
+ # Supplejack was created by DigitalNZ at the National Library of NZ
6
+ # and the Department of Internal Affairs. http://digitalnz.org/supplejack
7
+
8
+ require 'spec_helper'
9
+
10
+ class Test
11
+ include Supplejack::Request
12
+ end
13
+
14
+ module Supplejack
15
+ describe Request do
16
+ before(:each) do
17
+ @test = Test.new
18
+ Supplejack.stub(:api_key) { '123' }
19
+ Supplejack.stub(:api_url) { 'http://api.org' }
20
+ Supplejack.stub(:timeout) { 20 }
21
+ end
22
+
23
+ describe '#get' do
24
+ before(:each) do
25
+ RestClient::Request.stub(:execute).and_return(%{ {"search": {}} })
26
+ end
27
+
28
+ it 'serializes the parameters in the url' do
29
+ RestClient::Request.should_receive(:execute).with(hash_including(:url => "http://api.org/records.json?#{{:and => {:name => 'John'}}.to_query}&api_key=123"))
30
+ @test.get('/records', {:and => {:name => 'John'}})
31
+ end
32
+
33
+ it 'parses the JSON returned' do
34
+ RestClient::Request.stub(:execute).and_return(%{ {"search": {}} })
35
+ @test.get('/records').should eq({'search' => {}})
36
+ end
37
+
38
+ it 'logs a error correctly when the response from the API failed' do
39
+ RestClient::Request.stub(:execute).and_return(nil)
40
+ @subscriber = Supplejack::LogSubscriber.new
41
+ Supplejack::LogSubscriber.stub(:new) { @subscriber }
42
+ @subscriber.should_receive(:log_request)
43
+ @test.get('/records')
44
+ end
45
+
46
+ context 'request format' do
47
+ it 'executes a request in a json format' do
48
+ RestClient::Request.should_receive(:execute).with(hash_including(:url => 'http://api.org/records.json?api_key=123'))
49
+ @test.get('/records')
50
+ end
51
+
52
+ it 'overrides the response format with xml' do
53
+ RestClient::Request.should_receive(:execute).with(hash_including(:url => 'http://api.org/records.xml?api_key=123'))
54
+ @test.get('/records', {}, {:format => :xml})
55
+ end
56
+ end
57
+
58
+ context 'api key' do
59
+ it 'overrides the api key' do
60
+ RestClient::Request.should_receive(:execute).with(hash_including(:url => 'http://api.org/records.json?api_key=456'))
61
+ @test.get('/records', {:api_key => '456'})
62
+ end
63
+ end
64
+
65
+ context 'timeout' do
66
+ it 'calculates the timeout' do
67
+ @test.should_receive(:timeout).with(hash_including({:timeout => 60}))
68
+ @test.get('/', {}, {:timeout => 60})
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#post' do
74
+ before(:each) do
75
+ RestClient::Request.stub(:execute)
76
+ end
77
+
78
+ it 'executes a post request' do
79
+ RestClient::Request.should_receive(:execute).with(hash_including(:method => :post))
80
+ @test.post('/records/1/ucm', {})
81
+ end
82
+
83
+ it 'passes the payload along' do
84
+ payload = {'ucm_record' => {:name => 'geocords', :value => '1234'}}
85
+ RestClient::Request.should_receive(:execute).with(hash_including(:payload => payload.to_json))
86
+ @test.post('/records/1/ucm', {}, payload)
87
+ end
88
+
89
+ it 'adds the extra parameters to the post request' do
90
+ @test.should_receive(:full_url).with('/records/1/ucm', nil, {api_key: '12344'})
91
+ @test.post('/records/1/ucm', {api_key: '12344'}, {})
92
+ end
93
+
94
+ it 'adds json headers and converts the payload into json' do
95
+ RestClient::Request.should_receive(:execute).with(hash_including(:headers => {:content_type => :json, :accept => :json}, :payload => {records: [{record_id: 1, position: 1}, {record_id:2, position:2}]}.to_json))
96
+ @test.post('/records/1/ucm', {}, {records: [{record_id: 1, position: 1}, {record_id:2, position:2}]})
97
+ end
98
+
99
+ it 'parses the JSON response' do
100
+ RestClient::Request.stub(:execute) { {user: {name: 'John'}}.to_json }
101
+ @test.post('/users', {}, {}).should eq({'user' => {'name' => 'John'}})
102
+ end
103
+ end
104
+
105
+ describe '#delete' do
106
+ before(:each) do
107
+ RestClient::Request.stub(:execute)
108
+ end
109
+
110
+ it 'executes a delete request' do
111
+ RestClient::Request.should_receive(:execute).with(hash_including(:method => :delete))
112
+ @test.delete('/records/1/ucm/1')
113
+ end
114
+
115
+ it 'adds the extra parameters to the delete request' do
116
+ @test.should_receive(:full_url).with('/records/1/ucm/1', nil, {api_key: '12344'})
117
+ @test.delete('/records/1/ucm/1', {api_key: '12344'})
118
+ end
119
+ end
120
+
121
+ describe '#put' do
122
+ before(:each) do
123
+ RestClient::Request.stub(:execute)
124
+ end
125
+
126
+ it 'executes a put request' do
127
+ RestClient::Request.should_receive(:execute).with(hash_including(:method => :put))
128
+ @test.put('/records/1/ucm/1')
129
+ end
130
+
131
+ it 'passes the payload along' do
132
+ RestClient::Request.should_receive(:execute).with(hash_including(:payload => {:name => 1}.to_json))
133
+ @test.put('/records/1/ucm/1', {}, {:name => 1})
134
+ end
135
+
136
+ it 'adds the extra parameters to the put request' do
137
+ @test.should_receive(:full_url).with('/records/1/ucm/1', nil, {api_key: '12344'})
138
+ @test.put('/records/1/ucm/1', {api_key: '12344'}, {})
139
+ end
140
+
141
+ it 'adds json headers and converts the payload into json' do
142
+ RestClient::Request.should_receive(:execute).with(hash_including(:headers => {:content_type => :json, :accept => :json}, :payload => {records: [1,2,3]}.to_json))
143
+ @test.put('/records/1/ucm/1', {}, {records: [1,2,3]})
144
+ end
145
+
146
+ it 'parses the JSON response' do
147
+ RestClient::Request.stub(:execute) { {user: {name: 'John'}}.to_json }
148
+ @test.put('/users/1', {}, {}).should eq({'user' => {'name' => 'John'}})
149
+ end
150
+ end
151
+
152
+ describe '#timeout' do
153
+ it 'defaults to the timeout in the configuration' do
154
+ @test.send(:timeout).should eq 20
155
+ end
156
+
157
+ it 'defaults to 30 when not set in the configuration' do
158
+ Supplejack.stub(:timeout) { nil }
159
+ @test.send(:timeout).should eq 30
160
+ end
161
+
162
+ it 'overrides the timeout' do
163
+ @test.send(:timeout, {:timeout => 60}).should eq 60
164
+ end
165
+ end
166
+
167
+ describe '#full_url' do
168
+ it 'returns the full url with default api_url, format and api_key' do
169
+ @test.send(:full_url, '/records').should eq('http://api.org/records.json?api_key=123')
170
+ end
171
+
172
+ it 'overrides the format' do
173
+ @test.send(:full_url, '/records', 'xml').should eq('http://api.org/records.xml?api_key=123')
174
+ end
175
+
176
+ it 'overrides the api key' do
177
+ @test.send(:full_url, '/records', nil, {:api_key => '456'}).should eq('http://api.org/records.json?api_key=456')
178
+ end
179
+
180
+ it 'url encodes the parameters' do
181
+ @test.send(:full_url, '/records', nil, {:api_key => '456', :i => {:category => 'Images'}}).should eq('http://api.org/records.json?api_key=456&i%5Bcategory%5D=Images')
182
+ end
183
+
184
+ it 'adds debug=true when enable_debugging is set to true' do
185
+ Supplejack.stub(:enable_debugging) { true }
186
+ @test.send(:full_url, '/records', nil, {}).should eq 'http://api.org/records.json?api_key=123&debug=true'
187
+ end
188
+
189
+ it 'handles nil params' do
190
+ @test.send(:full_url, '/records', nil, nil).should eq 'http://api.org/records.json?api_key=123'
191
+ end
192
+ end
193
+
194
+ end
195
+ end
@@ -0,0 +1,727 @@
1
+ # The Supplejack Client code is Crown copyright (C) 2014, New Zealand Government,
2
+ # and is licensed under the GNU General Public License, version 3.
3
+ # See https://github.com/DigitalNZ/supplejack_client for details.
4
+ #
5
+ # Supplejack was created by DigitalNZ at the National Library of NZ
6
+ # and the Department of Internal Affairs. http://digitalnz.org/supplejack
7
+
8
+ require 'spec_helper'
9
+
10
+ class TestRecord
11
+ def initialize(attributes={})
12
+ end
13
+ end
14
+
15
+ class TestItem
16
+ def initialize(attributes={})
17
+ end
18
+ end
19
+
20
+ module Supplejack
21
+ describe Search do
22
+
23
+ describe '#initalize' do
24
+ it 'doesn\'t break when initalized with nil' do
25
+ Search.new(nil)
26
+ end
27
+ end
28
+
29
+ describe '#params' do
30
+ it 'defaults to facets set in initializer' do
31
+ Supplejack.stub(:facets) { ['location', 'description'] }
32
+ Search.new.api_params[:facets].should eq('location,description')
33
+ end
34
+
35
+ it 'sets the facets through the params' do
36
+ Search.new(:facets => 'name,description').api_params[:facets].should eq('name,description')
37
+ end
38
+
39
+ it 'defaults to facets_per_page set in initializer' do
40
+ Supplejack.stub(:facets_per_page) { 33 }
41
+ Search.new.api_params[:facets_per_page].should eq 33
42
+ end
43
+
44
+ it 'sets the facets_per_page through the params' do
45
+ Search.new(:facets_per_page => 13).api_params[:facets_per_page].should eq 13
46
+ end
47
+
48
+ it 'deletes rails specific params' do
49
+ params = Search.new(:controller => 'records', :action => 'index').api_params
50
+ params.should_not have_key(:controller)
51
+ params.should_not have_key(:action)
52
+ end
53
+ end
54
+
55
+ describe '#text' do
56
+ it 'returns the search text' do
57
+ Search.new(:text => 'dog').text.should eq('dog')
58
+ end
59
+ end
60
+
61
+ # describe '#geo_bbox' do
62
+ # end
63
+
64
+ # describe "#record_type" do
65
+ # end
66
+
67
+ describe '#page' do
68
+ it 'defaults to 1' do
69
+ Search.new.page.should eq 1
70
+ end
71
+
72
+ it 'converts the page to a number' do
73
+ Search.new(:page => '2').page.should eq 2
74
+ end
75
+ end
76
+
77
+ describe '#per_page' do
78
+ it 'defaults to the per_page in the initializer' do
79
+ Supplejack.stub(:per_page) { 16 }
80
+ Search.new.per_page.should eq 16
81
+ end
82
+
83
+ it 'sers the per_page through the params' do
84
+ Search.new(:per_page => '3').per_page.should eq 3
85
+ end
86
+ end
87
+
88
+ describe '#sort' do
89
+ it 'returns the field to sort on' do
90
+ Search.new(:sort => 'content_partner').sort.should eq 'content_partner'
91
+ end
92
+
93
+ it 'returns nil when not set' do
94
+ Search.new.sort.should be_nil
95
+ end
96
+ end
97
+
98
+ describe '#direction' do
99
+ it 'returns the direction to sort the results' do
100
+ Search.new(:direction => 'asc').direction.should eq 'asc'
101
+ end
102
+
103
+ it 'returns nil when not set' do
104
+ Search.new.direction.should be_nil
105
+ end
106
+ end
107
+
108
+ # describe "#custom_search" do
109
+ # end
110
+
111
+ describe '#search_attributes' do
112
+ it 'sets the filter defined in Supplejack.search_attributes with its current value' do
113
+ Supplejack.stub(:search_attributes) { [:location] }
114
+ Search.new(:i => {:location => 'Wellington'}).location.should eq 'Wellington'
115
+ end
116
+ end
117
+
118
+ describe '#filters' do
119
+ before(:each) do
120
+ @filters = {:location => 'Wellington', :country => 'New Zealand'}
121
+ end
122
+
123
+ it 'returns filters in a array format by default' do
124
+ Search.new(:i => @filters).filters.should include([:location, 'Wellington'], [:country, 'New Zealand'])
125
+ end
126
+
127
+ it 'returns a hash of the current search filters' do
128
+ Search.new(:i => @filters).filters(:format => :hash).should include(@filters)
129
+ end
130
+
131
+ it 'expands filters that contain arrays' do
132
+ @filters = {:location => ['Wellington', 'Auckland']}
133
+ Search.new(:i => @filters).filters.should include([:location, 'Wellington'], [:location, 'Auckland'])
134
+ end
135
+
136
+ it 'removes the location filter' do
137
+ @filters = {:location => ['Wellington', 'Auckland']}
138
+ Search.new(:i => @filters).filters(:except => [:location]).should_not include([:location, 'Wellington'], [:location, 'Auckland'])
139
+ end
140
+ end
141
+
142
+ describe "#facets" do
143
+ before(:each) do
144
+ @search = Search.new
145
+ Supplejack.facets = [:location]
146
+ @facets_hash = {"location" => {"Wellington" => 100}, "mayor" => {"Brake, Brian" => 20}}
147
+ @search.instance_variable_set(:@response, {"search" => {"facets" => @facets_hash}})
148
+ end
149
+
150
+ it 'executes the request only once' do
151
+ @search.should_receive(:execute_request).once
152
+ @search.facets
153
+ @search.facets
154
+ end
155
+
156
+ it 'handles a failed request from the API' do
157
+ @search.instance_variable_set(:@response, {'search' => {}})
158
+ @search.facets.should be_empty
159
+ end
160
+
161
+ it 'initializes a facet object for each facet' do
162
+ Supplejack::Facet.should_receive(:new).with('location', {'Wellington' => 100})
163
+ Supplejack::Facet.should_receive(:new).with('mayor', {'Brake, Brian' => 20})
164
+ @search.facets
165
+ end
166
+
167
+ it 'orders the facets based on the order set in the initializer' do
168
+ Supplejack.stub(:facets) { [:mayor, :location] }
169
+ @search.facets.first.name.should eq 'mayor'
170
+ @search.facets.last.name.should eq 'location'
171
+ end
172
+
173
+ it 'orders the facets the other way around' do
174
+ Supplejack.stub(:facets) { [:location, :mayor] }
175
+ @search.facets.first.name.should eq 'location'
176
+ @search.facets.last.name.should eq 'mayor'
177
+ end
178
+
179
+ it 'returns the facet values' do
180
+ @search.facets.first.values.should eq({'Wellington' => 100})
181
+ end
182
+ end
183
+
184
+ describe '#facet' do
185
+ before(:each) do
186
+ @search = Search.new
187
+ end
188
+
189
+ it 'returns the specified facet' do
190
+ facet = Supplejack::Facet.new('collection', [])
191
+ @search.stub(:facets) { [facet] }
192
+
193
+ @search.facet('collection').should eq facet
194
+ end
195
+
196
+ it 'returns nil when facet is not found' do
197
+ @search.stub(:facets) { [] }
198
+ @search.facet('collection').should be_nil
199
+ end
200
+
201
+ it 'returns nil if value is nil' do
202
+ facet = Supplejack::Facet.new('collection', [])
203
+ @search.stub(:facets) { [facet] }
204
+ @search.facet(nil).should be_nil
205
+ end
206
+ end
207
+
208
+ describe '#total' do
209
+ before(:each) do
210
+ @search = Search.new
211
+ @search.instance_variable_set(:@response, {'search' => {'result_count' => 100}})
212
+ end
213
+
214
+ it 'executes the request only once' do
215
+ @search.should_receive(:execute_request).once
216
+ @search.total
217
+ @search.total
218
+ end
219
+
220
+ it 'returns the total from the response' do
221
+ @search.total.should eq 100
222
+ end
223
+
224
+ it 'returns 0 when the request to the API failed' do
225
+ @search.instance_variable_set(:@response, {'search' => {}})
226
+ @search.total.should eq 0
227
+ end
228
+ end
229
+
230
+ describe '#results' do
231
+ before(:each) do
232
+ Supplejack.stub(:record_klass).and_return('TestRecord')
233
+ @search = Search.new
234
+ @record1 = {'id' => 1, 'title' => 'Wellington'}
235
+ @record2 = {'id' => 2, 'title' => 'Auckland'}
236
+ @search.instance_variable_set(:@response, {'search' => {'results' => [@record1, @record2]}})
237
+ end
238
+
239
+ it 'executes the request only once' do
240
+ @search.stub(:total) { 10 }
241
+ @search.should_receive(:execute_request).once
242
+ @search.results
243
+ @search.results
244
+ end
245
+
246
+ it 'initializes record objects with the default class' do
247
+ TestRecord.should_receive(:new).with(@record1)
248
+ TestRecord.should_receive(:new).with(@record2)
249
+ @search.results
250
+ end
251
+
252
+ it 'initializes record objects with the class provided in the params' do
253
+ @search = Search.new(:record_klass => 'test_item')
254
+ @search.instance_variable_set(:@response, {'search' => {'results' => [@record1, @record2]}})
255
+ TestItem.should_receive(:new).with(@record1)
256
+ TestItem.should_receive(:new).with(@record2)
257
+ @search.results
258
+ end
259
+
260
+ it 'returns a array of objects of the provided class' do
261
+ @search.results.first.class.should eq TestRecord
262
+ end
263
+
264
+ it 'returns an array wraped in paginated collection object' do
265
+ @search.results.current_page.should eq 1
266
+ end
267
+
268
+ it 'returns empty paginated collection when API request failed' do
269
+ @search.instance_variable_set(:@response, {'search' => {}})
270
+ @search.results.size.should eq 0
271
+ end
272
+ end
273
+
274
+ describe '#counts' do
275
+ before(:each) do
276
+ @search = Search.new
277
+ @search.stub(:fetch_counts) { {'images' => 100} }
278
+ end
279
+
280
+ context 'caching disabled' do
281
+ before(:each) do
282
+ Supplejack.stub(:enable_caching) { false }
283
+ end
284
+
285
+ it 'fetches the counts' do
286
+ query_params = {'images' => {:category => 'Images'}}
287
+ @search.should_receive(:fetch_counts).with(query_params)
288
+ @search.counts(query_params)
289
+ end
290
+ end
291
+ end
292
+
293
+ describe '#fetch_counts' do
294
+ context 'without filters' do
295
+ before(:each) do
296
+ @search = Search.new
297
+ end
298
+
299
+ it 'returns a hash with row names and its values' do
300
+ @search.stub(:get).and_return({'search' => {'facets' => {'counts' => {'images' => 151818}}}})
301
+ @search.fetch_counts({}).should eq({'images' => 151818})
302
+ end
303
+
304
+ it 'returns every count even when there are no results matching' do
305
+ @search.stub(:get).and_return({'search' => {'facets' => {'counts' => {'images' => 151818}}}})
306
+ @search.fetch_counts({:images => {}, :headings => {}}).should eq({'images' => 151818, 'headings' => 0})
307
+ end
308
+
309
+ it 'returns 0 for every facet when the API response fails' do
310
+ @search.stub(:get).and_raise(StandardError)
311
+ @search.fetch_counts({:images => {}}).should eq({'images' => 0})
312
+ end
313
+ end
314
+ end
315
+
316
+ describe '#counts_params' do
317
+ context 'without filters' do
318
+ before(:each) do
319
+ @search = Search.new
320
+ end
321
+
322
+ it 'requests record_type == all' do
323
+ query_parameters = {:headings => {:record_type => '1'}}
324
+ @search.counts_params(query_parameters).should include(:facet_query => query_parameters, :record_type => 'all')
325
+ end
326
+
327
+ it 'adds the restrictions set in the without variable' do
328
+ query_parameters = {:headings => {:record_type => '1'}}
329
+ @search.without = {:location => 'Wellington'}
330
+ @search.counts_params(query_parameters).should include(:without => {:location => 'Wellington'})
331
+ end
332
+
333
+ it 'restricts the result set to only ones that match the and filters' do
334
+ query_parameters = {:headings => {:record_type => '1'}}
335
+ @search.and = {:location => 'Wellington'}
336
+ @search.counts_params(query_parameters).should include(:and => {:location => 'Wellington'})
337
+ end
338
+
339
+ it 'restricts the result set to results that match any of the or filters' do
340
+ query_parameters = {:headings => {:record_type => '1'}}
341
+ @search.or = {:location => ['Wellington', 'Auckland']}
342
+ @search.counts_params(query_parameters).should include(:or => {:location => ['Wellington', 'Auckland']})
343
+ end
344
+
345
+ it 'executes a request with facet_queries' do
346
+ query_parameters = {:images => {:creator => 'all', :record_type => '0'}, :headings => {:record_type => '1'}}
347
+ @search.counts_params(query_parameters).should include(:facet_query => query_parameters)
348
+ end
349
+
350
+ it 'passes the text when present' do
351
+ @search = Search.new(:text => 'dogs')
352
+ @search.counts_params({}).should include(:text => 'dogs')
353
+ end
354
+
355
+ # it 'passes the geo_bbox when present' do
356
+ # @search = Search.new(geo_bbox: '1,2,3,4')
357
+ # @search.counts_params({}).should include(geo_bbox: '1,2,3,4')
358
+ # end
359
+
360
+ it 'merges the :i and :il filters with record_type 0' do
361
+ query_parameters = {:images => {'creator' => 'all', 'record_type' => '0'}, :headings => {'record_type' => '1', :dc_type => 'Group'}}
362
+ @search = Search.new(:i => {:category => 'Images'}, :il => {:year => '1998'})
363
+
364
+ images_query = {:creator => 'all', :record_type => '0', :category => 'Images', :year => '1998'}
365
+ headings_query = {:record_type => '1', :dc_type => 'Group'}
366
+
367
+ @search.counts_params(query_parameters).should include(:facet_query => {:images => images_query, :headings => headings_query})
368
+ end
369
+
370
+ it 'merges *_text fields' do
371
+ query_parameters = {:images => {'creator' => 'all', 'record_type' => '0'}}
372
+ @search = Search.new(:i => {:subject_text => 'dog'})
373
+
374
+ images_query = {:creator => 'all', :record_type => '0'}
375
+
376
+ @search.counts_params(query_parameters).should include(:text => 'dog', :query_fields => [:subject], :facet_query => {:images => images_query})
377
+ end
378
+ end
379
+
380
+ context 'with active filters' do
381
+ before(:each) do
382
+ @search = Search.new(:i => {:location => 'Wellington'})
383
+ end
384
+
385
+ it 'merges the existing filters into every facet query' do
386
+ query_parameters = {:images => {'creator' => 'all', 'record_type' => 0}}
387
+ expected_filters = {:images => {:creator => 'all', :location => 'Wellington', :record_type => 0}}
388
+ @search.counts_params(query_parameters).should include(:facet_query => expected_filters)
389
+ end
390
+
391
+ it 'merges existing filters without overriding' do
392
+ query_parameters = {:images => {'location' => 'Matapihi', 'record_type' => 0}}
393
+ expected_filters = {:images => {:location => ['Wellington', 'Matapihi'], :record_type => 0}}
394
+ @search.counts_params(query_parameters).should include(:facet_query => expected_filters)
395
+ end
396
+
397
+ it 'overrides the record_type' do
398
+ @search = Search.new(:record_type => '1')
399
+ query_parameters = {:images => {'record_type' => '0'}}
400
+ expected_filters = {:images => {:record_type => '0'}}
401
+ @search.counts_params(query_parameters).should include(:facet_query => expected_filters)
402
+ end
403
+
404
+ it 'merges existing negative filters' do
405
+ @search = Search.new(:i => {'-category' => 'Groups'})
406
+ query_parameters = {:photos => {'has_large_thumbnail_url' => 'Y'}}
407
+ expected_filters = {:photos => {:has_large_thumbnail_url => 'Y', :'-category' => 'Groups'}}
408
+ @search.counts_params(query_parameters).should include(:facet_query => expected_filters)
409
+ end
410
+ end
411
+ end
412
+
413
+ describe '#request_path' do
414
+ before(:each) do
415
+ @search = Search.new
416
+ end
417
+
418
+ it 'returns /records by default' do
419
+ @search.request_path.should eq '/records'
420
+ end
421
+ end
422
+
423
+ describe '#execute_request' do
424
+ before(:each) do
425
+ @search = Search.new
426
+ end
427
+
428
+ it 'only executes the request once' do
429
+ @search.should_receive(:get).once.and_return('{}')
430
+ @search.execute_request
431
+ @search.execute_request
432
+ end
433
+
434
+ it 'removes the results that match the without filters' do
435
+ @search.without = {:location => 'Wellington'}
436
+ @search.should_receive(:get).with('/records', hash_including(:without => {:location => 'Wellington'}))
437
+ @search.execute_request
438
+ end
439
+
440
+ it 'restricts the result set to only ones that match the and filters' do
441
+ @search.and = {:location => 'Wellington'}
442
+ @search.should_receive(:get).with('/records', hash_including(:and => {:location => 'Wellington'}))
443
+ @search.execute_request
444
+ end
445
+
446
+ it 'restricts the result set to only ones that match any of the or filters' do
447
+ @search.or = {:location => ['Wellington']}
448
+ @search.should_receive(:get).with('/records', hash_including(:or => {:location => ['Wellington']}))
449
+ @search.execute_request
450
+ end
451
+
452
+ it 'returns a empty search hash when a error is raised' do
453
+ @search.stub(:get).and_raise(StandardError)
454
+ @search.execute_request.should eq({'search' => {}})
455
+ end
456
+
457
+ context 'caching enabled' do
458
+ before :each do
459
+ @cache = double(:cache).as_null_object
460
+ Rails.stub(:cache) { @cache }
461
+ Supplejack.stub(:enable_caching) { true }
462
+ end
463
+
464
+ it 'caches the response when it is cacheable' do
465
+ search = Supplejack::Search.new
466
+ search.stub(:cacheable?) { true }
467
+ cache_key = Digest::MD5.hexdigest("/records?#{search.api_params.to_query}")
468
+ Rails.cache.should_receive(:fetch).with(cache_key, expires_in: 1.hour)
469
+ search.execute_request
470
+ end
471
+
472
+ it 'doesnt cache the response it is not cacheable' do
473
+ search = Supplejack::Search.new(text: "dogs")
474
+ search.stub(:cacheable?) { false }
475
+ Rails.cache.should_not_receive(:fetch)
476
+ search.execute_request
477
+ end
478
+ end
479
+ end
480
+
481
+ describe '#cacheable?' do
482
+ it 'returns true when it doesn\'t have a text parameter' do
483
+ Supplejack::Search.new.cacheable?.should be_true
484
+ end
485
+
486
+ it 'returns false when it has a text parameter' do
487
+ Supplejack::Search.new(text: 'Dogs').cacheable?.should be_false
488
+ end
489
+
490
+ it 'returns false then it\'s not the first page of results' do
491
+ Supplejack::Search.new(page: '2').cacheable?.should be_false
492
+ end
493
+ end
494
+
495
+ describe '#has_attribute_name?' do
496
+ before(:each) do
497
+ @search = Search.new
498
+ @search.location = ['Wellington', 'Auckland']
499
+ end
500
+
501
+ it 'returns true if value is in filter' do
502
+ @search.has_location?('Wellington').should be_true
503
+ end
504
+
505
+ it 'returns false is value is not in filter' do
506
+ @search.has_location?('Videos').should be_false
507
+ end
508
+
509
+ context 'search filter is single valued' do
510
+ before(:each) do
511
+ @search = Search.new
512
+ @search.location = 'Wellington'
513
+ end
514
+
515
+ it 'returns true if value matches filter' do
516
+ @search.has_location?('Wellington').should be_true
517
+ end
518
+
519
+ it 'returns false if value does not match the filter' do
520
+ @search.has_location?('Cats').should be_false
521
+ end
522
+
523
+ it 'returns false when location has nil value' do
524
+ @search.location = nil
525
+ @search.has_category?('Cats').should be_false
526
+ end
527
+
528
+ it 'shouldn\'t search for a non existent search attribute' do
529
+ Supplejack.stub(:search_attributes) { [] }
530
+ @search.should_not_receive(:has_filter_and_value?)
531
+ @search.has_category?('Cats')
532
+ end
533
+ end
534
+ end
535
+
536
+ describe '#categories' do
537
+ before(:each) do
538
+ @search = Search.new({:i => {:category => 'Books', :year => 2001}, :text => 'Dogs'})
539
+ @search.stub(:get) { {'search' => {'facets' => {'category' => {'Books' => 123}}, 'result_count' => 123}} }
540
+ end
541
+
542
+ it 'should call the fetch_values method' do
543
+ @search.should_receive(:facet_values).with('category', {})
544
+ @search.categories
545
+ end
546
+
547
+ it 'removes category filter from the search request' do
548
+ @search.should_receive(:get).with('/records', hash_including(:and => {:year => 2001})).and_return({'search' => {'facets' => {'category' => {'Books' => 123}}}})
549
+ @search.categories
550
+ end
551
+
552
+ it 'returns the category facet hash ' do
553
+ @search.categories.should include('Books' => 123)
554
+ end
555
+
556
+ it 'asks the API for 0 results' do
557
+ @search.should_receive(:get).with('/records', hash_including({:per_page => 0}))
558
+ @search.categories
559
+ end
560
+
561
+ it 'should return add the All count to the hash' do
562
+ @search.categories['All'].should eq 123
563
+ end
564
+
565
+ it 'orders the category values by :count' do
566
+ @search.should_receive(:facet_values).with('category', {:sort => :count})
567
+ @search.categories({:sort => :count})
568
+ end
569
+ end
570
+
571
+ describe '#fetch_facet_values' do
572
+ before(:each) do
573
+ @search = Search.new({:i => {:category => 'Books', :year => 2001}, :text => 'Dogs'})
574
+ @search.stub(:get) { {'search' => {'facets' => {'category' => {'Books' => 123}}, 'result_count' => 123}} }
575
+ end
576
+
577
+ it 'returns the category facet hash' do
578
+ @search.fetch_facet_values('category').should include('Books' => 123)
579
+ end
580
+
581
+ it 'returns empty values when the request to the API failed' do
582
+ @search.stub(:get).and_raise(StandardError)
583
+ @search.fetch_facet_values('category').should eq({'All' => 0})
584
+ end
585
+
586
+ it 'should add the All count to the hash' do
587
+ @search.fetch_facet_values('category')['All'].should eq 123
588
+ end
589
+
590
+ it 'doesnt return the All count ' do
591
+ @search.fetch_facet_values('category', {:all => false}).should_not have_key('All')
592
+ end
593
+
594
+ it 'memoizes the facet_values' do
595
+ @search.should_receive(:get).once
596
+ @search.fetch_facet_values('category')
597
+ @search.fetch_facet_values('category')
598
+ end
599
+
600
+ context 'sorting' do
601
+ before(:each) do
602
+ @facet = Supplejack::Facet.new('category', {'All' => 123, 'Books' => 123})
603
+ Supplejack::Facet.stub(:new) { @facet }
604
+ end
605
+
606
+ it 'initializes a Supplejack::Facet' do
607
+ Supplejack::Facet.should_receive(:new).with('category', {'All' => 123, 'Books' => 123})
608
+ @search.fetch_facet_values('category')
609
+ end
610
+
611
+ it 'tells the facet how to sort the values' do
612
+ @facet.should_receive(:values).with(:index)
613
+ @search.fetch_facet_values('category', {:sort => :index})
614
+ end
615
+
616
+ it 'doesn\'t sort by default' do
617
+ @facet.should_receive(:values).with(nil)
618
+ @search.fetch_facet_values('category')
619
+ end
620
+ end
621
+ end
622
+
623
+ describe 'facet_values_params' do
624
+ before(:each) do
625
+ @search = Search.new({:i => {:type => 'Person', :year => 2001}, :text => 'Dogs'})
626
+ end
627
+
628
+ it 'removes type filter from the search request' do
629
+ @search.facet_values_params('type').should include(:and => {:year => 2001})
630
+ end
631
+
632
+ it 'requests 0 results per_page' do
633
+ @search.facet_values_params('type').should include(:per_page => 0)
634
+ end
635
+
636
+ it 'adds without filters' do
637
+ @search = Search.new({:i => {:type => 'Person', :year => 2001, '-group' => 'Group'}, :text => 'Dogs'})
638
+ @search.facet_values_params('type').should include(:without => {:group => 'Group'})
639
+ end
640
+
641
+ it 'only adds the and_filters to :and' do
642
+ @search = Search.new({:i => {:type => 'Person', :year => 2001, '-group' => 'Group'}, :text => 'Dogs'})
643
+ @search.facet_values_params('type').should include(:and => {:year => 2001})
644
+ end
645
+
646
+ it 'gets the facet_values for a record_type 1' do
647
+ @search = Search.new({:i => {:type => 'Person'}, :h => {:group => 'Group'}, :text => 'Dogs', :record_type => 1})
648
+ @search.facet_values_params('group').should include(:and => {})
649
+ end
650
+
651
+ it 'restricts results to filters specified in without accessor' do
652
+ @search = Search.new
653
+ @search.without = {:website => 'Flickr'}
654
+ @search.facet_values_params('type').should include(:without => {:website => 'Flickr'})
655
+ end
656
+
657
+ it 'merges in the filters specified in without' do
658
+ @search = Search.new({:i => {'-type' => 'Person'}})
659
+ @search.without = {:website => 'Flickr'}
660
+ @search.facet_values_params('type').should include(:without => {:website => 'Flickr', :type => 'Person'})
661
+ end
662
+
663
+ it 'adds the restrictions set in the and variable' do
664
+ @search = Search.new
665
+ @search.and = {:content_partner => 'NLNZ'}
666
+ @search.facet_values_params('type').should include(:and => {:content_partner => 'NLNZ'})
667
+ end
668
+
669
+ it 'adds the restrictions set in the or variable' do
670
+ @search = Search.new
671
+ @search.or = {:content_partner => 'NLNZ'}
672
+ @search.facet_values_params('type').should include(:or => {:content_partner => 'NLNZ'})
673
+ end
674
+
675
+ it 'memoizes the params' do
676
+ @search = Search.new
677
+ @search.should_receive(:url_format).once.and_return(double(:url_format, :and_filters => {}))
678
+ @search.facet_values_params('type')
679
+ @search.facet_values_params('type')
680
+ end
681
+
682
+ it 'adds a parameter for facets_per_page if the option is present' do
683
+ @search.facet_values_params('type', {:facets_per_page => 15}).should include(:facets_per_page => 15)
684
+ end
685
+ end
686
+
687
+ describe '#facet_values' do
688
+ before(:each) do
689
+ @search = Search.new
690
+ @search.stub(:fetch_facet_values) { {'Books' => 100} }
691
+ end
692
+
693
+ context 'caching disabled' do
694
+ before(:each) do
695
+ Supplejack.stub(:enable_caching) { false }
696
+ end
697
+
698
+ it 'fetches the facet values' do
699
+ @search.should_receive(:fetch_facet_values).with('category', anything)
700
+ @search.facet_values('category', anything)
701
+ end
702
+ end
703
+ end
704
+
705
+ describe '#merge_extra_filters' do
706
+ before(:each) do
707
+ @search = Search.new
708
+ end
709
+
710
+ it 'merges the and filters' do
711
+ @search.and = {:type => 'Person'}
712
+ @search.merge_extra_filters({:and => {:location => 'Wellington'}}).should eq({:and => {:location => 'Wellington', :type => 'Person'}})
713
+ end
714
+
715
+ it 'merges the or filters' do
716
+ @search.or = {:type => 'Person'}
717
+ @search.merge_extra_filters({:and => {:location => 'Wellington'}}).should eq({:and => {:location => 'Wellington'}, :or => {:type => 'Person'}})
718
+ end
719
+
720
+ it 'merges the without filters' do
721
+ @search.without = {:type => 'Person'}
722
+ @search.merge_extra_filters({:and => {:location => 'Wellington'}}).should eq({:and => {:location => 'Wellington'}, :without => {:type => 'Person'}})
723
+ end
724
+ end
725
+
726
+ end
727
+ end