suitcase 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  Suitcase
2
2
  ========
3
3
 
4
- Ruby library that utilizes the EAN (Expedia.com) API for locating available hotels (and maybe rental cars and flights someday, too).
4
+ Ruby library that utilizes the EAN (Expedia.com) API for locating available hotels through their version 3 API. It also uses the Hotwire API to search for rental cars.
5
5
 
6
6
  Installation
7
7
  ------------
8
8
 
9
- Suitcase is a Ruby gem, meaning it can be installed via a simple `gem install suitcase`. It can also be added to a project's `Gemfile` with the following line: `gem 'suitcase'` (or `gem 'suitcase', git: "git://github.com/thoughtfusion/suitcase.git", branch: "development"` for the latest updates).
9
+ Suitcase is a Ruby gem, meaning it can be installed via a simple `gem install suitcase`. It can also be added to a project's `Gemfile` with the following line: `gem 'suitcase'` (or `gem 'suitcase', git: "git://github.com/thoughtfusion/suitcase.git", branch: "develop"` for the latest updates).
10
10
 
11
11
  Usage
12
12
  -----
data/Rakefile CHANGED
@@ -1,12 +1,13 @@
1
1
  require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
2
+ require "rake/testtask"
3
3
 
4
- task :default => :spec
5
-
6
- RSpec::Core::RakeTask.new(:spec) do |t|
7
- t.rspec_opts = %w(-c --fail-fast --format d)
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = 'test/**/*_test.rb'
6
+ t.libs.push 'test'
8
7
  end
9
8
 
9
+ task :default => :test
10
+
10
11
  task :install_nodoc => [:build] do
11
12
  system "gem install pkg/suitcase-#{Suitcase::VERSION}.gem --no-ri --no-rdoc"
12
13
  end
@@ -40,10 +40,10 @@ module Suitcase
40
40
  private
41
41
 
42
42
  def keys_to_strings(hash)
43
- hash.inject({}) {|memo, (k, v)|
43
+ hash.inject({}) do |memo, (k, v)|
44
44
  memo[k.to_s] = v
45
45
  memo
46
- }
46
+ end
47
47
  end
48
48
  end
49
49
  end
@@ -37,7 +37,7 @@ module Suitcase
37
37
  def self.parse_mask(bitmask)
38
38
  return nil unless bitmask
39
39
 
40
- BITS.select {|amenity, bit| (bitmask & bit) > 0 }.keys
40
+ BITS.select { |amenity, bit| (bitmask & bit) > 0 }.keys
41
41
  end
42
42
  end
43
43
  end
@@ -1,27 +1,63 @@
1
- module Suitcase
1
+ module Suitcase
2
+ # Public: An Exception to be raised from all EAN API-related errors.
2
3
  class EANException < Exception
3
- def initialize(message)
4
+ # Internal: Setter for the recovery information.
5
+ attr_writer :recovery
6
+
7
+ # Public: Getter for the recovery information.
8
+ attr_reader :recovery
9
+
10
+ # Internal: Setter for the error type.
11
+ attr_writer :type
12
+
13
+ # Public: Getter for the error type..
14
+ attr_reader :type
15
+
16
+ # Internal: Create a new EAN exception.
17
+ #
18
+ # message - The String error message returned by the API.
19
+ # type - The Symbol type of the error.
20
+ def initialize(message, type = nil)
21
+ @type = type
4
22
  super(message)
5
23
  end
24
+
25
+ # Public: Check if the error is recoverable. If it is, recovery information
26
+ # is in the attribute recovery.
27
+ #
28
+ # Returns a Boolean based on whether the error is recoverable.
29
+ def recoverable?
30
+ @recovery.is_a?(Hash)
31
+ end
6
32
  end
7
33
 
8
- # Used for organizing Bed options
34
+ # Public: A BedType represents a bed configuration for a Room.
9
35
  class BedType
10
- attr_accessor :id, :description
36
+ # Internal: The ID of the BedType.
37
+ attr_accessor :id
11
38
 
