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 +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
|