suitcase 1.3.1 → 1.4.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.
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