39
+ # Internal: The description of the BedType.
40
+ attr_accessor :description
41
+
42
+ # Internal: Create a new BedType.
43
+ #
44
+ # info - A Hash from the parsed API response with the following keys:
45
+ # :id - The ID of the BedType.
46
+ # :description 3- The description of the BedType.
12
47
  def initialize(info)
13
48
  @id, @description = info[:id], info[:description]
14
49
  end
15
50
  end
16
51
 
17
- # A Class representing a Hotel that stores information
18
- # about the hotel. It provides methods for checking
19
- # room availability, fetching images and just general
20
- # information providing.
52
+ # Public: A Class representing a single Hotel. It provides methods for
53
+ # all Hotel EAN-related queries in the gem.
21
54
  class Hotel
22
55
  extend Suitcase::Helpers
23
56
 
24
- AMENITIES = { pool: 1,
57
+ # Public: The Amenities that can be passed in to searches, and are returned
58
+ # from many queries.
59
+ AMENITIES = {
60
+ pool: 1,
25
61
  fitness_center: 2,
26
62
  restaurant: 3,
27
63
  children_activities: 4,
@@ -29,13 +65,22 @@ module Suitcase
29
65
  meeting_facilities: 6,
30
66
  pets: 7,
31
67
  wheelchair_accessible: 8,
32
- kitchen: 9 }
68
+ kitchen: 9
69
+ }
33
70
 
34
- attr_accessor :id, :name, :address, :city, :province, :amenities, :masked_amenities, :country_code, :high_rate, :low_rate, :longitude, :latitude, :rating, :postal_code, :supplier_type, :images, :nightly_rate_total, :airport_code, :property_category, :confidence_rating, :amenity_mask, :location_description, :short_description, :hotel_in_destination, :proximity_distance, :property_description, :number_of_floors, :number_of_rooms, :deep_link, :tripadvisor_rating
71
+ attr_accessor :id, :name, :address, :city, :province, :amenities,
72
+ :masked_amenities, :country_code, :high_rate, :low_rate,
73
+ :longitude, :latitude, :rating, :postal_code, :supplier_type,
74
+ :images, :nightly_rate_total, :airport_code,
75
+ :property_category, :confidence_rating, :amenity_mask,
76
+ :location_description, :short_description,
77
+ :hotel_in_destination, :proximity_distance,
78
+ :property_description, :number_of_floors, :number_of_rooms,
79
+ :deep_link, :tripadvisor_rating
35
80
 
36
- # Public: Initialize a new hotel
81
+ # Internal: Initialize a new Hotel.
37
82
  #
38
- # info - a Hash of the options listed in attr_accesor.
83
+ # info - A Hash of the options listed in attr_accessor.
39
84
  #
40
85
  # Returns a new Hotel object with the passed-in attributes.
41
86
  def initialize(info)
@@ -44,12 +89,16 @@ module Suitcase
44
89
  end
45
90
  end
46
91
 
47
- # Public: Find a Hotel based on known information
92
+ # Public: Find a Hotel based on ID, IDs, or location (and other options).
48
93
  #
49
- # info - a Hash of known information
94
+ # info - A Hash of known information about the query. Depending on the
95
+ # type of query being done, you can pass in three possible keys:
96
+ # :ids - An Array of unique IDs as assigned by the EAN API.
97
+ # :id - A single ID as assigned by the EAN API.
98
+ # other - Any other Hash keys will be sent to the generic
99
+ # find_by_info method.
50
100
  #
51
- # Returns a single Hotel if an id is passed in, otherwise
52
- # an Array of Hotels.
101
+ # Returns a single Hotel if an ID is passed in, or an Array of Hotels.
53
102
  def self.find(info)
54
103
  if info[:ids]
55
104
  find_by_ids(info[:ids], info[:session])
@@ -60,43 +109,55 @@ module Suitcase
60
109
  end
61
110
  end
62
111
 
63
- # Public: Find a Hotel by it's id.
112
+ # Interal: Find a Hotel by its ID.
64
113
  #
65
- # id - an Integer or String representation of the Hotel's
66
- # id.
114
+ # id - The Integer or String Hotel ID.
115
+ # session - A Session with session data.
67
116
  #
