drawbridge 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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