planefinder 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,217 @@
1
+ require 'spec_helper'
2
+
3
+ module Planefinder
4
+ describe AirplaneListing do
5
+ context "constructor" do
6
+ it "should accept 1 argument: json" do
7
+ listing_hash = JSON.parse(file_fixture('da40xl_airplanes.json')).first
8
+ a = AirplaneListing.new(listing_hash)
9
+ end
10
+ end
11
+
12
+ context "properties" do
13
+ let(:json) { JSON.parse(file_fixture('da40xl_airplanes.json')).first }
14
+ let(:listing) { AirplaneListing.new(json) }
15
+
16
+ it "should be accessible as instance variables after initialization" do
17
+ listing.model.should == "DA40XL"
18
+ listing.listing_id.should == "1581835"
19
+ listing.registration_number.should == "N866DS"
20
+ listing.total_time.should == "870"
21
+ listing.total_seats.should be_nil
22
+ listing.prop_4_time.should be_nil
23
+ end
24
+
25
+ it "should be accessible from the [] method by string or symbol" do
26
+ listing['model'].should == "DA40XL"
27
+ listing['total_time'].should == "870"
28
+ listing[:model].should == listing['model']
29
+ listing[:total_time].should == listing['total_time']
30
+ end
31
+ end
32
+
33
+ context "location" do
34
+ it "should default to nil location" do
35
+ a = AirplaneListing.new({})
36
+ a.location.should be_nil
37
+ end
38
+
39
+ it "should not be overridden if the listing has a 'location' attribute" do
40
+ a = AirplaneListing.new({'location' => 'Somewhere, USA'})
41
+ a.location.should be_nil
42
+ end
43
+
44
+ it "should have a value if the listing has a state" do
45
+ a = AirplaneListing.new({'state' => 'MA'})
46
+ a.location.should_not be_nil
47
+ end
48
+
49
+ it "should have a value if the listing has a city and state, different from the state value" do
50
+ state_only = AirplaneListing.new({'state' => 'MA'})
51
+ city_and_state = AirplaneListing.new({'state' => 'MA', 'city' => 'Boston'})
52
+ city_and_state.location.should_not be_nil
53
+ city_and_state.location.should_not == state_only.location
54
+ end
55
+
56
+ it "should use the zipcode location, regardless of state and city" do
57
+ zip_only = AirplaneListing.new({'zipcode' => '90210'})
58
+ zip_state = AirplaneListing.new({'zipcode' => '90210', 'state' => 'MA'})
59
+ zip_city_state = AirplaneListing.new({'zipcode' => '90210', 'state' => 'MA', 'city' => 'Boston'})
60
+ zip_only.location.should_not be_nil
61
+ [zip_state, zip_city_state].each { |l| l.location.should == zip_only.location }
62
+ end
63
+
64
+ it "should use phone area code if no zipcode and no city+state is available" do
65
+ phone_only = AirplaneListing.new({'home_phone' => '9785551212'})
66
+ zip_only = AirplaneListing.new({'zipcode' => '90210'})
67
+ state_only = AirplaneListing.new({'state' => 'MA'})
68
+ phone_zip = AirplaneListing.new({'zipcode' => '90210', 'home_phone' => '9785551212'})
69
+ phone_state = AirplaneListing.new({'state' => 'MA', 'home_phone' => '9785551212'})
70
+ phone_only.location.should_not be_nil
71
+ phone_zip.location.should == zip_only.location # zipcode is more reliable than phone area code
72
+ phone_state.location.should == state_only.location # area code roughly gets you into a state
73
+ end
74
+
75
+ def check_location_with_phones(phones, expected)
76
+ listing = AirplaneListing.new(phones)
77
+ listing.location.should_not be_nil
78
+ listing.location.should == expected.location
79
+ end
80
+
81
+ it "should use the best available phone number" do
82
+ best_phone_order = ['home_phone', 'work_phone', 'aff_home_phone', 'aff_work_phone']
83
+ phones = {'home_phone' => '111',
84
+ 'work_phone' => '222',
85
+ 'aff_home_phone' => '333',
86
+ 'aff_work_phone' => '444'}
87
+ listings_by_phone = {}
88
+ phones.each {|k, v| listings_by_phone[k] = AirplaneListing.new({k => v})}
89
+
90
+ check_location_with_phones(phones, listings_by_phone['home_phone'])
91
+ phones.delete('home_phone')
92
+ check_location_with_phones(phones, listings_by_phone['work_phone'])
93
+ phones.delete('work_phone')
94
+ check_location_with_phones(phones, listings_by_phone['aff_home_phone'])
95
+ phones.delete('aff_home_phone')
96
+ check_location_with_phones(phones, listings_by_phone['aff_work_phone'])
97
+ end
98
+
99
+ def check_location_with_zips(zips, expected)
100
+ listing = AirplaneListing.new(zips)
101
+ listing.location.should_not be_nil
102
+ listing.location.should == expected.location
103
+ end
104
+
105
+ it "should use the best available phone number" do
106
+ best_zip_order = ['zipcode', 'aff_zip']
107
+ zips = {'zipcode' => '90210', 'aff_zip' => '02118'}
108
+ listings_by_zip = {}
109
+ zips.each {|k, v| listings_by_zip[k] = AirplaneListing.new({k => v})}
110
+
111
+ check_location_with_zips(zips, listings_by_zip['zipcode'])
112
+ zips.delete('zipcode')
113
+ check_location_with_zips(zips, listings_by_zip['aff_zip'])
114
+ end
115
+
116
+ it "should return a LatLng for state location" do
117
+ listing = AirplaneListing.new({"state" => 'MA'})
118
+ listing.location.class.should == Geokit::LatLng
119
+ end
120
+
121
+ it "should return a LatLng for city, state location" do
122
+ listing = AirplaneListing.new({"city" => "Boston", "state" => 'MA'})
123
+ listing.location.class.should == Geokit::LatLng
124
+ end
125
+
126
+ it "should return a LatLng for zipcode location" do
127
+ listing = AirplaneListing.new({"zipcode" => '90210'})
128
+ listing.location.class.should == Geokit::LatLng
129
+ end
130
+
131
+ it "should return a LatLng for phone number" do
132
+ listing = AirplaneListing.new({"home_phone" => '9785551212'})
133
+ listing.location.class.should == Geokit::LatLng
134
+ end
135
+
136
+ it "should return invalid location for long state name or invalid state" do
137
+ AirplaneListing.new({"state" => 'Massachusetts'}).location.should_not be_valid
138
+ AirplaneListing.new({"state" => 'XX'}).location.should_not be_valid
139
+ end
140
+
141
+ it "should return properties" do
142
+ initial_properties = {"state" => 'MA', "city" => "Boston"}
143
+ l = AirplaneListing.new(initial_properties)
144
+ l.properties.should == initial_properties
145
+ end
146
+
147
+ it "should support to_s" do
148
+ l = AirplaneListing.new({"state" => 'MA', "city" => "Boston"})
149
+ str = l.to_s
150
+ str.should == l.properties.inspect
151
+ end
152
+
153
+ it "should provide location for a real listing" do
154
+ l = AirplaneListing.new(JSON.parse(file_fixture('da40xl_airplanes.json')).first)
155
+ l.location.should be_valid
156
+ end
157
+
158
+ context "calculating location in a specific order" do
159
+ let(:properties) {
160
+ # zipcode, city+state, state, phone
161
+ # zipcode and city+state are about equivalent, and achieve the same accuracy
162
+ # state and phone area code achieve the same accuracy
163
+ # home info is better than seller/company info
164
+ { "zipcode" => "02118", # Boston, MA
165
+ "city" => "Lowell", # Lowell, MA (with state)
166
+ "state" => "MA", # just MA
167
+ "home_phone" => "6035551212", # NH
168
+ "work_phone" => "2075551212", # ME
169
+ "fax" => "8025551212", # VT
170
+ "aff_zip" => "90210", # CA
171
+ "aff_city" => "Las Vegas", # Las Vegas, NV
172
+ "aff_state" => "NV", # just NV
173
+ "aff_home_phone" => "8015551212", # UT
174
+ "aff_work_phone" => "2065551212", # WA
175
+ "aff_fax" => "5035551212" # OR
176
+ } }
177
+ let(:property_order) { ['zipcode', 'city', 'state', 'home_phone', 'work_phone', 'fax',
178
+ 'aff_zip', 'aff_city', 'aff_state', 'aff_home_phone', 'aff_work_phone', 'aff_fax'] }
179
+
180
+ it "should calculate location" do
181
+ Geokit::Geocoders::AirplaneGeocoder.stub(:geocode)
182
+ property_order.each do |p|
183
+ if p =~ /city/
184
+ Geokit::Geocoders::AirplaneGeocoder.should_receive(:geocode).with("#{properties[p]}, #{properties[p.gsub('city', 'state')]}")
185
+ else
186
+ Geokit::Geocoders::AirplaneGeocoder.should_receive(:geocode).with(properties[p])
187
+ end
188
+
189
+ listing = AirplaneListing.new(properties)
190
+ loc = listing.location
191
+ properties.delete(p)
192
+ end
193
+ end
194
+
195
+ it "should know location type and text description" do
196
+ Geokit::Geocoders::AirplaneGeocoder.stub(:geocode)
197
+ property_order.each do |p|
198
+ listing = AirplaneListing.new(properties)
199
+ if p =~ /city/
200
+ # there are 2 types of city/state: the normal one (city, state) and the affiliate one (aff_city, aff_state)
201
+ city = p
202
+ state = p.gsub('city', 'state')
203
+ listing.location_text.should == "#{properties[city]}, #{properties[state]}"
204
+ listing.location_type.should == "#{city}, #{state}"
205
+ listing.location_description.should == "#{city}, #{state}: #{properties[city]}, #{properties[state]}"
206
+ else
207
+ listing.location_text.should == "#{properties[p]}"
208
+ listing.location_type.should == "#{p}"
209
+ listing.location_description.should == "#{p}: #{properties[p]}"
210
+ end
211
+ properties.delete(p)
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ module Planefinder
4
+ describe AirplaneMake do
5
+ let(:json_make) { JSON.parse(file_fixture('single_engine_makes.json')).select{ |m| m['name'] == 'Diamond' }.first }
6
+
7
+ it "should be initializable with a JSON object and a category" do
8
+ category = double(AirplaneCategory)
9
+ am = AirplaneMake.new(json_make, category)
10
+ am.count.should == 13
11
+ am.id.should == 155
12
+ am.name.should == "Diamond"
13
+ am.category.should == category
14
+ end
15
+
16
+ it "should support ==" do
17
+ a = AirplaneMake.new(json_make, 5)
18
+ b = AirplaneMake.new(json_make, 5)
19
+ c = AirplaneMake.new(json_make, 6)
20
+ a.should == b
21
+ b.should_not == c
22
+ end
23
+
24
+ it "should be able to fetch models for its make" do
25
+ models = [1, 2, 3]
26
+ category = double(AirplaneCategory)
27
+ am = AirplaneMake.new(json_make, category)
28
+ Planefinder.should_receive(:get_models_for_category_and_make).with(category, am) { models }
29
+ am.get_models.should == models
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+
3
+ module Planefinder
4
+ describe AirplaneModel do
5
+ let(:diamond_models) { JSON.parse(file_fixture('diamond_models.json')) }
6
+ let(:da40) { diamond_models.select { |m| m['name'] == "DA40" }.first }
7
+ let(:da40xls) { diamond_models.select { |m| m['name'] == "DA40 XLS" }.first }
8
+
9
+ describe "constructor" do
10
+ it "should take 3 arguments: json, category, make" do
11
+ am = AirplaneModel.new(da40, double(AirplaneCategory), double(AirplaneMake))
12
+ end
13
+
14
+ it "should have appropriate values after creation" do
15
+ cat = double(AirplaneCategory)
16
+ make = double(AirplaneMake)
17
+ am = AirplaneModel.new(da40, cat, make)
18
+ am.name.should == "DA40"
19
+ am.model_group.should == "DA40 Series"
20
+ am.id.should == 4140
21
+ am.count.should == 5
22
+ am.category.should == cat
23
+ am.make.should == make
24
+ end
25
+ end
26
+
27
+ it "should support ==" do
28
+ a = AirplaneModel.new(da40, 1, 155)
29
+ b = AirplaneModel.new(da40, 1, 155)
30
+ c = AirplaneModel.new(da40xls, 1, 155)
31
+ d = AirplaneModel.new(da40xls, 2, 155)
32
+ e = AirplaneModel.new(da40xls, 2, 400)
33
+
34
+ a.should == b
35
+ b.should == a
36
+ a.should_not == c
37
+ b.should_not == c
38
+ c.should_not == d
39
+ d.should_not == e
40
+ end
41
+
42
+ it "should be able to retrieve listings" do
43
+ make = double(Planefinder::AirplaneMake, :name => "Diamond")
44
+ cat = double(Planefinder::AirplaneCategory, :name => "Single Engine Piston")
45
+ am = AirplaneModel.new(da40, cat, make)
46
+ listing = AirplaneListing.new({})
47
+ Planefinder.stub(:search_by_model_make_category).and_return([listing])
48
+ Planefinder.should_receive(:search_by_model_make_category).with(am, make, cat)
49
+
50
+ listings = am.get_listings
51
+ listings.first.should == listing
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ describe Planefinder do
4
+ it "should have a version" do
5
+ Planefinder::VERSION.should_not be_empty
6
+ end
7
+
8
+ context "retrieving categories" do
9
+ it "should retrieve aircraft categories" do
10
+ categories = Planefinder.get_categories
11
+ categories.length.should == 9
12
+ categories.each { |c| c.class.should == Planefinder::AirplaneCategory }
13
+ end
14
+
15
+ it "should ignore the 'special_use_counts' category" do
16
+ categories = Planefinder.get_categories
17
+ categories.each { |c| c.id.should be > 0 }
18
+ end
19
+ end
20
+
21
+ context "retrieving makes" do
22
+ %w[get_makes_for_category get_makes].each do |method_name|
23
+ describe "##{method_name}" do
24
+ it "should retrieve a list of Single Engine makes for category 1" do
25
+ cat = mock(Planefinder::AirplaneCategory, :id => 1)
26
+ makes = Planefinder.send(method_name, cat)
27
+ makes.length.should == 136
28
+ makes.select { |m| m.name == 'Cessna' }.length.should == 1
29
+ end
30
+
31
+ it "should retrieve a list of Jet makes for category 4" do
32
+ cat = double(Planefinder::AirplaneCategory, :id => 4)
33
+ makes = Planefinder.send(method_name, cat)
34
+ makes.length.should == 28
35
+ makes.select { |m| m.name == 'Boeing' }.length.should == 1
36
+ end
37
+
38
+ it "should retrieve a list of Helicopter makes for category 100" do
39
+ cat = double(Planefinder::AirplaneCategory, :id => 100)
40
+ makes = Planefinder.send(method_name, cat)
41
+ makes.length.should == 9
42
+ makes.select { |m| m.name == 'Robinson' }.length.should == 1
43
+ end
44
+
45
+ it "should create AirplaneMakes with real category objects" do
46
+ cat = double(Planefinder::AirplaneCategory, :id => 100)
47
+ makes = Planefinder.send(method_name, cat)
48
+ makes.first.category.should == cat
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ context "retrieving models" do
55
+ %w[get_models_for_category_and_make get_models].each do |method_name|
56
+ describe "##{method_name}" do
57
+ it "should retrieve an array of AirplaneModel" do
58
+ cat = double(Planefinder::AirplaneCategory, :id => 1)
59
+ make = double(Planefinder::AirplaneMake, :id => 155)
60
+ Planefinder.send(method_name, cat, make).each do |model|
61
+ model.class.should == Planefinder::AirplaneModel
62
+ end
63
+ end
64
+
65
+ it "should retrieve a list of Diamond aircraft models for category 1, make 155" do
66
+ cat = double(Planefinder::AirplaneCategory, :id => 1)
67
+ make = double(Planefinder::AirplaneMake, :id => 155)
68
+ models = Planefinder.send(method_name, cat, make)
69
+ models.select { |m| m.name == 'DA40' }.length.should == 1
70
+ end
71
+
72
+ it "should retrieve a list of Robinson helicopter models for category 100, make 406" do
73
+ cat = double(Planefinder::AirplaneCategory, :id => 100)
74
+ make = double(Planefinder::AirplaneMake, :id => 406)
75
+ models = Planefinder.send(method_name, cat, make)
76
+ models.select { |m| m.name == 'R44' }.length.should == 1
77
+ end
78
+
79
+ it "should create AirplaneModels with real category and make objects" do
80
+ cat = double(Planefinder::AirplaneCategory, :id => 100)
81
+ make = double(Planefinder::AirplaneMake, :id => 406)
82
+ models = Planefinder.send(method_name, cat, make)
83
+ models.first.category.should == cat
84
+ models.first.make.should == make
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ context "retrieving airplane listings" do
91
+ describe "#search_by_model_make_category" do
92
+ it "should retrieve an array of AirplaneListing" do
93
+ model = double(Planefinder::AirplaneModel, :name => "DA40XL")
94
+ make = double(Planefinder::AirplaneMake, :name => "Diamond")
95
+ cat = double(Planefinder::AirplaneCategory, :name => "Single Engine Piston")
96
+ listings = Planefinder.search_by_model_make_category(model, make, cat)
97
+ listings.each { |l| l.class.should == Planefinder::AirplaneListing }
98
+ listings.length.should == 6
99
+ end
100
+ end
101
+ end
102
+
103
+ context "searching by freeform string" do
104
+ pending "should be supported"
105
+ end
106
+ end
@@ -0,0 +1,57 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ require 'planefinder'
9
+ require 'fakeweb'
10
+
11
+ def file_fixture(filename)
12
+ open(File.join(File.dirname(__FILE__), 'fixtures', "#{filename.to_s}")).read
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ config.treat_symbols_as_metadata_keys_with_true_values = true
17
+ config.run_all_when_everything_filtered = true
18
+ config.filter_run :focus
19
+
20
+ # Run specs in random order to surface order dependencies. If you find an
21
+ # order dependency and want to debug it, you can fix the order by providing
22
+ # the seed, which is printed after each run.
23
+ # --seed 1234
24
+ config.order = 'random'
25
+
26
+ config.add_setting :fixture_path
27
+ config.fixture_path = 'foo'
28
+
29
+ config.before(:suite) do
30
+ FakeWeb.allow_net_connect = false
31
+ FakeWeb.register_uri(:get,
32
+ 'http://www.trade-a-plane.com/app_ajax/get_categories?listing_type_id=1',
33
+ :body => file_fixture('airplane_categories.json'))
34
+ FakeWeb.register_uri(:get,
35
+ 'http://www.trade-a-plane.com/app_ajax/get_aircraft_models?category_id=1&make_id=155',
36
+ :body => file_fixture('diamond_models.json'))
37
+ FakeWeb.register_uri(:get,
38
+ 'http://www.trade-a-plane.com/app_ajax/get_aircraft_models?category_id=100&make_id=406',
39
+ :body => file_fixture('robinson_models.json'))
40
+ FakeWeb.register_uri(:get,
41
+ 'http://www.trade-a-plane.com/app_ajax/get_aircraft_makes?category_id=1&make_type_id=1',
42
+ :body => file_fixture('single_engine_makes.json'))
43
+ FakeWeb.register_uri(:get,
44
+ 'http://www.trade-a-plane.com/app_ajax/get_aircraft_makes?category_id=4&make_type_id=1',
45
+ :body => file_fixture('jet_makes.json'))
46
+ FakeWeb.register_uri(:get,
47
+ 'http://www.trade-a-plane.com/app_ajax/get_aircraft_makes?category_id=100&make_type_id=1',
48
+ :body => file_fixture('piston_helicopter_makes.json'))
49
+ FakeWeb.register_uri(:get,
50
+ 'http://www.trade-a-plane.com/app_ajax/search?s-type=aircraft&model=DA40XL&make=Diamond&category=Single%20Engine%20Piston',
51
+ :body => file_fixture('diamond_models.json'))
52
+ end
53
+
54
+ config.after(:suite) do
55
+ FakeWeb.allow_net_connect = true
56
+ end
57
+ end