68
- # Returns a single Hotel object.
117
+ # Returns a single Hotel.
69
118
  def self.find_by_id(id, session)
70
119
  params = { hotelId: id }
120
+
71
121
  if Configuration.cache? and Configuration.cache.cached?(:info, params)
72
122
  raw = Configuration.cache.get_query(:info, params)
73
123
  else
74
- url = url(:method => "info", :params => params, :session => session)
124
+ url = url(method: "info", params: params, session: session)
75
125
  raw = parse_response(url)
76
126
  handle_errors(raw)
77
- Configuration.cache.save_query(:info, params, raw) if Configuration.cache?
127
+ if Configuration.cache?
128
+ Configuration.cache.save_query(:info, params, raw)
129
+ end
78
130
  end
79
131
  hotel_data = parse_information(raw)
80
132
  update_session(raw, session)
133
+
81
134
  Hotel.new(hotel_data)
82
135
  end
83
136
 
137
+ # Internal: Find multiple Hotels based on multiple IDs.
138
+ #
139
+ # ids - An Array of String or Integer Hotel IDs to be found.
140
+ # session - A Session with session data stored in it.
141
+ #
142
+ # Returns an Array of Hotels.
84
143
  def self.find_by_ids(ids, session)
85
144
  params = { hotelIdList: ids.join(",") }
145
+
86
146
  if Configuration.cache? and Configuration.cache.cached?(:list, params)
87
147
  raw = Configuration.cache.get_query(:list, params)
88
148
  else
89
- url = url(:method => "list", :params => params, :session => session)
149
+ url = url(method: "list", params: params, session: session)
90
150
  raw = parse_response(url)
91
151
  handle_errors(raw)
92
- Configuration.cache.save_query(:list, params, raw) if Configuration.cache?
93
- end
94
- hotels = []
95
- [split(raw)].flatten.each do |hotel_data|
96
- hotels.push Hotel.new(parse_information(hotel_data))
152
+ if Configuration.cache?
153
+ Configuration.cache.save_query(:list, params, raw)
154
+ end
97
155
  end
98
156
  update_session(raw, session)
99
- hotels
157
+
158
+ [split(raw)].flatten.map do |hotel_data|
159
+ Hotel.new(parse_information(hotel_data))
160
+ end
100
161
  end
101
162
 
102
163
  # Public: Find a hotel by info other than it's id.
@@ -119,67 +180,128 @@ module Suitcase
119
180
 
120
181
  params["minRate"] = params[:min_rate] if params[:min_rate]
121
182
  params["maxRate"] = params[:max_rate] if params[:max_rate]
122
- hotels = []
183
+
123
184
  if Configuration.cache? and Configuration.cache.cached?(:list, params)
124
185
  parsed = Configuration.cache.get_query(:list, params)
125
186
  else
126
- parsed = parse_response(url(:method => "list", :params => params, :session => info[:session]))
187
+ url = url(method: "list", params: params, session: info[:session])
188
+ parsed = parse_response(url)
127
189
  handle_errors(parsed)
128
- Configuration.cache.save_query(:list, params, parsed) if Configuration.cache?
190
+ if Configuration.cache?
191
+ Configuration.cache.save_query(:list, params, parsed)
192
+ end
129
193
  end
130
- [split(parsed)].flatten.each do |hotel_data|
131
- hotels.push Hotel.new(parse_information(hotel_data))
194
+ hotels = [split(parsed)].flatten.map do |hotel_data|
195
+ Hotel.new(parse_information(hotel_data))
132
196
  end
133
197
  update_session(parsed, info[:session])
198
+
134
199
  info[:results] ? hotels[0..(info[:results]-1)] : hotels
135
200
  end
136
201
 
137
- # Public: Parse the information returned by a search request
202
+ # Public: Parse the information returned by a search request.
138
203
  #
139
- # parsed - a Hash representing the parsed JSON
204
+ # parsed - A Hash representing the parsed JSON.
140
205
  #
141
206
  # Returns a reformatted Hash with the specified accessors.
