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 +2 -2
- data/Rakefile +6 -5
- data/lib/suitcase/cache.rb +2 -2
- data/lib/suitcase/hotel/amenity.rb +1 -1
- data/lib/suitcase/hotel/hotel.rb +189 -60
- data/lib/suitcase/hotel/room.rb +9 -1
- data/lib/suitcase/version.rb +1 -1
- data/suitcase.gemspec +4 -2
- data/test/car_rentals/car_rental_test.rb +30 -0
- data/test/hotels/amenity_test.rb +23 -0
- data/test/hotels/caching_test.rb +42 -0
- data/test/hotels/ean_exception_test.rb +24 -0
- data/test/hotels/helpers_test.rb +44 -0
- data/test/hotels/hotel_location_test.rb +23 -0
- data/test/hotels/hotel_test.rb +85 -0
- data/test/hotels/image_test.rb +20 -0
- data/test/hotels/payment_option_test.rb +15 -0
- data/test/hotels/reservation_test.rb +15 -0
- data/test/hotels/room_test.rb +45 -0
- data/test/hotels/session_test.rb +14 -0
- data/{spec → test}/keys.rb +4 -2
- data/test/minitest_helper.rb +17 -0
- metadata +49 -31
- data/spec/amenity_spec.rb +0 -25
- data/spec/car_rentals/car_rental_spec.rb +0 -20
- data/spec/hotels/caching_spec.rb +0 -32
- data/spec/hotels/helpers_spec.rb +0 -40
- data/spec/hotels/hotel_location_spec.rb +0 -21
- data/spec/hotels/hotels_spec.rb +0 -77
- data/spec/hotels/images_spec.rb +0 -23
- data/spec/hotels/payment_options_spec.rb +0 -13
- data/spec/hotels/reservation_spec.rb +0 -11
- data/spec/hotels/rooms_spec.rb +0 -63
- data/spec/hotels/sessions_spec.rb +0 -43
- data/spec/spec_helper.rb +0 -3
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
|
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: "
|
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 "
|
2
|
+
require "rake/testtask"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
data/lib/suitcase/cache.rb
CHANGED
data/lib/suitcase/hotel/hotel.rb
CHANGED
@@ -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
|
-
|
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
|
-
#
|
34
|
+
# Public: A BedType represents a bed configuration for a Room.
|
9
35
|
class BedType
|
10
|
-
|
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
|
18
|
-
#
|
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
|
-
|
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,
|
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
|
-
#
|
81
|
+
# Internal: Initialize a new Hotel.
|
37
82
|
#
|
38
|
-
# info -
|
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
|
92
|
+
# Public: Find a Hotel based on ID, IDs, or location (and other options).
|
48
93
|
#
|
49
|
-
# info
|
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
|
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
|
-
#
|
112
|
+
# Interal: Find a Hotel by its ID.
|
64
113
|
#
|
65
|
-
# id
|
66
|
-
#
|
114
|
+
# id - The Integer or String Hotel ID.
|
115
|
+
# session - A Session with session data.
|
67
116
|
#
|
68
|
-
# Returns a single Hotel
|
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(:
|
124
|
+
url = url(method: "info", params: params, session: session)
|
75
125
|
raw = parse_response(url)
|
76
126
|
handle_errors(raw)
|
77
|
-
|
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(:
|
149
|
+
url = url(method: "list", params: params, session: session)
|
90
150
|
raw = parse_response(url)
|
91
151
|
handle_errors(raw)
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
187
|
+
url = url(method: "list", params: params, session: info[:session])
|
188
|
+
parsed = parse_response(url)
|
127
189
|
handle_errors(parsed)
|
128
|
-
|
190
|
+
if Configuration.cache?
|
191
|
+
Configuration.cache.save_query(:list, params, parsed)
|
192
|
+
end
|
129
193
|
end
|
130
|
-
[split(parsed)].flatten.
|
131
|
-
|
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 -
|
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
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
#
|
255
|
+
# Internal: Get images from the parsed JSON.
|
159
256
|
#
|
160
|
-
# parsed -
|
257
|
+
# parsed - A Hash representing the parsed JSON.
|
161
258
|
#
|
162
|
-
# Returns an Array of
|
259
|
+
# Returns an Array of Image.
|
163
260
|
def self.images(parsed)
|
164
|
-
images = parsed["HotelInformationResponse"]["HotelImages"]["HotelImage"].map
|
165
|
-
|
166
|
-
|
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
|
-
#
|
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 -
|
191
|
-
# the Suitcase::Room class
|
312
|
+
# info - A Hash of options that are the accessors in Rooms.
|
192
313
|
#
|
193
|
-
# Returns an Array of
|
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(:
|
330
|
+
parsed = Hotel.parse_response(Hotel.url(method: "avail", params: params, session: info[:session]))
|
209
331
|
Hotel.handle_errors(parsed)
|
210
|
-
|
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
|
-
|
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
|
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
|
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
|
data/lib/suitcase/hotel/room.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
module Suitcase
|
2
2
|
class Room
|
3
|
-
attr_accessor :rate_key, :hotel_id, :supplier_type, :rate_code,
|
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)
|
data/lib/suitcase/version.rb
CHANGED
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 "
|
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
|