isotope11-suitcase 1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +5 -0
  2. data/CHANGELOG.md +17 -0
  3. data/Gemfile +3 -0
  4. data/README.md +83 -0
  5. data/Rakefile +13 -0
  6. data/examples/hash_adapter.rb +15 -0
  7. data/examples/hotel_image_db.rb +24 -0
  8. data/examples/redis_adapter.rb +29 -0
  9. data/isotope11-suitcase.gemspec +30 -0
  10. data/lib/suitcase.rb +16 -0
  11. data/lib/suitcase/car_rental.rb +57 -0
  12. data/lib/suitcase/codes.rb +5 -0
  13. data/lib/suitcase/configuration.rb +29 -0
  14. data/lib/suitcase/core_ext/string.rb +13 -0
  15. data/lib/suitcase/hotel.rb +366 -0
  16. data/lib/suitcase/hotel/amenity.rb +46 -0
  17. data/lib/suitcase/hotel/bed_type.rb +21 -0
  18. data/lib/suitcase/hotel/cache.rb +52 -0
  19. data/lib/suitcase/hotel/cancellation.rb +55 -0
  20. data/lib/suitcase/hotel/ean_exception.rb +35 -0
  21. data/lib/suitcase/hotel/helpers.rb +210 -0
  22. data/lib/suitcase/hotel/image.rb +19 -0
  23. data/lib/suitcase/hotel/location.rb +67 -0
  24. data/lib/suitcase/hotel/nightly_rate.rb +14 -0
  25. data/lib/suitcase/hotel/payment_option.rb +41 -0
  26. data/lib/suitcase/hotel/reservation.rb +15 -0
  27. data/lib/suitcase/hotel/room.rb +139 -0
  28. data/lib/suitcase/hotel/session.rb +7 -0
  29. data/lib/suitcase/hotel/surcharge.rb +23 -0
  30. data/lib/suitcase/version.rb +3 -0
  31. data/test/car_rentals/car_rental_test.rb +30 -0
  32. data/test/hotels/amenity_test.rb +23 -0
  33. data/test/hotels/caching_test.rb +42 -0
  34. data/test/hotels/ean_exception_test.rb +24 -0
  35. data/test/hotels/helpers_test.rb +60 -0
  36. data/test/hotels/hotel_location_test.rb +23 -0
  37. data/test/hotels/hotel_test.rb +122 -0
  38. data/test/hotels/image_test.rb +20 -0
  39. data/test/hotels/payment_option_test.rb +15 -0
  40. data/test/hotels/reservation_test.rb +15 -0
  41. data/test/hotels/room_test.rb +53 -0
  42. data/test/hotels/session_test.rb +14 -0
  43. data/test/keys.rb +43 -0
  44. data/test/minitest_helper.rb +29 -0
  45. data/test/support/fake_response.rb +13 -0
  46. metadata +187 -0