142
207
  def self.parse_information(parsed)
143
208
  handle_errors(parsed)
144
- summary = parsed["hotelId"] ? parsed : parsed["HotelInformationResponse"]["HotelSummary"]
145
- parsed_info = { id: summary["hotelId"], name: summary["name"], address: summary["address1"], city: summary["city"], postal_code: summary["postalCode"], country_code: summary["countryCode"], rating: summary["hotelRating"], high_rate: summary["highRate"], low_rate: summary["lowRate"], latitude: summary["latitude"].to_f, longitude: summary["longitude"].to_f, province: summary["stateProvinceCode"], airport_code: summary["airportCode"], property_category: summary["propertyCategory"].to_i, proximity_distance: summary["proximityDistance"].to_s + summary["proximityUnit"].to_s, tripadvisor_rating: summary["tripAdvisorRating"], deep_link: summary["deepLink"] }
146
- parsed_info[:amenities] = parsed["HotelInformationResponse"]["PropertyAmenities"]["PropertyAmenity"].map { |x| Amenity.new(id: x["amenityId"], description: x["amenity"]) } if parsed["HotelInformationResponse"]
209
+
210
+ summary = if parsed["hotelId"]
211
+ parsed
212
+ else
213
+ parsed["HotelInformationResponse"]["HotelSummary"]
214
+ end
215
+ proximity_distance = summary["proximityDistance"].to_s
216
+ proximity_distance << summary["proximityUnit"].to_s
217
+ parsed_info = {
218
+ id: summary["hotelId"],
219
+ name: summary["name"],
220
+ address: summary["address1"],
221
+ city: summary["city"],
222
+ postal_code: summary["postalCode"],
223
+ country_code: summary["countryCode"],
224
+ rating: summary["hotelRating"],
225
+ high_rate: summary["highRate"],
226
+ low_rate: summary["lowRate"],
227
+ latitude: summary["latitude"].to_f,
228
+ longitude: summary["longitude"].to_f,
229
+ province: summary["stateProvinceCode"],
230
+ airport_code: summary["airportCode"],
231
+ property_category: summary["propertyCategory"].to_i,
232
+ proximity_distance: proximity_distance,
233
+ tripadvisor_rating: summary["tripAdvisorRating"],
234
+ deep_link: summary["deepLink"]
235
+ }
236
+ parsed_info[:amenities] = parsed["HotelInformationResponse"]["PropertyAmenities"]["PropertyAmenity"].map do |x|
237
+ Amenity.new(id: x["amenityId"], description: x["amenity"])
238
+ end if parsed["HotelInformationResponse"]
147
239
  parsed_info[:images] = images(parsed) if images(parsed)
148
- parsed_info[:property_description] = parsed["HotelInformationResponse"]["HotelDetails"]["propertyDescription"] if parsed["HotelInformationResponse"]
149
- parsed_info[:location_description] = summary["locationDescription"]
150
- parsed_info[:number_of_rooms] = parsed["HotelInformationResponse"]["HotelDetails"]["numberOfRooms"] if parsed["HotelInformationResponse"]
151
- parsed_info[:number_of_floors] = parsed["HotelInformationResponse"]["HotelDetails"]["numberOfFloors"] if parsed["HotelInformationResponse"]
240
+ if parsed["HotelInformationResponse"]
241
+ parsed_info[:property_description] = parsed["HotelInformationResponse"]["HotelDetails"]["propertyDescription"]
242
+ parsed_info[:number_of_rooms] = parsed["HotelInformationResponse"]["HotelDetails"]["numberOfRooms"]
243
+ parsed_info[:number_of_floors] = parsed["HotelInformationResponse"]["HotelDetails"]["numberOfFloors"]
244
+ end
245
+ if parsed["locationDescription"]
246
+ parsed_info[:location_description] = summary["locationDescription"]
247
+ end
152
248
  parsed_info[:short_description] = summary["shortDescription"]
153
249
  parsed_info[:amenity_mask] = summary["amenityMask"]
