planefinder 0.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.
@@ -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