@@ -0,0 +1,67 @@
1
+ module Suitcase
2
+ class Hotel
3
+ class Location
4
+ attr_accessor :destination_id, :type, :active, :city, :province,
5
+ :country, :country_code
6
+
7
+ def initialize(info)
8
+ info.each do |k, v|
9
+ instance_variable_set("@" + k.to_s, v)
10
+ end
11
+ end
12
+
13
+ class << self
14
+ include Helpers
15
+
16
+ # Public: Find a Location.
17
+ #
18
+ # info - A Hash of information to search by, including city & address.
19
+ #
20
+ # Returns an Array of Location's.
21
+ def find(info)
22
+ params = {}
23
+ [:city, :address].each do |dup|
24
+ params[dup] = info[dup] if info[dup]
25
+ end
26
+ if info[:destination_string]
27
+ params[:destinationString] = info[:destination_string]
28
+ end
29
+
30
+ if Configuration.cache? and Configuration.cache.cached?(:geoSearch, params)
31
+ raw = Configuration.cache.get_query(:geoSearch, params)
32
+ else
33
+ url = url(:method => 'geoSearch', :params => params, :session => info[:session])
34
+ raw = parse_response(url)
35
+ handle_errors(raw)
36
+ end
37
+
38
+ parse(raw)
39
+ end
40
+
41
+ def parse(raw)
42
+ [raw["LocationInfoResponse"]["LocationInfos"]["LocationInfo"]].flatten.map do |raw|
43
+ Location.new(
44
+ province: raw["stateProvinceCode"],
45
+ destination_id: raw["destinationId"],
46
+ type: raw["type"],
47
+ city: raw["city"],
48
+ active: raw["active"],
49
+ code: raw["code"],
50
+ country: raw["country"],
51
+ country_code: raw["countryCode"]
52
+ )
53
+ end
54
+ end
55
+
56
+ def handle_errors(info)
57
+ key = info.keys.first
58
+ if info[key] && info[key]["EanWsError"]
59
+ message = info[key]["EanWsError"]["presentationMessage"]
60
+ end
61
+
62
+ raise EANException.new(message) if message
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ module Suitcase
2
+ class NightlyRate
3
+ attr_accessor :promo, :rate, :base_rate
4
+
5
+ # Internal: Create a NightlyRate from the API response.
6
+ #
7
+ # info - A Hash from the API response containing nightly rate information.
8
+ def initialize(info)
9
+ @promo = info["@promo"]
10
+ @rate = info["@rate"]
11
+ @base_rate = info["@baseRate"]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,41 @@
1
+ module Suitcase
2
+ class Hotel
3
+ class PaymentOption
4
+ attr_accessor :code, :name
5
+
6
+ extend Helpers
7
+
8
+ # Internal: Create a PaymentOption.
9
+ #
10
+ # code - The String code from the API response (e.g. "VI").
11
+ # name - The String name of the PaymentOption.
12
+ def initialize(code, name)
13
+ @code, @name = code, name
14
+ end
15
+
16
+ # Public: Find PaymentOptions for a specific currency.
17
+ #
18
+ # info - A Hash of information with one key: :currency_code.
19
+ #
20
+ # Returns an Array of PaymentOption's.
21
+ def self.find(info)
22
+ params = { "currencyCode" => info[:currency_code] }
23
+
24
+ if Configuration.cache? and Configuration.cache.cached?(:paymentInfo, params)
25
+ types_raw = Configuration.cache.get_query(:paymentInfo, params)
26
+ else
27
+ types_raw = parse_response(url(:method => "paymentInfo", :params => params, :session => info[:session]))
28
+ Configuration.cache.save_query(:paymentInfo, params, types_raw) if Configuration.cache?
29
+ end
30
+ update_session(types_raw, info[:session])
31
+
32
+ types_raw["HotelPaymentResponse"].map do |raw|
33
+ types = raw[0] != "PaymentType" ? [] : raw[1]
34
+ types.map do |type|
35
+ PaymentOption.new(type["code"], type["name"])
36
+ end
37
+ end.flatten
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ module Suitcase
2
+ class Hotel
3
+ class Reservation
4
+ attr_accessor :itinerary_id, :confirmation_numbers, :raw, :surcharges
5
+
6
+ # Internal: Create a new Reservation from the API response.
7
+ #
8
+ # info - The Hash of information returned from the API.
9
+ def initialize(info)
10
+ @itinerary_id, @confirmation_numbers = info[:itinerary_id], [info[:confirmation_numbers]].flatten
11
+ @surcharges = info[:surcharges]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,139 @@
1
+ module Suitcase
2
+ class Hotel
3
+ class Room
4
+ attr_accessor :rate_key, :hotel_id, :supplier_type, :rate_code,
5
+ :room_type_code, :supplier_type, :tax_rate, :non_refundable,
6
+ :occupancy, :quoted_occupancy, :min_guest_age, :total,
7
+ :surcharge_total, :nightly_rate_total, :average_base_rate,
8
+ :average_rate, :max_nightly_rate, :currency_code, :value_adds,
9
+ :room_type_description, :price_breakdown, :total_price,
10
+ :average_nightly_rate, :promo, :promo_description,
11
+ :arrival, :departure, :rooms, :bed_types,
12
+ :cancellation_policy, :non_refundable,
13
+ :guarantee_required, :deposit_required, :surcharges,
14
+ :rate_description, :raw, :rate_change, :guarantee_only
15
+
16
+ extend Helpers
17
+ include Helpers
18
+
19
+ # Internal: Create a new Room from within a Room search query.
20
+ #
21
+ # info - A Hash of parsed information from the API, with any of the keys
22
+ # from the attr_accessor's list.
23
+ def initialize(info)
24
+ info.each do |k, v|
25
+ instance_variable_set("@" + k.to_s, v)
26
+ end
27
+ end
28
+
29
+ # Public: Reserve a room.
30
+ #
31
+ # info - A Hash of the information described on the Suitcase
32
+ # [wiki](http://github.com/thoughtfusion/suitcase/wiki/User-flow).
33
+ #
34
+ # Returns a Suitcase::Reservation.
35
+ def reserve!(info)
36
+ params = {}
37
+ params["hotelId"] = @hotel_id
38
+ params["arrivalDate"] = @arrival
39
+ params["departureDate"] = @departure
40
+ params["supplierType"] = @supplier_type
41
+ # Only submit the rateKey if it is a merchant hotel
42
+ params["rateKey"] = @rate_key if @supplier_type == "E"
43
+ params["rateTypeCode"] = @room_type_code
44
+ params["rateCode"] = @rate_code
45
+ params["roomTypeCode"] = @room_type_code
46
+ params["chargeableRate"] = chargeable_rate
47
+ params["email"] = info[:email]
48
+ params["firstName"] = info[:first_name]
49
+ params["lastName"] = info[:last_name]
50
+ params["homePhone"] = info[:home_phone]
51
+ params["workPhone"] = info[:work_phone] if info[:work_phone]
52
+ params["extension"] = info[:work_phone_extension] if info[:work_phone_extension]
53
+ params["faxPhone"] = info[:fax_phone] if info[:fax_phone]
54
+ params["companyName"] = info[:company_name] if info[:company_name]
55
+ if info[:additional_emails]
56
+ params["emailIntineraryList"] = info[:additional_emails].join(",")
57
+ end
58
+ params["creditCardType"] = info[:payment_option].code
59
+ params["creditCardNumber"] = info[:credit_card_number]
60
+ params["creditCardIdentifier"] = info[:credit_card_verification_code]
61
+ expiration_date = Date._parse(info[:credit_card_expiration_date])
62
+ params["creditCardExpirationMonth"] = if expiration_date[:mon] < 10
63
+ "0" + expiration_date[:mon].to_s
64
+ else
65
+ expiration_date[:mon].to_s
66
+ end
67
+ params["creditCardExpirationYear"] = expiration_date[:year].to_s
68
+ params["address1"] = info[:address1]
69
+ params["address2"] = info[:address2] if info[:address2]
70
+ params["address3"] = info[:address3] if info[:address3]
71
+ params["city"] = info[:city]
72
+ params.merge!(parameterize_rooms(@rooms))
73
+ @rooms.each_with_index do |room, index|
74
+ index += 1
75
+ params["room#{index}FirstName"] = room[:first_name] || params["firstName"] # defaults to the billing
76
+ params["room#{index}LastName"] = room[:last_name] || params["lastName"] # person's name
77
+ params["room#{index}BedTypeId"] = room[:bed_type].id if @supplier_type == "E"
78
+ params["room#{index}SmokingPreference"] = room[:smoking_preference] || "E"
79
+ end
80
+ params["stateProvinceCode"] = info[:province]
81
+ params["countryCode"] = info[:country]
82
+ params["postalCode"] = info[:postal_code]
83
+
84
+ uri = Room.url(
85
+ method: "res",
86
+ params: params,
87
+ include_key: true,
88
+ include_cid: true,
89
+ secure: true
90
+ )
91
+ session = Patron::Session.new
92
+ session.timeout = 30000
93
+ session.base_url = "https://" + uri.host
94
+ res = session.post uri.request_uri, {}
95
+ parsed = JSON.parse res.body
96
+
97
+ reservation_res = parsed["HotelRoomReservationResponse"]
98
+ handle_errors(parsed)
99
+ rate_info = reservation_res["RateInfos"]["RateInfo"]
100
+ surcharges = if @supplier_type == "E" && rate_info["ChargeableRateInfo"]["Surcharges"]
101
+ [rate_info["ChargeableRateInfo"]["Surcharges"]["Surcharge"]].
102
+ flatten.map { |s| Surcharge.parse(s) }
103
+ else
104
+ []
105
+ end
106
+ r = Reservation.new(
107
+ itinerary_id: reservation_res["itineraryId"],
108
+ confirmation_numbers: reservation_res["confirmationNumbers"],
109
+ surcharges: surcharges
110
+ )
111
+ r.raw = parsed
112
+ r
113
+ end
114
+
115
+ # Public: The chargeable rate for the Hotel room.
116
+ #
117
+ # Returns the chargeable rate for the Room.
118
+ def chargeable_rate
119
+ if @supplier_type == "E"
120
+ @total_price
121
+ else
122
+ @max_nightly_rate
123
+ end
124
+ end
125
+
126
+ # Public: The description of the displayed rate.
127
+ #
128
+ # Returns the rate description based on the `rate_change` attribute.
129
+ def room_rate_description
130
+ if @rate_change
131
+ "rate changes during the dates requested and the single nightly rate displayed is the highest nightly rate of the dates requested without taxes and fees."
132
+ else
133
+ "highest single night rate during the dates selected without taxes or fees"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+
@@ -0,0 +1,7 @@
1
+ module Suitcase
2
+ class Hotel
3
+ # Public: Hold user session data. A simple Struct provided to be passed in
4
+ # to some of the EAN methods.
5
+ Session = Struct.new(:id, :user_agent, :ip_address, :locale, :currency_code)
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ module Suitcase
2
+ class Hotel
3
+ # Public: A Surcharge represents a single surcharge on a Room.
4
+ class Surcharge
5
+ attr_accessor :amount, :type
6
+ # Internal: Create a new Surcharge.
7
+ #
8
+ # info - A Hash of parsed info from Surcharge.parse.
9
+ def initialize(info)
10
+ @amount, @type = info[:amount], info[:type]
11
+ end
12
+
13
+ # Internal: Parse a Surcharge from the room response.
14
+ #
15
+ # info - A Hash of the parsed JSON relevant to the surhcarge.
16
+ #
17
+ # Returns a Surcharge representing the info.
18
+ def self.parse(info)
19
+ new(amount: info["@amount"], type: info["@type"])
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module Suitcase
2
+ VERSION = "1.7.6"
3
+ end
@@ -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::Hotel::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::Hotel::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::Hotel::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::Hotel::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: Keys::RESERVATION_START_TIME, departure: Keys::RESERVATION_END_TIME).first
17
+
18
+ # Query 4
19
+ Suitcase::Hotel::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: Keys::RESERVATION_START_TIME, departure: Keys::RESERVATION_END_TIME)
33
+ Suitcase::Hotel::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: Keys::RESERVATION_START_TIME, departure: Keys::RESERVATION_END_TIME)
40
+ Suitcase::Hotel::PaymentOption.find currency_code: "USD"
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ require "minitest_helper"
2
+
3
+ describe Suitcase::Hotel::EANException do
4
+ before :each do
5
+ @exception = Suitcase::Hotel::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