154
250
  parsed_info[:masked_amenities] = Amenity.parse_mask(parsed_info[:amenity_mask])
251
+
155
252
  parsed_info
156
253
  end
157
254
 
158
- # Public: Get images from a parsed object
255
+ # Internal: Get images from the parsed JSON.
159
256
  #
160
- # parsed - a Hash representing the parsed JSON
257
+ # parsed - A Hash representing the parsed JSON.
161
258
  #
162
- # Returns an Array of Suitcase::Images.
259
+ # Returns an Array of Image.
163
260
  def self.images(parsed)
164
- images = parsed["HotelInformationResponse"]["HotelImages"]["HotelImage"].map { |image_data| Suitcase::Image.new(image_data) } if parsed["HotelInformationResponse"] && parsed["HotelInformationResponse"]["HotelImages"] && parsed["HotelInformationResponse"]["HotelImages"]["HotelImage"]
165
- images = [Suitcase::Image.new("thumbnailURL" => "http://images.travelnow.com" + parsed["thumbNailUrl"])] unless parsed["thumbNailUrl"].nil? or parsed["thumbNailUrl"].empty?
166
- return images ? images : []
261
+ images = parsed["HotelInformationResponse"]["HotelImages"]["HotelImage"].map do |image_data|
262
+ Suitcase::Image.new(image_data)
263
+ end if parsed["HotelInformationResponse"] && parsed["HotelInformationResponse"]["HotelImages"] && parsed["HotelInformationResponse"]["HotelImages"]["HotelImage"]
264
+ unless parsed["thumbNailUrl"].nil? or parsed["thumbNailUrl"].empty?
265
+ images = [Suitcase::Image.new("thumbnailURL" => "http://images.travelnow.com" + parsed["thumbNailUrl"])]
266
+ end
267
+
268
+ images || []
167
269
  end
168
270
 
169
- # Handle the errors from the response.
271
+ # Internal: Raise the errors returned from the response.
272
+ #
273
+ # info - The parsed JSON to get the errors from.
274
+ #
275
+ # Returns nothing.
170
276
  def self.handle_errors(info)
171
277
  key = info.keys.first
172
278
  if info[key] && info[key]["EanWsError"]
173
279
  message = info[key]["EanWsError"]["presentationMessage"]
280
+ exception = EANException.new(message)
281
+ if message =~ /Multiple locations/ && (info = info[key]["LocationInfos"])
282
+ exception.type = :multiple_locations
283
+ exception.recovery = {
284
+ alternate_locations: info["LocationInfo"].map { |h| h["code"] }
285
+ }
286
+ end
287
+
288
+ raise exception
174
289
  end
175
- raise EANException.new(message) if message
176
290
  end
177
291
 
292
+ # Internal: Split an Array of multiple Hotels.
293
+ #
294
+ # parsed - The parsed JSON of the Hotels.
295
+ #
296
+ # Returns an Array of Hashes representing Hotels.
178
297
  def self.split(parsed)
179
298
  hotels = parsed["HotelListResponse"]["HotelList"]
180
299
  hotels["HotelSummary"]
181
300
  end
182
301
 
302
+ # Public: Get the thumbnail URL of the image.
303
+ #
304
+ # Returns a String URL to the image thumbnail.
183
305
  def thumbnail_url
184
306
  first_image = images.find { |img| img.thumbnail_url != nil }
185
307
  first_image.thumbnail_url if first_image
@@ -187,10 +309,9 @@ module Suitcase
187
309
 
188
310
  # Public: Fetch possible rooms from a Hotel.
189
311
  #
190
- # info - a Hash of options described as the accessors in
191
- # the Suitcase::Room class
312
+ # info - A Hash of options that are the accessors in Rooms.
192
313
  #
193
- # Returns an Array of Suitcase::Rooms.
314
+ # Returns an Array of Rooms.
194
315
  def rooms(info)
195
316
  params = { rooms: [{adults: 1, children_ages: []}] }.merge(info)
196
317
  params[:rooms].each_with_index do |room, n|
@@ -202,24 +323,30 @@ module Suitcase
202
323
  params.delete(:arrival)
