drawbridge 0.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.
@@ -0,0 +1,3 @@
1
+ module Drawbridge
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,23 @@
1
+ require 'helper'
2
+
3
+ describe Drawbridge::Debug do
4
+ it "should return line" do
5
+ Drawbridge::Debug.draw_line.must_equal "*"*80
6
+ end
7
+
8
+ it "should output debugg" do
9
+ Drawbridge.config.expects(:endeca_debug).returns(true)
10
+ Drawbridge::Debug.expects(:puts).at_most(3)
11
+
12
+ Drawbridge::Debug.log 'test', []
13
+ end
14
+
15
+ it "should not do any debugging" do
16
+ Drawbridge.config.expects(:endeca_debug).returns(false)
17
+ Drawbridge::Debug.expects(:puts).never
18
+
19
+ Drawbridge::Debug.log 'test', []
20
+
21
+ end
22
+
23
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require "drawbridge"
5
+ require 'minitest/autorun'
6
+ require 'minitest/spec'
7
+ require 'minitest/mock'
8
+ require 'mocha/setup'
9
+
10
+ begin
11
+ require 'turn'
12
+ rescue LoadError
13
+ warn 'could not load turn gem'
14
+ end
15
+
16
+ Drawbridge.setup do |config|
17
+ config.endeca_debug = ENV.fetch('ENDECA_DEBUG') { false }
18
+ end
@@ -0,0 +1,68 @@
1
+ require 'helper'
2
+
3
+ describe Drawbridge::RefinementScrubber do
4
+
5
+ describe '::scrub' do
6
+
7
+ describe 'aggregated refinements' do
8
+ let(:refinements) { Drawbridge::RefinementScrubber.scrub(aggr_refinements) }
9
+ let(:properties){ refinements[2]['dimensionvalues'].first['dimvalueproperties'] }
10
+
11
+ it 'should add an aggrbins key' do
12
+ properties['aggrbins'].wont_be_nil
13
+ end
14
+
15
+ it 'should copy the data from DGraph.AggrBins' do
16
+ properties['aggrbins'].must_equal properties['dgraph.aggrbins']
17
+ end
18
+ end
19
+
20
+ describe 'non-aggregated refinements' do
21
+ let(:refinements) {Drawbridge::RefinementScrubber.scrub(non_aggr_refinements)}
22
+
23
+ end
24
+ end
25
+
26
+ def aggr_refinements
27
+ [
28
+ {"dimensionid"=>5885, "dimensionname"=>"price_range"},
29
+ {"dimensionid"=>3, "dimensionname"=>"bedrooms"},
30
+ {
31
+ "dimensionid"=>5974,
32
+ "dimensionname"=>"_telco",
33
+ "dimensionvalues"=>
34
+ [
35
+ {
36
+ "dimvaluename"=>"Time Warner Cable",
37
+ "dimvalueproperties"=>
38
+ {
39
+ "dgraph.aggrbins"=>"10",
40
+ "sortname"=>"Time Warner",
41
+ "sortfield"=>"timewarner"
42
+ },
43
+ "numberofrecords"=>"332",
44
+ "dimvalueid"=>5978},
45
+ {
46
+ "dimvaluename"=>"Verizon FiOS",
47
+ "dimvalueproperties"=>
48
+ {
49
+ "dgraph.aggrbins"=>"33",
50
+ "sortbar"=>"vzn_sort_bar",
51
+ "sortname"=>"Verizon",
52
+ "sortfield"=>"verizon"
53
+ },
54
+ "numberofrecords"=>"627",
55
+ "dimvalueid"=>5976
56
+ }
57
+ ]
58
+ }
59
+ ]
60
+ end
61
+
62
+ def non_aggr_refinements
63
+ [
64
+ {"dimensionid"=>5885, "dimensionname"=>"price_range"},
65
+ {"dimensionid"=>3, "dimensionname"=>"bedrooms"}
66
+ ]
67
+ end
68
+ end
@@ -0,0 +1,84 @@
1
+ require 'helper'
2
+
3
+ describe Drawbridge::Request do
4
+
5
+ let(:uri) { 'http://example.com/some-api-path' }
6
+ let(:query) { 'query=foo' }
7
+ let(:default_timeout) { 5 }
8
+ let(:success_code) { 200 }
9
+ let(:not_found_code) { 404 }
10
+
11
+ describe ".perform" do
12
+
13
+ it "should initialize new Object and perform a request" do
14
+ request = MiniTest::Mock.new
15
+ request.expect :perform, {}
16
+ Drawbridge::Request.expects(:new).with(uri, query, default_timeout).returns(request)
17
+ Drawbridge::Request.perform(uri, query)
18
+ end
19
+
20
+ end
21
+
22
+ describe "#perform" do
23
+
24
+ let(:request) { Drawbridge::Request.new uri, query, default_timeout }
25
+
26
+ it "should only make one request" do
27
+ request.expects(:get_response).once.returns({})
28
+ request.expects(:handle_response).once.returns({})
29
+ request.perform
30
+ end
31
+
32
+ describe "sucess" do
33
+
34
+ before do
35
+ mock_curb = MiniTest::Mock.new
36
+ mock_curb.expect :response_code, success_code
37
+ mock_curb.expect :body_str, "{\"foo\":\"bar'\t\"}"
38
+ Curl::Easy.expects(:perform).returns(mock_curb)
39
+ end
40
+
41
+ it "should return json parsed data" do
42
+ results = request.perform
43
+ results['foo'].must_equal 'bar''
44
+ end
45
+
46
+ it "does not encode single quotes when skipped" do
47
+ Drawbridge.config.skip_single_quote_encoding = true
48
+ results = request.perform
49
+ results['foo'].must_equal "bar'"
50
+ end
51
+
52
+ end
53
+
54
+ describe "unsucess" do
55
+ let(:mock_curb) { MiniTest::Mock.new }
56
+
57
+ it "should return default json" do
58
+ mock_curb.expect :response_code, not_found_code
59
+ mock_curb.expect :body_str, ""
60
+ mock_curb.expect :nil?, true
61
+ Curl::Easy.expects(:perform).returns(mock_curb)
62
+ request.perform.must_equal Drawbridge::Request::ResultsError
63
+ end
64
+
65
+ it "should throw RequestError when making HTTP request" do
66
+ Curl::Easy.expects(:perform).raises(StandardError)
67
+ lambda{ request.send(:get_response) }.must_raise Drawbridge::Request::RequestError
68
+ end
69
+
70
+ it 'should throw Curl::Err::TimeoutError when request times out' do
71
+ Curl::Easy.expects(:perform).raises(Curl::Err::TimeoutError)
72
+ lambda { request.send(:get_response) }.must_raise Curl::Err::TimeoutError
73
+ end
74
+
75
+ it "should throw Oj::ParseError" do
76
+ mock_curb.expect :response_code, success_code
77
+ mock_curb.expect :body_str, "!@!@#@NoData|*}"
78
+ lambda{ request.send(:handle_response, mock_curb) }.must_raise Drawbridge::Request::RequestError
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,388 @@
1
+ require 'helper'
2
+
3
+ describe Drawbridge::Result do
4
+
5
+ describe "aggregated results" do
6
+ let(:result) { Drawbridge::Result.new(aggregate_result_params) }
7
+
8
+ describe 'refinements' do
9
+ it "should not be empty" do
10
+ result.refinements.wont_be_empty
11
+ end
12
+
13
+ it "should include dimensionid" do
14
+ result.refinements.first['dimensionid'].must_equal 5885
15
+ end
16
+
17
+ it "should be downcased" do
18
+ result.refinements.first['dimensionid'].wont_be_nil
19
+ end
20
+
21
+ it "should downcase inner elements" do
22
+ result.refinements[2]['dimensionvalues'].first['dimvaluename'].wont_be_nil
23
+ end
24
+ end
25
+
26
+ describe "#meta_info" do
27
+ it "should not be empty" do
28
+ result.meta_info.wont_be_empty
29
+ end
30
+
31
+ it "should include pagenumber" do
32
+ result.meta_info['pagenumber'].must_equal 1
33
+ end
34
+ end
35
+
36
+ describe "#records" do
37
+ it "should not be empty" do
38
+ result.records.wont_be_empty
39
+ end
40
+
41
+ it "should access property" do
42
+ result.records.first['endeca_id'].must_equal '000001'
43
+ end
44
+
45
+ it "should include DerivedProperties in a record" do
46
+ result.records.first['bed_high'].must_equal '2'
47
+ end
48
+
49
+ describe 'geocode' do
50
+ it "should convert miles" do
51
+ result.records.first['miles_to_geocode'].must_equal "9.3"
52
+ end
53
+
54
+ it "should convert kilometers" do
55
+ result.records.first['kilometers_to_geocode'].must_equal "14.9669"
56
+ end
57
+ end
58
+
59
+ it 'should include matched_on' do
60
+ result.records.first['matched_on'].must_equal({'showapartment' => '1'})
61
+ end
62
+ end
63
+
64
+ describe "#breadcrumbs" do
65
+ let(:breadcrumbs) { result.breadcrumbs.first }
66
+
67
+ it "should not be empty" do
68
+ breadcrumbs.wont_be_empty
69
+ end
70
+
71
+ it "should include DimensionName" do
72
+ breadcrumbs['dimensionname'].must_equal '_source'
73
+ end
74
+
75
+ it "should include DimensionName" do
76
+ breadcrumbs['dimensionname'].must_equal '_source'
77
+ end
78
+
79
+ describe "DimensionValues" do
80
+ let(:dim_values) { breadcrumbs['dimensionvalues'].first }
81
+
82
+ it "should have DimValueName" do
83
+ dim_values['dimvaluename'].must_equal 'APTGUIDE'
84
+ end
85
+
86
+ it "should have DimValueID" do
87
+ dim_values['dimvalueid'].must_equal 5875
88
+ end
89
+ end
90
+ end
91
+
92
+ describe "#to_stats_hash" do
93
+ let(:hash) { result.to_stats_hash }
94
+
95
+ it "should have meta_info" do
96
+ hash[:meta_info].wont_be_empty
97
+ end
98
+
99
+ it "should have breadcrumbs" do
100
+ hash[:breadcrumbs].wont_be_empty
101
+ end
102
+
103
+ it "should have refinements" do
104
+ hash[:refinements].wont_be_empty
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ describe "non-aggregated results" do
111
+
112
+ let(:result) { Drawbridge::Result.new(no_aggregate_result_params) }
113
+
114
+ describe 'refinements' do
115
+ it "should not be empty" do
116
+ result.refinements.wont_be_empty
117
+ end
118
+
119
+ it "should include dimensionid" do
120
+ result.refinements.first['dimensionid'].must_equal 5885
121
+ end
122
+ end
123
+
124
+ describe "meta_info" do
125
+ it "should not be empty" do
126
+ result.meta_info.wont_be_empty
127
+ end
128
+
129
+ it "should include pagenumber" do
130
+ result.meta_info['pagenumber'].must_equal 1
131
+ end
132
+
133
+ it 'should include matched_on' do
134
+ result.records.first['matched_on'].must_equal nil
135
+ end
136
+
137
+ it "should be downcased" do
138
+ result.meta_info['pagenumber'].must_equal 1
139
+ end
140
+ end
141
+
142
+ describe "records" do
143
+ it "should not be empty" do
144
+ result.records.wont_be_empty
145
+ end
146
+
147
+ it "should asseess property" do
148
+ result.records.first['endeca_id'].must_equal '000001'
149
+ end
150
+ end
151
+
152
+ describe "empty results" do
153
+ it "should not throw an exception" do
154
+ result = Drawbridge::Result.new({})
155
+ result.records.must_equal []
156
+ end
157
+
158
+ end
159
+ end
160
+
161
+ describe "Endeca Error" do
162
+ { 404 => "error_result_404", 500 => "error_result_500" }.each do |status, responze|
163
+ describe "STATUS #{status}" do
164
+ let(:result) { Drawbridge::Result.new(send(responze)) }
165
+
166
+ it "should have Breadcrumbs" do
167
+ result.breadcrumbs.must_equal []
168
+ end
169
+
170
+ it "should have MetaInfo" do
171
+ result.meta_info.must_equal({})
172
+ end
173
+
174
+ it "should have Refinements" do
175
+ result.refinements.must_equal []
176
+ end
177
+
178
+ it "should have status" do
179
+ result.status.must_equal status
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ describe '#parse_matched_on' do
186
+ let(:result) { Drawbridge::Result.new({}) }
187
+ let(:record) {
188
+ { 'DGraph.WhyDidItMatch' => ["City: Chapel Hill",
189
+ "communitystatename: North Carolina"] }
190
+ }
191
+
192
+ it 'hashifies multi-item array of DGraph.WhyDidItMatch results' do
193
+ expected = { 'City' => 'Chapel Hill',
194
+ 'communitystatename' => 'North Carolina' }
195
+ result.send(:parse_matched_on, record).must_equal expected
196
+ end
197
+ end
198
+
199
+ private
200
+
201
+
202
+ def error_result_500
203
+ {
204
+ "methodResponse" => {
205
+ "fault" => {
206
+ "value" => {
207
+ "faultCode" => "-1",
208
+ "faultString" => "Error establishing connection to retrieve Navigation \
209
+ Engine request 'http://localhost:15400/graph?node=5875+5922+5966&select=\
210
+ agappointmentactive|attuverse|avgoverallrating|bathscat|bedbathleads|\
211
+ bedbathleadsreq|bold|brighthouse|charter|communityvideo|communityvideourl|\
212
+ comvideoheight|comvideowidth|coupon|coupontext|defaulttreatment|directv|\
213
+ emercialurl|endeca_id|floorplans|fpoptout|geocode|hashdfp|hashdphotos|hdtour\
214
+ |hdtoururl|hdtoururlmobile|hdvideo|hdvideourl|hidecurrentrentspecialsreq|\
215
+ iscollege|iscorporate|isincome|isluxury|ismilitary|ispet|issenior|\
216
+ largecustomcoupon|latitude|leadconfirmemail|leadmovedate|leadmovedatecal|\
217
+ leadmovedatecalreq|leadmovedatereq|leadmovereason|leadmovereasonreq|\
218
+ leadphonerequired|leadpricerange|leadpricerangereq|listingbedhigh|listingbedlow|\
219
+ listingid|listingpoints|listingpricehigh|listingpricelow|listingseopath|\
220
+ listingtier|livechat|longitude|mgtcodescription|mgtcoid|mktratingson|\
221
+ neighborhoods|numratings|officehours|overallratings|photoplus|photos|\
222
+ propertycity|propertyname|propertystatelong|propertyzip|ratings|semtollfree|\
223
+ showapartment|sortpricehigh|sortpricelow|sources|telcoadcode|timewarner|tour|\
224
+ tourend|tourheight|tourstart|toururl|tourwidth|twonameleads|verizon|webtollfree|\
225
+ xfinity|xfinityprefix|zipcode&group=5948+5923+5974+23+3+24+5951+4+22+5885+8+5967+\
226
+ 25&sort=searchonly|asc||isapartment|desc||pri_isnewyork|desc||sort_isnewyork|desc|\
227
+ |geocode(40.7204,-73.9946)|asc&groupby=listingid&offset=0&nbins=20&allbins=1&attrs=\
228
+ showapartment|1&pred=geocode%7cGCLT+40.7204%2c-73.9946+16.093470878864444&\
229
+ irversion=640'. Connection refused"
230
+ }
231
+ }
232
+ }
233
+ }
234
+ end
235
+
236
+ def error_result_404
237
+ {
238
+ "methodResponse" => {
239
+ "fault" => {
240
+ "value" => {
241
+ "faultCode" => "-1",
242
+ "faultString" => "HTTP Error 404 - Navigation Engine not able to process request \
243
+ 'http://localhost:15400/graph?node=0&groupby=somethingwrong&offset=0&nbins=10&allbins=1&irversion=640'."
244
+ }
245
+ }
246
+ }
247
+ }
248
+ end
249
+
250
+ def aggregate_result_params
251
+ {
252
+ "MetaInfo"=>{
253
+ "PageNumber"=>1
254
+ },
255
+ "Breadcrumbs" => [
256
+ {
257
+ "DimensionName" => "_source",
258
+ "DimensionValues" => [
259
+ {
260
+ "DimValueName" => "APTGUIDE",
261
+ "DimValueID" => 5875
262
+ }
263
+ ],
264
+ "Type" => "Navigation"
265
+ }
266
+ ],
267
+ "AggrRecords"=>[
268
+ {
269
+ "Records"=>[
270
+ {
271
+ "RecordSpec"=>"000001",
272
+ "Dimensions"=>{
273
+ "propertyzip"=>["99689"],
274
+ "propertycity"=>["Yakutat"]
275
+ },
276
+ "Properties"=>{
277
+ "leadphonerequired"=>"1",
278
+ "endeca_id"=>"000001",
279
+ "miles_to_geocode(40.720400,-73.994600)" => "9.3",
280
+ "kilometers_to_geocode(40.720400,-73.994600)" => "14.9669",
281
+ "DGraph.WhyDidItMatch" => "showapartment: 1"
282
+ }
283
+ }
284
+ ],
285
+ "RecordCount"=>3,
286
+ "DerivedProperties"=>{
287
+ "bed_high"=>"2",
288
+ "bed_low"=>"1"
289
+ }
290
+ },
291
+ {
292
+ "Records"=>[
293
+ {
294
+ "RecordSpec"=>"000002",
295
+ "Dimensions"=>{
296
+ "propertyzip"=>["99689"],
297
+ "propertycity"=>["Yakutat"]
298
+ },
299
+ "Properties"=>{
300
+ "leadphonerequired"=>"1",
301
+ "endeca_id"=>"000002"
302
+ }
303
+ }
304
+ ],
305
+ "RecordCount"=>3,
306
+ "DerivedProperties"=>{
307
+ "bed_high"=>"2",
308
+ "bed_low"=>"1"
309
+ }
310
+ }
311
+ ],
312
+ "Refinements"=>[
313
+ {
314
+ "DimensionID"=>5885,
315
+ "DimensionName"=>"price_range"
316
+ },
317
+ {
318
+ "DimensionID"=>3,
319
+ "DimensionName"=>"bedrooms"
320
+ },
321
+ {
322
+ "DimensionID" => 5974,
323
+ "DimensionName" => "_telco",
324
+ "DimensionValues" =>
325
+ [
326
+ {
327
+ "DimValueName" => "Time Warner Cable",
328
+ "DimValueProperties" => {"DGraph.AggrBins" => "10","sortname" => ["Time Warner"],"sortfield" => ["timewarner"]},
329
+ "NumberofRecords" => "332",
330
+ "DimValueID" => 5978
331
+ },
332
+ {
333
+ "DimValueName" => "Verizon FiOS",
334
+ "DimValueProperties" => {"DGraph.AggrBins" => "33","sortbar" => ["vzn_sort_bar"],"sortname" => ["Verizon"],"sortfield" => ["verizon"]},
335
+ "NumberofRecords" => "627",
336
+ "DimValueID" => 5976
337
+ }
338
+ ]
339
+ }
340
+ ]
341
+ }
342
+ end
343
+
344
+ def no_aggregate_result_params
345
+ {
346
+ "MetaInfo"=>{
347
+ "PageNumber"=>1
348
+ },
349
+ "Records"=>[
350
+ {
351
+ "RecordSpec"=>"000001",
352
+ "Dimensions"=>{
353
+ "propertyzip"=>["99689"],
354
+ "propertycity"=>["Yakutat"]
355
+ },
356
+ "Properties"=>{
357
+ "leadphonerequired"=>"1",
358
+ "endeca_id"=>"000001",
359
+ "miles_to_geocode(40.720400,-73.994600)" => "9.3",
360
+ "kilometers_to_geocode(40.720400,-73.994600)" => "14.9669"
361
+ }
362
+ },
363
+ {
364
+ "RecordSpec"=>"000002",
365
+ "Dimensions"=>{
366
+ "propertyzip"=>["99689"],
367
+ "propertycity"=>["Yakutat"]
368
+ },
369
+ "Properties"=>{
370
+ "leadphonerequired"=>"1",
371
+ "endeca_id"=>"000002"
372
+ }
373
+ }
374
+ ],
375
+ "Refinements"=>[
376
+ {
377
+ "DimensionID"=>5885,
378
+ "DimensionName"=>"price_range"
379
+ },
380
+ {
381
+ "DimensionID"=>3,
382
+ "DimensionName"=>"bedrooms"
383
+ }
384
+ ]
385
+ }
386
+ end
387
+
388
+ end