203
324
  params.delete(:departure)
204
325
  params["hotelId"] = @id
326
+
205
327
  if Configuration.cache? and Configuration.cache.cached?(:avail, params)
206
328
  parsed = Configuration.cache.get_query(:avail, params)
207
329
  else
208
- parsed = Hotel.parse_response(Hotel.url(:method => "avail", :params => params, :session => info[:session]))
330
+ parsed = Hotel.parse_response(Hotel.url(method: "avail", params: params, session: info[:session]))
209
331
  Hotel.handle_errors(parsed)
210
- Configuration.cache.save_query(:avail, params, parsed) if Configuration.cache?
332
+ if Configuration.cache?
333
+ Configuration.cache.save_query(:avail, params, parsed)
334
+ end
211
335
  end
212
336
  hotel_id = parsed["HotelRoomAvailabilityResponse"]["hotelId"]
213
337
  rate_key = parsed["HotelRoomAvailabilityResponse"]["rateKey"]
214
338
  supplier_type = parsed["HotelRoomAvailabilityResponse"]["HotelRoomResponse"][0]["supplierType"]
215
339
  Hotel.update_session(parsed, info[:session])
216
- rooms = parsed["HotelRoomAvailabilityResponse"]["HotelRoomResponse"].map do |raw_data|
340
+
341
+ parsed["HotelRoomAvailabilityResponse"]["HotelRoomResponse"].map do |raw_data|
217
342
  room_data = {}
218
343
  room_data[:rate_code] = raw_data["rateCode"]
219
344
  room_data[:room_type_code] = raw_data["roomTypeCode"]
220
345
  room_data[:room_type_description] = raw_data["roomTypeDescription"]
221
346
  room_data[:promo] = raw_data["RateInfo"]["@promo"].to_b
222
- room_data[:price_breakdown] = raw_data["RateInfo"]["ChargeableRateInfo"]["NightlyRatesPerRoom"]["NightlyRate"].map { |raw| NightlyRate.new(raw) } if raw_data["RateInfo"]["ChargeableRateInfo"] && raw_data["RateInfo"]["ChargeableRateInfo"]["NightlyRatesPerRoom"] && raw_data["RateInfo"]["ChargeableRateInfo"]["NightlyRatesPerRoom"]["NightlyRate"].is_a?(Array)
347
+ room_data[:price_breakdown] = raw_data["RateInfo"]["ChargeableRateInfo"]["NightlyRatesPerRoom"]["NightlyRate"].map do |raw|
348
+ NightlyRate.new(raw)
349
+ end if raw_data["RateInfo"]["ChargeableRateInfo"] && raw_data["RateInfo"]["ChargeableRateInfo"]["NightlyRatesPerRoom"] && raw_data["RateInfo"]["ChargeableRateInfo"]["NightlyRatesPerRoom"]["NightlyRate"].is_a?(Array)
223
350
  room_data[:total_price] = raw_data["RateInfo"]["ChargeableRateInfo"]["@total"]
224
351
  room_data[:nightly_rate_total] = raw_data["RateInfo"]["ChargeableRateInfo"]["@nightlyRateTotal"]
225
352
  room_data[:average_nightly_rate] = raw_data["RateInfo"]["ChargeableRateInfo"]["@averageRate"]
@@ -229,7 +356,9 @@ module Suitcase
229
356
  room_data[:hotel_id] = hotel_id
230
357
  room_data[:supplier_type] = supplier_type
231
358
  room_data[:rooms] = params[:rooms]
232
- room_data[:bed_types] = [raw_data["BedTypes"]["BedType"]].flatten.map { |x| BedType.new(id: x["@id"], description: x["description"]) } if raw_data["BedTypes"] && raw_data["BedTypes"]["BedType"]
359
+ room_data[:bed_types] = [raw_data["BedTypes"]["BedType"]].flatten.map do |x|
360
+ BedType.new(id: x["@id"], description: x["description"])
361
+ end if raw_data["BedTypes"] && raw_data["BedTypes"]["BedType"]
233
362
  Room.new(room_data)
234
363
  end
235
364
  end
@@ -1,6 +1,14 @@
1
1
  module Suitcase
2
2
  class Room
3
- attr_accessor :rate_key, :hotel_id, :supplier_type, :rate_code, :room_type_code, :supplier_type, :tax_rate, :non_refundable, :occupancy, :quoted_occupancy, :min_guest_age, :total, :surcharge_total, :nightly_rate_total, :average_base_rate, :average_rate, :max_nightly_rate, :currency_code, :value_adds, :room_type_description, :price_breakdown, :total_price, :average_nightly_rate, :promo, :arrival, :departure, :rooms, :bed_types
3
+ attr_accessor :rate_key, :hotel_id, :supplier_type, :rate_code,
4
+ :room_type_code, :supplier_type, :tax_rate, :non_refundable,
5
+ :occupancy, :quoted_occupancy, :min_guest_age, :total,
6
+ :surcharge_total, :nightly_rate_total, :average_base_rate,
7
+ :average_rate, :max_nightly_rate, :currency_code, :value_adds,
8
+ :room_type_description, :price_breakdown, :total_price,
9
+ :average_nightly_rate, :promo, :arrival, :departure, :rooms,
10
+ :bed_types
11
+
4
12
  extend Suitcase::Helpers
5
13
 
6
14
  def initialize(info)
@@ -1,3 +1,3 @@
1
1
  module Suitcase
2
- VERSION = "1.3.1"
2
+ VERSION = "1.4.0"
3
3
  end
data/suitcase.gemspec CHANGED
@@ -16,9 +16,11 @@ Gem::Specification.new do |s|
16
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
17
  s.require_paths = ["lib"]
18
18
 
19
- s.add_development_dependency "rspec"
19
+ s.add_development_dependency "minitest"
20
+ s.add_development_dependency "mocha"
21
+ s.add_development_dependency "turn"
22
+
20
23
  s.add_development_dependency "rake"
21
- s.add_development_dependency "factory_girl"
22
24
  s.add_development_dependency "pry"
23
25
 
24
26
  s.add_runtime_dependency "json"
@@ -0,0 +1,30 @@
1
+ require "minitest_helper"
2
+
3
+ describe Suitcase::CarRental do
4
+ before :each do
5
+ info = {
6
+ destination: "Seattle",
7
+ start_date: "07/04/2012",
8
+ end_date: "07/11/2012",
9
+ pickup_time: "07:30",
10
+ dropoff_time: "11:30"
11
+ }
12
+ @rentals = Suitcase::CarRental.find(info)
13
+ @rental = @rentals.first
14
+ end
15
+
16
+ [:seating, :type_name, :type_code, :possible_features,
17
+ :possible_models].each do |accessor|
18
+ it "has an accessor #{accessor}" do
19
+ @rental.must_respond_to(accessor)
20
+ @rental.must_respond_to((accessor.to_s + "=").to_sym)
21
+ end
22
+ end
23
+
24
+ describe ".find" do
25
+ it "returns an Array of CarRental's" do
26
+ @rentals.must_be_kind_of(Array)
27
+ @rental.must_be_kind_of(Suitcase::CarRental)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ require "minitest_helper"
2
+
3
+ describe Suitcase::Amenity do
4
+ describe ".parse_mask" do
5
+ describe "when provided bitmask is not nil or 0" do
6
+ it "returns an Array of Symbols representing given Amenities" do
7
+ Suitcase::Amenity.parse_mask(5).must_equal [:business_services, :hot_tub]
8
+ end
9
+ end
10
+
11
+ describe "when bitmask is 0" do
12
+ it "returns an empty Array" do
13
+ Suitcase::Amenity.parse_mask(0).must_equal []
14
+ end
15
+ end
16
+
17
+ describe "when provided bitmask is nil" do
18
+ it "returns nil" do
19
+ Suitcase::Amenity.parse_mask(nil).must_equal nil
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,42 @@
1
+ require "minitest_helper"
2
+
3
+ describe Suitcase do
4
+ before :each do
5
+ Suitcase::Configuration.cache = {}
6
+ end
7
+
8
+ it "caches all non-secure queries" do
9
+ # Query 1
10
+ hotel = Suitcase::Hotel.find(id: 123904)
11
+
12
+ # Query 2
13
+ Suitcase::Hotel.find(location: "Boston, US")
14
+
15
+ # Query 3
16
+ room = hotel.rooms(arrival: "6/23/2012", departure: "6/30/2012").first
17
+
18
+ # Query 4
19
+ Suitcase::PaymentOption.find currency_code: "USD"
20
+
21
+ # Query 5, don't cache though
22
+ room.rooms[0][:bed_type] = room.bed_types[0]
23
+ room.rooms[0][:smoking_preference] = "NS"
24
+ room.reserve!(Keys::VALID_RESERVATION_INFO)
25
+
26
+ Suitcase::Configuration.cache.keys.count.must_equal 4
27
+ end
28
+
29
+ it "retrieves from the cache if it's there" do
30
+ hotel = Suitcase::Hotel.find(id: 123904)
31
+ Suitcase::Hotel.find(location: "Boston, US")
32
+ hotel.rooms(arrival: "6/23/2012", departure: "6/30/2012")
33
+ Suitcase::PaymentOption.find currency_code: "USD"
34
+
35
+ # Disable API access
36
+ Net::HTTP.expects(:get_response).never
37
+ hotel = Suitcase::Hotel.find(id: 123904)
38
+ Suitcase::Hotel.find(location: "Boston, US")
39
+ hotel.rooms(arrival: "6/23/2012", departure: "6/30/2012")
40
+ Suitcase::PaymentOption.find currency_code: "USD"
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ require "minitest_helper"
2
+
3
+ describe Suitcase::EANException do
4
+ before :each do
5
+ @exception = Suitcase::EANException.new(nil)
6
+ end
7
+
8
+ it "has an accessor recovery" do
9
+ @exception.must_respond_to(:recovery)
10
+ @exception.must_respond_to(:recovery=)
11
+ end
12
+
13
+ describe "#recoverable" do
14
+ it "returns true if the recovery attribute is set" do
15
+ @exception.recovery = { locations: ["London", "New London"] }
16
+ @exception.recoverable?.must_equal(true)
17
+ end
18
+
19
+ it "returns false if the recovery attribute is not set" do
20
+ @exception.recovery = nil
21
+ @exception.recoverable?.must_equal(false)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ require "minitest_helper"
2
+
3
+ describe Suitcase::Helpers do
4
+ before :each do
5
+ class Dummy; extend Suitcase::Helpers; end
6
+ end
7
+
8
+ describe "#url" do
9
+ it "returns a URI with the proper base URL" do
10
+ url = Dummy.url(method: "action", params: {})
11
+ url.must_be_kind_of(URI)
12
+ url.host.must_match(/api.ean.com/)
13
+ end
14
+
15
+ describe "when using digital signature authorization" do
16
+ it "adds a 'sig' parameter" do
17
+ Suitcase::Configuration.expects(:use_signature_auth?).returns(true)
18
+ Dummy.expects(:generate_signature).returns("test")
19
+
20
+ url = Dummy.url(method: "action", params: {})
21
+ url.query.must_match(/sig=test/)
22
+ end
23
+ end
24
+ end
25
+
26
+ describe "#parse_response" do
27
+ it "raises an exception when passed an invalid URI" do
28
+ proc do
29
+ Dummy.parse_response(URI.parse("http://google.com"))
30
+ end.must_raise JSON::ParserError
31
+ end
32
+ end
33
+
34
+ describe "#generate_signature" do
35
+ it "returns the encrypted API key, shared secret, and timestamp" do
36
+ Suitcase::Configuration.expects(:hotel_api_key).returns("abc")
37
+ Suitcase::Configuration.expects(:hotel_shared_secret).returns("123")
38
+ Time.expects(:now).returns("10")
39
+
40
+ signature = Dummy.generate_signature
41
+ signature.must_equal(Digest::MD5.hexdigest("abc12310"))
42
+ end
43
+ end
44
+ end