From 4e99fc0ce7002b48e83d5da190aa4f518e4c2070 Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 13:01:21 +0800 Subject: [PATCH 001/118] Make host_percentage_fee a method --- lib/concierge/entities/quotation.rb | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/lib/concierge/entities/quotation.rb b/lib/concierge/entities/quotation.rb index 42247194c..302de13ca 100644 --- a/lib/concierge/entities/quotation.rb +++ b/lib/concierge/entities/quotation.rb @@ -13,30 +13,41 @@ # +available+: whether or not the property is available for the given dates # +total+: the quoted price for the booking # +currency+: the currency used for the quotation -# +host_fee_percentage+: the host fee percent, which included in quoted price # # The quotation is only successful if the +errors+ attribute is empty. class Quotation include Hanami::Entity include Hanami::Validations - attribute :property_id, type: String - attribute :unit_id, type: String - attribute :check_in, type: String - attribute :check_out, type: String - attribute :guests, type: Integer - attribute :available, type: Boolean - attribute :total, type: Float - attribute :currency, type: String - attribute :host_fee_percentage, type: Float + attribute :property_id, type: String + attribute :unit_id, type: String + attribute :check_in, type: String + attribute :check_out, type: String + attribute :guests, type: Integer + attribute :available, type: Boolean + attribute :total, type: Float + attribute :currency, type: String + #validates :net_rate, presence: true + + # The fee, already included in quoted total price def host_fee (total - net_rate).round(2) end + # The quotation without host fee def net_rate coefficient = 1 + (host_fee_percentage.to_f / 100) (total / coefficient).round(2) end + # The fee percentage, already included in quoted total price + def host_fee_percentage + @host_fee_percentage ||= HostRepository.find(property.host_id).fee_percentage + end + + def property + @property ||= PropertyRepository.identified_by(property_id).first + end + end From 181371125156077cba68f748afdd9d4adc93d2e4 Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 13:01:43 +0800 Subject: [PATCH 002/118] Fix waytostay specs --- lib/concierge/suppliers/waytostay/quote.rb | 11 ----------- spec/fixtures/waytostay/bookings/quote.json | 2 +- spec/lib/concierge/suppliers/waytostay/client_spec.rb | 8 +++++++- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/concierge/suppliers/waytostay/quote.rb b/lib/concierge/suppliers/waytostay/quote.rb index 3295f3a09..da7c4f980 100644 --- a/lib/concierge/suppliers/waytostay/quote.rb +++ b/lib/concierge/suppliers/waytostay/quote.rb @@ -87,21 +87,10 @@ def quote_params_from(response) check_out: response.get("booking_details.departure_date"), guests: response.get("booking_details.number_of_adults"), total: response.get("pricing.pricing_summary.gross_total"), - host_fee_percentage: host.fee_percentage, currency: response.get("pricing.currency"), available: true } end - # Get the first host under Waytostay, because there - # should only be one host - # - def host - @host ||= begin - supplier = SupplierRepository.named(Waytostay::Client::SUPPLIER_NAME) - HostRepository.from_supplier(supplier).first - end - end - end end diff --git a/spec/fixtures/waytostay/bookings/quote.json b/spec/fixtures/waytostay/bookings/quote.json index 25706cee8..a56e66481 100644 --- a/spec/fixtures/waytostay/bookings/quote.json +++ b/spec/fixtures/waytostay/bookings/quote.json @@ -1,6 +1,6 @@ { "booking_details": { - "property_reference": "016111", + "property_reference": "success", "arrival_date": "2016-07-14", "departure_date": "2016-07-17", "number_of_nights": 3, diff --git a/spec/lib/concierge/suppliers/waytostay/client_spec.rb b/spec/lib/concierge/suppliers/waytostay/client_spec.rb index 98db85e8b..3c8476a40 100644 --- a/spec/lib/concierge/suppliers/waytostay/client_spec.rb +++ b/spec/lib/concierge/suppliers/waytostay/client_spec.rb @@ -146,7 +146,7 @@ end describe "#quote" do - + let(:host) { create_host(fee_percentage: 7.0) } let(:quote_url) { base_url + Waytostay::Quote::ENDPOINT } let(:quote_post_body) {{ @@ -181,6 +181,12 @@ stub_call(:post, quote_url, body: timeout_waytostay_params.to_json, strict: true) { raise Faraday::TimeoutError } + + + %w(success unavailable less_than_min malformed_response, + earlier_than_cutoff timeout).each do |id| + create_property(identifier: id, host_id: host.id) + end end it_behaves_like "supplier quote method" do From 2e31e04efb32c090639dc0ae6a24093fbbf01af4 Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 13:07:00 +0800 Subject: [PATCH 003/118] Fix atleisure specs --- lib/concierge/suppliers/atleisure/price.rb | 23 ++++--------------- .../suppliers/atleisure/price_spec.rb | 7 +++--- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/lib/concierge/suppliers/atleisure/price.rb b/lib/concierge/suppliers/atleisure/price.rb index 42c795237..f07b1f5a5 100644 --- a/lib/concierge/suppliers/atleisure/price.rb +++ b/lib/concierge/suppliers/atleisure/price.rb @@ -38,9 +38,6 @@ def initialize(credentials) # Calls the CheckAvailabilityV1 method from the AtLeisure JSON-RPC interface. # Returns a +Result+ object. def quote(params) - host = fetch_host - return host_not_found unless host - stay_details = { "HouseCode" => params[:property_id], "ArrivalDate" => params[:check_in].to_s, @@ -52,7 +49,7 @@ def quote(params) result = client.invoke("CheckAvailabilityV1", stay_details) if result.success? - parse_quote_response(params, result.value, host.fee_percentage) + parse_quote_response(params, result.value) else result end @@ -60,19 +57,8 @@ def quote(params) private - # Get the first host under AtLeisure, because there - # should only be one host - def fetch_host - supplier = SupplierRepository.named(AtLeisure::Client::SUPPLIER_NAME) - HostRepository.from_supplier(supplier).first - end - - def host_not_found - Result.error(:host_not_found) - end - - def parse_quote_response(params, response, host_fee_percentage) - quotation = build_quotation(params, host_fee_percentage) + def parse_quote_response(params, response) + quotation = build_quotation(params) if response["OnRequest"] == "Yes" no_instant_confirmation @@ -100,14 +86,13 @@ def parse_quote_response(params, response, host_fee_percentage) end end - def build_quotation(params, host_fee_percentage) + def build_quotation(params) Quotation.new( property_id: params[:property_id], check_in: params[:check_in].to_s, check_out: params[:check_out].to_s, guests: params[:guests], currency: CURRENCY, - host_fee_percentage: host_fee_percentage, ) end diff --git a/spec/lib/concierge/suppliers/atleisure/price_spec.rb b/spec/lib/concierge/suppliers/atleisure/price_spec.rb index 93c898be0..c7809535c 100644 --- a/spec/lib/concierge/suppliers/atleisure/price_spec.rb +++ b/spec/lib/concierge/suppliers/atleisure/price_spec.rb @@ -6,13 +6,13 @@ include Support::Factories let(:credentials) { double(username: "roomorama", password: "atleisure-roomorama") } + let(:supplier) { create_supplier(name: AtLeisure::Client::SUPPLIER_NAME) } + let!(:host) { create_host(supplier_id: supplier.id, fee_percentage: 7.0) } let(:params) { { property_id: "AT-123", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } } before do - supplier = create_supplier(name: AtLeisure::Client::SUPPLIER_NAME) - create_host(supplier_id: supplier.id, fee_percentage: 7.0) allow_any_instance_of(Concierge::JSONRPC).to receive(:request_id) { 888888888888 } end @@ -105,6 +105,7 @@ end it "returns an available quotation properly priced according to the response" do + create_property(identifier: "AT-123", host_id: host.id) stub_with_fixture("atleisure/available.json") result = subject.quote(params) @@ -122,7 +123,7 @@ expect(quotation.host_fee_percentage).to eq(7) end - it 'fails if host is not found' do + xit 'fails if host is not found' do allow(subject).to receive(:fetch_host) { nil } result = subject.quote(params) From 376be74dadd86efe281d3182bbfae02c9de0d273 Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 13:54:52 +0800 Subject: [PATCH 004/118] Fix kigo specs --- lib/concierge/suppliers/kigo/response_parser.rb | 10 ---------- .../concierge/suppliers/kigo/response_parser_spec.rb | 5 +++-- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/lib/concierge/suppliers/kigo/response_parser.rb b/lib/concierge/suppliers/kigo/response_parser.rb index 5beea84bd..1c12a175a 100644 --- a/lib/concierge/suppliers/kigo/response_parser.rb +++ b/lib/concierge/suppliers/kigo/response_parser.rb @@ -58,10 +58,8 @@ def compute_pricing(response) end return property_not_found unless property - return host_not_found unless host quotation.available = true - quotation.host_fee_percentage = host.fee_percentage quotation.currency = currency quotation.total = total.to_f @@ -179,14 +177,6 @@ def property_not_found Result.error(:property_not_found) end - def host_not_found - Result.error(:host_not_found) - end - - def host - @host ||= HostRepository.find(property.host_id) - end - def property @property ||= PropertyRepository.identified_by(params[:property_id]).first end diff --git a/spec/lib/concierge/suppliers/kigo/response_parser_spec.rb b/spec/lib/concierge/suppliers/kigo/response_parser_spec.rb index eb2035791..9c89930e6 100644 --- a/spec/lib/concierge/suppliers/kigo/response_parser_spec.rb +++ b/spec/lib/concierge/suppliers/kigo/response_parser_spec.rb @@ -89,7 +89,7 @@ expect(result.error.code).to eq :property_not_found end - it "fails if host not found" do + xit "fails if host not found" do allow(subject).to receive(:host) { nil } response = read_fixture("kigo/success.json") result = subject.compute_pricing(response) @@ -119,7 +119,8 @@ it "returns net ammount if host has a fee" do host.fee_percentage = 8.0 - allow(subject).to receive(:host) { host } + HostRepository.update host + #allow(subject).to receive(:host) { host } response = read_fixture("kigo/success.json") result = subject.compute_pricing(response) From 771527482507d4f31ced8a0c2e46e893cb393578 Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 14:39:16 +0800 Subject: [PATCH 005/118] Fix specs --- lib/concierge/suppliers/ciirus/price.rb | 21 +++---------------- .../suppliers/poplidays/mappers/quote.rb | 6 ++---- lib/concierge/suppliers/poplidays/price.rb | 15 +------------ spec/api/controllers/atleisure/quote_spec.rb | 9 ++++---- .../api/controllers/kigo/legacy/quote_spec.rb | 2 +- spec/api/controllers/kigo/quote_spec.rb | 2 +- spec/api/controllers/poplidays/quote_spec.rb | 3 ++- .../shared/kigo_price_quotation.rb | 2 -- spec/api/controllers/waytostay/quote_spec.rb | 5 ++++- .../concierge/suppliers/ciirus/price_spec.rb | 11 ++-------- .../concierge/suppliers/kigo/booking_spec.rb | 2 +- .../suppliers/poplidays/mappers/quote_spec.rb | 8 ++++--- .../suppliers/poplidays/price_spec.rb | 4 ++-- 13 files changed, 28 insertions(+), 62 deletions(-) diff --git a/lib/concierge/suppliers/ciirus/price.rb b/lib/concierge/suppliers/ciirus/price.rb index 2bc96c4fa..a40daec06 100644 --- a/lib/concierge/suppliers/ciirus/price.rb +++ b/lib/concierge/suppliers/ciirus/price.rb @@ -17,8 +17,7 @@ module Ciirus # # The +quote+ method returns a +Result+ object that, when successful, encapsulates the # resulting +Quotation+ object. - # Actually the main logic of building the +Quotation+ object is in +QuoteFetcher+ class, - # while +Price+ responsible for filling +host_fee_percentage+ field. + # Actually the main logic of building the +Quotation+ object is in +QuoteFetcher+ class class Price attr_reader :credentials @@ -29,30 +28,16 @@ def initialize(credentials) def quote(params) property = fetch_property(params[:property_id]) return property_not_found unless property - host = fetch_host(property.host_id) - return host_not_found unless host - quotation = Ciirus::Commands::QuoteFetcher.new(credentials).call(params) - return quotation unless quotation.success? - - quotation.value.host_fee_percentage = host.fee_percentage - quotation + Ciirus::Commands::QuoteFetcher.new(credentials).call(params) end def property_not_found Result.error(:property_not_found) end - def host_not_found - Result.error(:host_not_found) - end - - def fetch_host(id) - HostRepository.find(id) - end - def fetch_property(id) PropertyRepository.identified_by(id).first end end -end \ No newline at end of file +end diff --git a/lib/concierge/suppliers/poplidays/mappers/quote.rb b/lib/concierge/suppliers/poplidays/mappers/quote.rb index 19d6aaff4..3df6560ec 100644 --- a/lib/concierge/suppliers/poplidays/mappers/quote.rb +++ b/lib/concierge/suppliers/poplidays/mappers/quote.rb @@ -17,16 +17,14 @@ class Quote # * +mandatory_services+ [Float] mandatory services price # * +quote+ [Result] result which contains response from Poplidays booking/easy method # in "EVALUATION" mode - # * +host_fee_percentage+ [Float] # Returns a +Result+ wrapping +Roomorama::Quotation+ - def build(params, mandatory_services, quote, host_fee_percentage) + def build(params, mandatory_services, quote) quotation = ::Quotation.new( property_id: params[:property_id], check_in: params[:check_in].to_s, check_out: params[:check_out].to_s, guests: params[:guests], available: available?(quote), - host_fee_percentage: host_fee_percentage ) if quotation.available return Result.error(:unexpected_quote) unless quote.value['value'] @@ -48,4 +46,4 @@ def calc_total(mandaroty_services, quote) end end end -end \ No newline at end of file +end diff --git a/lib/concierge/suppliers/poplidays/price.rb b/lib/concierge/suppliers/poplidays/price.rb index 876b5dc2f..920b8385c 100644 --- a/lib/concierge/suppliers/poplidays/price.rb +++ b/lib/concierge/suppliers/poplidays/price.rb @@ -44,16 +44,13 @@ def initialize(credentials) # for when calculating the subtotal. For that purpose, an API call to # the property details endpoint is made and that value is extracted. def quote(params) - host = fetch_host - return host_not_found unless host - mandatory_services = retrieve_mandatory_services(params[:property_id]) return mandatory_services unless mandatory_services.success? quote = retrieve_quote(params) return quote if unknown_errors?(quote) - mapper.build(params, mandatory_services.value, quote, host.fee_percentage) + mapper.build(params, mandatory_services.value, quote) end private @@ -66,16 +63,6 @@ def unknown_errors?(quote) !quote.success? && ![:http_status_400, :http_status_409].include?(quote.error.code) end - # Get the first Poplidays host, because there should only be one host - def fetch_host - supplier = SupplierRepository.named(Poplidays::Client::SUPPLIER_NAME) - HostRepository.from_supplier(supplier).first - end - - def host_not_found - Result.error(:host_not_found) - end - def retrieve_quote(params) fetcher = Poplidays::Commands::QuoteFetcher.new(credentials) fetcher.call(params) diff --git a/spec/api/controllers/atleisure/quote_spec.rb b/spec/api/controllers/atleisure/quote_spec.rb index d2dcec5ce..cc56dcff8 100644 --- a/spec/api/controllers/atleisure/quote_spec.rb +++ b/spec/api/controllers/atleisure/quote_spec.rb @@ -7,13 +7,12 @@ include Support::Fixtures include Support::Factories - before do - supplier = create_supplier(name: AtLeisure::Client::SUPPLIER_NAME) - create_host(supplier_id: supplier.id, fee_percentage: 7.0) - end + let(:supplier) { create_supplier(name: AtLeisure::Client::SUPPLIER_NAME) } + let(:host) { create_host(supplier_id: supplier.id, fee_percentage: 7.0) } + let!(:property) { create_property(identifier: "AT-123", host_id: host.id) } let(:params) { - { property_id: "AT-123", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } + { property_id: property.identifier, check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } } let(:endpoint) { AtLeisure::Price::ENDPOINT } diff --git a/spec/api/controllers/kigo/legacy/quote_spec.rb b/spec/api/controllers/kigo/legacy/quote_spec.rb index 309c114d9..e832c74d2 100644 --- a/spec/api/controllers/kigo/legacy/quote_spec.rb +++ b/spec/api/controllers/kigo/legacy/quote_spec.rb @@ -9,7 +9,7 @@ include Support::Factories let!(:host) { create_host(fee_percentage: 7.0) } - let!(:property) { create_property(identifier: "567") } + let!(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) { { property_id: property.identifier, check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } diff --git a/spec/api/controllers/kigo/quote_spec.rb b/spec/api/controllers/kigo/quote_spec.rb index be00828c0..bbed1c7da 100644 --- a/spec/api/controllers/kigo/quote_spec.rb +++ b/spec/api/controllers/kigo/quote_spec.rb @@ -9,7 +9,7 @@ include Support::Factories let!(:host) { create_host(fee_percentage: 7.0) } - let!(:property) { create_property(identifier: "567") } + let!(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) { { property_id: property.identifier, check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } diff --git a/spec/api/controllers/poplidays/quote_spec.rb b/spec/api/controllers/poplidays/quote_spec.rb index abf72a7b1..13d2739de 100644 --- a/spec/api/controllers/poplidays/quote_spec.rb +++ b/spec/api/controllers/poplidays/quote_spec.rb @@ -10,8 +10,9 @@ let!(:supplier) { create_supplier(name: Poplidays::Client::SUPPLIER_NAME) } let!(:host) { create_host(supplier_id: supplier.id, fee_percentage: 5) } + let!(:property) { create_property(identifier: '48327', host_id: host.id) } let(:params) { - { property_id: '48327', check_in: '2016-12-17', check_out: '2016-12-26', guests: 2 } + { property_id: property.identifier, check_in: '2016-12-17', check_out: '2016-12-26', guests: 2 } } let(:credentials) do double(url: 'api.poplidays.com', diff --git a/spec/api/controllers/shared/kigo_price_quotation.rb b/spec/api/controllers/shared/kigo_price_quotation.rb index 1b36a9c1f..80e9c5536 100644 --- a/spec/api/controllers/shared/kigo_price_quotation.rb +++ b/spec/api/controllers/shared/kigo_price_quotation.rb @@ -68,7 +68,6 @@ before { stub_call(:post, endpoint) { [200, {}, read_fixture("kigo/success.json")] } } it "returns available quotations with price when the call is successful" do - allow_any_instance_of(Kigo::ResponseParser).to receive(:host) { Host.new(fee_percentage: 0) } response = parse_response(described_class.new.call(params)) expect(response.status).to eq 200 @@ -83,7 +82,6 @@ end it "returns available quotations with gross rate" do - allow_any_instance_of(Kigo::ResponseParser).to receive(:host) { Host.new(fee_percentage: 7.0) } response = parse_response(described_class.new.call(params)) expect(response.status).to eq 200 diff --git a/spec/api/controllers/waytostay/quote_spec.rb b/spec/api/controllers/waytostay/quote_spec.rb index 160d34c89..f58576bb0 100644 --- a/spec/api/controllers/waytostay/quote_spec.rb +++ b/spec/api/controllers/waytostay/quote_spec.rb @@ -5,9 +5,12 @@ RSpec.describe API::Controllers::Waytostay::Quote do include Support::HTTPStubbing + include Support::Factories + let(:host) { create_host(fee_percentage: 7) } + let(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) { - { property_id: "567", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } + { property_id: property.identifier, check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } } it_behaves_like "performing parameter validations", controller_generator: -> { described_class.new } do diff --git a/spec/lib/concierge/suppliers/ciirus/price_spec.rb b/spec/lib/concierge/suppliers/ciirus/price_spec.rb index dc9faa4b3..5ccb49578 100644 --- a/spec/lib/concierge/suppliers/ciirus/price_spec.rb +++ b/spec/lib/concierge/suppliers/ciirus/price_spec.rb @@ -29,7 +29,7 @@ expect(result.error.code).to eq :property_not_found end - it 'fails if host is not found' do + xit 'fails if host is not found' do allow(subject).to receive(:fetch_host) { nil } result = subject.quote(params) @@ -37,12 +37,5 @@ expect(result.error.code).to eq :host_not_found end - it 'fills host_fee_percentage' do - allow_any_instance_of(Ciirus::Commands::QuoteFetcher).to receive(:call) { Result.new(Quotation.new) } - - result = subject.quote(params) - expect(result).to be_success - expect(result.value.host_fee_percentage).to eq(7) - end end -end \ No newline at end of file +end diff --git a/spec/lib/concierge/suppliers/kigo/booking_spec.rb b/spec/lib/concierge/suppliers/kigo/booking_spec.rb index d1791cd35..edf7efcb5 100644 --- a/spec/lib/concierge/suppliers/kigo/booking_spec.rb +++ b/spec/lib/concierge/suppliers/kigo/booking_spec.rb @@ -2,6 +2,7 @@ RSpec.describe Kigo::Booking do include Support::Fixtures + include Support::Factories include Support::HTTPStubbing let(:credentials) { double(subscription_key: '32933') } @@ -41,7 +42,6 @@ end it 'returns wrapped reservation with code if success' do - allow_any_instance_of(Kigo::ResponseParser).to receive(:host) { Host.new(fee_percentage: 0) } stub_call(:post, endpoint) { [200, {}, read_fixture('kigo/success_booking.json')] } result = subject.book(params) diff --git a/spec/lib/concierge/suppliers/poplidays/mappers/quote_spec.rb b/spec/lib/concierge/suppliers/poplidays/mappers/quote_spec.rb index cd34bb9e8..bc6d912ed 100644 --- a/spec/lib/concierge/suppliers/poplidays/mappers/quote_spec.rb +++ b/spec/lib/concierge/suppliers/poplidays/mappers/quote_spec.rb @@ -2,9 +2,11 @@ RSpec.describe Poplidays::Mappers::Quote do include Support::Fixtures + include Support::Factories + let(:host) { create_host(fee_percentage: 5.0) } + let!(:property) { create_property(identifier: '33680', host_id: host.id) } let(:mandatory_services) { 25.0 } - let(:host_fee_percentage) { 5.0 } let(:params) do API::Controllers::Params::Quote.new(property_id: '33680', check_in: '2017-08-01', @@ -13,7 +15,7 @@ end subject { described_class.new } - let(:result) { subject.build(params, mandatory_services, quote, host_fee_percentage) } + let(:result) { subject.build(params, mandatory_services, quote) } context 'for success response' do let(:quote) do @@ -68,4 +70,4 @@ end -end \ No newline at end of file +end diff --git a/spec/lib/concierge/suppliers/poplidays/price_spec.rb b/spec/lib/concierge/suppliers/poplidays/price_spec.rb index 21c4bcf17..5abe3eddf 100644 --- a/spec/lib/concierge/suppliers/poplidays/price_spec.rb +++ b/spec/lib/concierge/suppliers/poplidays/price_spec.rb @@ -7,6 +7,7 @@ let!(:supplier) { create_supplier(name: Poplidays::Client::SUPPLIER_NAME) } let!(:host) { create_host(supplier_id: supplier.id, fee_percentage: 5) } + let!(:property) { create_property(identifier: '3498', host_id: host.id) } let(:params) { { property_id: '3498', check_in: '2016-12-17', check_out: '2016-12-26', guests: 2 } } @@ -50,8 +51,7 @@ expect(result.error.code).to eq :connection_timeout end - it 'fails if host is not found' do - allow(subject).to receive(:fetch_host) { nil } + xit 'fails if host is not found' do result = subject.quote(params) expect(result).not_to be_success From e1d8e77a14a2bf8962127f407580f267fe6be595 Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 16:42:16 +0800 Subject: [PATCH 006/118] Remove intermediat class --- lib/concierge/suppliers/ciirus/client.rb | 4 +- lib/concierge/suppliers/ciirus/price.rb | 43 ------------------- .../concierge/suppliers/ciirus/price_spec.rb | 41 ------------------ 3 files changed, 2 insertions(+), 86 deletions(-) delete mode 100644 lib/concierge/suppliers/ciirus/price.rb delete mode 100644 spec/lib/concierge/suppliers/ciirus/price_spec.rb diff --git a/lib/concierge/suppliers/ciirus/client.rb b/lib/concierge/suppliers/ciirus/client.rb index b9392e05e..35f4a956f 100644 --- a/lib/concierge/suppliers/ciirus/client.rb +++ b/lib/concierge/suppliers/ciirus/client.rb @@ -25,7 +25,7 @@ def initialize(credentials) # Ciirus, a generic error message is sent back to the caller, and the failure # is logged. def quote(params) - Ciirus::Price.new(credentials).quote(params) + Ciirus::Commands::QuoteFetcher.new(credentials).call(params) end # Returns a +Result+ wrapping +Reservation+ in success case. @@ -44,4 +44,4 @@ def cancel(params) Ciirus::Commands::Cancel.new(credentials).call(params[:reference_number]) end end -end \ No newline at end of file +end diff --git a/lib/concierge/suppliers/ciirus/price.rb b/lib/concierge/suppliers/ciirus/price.rb deleted file mode 100644 index a40daec06..000000000 --- a/lib/concierge/suppliers/ciirus/price.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Ciirus - - # +Ciirus::Price+ - # - # This class is responsible for wrapping the logic related to making a price quotation - # to Ciirus, parsing the response, and building the +Quotation+ object with the data - # returned from their API. - # - # Usage - # - # result = Ciirus::Price.new(credentials).quote(stay_params) - # if result.success? - # process_quotation(result.value) - # else - # handle_error(result.error) - # end - # - # The +quote+ method returns a +Result+ object that, when successful, encapsulates the - # resulting +Quotation+ object. - # Actually the main logic of building the +Quotation+ object is in +QuoteFetcher+ class - class Price - attr_reader :credentials - - def initialize(credentials) - @credentials = credentials - end - - def quote(params) - property = fetch_property(params[:property_id]) - return property_not_found unless property - - Ciirus::Commands::QuoteFetcher.new(credentials).call(params) - end - - def property_not_found - Result.error(:property_not_found) - end - - def fetch_property(id) - PropertyRepository.identified_by(id).first - end - end -end diff --git a/spec/lib/concierge/suppliers/ciirus/price_spec.rb b/spec/lib/concierge/suppliers/ciirus/price_spec.rb deleted file mode 100644 index 5ccb49578..000000000 --- a/spec/lib/concierge/suppliers/ciirus/price_spec.rb +++ /dev/null @@ -1,41 +0,0 @@ -require "spec_helper" - -RSpec.describe Ciirus::Price do - include Support::Fixtures - include Support::Factories - - let!(:host) { create_host(fee_percentage: 7) } - let!(:property) { create_property(identifier: '123', host_id: host.id) } - let(:credentials) do - double(username: 'Foo', - password: '123', - url: 'http://proxy.roomorama.com/ciirus') - end - let(:params) do - API::Controllers::Params::Quote.new(property_id: '123', - check_in: '2017-08-01', - check_out: '2017-08-05', - guests: 2) - end - - subject { described_class.new(credentials) } - - describe '#quote' do - it 'fails if property is not found' do - params.property_id = 'unknown id' - result = subject.quote(params) - - expect(result).not_to be_success - expect(result.error.code).to eq :property_not_found - end - - xit 'fails if host is not found' do - allow(subject).to receive(:fetch_host) { nil } - result = subject.quote(params) - - expect(result).not_to be_success - expect(result.error.code).to eq :host_not_found - end - - end -end From fa0a33b4901e9eee9c51eed6a014b4a781a9fd09 Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 18:07:39 +0800 Subject: [PATCH 007/118] Add quote validation --- apps/api/application.rb | 2 + apps/api/middlewares/quote_validations.rb | 56 +++++++++++++++ .../api/middlewares/quote_validations_spec.rb | 69 +++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 apps/api/middlewares/quote_validations.rb create mode 100644 spec/api/middlewares/quote_validations_spec.rb diff --git a/apps/api/application.rb b/apps/api/application.rb index 309e5ec1e..453ee823a 100644 --- a/apps/api/application.rb +++ b/apps/api/application.rb @@ -3,6 +3,7 @@ require_relative "middlewares/request_logging" require_relative "middlewares/authentication" require_relative "middlewares/roomorama_webhook" +require_relative "middlewares/quote_validations" require_relative "middlewares/request_context" require_relative "views/accept_json" @@ -32,6 +33,7 @@ class Application < Hanami::Application middleware.use API::Middlewares::HealthCheck middleware.use API::Middlewares::Authentication middleware.use API::Middlewares::RoomoramaWebhook + middleware.use API::Middlewares::QuoteValidations middleware.use API::Middlewares::RequestContext view.prepare do diff --git a/apps/api/middlewares/quote_validations.rb b/apps/api/middlewares/quote_validations.rb new file mode 100644 index 000000000..014ae192f --- /dev/null +++ b/apps/api/middlewares/quote_validations.rb @@ -0,0 +1,56 @@ +require_relative "../../../lib/concierge/json" +require_relative "../../../lib/concierge/safe_access_hash" + +module API::Middlewares + + # +API::Middlewares::QuoteValidations+ + # + # Quotation queries requires that Concierge knows what host_fee to + # append to the response from supplier. Hence we require that the property + # record, and hence the host record, exists on Concierge. If we don't impose + # this check, a quote call to partner would succeed (for active property) and + # Concierge would mistakenly return $0 for host fee to Roomorama. + # + # This should be called after RoomoramaWebhook, so we expect the massaged + # concierge params + class QuoteValidations + + include Concierge::JSON + + attr_reader :app, :env, :webhook_payload + + def initialize(app) + @app = app + end + + def call(env) + @env = env + + return app.call(env) unless quote_request? + + return app.call(env) if property_exists? + + [404, {}, ["Property not found on Concierge"]] + end + + private + + def quote_request? + env['PATH_INFO'].include? 'quote' + end + + def property_exists? + body = read_request_body(env) + json_payload = json_decode(body) + property = PropertyRepository.identified_by(json_payload.value["property_id"]) + property.count > 0 + end + + def read_request_body(env) + env["rack.input"].read.tap do + env["rack.input"].rewind + end + end + end +end + diff --git a/spec/api/middlewares/quote_validations_spec.rb b/spec/api/middlewares/quote_validations_spec.rb new file mode 100644 index 000000000..e8a6d24cb --- /dev/null +++ b/spec/api/middlewares/quote_validations_spec.rb @@ -0,0 +1,69 @@ +require "spec_helper" + +RSpec.describe API::Middlewares::QuoteValidations do + include Support::Factories + + let(:quote_request) { + { + property_id: "023", + check_in: "2016-10-01", + check_out: "2016-10-05", + guests: 2 + } + } + + let(:request_body) { StringIO.new(quote_request.to_json) } + let(:app) { Rack::MockRequest.new(subject) } + let(:response) { "OK" } + let(:upstream) { lambda { |env| [200, {}, response] } } + let(:headers) { + { + input: request_body, + "CONTENT_TYPE" => "application/json" + } + } + + subject { described_class.new(upstream) } + + describe "Not a quote request" do + it "does not touch the request" do + expect(post("/supplierx/cancel", headers)).to eq [200, { "Content-Length" => response.size.to_s }, response] + end + end + + describe "Property and host exists" do + let!(:host) { create_host(fee_percentage: 7) } + let!(:property) { create_property(identifier:"023", host_id: host.id) } + + it "forwards to upstream" do + expect(post("/supplierx/quote", headers)).to eq [200, { "Content-Length" => response.size.to_s }, response] + end + end + + describe "Unknown property and host" do + let(:property_not_found) { + [404, { "Content-Length" => "31" }, "Property not found on Concierge"] + } + + before do + PropertyRepository.clear + HostRepository.clear + end + + it "returns 404" do + expect(post("/supplierx/quote", headers)).to eq property_not_found + end + end + + def post(path, params = {}) + to_rack(app.post(path, params)) + end + + def to_rack(response) + [ + response.status, + response.header, + response.body + ] + end +end From 666d548c35406aedecfc972baa543ae9b7bf02fb Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 18:39:19 +0800 Subject: [PATCH 008/118] Remove redundant check --- lib/concierge/suppliers/kigo/response_parser.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/concierge/suppliers/kigo/response_parser.rb b/lib/concierge/suppliers/kigo/response_parser.rb index 1c12a175a..878d1f4d8 100644 --- a/lib/concierge/suppliers/kigo/response_parser.rb +++ b/lib/concierge/suppliers/kigo/response_parser.rb @@ -28,7 +28,6 @@ def initialize(params) # in case the response is successful. Possible errors that could # happen in this step are: # - # +property_not_found+: the param's +property_id+ doesn't persist in our database. # +invalid_json_representation+: the response sent back is not a valid JSON. # +quote_call_failed+: the response status is not +E_OK+. # +unrecognised_response+: the response was successful, but the format cannot @@ -57,7 +56,6 @@ def compute_pricing(response) end end - return property_not_found unless property quotation.available = true quotation.currency = currency @@ -173,14 +171,6 @@ def unrecognised_response Result.error(:unrecognised_response) end - def property_not_found - Result.error(:property_not_found) - end - - def property - @property ||= PropertyRepository.identified_by(params[:property_id]).first - end - def non_successful_result_code message = "The `API_RESULT_CODE` obtained was not equal to `E_OK`. Check Kigo's " + "API documentation for an explanation for the `API_RESULT_CODE` returned." From 4ce75bec5d04cbab4ddf89c98b8730a6f0c6caaa Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 18:47:38 +0800 Subject: [PATCH 009/118] Remove skipped specs --- spec/api/controllers/ciirus/quote_spec.rb | 2 +- .../suppliers/atleisure/price_spec.rb | 8 ------- .../suppliers/kigo/response_parser_spec.rb | 21 ------------------- .../suppliers/poplidays/price_spec.rb | 7 ------- 4 files changed, 1 insertion(+), 37 deletions(-) diff --git a/spec/api/controllers/ciirus/quote_spec.rb b/spec/api/controllers/ciirus/quote_spec.rb index eecbb6433..65a2c3332 100644 --- a/spec/api/controllers/ciirus/quote_spec.rb +++ b/spec/api/controllers/ciirus/quote_spec.rb @@ -84,4 +84,4 @@ def provoke_failure! end end end -end \ No newline at end of file +end diff --git a/spec/lib/concierge/suppliers/atleisure/price_spec.rb b/spec/lib/concierge/suppliers/atleisure/price_spec.rb index c7809535c..61a854b8a 100644 --- a/spec/lib/concierge/suppliers/atleisure/price_spec.rb +++ b/spec/lib/concierge/suppliers/atleisure/price_spec.rb @@ -123,14 +123,6 @@ expect(quotation.host_fee_percentage).to eq(7) end - xit 'fails if host is not found' do - allow(subject).to receive(:fetch_host) { nil } - result = subject.quote(params) - - expect(result).not_to be_success - expect(result.error.code).to eq :host_not_found - end - def stub_with_fixture(name) atleisure_response = JSON.parse(read_fixture(name)) response = { diff --git a/spec/lib/concierge/suppliers/kigo/response_parser_spec.rb b/spec/lib/concierge/suppliers/kigo/response_parser_spec.rb index 9c89930e6..c6c835bf1 100644 --- a/spec/lib/concierge/suppliers/kigo/response_parser_spec.rb +++ b/spec/lib/concierge/suppliers/kigo/response_parser_spec.rb @@ -77,27 +77,6 @@ expect(quotation.available).to eq false end - it "fails if property not found" do - request_params[:property_id] = 'unknown id' - - subject = described_class.new(request_params) - - response = read_fixture("kigo/success.json") - result = subject.compute_pricing(response) - - expect(result).not_to be_success - expect(result.error.code).to eq :property_not_found - end - - xit "fails if host not found" do - allow(subject).to receive(:host) { nil } - response = read_fixture("kigo/success.json") - result = subject.compute_pricing(response) - - expect(result).not_to be_success - expect(result.error.code).to eq :host_not_found - end - it "returns a quotation with the returned information on success" do response = read_fixture("kigo/success.json") result = subject.compute_pricing(response) diff --git a/spec/lib/concierge/suppliers/poplidays/price_spec.rb b/spec/lib/concierge/suppliers/poplidays/price_spec.rb index 5abe3eddf..ad29941e3 100644 --- a/spec/lib/concierge/suppliers/poplidays/price_spec.rb +++ b/spec/lib/concierge/suppliers/poplidays/price_spec.rb @@ -51,13 +51,6 @@ expect(result.error.code).to eq :connection_timeout end - xit 'fails if host is not found' do - result = subject.quote(params) - - expect(result).not_to be_success - expect(result.error.code).to eq :host_not_found - end - it 'returns the underlying network error if any happened in the call for the quote endpoint' do stub_with_fixture(property_details_endpoint, 'poplidays/property_details.json') stub_call(:post, quote_endpoint) { raise Faraday::TimeoutError } From 15ed5199d3c5ac549bf10de0055411fec524e0c7 Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 19:12:39 +0800 Subject: [PATCH 010/118] Check property supplier too --- apps/api/middlewares/quote_validations.rb | 21 +++++++++++++------ .../api/middlewares/quote_validations_spec.rb | 15 +++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/api/middlewares/quote_validations.rb b/apps/api/middlewares/quote_validations.rb index 014ae192f..c9f3ec288 100644 --- a/apps/api/middlewares/quote_validations.rb +++ b/apps/api/middlewares/quote_validations.rb @@ -26,11 +26,11 @@ def initialize(app) def call(env) @env = env - return app.call(env) unless quote_request? - - return app.call(env) if property_exists? - - [404, {}, ["Property not found on Concierge"]] + if quote_request? && !property_exists? + [404, {}, ["Property not found on Concierge"]] + else + app.call(env) + end end private @@ -42,10 +42,19 @@ def quote_request? def property_exists? body = read_request_body(env) json_payload = json_decode(body) - property = PropertyRepository.identified_by(json_payload.value["property_id"]) + property = PropertyRepository.identified_by(json_payload.value["property_id"]). + from_supplier(supplier) property.count > 0 end + def supplier + SupplierRepository.named supplier_path_params + end + # Returns "supplier_x" from "/supplier_x/quote" + def supplier_path_params + return env['PATH_INFO'][/\/(.*)\/quote/, 1] + end + def read_request_body(env) env["rack.input"].read.tap do env["rack.input"].rewind diff --git a/spec/api/middlewares/quote_validations_spec.rb b/spec/api/middlewares/quote_validations_spec.rb index e8a6d24cb..ed16935ed 100644 --- a/spec/api/middlewares/quote_validations_spec.rb +++ b/spec/api/middlewares/quote_validations_spec.rb @@ -3,6 +3,7 @@ RSpec.describe API::Middlewares::QuoteValidations do include Support::Factories + let!(:supplier) { create_supplier(name: "supplier_x") } let(:quote_request) { { property_id: "023", @@ -27,16 +28,16 @@ describe "Not a quote request" do it "does not touch the request" do - expect(post("/supplierx/cancel", headers)).to eq [200, { "Content-Length" => response.size.to_s }, response] + expect(post("/supplier_x/cancel", headers)).to eq [200, { "Content-Length" => response.size.to_s }, response] end end describe "Property and host exists" do - let!(:host) { create_host(fee_percentage: 7) } + let!(:host) { create_host(fee_percentage: 7, supplier_id: supplier.id) } let!(:property) { create_property(identifier:"023", host_id: host.id) } it "forwards to upstream" do - expect(post("/supplierx/quote", headers)).to eq [200, { "Content-Length" => response.size.to_s }, response] + expect(post("/supplier_x/quote", headers)).to eq [200, { "Content-Length" => response.size.to_s }, response] end end @@ -46,12 +47,14 @@ } before do - PropertyRepository.clear - HostRepository.clear + create_supplier(name: "supplier_y") + host_y = create_host(fee_percentage: 7) + # same identifier, but different supplier + create_property(identifier: "023", host_id: host_y.id) end it "returns 404" do - expect(post("/supplierx/quote", headers)).to eq property_not_found + expect(post("/supplier_x/quote", headers)).to eq property_not_found end end From 9ceebea88e92b822f3ee48b2c2625a5cb0414c8d Mon Sep 17 00:00:00 2001 From: keang Date: Tue, 4 Oct 2016 11:50:35 +0800 Subject: [PATCH 011/118] Remove middlewares --- apps/api/application.rb | 2 - apps/api/middlewares/quote_validations.rb | 65 ----------------- .../api/middlewares/quote_validations_spec.rb | 72 ------------------- 3 files changed, 139 deletions(-) delete mode 100644 apps/api/middlewares/quote_validations.rb delete mode 100644 spec/api/middlewares/quote_validations_spec.rb diff --git a/apps/api/application.rb b/apps/api/application.rb index 453ee823a..309e5ec1e 100644 --- a/apps/api/application.rb +++ b/apps/api/application.rb @@ -3,7 +3,6 @@ require_relative "middlewares/request_logging" require_relative "middlewares/authentication" require_relative "middlewares/roomorama_webhook" -require_relative "middlewares/quote_validations" require_relative "middlewares/request_context" require_relative "views/accept_json" @@ -33,7 +32,6 @@ class Application < Hanami::Application middleware.use API::Middlewares::HealthCheck middleware.use API::Middlewares::Authentication middleware.use API::Middlewares::RoomoramaWebhook - middleware.use API::Middlewares::QuoteValidations middleware.use API::Middlewares::RequestContext view.prepare do diff --git a/apps/api/middlewares/quote_validations.rb b/apps/api/middlewares/quote_validations.rb deleted file mode 100644 index c9f3ec288..000000000 --- a/apps/api/middlewares/quote_validations.rb +++ /dev/null @@ -1,65 +0,0 @@ -require_relative "../../../lib/concierge/json" -require_relative "../../../lib/concierge/safe_access_hash" - -module API::Middlewares - - # +API::Middlewares::QuoteValidations+ - # - # Quotation queries requires that Concierge knows what host_fee to - # append to the response from supplier. Hence we require that the property - # record, and hence the host record, exists on Concierge. If we don't impose - # this check, a quote call to partner would succeed (for active property) and - # Concierge would mistakenly return $0 for host fee to Roomorama. - # - # This should be called after RoomoramaWebhook, so we expect the massaged - # concierge params - class QuoteValidations - - include Concierge::JSON - - attr_reader :app, :env, :webhook_payload - - def initialize(app) - @app = app - end - - def call(env) - @env = env - - if quote_request? && !property_exists? - [404, {}, ["Property not found on Concierge"]] - else - app.call(env) - end - end - - private - - def quote_request? - env['PATH_INFO'].include? 'quote' - end - - def property_exists? - body = read_request_body(env) - json_payload = json_decode(body) - property = PropertyRepository.identified_by(json_payload.value["property_id"]). - from_supplier(supplier) - property.count > 0 - end - - def supplier - SupplierRepository.named supplier_path_params - end - # Returns "supplier_x" from "/supplier_x/quote" - def supplier_path_params - return env['PATH_INFO'][/\/(.*)\/quote/, 1] - end - - def read_request_body(env) - env["rack.input"].read.tap do - env["rack.input"].rewind - end - end - end -end - diff --git a/spec/api/middlewares/quote_validations_spec.rb b/spec/api/middlewares/quote_validations_spec.rb deleted file mode 100644 index ed16935ed..000000000 --- a/spec/api/middlewares/quote_validations_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -require "spec_helper" - -RSpec.describe API::Middlewares::QuoteValidations do - include Support::Factories - - let!(:supplier) { create_supplier(name: "supplier_x") } - let(:quote_request) { - { - property_id: "023", - check_in: "2016-10-01", - check_out: "2016-10-05", - guests: 2 - } - } - - let(:request_body) { StringIO.new(quote_request.to_json) } - let(:app) { Rack::MockRequest.new(subject) } - let(:response) { "OK" } - let(:upstream) { lambda { |env| [200, {}, response] } } - let(:headers) { - { - input: request_body, - "CONTENT_TYPE" => "application/json" - } - } - - subject { described_class.new(upstream) } - - describe "Not a quote request" do - it "does not touch the request" do - expect(post("/supplier_x/cancel", headers)).to eq [200, { "Content-Length" => response.size.to_s }, response] - end - end - - describe "Property and host exists" do - let!(:host) { create_host(fee_percentage: 7, supplier_id: supplier.id) } - let!(:property) { create_property(identifier:"023", host_id: host.id) } - - it "forwards to upstream" do - expect(post("/supplier_x/quote", headers)).to eq [200, { "Content-Length" => response.size.to_s }, response] - end - end - - describe "Unknown property and host" do - let(:property_not_found) { - [404, { "Content-Length" => "31" }, "Property not found on Concierge"] - } - - before do - create_supplier(name: "supplier_y") - host_y = create_host(fee_percentage: 7) - # same identifier, but different supplier - create_property(identifier: "023", host_id: host_y.id) - end - - it "returns 404" do - expect(post("/supplier_x/quote", headers)).to eq property_not_found - end - end - - def post(path, params = {}) - to_rack(app.post(path, params)) - end - - def to_rack(response) - [ - response.status, - response.header, - response.body - ] - end -end From 3fb9c8fed6764631c2eccaa5a1ee7c4d0b662a4c Mon Sep 17 00:00:00 2001 From: keang Date: Tue, 4 Oct 2016 11:50:51 +0800 Subject: [PATCH 012/118] Fix specs --- apps/api/controllers/quote.rb | 10 ++++++++++ spec/api/controllers/ciirus/quote_spec.rb | 5 +++-- spec/api/controllers/jtb/quote_spec.rb | 11 ++++++++--- spec/api/controllers/kigo/legacy/quote_spec.rb | 3 ++- spec/api/controllers/kigo/quote_spec.rb | 3 ++- spec/api/controllers/saw/quote_spec.rb | 7 ++++++- .../shared/multi_unit_quote_validations.rb | 2 +- spec/api/controllers/shared/quote_validations.rb | 10 ++++++++++ spec/api/controllers/waytostay/quote_spec.rb | 5 +++-- 9 files changed, 45 insertions(+), 11 deletions(-) diff --git a/apps/api/controllers/quote.rb b/apps/api/controllers/quote.rb index ed1a4a2b5..820a2efbc 100644 --- a/apps/api/controllers/quote.rb +++ b/apps/api/controllers/quote.rb @@ -46,6 +46,10 @@ def self.included(base) def call(params) if params.valid? + unless property_exists?(params[:property_id]) + return status 404, invalid_request("Property not found") + end + quotation_result = quote_price(params) if quotation_result.success? @@ -78,6 +82,12 @@ def announce_error(result) }) end + def property_exists?(id) + supplier = SupplierRepository.named supplier_name + ! PropertyRepository.identified_by(id). + from_supplier(supplier).first.nil? + end + # Get the quote result from client implementations. # # The +params+ argument given is an instance of +API::Controllers::Params::Quote+. diff --git a/spec/api/controllers/ciirus/quote_spec.rb b/spec/api/controllers/ciirus/quote_spec.rb index 65a2c3332..921827a45 100644 --- a/spec/api/controllers/ciirus/quote_spec.rb +++ b/spec/api/controllers/ciirus/quote_spec.rb @@ -8,10 +8,11 @@ include Support::SOAPStubbing include Support::Factories - let!(:host) { create_host(fee_percentage: 7) } + let!(:supplier) { create_supplier(name: Ciirus::Client::SUPPLIER_NAME) } + let!(:host) { create_host(fee_percentage: 7, supplier_id: supplier.id) } let!(:property) { create_property(identifier: '38180', host_id: host.id) } let(:params) { - { property_id: '38180', check_in: '2016-05-01', check_out: '2016-05-12', guests: 3 } + { property_id: property.identifier, check_in: '2016-05-01', check_out: '2016-05-12', guests: 3 } } let(:success_response) { read_fixture('ciirus/responses/property_quote_response.xml') } diff --git a/spec/api/controllers/jtb/quote_spec.rb b/spec/api/controllers/jtb/quote_spec.rb index 8c9db8191..1bb7577f3 100644 --- a/spec/api/controllers/jtb/quote_spec.rb +++ b/spec/api/controllers/jtb/quote_spec.rb @@ -5,12 +5,17 @@ RSpec.describe API::Controllers::JTB::Quote do include Support::HTTPStubbing include Support::Fixtures + include Support::Factories + + let!(:supplier) { create_supplier(name: JTB::Client::SUPPLIER_NAME) } + let(:host) { create_host(supplier_id: supplier.id) } + let(:property) { create_property(identifier: "J123", host_id: host.id) } it_behaves_like "performing multi unit parameter validations", controller_generator: -> { described_class.new } it_behaves_like "external error reporting" do let(:params) { - { property_id: "321", unit_id: "123", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } + { property_id: property.identifier, unit_id: "123", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } } let(:supplier_name) { "JTB" } let(:error_code) { "savon_erorr" } @@ -23,7 +28,7 @@ def provoke_failure! describe "#call" do let(:params) { - { property_id: "J123", unit_id: "123J", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } + { property_id: property.identifier, unit_id: "123J", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } } it "indicates the unit is unavailable in case there are no rate plans" do @@ -44,7 +49,7 @@ def provoke_failure! context "when stay length is > 15 days" do let(:params) { - { property_id: "J123", unit_id: "123J", check_in: "2016-02-22", check_out: "2016-03-25", guests: 2 } + { property_id: property.identifier, unit_id: "123J", check_in: "2016-02-22", check_out: "2016-03-25", guests: 2 } } it "respond with the stay_too_long error" do response = parse_response(subject.call(params)) diff --git a/spec/api/controllers/kigo/legacy/quote_spec.rb b/spec/api/controllers/kigo/legacy/quote_spec.rb index e832c74d2..f8d00aaf1 100644 --- a/spec/api/controllers/kigo/legacy/quote_spec.rb +++ b/spec/api/controllers/kigo/legacy/quote_spec.rb @@ -8,7 +8,8 @@ include Support::Fixtures include Support::Factories - let!(:host) { create_host(fee_percentage: 7.0) } + let!(:supplier) { create_supplier(name: Kigo::Legacy::SUPPLIER_NAME) } + let!(:host) { create_host(supplier_id: supplier.id, fee_percentage: 7.0) } let!(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) { diff --git a/spec/api/controllers/kigo/quote_spec.rb b/spec/api/controllers/kigo/quote_spec.rb index bbed1c7da..c597d0082 100644 --- a/spec/api/controllers/kigo/quote_spec.rb +++ b/spec/api/controllers/kigo/quote_spec.rb @@ -8,7 +8,8 @@ include Support::Fixtures include Support::Factories - let!(:host) { create_host(fee_percentage: 7.0) } + let!(:supplier) { create_supplier(name: Kigo::Client::SUPPLIER_NAME) } + let!(:host) { create_host(fee_percentage: 7.0, supplier_id: supplier.id) } let!(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) { diff --git a/spec/api/controllers/saw/quote_spec.rb b/spec/api/controllers/saw/quote_spec.rb index b6cb37812..bd278fed0 100644 --- a/spec/api/controllers/saw/quote_spec.rb +++ b/spec/api/controllers/saw/quote_spec.rb @@ -5,11 +5,16 @@ RSpec.describe API::Controllers::SAW::Quote do include Support::HTTPStubbing include Support::Fixtures + include Support::Factories include Support::SAW::MockRequest + let!(:supplier) { create_supplier(name: SAW::Client::SUPPLIER_NAME) } + let!(:host) { create_host(fee_percentage: 7.0, supplier_id: supplier.id) } + let!(:property) { create_property(identifier: "567", host_id: host.id) } + let(:params) do { - property_id: "1", + property_id: property.identifier, unit_id: '10612', check_in: "2015-02-26", check_out: "2015-02-28", diff --git a/spec/api/controllers/shared/multi_unit_quote_validations.rb b/spec/api/controllers/shared/multi_unit_quote_validations.rb index 51d0d24ce..398504363 100644 --- a/spec/api/controllers/shared/multi_unit_quote_validations.rb +++ b/spec/api/controllers/shared/multi_unit_quote_validations.rb @@ -4,7 +4,7 @@ RSpec.shared_examples "performing multi unit parameter validations" do |controller_generator:| let(:params) { - { property_id: "A123", check_in: "2016-03-22", check_out: "2016-03-24", guests: 2, unit_id: "EX333" } + { property_id: property.identifier, check_in: "2016-03-22", check_out: "2016-03-24", guests: 2, unit_id: "EX333" } } it_behaves_like "performing parameter validations", controller_generator: -> { described_class.new } do diff --git a/spec/api/controllers/shared/quote_validations.rb b/spec/api/controllers/shared/quote_validations.rb index 26303d336..7a77c11fa 100644 --- a/spec/api/controllers/shared/quote_validations.rb +++ b/spec/api/controllers/shared/quote_validations.rb @@ -84,6 +84,16 @@ expect(response.body["errors"]).to eq({ "quote" => "Could not quote price with remote supplier" }) end + it "returns 404 if property is not found in the database" do + controller = controller_generator.call + allow(controller).to receive(:property_exists?) { false } + + response = call(controller, valid_params) + expect(response.status).to eq 404 + expect(response.body["status"]).to eq "error" + expect(response.body["errors"]).to eq("Property not found") + end + private def call(controller, params) diff --git a/spec/api/controllers/waytostay/quote_spec.rb b/spec/api/controllers/waytostay/quote_spec.rb index f58576bb0..b0658535c 100644 --- a/spec/api/controllers/waytostay/quote_spec.rb +++ b/spec/api/controllers/waytostay/quote_spec.rb @@ -7,7 +7,8 @@ include Support::HTTPStubbing include Support::Factories - let(:host) { create_host(fee_percentage: 7) } + let!(:supplier) { create_supplier(name: Waytostay::Client::SUPPLIER_NAME) } + let(:host) { create_host(supplier_id: supplier.id, fee_percentage: 7) } let(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) { { property_id: property.identifier, check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } @@ -19,7 +20,7 @@ it_behaves_like "external error reporting" do let(:params) { - { property_id: "321", unit_id: "123", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } + { property_id: property.identifier, unit_id: "123", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } } let(:supplier_name) { "WayToStay" } let(:error_code) { "savon_erorr" } From c9b86f28ed36f2e1d72f530551aef8e8ec39b1dd Mon Sep 17 00:00:00 2001 From: Keang Date: Tue, 4 Oct 2016 14:25:22 +0800 Subject: [PATCH 013/118] Add changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0514ff9e5..49aff8e73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ This file summarises the most important changes that went live on each release of Concierge. Please check the Wiki entry on the release process to understand how this file is formatted and how the process works. +## Unreleased +### Changed +- Abstract host fee calculation from suppliers to entity level +- Return 404 for attempts to quote a property not in records + ## [0.11.6] - 2016-10-03 ### Fixed - Proper order of table columns for sync_process/index page From bb2125a729fac561be75f02f82a7e4f1e919e10b Mon Sep 17 00:00:00 2001 From: Keang Date: Tue, 4 Oct 2016 14:56:01 +0800 Subject: [PATCH 014/118] Lazy variables --- spec/api/controllers/jtb/quote_spec.rb | 2 +- spec/api/controllers/kigo/legacy/quote_spec.rb | 6 +++--- spec/api/controllers/saw/quote_spec.rb | 6 +++--- spec/api/controllers/waytostay/quote_spec.rb | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/api/controllers/jtb/quote_spec.rb b/spec/api/controllers/jtb/quote_spec.rb index 1bb7577f3..4b6287510 100644 --- a/spec/api/controllers/jtb/quote_spec.rb +++ b/spec/api/controllers/jtb/quote_spec.rb @@ -7,7 +7,7 @@ include Support::Fixtures include Support::Factories - let!(:supplier) { create_supplier(name: JTB::Client::SUPPLIER_NAME) } + let(:supplier) { create_supplier(name: JTB::Client::SUPPLIER_NAME) } let(:host) { create_host(supplier_id: supplier.id) } let(:property) { create_property(identifier: "J123", host_id: host.id) } diff --git a/spec/api/controllers/kigo/legacy/quote_spec.rb b/spec/api/controllers/kigo/legacy/quote_spec.rb index f8d00aaf1..a3bf236e3 100644 --- a/spec/api/controllers/kigo/legacy/quote_spec.rb +++ b/spec/api/controllers/kigo/legacy/quote_spec.rb @@ -8,9 +8,9 @@ include Support::Fixtures include Support::Factories - let!(:supplier) { create_supplier(name: Kigo::Legacy::SUPPLIER_NAME) } - let!(:host) { create_host(supplier_id: supplier.id, fee_percentage: 7.0) } - let!(:property) { create_property(identifier: "567", host_id: host.id) } + let(:supplier) { create_supplier(name: Kigo::Legacy::SUPPLIER_NAME) } + let(:host) { create_host(supplier_id: supplier.id, fee_percentage: 7.0) } + let(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) { { property_id: property.identifier, check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } diff --git a/spec/api/controllers/saw/quote_spec.rb b/spec/api/controllers/saw/quote_spec.rb index bd278fed0..e9195f921 100644 --- a/spec/api/controllers/saw/quote_spec.rb +++ b/spec/api/controllers/saw/quote_spec.rb @@ -8,9 +8,9 @@ include Support::Factories include Support::SAW::MockRequest - let!(:supplier) { create_supplier(name: SAW::Client::SUPPLIER_NAME) } - let!(:host) { create_host(fee_percentage: 7.0, supplier_id: supplier.id) } - let!(:property) { create_property(identifier: "567", host_id: host.id) } + let(:supplier) { create_supplier(name: SAW::Client::SUPPLIER_NAME) } + let(:host) { create_host(fee_percentage: 7.0, supplier_id: supplier.id) } + let(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) do { diff --git a/spec/api/controllers/waytostay/quote_spec.rb b/spec/api/controllers/waytostay/quote_spec.rb index b0658535c..419a169b1 100644 --- a/spec/api/controllers/waytostay/quote_spec.rb +++ b/spec/api/controllers/waytostay/quote_spec.rb @@ -7,7 +7,7 @@ include Support::HTTPStubbing include Support::Factories - let!(:supplier) { create_supplier(name: Waytostay::Client::SUPPLIER_NAME) } + let(:supplier) { create_supplier(name: Waytostay::Client::SUPPLIER_NAME) } let(:host) { create_host(supplier_id: supplier.id, fee_percentage: 7) } let(:property) { create_property(identifier: "567", host_id: host.id) } let(:params) { From e600f2e920ef5eeec46198bb1d68e141a50f55e7 Mon Sep 17 00:00:00 2001 From: Keang Date: Tue, 4 Oct 2016 15:14:09 +0800 Subject: [PATCH 015/118] Catch nil supplier --- apps/api/controllers/quote.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/api/controllers/quote.rb b/apps/api/controllers/quote.rb index 820a2efbc..e906fbfd5 100644 --- a/apps/api/controllers/quote.rb +++ b/apps/api/controllers/quote.rb @@ -46,9 +46,8 @@ def self.included(base) def call(params) if params.valid? - unless property_exists?(params[:property_id]) - return status 404, invalid_request("Property not found") - end + return status 500, error_response("No supplier record in database.") unless supplier + return status 404, error_response("Property not found") unless property_exists?(params[:property_id]) quotation_result = quote_price(params) @@ -58,16 +57,16 @@ def call(params) else announce_error(quotation_result) error_message = quotation_result.error.data || { quote: GENERIC_ERROR } - status 503, invalid_request(error_message) + status 503, error_response(error_message) end else - status 422, invalid_request(params.error_messages) + status 422, error_response(params.error_messages) end end private - def invalid_request(errors) + def error_response(errors) response = { status: "error" }.merge!(errors: errors) json_encode(response) end @@ -83,11 +82,14 @@ def announce_error(result) end def property_exists?(id) - supplier = SupplierRepository.named supplier_name ! PropertyRepository.identified_by(id). from_supplier(supplier).first.nil? end + def supplier + SupplierRepository.named supplier_name + end + # Get the quote result from client implementations. # # The +params+ argument given is an instance of +API::Controllers::Params::Quote+. @@ -105,8 +107,9 @@ def quote_price(params) raise NotImplementedError end - # This is used when reporting errors from the supplier. - # Should return a string + # Should return a string. + # This is used when reporting error and + # searching for property def supplier_name raise NotImplementedError end From e2efcbc087ad3e7c2ad9d5b8e6a6c7aa0425324f Mon Sep 17 00:00:00 2001 From: Keang Date: Tue, 4 Oct 2016 15:22:12 +0800 Subject: [PATCH 016/118] Memoize --- apps/api/controllers/quote.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/controllers/quote.rb b/apps/api/controllers/quote.rb index e906fbfd5..839947241 100644 --- a/apps/api/controllers/quote.rb +++ b/apps/api/controllers/quote.rb @@ -87,7 +87,7 @@ def property_exists?(id) end def supplier - SupplierRepository.named supplier_name + @supplier ||= SupplierRepository.named supplier_name end # Get the quote result from client implementations. From 29cb1b412b07bf5e9904589d4f2572958250a0fa Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 26 Aug 2016 12:16:16 +0600 Subject: [PATCH 017/118] RU: update .env.example --- .env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.example b/.env.example index e5b05d155..86e065ad0 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,7 @@ ROOMORAMA_SECRET_ATLEISURE=xxx ROOMORAMA_SECRET_POPLIDAYS=xxx ROOMORAMA_SECRET_CIIRUS=xxx ROOMORAMA_SECRET_SAW=xxx +ROOMORAMA_SECRET_RENTALS_UNITED=xxx ROLLBAR_ACCESS_TOKEN=not_used_in_development CONCIERGE_WEB_APP_SECRET=12345 SERVE_STATIC_ASSETS=true From e8c6489e092e2907f912517a53484c852622dc20 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 26 Aug 2016 12:16:37 +0600 Subject: [PATCH 018/118] RU: update credentials --- config/credentials/production.yml | 5 +++++ config/credentials/staging.yml | 5 +++++ config/credentials/test.yml | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/config/credentials/production.yml b/config/credentials/production.yml index 4eb61248c..43450afd7 100644 --- a/config/credentials/production.yml +++ b/config/credentials/production.yml @@ -44,3 +44,8 @@ poplidays: url: <%= ENV["POPLIDAYS_URL"] %> client_key: <%= ENV["POPLIDAYS_CLIENT_KEY"] %> passphrase: <%= ENV["POPLIDAYS_PASSPHRASE"] %> + +rentals_united: + username: <%= ENV["RENTALS_UNITED_USERNAME"] %> + password: <%= ENV["RENTALS_UNITED_PASSWORD"] %> + url: <%= ENV["RENTALS_UNITED_URL"] %> diff --git a/config/credentials/staging.yml b/config/credentials/staging.yml index e0bcaa086..812599d99 100644 --- a/config/credentials/staging.yml +++ b/config/credentials/staging.yml @@ -44,3 +44,8 @@ poplidays: url: <%= ENV["POPLIDAYS_URL"] %> client_key: <%= ENV["POPLIDAYS_CLIENT_KEY"] %> passphrase: <%= ENV["POPLIDAYS_PASSPHRASE"] %> + +rentals_united: + username: <%= ENV["RENTALS_UNITED_USERNAME"] %> + password: <%= ENV["RENTALS_UNITED_PASSWORD"] %> + url: <%= ENV["RENTALS_UNITED_URL"] %> diff --git a/config/credentials/test.yml b/config/credentials/test.yml index fa340198a..a7e980084 100644 --- a/config/credentials/test.yml +++ b/config/credentials/test.yml @@ -52,3 +52,8 @@ poplidays: url: 'www.example.org' client_key: "123" passphrase: "123" + +rentals_united: + username: "roomorama-user" + password: "roomorama-pass" + url: "http://www.example.org" From 29ae9b6afbf6c3a7208c13af154e737f039e767b Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 26 Aug 2016 12:17:34 +0600 Subject: [PATCH 019/118] RU: update validate_supplier_credentials.rb --- .../validate_supplier_credentials.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/api/config/initializers/validate_supplier_credentials.rb b/apps/api/config/initializers/validate_supplier_credentials.rb index 3b0d22fe1..5b930e4cc 100644 --- a/apps/api/config/initializers/validate_supplier_credentials.rb +++ b/apps/api/config/initializers/validate_supplier_credentials.rb @@ -2,13 +2,14 @@ if enforce_on_envs.include?(Hanami.env) Concierge::Credentials.validate_credentials!({ - atleisure: %w(username password test_mode), - jtb: %w(id user password company url), - kigo: %w(subscription_key), - kigolegacy: %w(username password), - waytostay: %w(client_id client_secret url token_url), - ciirus: %w(url username password), - saw: %w(username password url), - poplidays: %w(url client_key passphrase) + atleisure: %w(username password test_mode), + jtb: %w(id user password company url), + kigo: %w(subscription_key), + kigolegacy: %w(username password), + waytostay: %w(client_id client_secret url token_url), + ciirus: %w(url username password), + saw: %w(username password url), + poplidays: %w(url client_key passphrase), + rentals_united: %w(username password url) }) end From 845f375d73c0ceaba58588f828a0365118954fb2 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 26 Aug 2016 12:18:18 +0600 Subject: [PATCH 020/118] RU: update config/environment_variables.yml --- apps/api/config/environment_variables.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/api/config/environment_variables.yml b/apps/api/config/environment_variables.yml index a5dc050f4..a7140cc80 100644 --- a/apps/api/config/environment_variables.yml +++ b/apps/api/config/environment_variables.yml @@ -6,4 +6,5 @@ - ROOMORAMA_SECRET_WAYTOSTAY - ROOMORAMA_SECRET_CIIRUS - ROOMORAMA_SECRET_SAW +- ROOMORAMA_SECRET_RENTALS_UNITED - ZENDESK_NOTIFY_URL From cf7fb1d86bb3527f01d08b57ffc2eb199274aee9 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 26 Aug 2016 12:19:33 +0600 Subject: [PATCH 021/118] RU: update middlewares/authentication --- apps/api/middlewares/authentication.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/api/middlewares/authentication.rb b/apps/api/middlewares/authentication.rb index eec19c577..5d8921869 100644 --- a/apps/api/middlewares/authentication.rb +++ b/apps/api/middlewares/authentication.rb @@ -46,14 +46,15 @@ class Authentication # secrets.for(request_path) # => X32842I class Secrets APP_SECRETS = { - "/jtb" => ENV["ROOMORAMA_SECRET_JTB"], - "/kigo/legacy" => ENV["ROOMORAMA_SECRET_KIGO_LEGACY"], - "/kigo" => ENV["ROOMORAMA_SECRET_KIGO"], - "/atleisure" => ENV["ROOMORAMA_SECRET_ATLEISURE"], - "/poplidays" => ENV["ROOMORAMA_SECRET_POPLIDAYS"], - "/waytostay" => ENV["ROOMORAMA_SECRET_WAYTOSTAY"], - "/ciirus" => ENV["ROOMORAMA_SECRET_CIIRUS"], - "/saw" => ENV["ROOMORAMA_SECRET_SAW"] + "/jtb" => ENV["ROOMORAMA_SECRET_JTB"], + "/kigo/legacy" => ENV["ROOMORAMA_SECRET_KIGO_LEGACY"], + "/kigo" => ENV["ROOMORAMA_SECRET_KIGO"], + "/atleisure" => ENV["ROOMORAMA_SECRET_ATLEISURE"], + "/poplidays" => ENV["ROOMORAMA_SECRET_POPLIDAYS"], + "/waytostay" => ENV["ROOMORAMA_SECRET_WAYTOSTAY"], + "/ciirus" => ENV["ROOMORAMA_SECRET_CIIRUS"], + "/saw" => ENV["ROOMORAMA_SECRET_SAW"], + "/rentals_united" => ENV["ROOMORAMA_SECRET_RENTALS_UNITED"] } attr_reader :mapping From 57264932de06bc8df33fa06fc728afd6d5be6706 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 26 Aug 2016 12:36:46 +0600 Subject: [PATCH 022/118] RU: add metadata worker class --- .../suppliers/rentals_united/metadata.rb | 32 +++++++++++++++++++ config/suppliers.yml | 5 +++ 2 files changed, 37 insertions(+) create mode 100644 apps/workers/suppliers/rentals_united/metadata.rb diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb new file mode 100644 index 000000000..dad10de84 --- /dev/null +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -0,0 +1,32 @@ +module Workers::Suppliers::RentalsUnited + # +Workers::Suppliers::RentalsUnited+ + # + # Performs synchronisation with supplier + class Metadata + attr_reader :synchronisation, :host + + def initialize(host) + @host = host + @synchronisation = Workers::PropertySynchronisation.new(host) + end + + def perform + end + + private + + def importer + @properties ||= ::RentalsUnited::Importer.new(credentials) + end + + def credentials + @credentials ||= Concierge::Credentials.for( + ::RentalsUnited::Client::SUPPLIER_NAME + ) + end + end +end + +Concierge::Announcer.on("metadata.RentalsUnited") do |host| + Workers::Suppliers::RentalsUnited::Metadata.new(host).perform +end diff --git a/config/suppliers.yml b/config/suppliers.yml index e52aac053..bc190cbc9 100644 --- a/config/suppliers.yml +++ b/config/suppliers.yml @@ -57,3 +57,8 @@ Poplidays: every: "1d" availabilities: every: "5h" + +RentalsUnited: + workers: + metadata: + every: "1d" From 797d21b1bf37617e507e422e36b033eae88a9a99 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 31 Aug 2016 17:43:09 +0600 Subject: [PATCH 023/118] RU: update worker class to follow the latest changes from development --- apps/workers/suppliers/rentals_united/metadata.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index dad10de84..e6b0ea63b 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -27,6 +27,7 @@ def credentials end end -Concierge::Announcer.on("metadata.RentalsUnited") do |host| +Concierge::Announcer.on("metadata.RentalsUnited") do |host, args| Workers::Suppliers::RentalsUnited::Metadata.new(host).perform + Result.new({}) end From 6d56c95d73d7b347b335fed3e8187bf9c8e480da Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 2 Sep 2016 11:33:27 +0600 Subject: [PATCH 024/118] RU: worker and data fetcher classes --- .../suppliers/rentals_united/metadata.rb | 59 ++++++++++ .../rentals_united/commands/base_fetcher.rb | 78 +++++++++++++ .../rentals_united/commands/cities_fetcher.rb | 43 +++++++ .../commands/property_ids_fetcher.rb | 56 ++++++++++ .../suppliers/rentals_united/entities/city.rb | 20 ++++ .../suppliers/rentals_united/importer.rb | 36 ++++++ .../suppliers/rentals_united/mappers/city.rb | 30 +++++ .../rentals_united/payload_builder.rb | 35 ++++++ .../rentals_united/response_parser.rb | 30 +++++ .../templates/cities_fetch.xml.erb | 6 + .../templates/properties_fetch.xml.erb | 8 ++ spec/fixtures/rentals_united/bad_xml.xml | 1 + .../rentals_united/cities/empty_list.xml | 4 + .../rentals_united/cities/error_status.xml | 3 + .../rentals_united/cities/multiple_cities.xml | 8 ++ .../rentals_united/cities/one_city.xml | 6 + .../rentals_united/properties/empty_list.xml | 4 + .../properties/error_status.xml | 3 + .../properties/multiple_properties.xml | 27 +++++ .../properties/one_property.xml | 16 +++ .../commands/cities_fetcher_spec.rb | 105 ++++++++++++++++++ .../commands/property_ids_fetcher_spec.rb | 100 +++++++++++++++++ .../suppliers/rentals_united/importer_spec.rb | 29 +++++ .../rentals_united/payload_builder_spec.rb | 47 ++++++++ .../rentals_united/response_parser_spec.rb | 26 +++++ 25 files changed, 780 insertions(+) create mode 100644 lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/cities_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/city.rb create mode 100644 lib/concierge/suppliers/rentals_united/importer.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/city.rb create mode 100644 lib/concierge/suppliers/rentals_united/payload_builder.rb create mode 100644 lib/concierge/suppliers/rentals_united/response_parser.rb create mode 100644 lib/concierge/suppliers/rentals_united/templates/cities_fetch.xml.erb create mode 100644 lib/concierge/suppliers/rentals_united/templates/properties_fetch.xml.erb create mode 100644 spec/fixtures/rentals_united/bad_xml.xml create mode 100644 spec/fixtures/rentals_united/cities/empty_list.xml create mode 100644 spec/fixtures/rentals_united/cities/error_status.xml create mode 100644 spec/fixtures/rentals_united/cities/multiple_cities.xml create mode 100644 spec/fixtures/rentals_united/cities/one_city.xml create mode 100644 spec/fixtures/rentals_united/properties/empty_list.xml create mode 100644 spec/fixtures/rentals_united/properties/error_status.xml create mode 100644 spec/fixtures/rentals_united/properties/multiple_properties.xml create mode 100644 spec/fixtures/rentals_united/properties/one_property.xml create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/cities_fetcher_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/importer_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/response_parser_spec.rb diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index e6b0ea63b..09ab37879 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -11,6 +11,19 @@ def initialize(host) end def perform + result = fetch_cities + return unless result.success? + + cities = result.value + cities.each do |city| + result = fetch_property_ids(city.location_id) + return unless result.success? + + property_ids = result.value + property_ids.each do |property_id| + result = fetch_property(property_id) + end + end end private @@ -24,6 +37,52 @@ def credentials ::RentalsUnited::Client::SUPPLIER_NAME ) end + + def fetch_cities + announce_error("Failed to fetch locations") do + importer.fetch_cities + end + end + + def fetch_property_ids(location_id) + announce_error("Failed to fetch properties for location `#{location_id}`") do + importer.fetch_property_ids(location_id) + end + end + + def announce_error(message) + yield.tap do |result| + announce_context_error(message) unless result.success? + end + end + + def report_error(message) + yield.tap do |result| + augment_context_error(message) unless result.success? + end + end + + def augment_context_error(message) + message = { + label: 'Synchronisation Failure', + message: message, + backtrace: caller + } + context = Concierge::Context::Message.new(message) + Concierge.context.augment(context) + end + + def announce_context_error(message, result) + augment_context_error(message) + + Concierge::Announcer.trigger(Concierge::Errors::EXTERNAL_ERROR, { + operation: 'sync', + supplier: Ciirus::Client::SUPPLIER_NAME, + code: result.error.code, + context: Concierge.context.to_h, + happened_at: Time.now + }) + end end end diff --git a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb new file mode 100644 index 000000000..083dfeceb --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb @@ -0,0 +1,78 @@ +module RentalsUnited + module Commands + class BaseFetcher + attr_reader :credentials + + def initialize(credentials) + @credentials = credentials + end + + def payload_builder + @payload_builder ||= RentalsUnited::PayloadBuilder.new(credentials) + end + + def response_parser + @response_parser ||= RentalsUnited::ResponseParser.new + end + + def http + @http_client ||= Concierge::HTTPClient.new(credentials.url) + end + + def headers + { "Content-Type" => "application/xml" } + end + + def get_status(hash, root_tag_name) + hash.get("#{root_tag_name}.Status") + end + + def get_status_code(status) + status.attributes["ID"] + end + + def valid_status?(hash, root_tag_name) + status = get_status(hash, root_tag_name) + + return false unless status + + get_status_code(status) == "0" + end + + def error_result(hash, root_tag_name) + status = get_status(hash, root_tag_name) + + if status + code = get_status_code(status) + description = nil + + augment_with_error(code, description, caller) + Result.error(code) + else + code = :unrecognised_response + unrecognised_response_event(caller) + end + Result.error(code) + end + + def augment_with_error(code, description, backtrace) + message = "Response indicating the Status with ID `#{code}`, and description `#{description}`" + mismatch(message, backtrace) + end + + def mismatch(message, backtrace) + response_mismatch = Concierge::Context::ResponseMismatch.new( + message: message, + backtrace: backtrace + ) + + Concierge.context.augment(response_mismatch) + end + + def unrecognised_response_event(backtrace) + message = "Error response could not be recognised (no `Status` tag in the response)" + mismatch(message, backtrace) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/cities_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/cities_fetcher.rb new file mode 100644 index 000000000..ca61ad6ad --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/cities_fetcher.rb @@ -0,0 +1,43 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::CitiesFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # cities from RentalsUnited, parsing the response, and building + # +Result+ object. + class CitiesFetcher < BaseFetcher + attr_reader :location + + ROOT_TAG = "Pull_ListCitiesProps_RS" + + # Retrieves cities with active properties. + # + # Cities without active properties are ignored. + def fetch_cities + payload = payload_builder.build_cities_fetch_payload + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_cities(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_cities(hash) + cities = hash.get("#{ROOT_TAG}.CitiesProps.CityProps") + return [] unless cities + + Array(cities).map do |city| + mapper = RentalsUnited::Mappers::City.new(city) + mapper.build + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb new file mode 100644 index 000000000..858fe4dbc --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb @@ -0,0 +1,56 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::PropertyIdsFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # property ids from RentalsUnited, parsing the response, and building + # +Result+ object. + class PropertyIdsFetcher < BaseFetcher + attr_reader :location_id + + ROOT_TAG = "Pull_ListProp_RS" + + # Initialize +PropertyIdsFetcher+ command. + # + # Arguments + # + # * +credentials+ + # * +location_id+ + # + # Usage: + # + # RentalsUnited::Commands::PropertyIdsFetcher.new( + # credentials, + # location_id + # ) + def initialize(credentials, location_id) + super(credentials) + + @location_id = location_id + end + + def fetch_property_ids + payload = payload_builder.build_property_ids_fetch_payload(location_id) + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_property_ids(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_property_ids(hash) + properties = hash.get("#{ROOT_TAG}.Properties.Property") + return [] unless properties + + Array(properties).map { |property| property["ID"] } + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/entities/city.rb b/lib/concierge/suppliers/rentals_united/entities/city.rb new file mode 100644 index 000000000..8e89cdfe6 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/city.rb @@ -0,0 +1,20 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::City+ + # + # This entity represents a city object + # + # Attributes + # + # +location_id+ - city location id + # +properties_count+ - number of properties in city + class City + attr_reader :location_id, :properties_count + + def initialize(location_id:, properties_count:) + @location_id = location_id + @properties_count = properties_count + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb new file mode 100644 index 000000000..9784cac79 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -0,0 +1,36 @@ +module RentalsUnited + # +RentalsUnited::Importer+ + # + # This class wraps supplier API and provides data for building properties. + # + # Usage + # + # importer = RentalsUnited::Importer.new(credentials) + class Importer + + attr_reader :credentials + + def initialize(credentials) + @credentials = credentials + end + + # Retrieves cities with active properties. + # + # Cities without active properties will be filtered out. + def fetch_cities + cities_fetcher = Commands::CitiesFetcher.new(credentials) + cities_fetcher.fetch_cities + end + + # Retrieves property ids by location id. + # + # IDs of properties which are no longer available will be filtered out. + def fetch_property_ids(location_id) + properties_fetcher = Commands::PropertyIdsFetcher.new( + credentials, + location_id + ) + properties_fetcher.fetch_property_ids + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/city.rb b/lib/concierge/suppliers/rentals_united/mappers/city.rb new file mode 100644 index 000000000..4a65214db --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/city.rb @@ -0,0 +1,30 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::City+ + # + # This class is responsible for building a +RentalsUnited::Entities::City+ + # object from a hash which was fetched from the RentalsUnited API. + class City + attr_reader :city + + # Initialize +RentalsUnited::Mappers::City+ + # + # Arguments: + # + # * +city+ [Nori::StringWithAttributes] city object + def initialize(city) + @city = city + end + + # Builds a city + # + # Returns [RentalsUnited::Entities::City] + def build + Entities::City.new( + location_id: city.attributes["LocationID"], + properties_count: city.to_i + ) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb new file mode 100644 index 000000000..1a85aad79 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -0,0 +1,35 @@ +require 'tilt' + +module RentalsUnited + # +RentalsUnited::PayloadBuilder+ + # + # This class builds XML payloads for all RentalsUnited endpoints. + class PayloadBuilder + TEMPLATES_PATH = "lib/concierge/suppliers/rentals_united/templates" + + attr_reader :credentials + + def initialize(credentials) + @credentials = credentials + end + + def build_property_ids_fetch_payload(location_id) + template_locals = { + credentials: credentials, + location_id: location_id + } + render(:properties_fetch, template_locals) + end + + def build_cities_fetch_payload + template_locals = { credentials: credentials } + render(:cities_fetch, template_locals) + end + + private + def render(template_name, local_vars) + path = Hanami.root.join(TEMPLATES_PATH, "#{template_name}.xml.erb") + Tilt.new(path).render(Object.new, local_vars) + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/response_parser.rb b/lib/concierge/suppliers/rentals_united/response_parser.rb new file mode 100644 index 000000000..97844860d --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/response_parser.rb @@ -0,0 +1,30 @@ +module RentalsUnited + # +RentalsUnited::ResponseParser+ + # + # This class is intended to convert all response data from RentalsUnited API + # to a hash. + # +Concierge::SafeAccessHash+ is used as hash implemetation to provide + # safe access to hash keys and values + class ResponseParser + # Convert +Result+ response to a safe hash. + # +Nori+ library is used as XML to Hash translator + # + # Example: + # + # hash = ResponseParser.to_hash(http_response) + # + # Returns +Concierge::SafeAccessHash+ object + def to_hash(result_body) + safe_hash(parse_hash(result_body)) + end + + private + def safe_hash(usual_hash) + Concierge::SafeAccessHash.new(usual_hash) + end + + def parse_hash(response) + Nori.new.parse(response) + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/templates/cities_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/cities_fetch.xml.erb new file mode 100644 index 000000000..7b7ca3948 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/cities_fetch.xml.erb @@ -0,0 +1,6 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + diff --git a/lib/concierge/suppliers/rentals_united/templates/properties_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/properties_fetch.xml.erb new file mode 100644 index 000000000..ca49b31f3 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/properties_fetch.xml.erb @@ -0,0 +1,8 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + + <%= location_id %> + diff --git a/spec/fixtures/rentals_united/bad_xml.xml b/spec/fixtures/rentals_united/bad_xml.xml new file mode 100644 index 000000000..2d812ae30 --- /dev/null +++ b/spec/fixtures/rentals_united/bad_xml.xml @@ -0,0 +1 @@ +Bad xml diff --git a/spec/fixtures/rentals_united/cities/empty_list.xml b/spec/fixtures/rentals_united/cities/empty_list.xml new file mode 100644 index 000000000..b6adb503b --- /dev/null +++ b/spec/fixtures/rentals_united/cities/empty_list.xml @@ -0,0 +1,4 @@ + + Success + + diff --git a/spec/fixtures/rentals_united/cities/error_status.xml b/spec/fixtures/rentals_united/cities/error_status.xml new file mode 100644 index 000000000..0c04ff0de --- /dev/null +++ b/spec/fixtures/rentals_united/cities/error_status.xml @@ -0,0 +1,3 @@ + + Test Error + diff --git a/spec/fixtures/rentals_united/cities/multiple_cities.xml b/spec/fixtures/rentals_united/cities/multiple_cities.xml new file mode 100644 index 000000000..ea1c333e8 --- /dev/null +++ b/spec/fixtures/rentals_united/cities/multiple_cities.xml @@ -0,0 +1,8 @@ + + Success + + 1 + 1 + 2 + + diff --git a/spec/fixtures/rentals_united/cities/one_city.xml b/spec/fixtures/rentals_united/cities/one_city.xml new file mode 100644 index 000000000..477924481 --- /dev/null +++ b/spec/fixtures/rentals_united/cities/one_city.xml @@ -0,0 +1,6 @@ + + Success + + 1 + + diff --git a/spec/fixtures/rentals_united/properties/empty_list.xml b/spec/fixtures/rentals_united/properties/empty_list.xml new file mode 100644 index 000000000..f3b4714ad --- /dev/null +++ b/spec/fixtures/rentals_united/properties/empty_list.xml @@ -0,0 +1,4 @@ + + Success + + diff --git a/spec/fixtures/rentals_united/properties/error_status.xml b/spec/fixtures/rentals_united/properties/error_status.xml new file mode 100644 index 000000000..328c23348 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/error_status.xml @@ -0,0 +1,3 @@ + + Test Error + diff --git a/spec/fixtures/rentals_united/properties/multiple_properties.xml b/spec/fixtures/rentals_united/properties/multiple_properties.xml new file mode 100644 index 000000000..bd65e7063 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/multiple_properties.xml @@ -0,0 +1,27 @@ + + Success + + + 519688 + Test property + 427698 + 24958 + 2016-08-31 11:39:48 + 2016-08-31 + true + true + 0 + + + 519689 + Test property + 427698 + 24958 + 2016-08-31 11:39:48 + 2016-08-31 + true + true + 0 + + + diff --git a/spec/fixtures/rentals_united/properties/one_property.xml b/spec/fixtures/rentals_united/properties/one_property.xml new file mode 100644 index 000000000..85ba4f40a --- /dev/null +++ b/spec/fixtures/rentals_united/properties/one_property.xml @@ -0,0 +1,16 @@ + + Success + + + 519688 + Test property + 427698 + 24958 + 2016-08-31 11:39:48 + 2016-08-31 + true + true + 0 + + + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/cities_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/cities_fetcher_spec.rb new file mode 100644 index 000000000..4ea994af6 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/cities_fetcher_spec.rb @@ -0,0 +1,105 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::CitiesFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:subject) { described_class.new(credentials) } + let(:url) { credentials.url } + let(:expected_locations) {{'1505' => 1, '2503' => 1, '1144' => 2}} + + it "returns an empty array when there is no active properties" do + stub_data = read_fixture("rentals_united/cities/empty_list.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_cities + expect(result).to be_success + expect(result.value).to eq([]) + end + + it "returns city object when there is one city" do + stub_data = read_fixture("rentals_united/cities/one_city.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_cities + expect(result).to be_success + expect(result.value).to be_kind_of(Array) + expect(result.value.size).to eq(1) + expect(result.value).to all(be_kind_of(RentalsUnited::Entities::City)) + + city = result.value.first + expect(city.location_id).to eq("1505") + expect(city.properties_count).to eq(1) + end + + it "returns multiple city objects" do + stub_data = read_fixture("rentals_united/cities/multiple_cities.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_cities + expect(result).to be_success + + cities = result.value + expect(cities).to be_kind_of(Array) + expect(cities.size).to eq(3) + expect(cities).to all(be_kind_of(RentalsUnited::Entities::City)) + + expected_locations.each do |location_id, properties_count| + city = cities.find { |c| c.location_id == location_id } + expect(city.properties_count).to eq(properties_count) + end + end + + context "when response from the api has error status" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/cities/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_cities + + expect(result).not_to be_success + expect(result.error.code).to eq("9999") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `9999`, and description ``" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_cities + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.fetch_cities + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb new file mode 100644 index 000000000..2a285cc92 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb @@ -0,0 +1,100 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::PropertyIdsFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:location_id) { "1234" } + let(:subject) { described_class.new(credentials, location_id) } + let(:url) { credentials.url } + + it "returns an empty array when there is no properties in location" do + stub_data = read_fixture("rentals_united/properties/empty_list.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property_ids + expect(result).to be_success + expect(result.value).to eq([]) + end + + it "returns property idwhen there is only one property" do + stub_data = read_fixture("rentals_united/properties/one_property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property_ids + expect(result).to be_success + expect(result.value).to be_kind_of(Array) + expect(result.value.size).to eq(1) + expect(result.value).to all(be_kind_of(String)) + + property_id = result.value.first + expect(property_id).to eq("519688") + end + + it "returns multiple city objects" do + stub_data = read_fixture("rentals_united/properties/multiple_properties.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property_ids + expect(result).to be_success + + properties = result.value + expect(properties).to be_kind_of(Array) + expect(properties.size).to eq(2) + expect(properties).to all(be_kind_of(String)) + expect(properties).to eq(["519688", "519689"]) + end + + context "when response from the api has error status" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/properties/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property_ids + + expect(result).not_to be_success + expect(result.error.code).to eq("9999") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `9999`, and description ``" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property_ids + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.fetch_property_ids + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb new file mode 100644 index 000000000..7b31fe7b8 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -0,0 +1,29 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Importer do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:importer) { described_class.new(credentials) } + + describe "#fetch_cities" do + it "calls fetcher class to load cities " do + fetcher_class = RentalsUnited::Commands::CitiesFetcher + + expect_any_instance_of(fetcher_class).to(receive(:fetch_cities)) + importer.fetch_cities + end + end + + describe "#fetch_property_ids" do + let(:location_id) { "1234" } + + it "calls fetcher class to load properties" do + fetcher_class = RentalsUnited::Commands::PropertyIdsFetcher + + expect_any_instance_of(fetcher_class).to(receive(:fetch_property_ids)) + importer.fetch_property_ids(location_id) + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb new file mode 100644 index 000000000..3cd7d1a03 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -0,0 +1,47 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::PayloadBuilder do + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:builder) { described_class.new(credentials) } + + describe '#build_property_ids_fetch_payload' do + let(:params) do + { + location_id: "123" + } + end + + it 'embedds username and password to request' do + xml = builder.build_property_ids_fetch_payload(params[:location_id]) + hash = to_hash(xml) + + authentication = hash.get("Pull_ListProp_RQ.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + + it 'adds location_id to request' do + xml = builder.build_property_ids_fetch_payload(params[:location_id]) + hash = to_hash(xml) + + location_id = hash.get("Pull_ListProp_RQ.LocationID") + expect(location_id).to eq(params[:location_id]) + end + end + + describe '#build_cities_fetch_payload' do + it 'embedds username and password to request' do + xml = builder.build_cities_fetch_payload + hash = to_hash(xml) + + authentication = hash.get("Pull_ListCitiesProps_RQ.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + end + + private + def to_hash(xml) + Concierge::SafeAccessHash.new(Nori.new.parse(xml)) + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/response_parser_spec.rb b/spec/lib/concierge/suppliers/rentals_united/response_parser_spec.rb new file mode 100644 index 000000000..29362a70b --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/response_parser_spec.rb @@ -0,0 +1,26 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::ResponseParser do + let(:xml_response) do + "Some Response" + end + + let(:bad_xml_response) { 'Server Error' } + + it "converts XML response to a safe hash object with given attributes" do + response_parser = described_class.new + hash = response_parser.to_hash(xml_response) + + expect(hash).to be_kind_of(Concierge::SafeAccessHash) + expect(hash.get("response")).to eq("Some Response") + expect(hash.to_h).to eq({ "response" => "Some Response" }) + end + + it "returns an empty object in case when XML format is not correct" do + response_parser = described_class.new + hash = response_parser.to_hash(bad_xml_response) + + expect(hash).to be_kind_of(Concierge::SafeAccessHash) + expect(hash.to_h).to eq({}) + end +end From 427ef5b9f3c3cc74a81ca7eda8b1bcedb533c58d Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 2 Sep 2016 12:30:04 +0600 Subject: [PATCH 025/118] RU: fetching detailed properties by id --- .../suppliers/rentals_united/metadata.rb | 6 ++ .../commands/property_fetcher.rb | 57 ++++++++++++ .../suppliers/rentals_united/importer.rb | 9 ++ .../rentals_united/mappers/property.rb | 30 ++++++ .../rentals_united/payload_builder.rb | 10 +- .../templates/property_fetch.xml.erb | 8 ++ ...tch.xml.erb => property_ids_fetch.xml.erb} | 0 .../properties/error_status.xml | 4 +- .../rentals_united/properties/not_found.xml | 3 + .../rentals_united/properties/property.xml | 86 +++++++++++++++++ .../empty_list.xml | 0 .../property_ids/error_status.xml | 3 + .../multiple_properties.xml | 0 .../one_property.xml | 0 .../commands/property_fetcher_spec.rb | 92 +++++++++++++++++++ .../commands/property_ids_fetcher_spec.rb | 8 +- .../suppliers/rentals_united/importer_spec.rb | 13 ++- .../rentals_united/payload_builder_spec.rb | 25 +++++ 18 files changed, 346 insertions(+), 8 deletions(-) create mode 100644 lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/property.rb create mode 100644 lib/concierge/suppliers/rentals_united/templates/property_fetch.xml.erb rename lib/concierge/suppliers/rentals_united/templates/{properties_fetch.xml.erb => property_ids_fetch.xml.erb} (100%) create mode 100644 spec/fixtures/rentals_united/properties/not_found.xml create mode 100644 spec/fixtures/rentals_united/properties/property.xml rename spec/fixtures/rentals_united/{properties => property_ids}/empty_list.xml (100%) create mode 100644 spec/fixtures/rentals_united/property_ids/error_status.xml rename spec/fixtures/rentals_united/{properties => property_ids}/multiple_properties.xml (100%) rename spec/fixtures/rentals_united/{properties => property_ids}/one_property.xml (100%) create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index 09ab37879..d436d608d 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -50,6 +50,12 @@ def fetch_property_ids(location_id) end end + def fetch_property(property_id) + announce_error("Failed to fetch property with ID #{property_id}`") do + importer.fetch_property(property_id) + end + end + def announce_error(message) yield.tap do |result| announce_context_error(message) unless result.success? diff --git a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb new file mode 100644 index 000000000..a5652d94c --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb @@ -0,0 +1,57 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::PropertyFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # property from RentalsUnited, parsing the response, and building + # +Result+ object. + class PropertyFetcher < BaseFetcher + attr_reader :property_id + + ROOT_TAG = "Pull_ListSpecProp_RS" + + # Initialize +PropertyFetcher+ command. + # + # Arguments + # + # * +credentials+ + # * +property_id+ + # + # Usage: + # + # RentalsUnited::Commands::PropertyFetcher.new( + # credentials, + # property_id + # ) + def initialize(credentials, property_id) + super(credentials) + + @property_id = property_id + end + + def fetch_property + payload = payload_builder.build_property_fetch_payload(property_id) + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_property(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_property(hash) + property = hash.get("#{ROOT_TAG}.Property") + return error_result(hash, ROOT_TAG) unless property + + mapper = RentalsUnited::Mappers::Property.new(property) + mapper.build_property + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb index 9784cac79..28460dc40 100644 --- a/lib/concierge/suppliers/rentals_united/importer.rb +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -32,5 +32,14 @@ def fetch_property_ids(location_id) ) properties_fetcher.fetch_property_ids end + + # Retrieves property by its id. + def fetch_property(property_id) + property_fetcher = Commands::PropertyFetcher.new( + credentials, + property_id + ) + property_fetcher.fetch_property + end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb new file mode 100644 index 000000000..a4f863788 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -0,0 +1,30 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Property+ + # + # This class is responsible for building a + # +RentalsUnited::Entities::Property+ object from a hash which was fetched + # from the RentalsUnited API. + class Property + attr_reader :property_hash + + # Initialize +RentalsUnited::Mappers::Property+ + # + # Arguments: + # + # * +property_hash+ [Concierge::SafeAccessHash] property hash object + def initialize(property_hash) + @property_hash = property_hash + end + + # Builds a property_hash + # + # Returns [RentalsUnited::Entities::Property] + def build_property + property = Roomorama::Property.new(property_hash.get("ID")) + property.title = property_hash.get("Name") + property + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index 1a85aad79..0416803ab 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -18,7 +18,7 @@ def build_property_ids_fetch_payload(location_id) credentials: credentials, location_id: location_id } - render(:properties_fetch, template_locals) + render(:property_ids_fetch, template_locals) end def build_cities_fetch_payload @@ -26,6 +26,14 @@ def build_cities_fetch_payload render(:cities_fetch, template_locals) end + def build_property_fetch_payload(property_id) + template_locals = { + credentials: credentials, + property_id: property_id + } + render(:property_fetch, template_locals) + end + private def render(template_name, local_vars) path = Hanami.root.join(TEMPLATES_PATH, "#{template_name}.xml.erb") diff --git a/lib/concierge/suppliers/rentals_united/templates/property_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/property_fetch.xml.erb new file mode 100644 index 000000000..b55f264c9 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/property_fetch.xml.erb @@ -0,0 +1,8 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + + <%= property_id %> + diff --git a/lib/concierge/suppliers/rentals_united/templates/properties_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb similarity index 100% rename from lib/concierge/suppliers/rentals_united/templates/properties_fetch.xml.erb rename to lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb diff --git a/spec/fixtures/rentals_united/properties/error_status.xml b/spec/fixtures/rentals_united/properties/error_status.xml index 328c23348..0c4386de0 100644 --- a/spec/fixtures/rentals_united/properties/error_status.xml +++ b/spec/fixtures/rentals_united/properties/error_status.xml @@ -1,3 +1,3 @@ - + Test Error - + diff --git a/spec/fixtures/rentals_united/properties/not_found.xml b/spec/fixtures/rentals_united/properties/not_found.xml new file mode 100644 index 000000000..b224fc4e3 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/not_found.xml @@ -0,0 +1,3 @@ + + Property does not exist. + diff --git a/spec/fixtures/rentals_united/properties/property.xml b/spec/fixtures/rentals_united/properties/property.xml new file mode 100644 index 000000000..585a55a2f --- /dev/null +++ b/spec/fixtures/rentals_united/properties/property.xml @@ -0,0 +1,86 @@ + + Success + + -1 + 519688 + Test property + 427698 + 24958 + 2016-08-31 11:39:48 + 2016-08-31 + true + true + 427698 + false + false + 2.6500 + 39 + 2 + 2 + 1 + 3 + 3 + Pereleta 22 + 644119 + + 55.0003426 + 73.2965942999999 + + + Ruslan Sharipov + sharipov.reg@gmail.com + +79618492980 + 1 + + + + + 13:00 + 17:00 + 11:00 + at_the_apartment + + + + 99.6000 + 5.50 + + + + 7 + 100 + 180 + 187 + 227 + 281 + 368 + 596 + 689 + 802 + 803 + + + + + + + + https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082398988145159.jpg + https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399010765284.jpg + https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg + https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399224174818.jpg + + + + + + + + + + + + diff --git a/spec/fixtures/rentals_united/properties/empty_list.xml b/spec/fixtures/rentals_united/property_ids/empty_list.xml similarity index 100% rename from spec/fixtures/rentals_united/properties/empty_list.xml rename to spec/fixtures/rentals_united/property_ids/empty_list.xml diff --git a/spec/fixtures/rentals_united/property_ids/error_status.xml b/spec/fixtures/rentals_united/property_ids/error_status.xml new file mode 100644 index 000000000..328c23348 --- /dev/null +++ b/spec/fixtures/rentals_united/property_ids/error_status.xml @@ -0,0 +1,3 @@ + + Test Error + diff --git a/spec/fixtures/rentals_united/properties/multiple_properties.xml b/spec/fixtures/rentals_united/property_ids/multiple_properties.xml similarity index 100% rename from spec/fixtures/rentals_united/properties/multiple_properties.xml rename to spec/fixtures/rentals_united/property_ids/multiple_properties.xml diff --git a/spec/fixtures/rentals_united/properties/one_property.xml b/spec/fixtures/rentals_united/property_ids/one_property.xml similarity index 100% rename from spec/fixtures/rentals_united/properties/one_property.xml rename to spec/fixtures/rentals_united/property_ids/one_property.xml diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb new file mode 100644 index 000000000..8f85d5f53 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -0,0 +1,92 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::PropertyFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:property_id) { "1234" } + let(:subject) { described_class.new(credentials, property_id) } + let(:url) { credentials.url } + + it "returns an error if property does not exist" do + stub_data = read_fixture("rentals_united/properties/not_found.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + expect(result).not_to be_success + expect(result.error.code).to eq("56") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `56`, and description ``" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + it "returns property" do + stub_data = read_fixture("rentals_united/properties/property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + expect(result).to be_success + + property = result.value + expect(property).to be_kind_of(Roomorama::Property) + expect(property.identifier).to eq("519688") + expect(property.title).to eq("Test property") + end + + context "when response from the api has error status" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/properties/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + + expect(result).not_to be_success + expect(result.error.code).to eq("9999") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `9999`, and description ``" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.fetch_property + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb index 2a285cc92..3b5269d68 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb @@ -10,7 +10,7 @@ let(:url) { credentials.url } it "returns an empty array when there is no properties in location" do - stub_data = read_fixture("rentals_united/properties/empty_list.xml") + stub_data = read_fixture("rentals_united/property_ids/empty_list.xml") stub_call(:post, url) { [200, {}, stub_data] } result = subject.fetch_property_ids @@ -19,7 +19,7 @@ end it "returns property idwhen there is only one property" do - stub_data = read_fixture("rentals_united/properties/one_property.xml") + stub_data = read_fixture("rentals_united/property_ids/one_property.xml") stub_call(:post, url) { [200, {}, stub_data] } result = subject.fetch_property_ids @@ -33,7 +33,7 @@ end it "returns multiple city objects" do - stub_data = read_fixture("rentals_united/properties/multiple_properties.xml") + stub_data = read_fixture("rentals_united/property_ids/multiple_properties.xml") stub_call(:post, url) { [200, {}, stub_data] } result = subject.fetch_property_ids @@ -48,7 +48,7 @@ context "when response from the api has error status" do it "returns a result with an appropriate error" do - stub_data = read_fixture("rentals_united/properties/error_status.xml") + stub_data = read_fixture("rentals_united/property_ids/error_status.xml") stub_call(:post, url) { [200, {}, stub_data] } result = subject.fetch_property_ids diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb index 7b31fe7b8..54ee166d4 100644 --- a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -19,11 +19,22 @@ describe "#fetch_property_ids" do let(:location_id) { "1234" } - it "calls fetcher class to load properties" do + it "calls fetcher class to load property ids" do fetcher_class = RentalsUnited::Commands::PropertyIdsFetcher expect_any_instance_of(fetcher_class).to(receive(:fetch_property_ids)) importer.fetch_property_ids(location_id) end end + + describe "#fetch_property" do + let(:property_id) { "588788" } + + it "calls fetcher class to load property" do + fetcher_class = RentalsUnited::Commands::PropertyFetcher + + expect_any_instance_of(fetcher_class).to(receive(:fetch_property)) + importer.fetch_property(property_id) + end + end end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index 3cd7d1a03..d6d0255d5 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -40,6 +40,31 @@ end end + describe '#build_property_ids_fetch_payload' do + let(:params) do + { + property_id: "123" + } + end + + it 'embedds username and password to request' do + xml = builder.build_property_fetch_payload(params[:property_id]) + hash = to_hash(xml) + + authentication = hash.get("Pull_ListSpecProp_RQ.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + + it 'adds property_id to request' do + xml = builder.build_property_fetch_payload(params[:property_id]) + hash = to_hash(xml) + + property_id = hash.get("Pull_ListSpecProp_RQ.PropertyID") + expect(property_id).to eq(params[:property_id]) + end + end + private def to_hash(xml) Concierge::SafeAccessHash.new(Nori.new.parse(xml)) From 0dbe169c39abdab4318d28f714b8c3c681b481fe Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 2 Sep 2016 17:16:53 +0600 Subject: [PATCH 026/118] RU: update property mapping --- .../dictionaries/property_types.json | 14 ++++ .../dictionaries/property_types.rb | 34 +++++++++ .../rentals_united/entities/property_type.rb | 24 +++++++ .../rentals_united/mappers/property.rb | 33 +++++++++ .../rentals_united/properties/property.xml | 9 +-- .../property_with_multiple_descriptions.xml | 14 ++++ .../commands/property_fetcher_spec.rb | 71 +++++++++++++------ 7 files changed, 170 insertions(+), 29 deletions(-) create mode 100644 lib/concierge/suppliers/rentals_united/dictionaries/property_types.json create mode 100644 lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/property_type.rb create mode 100644 spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/property_types.json b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.json new file mode 100644 index 000000000..11951580b --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.json @@ -0,0 +1,14 @@ +[ + { "id": "3", "rentals_united_name": "Apartment", "roomorama_name": "apartment", "roomorama_subtype_name": null }, + { "id": "4", "rentals_united_name": "Bed and breakfast", "roomorama_name": "bnb", "roomorama_subtype_name": null }, + { "id": "7", "rentals_united_name": "Chalet", "roomorama_name": "house", "roomorama_subtype_name": null }, + { "id": "16", "rentals_united_name": "Guest house", "roomorama_name": null, "roomorama_subtype_name": null }, + { "id": "20", "rentals_united_name": "Hotel", "roomorama_name": "hotel", "roomorama_subtype_name": null }, + { "id": "30", "rentals_united_name": "Resort", "roomorama_name": "hotel", "roomorama_subtype_name": "resort" }, + { "id": "35", "rentals_united_name": "Villa", "roomorama_name": "house", "roomorama_subtype_name": "villa" }, + { "id": "37", "rentals_united_name": "Castle", "roomorama_name": "house", "roomorama_subtype_name": "chateau" }, + { "id": "63", "rentals_united_name": "Aparthotel", "roomorama_name": "apartment", "roomorama_subtype_name": null }, + { "id": "64", "rentals_united_name": "Boat", "roomorama_name": null, "roomorama_subtype_name": null }, + { "id": "65", "rentals_united_name": "Cottage", "roomorama_name": "house", "roomorama_subtype_name": "cottage" }, + { "id": "66", "rentals_united_name": "Camping", "roomorama_name": "house", "roomorama_subtype_name": null } +] diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb new file mode 100644 index 000000000..81a5086be --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb @@ -0,0 +1,34 @@ +module RentalsUnited + module Dictionaries + class PropertyTypes + class << self + def find(id) + all.find { |p| p.id == id } + end + + def all + @all ||= property_hashes.map do |hash| + Entities::PropertyType.new( + id: hash["id"], + name: hash["rentals_united_name"], + roomorama_name: hash["roomorama_name"], + roomorama_subtype_name: hash["roomorama_subtype_name"] + ) + end + end + + private + def property_hashes + JSON.parse(File.read(file_path)) + end + + def file_path + Hanami.root.join( + "lib/concierge/suppliers/rentals_united/dictionaries", + "property_types.json" + ).to_s + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/entities/property_type.rb b/lib/concierge/suppliers/rentals_united/entities/property_type.rb new file mode 100644 index 000000000..787c7a9da --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/property_type.rb @@ -0,0 +1,24 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::PropertyType+ + # + # This entity represents a property type object. + # + # Attributes: + # + # +id+ - rentals united property type id + # +name+ - rentals united property type name + # +roomorama_name+ - roomorama name + # +roomorama_subtype_name+ - roomorama subtype name + class PropertyType + attr_reader :id, :name, :roomorama_name, :roomorama_subtype_name + + def initialize(id:, name:, roomorama_name:, roomorama_subtype_name:) + @id = id + @name = name + @roomorama_name = roomorama_name + @roomorama_subtype_name = roomorama_subtype_name + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index a4f863788..b984e9f16 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -8,6 +8,8 @@ module Mappers class Property attr_reader :property_hash + EN_DESCRIPTION_LANG_CODE = "1" + # Initialize +RentalsUnited::Mappers::Property+ # # Arguments: @@ -23,8 +25,39 @@ def initialize(property_hash) def build_property property = Roomorama::Property.new(property_hash.get("ID")) property.title = property_hash.get("Name") + property.description = en_description(property_hash) + property.lat = property_hash.get("Coordinates.Latitude").to_f + property.lng = property_hash.get("Coordinates.Longitude").to_f + property.address = property_hash.get("Street") + property.postal_code = property_hash.get("ZipCode") + + property_type = find_property_type(property_hash.get("ObjectTypeID")) + + if property_type + property.type = property_type.roomorama_name + property.subtype = property_type.roomorama_subtype_name + end + + set_images!(property) + property end + + private + def set_images!(property); end + + def find_property_type(id) + RentalsUnited::Dictionaries::PropertyTypes.find(id) + end + + def en_description(hash) + descriptions = hash.get("Descriptions.Description") + en_description = Array(descriptions).find do |desc| + desc["@LanguageID"] == EN_DESCRIPTION_LANG_CODE + end + + en_description["Text"] + end end end end diff --git a/spec/fixtures/rentals_united/properties/property.xml b/spec/fixtures/rentals_united/properties/property.xml index 585a55a2f..2123df2a0 100644 --- a/spec/fixtures/rentals_united/properties/property.xml +++ b/spec/fixtures/rentals_united/properties/property.xml @@ -18,9 +18,9 @@ 2 2 1 - 3 + 35 3 - Pereleta 22 + Test street address 644119 55.0003426 @@ -76,10 +76,7 @@ - + diff --git a/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml b/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml new file mode 100644 index 000000000..5adf80176 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml @@ -0,0 +1,14 @@ + + Success + + 519688 + + + + + + + + + + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 8f85d5f53..b7777a4f5 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -25,35 +25,60 @@ expect(event[:backtrace].any?).to be true end - it "returns property" do - stub_data = read_fixture("rentals_united/properties/property.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property - expect(result).to be_success - - property = result.value - expect(property).to be_kind_of(Roomorama::Property) - expect(property.identifier).to eq("519688") - expect(property.title).to eq("Test property") - end + context "when response contains property data" do + let(:file_name) { "rentals_united/properties/property.xml" } - context "when response from the api has error status" do - it "returns a result with an appropriate error" do - stub_data = read_fixture("rentals_united/properties/error_status.xml") + before do + stub_data = read_fixture(file_name) stub_call(:post, url) { [200, {}, stub_data] } + end + let(:property) do result = subject.fetch_property + expect(result).to be_success - expect(result).not_to be_success - expect(result.error.code).to eq("9999") + result.value + end - event = Concierge.context.events.last.to_h - expect(event[:message]).to eq( - "Response indicating the Status with ID `9999`, and description ``" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true + it "returns property object" do + expect(property).to be_kind_of(Roomorama::Property) + end + + it "sets id to the property" do + expect(property.identifier).to eq("519688") + end + + it "sets title to the property" do + expect(property.title).to eq("Test property") + end + + it "sets type to the property" do + expect(property.type).to eq("house") + end + + it "sets subtype to the property" do + expect(property.subtype).to eq("villa") + end + + it "sets address information to the property" do + expect(property.lat).to eq(55.0003426) + expect(property.lng).to eq(73.2965942999999) + expect(property.address).to eq("Test street address") + # expect(property.city).to eq("Namyangju-si") + # expect(property.neighborhood).to eq("Gyeonggi-do") + expect(property.postal_code).to eq("644119") + end + + it "sets en description to the property" do + expect(property.description).to eq("Test description") + end + + context "when multiple descriptions are available" do + let(:file_name) { "rentals_united/properties/property_with_multiple_descriptions.xml" } + + it "sets en description to the property" do + expect(property.description).to eq("Yet another one description") + end end end From 073f079ea3d2f6616aee11f17b0b103e13e71d5b Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 5 Sep 2016 10:02:49 +0600 Subject: [PATCH 027/118] RU: property images mapping --- .../rentals_united/mappers/image_set.rb | 53 +++++++++++++++ .../rentals_united/mappers/property.rb | 9 ++- .../rentals_united/properties/property.xml | 2 - .../properties/property_with_one_image.xml | 10 +++ .../property_without_descriptions.xml | 7 ++ .../properties/property_without_images.xml | 7 ++ .../commands/property_fetcher_spec.rb | 66 +++++++++++++++++++ 7 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 lib/concierge/suppliers/rentals_united/mappers/image_set.rb create mode 100644 spec/fixtures/rentals_united/properties/property_with_one_image.xml create mode 100644 spec/fixtures/rentals_united/properties/property_without_descriptions.xml create mode 100644 spec/fixtures/rentals_united/properties/property_without_images.xml diff --git a/lib/concierge/suppliers/rentals_united/mappers/image_set.rb b/lib/concierge/suppliers/rentals_united/mappers/image_set.rb new file mode 100644 index 000000000..3cb574b34 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/image_set.rb @@ -0,0 +1,53 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::ImageSet+ + # + # This class is responsible for building an array of images for + # properties and units. + # + # Array of images includes +Roomorama::Image+ objects + class ImageSet + attr_reader :raw_images + + # Rentals United image types mapping (image type id => name) + # Names are used as captions for +Roomorama::Image+ objects + IMAGE_TYPES = { + "1" => "Main image", + "2" => "Property plan", + "3" => "Interior", + "4" => "Exterior" + } + + # Initialize +RentalsUnited::Mappers::ImageSet+ mapper + # + # Arguments: + # + # * +raw_images+ [Array(Nori::StringWithAttributes)] array + def initialize(raw_images) + @raw_images = raw_images + end + + # Builds an array of property images + # + # If image URL is not valid (contains a space sign) then image is not + # included in the result array. + # + # Returns +Array+ array of images + def build_images + raw_images.map { |raw_image| build_image(raw_image) } + end + + private + def build_image(raw_image) + url = URI.encode(raw_image.to_s) + identifier = Digest::MD5.hexdigest(url) + + image = Roomorama::Image.new(identifier) + image.url = url + image.caption = IMAGE_TYPES[raw_image.attributes["ImageTypeID"]] + + image + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index b984e9f16..048eb9bce 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -44,7 +44,12 @@ def build_property end private - def set_images!(property); end + def set_images!(property) + raw_images = Array(property_hash.get("Images.Image")) + + mapper = Mappers::ImageSet.new(raw_images) + mapper.build_images.each { |image| property.add_image(image) } + end def find_property_type(id) RentalsUnited::Dictionaries::PropertyTypes.find(id) @@ -56,7 +61,7 @@ def en_description(hash) desc["@LanguageID"] == EN_DESCRIPTION_LANG_CODE end - en_description["Text"] + en_description["Text"] if en_description end end end diff --git a/spec/fixtures/rentals_united/properties/property.xml b/spec/fixtures/rentals_united/properties/property.xml index 2123df2a0..78a7f75e0 100644 --- a/spec/fixtures/rentals_united/properties/property.xml +++ b/spec/fixtures/rentals_united/properties/property.xml @@ -66,9 +66,7 @@ https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082398988145159.jpg - https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399010765284.jpg https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg - https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399224174818.jpg diff --git a/spec/fixtures/rentals_united/properties/property_with_one_image.xml b/spec/fixtures/rentals_united/properties/property_with_one_image.xml new file mode 100644 index 000000000..e34b8120f --- /dev/null +++ b/spec/fixtures/rentals_united/properties/property_with_one_image.xml @@ -0,0 +1,10 @@ + + Success + + -1 + 519688 + + https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg + + + diff --git a/spec/fixtures/rentals_united/properties/property_without_descriptions.xml b/spec/fixtures/rentals_united/properties/property_without_descriptions.xml new file mode 100644 index 000000000..b3681e2b9 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/property_without_descriptions.xml @@ -0,0 +1,7 @@ + + Success + + -1 + 519688 + + diff --git a/spec/fixtures/rentals_united/properties/property_without_images.xml b/spec/fixtures/rentals_united/properties/property_without_images.xml new file mode 100644 index 000000000..b3681e2b9 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/property_without_images.xml @@ -0,0 +1,7 @@ + + Success + + -1 + 519688 + + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index b7777a4f5..a44d75c91 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -80,6 +80,72 @@ expect(property.description).to eq("Yet another one description") end end + + context "when no available descriptions" do + let(:file_name) { "rentals_united/properties/property_without_descriptions.xml" } + + it "sets description to nil" do + expect(property.description).to be_nil + end + end + + context "when mapping single image to property" do + let(:file_name) { "rentals_united/properties/property_with_one_image.xml" } + + let(:expected_images) do + { + "a0c68acc113db3b58376155c283dfd59" => { + url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg", + caption: 'Main image' + } + } + end + + it "adds array of with one image to the property" do + expect(property.images.size).to eq(1) + expect(property.images).to all(be_kind_of(Roomorama::Image)) + expect(property.images.map(&:identifier)).to eq(expected_images.keys) + + property.images.each do |image| + expect(image.url).to eq(expected_images[image.identifier][:url]) + expect(image.caption).to eq(expected_images[image.identifier][:caption]) + end + end + end + + context "when mapping multiple images to property" do + let(:expected_images) do + { + "62fc304eb20a25669b84d2ca2ea61308" => { + url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082398988145159.jpg", + caption: 'Interior' + }, + "a0c68acc113db3b58376155c283dfd59" => { + url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg", + caption: 'Main image' + } + } + end + + it "adds array of images to the property" do + expect(property.images.size).to eq(2) + expect(property.images).to all(be_kind_of(Roomorama::Image)) + expect(property.images.map(&:identifier)).to eq(expected_images.keys) + + property.images.each do |image| + expect(image.url).to eq(expected_images[image.identifier][:url]) + expect(image.caption).to eq(expected_images[image.identifier][:caption]) + end + end + end + + context "when there is no images for property" do + let(:file_name) { "rentals_united/properties/property_without_images.xml" } + + it "returns an empty array for property images" do + expect(property.images).to eq([]) + end + end end context "when response from the api is not well-formed xml" do From f4c5d25135a5ed884d4dd6780e7308cbe98060e4 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 5 Sep 2016 12:12:50 +0600 Subject: [PATCH 028/118] RU: amenities mapping --- .../dictionaries/amenities.json | 473 ++++++++++++++++++ .../rentals_united/dictionaries/amenities.rb | 66 +++ .../rentals_united/mappers/property.rb | 20 +- .../properties/property_without_amenities.xml | 7 + .../commands/property_fetcher_spec.rb | 16 + .../dictionaries/amenities_spec.rb | 56 +++ 6 files changed, 630 insertions(+), 8 deletions(-) create mode 100644 lib/concierge/suppliers/rentals_united/dictionaries/amenities.json create mode 100644 lib/concierge/suppliers/rentals_united/dictionaries/amenities.rb create mode 100644 spec/fixtures/rentals_united/properties/property_without_amenities.xml create mode 100644 spec/lib/concierge/suppliers/rentals_united/dictionaries/amenities_spec.rb diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/amenities.json b/lib/concierge/suppliers/rentals_united/dictionaries/amenities.json new file mode 100644 index 000000000..a655c84d7 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/dictionaries/amenities.json @@ -0,0 +1,473 @@ +{ + "enabled-amenities": [ + { "id": "2", "name": "Cookware & Kitchen Utensils", "roomorama_name": "kitchen" }, + { "id": "7", "name": "Bed Linen & Towels", "roomorama_name": "bed_linen_and_towels" }, + { "id": "11", "name": "Washing Machine", "roomorama_name": "laundry" }, + { "id": "19", "name": "Cable TV", "roomorama_name": "cabletv" }, + { "id": "23", "name": "DVD", "roomorama_name": "tv" }, + { "id": "74", "name": "TV", "roomorama_name": "tv" }, + { "id": "89", "name": "Balcony", "roomorama_name": "balcony" }, + { "id": "96", "name": "Small Balcony", "roomorama_name": "balcony" }, + { "id": "101", "name": "Kitchen", "roomorama_name": "kitchen" }, + { "id": "134", "name": "washing machine with drier", "roomorama_name": "laundry" }, + { "id": "166", "name": "TV (local channels only)", "roomorama_name": "tv" }, + { "id": "167", "name": "satellite TV", "roomorama_name": "tv" }, + { "id": "174", "name": "internet connection", "roomorama_name": "internet" }, + { "id": "180", "name": "air conditioning", "roomorama_name": "airconditioning" }, + { "id": "227", "name": "swimming pool", "roomorama_name": "pool" }, + { "id": "234", "name": "Laundry", "roomorama_name": "laundry" }, + { "id": "235", "name": "Breakfast", "roomorama_name": "breakfast" }, + { "id": "281", "name": "Wheelchair access possible", "roomorama_name": "wheelchairaccess" }, + { "id": "294", "name": "A Gym is in the building for guests to use", "roomorama_name": "gym" }, + { "id": "295", "name": "On street parking", "roomorama_name": "parking" }, + { "id": "296", "name": "Underground parking", "roomorama_name": "parking" }, + { "id": "302", "name": "Guarded parking", "roomorama_name": "parking" }, + { "id": "325", "name": "DVD player", "roomorama_name": "tv" }, + { "id": "339", "name": "FREE internet access", "roomorama_name": "internet" }, + { "id": "371", "name": "Home Theatre", "roomorama_name": "tv" }, + { "id": "438", "name": "High speed Internet access", "roomorama_name": "internet" }, + { "id": "448", "name": "Free weekly cleaning", "roomorama_name": "free_cleaning" }, + { "id": "504", "name": "Private parking", "roomorama_name": "parking" }, + { "id": "589", "name": "Bed Linen", "roomorama_name": "bed_linen_and_towels" }, + { "id": "613", "name": "Laundry (Private)", "roomorama_name": "laundry" }, + { "id": "614", "name": "Laundry (Common)", "roomorama_name": "laundry" }, + { "id": "616", "name": "Doorman", "roomorama_name": "doorman" }, + { "id": "625", "name": "Garden (Private)", "roomorama_name": "outdoor_space" }, + { "id": "626", "name": "Garden (Common)", "roomorama_name": "outdoor_space" }, + { "id": "628", "name": "Laundry Service", "roomorama_name": "laundry" }, + { "id": "639", "name": "Meal Plan - Bed and Breakfast", "roomorama_name": "breakfast" }, + { "id": "665", "name": "Continental Breakfast", "roomorama_name": "breakfast" }, + { "id": "689", "name": "Elevator", "roomorama_name": "elevator" }, + { "id": "697", "name": "Meal Plan - FAP/Full-board", "roomorama_name": "breakfast" }, + { "id": "698", "name": "Full English Breakfast", "roomorama_name": "breakfast" }, + { "id": "717", "name": "Meal Plan - MAP/Half-board", "roomorama_name": "breakfast" }, + { "id": "737", "name": "Heated Pool", "roomorama_name": "pool" }, + { "id": "738", "name": "Indoor Pool", "roomorama_name": "pool" }, + { "id": "739", "name": "Childrens Pool", "roomorama_name": "pool" }, + { "id": "740", "name": "Outdoor Pool", "roomorama_name": "pool" }, + { "id": "792", "name": "Wireless Internet", "roomorama_name": "wifi" }, + { "id": "793", "name": "Parking", "roomorama_name": "parking" }, + { "id": "794", "name": "Outdoor Parking", "roomorama_name": "parking" }, + { "id": "795", "name": "Valet Parking", "roomorama_name": "parking" }, + { "id": "803", "name": "Free parking on the street", "roomorama_name": "parking" }, + { "id": "804", "name": "Paid parking on the street", "roomorama_name": "parking" }, + { "id": "805", "name": "Free parking with garage", "roomorama_name": "parking" }, + { "id": "806", "name": "Paid parking with garage", "roomorama_name": "parking" }, + { "id": "807", "name": "Free cable internet", "roomorama_name": "internet" }, + { "id": "808", "name": "Paid cable internet", "roomorama_name": "internet" }, + { "id": "809", "name": "Paid wireless internet", "roomorama_name": "wifi" }, + { "id": "815", "name": "Communal pool", "roomorama_name": "pool" }, + { "id": "816", "name": "Private pool", "roomorama_name": "pool" }, + { "id": "826", "name": "Shared Swimming Pool", "roomorama_name": "pool" }, + { "id": "834", "name": "Gym", "roomorama_name": "gym" }, + { "id": "851", "name": "TV 3D", "roomorama_name": "tv" }, + { "id": "852", "name": "Smart TV", "roomorama_name": "tv" } + ], + + "disabled-amenities": [ + { "id": "3", "name": "Crockery & Cutlery", "roomorama_name": "" }, + { "id": "4", "name": "Iron & Ironing Board", "roomorama_name": "" }, + { "id": "5", "name": "Drying Rack", "roomorama_name": "" }, + { "id": "6", "name": "Hair Dryer", "roomorama_name": "" }, + { "id": "8", "name": "Toiletries", "roomorama_name": "" }, + { "id": "13", "name": "Dishwasher", "roomorama_name": "" }, + { "id": "17", "name": "kettle", "roomorama_name": "" }, + { "id": "21", "name": "Alarm Clock", "roomorama_name": "" }, + { "id": "22", "name": "Stereo", "roomorama_name": "" }, + { "id": "24", "name": "CD player", "roomorama_name": "" }, + { "id": "29", "name": "bidet", "roomorama_name": "" }, + { "id": "32", "name": "cupboard", "roomorama_name": "" }, + { "id": "33", "name": "vanity cupboard", "roomorama_name": "" }, + { "id": "37", "name": "toilet", "roomorama_name": "" }, + { "id": "61", "name": "double bed", "roomorama_name": "" }, + { "id": "66", "name": "built-in wardrobes", "roomorama_name": "" }, + { "id": "70", "name": "night table", "roomorama_name": "" }, + { "id": "71", "name": "night tables", "roomorama_name": "" }, + { "id": "72", "name": "reading lamps", "roomorama_name": "" }, + { "id": "73", "name": "desk", "roomorama_name": "" }, + { "id": "78", "name": "chest of drawers", "roomorama_name": "" }, + { "id": "81", "name": "Bathroom", "roomorama_name": "" }, + { "id": "99", "name": "Lounge", "roomorama_name": "" }, + { "id": "100", "name": "Terrace", "roomorama_name": "" }, + { "id": "114", "name": "cooking hob", "roomorama_name": "" }, + { "id": "115", "name": "oven", "roomorama_name": "" }, + { "id": "119", "name": "cooker", "roomorama_name": "" }, + { "id": "123", "name": "electric kettle", "roomorama_name": "" }, + { "id": "124", "name": "microwave", "roomorama_name": "" }, + { "id": "125", "name": "toaster", "roomorama_name": "" }, + { "id": "128", "name": "plates", "roomorama_name": "" }, + { "id": "129", "name": "pans", "roomorama_name": "" }, + { "id": "130", "name": "fridge / freezer", "roomorama_name": "" }, + { "id": "131", "name": "fridge", "roomorama_name": "" }, + { "id": "140", "name": "coffee maker", "roomorama_name": "" }, + { "id": "142", "name": "dish rack", "roomorama_name": "" }, + { "id": "143", "name": "vacuum cleaner", "roomorama_name": "" }, + { "id": "146", "name": "gas/electric hob", "roomorama_name": "" }, + { "id": "150", "name": "breakfast bar and stools", "roomorama_name": "" }, + { "id": "152", "name": "freezer", "roomorama_name": "" }, + { "id": "157", "name": "kitchenette", "roomorama_name": "" }, + { "id": "161", "name": "armchairs", "roomorama_name": "" }, + { "id": "163", "name": "coffee table", "roomorama_name": "" }, + { "id": "182", "name": "sofa", "roomorama_name": "" }, + { "id": "187", "name": "heating", "roomorama_name": "" }, + { "id": "188", "name": "lamp", "roomorama_name": "" }, + { "id": "189", "name": "table and chairs", "roomorama_name": "" }, + { "id": "197", "name": "shelves", "roomorama_name": "" }, + { "id": "198", "name": "radio", "roomorama_name": "" }, + { "id": "200", "name": "double sofa bed", "roomorama_name": "" }, + { "id": "201", "name": "wardrobe", "roomorama_name": "" }, + { "id": "203", "name": "double sofa", "roomorama_name": "" }, + { "id": "205", "name": "Help Desk", "roomorama_name": "" }, + { "id": "208", "name": "High Chairs", "roomorama_name": "" }, + { "id": "209", "name": "Extra Bed", "roomorama_name": "" }, + { "id": "210", "name": "Mattress", "roomorama_name": "" }, + { "id": "215", "name": "Airport Pick-up Service", "roomorama_name": "" }, + { "id": "225", "name": "Maid Service", "roomorama_name": "" }, + { "id": "232", "name": "Dry Cleaning & Laundry", "roomorama_name": "" }, + { "id": "233", "name": "Dry Cleaning", "roomorama_name": "" }, + { "id": "237", "name": "sofabed", "roomorama_name": "" }, + { "id": "239", "name": "shower", "roomorama_name": "" }, + { "id": "245", "name": "washbasin ", "roomorama_name": "" }, + { "id": "250", "name": "dining table", "roomorama_name": "" }, + { "id": "253", "name": "jacuzzi", "roomorama_name": "" }, + { "id": "258", "name": "stove", "roomorama_name": "" }, + { "id": "261", "name": "Fan", "roomorama_name": "" }, + { "id": "312", "name": "Fax Machine", "roomorama_name": "" }, + { "id": "315", "name": "bathtub", "roomorama_name": "" }, + { "id": "323", "name": "single bed", "roomorama_name": "" }, + { "id": "324", "name": "king size bed", "roomorama_name": "" }, + { "id": "330", "name": "city maps", "roomorama_name": "" }, + { "id": "346", "name": "garden", "roomorama_name": "" }, + { "id": "349", "name": "Wood burning fireplace", "roomorama_name": "" }, + { "id": "355", "name": "Upon weekly stays: maidservice including personal laundry / ironing", "roomorama_name": "" }, + { "id": "363", "name": "armchair", "roomorama_name": "" }, + { "id": "364", "name": "Fireplace", "roomorama_name": "" }, + { "id": "365", "name": "Sauna", "roomorama_name": "" }, + { "id": "374", "name": "Cell Phone Rentals", "roomorama_name": "" }, + { "id": "380", "name": "Complimentary Tea & Coffee", "roomorama_name": "" }, + { "id": "390", "name": "Espresso-Machine", "roomorama_name": "" }, + { "id": "391", "name": "Hot Tub", "roomorama_name": "" }, + { "id": "395", "name": "Towels", "roomorama_name": "" }, + { "id": "404", "name": "Health Club", "roomorama_name": "" }, + { "id": "408", "name": "BBQ grill", "roomorama_name": "" }, + { "id": "409", "name": "en suite bathroom", "roomorama_name": "" }, + { "id": "413", "name": "Ice Maker", "roomorama_name": "" }, + { "id": "418", "name": "Video game system", "roomorama_name": "" }, + { "id": "421", "name": "Game room", "roomorama_name": "" }, + { "id": "429", "name": "Computer rental", "roomorama_name": "" }, + { "id": "436", "name": "blender", "roomorama_name": "" }, + { "id": "439", "name": "dining room", "roomorama_name": "" }, + { "id": "440", "name": "Pair of twin beds", "roomorama_name": "" }, + { "id": "444", "name": "Bunk Bed", "roomorama_name": "" }, + { "id": "447", "name": "Heated towel bar", "roomorama_name": "" }, + { "id": "450", "name": "street", "roomorama_name": "" }, + { "id": "451", "name": "Courtyard", "roomorama_name": "" }, + { "id": "453", "name": "Annex Room", "roomorama_name": "" }, + { "id": "456", "name": "chairs", "roomorama_name": "" }, + { "id": "461", "name": "Business centre", "roomorama_name": "" }, + { "id": "478", "name": "Downtown", "roomorama_name": "" }, + { "id": "485", "name": "Queen size bed", "roomorama_name": "" }, + { "id": "490", "name": "Fan(s) on request", "roomorama_name": "" }, + { "id": "491", "name": "Iron/Ironing board on request", "roomorama_name": "" }, + { "id": "497", "name": "Luggage Storage Facilities", "roomorama_name": "" }, + { "id": "503", "name": "mirror", "roomorama_name": "" }, + { "id": "506", "name": "cupboards", "roomorama_name": "" }, + { "id": "507", "name": "Table", "roomorama_name": "" }, + { "id": "508", "name": "Chair", "roomorama_name": "" }, + { "id": "511", "name": "Sea", "roomorama_name": "" }, + { "id": "516", "name": "En suite shower", "roomorama_name": "" }, + { "id": "590", "name": "Iron", "roomorama_name": "" }, + { "id": "591", "name": "Ironing Board", "roomorama_name": "" }, + { "id": "592", "name": "Telephone", "roomorama_name": "" }, + { "id": "593", "name": "Computer", "roomorama_name": "" }, + { "id": "594", "name": "Canal view", "roomorama_name": "" }, + { "id": "595", "name": "Pets are welcome", "roomorama_name": "" }, + { "id": "596", "name": "Free cot in the apartment", "roomorama_name": "" }, + { "id": "597", "name": "Free cot on request", "roomorama_name": "" }, + { "id": "598", "name": "Concierge", "roomorama_name": "" }, + { "id": "599", "name": "Washer dryer", "roomorama_name": "" }, + { "id": "600", "name": "Dryer", "roomorama_name": "" }, + { "id": "601", "name": "Safe", "roomorama_name": "" }, + { "id": "602", "name": "Ski Storage", "roomorama_name": "" }, + { "id": "603", "name": "Mountain View", "roomorama_name": "" }, + { "id": "604", "name": "Seaview", "roomorama_name": "" }, + { "id": "605", "name": "Fitness Room", "roomorama_name": "" }, + { "id": "606", "name": "Spa", "roomorama_name": "" }, + { "id": "607", "name": "Steam room", "roomorama_name": "" }, + { "id": "608", "name": "Room Service", "roomorama_name": "" }, + { "id": "609", "name": "Slope View", "roomorama_name": "" }, + { "id": "611", "name": "Hot Tub (Private)", "roomorama_name": "" }, + { "id": "612", "name": "Hot Tub (Common)", "roomorama_name": "" }, + { "id": "615", "name": "Minibar", "roomorama_name": "" }, + { "id": "617", "name": "Breakfast Room", "roomorama_name": "" }, + { "id": "618", "name": "Meeting Room", "roomorama_name": "" }, + { "id": "619", "name": "Restaurant", "roomorama_name": "" }, + { "id": "620", "name": "Bar", "roomorama_name": "" }, + { "id": "621", "name": "Beauty Salon", "roomorama_name": "" }, + { "id": "622", "name": "Children Area", "roomorama_name": "" }, + { "id": "623", "name": "Ski In and Out", "roomorama_name": "" }, + { "id": "624", "name": "Pull-Out Bed", "roomorama_name": "" }, + { "id": "627", "name": "Reception", "roomorama_name": "" }, + { "id": "629", "name": "Adjoining Rooms", "roomorama_name": "" }, + { "id": "630", "name": "Outlet Adapters", "roomorama_name": "" }, + { "id": "631", "name": "Airline Desk", "roomorama_name": "" }, + { "id": "632", "name": "Meal Plan - American", "roomorama_name": "" }, + { "id": "633", "name": "ATM/Cash Machine", "roomorama_name": "" }, + { "id": "634", "name": "Audio Visual Equipment", "roomorama_name": "" }, + { "id": "635", "name": "Babysitting/Child Services", "roomorama_name": "" }, + { "id": "636", "name": "Barber Shop", "roomorama_name": "" }, + { "id": "637", "name": "On The Bay", "roomorama_name": "" }, + { "id": "638", "name": "Bay View", "roomorama_name": "" }, + { "id": "640", "name": "Baby Listening Device", "roomorama_name": "" }, + { "id": "641", "name": "Beach View", "roomorama_name": "" }, + { "id": "642", "name": "Beach", "roomorama_name": "" }, + { "id": "643", "name": "Barber/Beauty Shop", "roomorama_name": "" }, + { "id": "644", "name": "Porters", "roomorama_name": "" }, + { "id": "645", "name": "Bicycle Rentals", "roomorama_name": "" }, + { "id": "646", "name": "Blackboard", "roomorama_name": "" }, + { "id": "647", "name": "Billiards / Pool Tables", "roomorama_name": "" }, + { "id": "648", "name": "Boating", "roomorama_name": "" }, + { "id": "649", "name": "Boutiques", "roomorama_name": "" }, + { "id": "650", "name": "Bowling", "roomorama_name": "" }, + { "id": "651", "name": "Meal Plan - Bermuda", "roomorama_name": "" }, + { "id": "652", "name": "Braille Elevator", "roomorama_name": "" }, + { "id": "653", "name": "Breakfast Buffet", "roomorama_name": "" }, + { "id": "654", "name": "Bathroom Telephone", "roomorama_name": "" }, + { "id": "655", "name": "Canopy / Poster Bed", "roomorama_name": "" }, + { "id": "656", "name": "Car Rental Desk", "roomorama_name": "" }, + { "id": "657", "name": "Casino", "roomorama_name": "" }, + { "id": "658", "name": "Castle Room", "roomorama_name": "" }, + { "id": "659", "name": "Meal Plan - Caribbean", "roomorama_name": "" }, + { "id": "660", "name": "CD Player", "roomorama_name": "" }, + { "id": "661", "name": "Ceiling Fan", "roomorama_name": "" }, + { "id": "662", "name": "City View", "roomorama_name": "" }, + { "id": "663", "name": "Conference Facilities", "roomorama_name": "" }, + { "id": "664", "name": "Conference Suite", "roomorama_name": "" }, + { "id": "666", "name": "Coffee Shop", "roomorama_name": "" }, + { "id": "667", "name": "Coffee Maker in Room", "roomorama_name": "" }, + { "id": "668", "name": "Computer in Room", "roomorama_name": "" }, + { "id": "669", "name": "Concierge Desk", "roomorama_name": "" }, + { "id": "670", "name": "Connecting Rooms", "roomorama_name": "" }, + { "id": "671", "name": "Meal Plan - Continental", "roomorama_name": "" }, + { "id": "672", "name": "Copy Service", "roomorama_name": "" }, + { "id": "673", "name": "Cordless Phone", "roomorama_name": "" }, + { "id": "674", "name": "Cribs Available", "roomorama_name": "" }, + { "id": "675", "name": "Courtesy Car", "roomorama_name": "" }, + { "id": "676", "name": "City Center", "roomorama_name": "" }, + { "id": "677", "name": "Currency Exchange", "roomorama_name": "" }, + { "id": "678", "name": "Data port Available", "roomorama_name": "" }, + { "id": "679", "name": "24 Hour Front Desk", "roomorama_name": "" }, + { "id": "680", "name": "Dining Guide", "roomorama_name": "" }, + { "id": "681", "name": "Dinner", "roomorama_name": "" }, + { "id": "682", "name": "Handicapped Rooms/Facilities", "roomorama_name": "" }, + { "id": "683", "name": "Disco", "roomorama_name": "" }, + { "id": "684", "name": "Doctor on Call", "roomorama_name": "" }, + { "id": "685", "name": "Drugstore", "roomorama_name": "" }, + { "id": "686", "name": "Driving Range", "roomorama_name": "" }, + { "id": "687", "name": "Desk with lamp", "roomorama_name": "" }, + { "id": "688", "name": "Electronic Door Locks", "roomorama_name": "" }, + { "id": "690", "name": "Email Service", "roomorama_name": "" }, + { "id": "691", "name": "Live Entertainment", "roomorama_name": "" }, + { "id": "692", "name": "Meal Plan - European", "roomorama_name": "" }, + { "id": "693", "name": "Express Check In", "roomorama_name": "" }, + { "id": "694", "name": "Executive Desk", "roomorama_name": "" }, + { "id": "695", "name": "Express Checkout", "roomorama_name": "" }, + { "id": "696", "name": "Executive Level", "roomorama_name": "" }, + { "id": "699", "name": "Female Executive Rooms", "roomorama_name": "" }, + { "id": "700", "name": "Fishing", "roomorama_name": "" }, + { "id": "701", "name": "Florist", "roomorama_name": "" }, + { "id": "702", "name": "Free Parking", "roomorama_name": "" }, + { "id": "703", "name": "Free Local Telephone Calls", "roomorama_name": "" }, + { "id": "704", "name": "Free Transportation", "roomorama_name": "" }, + { "id": "705", "name": "Garden View", "roomorama_name": "" }, + { "id": "706", "name": "Gift Shop", "roomorama_name": "" }, + { "id": "707", "name": "Game Rental", "roomorama_name": "" }, + { "id": "708", "name": "Golf", "roomorama_name": "" }, + { "id": "709", "name": "Golf Course View", "roomorama_name": "" }, + { "id": "710", "name": "Horseback Riding", "roomorama_name": "" }, + { "id": "711", "name": "Jogging Track", "roomorama_name": "" }, + { "id": "712", "name": "Kennels", "roomorama_name": "" }, + { "id": "713", "name": "Childrens Activities", "roomorama_name": "" }, + { "id": "714", "name": "Lake View", "roomorama_name": "" }, + { "id": "715", "name": "Guest Laundromat", "roomorama_name": "" }, + { "id": "716", "name": "Lunch", "roomorama_name": "" }, + { "id": "718", "name": "Massage", "roomorama_name": "" }, + { "id": "719", "name": "Miniature Golf", "roomorama_name": "" }, + { "id": "720", "name": "In Room Movies", "roomorama_name": "" }, + { "id": "721", "name": "Meeting Facilities", "roomorama_name": "" }, + { "id": "722", "name": "Meeting Suite", "roomorama_name": "" }, + { "id": "723", "name": "Multilingual", "roomorama_name": "" }, + { "id": "724", "name": "Nursery for Children", "roomorama_name": "" }, + { "id": "725", "name": "No Smoking Rooms/Facilities", "roomorama_name": "" }, + { "id": "726", "name": "Night Club", "roomorama_name": "" }, + { "id": "727", "name": "Free Newspaper", "roomorama_name": "" }, + { "id": "728", "name": "News Stand", "roomorama_name": "" }, + { "id": "729", "name": "Ocean View", "roomorama_name": "" }, + { "id": "730", "name": "Overhead Projector", "roomorama_name": "" }, + { "id": "731", "name": "Parasailing", "roomorama_name": "" }, + { "id": "732", "name": "Park View", "roomorama_name": "" }, + { "id": "733", "name": "No Pets Allowed", "roomorama_name": "" }, + { "id": "734", "name": "Phone Service", "roomorama_name": "" }, + { "id": "735", "name": "Picnic Area/Tables", "roomorama_name": "" }, + { "id": "736", "name": "Play Ground", "roomorama_name": "" }, + { "id": "741", "name": "Poolside Snackbar", "roomorama_name": "" }, + { "id": "742", "name": "Projector", "roomorama_name": "" }, + { "id": "743", "name": "Squash", "roomorama_name": "" }, + { "id": "744", "name": "River View", "roomorama_name": "" }, + { "id": "745", "name": "Ramp Access to Buildings", "roomorama_name": "" }, + { "id": "746", "name": "24 Hour Room Service", "roomorama_name": "" }, + { "id": "747", "name": "Safe Deposit", "roomorama_name": "" }, + { "id": "748", "name": "Sailing", "roomorama_name": "" }, + { "id": "749", "name": "Scuba Diving", "roomorama_name": "" }, + { "id": "750", "name": "Secretarial Service", "roomorama_name": "" }, + { "id": "751", "name": "24 Hour Security", "roomorama_name": "" }, + { "id": "752", "name": "Shopping Mall", "roomorama_name": "" }, + { "id": "753", "name": "Free Airport Shuttle", "roomorama_name": "" }, + { "id": "754", "name": "Skeet Shooting", "roomorama_name": "" }, + { "id": "755", "name": "Skiing", "roomorama_name": "" }, + { "id": "756", "name": "Cross Country Skiing", "roomorama_name": "" }, + { "id": "757", "name": "Snorkeling", "roomorama_name": "" }, + { "id": "758", "name": "Snowboarding", "roomorama_name": "" }, + { "id": "759", "name": "Fitness Center or Spa", "roomorama_name": "" }, + { "id": "760", "name": "Steam Bath", "roomorama_name": "" }, + { "id": "761", "name": "Telex", "roomorama_name": "" }, + { "id": "762", "name": "Indoor Tennis", "roomorama_name": "" }, + { "id": "763", "name": "Tennis", "roomorama_name": "" }, + { "id": "764", "name": "Outdoor Tennis", "roomorama_name": "" }, + { "id": "765", "name": "Tour Desk", "roomorama_name": "" }, + { "id": "766", "name": "Translation Service", "roomorama_name": "" }, + { "id": "767", "name": "Laundry Services", "roomorama_name": "" }, + { "id": "768", "name": "Vending Machines", "roomorama_name": "" }, + { "id": "769", "name": "VIP Rooms/Services", "roomorama_name": "" }, + { "id": "770", "name": "Volleyball", "roomorama_name": "" }, + { "id": "771", "name": "Wake-up Service", "roomorama_name": "" }, + { "id": "772", "name": "Wedding Services", "roomorama_name": "" }, + { "id": "773", "name": "Wind Surfing", "roomorama_name": "" }, + { "id": "774", "name": "Water Skiing", "roomorama_name": "" }, + { "id": "775", "name": "Grab Bars in Bathroom", "roomorama_name": "" }, + { "id": "776", "name": "Heated Guest Rooms", "roomorama_name": "" }, + { "id": "777", "name": "Modem in Room", "roomorama_name": "" }, + { "id": "778", "name": "Murphy Bed", "roomorama_name": "" }, + { "id": "779", "name": "Rollaway Beds", "roomorama_name": "" }, + { "id": "780", "name": "Bathrobes", "roomorama_name": "" }, + { "id": "781", "name": "Smoke Detectors", "roomorama_name": "" }, + { "id": "782", "name": "Solarium", "roomorama_name": "" }, + { "id": "783", "name": "Sprinklers In Rooms", "roomorama_name": "" }, + { "id": "784", "name": "Theater Desk", "roomorama_name": "" }, + { "id": "785", "name": "Temperature Control", "roomorama_name": "" }, + { "id": "786", "name": "Trouser Press", "roomorama_name": "" }, + { "id": "788", "name": "Ipod Dock", "roomorama_name": "" }, + { "id": "789", "name": "Heating", "roomorama_name": "" }, + { "id": "790", "name": "Hi-Fi", "roomorama_name": "" }, + { "id": "791", "name": "Duty Free Shop", "roomorama_name": "" }, + { "id": "796", "name": "Prayer Mats", "roomorama_name": "" }, + { "id": "797", "name": "Racquetball Courts", "roomorama_name": "" }, + { "id": "798", "name": "Refrigerator", "roomorama_name": "" }, + { "id": "799", "name": "Smoking", "roomorama_name": "" }, + { "id": "800", "name": "Chef Provided", "roomorama_name": "" }, + { "id": "801", "name": "Marina View", "roomorama_name": "" }, + { "id": "802", "name": "Smoking allowed", "roomorama_name": "" }, + { "id": "810", "name": "Paid cot on request", "roomorama_name": "" }, + { "id": "811", "name": "Petanque", "roomorama_name": "" }, + { "id": "812", "name": "Ask for smoking", "roomorama_name": "" }, + { "id": "813", "name": "Ask for pets", "roomorama_name": "" }, + { "id": "814", "name": "Ask for accessibility", "roomorama_name": "" }, + { "id": "817", "name": "Ping-pong table", "roomorama_name": "" }, + { "id": "818", "name": "Breakfast booking possible", "roomorama_name": "" }, + { "id": "819", "name": "House cleaning optional", "roomorama_name": "" }, + { "id": "820", "name": "Sports - swimming", "roomorama_name": "" }, + { "id": "821", "name": "Local hospital", "roomorama_name": "" }, + { "id": "822", "name": "Local groceries", "roomorama_name": "" }, + { "id": "823", "name": "Waterfront", "roomorama_name": "" }, + { "id": "824", "name": "Near ocean", "roomorama_name": "" }, + { "id": "825", "name": "Shared Kitchen", "roomorama_name": "" }, + { "id": "827", "name": "garage", "roomorama_name": "" }, + { "id": "828", "name": "Family/kids friendly", "roomorama_name": "" }, + { "id": "830", "name": "Juicer", "roomorama_name": "" }, + { "id": "831", "name": "Security camera at entrance", "roomorama_name": "" }, + { "id": "832", "name": "MP3", "roomorama_name": "" }, + { "id": "833", "name": "Baby cot", "roomorama_name": "" }, + { "id": "835", "name": "Paddle", "roomorama_name": "" }, + { "id": "836", "name": "Taxi access", "roomorama_name": "" }, + { "id": "837", "name": "Rooftop access", "roomorama_name": "" }, + { "id": "838", "name": "Baby high chair", "roomorama_name": "" }, + { "id": "839", "name": "Baby cot paid", "roomorama_name": "" }, + { "id": "840", "name": "Baby chair on request", "roomorama_name": "" }, + { "id": "841", "name": "High chair on request", "roomorama_name": "" }, + { "id": "842", "name": "Pets paid", "roomorama_name": "" }, + { "id": "843", "name": "Pets accepted under request", "roomorama_name": "" }, + { "id": "844", "name": "Stroller", "roomorama_name": "" }, + { "id": "845", "name": "Baby cutlery", "roomorama_name": "" }, + { "id": "846", "name": "Internet connection on request", "roomorama_name": "" }, + { "id": "847", "name": "Cable TV on request", "roomorama_name": "" }, + { "id": "848", "name": "Dry cleaning on request", "roomorama_name": "" }, + { "id": "849", "name": "Free international calls", "roomorama_name": "" }, + { "id": "850", "name": "Chimney", "roomorama_name": "" }, + { "id": "853", "name": "Free car", "roomorama_name": "" }, + { "id": "854", "name": "Free bike", "roomorama_name": "" }, + { "id": "855", "name": "Credit card payment accepted", "roomorama_name": "" }, + { "id": "856", "name": "No parties", "roomorama_name": "" }, + { "id": "857", "name": "No children under 4", "roomorama_name": "" }, + { "id": "858", "name": "No children under 12", "roomorama_name": "" }, + { "id": "859", "name": "Anyone under 25 years", "roomorama_name": "" }, + { "id": "860", "name": "Anyone under 30 years", "roomorama_name": "" }, + { "id": "861", "name": "Anyone under 35 years", "roomorama_name": "" }, + { "id": "862", "name": "Anyone under 18 years", "roomorama_name": "" }, + { "id": "863", "name": "No reservation more than 30 days", "roomorama_name": "" }, + { "id": "864", "name": "Groups under 18 years", "roomorama_name": "" }, + { "id": "865", "name": "Groups under 25 years", "roomorama_name": "" }, + { "id": "866", "name": "Groups under 30 years", "roomorama_name": "" }, + { "id": "867", "name": "Groups under 35 years", "roomorama_name": "" }, + { "id": "868", "name": "Only families", "roomorama_name": "" }, + { "id": "869", "name": "Anyone under 40 years", "roomorama_name": "" }, + { "id": "870", "name": "No children under 6", "roomorama_name": "" }, + { "id": "871", "name": "Groups under 50 years", "roomorama_name": "" }, + { "id": "872", "name": "Same sex groups under 30 years", "roomorama_name": "" }, + { "id": "873", "name": "Groups under 45 years", "roomorama_name": "" }, + { "id": "874", "name": "Same sex groups under 35 years", "roomorama_name": "" }, + { "id": "875", "name": "Arrivals on Sunday", "roomorama_name": "" }, + { "id": "876", "name": "Families or couples only", "roomorama_name": "" }, + { "id": "877", "name": "Baby high chair paid", "roomorama_name": "" }, + { "id": "878", "name": "Baby chair paid", "roomorama_name": "" }, + { "id": "879", "name": "Wifi USB Adapter", "roomorama_name": "" }, + { "id": "880", "name": "Bottled water", "roomorama_name": "" }, + { "id": "881", "name": "Centrally controlled ventilation", "roomorama_name": "" }, + { "id": "882", "name": "Electrical adapters available", "roomorama_name": "" }, + { "id": "883", "name": "Hypoallergenic rooms", "roomorama_name": "" }, + { "id": "884", "name": "Internet browser TV", "roomorama_name": "" }, + { "id": "885", "name": "Power converters", "roomorama_name": "" }, + { "id": "886", "name": "Sewing kit", "roomorama_name": "" }, + { "id": "887", "name": "Printer", "roomorama_name": "" }, + { "id": "888", "name": "Slippers", "roomorama_name": "" }, + { "id": "889", "name": "Sound system", "roomorama_name": "" }, + { "id": "890", "name": "Sound proofed windows", "roomorama_name": "" }, + { "id": "891", "name": "Weighting scale", "roomorama_name": "" }, + { "id": "892", "name": "Run of the house", "roomorama_name": "" }, + { "id": "893", "name": "Window", "roomorama_name": "" }, + { "id": "894", "name": "Veranda", "roomorama_name": "" }, + { "id": "895", "name": "Patio", "roomorama_name": "" }, + { "id": "896", "name": "AC public areas", "roomorama_name": "" }, + { "id": "897", "name": "Aqua sports center", "roomorama_name": "" }, + { "id": "898", "name": "Courier service", "roomorama_name": "" }, + { "id": "899", "name": "Creche", "roomorama_name": "" }, + { "id": "900", "name": "Housekeeping service", "roomorama_name": "" }, + { "id": "901", "name": "Entertainment recreation", "roomorama_name": "" }, + { "id": "902", "name": "Diving", "roomorama_name": "" }, + { "id": "903", "name": "Hair salon", "roomorama_name": "" }, + { "id": "904", "name": "Hotel shops", "roomorama_name": "" }, + { "id": "905", "name": "Island hopping", "roomorama_name": "" }, + { "id": "906", "name": "Jet skiing", "roomorama_name": "" }, + { "id": "907", "name": "Kids eat for free", "roomorama_name": "" }, + { "id": "908", "name": "Late check-out available", "roomorama_name": "" }, + { "id": "909", "name": "Limo town car service available", "roomorama_name": "" }, + { "id": "910", "name": "Security guard", "roomorama_name": "" }, + { "id": "911", "name": "Shoe shine", "roomorama_name": "" }, + { "id": "912", "name": "Shoe polishing machine", "roomorama_name": "" }, + { "id": "913", "name": "Shuttle service", "roomorama_name": "" }, + { "id": "914", "name": "Suitable for children", "roomorama_name": "" }, + { "id": "915", "name": "Ticket service", "roomorama_name": "" }, + { "id": "916", "name": "Turndown service", "roomorama_name": "" }, + { "id": "917", "name": "Umbrella", "roomorama_name": "" }, + { "id": "918", "name": "Welcome amenities", "roomorama_name": "" } + ] +} diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/amenities.rb b/lib/concierge/suppliers/rentals_united/dictionaries/amenities.rb new file mode 100644 index 000000000..8129ccfd4 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/dictionaries/amenities.rb @@ -0,0 +1,66 @@ +module RentalsUnited + module Dictionaries + # +RentalsUnited::Dictionaries::Amenities+ + # + # This class is responsible for mapping amenitites between RU and + # Roomorama APIs. + class Amenities + attr_reader :facility_service_ids + + # Initialize amenities converter + # + # Arguments + # + # * +acility_service_ids+ [Array] + def initialize(facility_service_ids) + @facility_service_ids = Array(facility_service_ids) + end + + # Converts RU facility services to Roomorama amenities + # + # Returns +Array+ array with uniq supported amenitites + def convert + roomorama_amenities = facility_service_ids.map do |service_id| + amenity = self.class.find(service_id) + amenity["roomorama_name"] if amenity + end + + roomorama_amenities.compact.uniq + end + + class << self + # Looks up for supported amenity by id. + # Returns nil if supported amenity was not found. + # + # Arguments + # + # * +service_id+ [String] rentals united id of facility service + # + # Returns a +Hash+ with mapping if supported facility service is found + # and +nil+ when there is no supported service with given id. + def find(service_id) + supported_amenities.find { |amenity| amenity["id"] == service_id } + end + + # Returns a hash with mapping between Rentals United facility services + # and Roomorama API supported amenities + # + # Returns an +Array+ with +Hash+ objects + def supported_amenities + @supported_amenities ||= load_amenities["enabled-amenities"] + end + + def load_amenities + JSON.parse(File.read(file_path)) + end + + def file_path + Hanami.root.join( + "lib/concierge/suppliers/rentals_united/dictionaries", + "amenities.json" + ).to_s + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index 048eb9bce..cc1e1ded9 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -6,7 +6,7 @@ module Mappers # +RentalsUnited::Entities::Property+ object from a hash which was fetched # from the RentalsUnited API. class Property - attr_reader :property_hash + attr_reader :property_hash, :amenities_dictionary EN_DESCRIPTION_LANG_CODE = "1" @@ -17,19 +17,23 @@ class Property # * +property_hash+ [Concierge::SafeAccessHash] property hash object def initialize(property_hash) @property_hash = property_hash + @amenities_dictionary = Dictionaries::Amenities.new( + property_hash.get("Amenities.Amenity") + ) end - # Builds a property_hash + # Builds a property # # Returns [RentalsUnited::Entities::Property] def build_property property = Roomorama::Property.new(property_hash.get("ID")) - property.title = property_hash.get("Name") - property.description = en_description(property_hash) - property.lat = property_hash.get("Coordinates.Latitude").to_f - property.lng = property_hash.get("Coordinates.Longitude").to_f - property.address = property_hash.get("Street") - property.postal_code = property_hash.get("ZipCode") + property.title = property_hash.get("Name") + property.description = en_description(property_hash) + property.lat = property_hash.get("Coordinates.Latitude").to_f + property.lng = property_hash.get("Coordinates.Longitude").to_f + property.address = property_hash.get("Street") + property.postal_code = property_hash.get("ZipCode") + property.amenities = amenities_dictionary.convert property_type = find_property_type(property_hash.get("ObjectTypeID")) diff --git a/spec/fixtures/rentals_united/properties/property_without_amenities.xml b/spec/fixtures/rentals_united/properties/property_without_amenities.xml new file mode 100644 index 000000000..b3681e2b9 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/property_without_amenities.xml @@ -0,0 +1,7 @@ + + Success + + -1 + 519688 + + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index a44d75c91..02b565f87 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -89,6 +89,22 @@ end end + context "when mapping amenities" do + it "adds amenities to property" do + expect(property.amenities).to eq( + ["bed_linen_and_towels", "airconditioning", "pool", "wheelchairaccess", "elevator", "parking"] + ) + end + + it "sets amenities to empty array if there is no amenities" do + file_name = "rentals_united/properties/property_without_amenities.xml" + + expect(property.amenities).to eq( + ["bed_linen_and_towels", "airconditioning", "pool", "wheelchairaccess", "elevator", "parking"] + ) + end + end + context "when mapping single image to property" do let(:file_name) { "rentals_united/properties/property_with_one_image.xml" } diff --git a/spec/lib/concierge/suppliers/rentals_united/dictionaries/amenities_spec.rb b/spec/lib/concierge/suppliers/rentals_united/dictionaries/amenities_spec.rb new file mode 100644 index 000000000..4686cc7af --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/dictionaries/amenities_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Dictionaries::Amenities do + describe "#supported_amenities" do + it "returns array of supported amenities" do + expect(described_class.supported_amenities).to be_kind_of(Array) + expect(described_class.supported_amenities).to all(be_kind_of(Hash)) + end + end + + describe "#convert" do + it "returns an emptry string when there is no given RU services" do + service_ids = [] + + amenities = described_class.new(service_ids).convert + expect(amenities).to eq([]) + end + + it "returns an empty string when there is no any match in given RU services" do + service_ids = ["8888", "9999"] + + amenities = described_class.new(service_ids).convert + expect(amenities).to eq([]) + end + + it "converts RU services to amenities if there is a match" do + service_ids = ["11", "74"] + + amenities = described_class.new(service_ids).convert + expect(amenities).to eq(["laundry", "tv"]) + end + + it "skips unknown RU services and returns only amenities with matches" do + service_ids = ["11", "74", "8888"] + + amenities = described_class.new(service_ids).convert + expect(amenities).to eq(["laundry", "tv"]) + end + + it "removes duplicates if there are two RU services with the same name" do + service_ids = ["89", "89", "89"] + + amenities = described_class.new(service_ids).convert + expect(amenities).to eq(["balcony"]) + end + + it "removes duplicates if there are RU services with the same match" do + service_ids = ["89", "96"] + + amenities = described_class.new(service_ids).convert + expect(amenities).to eq(["balcony"]) + end + end + end +end From 5bcebeecf6bb55c893274ebfa3ddf33be6c71536 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 5 Sep 2016 12:18:08 +0600 Subject: [PATCH 029/118] RU: spec update --- .../rentals_united/commands/property_fetcher_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 02b565f87..a61092ff4 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -96,12 +96,12 @@ ) end - it "sets amenities to empty array if there is no amenities" do - file_name = "rentals_united/properties/property_without_amenities.xml" + context "and when there is no amenities" do + let(:file_name) { "rentals_united/properties/property_without_amenities.xml" } - expect(property.amenities).to eq( - ["bed_linen_and_towels", "airconditioning", "pool", "wheelchairaccess", "elevator", "parking"] - ) + it "sets empty amenities" do + expect(property.amenities).to eq([]) + end end end From f6130fa7aafe196318a0ed8eae01a0ed66cb30b6 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 5 Sep 2016 16:19:39 +0600 Subject: [PATCH 030/118] RU: checkin/checkout mapping --- .../suppliers/rentals_united/mappers/property.rb | 13 +++++++++++++ .../commands/property_fetcher_spec.rb | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index cc1e1ded9..c54134345 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -34,6 +34,8 @@ def build_property property.address = property_hash.get("Street") property.postal_code = property_hash.get("ZipCode") property.amenities = amenities_dictionary.convert + property.check_in_time = check_in_time + property.check_out_time = check_out_time property_type = find_property_type(property_hash.get("ObjectTypeID")) @@ -67,6 +69,17 @@ def en_description(hash) en_description["Text"] if en_description end + + def check_in_time + from = property_hash.get("CheckInOut.CheckInFrom") + to = property_hash.get("CheckInOut.CheckInTo") + + "#{from}-#{to}" if from && to + end + + def check_out_time + property_hash.get("CheckInOut.CheckOutUntil") + end end end end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index a61092ff4..342732940 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -73,6 +73,14 @@ expect(property.description).to eq("Test description") end + it "sets check_in_time to the property" do + expect(property.check_in_time).to eq("13:00-17:00") + end + + it "sets check_out_time to the property" do + expect(property.check_out_time).to eq("11:00") + end + context "when multiple descriptions are available" do let(:file_name) { "rentals_united/properties/property_with_multiple_descriptions.xml" } From 90a148cfa6bb84ae5d55e487c990e10d9991661e Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 6 Sep 2016 16:14:50 +0600 Subject: [PATCH 031/118] RU: properties synchronisation update --- .../suppliers/rentals_united/metadata.rb | 44 ++++++++-- .../rentals_united/commands/cities_fetcher.rb | 43 ---------- .../commands/location_ids_fetcher.rb | 38 +++++++++ .../commands/locations_currencies_fetcher.rb | 39 +++++++++ .../commands/locations_fetcher.rb | 56 +++++++++++++ .../commands/property_fetcher.rb | 13 +-- .../rentals_united/converters/country_code.rb | 28 +++++++ .../suppliers/rentals_united/entities/city.rb | 20 ----- .../rentals_united/entities/location.rb | 14 ++++ .../suppliers/rentals_united/importer.rb | 35 ++++++-- .../suppliers/rentals_united/mappers/city.rb | 30 ------- .../rentals_united/mappers/location.rb | 75 +++++++++++++++++ .../rentals_united/mappers/property.rb | 24 +++++- .../rentals_united/payload_builder.rb | 14 +++- .../location_currencies_fetch.xml.erb | 6 ++ ...tch.xml.erb => location_ids_fetch.xml.erb} | 0 .../templates/locations_fetch.xml.erb | 6 ++ .../location_currencies/currencies.xml | 20 +++++ .../location_currencies/error_status.xml | 3 + .../{cities => location_ids}/empty_list.xml | 0 .../{cities => location_ids}/error_status.xml | 0 .../multiple_locations.xml} | 0 .../one_location.xml} | 0 .../rentals_united/locations/error_status.xml | 3 + .../rentals_united/locations/locations.xml | 17 ++++ .../location_currencies_fetcher_spec.rb | 84 +++++++++++++++++++ ...r_spec.rb => location_ids_fetcher_spec.rb} | 49 +++++------ .../commands/locations_fetcher_spec.rb | 79 +++++++++++++++++ .../commands/property_fetcher_spec.rb | 36 +++++++- .../suppliers/rentals_united/importer_spec.rb | 35 ++++++-- .../rentals_united/payload_builder_spec.rb | 26 +++++- 31 files changed, 681 insertions(+), 156 deletions(-) delete mode 100644 lib/concierge/suppliers/rentals_united/commands/cities_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/converters/country_code.rb delete mode 100644 lib/concierge/suppliers/rentals_united/entities/city.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/location.rb delete mode 100644 lib/concierge/suppliers/rentals_united/mappers/city.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/location.rb create mode 100644 lib/concierge/suppliers/rentals_united/templates/location_currencies_fetch.xml.erb rename lib/concierge/suppliers/rentals_united/templates/{cities_fetch.xml.erb => location_ids_fetch.xml.erb} (100%) create mode 100644 lib/concierge/suppliers/rentals_united/templates/locations_fetch.xml.erb create mode 100644 spec/fixtures/rentals_united/location_currencies/currencies.xml create mode 100644 spec/fixtures/rentals_united/location_currencies/error_status.xml rename spec/fixtures/rentals_united/{cities => location_ids}/empty_list.xml (100%) rename spec/fixtures/rentals_united/{cities => location_ids}/error_status.xml (100%) rename spec/fixtures/rentals_united/{cities/multiple_cities.xml => location_ids/multiple_locations.xml} (100%) rename spec/fixtures/rentals_united/{cities/one_city.xml => location_ids/one_location.xml} (100%) create mode 100644 spec/fixtures/rentals_united/locations/error_status.xml create mode 100644 spec/fixtures/rentals_united/locations/locations.xml create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/location_currencies_fetcher_spec.rb rename spec/lib/concierge/suppliers/rentals_united/commands/{cities_fetcher_spec.rb => location_ids_fetcher_spec.rb} (64%) create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index d436d608d..48faab3c3 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -11,17 +11,31 @@ def initialize(host) end def perform - result = fetch_cities + result = fetch_location_ids return unless result.success? - cities = result.value - cities.each do |city| - result = fetch_property_ids(city.location_id) + location_ids = result.value + + result = fetch_locations(location_ids) + return unless result.success? + + locations = result.value + + result = fetch_currencies + return unless result.success? + + currencies = result.value + + locations.each do |location| + location.currency = currencies[location.id] + return unless location.currency + + result = fetch_property_ids(location.id) return unless result.success? property_ids = result.value property_ids.each do |property_id| - result = fetch_property(property_id) + result = fetch_property(property_id, location) end end end @@ -38,9 +52,21 @@ def credentials ) end - def fetch_cities + def fetch_location_ids + announce_error("Failed to fetch location ids") do + importer.fetch_location_ids + end + end + + def fetch_locations(location_ids) announce_error("Failed to fetch locations") do - importer.fetch_cities + importer.fetch_locations(location_ids) + end + end + + def fetch_currencies + announce_error("Failed to fetch currencies") do + importer.fetch_currencies end end @@ -50,9 +76,9 @@ def fetch_property_ids(location_id) end end - def fetch_property(property_id) + def fetch_property(property_id, location) announce_error("Failed to fetch property with ID #{property_id}`") do - importer.fetch_property(property_id) + importer.fetch_property(property_id, location) end end diff --git a/lib/concierge/suppliers/rentals_united/commands/cities_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/cities_fetcher.rb deleted file mode 100644 index ca61ad6ad..000000000 --- a/lib/concierge/suppliers/rentals_united/commands/cities_fetcher.rb +++ /dev/null @@ -1,43 +0,0 @@ -module RentalsUnited - module Commands - # +RentalsUnited::Commands::CitiesFetcher+ - # - # This class is responsible for wrapping the logic related to fetching - # cities from RentalsUnited, parsing the response, and building - # +Result+ object. - class CitiesFetcher < BaseFetcher - attr_reader :location - - ROOT_TAG = "Pull_ListCitiesProps_RS" - - # Retrieves cities with active properties. - # - # Cities without active properties are ignored. - def fetch_cities - payload = payload_builder.build_cities_fetch_payload - result = http.post(credentials.url, payload, headers) - - return result unless result.success? - - result_hash = response_parser.to_hash(result.value.body) - - if valid_status?(result_hash, ROOT_TAG) - Result.new(build_cities(result_hash)) - else - error_result(result_hash, ROOT_TAG) - end - end - - private - def build_cities(hash) - cities = hash.get("#{ROOT_TAG}.CitiesProps.CityProps") - return [] unless cities - - Array(cities).map do |city| - mapper = RentalsUnited::Mappers::City.new(city) - mapper.build - end - end - end - end -end diff --git a/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb new file mode 100644 index 000000000..079039631 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb @@ -0,0 +1,38 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::LocationIdsFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # location ids from RentalsUnited + class LocationIdsFetcher < BaseFetcher + attr_reader :location + + ROOT_TAG = "Pull_ListCitiesProps_RS" + + # Retrieves location ids with active properties. + # + # Locations without active properties are ignored. + def fetch_location_ids + payload = payload_builder.build_location_ids_fetch_payload + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(parse_location_ids(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def parse_location_ids(hash) + locations = hash.get("#{ROOT_TAG}.CitiesProps.CityProps") + + Array(locations).map { |location| location.attributes["LocationID"] } + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb new file mode 100644 index 000000000..708de1a3d --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb @@ -0,0 +1,39 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::LocationCurrenciesFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # location currencies from RentalsUnited + class LocationCurrenciesFetcher < BaseFetcher + ROOT_TAG = "Pull_ListCurrenciesWithCities_RS" + + def fetch_location_currencies + payload = payload_builder.build_location_currencies_fetch_payload + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + location_currencies = {} + + currencies_hash = result_hash.get("#{ROOT_TAG}.Currencies.Currency") + currencies_hash.each do |currency_hash| + safe_hash = Concierge::SafeAccessHash.new(currency_hash) + location_ids = Array(safe_hash.get("Locations.LocationID")) + currency_code = safe_hash.get("@CurrencyCode") + + location_ids.each do |location_id| + location_currencies[location_id] = currency_code + end + end + + Result.new(location_currencies) + else + error_result(result_hash, ROOT_TAG) + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb new file mode 100644 index 000000000..29f73c011 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb @@ -0,0 +1,56 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::LocationsFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # locations from RentalsUnited, parsing the response, and building + # +Result+ object. + class LocationsFetcher < BaseFetcher + attr_reader :location_ids + + ROOT_TAG = "Pull_ListLocations_RS" + + def initialize(credentials, location_ids) + super(credentials) + @location_ids = location_ids + end + + def fetch_locations + payload = payload_builder.build_locations_fetch_payload + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + raw_locations = build_raw_locations(result_hash) + locations = location_ids.map { |id| build_location(id, raw_locations) } + + Result.new(locations) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_raw_locations(hash) + locations = hash.get("Pull_ListLocations_RS.Locations.Location") + + Array(locations).map do |l| + { + id: l.attributes["LocationID"], + name: l.to_s, + type: l.attributes["LocationTypeID"].to_i, + parent_id: l.attributes["ParentLocationID"] + } + end + end + + def build_location(location_id, raw_locations) + mapper = Mappers::Location.new(location_id, raw_locations) + mapper.build_location + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb index a5652d94c..2e33d0701 100644 --- a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb @@ -6,7 +6,7 @@ module Commands # property from RentalsUnited, parsing the response, and building # +Result+ object. class PropertyFetcher < BaseFetcher - attr_reader :property_id + attr_reader :property_id, :location ROOT_TAG = "Pull_ListSpecProp_RS" @@ -15,18 +15,21 @@ class PropertyFetcher < BaseFetcher # Arguments # # * +credentials+ - # * +property_id+ + # * +property_id+ [String] + # * +location+ [Entities::Location] # # Usage: # # RentalsUnited::Commands::PropertyFetcher.new( # credentials, - # property_id + # property_id, + # location # ) - def initialize(credentials, property_id) + def initialize(credentials, property_id, location) super(credentials) @property_id = property_id + @location = location end def fetch_property @@ -49,7 +52,7 @@ def build_property(hash) property = hash.get("#{ROOT_TAG}.Property") return error_result(hash, ROOT_TAG) unless property - mapper = RentalsUnited::Mappers::Property.new(property) + mapper = RentalsUnited::Mappers::Property.new(property, location) mapper.build_property end end diff --git a/lib/concierge/suppliers/rentals_united/converters/country_code.rb b/lib/concierge/suppliers/rentals_united/converters/country_code.rb new file mode 100644 index 000000000..3e3e7425e --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/converters/country_code.rb @@ -0,0 +1,28 @@ +module RentalsUnited + module Converters + # +RentalsUnited::Converters::CountryCode+ + # + # This class acts as a facade between country codes converter library and + # our source code for abstracting library's entities and using only needed + # part of the library API. + class CountryCode + class << self + # Returns country code by its name + # + # Arguments + # * +name+ [String] name of the country + # + # Example + # + # CountryCode.code_by_name("Korea, Republic of") + # => "KR" + # + # Returns [String] country code + def code_by_name(name) + country = IsoCountryCodes.search_by_name(name).first + (country && country.alpha2).to_s + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/entities/city.rb b/lib/concierge/suppliers/rentals_united/entities/city.rb deleted file mode 100644 index 8e89cdfe6..000000000 --- a/lib/concierge/suppliers/rentals_united/entities/city.rb +++ /dev/null @@ -1,20 +0,0 @@ -module RentalsUnited - module Entities - # +RentalsUnited::Entities::City+ - # - # This entity represents a city object - # - # Attributes - # - # +location_id+ - city location id - # +properties_count+ - number of properties in city - class City - attr_reader :location_id, :properties_count - - def initialize(location_id:, properties_count:) - @location_id = location_id - @properties_count = properties_count - end - end - end -end diff --git a/lib/concierge/suppliers/rentals_united/entities/location.rb b/lib/concierge/suppliers/rentals_united/entities/location.rb new file mode 100644 index 000000000..46bc02dc3 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/location.rb @@ -0,0 +1,14 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::Location+ + # + # This entity represents a location type object. + class Location + attr_accessor :id, :neighborhood, :city, :region, :country, :currency + + def initialize(id) + @id = id + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb index 28460dc40..5b43b6593 100644 --- a/lib/concierge/suppliers/rentals_united/importer.rb +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -14,12 +14,32 @@ def initialize(credentials) @credentials = credentials end - # Retrieves cities with active properties. + # Retrieves location ids with active properties. # - # Cities without active properties will be filtered out. - def fetch_cities - cities_fetcher = Commands::CitiesFetcher.new(credentials) - cities_fetcher.fetch_cities + # Locations without active properties will be filtered out. + def fetch_location_ids + fetcher = Commands::LocationIdsFetcher.new(credentials) + fetcher.fetch_location_ids + end + + # Retrieves locations by given location_ids. + # + # Arguments: + # + # * +location_ids+ [Array] ids array of locations to fetch + # + # Returns [Array] array of location objects + def fetch_locations(location_ids) + fetcher = Commands::LocationsFetcher.new(credentials, location_ids) + fetcher.fetch_locations + end + + # Retrieves locations - currencies mapping. + # + # Returns [Hash] hash with location_id => currency key-values + def fetch_location_currencies + fetcher = Commands::LocationCurrenciesFetcher.new(credentials) + fetcher.fetch_location_currencies end # Retrieves property ids by location id. @@ -34,10 +54,11 @@ def fetch_property_ids(location_id) end # Retrieves property by its id. - def fetch_property(property_id) + def fetch_property(property_id, location) property_fetcher = Commands::PropertyFetcher.new( credentials, - property_id + property_id, + location ) property_fetcher.fetch_property end diff --git a/lib/concierge/suppliers/rentals_united/mappers/city.rb b/lib/concierge/suppliers/rentals_united/mappers/city.rb deleted file mode 100644 index 4a65214db..000000000 --- a/lib/concierge/suppliers/rentals_united/mappers/city.rb +++ /dev/null @@ -1,30 +0,0 @@ -module RentalsUnited - module Mappers - # +RentalsUnited::Mappers::City+ - # - # This class is responsible for building a +RentalsUnited::Entities::City+ - # object from a hash which was fetched from the RentalsUnited API. - class City - attr_reader :city - - # Initialize +RentalsUnited::Mappers::City+ - # - # Arguments: - # - # * +city+ [Nori::StringWithAttributes] city object - def initialize(city) - @city = city - end - - # Builds a city - # - # Returns [RentalsUnited::Entities::City] - def build - Entities::City.new( - location_id: city.attributes["LocationID"], - properties_count: city.to_i - ) - end - end - end -end diff --git a/lib/concierge/suppliers/rentals_united/mappers/location.rb b/lib/concierge/suppliers/rentals_united/mappers/location.rb new file mode 100644 index 000000000..5fbcf505f --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/location.rb @@ -0,0 +1,75 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Location+ + # + # This class is responsible for building a location object. + class Location + attr_reader :location_id, :raw_locations_database + + # Rentals United mapping of location ids and location types. + # Worldwide and Continent type locations are not mapped. + LOCATION_TYPES = { + 2 => "Country", + 3 => "Region", + 4 => "City", + 5 => "District" + } + + # Initialize +RentalsUnited::Mappers::Location+ mapper + # + # Arguments: + # + # * +location_id+ [String] id of location + # * +raw_locations_database+ [Array] database of locations data + def initialize(location_id, raw_locations_database) + @location_id = location_id + @raw_locations_database = raw_locations_database + end + + # Iterate over location hierarchy by location type id attribute. + # + # Each level of hierarchy provide location data: region, city, country + # names. + # + # Iteration starts from the level of the current type of given location + # and ends when hits "Country" type. + def build_location + location = Entities::Location.new(location_id) + + current_level = find_location_data(location_id) + current_level_type = current_level[:type] + update_location(location, current_level) + + while(LOCATION_TYPES.keys.include?(current_level_type)) do + parent_location_id = current_level[:parent_id] + + current_level = find_location_data(parent_location_id) + current_level_type = current_level[:type] + + update_location(location, current_level) + end + + location + end + + def find_location_data(id) + raw_locations_database.find do |location| + location[:id] == id + end + end + + def update_location(location, current_level) + case current_level[:type] + when 5 + location.neighborhood = current_level[:name] + when 4 + location.city = current_level[:name] + when 3 + location.region = current_level[:name] + when 2 + location.country = current_level[:name] + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index c54134345..98630850f 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -6,17 +6,22 @@ module Mappers # +RentalsUnited::Entities::Property+ object from a hash which was fetched # from the RentalsUnited API. class Property - attr_reader :property_hash, :amenities_dictionary + attr_reader :property_hash, :location, :amenities_dictionary EN_DESCRIPTION_LANG_CODE = "1" + CANCELLATION_POLICY = "super_strict" + DEFAULT_PROPERTY_RATE = "9999" + MINIMUM_STAY = nil # Initialize +RentalsUnited::Mappers::Property+ # # Arguments: # # * +property_hash+ [Concierge::SafeAccessHash] property hash object - def initialize(property_hash) + # * +location+ [Entities::Location] location object + def initialize(property_hash, location) @property_hash = property_hash + @location = location @amenities_dictionary = Dictionaries::Amenities.new( property_hash.get("Amenities.Amenity") ) @@ -36,6 +41,17 @@ def build_property property.amenities = amenities_dictionary.convert property.check_in_time = check_in_time property.check_out_time = check_out_time + property.country_code = country_code(location) + property.currency = location.currency + property.city = location.city + property.neighborhood = location.neighborhood + property.nightly_rate = DEFAULT_PROPERTY_RATE + property.weekly_rate = DEFAULT_PROPERTY_RATE + property.monthly_rate = DEFAULT_PROPERTY_RATE + property.minimum_stay = MINIMUM_STAY + property.cancellation_policy = CANCELLATION_POLICY + property.default_to_available = true + property.instant_booking! property_type = find_property_type(property_hash.get("ObjectTypeID")) @@ -80,6 +96,10 @@ def check_in_time def check_out_time property_hash.get("CheckInOut.CheckOutUntil") end + + def country_code(location) + Converters::CountryCode.code_by_name(location.country) + end end end end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index 0416803ab..4ffb94c0e 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -21,9 +21,19 @@ def build_property_ids_fetch_payload(location_id) render(:property_ids_fetch, template_locals) end - def build_cities_fetch_payload + def build_location_ids_fetch_payload template_locals = { credentials: credentials } - render(:cities_fetch, template_locals) + render(:location_ids_fetch, template_locals) + end + + def build_locations_fetch_payload + template_locals = { credentials: credentials } + render(:locations_fetch, template_locals) + end + + def build_location_currencies_fetch_payload + template_locals = { credentials: credentials } + render(:location_currencies_fetch, template_locals) end def build_property_fetch_payload(property_id) diff --git a/lib/concierge/suppliers/rentals_united/templates/location_currencies_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/location_currencies_fetch.xml.erb new file mode 100644 index 000000000..dfd13dcbf --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/location_currencies_fetch.xml.erb @@ -0,0 +1,6 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + diff --git a/lib/concierge/suppliers/rentals_united/templates/cities_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/location_ids_fetch.xml.erb similarity index 100% rename from lib/concierge/suppliers/rentals_united/templates/cities_fetch.xml.erb rename to lib/concierge/suppliers/rentals_united/templates/location_ids_fetch.xml.erb diff --git a/lib/concierge/suppliers/rentals_united/templates/locations_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/locations_fetch.xml.erb new file mode 100644 index 000000000..44125597d --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/locations_fetch.xml.erb @@ -0,0 +1,6 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + diff --git a/spec/fixtures/rentals_united/location_currencies/currencies.xml b/spec/fixtures/rentals_united/location_currencies/currencies.xml new file mode 100644 index 000000000..65568a6d2 --- /dev/null +++ b/spec/fixtures/rentals_united/location_currencies/currencies.xml @@ -0,0 +1,20 @@ + + Success + + + + 7892 + + + + + 4530 + 4977 + + + + + + + + diff --git a/spec/fixtures/rentals_united/location_currencies/error_status.xml b/spec/fixtures/rentals_united/location_currencies/error_status.xml new file mode 100644 index 000000000..7da93453d --- /dev/null +++ b/spec/fixtures/rentals_united/location_currencies/error_status.xml @@ -0,0 +1,3 @@ + + Test Error + diff --git a/spec/fixtures/rentals_united/cities/empty_list.xml b/spec/fixtures/rentals_united/location_ids/empty_list.xml similarity index 100% rename from spec/fixtures/rentals_united/cities/empty_list.xml rename to spec/fixtures/rentals_united/location_ids/empty_list.xml diff --git a/spec/fixtures/rentals_united/cities/error_status.xml b/spec/fixtures/rentals_united/location_ids/error_status.xml similarity index 100% rename from spec/fixtures/rentals_united/cities/error_status.xml rename to spec/fixtures/rentals_united/location_ids/error_status.xml diff --git a/spec/fixtures/rentals_united/cities/multiple_cities.xml b/spec/fixtures/rentals_united/location_ids/multiple_locations.xml similarity index 100% rename from spec/fixtures/rentals_united/cities/multiple_cities.xml rename to spec/fixtures/rentals_united/location_ids/multiple_locations.xml diff --git a/spec/fixtures/rentals_united/cities/one_city.xml b/spec/fixtures/rentals_united/location_ids/one_location.xml similarity index 100% rename from spec/fixtures/rentals_united/cities/one_city.xml rename to spec/fixtures/rentals_united/location_ids/one_location.xml diff --git a/spec/fixtures/rentals_united/locations/error_status.xml b/spec/fixtures/rentals_united/locations/error_status.xml new file mode 100644 index 000000000..fd7655cf8 --- /dev/null +++ b/spec/fixtures/rentals_united/locations/error_status.xml @@ -0,0 +1,3 @@ + + Test Error + diff --git a/spec/fixtures/rentals_united/locations/locations.xml b/spec/fixtures/rentals_united/locations/locations.xml new file mode 100644 index 000000000..c9534f5d4 --- /dev/null +++ b/spec/fixtures/rentals_united/locations/locations.xml @@ -0,0 +1,17 @@ + + Success + + Worldwide + Europe + France + Germany + Greece + Hungary + Languedoc-Roussillon + Ile-de-France + Haute-Normandie + Pantin + Paris + Plaisir + + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/location_currencies_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/location_currencies_fetcher_spec.rb new file mode 100644 index 000000000..c38d18f98 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/location_currencies_fetcher_spec.rb @@ -0,0 +1,84 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::LocationCurrenciesFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:subject) { described_class.new(credentials) } + let(:url) { credentials.url } + + it "fetches currencies for locations" do + stub_data = read_fixture("rentals_united/location_currencies/currencies.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_location_currencies + expect(result).to be_success + expect(result.value.size).to eq(3) + expect(result.value["7892"]).to eq("AUD") + expect(result.value["4530"]).to eq("CAD") + expect(result.value["4977"]).to eq("CAD") + end + + it "returns nil locations which has no currency" do + stub_data = read_fixture("rentals_united/location_currencies/currencies.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_location_currencies + expect(result).to be_success + expect(result.value.size).to eq(3) + expect(result.value["1111"]).to be_nil + end + + context "when response from the api has error status" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/location_currencies/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_location_currencies + + expect(result).not_to be_success + expect(result.error.code).to eq("9999") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `9999`, and description ``" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_location_currencies + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.fetch_location_currencies + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/cities_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher_spec.rb similarity index 64% rename from spec/lib/concierge/suppliers/rentals_united/commands/cities_fetcher_spec.rb rename to spec/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher_spec.rb index 4ea994af6..948f4d840 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/cities_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher_spec.rb @@ -1,62 +1,57 @@ require "spec_helper" -RSpec.describe RentalsUnited::Commands::CitiesFetcher do +RSpec.describe RentalsUnited::Commands::LocationIdsFetcher do include Support::HTTPStubbing include Support::Fixtures let(:credentials) { Concierge::Credentials.for("rentals_united") } let(:subject) { described_class.new(credentials) } let(:url) { credentials.url } - let(:expected_locations) {{'1505' => 1, '2503' => 1, '1144' => 2}} + let(:expected_locations) { ['1505', '2503', '1144'] } it "returns an empty array when there is no active properties" do - stub_data = read_fixture("rentals_united/cities/empty_list.xml") + stub_data = read_fixture("rentals_united/location_ids/empty_list.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_cities + result = subject.fetch_location_ids expect(result).to be_success expect(result.value).to eq([]) end - it "returns city object when there is one city" do - stub_data = read_fixture("rentals_united/cities/one_city.xml") + it "returns array with location id when there is one location" do + stub_data = read_fixture("rentals_united/location_ids/one_location.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_cities + result = subject.fetch_location_ids expect(result).to be_success expect(result.value).to be_kind_of(Array) expect(result.value.size).to eq(1) - expect(result.value).to all(be_kind_of(RentalsUnited::Entities::City)) + expect(result.value).to all(be_kind_of(String)) - city = result.value.first - expect(city.location_id).to eq("1505") - expect(city.properties_count).to eq(1) + location_id = result.value.first + expect(location_id).to eq("1505") end - it "returns multiple city objects" do - stub_data = read_fixture("rentals_united/cities/multiple_cities.xml") + it "returns multiple location ids" do + stub_data = read_fixture("rentals_united/location_ids/multiple_locations.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_cities + result = subject.fetch_location_ids expect(result).to be_success - cities = result.value - expect(cities).to be_kind_of(Array) - expect(cities.size).to eq(3) - expect(cities).to all(be_kind_of(RentalsUnited::Entities::City)) - - expected_locations.each do |location_id, properties_count| - city = cities.find { |c| c.location_id == location_id } - expect(city.properties_count).to eq(properties_count) - end + location_ids = result.value + expect(location_ids).to be_kind_of(Array) + expect(location_ids.size).to eq(3) + expect(location_ids).to all(be_kind_of(String)) + expect(location_ids.sort).to eq(expected_locations.sort) end context "when response from the api has error status" do it "returns a result with an appropriate error" do - stub_data = read_fixture("rentals_united/cities/error_status.xml") + stub_data = read_fixture("rentals_united/location_ids/error_status.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_cities + result = subject.fetch_location_ids expect(result).not_to be_success expect(result.error.code).to eq("9999") @@ -75,7 +70,7 @@ stub_data = read_fixture("rentals_united/bad_xml.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_cities + result = subject.fetch_location_ids expect(result).not_to be_success expect(result.error.code).to eq(:unrecognised_response) @@ -93,7 +88,7 @@ it "returns a result with an appropriate error" do stub_call(:post, url) { raise Faraday::TimeoutError } - result = subject.fetch_cities + result = subject.fetch_location_ids expect(result).not_to be_success expect(result.error.code).to eq :connection_timeout diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb new file mode 100644 index 000000000..87bc061fb --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb @@ -0,0 +1,79 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::LocationsFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:location_ids) { ["1505"] } + let(:subject) { described_class.new(credentials, location_ids) } + let(:url) { credentials.url } + + it "fetches and returns one location" do + stub_data = read_fixture("rentals_united/locations/locations.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).to be_success + expect(result.value.size).to eq(1) + + location = result.value.first + expect(location).to be_kind_of(RentalsUnited::Entities::Location) + expect(location.id).to eq("1505") + expect(location.city).to eq("Paris") + expect(location.region).to eq("Ile-de-France") + expect(location.country).to eq("France") + end + + context "when response from the api has error status" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/locations/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + + expect(result).not_to be_success + expect(result.error.code).to eq("9999") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `9999`, and description ``" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.fetch_locations + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 342732940..196d9e9f5 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -6,7 +6,16 @@ let(:credentials) { Concierge::Credentials.for("rentals_united") } let(:property_id) { "1234" } - let(:subject) { described_class.new(credentials, property_id) } + let(:location) do + double( + id: '1505', + city: 'Paris', + neighborhood: 'Ile-de-France', + country: 'France', + currency: 'EUR' + ) + end + let(:subject) { described_class.new(credentials, property_id, location) } let(:url) { credentials.url } it "returns an error if property does not exist" do @@ -64,9 +73,10 @@ expect(property.lat).to eq(55.0003426) expect(property.lng).to eq(73.2965942999999) expect(property.address).to eq("Test street address") - # expect(property.city).to eq("Namyangju-si") - # expect(property.neighborhood).to eq("Gyeonggi-do") + expect(property.city).to eq("Paris") + expect(property.neighborhood).to eq("Ile-de-France") expect(property.postal_code).to eq("644119") + expect(property.country_code).to eq("FR") end it "sets en description to the property" do @@ -81,6 +91,26 @@ expect(property.check_out_time).to eq("11:00") end + it "sets currency to the property" do + expect(property.currency).to eq("EUR") + end + + it "sets cancellation_policy to the property" do + expect(property.cancellation_policy).to eq("super_strict") + end + + it "sets default_to_available flag" do + expect(property.default_to_available).to eq(true) + end + + it "does not set multi-unit flag" do + expect(property.multi_unit).to eq(false) + end + + it "sets instant_booking flag" do + expect(property.instant_booking?).to eq(true) + end + context "when multiple descriptions are available" do let(:file_name) { "rentals_united/properties/property_with_multiple_descriptions.xml" } diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb index 54ee166d4..7c55ec1df 100644 --- a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -7,12 +7,34 @@ let(:credentials) { Concierge::Credentials.for("rentals_united") } let(:importer) { described_class.new(credentials) } - describe "#fetch_cities" do - it "calls fetcher class to load cities " do - fetcher_class = RentalsUnited::Commands::CitiesFetcher + describe "#fetch_location_ids" do + it "calls fetcher class to load location ids" do + fetcher_class = RentalsUnited::Commands::LocationIdsFetcher - expect_any_instance_of(fetcher_class).to(receive(:fetch_cities)) - importer.fetch_cities + expect_any_instance_of(fetcher_class).to(receive(:fetch_location_ids)) + importer.fetch_location_ids + end + end + + describe "#fetch_locations" do + let(:location_ids) { ['10', '20', '30'] } + + it "calls fetcher class to load locations" do + fetcher_class = RentalsUnited::Commands::LocationsFetcher + + expect_any_instance_of(fetcher_class).to(receive(:fetch_locations)) + importer.fetch_locations(location_ids) + end + end + + describe "#fetch_location_currencies" do + it "calls fetcher class to load locations and currencies" do + fetcher_class = RentalsUnited::Commands::LocationCurrenciesFetcher + + expect_any_instance_of(fetcher_class).to( + receive(:fetch_location_currencies) + ) + importer.fetch_location_currencies end end @@ -29,12 +51,13 @@ describe "#fetch_property" do let(:property_id) { "588788" } + let(:location) { double(id: '1') } it "calls fetcher class to load property" do fetcher_class = RentalsUnited::Commands::PropertyFetcher expect_any_instance_of(fetcher_class).to(receive(:fetch_property)) - importer.fetch_property(property_id) + importer.fetch_property(property_id, location) end end end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index d6d0255d5..734f96b13 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -29,9 +29,9 @@ end end - describe '#build_cities_fetch_payload' do + describe '#build_location_ids_fetch_payload' do it 'embedds username and password to request' do - xml = builder.build_cities_fetch_payload + xml = builder.build_location_ids_fetch_payload hash = to_hash(xml) authentication = hash.get("Pull_ListCitiesProps_RQ.Authentication") @@ -40,6 +40,28 @@ end end + describe '#build_locations_fetch_payload' do + it 'embedds username and password to request' do + xml = builder.build_locations_fetch_payload + hash = to_hash(xml) + + authentication = hash.get("Pull_ListLocations_RQ.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + end + + describe '#build_location_currencies_fetch_payload' do + it 'embedds username and password to request' do + xml = builder.build_location_currencies_fetch_payload + hash = to_hash(xml) + + authentication = hash.get("Pull_ListCurrenciesWithCities_RQ.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + end + describe '#build_property_ids_fetch_payload' do let(:params) do { From c404cf07480b25b7e304e5f91a013fd78d309c05 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 6 Sep 2016 17:44:58 +0600 Subject: [PATCH 032/118] RU: metadata worker update --- .../suppliers/rentals_united/metadata.rb | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index 48faab3c3..cf49bafbc 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -28,14 +28,20 @@ def perform locations.each do |location| location.currency = currencies[location.id] - return unless location.currency - result = fetch_property_ids(location.id) - return unless result.success? - - property_ids = result.value - property_ids.each do |property_id| - result = fetch_property(property_id, location) + if location.currency + result = fetch_property_ids(location.id) + return unless result.success? + + property_ids = result.value + property_ids.each do |property_id| + synchronisation.start(property_id) do + fetch_property(property_id, location) + end + end + else + announce_context_error(message) + return end end end @@ -65,7 +71,7 @@ def fetch_locations(location_ids) end def fetch_currencies - announce_error("Failed to fetch currencies") do + announce_error("Failed to fetch locations-currencies mapping") do importer.fetch_currencies end end From e5755f1e026ed38c0b0fb64f04bfc06107a5a85d Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 6 Sep 2016 17:47:25 +0600 Subject: [PATCH 033/118] RU: fix spec --- .../suppliers/rentals_united/commands/property_fetcher_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 196d9e9f5..225b11a51 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -104,7 +104,7 @@ end it "does not set multi-unit flag" do - expect(property.multi_unit).to eq(false) + expect(property.multi_unit).to be_nil end it "sets instant_booking flag" do From 97289daac6b458a157f41abe8ad24c0cca7adc92 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 6 Sep 2016 21:48:19 +0600 Subject: [PATCH 034/118] RU: add descriptions for error statuses (will be helpful for debugging errors) --- .../rentals_united/commands/base_fetcher.rb | 10 +- .../rentals_united/dictionaries/statuses.json | 133 ++++++++++++++++++ .../rentals_united/dictionaries/statuses.rb | 23 +++ .../commands/property_fetcher_spec.rb | 2 +- 4 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 lib/concierge/suppliers/rentals_united/dictionaries/statuses.json create mode 100644 lib/concierge/suppliers/rentals_united/dictionaries/statuses.rb diff --git a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb index 083dfeceb..6cfde15dd 100644 --- a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb @@ -3,6 +3,8 @@ module Commands class BaseFetcher attr_reader :credentials + VALID_RU_STATUS_CODES = ["0"] + def initialize(credentials) @credentials = credentials end @@ -31,12 +33,16 @@ def get_status_code(status) status.attributes["ID"] end + def get_status_description(code) + RentalsUnited::Dictionaries::Statuses.find(code) + end + def valid_status?(hash, root_tag_name) status = get_status(hash, root_tag_name) return false unless status - get_status_code(status) == "0" + VALID_RU_STATUS_CODES.include?(get_status_code(status)) end def error_result(hash, root_tag_name) @@ -44,7 +50,7 @@ def error_result(hash, root_tag_name) if status code = get_status_code(status) - description = nil + description = get_status_description(code) augment_with_error(code, description, caller) Result.error(code) diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/statuses.json b/lib/concierge/suppliers/rentals_united/dictionaries/statuses.json new file mode 100644 index 000000000..b82e633a3 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/dictionaries/statuses.json @@ -0,0 +1,133 @@ +{ + "0": "Success", + "1": "Property is not available for a given dates", + "10": "Could not insert early departure fee, From:{0} To:{1} Fee:{2}", + "100": "Property description is required", + "101": "Pets not allowed", + "102": "Currency doesn't match with city currency", + "103": "Properties collection cannot be empty", + "104": "You need provide at least one value to modify stay.", + "105": "Some periods overlap. Periods must be separable.", + "106": "You can only modify stay in confirmed reservation.", + "107": "No reserved apartment found.", + "108": "Client Price cannot be negative", + "109": "Already Paid cannot be negative.", + "11": "Wrong payment method id:{0}", + "110": "Can not use OwnerID created by other users.", + "111": "Only property owner can add reviews.", + "112": "Review rating value must be between 0-5", + "113": "Submitted date must be later than arrival date", + "114": "Cannot remove confirmed reservation. Some periods ignored.", + "115": "MinStays collection cannot be empty.", + "116": "LocationID {0} is not proper city location.", + "117": "Only one description allowed per language.", + "118": "Max number of guests must be of positive value.", + "119": "Property name is not defined.", + "12": "Wrong deposit type id:{0}", + "120": "Check-in / check-out details are incorrect.", + "121": "Reservation not mapped in PMS. Contact IT support with Rentals United ID and your PMS Reservation ID.", + "122": "Failed to modify reservation in PMS. Try again or contact IT support for more information.", + "123": "Failed to cancel reservation in PMS. Try again or contact IT support for more information.", + "124": "Failed to insert reservation in PMS. Try again or contact IT support for more information.", + "125": "Wrong quantity of amenities. It should be between 0 - 32767.", + "126": "Invalid URL.", + "127": "Missing mandatory element: {0}.", + "128": "Cancellation policy text cannot be empty.", + "129": "Only reservations for apartments from same city are allowed", + "13": "Cancallation policies overlaps", + "130": "Cannot change apartment from city other than initial reservation. Cancel this reservation and create new one.", + "14": "Owner does not exist", + "15": "Apartment name ({0}) already exist in database.", + "16": "You already defined apartment with PUID:{0}", + "17": "Unexpected error, contact IT or try again", + "18": "Property with given ID does not exist.", + "19": "Dates mishmash", + "2": "Nothing available for a given dates", + "20": "Past dates", + "21": "Weird block dates for property: {0} - {1} - {2}. Whole block is {3} - {4}", + "22": "We have confirmed reservation for those dates. Please cancel the reservation instead of marking the dates as available.", + "23": "Wrong ImageTypeID:{0}", + "24": "Your are not the owner of the apartment.", + "25": "The value of 'Bigger' must be smaller than the value of 'Smaller'.", + "26": "Warning! Look at Notifs collection.", + "27": "DaysToArrivalFrom and DaysToArrivalTo requires positive values.", + "28": "Reservation does not exist.", + "29": "Requested stay, cost details do not match with property on reservation on hold.", + "3": "Property has no price settings for a given dates", + "30": "Element ignored because of other errors.", + "31": "Error occured. All changes rolled back.", + "32": "Bigger and Smaller requires positive values.", + "33": "Smaller is smaller than Bigger.", + "34": "RUPrice is not valid. Correct price is:{0}", + "35": "AlreadyPaid is bigger than ClientPrice.", + "36": "Wrong DetailedLocationID. City or district precision is required.", + "37": "Property name is too long (max 50).", + "38": "Property has missing data and cannot be offered.", + "39": "Location does not exist.", + "4": "Wrong destination id:{0}", + "40": "You cannot define discounts before the prices. The property has missing prices in given dates.", + "41": "The reservation was created by the other user.", + "42": "The reservation is expired.", + "43": "You cannot confirm this reservation. It's broken.", + "44": "The apartments are not in the same city.", + "45": "Data validation error.", + "46": "The property is not active. PropertyID:{0}", + "47": "Property is not available for a given dates. PropertyID:{0}", + "48": "The reservation is not on Put On Hold status.", + "49": "CountryID does not exist.", + "5": "Wrong distance unit id:{0}", + "50": "Guest name is required.", + "51": "Guest surname is required.", + "52": "Guest email is required.", + "53": "This method is deprecated. Use Push_PutConfirmedReservationMulti_RS", + "54": "This method is deprecated. Use Push_PutPropertiesOnHold_RQ", + "55": "Negative values in price elements is not allowed.", + "56": "Property does not exist.", + "57": "The request contains both types of composition definitions: composition and composition with amenities. Please use only one type.", + "58": "This amenity: {0} is not allowed in room type: {1}", + "59": "Positive value is required", + "6": "Wrong composition room id:{0}", + "60": "Duplicate value in LOSS element", + "61": "Duplicate value in EGPS element", + "62": "Missing Text or Image value.", + "63": "Wrong laguage id:{0}.", + "64": "DayOfWeek attribute must be between {0} and {1}.", + "65": "No permission to property {0}.", + "66": "Coordinates are missing or are invalid.", + "67": "Duplicate value in LOSPS element", + "68": "NumberOfGuests in LOSP element has to be greather than 0", + "69": "Building does not exist", + "7": "Wrong amenity id:{0}", + "70": "Some properties not updated:{0}", + "71": "Wrong security deposit type id: {0}", + "72": "Discount value can't be lower than 0.", + "73": "At least one PropertyID element is required.", + "74": "DateFrom has to be earlier than DateTo.", + "75": "DateFrom has to be earlier or equal to DateTo.", + "76": "Number of guests exceedes the maximum allowed.", + "77": "NOP: positive value required.", + "78": "Minimum stay is not valid (X nights).", + "79": "Stay period doesn't match with minimum stay", + "8": "Wrong arrival instructions", + "80": "Cannot activate archived property", + "81": "You don't have permission to modify this owner", + "82": "Apartment is Archived or no longer available or not Active", + "83": "Mixed owners in the request. Contact IT.", + "84": "Too many properties in your request (max 100).", + "85": "Invalid time value. Allowed values 00:00 - 23:59", + "86": "Operation has reached the maximum limit of time. The results are not complete.", + "87": "Wrong page URL Type", + "88": "Wrong date format for parameter {0}", + "89": "Stay period doesn't match with changeover", + "9": "Could not insert late arrival fee, From:{0} To:{1} Fee:{2}", + "90": "Enqueued", + "91": "Not found", + "92": "Duplicate value in distances.", + "93": "Unauthorized", + "94": "Some of required fields were not filled.", + "95": "Email already exists.", + "96": "Password must be at least 8 characters long.", + "97": "Standard number of guests must be of positive value.", + "98": "Deposit amount can't exceed value of 214,748.3647", + "99": "Technical error - missing file" +} diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/statuses.rb b/lib/concierge/suppliers/rentals_united/dictionaries/statuses.rb new file mode 100644 index 000000000..04dd1ab15 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/dictionaries/statuses.rb @@ -0,0 +1,23 @@ +module RentalsUnited + module Dictionaries + class Statuses + class << self + def find(id) + statuses_hash[id] + end + + private + def statuses_hash + @statuses_hash ||= JSON.parse(File.read(file_path)) + end + + def file_path + Hanami.root.join( + "lib/concierge/suppliers/rentals_united/dictionaries", + "statuses.json" + ).to_s + end + end + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 225b11a51..011c8d405 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -28,7 +28,7 @@ event = Concierge.context.events.last.to_h expect(event[:message]).to eq( - "Response indicating the Status with ID `56`, and description ``" + "Response indicating the Status with ID `56`, and description `Property does not exist.`" ) expect(event[:backtrace]).to be_kind_of(Array) expect(event[:backtrace].any?).to be true From dc41a8a9a1d21a7fdf0178d37f00b99b457e72fa Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 6 Sep 2016 22:01:23 +0600 Subject: [PATCH 035/118] RU: add dictionaries specs --- .../dictionaries/property_types_spec.rb | 36 +++++++++++++++++++ .../dictionaries/statuses_spec.rb | 18 ++++++++++ 2 files changed, 54 insertions(+) create mode 100644 spec/lib/concierge/suppliers/rentals_united/dictionaries/property_types_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/dictionaries/statuses_spec.rb diff --git a/spec/lib/concierge/suppliers/rentals_united/dictionaries/property_types_spec.rb b/spec/lib/concierge/suppliers/rentals_united/dictionaries/property_types_spec.rb new file mode 100644 index 000000000..1a1a5dfb5 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/dictionaries/property_types_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Dictionaries::PropertyTypes do + describe "#find" do + it "returns property type by its id" do + property_type = described_class.find("35") + + expect(property_type).to be_kind_of( + RentalsUnited::Entities::PropertyType + ) + expect(property_type.id).to eq("35") + expect(property_type.name).to eq("Villa") + expect(property_type.roomorama_name).to eq("house") + expect(property_type.roomorama_subtype_name).to eq("villa") + end + + it "returns nil if property type was not found" do + property_type = described_class.find("3500") + + expect(property_type).to be_nil + end + end + + describe "#all" do + it "returns all property types" do + property_types = described_class.all + + expect(property_types.size).to eq(12) + expect(property_types).to all( + be_kind_of(RentalsUnited::Entities::PropertyType) + ) + end + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/dictionaries/statuses_spec.rb b/spec/lib/concierge/suppliers/rentals_united/dictionaries/statuses_spec.rb new file mode 100644 index 000000000..8e815f70c --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/dictionaries/statuses_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Dictionaries::Statuses do + let(:supported_error_codes) { 0..130 } + + it "finds error description" do + message = described_class.find("118") + expect(message).to eq("Max number of guests must be of positive value.") + end + + it "finds descriptions for all supported error codes" do + supported_error_codes.each do |error_code| + expect(described_class.find(error_code.to_s)).not_to be_nil + end + end + end +end From 3ef6e3977efc4138b957222c60f7f81b0cc7a8f9 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 6 Sep 2016 22:03:45 +0600 Subject: [PATCH 036/118] RU: add country code converter spec --- .../rentals_united/converters/country_code_spec.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 spec/lib/concierge/suppliers/rentals_united/converters/country_code_spec.rb diff --git a/spec/lib/concierge/suppliers/rentals_united/converters/country_code_spec.rb b/spec/lib/concierge/suppliers/rentals_united/converters/country_code_spec.rb new file mode 100644 index 000000000..627faf749 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/converters/country_code_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Converters::CountryCode do + it "returns country code by its name" do + code = described_class.code_by_name("Kuwait") + expect(code).to eq("KW") + end + end +end From 6afbad355af7929d0c7eba16e8456bfc128fec63 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 6 Sep 2016 22:18:08 +0600 Subject: [PATCH 037/118] RU: documentation update --- .../dictionaries/property_types.rb | 18 +++++++++++++++++ .../rentals_united/dictionaries/statuses.rb | 20 +++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb index 81a5086be..2d9f04a52 100644 --- a/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb +++ b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb @@ -1,11 +1,29 @@ module RentalsUnited module Dictionaries + # +RentalsUnited::Dictionaries::PropertyTypes+ + # + # This class is responsible for mapping property types between RU and + # Roomorama APIs. class PropertyTypes class << self + # Find property type by its id + # + # Arguments + # + # * +id+ [String] id of property type + # + # Usage + # + # RentalsUnited::Dictionaries::PropertyTypes.find("35") + # + # Returns [Entities::PropertyType] property type object def find(id) all.find { |p| p.id == id } end + # Find all property types + # + # Returns [Array] array of property types def all @all ||= property_hashes.map do |hash| Entities::PropertyType.new( diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/statuses.rb b/lib/concierge/suppliers/rentals_united/dictionaries/statuses.rb index 04dd1ab15..fe3aee106 100644 --- a/lib/concierge/suppliers/rentals_united/dictionaries/statuses.rb +++ b/lib/concierge/suppliers/rentals_united/dictionaries/statuses.rb @@ -1,9 +1,25 @@ module RentalsUnited module Dictionaries + # +RentalsUnited::Dictionaries::Statuses+ + # + # This class is responsible for mapping RentalsUnited error codes and + # descriptions. class Statuses class << self - def find(id) - statuses_hash[id] + # Return error descriptions by error code + # + # Arguments + # + # * +code+ [String] error code + # + # Usage + # + # RentalsUnited::Dictionaries::Statuses.find("1") + # => "Property is not available for a given dates" + # + # Returns [String] error description + def find(code) + statuses_hash[code] end private From 61f7decd0b514772416c23476c2134d171de4dd3 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 7 Sep 2016 08:04:45 +0600 Subject: [PATCH 038/118] RU: split method into two --- .../commands/locations_currencies_fetcher.rb | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb index 708de1a3d..2f35e1298 100644 --- a/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb @@ -16,24 +16,30 @@ def fetch_location_currencies result_hash = response_parser.to_hash(result.value.body) if valid_status?(result_hash, ROOT_TAG) - location_currencies = {} - - currencies_hash = result_hash.get("#{ROOT_TAG}.Currencies.Currency") - currencies_hash.each do |currency_hash| - safe_hash = Concierge::SafeAccessHash.new(currency_hash) - location_ids = Array(safe_hash.get("Locations.LocationID")) - currency_code = safe_hash.get("@CurrencyCode") - - location_ids.each do |location_id| - location_currencies[location_id] = currency_code - end - end - + location_currencies = build_location_currencies(result_hash) Result.new(location_currencies) else error_result(result_hash, ROOT_TAG) end end + + private + def build_location_currencies(result_hash) + location_currencies = {} + + currencies_hash = result_hash.get("#{ROOT_TAG}.Currencies.Currency") + currencies_hash.each do |currency_hash| + safe_hash = Concierge::SafeAccessHash.new(currency_hash) + location_ids = Array(safe_hash.get("Locations.LocationID")) + currency_code = safe_hash.get("@CurrencyCode") + + location_ids.each do |location_id| + location_currencies[location_id] = currency_code + end + end + + location_currencies + end end end end From d76d88d79e9c2c30e0a1a4b14a6d4b531d007d9d Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 15:34:00 +0600 Subject: [PATCH 039/118] RU: fix issues while metadata sync --- .../suppliers/rentals_united/metadata.rb | 8 ++-- .../suppliers/rentals_united/client.rb | 12 ++++++ .../rentals_united/dictionaries/bedrooms.json | 33 +++++++++++++++ .../rentals_united/dictionaries/bedrooms.rb | 40 +++++++++++++++++++ .../rentals_united/mappers/property.rb | 12 +++++- .../rentals_united/properties/property.xml | 2 +- .../commands/property_fetcher_spec.rb | 8 ++++ .../dictionaries/bedrooms_spec.rb | 25 ++++++++++++ 8 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 lib/concierge/suppliers/rentals_united/client.rb create mode 100644 lib/concierge/suppliers/rentals_united/dictionaries/bedrooms.json create mode 100644 lib/concierge/suppliers/rentals_united/dictionaries/bedrooms.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms_spec.rb diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index cf49bafbc..ea460c822 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -21,7 +21,7 @@ def perform locations = result.value - result = fetch_currencies + result = fetch_location_currencies return unless result.success? currencies = result.value @@ -44,6 +44,8 @@ def perform return end end + + synchronisation.finish! end private @@ -70,9 +72,9 @@ def fetch_locations(location_ids) end end - def fetch_currencies + def fetch_location_currencies announce_error("Failed to fetch locations-currencies mapping") do - importer.fetch_currencies + importer.fetch_location_currencies end end diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb new file mode 100644 index 000000000..179bfa4ca --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -0,0 +1,12 @@ +module RentalsUnited + # +RentalsUnited::Client+ + class Client + SUPPLIER_NAME = "rentals_united" + + attr_reader :credentials + + def initialize(credentials) + @credentials = credentials + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms.json b/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms.json new file mode 100644 index 000000000..729810e4d --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms.json @@ -0,0 +1,33 @@ +[ + { "type_id": "1", "bedrooms": 0, "name": "Studio" }, + { "type_id": "2", "bedrooms": 1, "name": "One Bedroom" }, + { "type_id": "3", "bedrooms": 2, "name": "Two Bedroom" }, + { "type_id": "4", "bedrooms": 3, "name": "Three Bedroom" }, + { "type_id": "11", "bedrooms": 5, "name": "Five Bedroom" }, + { "type_id": "12", "bedrooms": 4, "name": "Four Bedroom" }, + { "type_id": "26", "bedrooms": 6, "name": "Six Bedroom" }, + { "type_id": "27", "bedrooms": 7, "name": "Seven Bedroom" }, + { "type_id": "28", "bedrooms": 8, "name": "Eight Bedroom" }, + { "type_id": "29", "bedrooms": 9, "name": "Nine Bedroom" }, + { "type_id": "30", "bedrooms": 10, "name": "Ten Bedroom" }, + { "type_id": "34", "bedrooms": 11, "name": "Eleven Bedroom" }, + { "type_id": "35", "bedrooms": 12, "name": "Twelve Bedroom" }, + { "type_id": "36", "bedrooms": 13, "name": "Thirteen Bedroom" }, + { "type_id": "37", "bedrooms": 14, "name": "Fourteen Bedroom" }, + { "type_id": "38", "bedrooms": 15, "name": "Fifteen Bedroom" }, + { "type_id": "39", "bedrooms": 16, "name": "Sixteen Bedroom" }, + { "type_id": "40", "bedrooms": 17, "name": "Seventeen Bedroom" }, + { "type_id": "41", "bedrooms": 18, "name": "Eighteen Bedroom" }, + { "type_id": "42", "bedrooms": 19, "name": "Nineteen Bedroom" }, + { "type_id": "43", "bedrooms": 20, "name": "Twenty Bedroom" }, + { "type_id": "44", "bedrooms": 21, "name": "Twentyone Bedroom" }, + { "type_id": "45", "bedrooms": 22, "name": "Twentytwo Bedroom" }, + { "type_id": "46", "bedrooms": 23, "name": "Twentythree Bedroom" }, + { "type_id": "47", "bedrooms": 24, "name": "Twentyfour Bedroom" }, + { "type_id": "48", "bedrooms": 25, "name": "Twentyfive Bedroom" }, + { "type_id": "49", "bedrooms": 26, "name": "Twentysix Bedroom" }, + { "type_id": "50", "bedrooms": 27, "name": "Twentyseven Bedroom" }, + { "type_id": "51", "bedrooms": 28, "name": "Twentyeight Bedroom" }, + { "type_id": "52", "bedrooms": 29, "name": "Twentynine Bedroom" }, + { "type_id": "53", "bedrooms": 30, "name": "Thirty Bedroom" } +] diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms.rb b/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms.rb new file mode 100644 index 000000000..040bc44c8 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms.rb @@ -0,0 +1,40 @@ +module RentalsUnited + module Dictionaries + # +RentalsUnited::Dictionaries::Bedrooms+ + # + # This class is responsible for bedrooms count mapping to bedrooms type ids + class Bedrooms + class << self + # Find bedrooms count by bedroom type id + # + # Arguments + # + # * +id+ [String] id of bedroom type + # + # Usage + # + # RentalsUnited::Dictionaries::Bedrooms.count_by_type_id("1") + # + # Returns [Integer] bedrooms count + def count_by_type_id(id) + type = bedrooms.find { |bedroom| bedroom["type_id"] == id } + return nil unless type + + type["bedrooms"] + end + + private + def bedrooms + JSON.parse(File.read(file_path)) + end + + def file_path + Hanami.root.join( + "lib/concierge/suppliers/rentals_united/dictionaries", + "bedrooms.json" + ).to_s + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index 98630850f..b6b3a1189 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -9,9 +9,9 @@ class Property attr_reader :property_hash, :location, :amenities_dictionary EN_DESCRIPTION_LANG_CODE = "1" - CANCELLATION_POLICY = "super_strict" + CANCELLATION_POLICY = "strict" DEFAULT_PROPERTY_RATE = "9999" - MINIMUM_STAY = nil + MINIMUM_STAY = 1 # Initialize +RentalsUnited::Mappers::Property+ # @@ -45,6 +45,8 @@ def build_property property.currency = location.currency property.city = location.city property.neighborhood = location.neighborhood + property.max_guests = property_hash.get("CanSleepMax").to_i + property.number_of_bedrooms = number_of_bedrooms property.nightly_rate = DEFAULT_PROPERTY_RATE property.weekly_rate = DEFAULT_PROPERTY_RATE property.monthly_rate = DEFAULT_PROPERTY_RATE @@ -77,6 +79,12 @@ def find_property_type(id) RentalsUnited::Dictionaries::PropertyTypes.find(id) end + def number_of_bedrooms + RentalsUnited::Dictionaries::Bedrooms.count_by_type_id( + property_hash.get("PropertyTypeID") + ) + end + def en_description(hash) descriptions = hash.get("Descriptions.Description") en_description = Array(descriptions).find do |desc| diff --git a/spec/fixtures/rentals_united/properties/property.xml b/spec/fixtures/rentals_united/properties/property.xml index 78a7f75e0..f17d7e976 100644 --- a/spec/fixtures/rentals_united/properties/property.xml +++ b/spec/fixtures/rentals_united/properties/property.xml @@ -17,7 +17,7 @@ 39 2 2 - 1 + 4 35 3 Test street address diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 011c8d405..5e5075eb3 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -69,6 +69,14 @@ expect(property.subtype).to eq("villa") end + it "sets max_guests to the property" do + expect(property.max_guests).to eq(2) + end + + it "sets number_of_bedrooms to the property" do + expect(property.number_of_bedrooms).to eq(3) + end + it "sets address information to the property" do expect(property.lat).to eq(55.0003426) expect(property.lng).to eq(73.2965942999999) diff --git a/spec/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms_spec.rb b/spec/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms_spec.rb new file mode 100644 index 000000000..d5bbe1131 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/dictionaries/bedrooms_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Dictionaries::Bedrooms do + describe "#count_by_type_id" do + it "returns bedrooms count by type id" do + count = described_class.count_by_type_id("46") + + expect(count).to eq(23) + end + + it "returns nil if property type was not found" do + count = described_class.count_by_type_id("9999") + + expect(count).to eq(nil) + end + + it "returns 0 for Studio" do + count = described_class.count_by_type_id("1") + + expect(count).to eq(0) + end + end + end +end From 0c98414dcabdd78600e299549a0902cda9ef3833 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 20:40:30 +0600 Subject: [PATCH 040/118] RU: keep cancellation policy as strict for now... --- .../suppliers/rentals_united/commands/property_fetcher_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 5e5075eb3..649ddf443 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -104,7 +104,7 @@ end it "sets cancellation_policy to the property" do - expect(property.cancellation_policy).to eq("super_strict") + expect(property.cancellation_policy).to eq("strict") end it "sets default_to_available flag" do From 1956f39b4908887ae0e27557aa96bd1d5701fc1e Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 21:14:47 +0600 Subject: [PATCH 041/118] RU: set default available to false --- lib/concierge/suppliers/rentals_united/mappers/property.rb | 2 +- .../rentals_united/commands/property_fetcher_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index b6b3a1189..922dc43a0 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -52,7 +52,7 @@ def build_property property.monthly_rate = DEFAULT_PROPERTY_RATE property.minimum_stay = MINIMUM_STAY property.cancellation_policy = CANCELLATION_POLICY - property.default_to_available = true + property.default_to_available = false property.instant_booking! property_type = find_property_type(property_hash.get("ObjectTypeID")) diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 649ddf443..bff4ebe5c 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -107,8 +107,8 @@ expect(property.cancellation_policy).to eq("strict") end - it "sets default_to_available flag" do - expect(property.default_to_available).to eq(true) + it "sets default_to_available flag to false" do + expect(property.default_to_available).to eq(false) end it "does not set multi-unit flag" do From a720d941202d358d80fd53f286cf2e152b7acb0b Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 22:55:46 +0600 Subject: [PATCH 042/118] RU: spec for metadata worker --- .../suppliers/rentals_united/metadata.rb | 7 +- .../suppliers/rentals_united/metadata_spec.rb | 171 ++++++++++++++++++ 2 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 spec/workers/suppliers/rentals_united/metadata_spec.rb diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index ea460c822..e077a65fb 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -40,7 +40,8 @@ def perform end end else - announce_context_error(message) + message = "Failed to find currency for location with id `#{location.id}`" + announce_context_error(message, result) return end end @@ -85,14 +86,14 @@ def fetch_property_ids(location_id) end def fetch_property(property_id, location) - announce_error("Failed to fetch property with ID #{property_id}`") do + announce_error("Failed to fetch property with ID `#{property_id}`") do importer.fetch_property(property_id, location) end end def announce_error(message) yield.tap do |result| - announce_context_error(message) unless result.success? + announce_context_error(message, result) unless result.success? end end diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb new file mode 100644 index 000000000..6b461f979 --- /dev/null +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -0,0 +1,171 @@ +require "spec_helper" + +RSpec.describe Workers::Suppliers::RentalsUnited::Metadata do + include Support::Factories + include Support::HTTPStubbing + include Support::Fixtures + + let(:host) { create_host } + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:url) { credentials.url } + + describe "#perform operation" do + let(:worker) do + described_class.new(host) + end + + it "fails when fetching location ids returns an error" do + stub_data = read_fixture("rentals_united/location_ids/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = worker.perform + expect(result).to be_nil + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq("Failed to fetch location ids") + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + describe "when #fetch_location_ids is working" do + before do + expect_any_instance_of(RentalsUnited::Importer).to( + receive(:fetch_location_ids) + ).and_return( + Result.new(["1505"]) + ) + end + + it "fails when fetching locations by location_ids returns an error" do + stub_data = read_fixture("rentals_united/locations/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = worker.perform + expect(result).to be_nil + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq("Failed to fetch locations") + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + describe "when #fetch_locations is working" do + before do + expect_any_instance_of(RentalsUnited::Importer).to( + receive(:fetch_locations) + ).and_return( + Result.new( + [RentalsUnited::Entities::Location.new("1505")] + ) + ) + end + + it "fails when fetching location currencies returns an error" do + stub_data = read_fixture("rentals_united/location_currencies/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = worker.perform + expect(result).to be_nil + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq( + "Failed to fetch locations-currencies mapping" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + describe "when #fetch_location_currencies is working" do + before do + expect_any_instance_of(RentalsUnited::Importer).to( + receive(:fetch_location_currencies) + ).and_return( + Result.new({"1506" => "EUR", "1606" => "USD"}) + ) + end + + it "fails when there is no currency for location" do + result = worker.perform + expect(result).to be_nil + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq( + "Failed to find currency for location with id `1505`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + describe "when currency for location exists" do + before do + expect_any_instance_of(RentalsUnited::Importer).to( + receive(:fetch_location_currencies) + ).and_return( + Result.new({"1505" => "EUR", "1606" => "USD"}) + ) + end + + it "fails when fetching property ids for location returns an error" do + stub_data = read_fixture("rentals_united/property_ids/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = worker.perform + expect(result).to be_nil + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq( + "Failed to fetch properties for location `1505`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + describe "when #fetch_property_ids is working" do + before do + expect_any_instance_of(RentalsUnited::Importer).to( + receive(:fetch_property_ids) + ).and_return( + Result.new(["1234", "2345"]) + ) + end + + it "calls synchronisation block for every property id" do + expected_property_ids = ["1234", "2345"] + + expected_property_ids.each do |property_id| + expect(worker.synchronisation).to receive(:start).with(property_id) + end + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + expect(result.to_h[:successful]).to be true + end + + it "fails when #fetch_property returns an error" do + stub_data = read_fixture("rentals_united/properties/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = worker.perform + + expect(result).to be_kind_of(SyncProcess) + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq( + "Failed to fetch property with ID `2345`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + end + end + end + end +end From 8c2bb8cdd3b1ce8f599d18ea8a10edc44fdd6641 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 12 Sep 2016 14:56:46 +0600 Subject: [PATCH 043/118] RU: use super_elite cancellation policy --- lib/concierge/suppliers/rentals_united/mappers/property.rb | 2 +- .../suppliers/rentals_united/commands/property_fetcher_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index 922dc43a0..c8b1d7d7d 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -9,7 +9,7 @@ class Property attr_reader :property_hash, :location, :amenities_dictionary EN_DESCRIPTION_LANG_CODE = "1" - CANCELLATION_POLICY = "strict" + CANCELLATION_POLICY = "super_elite" DEFAULT_PROPERTY_RATE = "9999" MINIMUM_STAY = 1 diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index bff4ebe5c..1c9683895 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -104,7 +104,7 @@ end it "sets cancellation_policy to the property" do - expect(property.cancellation_policy).to eq("strict") + expect(property.cancellation_policy).to eq("super_elite") end it "sets default_to_available flag to false" do From beb3d2b99a6e831ad88f8ab25c95db4628c5e24e Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 12 Sep 2016 15:42:08 +0600 Subject: [PATCH 044/118] RU: more specs for metadata worker --- .../suppliers/rentals_united/metadata_spec.rb | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index 6b461f979..75127ab0e 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -52,13 +52,17 @@ end describe "when #fetch_locations is working" do + let(:location) do + location = RentalsUnited::Entities::Location.new("1505") + location.country = "France" + location + end + before do expect_any_instance_of(RentalsUnited::Importer).to( receive(:fetch_locations) ).and_return( - Result.new( - [RentalsUnited::Entities::Location.new("1505")] - ) + Result.new([location]) ) end @@ -163,6 +167,29 @@ expect(event[:backtrace]).to be_kind_of(Array) expect(event[:backtrace].any?).to be true end + + describe "when #fetch_property is working" do + before do + stub_data = read_fixture("rentals_united/properties/property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + end + + it "creates record in the database" do + allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.new('success') } + + expect { + worker.perform + }.to change { PropertyRepository.count }.by(1) + end + + it 'doesnt create property with unsuccessful publishing' do + allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.error('fail') } + + expect { + worker.perform + }.to_not change { PropertyRepository.count } + end + end end end end From b0c40d91a50b611b0bbc2fb1fdae3f4d01c9adf1 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 12 Sep 2016 18:26:59 +0600 Subject: [PATCH 045/118] RU: add some forgotten fixes & specs --- .../suppliers/rentals_united/metadata.rb | 21 +++++-- .../dictionaries/property_types.json | 6 +- .../dictionaries/property_types.rb | 8 ++- .../suppliers/rentals_united/importer.rb | 12 ++++ .../rentals_united/mappers/property.rb | 37 +++++++---- .../rentals_united/properties/archived.xml | 10 +++ .../rentals_united/properties/not_active.xml | 10 +++ .../not_supported_boat_property.xml | 10 +++ .../not_supported_camping_property.xml | 10 +++ .../not_supported_hotel_property.xml | 10 +++ .../rentals_united/properties/property.xml | 2 +- .../property_with_multiple_descriptions.xml | 3 + .../properties/property_with_one_image.xml | 3 + .../properties/property_without_amenities.xml | 3 + .../property_without_descriptions.xml | 3 + .../properties/property_without_images.xml | 3 + .../commands/property_fetcher_spec.rb | 60 ++++++++++++++++++ .../dictionaries/property_types_spec.rb | 6 ++ .../suppliers/rentals_united/importer_spec.rb | 63 +++++++++++++++++++ .../suppliers/rentals_united/metadata_spec.rb | 38 ++++++----- 20 files changed, 275 insertions(+), 43 deletions(-) create mode 100644 spec/fixtures/rentals_united/properties/archived.xml create mode 100644 spec/fixtures/rentals_united/properties/not_active.xml create mode 100644 spec/fixtures/rentals_united/properties/not_supported_boat_property.xml create mode 100644 spec/fixtures/rentals_united/properties/not_supported_camping_property.xml create mode 100644 spec/fixtures/rentals_united/properties/not_supported_hotel_property.xml diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index e077a65fb..fd6726a7e 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -34,10 +34,19 @@ def perform return unless result.success? property_ids = result.value - property_ids.each do |property_id| - synchronisation.start(property_id) do - fetch_property(property_id, location) + + result = fetch_properties_by_ids(property_ids, location) + + if result.success? + properties = result.value + + properties.each do |property| + synchronisation.start(property.identifier) { Result.new(property) } end + else + message = "Failed to fetch properties for ids `#{property_ids}` in location `#{location.id}`" + announce_context_error(message, result) + return end else message = "Failed to find currency for location with id `#{location.id}`" @@ -85,9 +94,9 @@ def fetch_property_ids(location_id) end end - def fetch_property(property_id, location) - announce_error("Failed to fetch property with ID `#{property_id}`") do - importer.fetch_property(property_id, location) + def fetch_properties_by_ids(property_ids, location) + announce_error("Failed to fetch properties for location `#{location.id}`") do + importer.fetch_properties_by_ids(property_ids, location) end end diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/property_types.json b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.json index 11951580b..84e3caade 100644 --- a/lib/concierge/suppliers/rentals_united/dictionaries/property_types.json +++ b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.json @@ -2,13 +2,13 @@ { "id": "3", "rentals_united_name": "Apartment", "roomorama_name": "apartment", "roomorama_subtype_name": null }, { "id": "4", "rentals_united_name": "Bed and breakfast", "roomorama_name": "bnb", "roomorama_subtype_name": null }, { "id": "7", "rentals_united_name": "Chalet", "roomorama_name": "house", "roomorama_subtype_name": null }, - { "id": "16", "rentals_united_name": "Guest house", "roomorama_name": null, "roomorama_subtype_name": null }, - { "id": "20", "rentals_united_name": "Hotel", "roomorama_name": "hotel", "roomorama_subtype_name": null }, + { "id": "16", "rentals_united_name": "Guest house", "roomorama_name": "house", "roomorama_subtype_name": null }, + { "id": "20", "rentals_united_name": "Hotel", "roomorama_name": null, "roomorama_subtype_name": null }, { "id": "30", "rentals_united_name": "Resort", "roomorama_name": "hotel", "roomorama_subtype_name": "resort" }, { "id": "35", "rentals_united_name": "Villa", "roomorama_name": "house", "roomorama_subtype_name": "villa" }, { "id": "37", "rentals_united_name": "Castle", "roomorama_name": "house", "roomorama_subtype_name": "chateau" }, { "id": "63", "rentals_united_name": "Aparthotel", "roomorama_name": "apartment", "roomorama_subtype_name": null }, { "id": "64", "rentals_united_name": "Boat", "roomorama_name": null, "roomorama_subtype_name": null }, { "id": "65", "rentals_united_name": "Cottage", "roomorama_name": "house", "roomorama_subtype_name": "cottage" }, - { "id": "66", "rentals_united_name": "Camping", "roomorama_name": "house", "roomorama_subtype_name": null } + { "id": "66", "rentals_united_name": "Camping", "roomorama_name": null, "roomorama_subtype_name": null } ] diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb index 2d9f04a52..d44f2bb01 100644 --- a/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb +++ b/lib/concierge/suppliers/rentals_united/dictionaries/property_types.rb @@ -18,7 +18,13 @@ class << self # # Returns [Entities::PropertyType] property type object def find(id) - all.find { |p| p.id == id } + property_type = all.find { |p| p.id == id } + + if property_type + return nil if property_type.roomorama_name.nil? + + property_type + end end # Find all property types diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb index 5b43b6593..95e79fb7c 100644 --- a/lib/concierge/suppliers/rentals_united/importer.rb +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -62,5 +62,17 @@ def fetch_property(property_id, location) ) property_fetcher.fetch_property end + + def fetch_properties_by_ids(property_ids, location) + properties = property_ids.map do |property_id| + result = fetch_property(property_id, location) + + return result unless result.success? + + result.value + end.compact + + Result.new(properties) + end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index c8b1d7d7d..06c33a84a 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -6,7 +6,8 @@ module Mappers # +RentalsUnited::Entities::Property+ object from a hash which was fetched # from the RentalsUnited API. class Property - attr_reader :property_hash, :location, :amenities_dictionary + attr_reader :property_hash, :location, :amenities_dictionary, + :property_type EN_DESCRIPTION_LANG_CODE = "1" CANCELLATION_POLICY = "super_elite" @@ -25,12 +26,17 @@ def initialize(property_hash, location) @amenities_dictionary = Dictionaries::Amenities.new( property_hash.get("Amenities.Amenity") ) + @property_type ||= Dictionaries::PropertyTypes.find( + property_hash.get("ObjectTypeID") + ) end # Builds a property # # Returns [RentalsUnited::Entities::Property] def build_property + return nil if not_active? || archived? || not_supported_property_type? + property = Roomorama::Property.new(property_hash.get("ID")) property.title = property_hash.get("Name") property.description = en_description(property_hash) @@ -55,19 +61,18 @@ def build_property property.default_to_available = false property.instant_booking! - property_type = find_property_type(property_hash.get("ObjectTypeID")) - - if property_type - property.type = property_type.roomorama_name - property.subtype = property_type.roomorama_subtype_name - end - + set_property_type_and_subtype!(property) if property_type set_images!(property) property end private + def set_property_type_and_subtype!(property) + property.type = property_type.roomorama_name + property.subtype = property_type.roomorama_subtype_name + end + def set_images!(property) raw_images = Array(property_hash.get("Images.Image")) @@ -75,10 +80,6 @@ def set_images!(property) mapper.build_images.each { |image| property.add_image(image) } end - def find_property_type(id) - RentalsUnited::Dictionaries::PropertyTypes.find(id) - end - def number_of_bedrooms RentalsUnited::Dictionaries::Bedrooms.count_by_type_id( property_hash.get("PropertyTypeID") @@ -108,6 +109,18 @@ def check_out_time def country_code(location) Converters::CountryCode.code_by_name(location.country) end + + def not_active? + property_hash.get("IsActive") == false + end + + def archived? + property_hash.get("IsArchived") == true + end + + def not_supported_property_type? + property_type.nil? + end end end end diff --git a/spec/fixtures/rentals_united/properties/archived.xml b/spec/fixtures/rentals_united/properties/archived.xml new file mode 100644 index 000000000..37473f470 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/archived.xml @@ -0,0 +1,10 @@ + + Success + + -1 + 519688 + true + true + 35 + + diff --git a/spec/fixtures/rentals_united/properties/not_active.xml b/spec/fixtures/rentals_united/properties/not_active.xml new file mode 100644 index 000000000..28b01574e --- /dev/null +++ b/spec/fixtures/rentals_united/properties/not_active.xml @@ -0,0 +1,10 @@ + + Success + + -1 + 519688 + false + false + 35 + + diff --git a/spec/fixtures/rentals_united/properties/not_supported_boat_property.xml b/spec/fixtures/rentals_united/properties/not_supported_boat_property.xml new file mode 100644 index 000000000..7d0fbe59d --- /dev/null +++ b/spec/fixtures/rentals_united/properties/not_supported_boat_property.xml @@ -0,0 +1,10 @@ + + Success + + -1 + 519688 + true + false + 64 + + diff --git a/spec/fixtures/rentals_united/properties/not_supported_camping_property.xml b/spec/fixtures/rentals_united/properties/not_supported_camping_property.xml new file mode 100644 index 000000000..ad06ebff7 --- /dev/null +++ b/spec/fixtures/rentals_united/properties/not_supported_camping_property.xml @@ -0,0 +1,10 @@ + + Success + + -1 + 519688 + true + false + 66 + + diff --git a/spec/fixtures/rentals_united/properties/not_supported_hotel_property.xml b/spec/fixtures/rentals_united/properties/not_supported_hotel_property.xml new file mode 100644 index 000000000..41d8872ba --- /dev/null +++ b/spec/fixtures/rentals_united/properties/not_supported_hotel_property.xml @@ -0,0 +1,10 @@ + + Success + + -1 + 519688 + true + false + 20 + + diff --git a/spec/fixtures/rentals_united/properties/property.xml b/spec/fixtures/rentals_united/properties/property.xml index f17d7e976..351aaf5a7 100644 --- a/spec/fixtures/rentals_united/properties/property.xml +++ b/spec/fixtures/rentals_united/properties/property.xml @@ -11,7 +11,7 @@ true true 427698 - false + true false 2.6500 39 diff --git a/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml b/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml index 5adf80176..2ca00fd2e 100644 --- a/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml +++ b/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml @@ -2,6 +2,9 @@ Success 519688 + true + false + 35 diff --git a/spec/fixtures/rentals_united/properties/property_with_one_image.xml b/spec/fixtures/rentals_united/properties/property_with_one_image.xml index e34b8120f..d3e2ea2f8 100644 --- a/spec/fixtures/rentals_united/properties/property_with_one_image.xml +++ b/spec/fixtures/rentals_united/properties/property_with_one_image.xml @@ -3,6 +3,9 @@ -1 519688 + true + false + 35 https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg diff --git a/spec/fixtures/rentals_united/properties/property_without_amenities.xml b/spec/fixtures/rentals_united/properties/property_without_amenities.xml index b3681e2b9..a030892da 100644 --- a/spec/fixtures/rentals_united/properties/property_without_amenities.xml +++ b/spec/fixtures/rentals_united/properties/property_without_amenities.xml @@ -3,5 +3,8 @@ -1 519688 + true + false + 35 diff --git a/spec/fixtures/rentals_united/properties/property_without_descriptions.xml b/spec/fixtures/rentals_united/properties/property_without_descriptions.xml index b3681e2b9..a030892da 100644 --- a/spec/fixtures/rentals_united/properties/property_without_descriptions.xml +++ b/spec/fixtures/rentals_united/properties/property_without_descriptions.xml @@ -3,5 +3,8 @@ -1 519688 + true + false + 35 diff --git a/spec/fixtures/rentals_united/properties/property_without_images.xml b/spec/fixtures/rentals_united/properties/property_without_images.xml index b3681e2b9..a030892da 100644 --- a/spec/fixtures/rentals_united/properties/property_without_images.xml +++ b/spec/fixtures/rentals_united/properties/property_without_images.xml @@ -3,5 +3,8 @@ -1 519688 + true + false + 35 diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 1c9683895..1443853b3 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -210,6 +210,66 @@ end end + context "when property is not active" do + it "returns nil" do + stub_data = read_fixture("rentals_united/properties/not_active.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + + expect(result).to be_success + expect(result.value).to be_nil + end + end + + context "when property is archived" do + it "returns nil" do + stub_data = read_fixture("rentals_united/properties/archived.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + + expect(result).to be_success + expect(result.value).to be_nil + end + end + + context "when property is hotel-typed" do + it "returns nil" do + stub_data = read_fixture("rentals_united/properties/not_supported_hotel_property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + + expect(result).to be_success + expect(result.value).to be_nil + end + end + + context "when property is boat-typed" do + it "returns nil" do + stub_data = read_fixture("rentals_united/properties/not_supported_boat_property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + + expect(result).to be_success + expect(result.value).to be_nil + end + end + + context "when property is camping-typed" do + it "returns nil" do + stub_data = read_fixture("rentals_united/properties/not_supported_camping_property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + + expect(result).to be_success + expect(result.value).to be_nil + end + end + context "when response from the api is not well-formed xml" do it "returns a result with an appropriate error" do stub_data = read_fixture("rentals_united/bad_xml.xml") diff --git a/spec/lib/concierge/suppliers/rentals_united/dictionaries/property_types_spec.rb b/spec/lib/concierge/suppliers/rentals_united/dictionaries/property_types_spec.rb index 1a1a5dfb5..5ac3a8cfd 100644 --- a/spec/lib/concierge/suppliers/rentals_united/dictionaries/property_types_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/dictionaries/property_types_spec.rb @@ -20,6 +20,12 @@ module RentalsUnited expect(property_type).to be_nil end + + it "returns nil if property type was found but has no mapping to Roomorama" do + property_type = described_class.find("20") + + expect(property_type).to be_nil + end end describe "#all" do diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb index 7c55ec1df..e20b82886 100644 --- a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -60,4 +60,67 @@ importer.fetch_property(property_id, location) end end + + describe "#fetch_properties_by_ids" do + let(:property_ids) { ["222", "333"] } + let(:location) { double(id: '1') } + + it "calls #fetch_property for every property id" do + property_ids.each do |id| + expect_any_instance_of(described_class).to( + receive(:fetch_property).with(id, location) do + Result.new("success") + end + ) + end + + result = importer.fetch_properties_by_ids(property_ids, location) + expect(result).to be_success + end + + it "ignores nil results" do + expect_any_instance_of(described_class).to( + receive(:fetch_property).with("222", location) do + Result.new("success") + end + ) + + expect_any_instance_of(described_class).to( + receive(:fetch_property).with("333", location) do + Result.new(nil) + end + ) + + result = importer.fetch_properties_by_ids(property_ids, location) + expect(result).to be_success + expect(result.value.size).to eq(1) + end + + it "returns error if fetching property_id fails" do + expect_any_instance_of(described_class).to( + receive(:fetch_property).with("222", location) do + Result.error("fail") + end + ) + + result = importer.fetch_properties_by_ids(property_ids, location) + expect(result).not_to be_success + end + + it "returns error on fail even if previous fetchers returned success" do + expect_any_instance_of(described_class).to( + receive(:fetch_property).with("222", location) do + Result.new("success") + end + ) + expect_any_instance_of(described_class).to( + receive(:fetch_property).with("333", location) do + Result.error("fail") + end + ) + + result = importer.fetch_properties_by_ids(property_ids, location) + expect(result).not_to be_success + end + end end diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index 75127ab0e..8a138d482 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -135,45 +135,43 @@ expect_any_instance_of(RentalsUnited::Importer).to( receive(:fetch_property_ids) ).and_return( - Result.new(["1234", "2345"]) + Result.new(["1234"]) ) end - it "calls synchronisation block for every property id" do - expected_property_ids = ["1234", "2345"] - - expected_property_ids.each do |property_id| - expect(worker.synchronisation).to receive(:start).with(property_id) - end - - result = worker.perform - expect(result).to be_kind_of(SyncProcess) - expect(result.to_h[:successful]).to be true - end - - it "fails when #fetch_property returns an error" do - stub_data = read_fixture("rentals_united/properties/error_status.xml") - stub_call(:post, url) { [200, {}, stub_data] } + it "fails when #fetch_properties_by_ids returns an error" do + allow_any_instance_of(RentalsUnited::Importer).to receive(:fetch_properties_by_ids) { Result.error('fail') } result = worker.perform - - expect(result).to be_kind_of(SyncProcess) + expect(result).to be_nil event = Concierge.context.events.last.to_h expect(event[:label]).to eq("Synchronisation Failure") expect(event[:message]).to eq( - "Failed to fetch property with ID `2345`" + "Failed to fetch properties for ids `[\"1234\"]` in location `1505`" ) expect(event[:backtrace]).to be_kind_of(Array) expect(event[:backtrace].any?).to be true end - describe "when #fetch_property is working" do + describe "when #fetch_properties_by_ids is working" do before do stub_data = read_fixture("rentals_united/properties/property.xml") stub_call(:post, url) { [200, {}, stub_data] } end + it "calls synchronisation block for every property id" do + expected_property_ids = ["519688"] + + expected_property_ids.each do |property_id| + expect(worker.synchronisation).to receive(:start).with(property_id) + end + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + expect(result.to_h[:successful]).to be true + end + it "creates record in the database" do allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.new('success') } From cb67c7ec93084c19894ad74ff5a6255d832d63d6 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 13 Sep 2016 09:53:52 +0600 Subject: [PATCH 046/118] RU: typo --- apps/workers/suppliers/rentals_united/metadata.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index fd6726a7e..f236e0853 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -127,7 +127,7 @@ def announce_context_error(message, result) Concierge::Announcer.trigger(Concierge::Errors::EXTERNAL_ERROR, { operation: 'sync', - supplier: Ciirus::Client::SUPPLIER_NAME, + supplier: RentalsUnited::Client::SUPPLIER_NAME, code: result.error.code, context: Concierge.context.to_h, happened_at: Time.now From 2f7d53b5224b58c15f7f074b251c1f7922837e85 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 13 Sep 2016 15:59:36 +0600 Subject: [PATCH 047/118] RU: documentation update --- .../commands/location_ids_fetcher.rb | 3 +++ .../commands/locations_currencies_fetcher.rb | 4 ++++ .../commands/locations_fetcher.rb | 4 ++++ .../commands/property_fetcher.rb | 4 ++++ .../commands/property_ids_fetcher.rb | 6 ++++++ .../suppliers/rentals_united/importer.rb | 19 +++++++++++++++++-- 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb index 079039631..f3d5554f7 100644 --- a/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb @@ -12,6 +12,9 @@ class LocationIdsFetcher < BaseFetcher # Retrieves location ids with active properties. # # Locations without active properties are ignored. + # + # Returns a +Result+ wrapping +Array+ of +String+ location ids + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_location_ids payload = payload_builder.build_location_ids_fetch_payload result = http.post(credentials.url, payload, headers) diff --git a/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb index 2f35e1298..7c8968ce4 100644 --- a/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb @@ -7,6 +7,10 @@ module Commands class LocationCurrenciesFetcher < BaseFetcher ROOT_TAG = "Pull_ListCurrenciesWithCities_RS" + # Retrieves locations - currencies mapping. + # + # Returns a +Result+ wrapping +Hash+ with location_id => currency pairs + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_location_currencies payload = payload_builder.build_location_currencies_fetch_payload result = http.post(credentials.url, payload, headers) diff --git a/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb index 29f73c011..b6c4928c2 100644 --- a/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb @@ -15,6 +15,10 @@ def initialize(credentials, location_ids) @location_ids = location_ids end + # Retrieves locations by location_ids + # + # Returns a +Result+ wrapping +Array+ of +Entities::Location+ objects + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_locations payload = payload_builder.build_locations_fetch_payload result = http.post(credentials.url, payload, headers) diff --git a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb index 2e33d0701..ebf82f5e9 100644 --- a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb @@ -32,6 +32,10 @@ def initialize(credentials, property_id, location) @location = location end + # Retrieves property + # + # Returns a +Result+ wrapping +Roomorama::Property+ object + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_property payload = payload_builder.build_property_fetch_payload(property_id) result = http.post(credentials.url, payload, headers) diff --git a/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb index 858fe4dbc..30223e57c 100644 --- a/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb @@ -29,6 +29,12 @@ def initialize(credentials, location_id) @location_id = location_id end + # Retrieves property ids + # + # IDs of properties which are no longer available will be filtered out. + # + # Returns a +Result+ wrapping +Array+ of +String+ property ids + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_property_ids payload = payload_builder.build_property_ids_fetch_payload(location_id) result = http.post(credentials.url, payload, headers) diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb index 95e79fb7c..50051e4b1 100644 --- a/lib/concierge/suppliers/rentals_united/importer.rb +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -17,6 +17,9 @@ def initialize(credentials) # Retrieves location ids with active properties. # # Locations without active properties will be filtered out. + # + # Returns a +Result+ wrapping +Array+ of +String+ location ids + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_location_ids fetcher = Commands::LocationIdsFetcher.new(credentials) fetcher.fetch_location_ids @@ -28,7 +31,8 @@ def fetch_location_ids # # * +location_ids+ [Array] ids array of locations to fetch # - # Returns [Array] array of location objects + # Returns a +Result+ wrapping +Array+ of +Entities::Location+ objects + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_locations(location_ids) fetcher = Commands::LocationsFetcher.new(credentials, location_ids) fetcher.fetch_locations @@ -36,7 +40,8 @@ def fetch_locations(location_ids) # Retrieves locations - currencies mapping. # - # Returns [Hash] hash with location_id => currency key-values + # Returns a +Result+ wrapping +Hash+ with location_id => currency pairs + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_location_currencies fetcher = Commands::LocationCurrenciesFetcher.new(credentials) fetcher.fetch_location_currencies @@ -45,6 +50,9 @@ def fetch_location_currencies # Retrieves property ids by location id. # # IDs of properties which are no longer available will be filtered out. + # + # Returns a +Result+ wrapping +Array+ of +String+ property ids + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_property_ids(location_id) properties_fetcher = Commands::PropertyIdsFetcher.new( credentials, @@ -54,6 +62,9 @@ def fetch_property_ids(location_id) end # Retrieves property by its id. + # + # Returns a +Result+ wrapping +Roomorama::Property+ object + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_property(property_id, location) property_fetcher = Commands::PropertyFetcher.new( credentials, @@ -63,6 +74,10 @@ def fetch_property(property_id, location) property_fetcher.fetch_property end + # Retrieves properties by given ids + # + # Returns a +Result+ wrapping +Array+ of +Roomorama::Property+ objects + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_properties_by_ids(property_ids, location) properties = property_ids.map do |property_id| result = fetch_property(property_id, location) From 70aa640dca087dada5e2d9ced628b1c4c8420f11 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 13 Sep 2016 16:02:42 +0600 Subject: [PATCH 048/118] RU: correct typo in payload builder spec --- .../concierge/suppliers/rentals_united/payload_builder_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index 734f96b13..5c12d496f 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -62,7 +62,7 @@ end end - describe '#build_property_ids_fetch_payload' do + describe '#build_property_fetch_payload' do let(:params) do { property_id: "123" From 68500cd6765a5eaff26d7fb9b1d4bc0049eb9cf8 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 19 Sep 2016 13:39:56 +0600 Subject: [PATCH 049/118] RU: to_s and strip postal codes --- lib/concierge/suppliers/rentals_united/mappers/property.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index 06c33a84a..aa5b7f8c4 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -43,7 +43,7 @@ def build_property property.lat = property_hash.get("Coordinates.Latitude").to_f property.lng = property_hash.get("Coordinates.Longitude").to_f property.address = property_hash.get("Street") - property.postal_code = property_hash.get("ZipCode") + property.postal_code = property_hash.get("ZipCode").to_s.strip property.amenities = amenities_dictionary.convert property.check_in_time = check_in_time property.check_out_time = check_out_time From cb5ea21b849bb478834d7f4742f1ddda5962380f Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 20 Sep 2016 17:22:34 +0600 Subject: [PATCH 050/118] RU: refactor api calls by removing passing credentials.url into each call --- .../rentals_united/commands/base_fetcher.rb | 19 +++++++++++++------ .../commands/location_ids_fetcher.rb | 2 +- .../commands/locations_currencies_fetcher.rb | 2 +- .../commands/locations_fetcher.rb | 2 +- .../commands/property_fetcher.rb | 2 +- .../commands/property_ids_fetcher.rb | 2 +- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb index 6cfde15dd..42553ccfa 100644 --- a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb @@ -17,12 +17,10 @@ def response_parser @response_parser ||= RentalsUnited::ResponseParser.new end - def http - @http_client ||= Concierge::HTTPClient.new(credentials.url) - end - - def headers - { "Content-Type" => "application/xml" } + # All API calls performed to the same base URL which determined in + # the instance of http client. + def api_call(payload) + http.post("", payload, headers) end def get_status(hash, root_tag_name) @@ -79,6 +77,15 @@ def unrecognised_response_event(backtrace) message = "Error response could not be recognised (no `Status` tag in the response)" mismatch(message, backtrace) end + + private + def http + @http_client ||= Concierge::HTTPClient.new(credentials.url) + end + + def headers + { "Content-Type" => "application/xml" } + end end end end diff --git a/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb index f3d5554f7..ccf2b2273 100644 --- a/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb @@ -17,7 +17,7 @@ class LocationIdsFetcher < BaseFetcher # Returns a +Result+ with +Result::Error+ when operation fails def fetch_location_ids payload = payload_builder.build_location_ids_fetch_payload - result = http.post(credentials.url, payload, headers) + result = api_call(payload) return result unless result.success? diff --git a/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb index 7c8968ce4..5f1118227 100644 --- a/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/locations_currencies_fetcher.rb @@ -13,7 +13,7 @@ class LocationCurrenciesFetcher < BaseFetcher # Returns a +Result+ with +Result::Error+ when operation fails def fetch_location_currencies payload = payload_builder.build_location_currencies_fetch_payload - result = http.post(credentials.url, payload, headers) + result = api_call(payload) return result unless result.success? diff --git a/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb index b6c4928c2..24be72ca0 100644 --- a/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb @@ -21,7 +21,7 @@ def initialize(credentials, location_ids) # Returns a +Result+ with +Result::Error+ when operation fails def fetch_locations payload = payload_builder.build_locations_fetch_payload - result = http.post(credentials.url, payload, headers) + result = api_call(payload) return result unless result.success? diff --git a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb index ebf82f5e9..15046389b 100644 --- a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb @@ -38,7 +38,7 @@ def initialize(credentials, property_id, location) # Returns a +Result+ with +Result::Error+ when operation fails def fetch_property payload = payload_builder.build_property_fetch_payload(property_id) - result = http.post(credentials.url, payload, headers) + result = api_call(payload) return result unless result.success? diff --git a/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb index 30223e57c..0dda47bc0 100644 --- a/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb @@ -37,7 +37,7 @@ def initialize(credentials, location_id) # Returns a +Result+ with +Result::Error+ when operation fails def fetch_property_ids payload = payload_builder.build_property_ids_fetch_payload(location_id) - result = http.post(credentials.url, payload, headers) + result = api_call(payload) return result unless result.success? From e981a4e0548df33442752758281e67f6b9432ce2 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 20 Sep 2016 18:11:55 +0600 Subject: [PATCH 051/118] RU: get rid of switch statement in location mapper --- .../rentals_united/entities/location.rb | 8 +++++ .../rentals_united/mappers/location.rb | 32 ++++++++----------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/entities/location.rb b/lib/concierge/suppliers/rentals_united/entities/location.rb index 46bc02dc3..12b71117c 100644 --- a/lib/concierge/suppliers/rentals_united/entities/location.rb +++ b/lib/concierge/suppliers/rentals_united/entities/location.rb @@ -9,6 +9,14 @@ class Location def initialize(id) @id = id end + + def load(attrs) + self.neighborhood = attrs[:neighborhood] + self.city = attrs[:city] + self.region = attrs[:region] + self.country = attrs[:country] + self + end end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/location.rb b/lib/concierge/suppliers/rentals_united/mappers/location.rb index 5fbcf505f..fa287475c 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/location.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/location.rb @@ -9,10 +9,10 @@ class Location # Rentals United mapping of location ids and location types. # Worldwide and Continent type locations are not mapped. LOCATION_TYPES = { - 2 => "Country", - 3 => "Region", - 4 => "City", - 5 => "District" + 2 => :country, + 3 => :region, + 4 => :city, + 5 => :neighborhood } # Initialize +RentalsUnited::Mappers::Location+ mapper @@ -38,7 +38,9 @@ def build_location current_level = find_location_data(location_id) current_level_type = current_level[:type] - update_location(location, current_level) + + location_hash = {} + update_location_hash(location_hash, current_level) while(LOCATION_TYPES.keys.include?(current_level_type)) do parent_location_id = current_level[:parent_id] @@ -46,29 +48,23 @@ def build_location current_level = find_location_data(parent_location_id) current_level_type = current_level[:type] - update_location(location, current_level) + update_location_hash(location_hash, current_level) end - location + location.load(location_hash) end + private def find_location_data(id) raw_locations_database.find do |location| location[:id] == id end end - def update_location(location, current_level) - case current_level[:type] - when 5 - location.neighborhood = current_level[:name] - when 4 - location.city = current_level[:name] - when 3 - location.region = current_level[:name] - when 2 - location.country = current_level[:name] - end + def update_location_hash(location_hash, current_level) + key = LOCATION_TYPES[current_level[:type]] + value = current_level[:name] + location_hash[key] = value end end end From 63827dc1755c0b218320d891f2b9a8d15e831152 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 13:50:25 +0600 Subject: [PATCH 052/118] RU: add nex_context for worker --- apps/workers/suppliers/rentals_united/metadata.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index f236e0853..d2fd373e8 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -11,17 +11,17 @@ def initialize(host) end def perform - result = fetch_location_ids + result = synchronisation.new_context { fetch_location_ids } return unless result.success? location_ids = result.value - result = fetch_locations(location_ids) + result = synchronisation.new_context { fetch_locations(location_ids) } return unless result.success? locations = result.value - result = fetch_location_currencies + result = synchronisation.new_context { fetch_location_currencies } return unless result.success? currencies = result.value @@ -30,12 +30,12 @@ def perform location.currency = currencies[location.id] if location.currency - result = fetch_property_ids(location.id) + result = synchronisation.new_context { fetch_property_ids(location.id) } return unless result.success? property_ids = result.value - result = fetch_properties_by_ids(property_ids, location) + result = synchronisation.new_context { fetch_properties_by_ids(property_ids, location) } if result.success? properties = result.value From efb5c12e432e4b1ea7766814d752f053472fa61c Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 18:37:18 +0600 Subject: [PATCH 053/118] RU: add pets_allowed and smoking_allowed --- .../rentals_united/dictionaries/amenities.rb | 11 +++++ .../rentals_united/mappers/property.rb | 4 +- .../commands/property_fetcher_spec.rb | 41 +++++++++++++++++ .../dictionaries/amenities_spec.rb | 44 +++++++++++++++++++ 4 files changed, 99 insertions(+), 1 deletion(-) diff --git a/lib/concierge/suppliers/rentals_united/dictionaries/amenities.rb b/lib/concierge/suppliers/rentals_united/dictionaries/amenities.rb index 8129ccfd4..48e1a90c5 100644 --- a/lib/concierge/suppliers/rentals_united/dictionaries/amenities.rb +++ b/lib/concierge/suppliers/rentals_united/dictionaries/amenities.rb @@ -7,6 +7,9 @@ module Dictionaries class Amenities attr_reader :facility_service_ids + SMOKING_ALLOWED_IDS = ["799", "802"] + PETS_ALLOWED_IDS = ["595"] + # Initialize amenities converter # # Arguments @@ -28,6 +31,14 @@ def convert roomorama_amenities.compact.uniq end + def smoking_allowed? + facility_service_ids.any? { |id| SMOKING_ALLOWED_IDS.include?(id) } + end + + def pets_allowed? + facility_service_ids.any? { |id| PETS_ALLOWED_IDS.include?(id) } + end + class << self # Looks up for supported amenity by id. # Returns nil if supported amenity was not found. diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index aa5b7f8c4..b398f44e6 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -44,7 +44,6 @@ def build_property property.lng = property_hash.get("Coordinates.Longitude").to_f property.address = property_hash.get("Street") property.postal_code = property_hash.get("ZipCode").to_s.strip - property.amenities = amenities_dictionary.convert property.check_in_time = check_in_time property.check_out_time = check_out_time property.country_code = country_code(location) @@ -53,6 +52,9 @@ def build_property property.neighborhood = location.neighborhood property.max_guests = property_hash.get("CanSleepMax").to_i property.number_of_bedrooms = number_of_bedrooms + property.amenities = amenities_dictionary.convert + property.pets_allowed = amenities_dictionary.pets_allowed? + property.smoking_allowed = amenities_dictionary.smoking_allowed? property.nightly_rate = DEFAULT_PROPERTY_RATE property.weekly_rate = DEFAULT_PROPERTY_RATE property.monthly_rate = DEFAULT_PROPERTY_RATE diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 1443853b3..0b62f915f 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -149,6 +149,47 @@ expect(property.amenities).to eq([]) end end + + it "sets smoking_allowed to false" do + amenities_dict = RentalsUnited::Dictionaries::Amenities + + expect_any_instance_of(amenities_dict) + .to(receive(:smoking_allowed?)) + .and_return(false) + + + expect(property.smoking_allowed).to eq(false) + end + + it "sets smoking_allowed to true" do + amenities_dict = RentalsUnited::Dictionaries::Amenities + + expect_any_instance_of(amenities_dict) + .to(receive(:smoking_allowed?)) + .and_return(true) + + expect(property.smoking_allowed).to eq(true) + end + + it "sets pets_allowed to false" do + amenities_dict = RentalsUnited::Dictionaries::Amenities + + expect_any_instance_of(amenities_dict) + .to(receive(:pets_allowed?)) + .and_return(false) + + expect(property.pets_allowed).to eq(false) + end + + it "sets pets_allowed to true" do + amenities_dict = RentalsUnited::Dictionaries::Amenities + + expect_any_instance_of(amenities_dict) + .to(receive(:pets_allowed?)) + .and_return(true) + + expect(property.pets_allowed).to eq(true) + end end context "when mapping single image to property" do diff --git a/spec/lib/concierge/suppliers/rentals_united/dictionaries/amenities_spec.rb b/spec/lib/concierge/suppliers/rentals_united/dictionaries/amenities_spec.rb index 4686cc7af..3bdf05628 100644 --- a/spec/lib/concierge/suppliers/rentals_united/dictionaries/amenities_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/dictionaries/amenities_spec.rb @@ -52,5 +52,49 @@ module RentalsUnited expect(amenities).to eq(["balcony"]) end end + + describe "#smoking_allowed?" do + let(:service_ids) { ["89", "96"] } + + it "returns false when no smoking_allowed facilities are included" do + smoking_allowed_ids = [] + ids = (service_ids + smoking_allowed_ids).flatten + + dictionary = described_class.new(ids) + expect(dictionary).not_to be_smoking_allowed + end + + it "returns true when one of smoking_allowed facilities is included" do + smoking_allowed_ids = ["799", "802"] + smoking_allowed_ids.each do |id| + ids = (service_ids + [id]).flatten + + dictionary = described_class.new(ids) + expect(dictionary).to be_smoking_allowed + end + end + end + + describe "#pets_allowed?" do + let(:service_ids) { ["89", "96"] } + + it "returns false when no pets_allowed facilities are included" do + pets_allowed_ids = [] + ids = (service_ids + pets_allowed_ids).flatten + + dictionary = described_class.new(ids) + expect(dictionary).not_to be_pets_allowed + end + + it "returns true when one of pets_allowed facilities is included" do + pets_allowed_ids = ["595"] + pets_allowed_ids.each do |id| + ids = (service_ids + [id]).flatten + + dictionary = described_class.new(ids) + expect(dictionary).to be_pets_allowed + end + end + end end end From 435985a6efb875ebbf76bf728bc21cc7035f5db8 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 19:40:42 +0600 Subject: [PATCH 054/118] RU: add floor mapping --- .../rentals_united/mappers/property.rb | 13 +++ .../properties/basement_floor.xml | 81 +++++++++++++++++++ .../commands/property_fetcher_spec.rb | 24 ++++++ 3 files changed, 118 insertions(+) create mode 100644 spec/fixtures/rentals_united/properties/basement_floor.xml diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index b398f44e6..59bd8ca50 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -52,6 +52,7 @@ def build_property property.neighborhood = location.neighborhood property.max_guests = property_hash.get("CanSleepMax").to_i property.number_of_bedrooms = number_of_bedrooms + property.floor = floor property.amenities = amenities_dictionary.convert property.pets_allowed = amenities_dictionary.pets_allowed? property.smoking_allowed = amenities_dictionary.smoking_allowed? @@ -88,6 +89,18 @@ def number_of_bedrooms ) end + # RU sends -1000 for Basement + # 0 for Ground + # 0..100 for usual floor number + # + # Replace -1000 with just -1 because we don't want our users to + # be burnt away -1000 floors under the earth. + def floor + ru_floor_value = property_hash.get("Floor").to_i + return -1 if ru_floor_value == -1000 + return ru_floor_value + end + def en_description(hash) descriptions = hash.get("Descriptions.Description") en_description = Array(descriptions).find do |desc| diff --git a/spec/fixtures/rentals_united/properties/basement_floor.xml b/spec/fixtures/rentals_united/properties/basement_floor.xml new file mode 100644 index 000000000..df31e0a1d --- /dev/null +++ b/spec/fixtures/rentals_united/properties/basement_floor.xml @@ -0,0 +1,81 @@ + + Success + + -1 + 519688 + Test property + 427698 + 24958 + 2016-08-31 11:39:48 + 2016-08-31 + true + true + 427698 + true + false + 2.6500 + 39 + 2 + 2 + 4 + 35 + -1000 + Test street address + 644119 + + 55.0003426 + 73.2965942999999 + + + Ruslan Sharipov + sharipov.reg@gmail.com + +79618492980 + 1 + + + + + 13:00 + 17:00 + 11:00 + at_the_apartment + + + + 99.6000 + 5.50 + + + + 7 + 100 + 180 + 187 + 227 + 281 + 368 + 596 + 689 + 802 + 803 + + + + + + + + https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082398988145159.jpg + https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg + + + + + + + + + + + + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 0b62f915f..f26de5237 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -343,4 +343,28 @@ expect(event[:message]).to eq("timeout") end end + + context "when mapping floors" do + it "sets floor to the property if it's usual floor value" do + stub_data = read_fixture("rentals_united/properties/property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + expect(result).to be_success + + property = result.value + expect(property.floor).to eq(3) + end + + it "sets floor -1 to the property if it's Basement encoded by -1000" do + stub_data = read_fixture("rentals_united/properties/basement_floor.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_property + expect(result).to be_success + + property = result.value + expect(property.floor).to eq(-1) + end + end end From fb570385c336f0cf1a54c92b14c7a53565290793 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 19:57:37 +0600 Subject: [PATCH 055/118] RU: add surface and surface_unit to mapping --- .../suppliers/rentals_united/mappers/property.rb | 3 +++ .../rentals_united/commands/property_fetcher_spec.rb | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index 59bd8ca50..5d829a7cd 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -13,6 +13,7 @@ class Property CANCELLATION_POLICY = "super_elite" DEFAULT_PROPERTY_RATE = "9999" MINIMUM_STAY = 1 + SURFACE_UNIT = "metric" # Initialize +RentalsUnited::Mappers::Property+ # @@ -51,6 +52,8 @@ def build_property property.city = location.city property.neighborhood = location.neighborhood property.max_guests = property_hash.get("CanSleepMax").to_i + property.surface = property_hash.get("Space").to_i + property.surface_unit = SURFACE_UNIT property.number_of_bedrooms = number_of_bedrooms property.floor = floor property.amenities = amenities_dictionary.convert diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index f26de5237..9ed42f8c6 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -77,6 +77,14 @@ expect(property.number_of_bedrooms).to eq(3) end + it "sets surface to the property" do + expect(property.surface).to eq(39) + end + + it "sets surface_unit to the property" do + expect(property.surface_unit).to eq("metric") + end + it "sets address information to the property" do expect(property.lat).to eq(55.0003426) expect(property.lng).to eq(73.2965942999999) From 71ca1708463b0eec340bf4642687d6db40bfaaaa Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 22 Sep 2016 00:22:59 +0600 Subject: [PATCH 056/118] RU: include NLA flag --- .../rentals_united/templates/property_ids_fetch.xml.erb | 1 + .../suppliers/rentals_united/payload_builder_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb index ca49b31f3..a6d825667 100644 --- a/lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb +++ b/lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb @@ -5,4 +5,5 @@ <%= location_id %> + false diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index 5c12d496f..ea9f73cf4 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -27,6 +27,14 @@ location_id = hash.get("Pull_ListProp_RQ.LocationID") expect(location_id).to eq(params[:location_id]) end + + it 'adds include_nla flag to request' do + xml = builder.build_property_ids_fetch_payload(params[:property_id]) + hash = to_hash(xml) + + include_nla = hash.get("Pull_ListProp_RQ.IncludeNLA") + expect(include_nla).to eq(false) + end end describe '#build_location_ids_fetch_payload' do From 3d0f847c43851e53373805740f8697c3d24de8e8 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 22 Sep 2016 01:09:49 +0600 Subject: [PATCH 057/118] RU: add more location builder specs + Result.error when location is unknown --- .../commands/locations_fetcher.rb | 9 +- .../rentals_united/mappers/location.rb | 4 + .../rentals_united/locations/locations.xml | 1 + .../locations/no_parent_for_city.xml | 18 ++ .../locations/no_parent_for_country.xml | 18 ++ .../locations/no_parent_for_neighborhood.xml | 18 ++ .../locations/no_parent_for_region.xml | 18 ++ .../commands/locations_fetcher_spec.rb | 162 ++++++++++++++++-- 8 files changed, 233 insertions(+), 15 deletions(-) create mode 100644 spec/fixtures/rentals_united/locations/no_parent_for_city.xml create mode 100644 spec/fixtures/rentals_united/locations/no_parent_for_country.xml create mode 100644 spec/fixtures/rentals_united/locations/no_parent_for_neighborhood.xml create mode 100644 spec/fixtures/rentals_united/locations/no_parent_for_region.xml diff --git a/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb index 24be72ca0..ed7441b48 100644 --- a/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/locations_fetcher.rb @@ -29,7 +29,14 @@ def fetch_locations if valid_status?(result_hash, ROOT_TAG) raw_locations = build_raw_locations(result_hash) - locations = location_ids.map { |id| build_location(id, raw_locations) } + + locations = location_ids.map do |id| + location = build_location(id, raw_locations) + + return Result.error(:unknown_location) unless location + + location + end Result.new(locations) else diff --git a/lib/concierge/suppliers/rentals_united/mappers/location.rb b/lib/concierge/suppliers/rentals_united/mappers/location.rb index fa287475c..1b33aefb9 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/location.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/location.rb @@ -37,6 +37,8 @@ def build_location location = Entities::Location.new(location_id) current_level = find_location_data(location_id) + return nil unless current_level + current_level_type = current_level[:type] location_hash = {} @@ -46,6 +48,8 @@ def build_location parent_location_id = current_level[:parent_id] current_level = find_location_data(parent_location_id) + return nil unless current_level + current_level_type = current_level[:type] update_location_hash(location_hash, current_level) diff --git a/spec/fixtures/rentals_united/locations/locations.xml b/spec/fixtures/rentals_united/locations/locations.xml index c9534f5d4..44be0384a 100644 --- a/spec/fixtures/rentals_united/locations/locations.xml +++ b/spec/fixtures/rentals_united/locations/locations.xml @@ -13,5 +13,6 @@ Pantin Paris Plaisir + Neighborhood diff --git a/spec/fixtures/rentals_united/locations/no_parent_for_city.xml b/spec/fixtures/rentals_united/locations/no_parent_for_city.xml new file mode 100644 index 000000000..44aab1776 --- /dev/null +++ b/spec/fixtures/rentals_united/locations/no_parent_for_city.xml @@ -0,0 +1,18 @@ + + Success + + Worldwide + Europe + France + Germany + Greece + Hungary + Languedoc-Roussillon + Ile-de-France + Haute-Normandie + Pantin + Paris + Plaisir + Neighborhood + + diff --git a/spec/fixtures/rentals_united/locations/no_parent_for_country.xml b/spec/fixtures/rentals_united/locations/no_parent_for_country.xml new file mode 100644 index 000000000..bd21e8143 --- /dev/null +++ b/spec/fixtures/rentals_united/locations/no_parent_for_country.xml @@ -0,0 +1,18 @@ + + Success + + Worldwide + Europe + France + Germany + Greece + Hungary + Languedoc-Roussillon + Ile-de-France + Haute-Normandie + Pantin + Paris + Plaisir + Neighborhood + + diff --git a/spec/fixtures/rentals_united/locations/no_parent_for_neighborhood.xml b/spec/fixtures/rentals_united/locations/no_parent_for_neighborhood.xml new file mode 100644 index 000000000..47b1d8f92 --- /dev/null +++ b/spec/fixtures/rentals_united/locations/no_parent_for_neighborhood.xml @@ -0,0 +1,18 @@ + + Success + + Worldwide + Europe + France + Germany + Greece + Hungary + Languedoc-Roussillon + Ile-de-France + Haute-Normandie + Pantin + Paris + Plaisir + Neighborhood + + diff --git a/spec/fixtures/rentals_united/locations/no_parent_for_region.xml b/spec/fixtures/rentals_united/locations/no_parent_for_region.xml new file mode 100644 index 000000000..4c7ef3c4d --- /dev/null +++ b/spec/fixtures/rentals_united/locations/no_parent_for_region.xml @@ -0,0 +1,18 @@ + + Success + + Worldwide + Europe + France + Germany + Greece + Hungary + Languedoc-Roussillon + Ile-de-France + Haute-Normandie + Pantin + Paris + Plaisir + Neighborhood + + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb index 87bc061fb..ffe30d36a 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb @@ -5,27 +5,155 @@ include Support::Fixtures let(:credentials) { Concierge::Credentials.for("rentals_united") } - let(:location_ids) { ["1505"] } - let(:subject) { described_class.new(credentials, location_ids) } let(:url) { credentials.url } - it "fetches and returns one location" do - stub_data = read_fixture("rentals_united/locations/locations.xml") - stub_call(:post, url) { [200, {}, stub_data] } + context "when location does not exist" do + let(:location_ids) { ["9998"] } + let(:subject) { described_class.new(credentials, location_ids) } - result = subject.fetch_locations - expect(result).to be_success - expect(result.value.size).to eq(1) + it "returns error" do + stub_data = read_fixture("rentals_united/locations/locations.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).not_to be_success + expect(result.error.code).to eq(:unknown_location) + end + end + + context "when fetching location for neighborhood" do + let(:location_ids) { ["9999"] } + let(:subject) { described_class.new(credentials, location_ids) } + + it "fetches and returns one location" do + stub_data = read_fixture("rentals_united/locations/locations.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).to be_success + expect(result.value.size).to eq(1) + + location = result.value.first + expect(location).to be_kind_of(RentalsUnited::Entities::Location) + expect(location.id).to eq("9999") + expect(location.city).to eq("Paris") + expect(location.region).to eq("Ile-de-France") + expect(location.country).to eq("France") + expect(location.neighborhood).to eq("Neighborhood") + end - location = result.value.first - expect(location).to be_kind_of(RentalsUnited::Entities::Location) - expect(location.id).to eq("1505") - expect(location.city).to eq("Paris") - expect(location.region).to eq("Ile-de-France") - expect(location.country).to eq("France") + context "when parent location does not exist" do + it "returns error" do + stub_data = read_fixture("rentals_united/locations/no_parent_for_neighborhood.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).not_to be_success + expect(result.error.code).to eq(:unknown_location) + end + end + end + + context "when fetching location for city" do + let(:location_ids) { ["1505"] } + let(:subject) { described_class.new(credentials, location_ids) } + + it "fetches and returns one location" do + stub_data = read_fixture("rentals_united/locations/locations.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).to be_success + expect(result.value.size).to eq(1) + + location = result.value.first + expect(location).to be_kind_of(RentalsUnited::Entities::Location) + expect(location.id).to eq("1505") + expect(location.city).to eq("Paris") + expect(location.region).to eq("Ile-de-France") + expect(location.country).to eq("France") + end + + context "when parent location does not exist" do + it "returns error" do + stub_data = read_fixture("rentals_united/locations/no_parent_for_city.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).not_to be_success + expect(result.error.code).to eq(:unknown_location) + end + end + end + + context "when fetching location for region" do + let(:location_ids) { ["10351"] } + let(:subject) { described_class.new(credentials, location_ids) } + + it "fetches and returns one location" do + stub_data = read_fixture("rentals_united/locations/locations.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).to be_success + expect(result.value.size).to eq(1) + + location = result.value.first + expect(location).to be_kind_of(RentalsUnited::Entities::Location) + expect(location.id).to eq("10351") + expect(location.city).to eq(nil) + expect(location.region).to eq("Ile-de-France") + expect(location.country).to eq("France") + end + + context "when parent location does not exist" do + it "returns error" do + stub_data = read_fixture("rentals_united/locations/no_parent_for_region.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).not_to be_success + expect(result.error.code).to eq(:unknown_location) + end + end + end + + context "when fetching location for country" do + let(:location_ids) { ["20"] } + let(:subject) { described_class.new(credentials, location_ids) } + + it "fetches and returns one location" do + stub_data = read_fixture("rentals_united/locations/locations.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).to be_success + expect(result.value.size).to eq(1) + + location = result.value.first + expect(location).to be_kind_of(RentalsUnited::Entities::Location) + expect(location.id).to eq("20") + expect(location.city).to eq(nil) + expect(location.region).to eq(nil) + expect(location.country).to eq("France") + end + + context "when parent location does not exist" do + it "returns location anyway" do + stub_data = read_fixture("rentals_united/locations/no_parent_for_country.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_locations + expect(result).not_to be_success + expect(result.error.code).to eq(:unknown_location) + end + end end context "when response from the api has error status" do + let(:location_ids) { ["1505"] } + let(:subject) { described_class.new(credentials, location_ids) } + it "returns a result with an appropriate error" do stub_data = read_fixture("rentals_united/locations/error_status.xml") stub_call(:post, url) { [200, {}, stub_data] } @@ -45,6 +173,9 @@ end context "when response from the api is not well-formed xml" do + let(:location_ids) { ["1505"] } + let(:subject) { described_class.new(credentials, location_ids) } + it "returns a result with an appropriate error" do stub_data = read_fixture("rentals_united/bad_xml.xml") stub_call(:post, url) { [200, {}, stub_data] } @@ -64,6 +195,9 @@ end context "when request fails due to timeout error" do + let(:location_ids) { ["1505"] } + let(:subject) { described_class.new(credentials, location_ids) } + it "returns a result with an appropriate error" do stub_call(:post, url) { raise Faraday::TimeoutError } From 3f4fadeeaefdcabb4f64316ec7f58d3d66670f03 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 09:45:09 +0600 Subject: [PATCH 058/118] RU: refactor mappers + add property owner info --- .../suppliers/rentals_united/metadata.rb | 40 ++- .../rentals_united/commands/owners_fetcher.rb | 41 +++ .../commands/property_fetcher.rb | 13 +- .../rentals_united/entities/owner.rb | 18 ++ .../rentals_united/entities/property.rb | 48 +++ .../suppliers/rentals_united/importer.rb | 22 +- .../suppliers/rentals_united/mappers/owner.rb | 29 ++ .../rentals_united/mappers/property.rb | 102 ++---- .../mappers/roomorama_property.rb | 121 +++++++ .../rentals_united/payload_builder.rb | 5 + .../templates/owners_fetch.xml.erb | 6 + spec/fixtures/rentals_united/owners/empty.xml | 4 + .../rentals_united/owners/error_status.xml | 3 + .../fixtures/rentals_united/owners/owners.xml | 19 ++ .../not_supported_boat_property.xml | 10 - .../not_supported_camping_property.xml | 10 - .../not_supported_hotel_property.xml | 10 - .../commands/owners_fetcher_spec.rb | 90 ++++++ .../commands/property_fetcher_spec.rb | 300 +----------------- .../suppliers/rentals_united/importer_spec.rb | 45 ++- .../rentals_united/mappers/owners_spec.rb | 27 ++ .../rentals_united/mappers/property_spec.rb | 202 ++++++++++++ .../mappers/roomorama_property_spec.rb | 284 +++++++++++++++++ .../rentals_united/payload_builder_spec.rb | 11 + .../suppliers/rentals_united/metadata_spec.rb | 174 ++++++---- 25 files changed, 1127 insertions(+), 507 deletions(-) create mode 100644 lib/concierge/suppliers/rentals_united/commands/owners_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/owner.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/property.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/owner.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb create mode 100644 lib/concierge/suppliers/rentals_united/templates/owners_fetch.xml.erb create mode 100644 spec/fixtures/rentals_united/owners/empty.xml create mode 100644 spec/fixtures/rentals_united/owners/error_status.xml create mode 100644 spec/fixtures/rentals_united/owners/owners.xml delete mode 100644 spec/fixtures/rentals_united/properties/not_supported_boat_property.xml delete mode 100644 spec/fixtures/rentals_united/properties/not_supported_camping_property.xml delete mode 100644 spec/fixtures/rentals_united/properties/not_supported_hotel_property.xml create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/owners_fetcher_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/owners_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/property_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index d2fd373e8..71013196c 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -26,6 +26,11 @@ def perform currencies = result.value + result = synchronisation.new_context { fetch_owners } + return unless result.success? + + owners = result.value + locations.each do |location| location.currency = currencies[location.id] @@ -35,13 +40,28 @@ def perform property_ids = result.value - result = synchronisation.new_context { fetch_properties_by_ids(property_ids, location) } + result = synchronisation.new_context { fetch_properties_by_ids(property_ids) } if result.success? properties = result.value properties.each do |property| - synchronisation.start(property.identifier) { Result.new(property) } + owner = find_owner(owners, property.owner_id) + + if owner + synchronisation.start(property.id) do + mapper = ::RentalsUnited::Mappers::RoomoramaProperty.new( + property, + location, + owner + ) + mapper.build_roomorama_property + end + else + message = "Failed to find owner for property id `#{property.id}`" + announce_context_error(message, result) + return + end end else message = "Failed to fetch properties for ids `#{property_ids}` in location `#{location.id}`" @@ -70,6 +90,10 @@ def credentials ) end + def find_owner(owners, owner_id) + owners.find { |o| o.id == owner_id } + end + def fetch_location_ids announce_error("Failed to fetch location ids") do importer.fetch_location_ids @@ -88,15 +112,21 @@ def fetch_location_currencies end end + def fetch_owners + announce_error("Failed to fetch owners") do + importer.fetch_owners + end + end + def fetch_property_ids(location_id) announce_error("Failed to fetch properties for location `#{location_id}`") do importer.fetch_property_ids(location_id) end end - def fetch_properties_by_ids(property_ids, location) - announce_error("Failed to fetch properties for location `#{location.id}`") do - importer.fetch_properties_by_ids(property_ids, location) + def fetch_properties_by_ids(property_ids) + announce_error("Failed to fetch properties by ids `#{property_ids}`") do + importer.fetch_properties_by_ids(property_ids) end end diff --git a/lib/concierge/suppliers/rentals_united/commands/owners_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/owners_fetcher.rb new file mode 100644 index 000000000..a535dd2ab --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/owners_fetcher.rb @@ -0,0 +1,41 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::OwnersFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # owners from RentalsUnited + class OwnersFetcher < BaseFetcher + ROOT_TAG = "Pull_ListAllOwners_RS" + + # Retrieves owners. + # + # Returns a +Result+ wrapping +Array+ of +Entities::Owner+ + # Returns a +Result+ with +Result::Error+ when operation fails + def fetch_owners + payload = payload_builder.build_owners_fetch_payload + result = api_call(payload) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_owners(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_owners(hash) + owners = hash.get("#{ROOT_TAG}.Owners.Owner") + + Array(owners).map do |owner_hash| + safe_hash = Concierge::SafeAccessHash.new(owner_hash) + mapper = RentalsUnited::Mappers::Owner.new(safe_hash) + mapper.build_owner + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb index 15046389b..8f60ea8f0 100644 --- a/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/property_fetcher.rb @@ -6,7 +6,7 @@ module Commands # property from RentalsUnited, parsing the response, and building # +Result+ object. class PropertyFetcher < BaseFetcher - attr_reader :property_id, :location + attr_reader :property_id ROOT_TAG = "Pull_ListSpecProp_RS" @@ -16,25 +16,22 @@ class PropertyFetcher < BaseFetcher # # * +credentials+ # * +property_id+ [String] - # * +location+ [Entities::Location] # # Usage: # # RentalsUnited::Commands::PropertyFetcher.new( # credentials, - # property_id, - # location + # property_id # ) - def initialize(credentials, property_id, location) + def initialize(credentials, property_id) super(credentials) @property_id = property_id - @location = location end # Retrieves property # - # Returns a +Result+ wrapping +Roomorama::Property+ object + # Returns a +Result+ wrapping +Entities::Property+ object # Returns a +Result+ with +Result::Error+ when operation fails def fetch_property payload = payload_builder.build_property_fetch_payload(property_id) @@ -56,7 +53,7 @@ def build_property(hash) property = hash.get("#{ROOT_TAG}.Property") return error_result(hash, ROOT_TAG) unless property - mapper = RentalsUnited::Mappers::Property.new(property, location) + mapper = RentalsUnited::Mappers::Property.new(property) mapper.build_property end end diff --git a/lib/concierge/suppliers/rentals_united/entities/owner.rb b/lib/concierge/suppliers/rentals_united/entities/owner.rb new file mode 100644 index 000000000..66d978939 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/owner.rb @@ -0,0 +1,18 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::Owner+ + # + # This entity represents an owner object. + class Owner + attr_accessor :id, :first_name, :last_name, :email, :phone + + def initialize(id:, first_name:, last_name:, email:, phone:) + @id = id + @first_name = first_name + @last_name = last_name + @email = email + @phone = phone + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/entities/property.rb b/lib/concierge/suppliers/rentals_united/entities/property.rb new file mode 100644 index 000000000..f77fa1374 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/property.rb @@ -0,0 +1,48 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::Property+ + # + # This entity represents a property object. + class Property + attr_accessor :id, :title, :description, :lat, :lng, :address, + :postal_code, :check_in_time, :check_out_time, :max_guests, + :surface, :bedroom_type_id, :property_type_id, :floor, + :images, :amenities, :owner_id + + attr_writer :active, :archived + + def initialize(id:, title:, description:, lat:, lng:, address:, + postal_code:, check_in_time:, check_out_time:, + max_guests:, surface:, bedroom_type_id:, property_type_id:, + floor:, images:, amenities:, active:, archived:, owner_id:) + @id = id + @title = title + @description = description + @lat = lat + @lng = lng + @address = address + @postal_code = postal_code + @check_in_time = check_in_time + @check_out_time = check_out_time + @max_guests = max_guests + @surface = surface + @bedroom_type_id = bedroom_type_id + @property_type_id = property_type_id + @floor = floor + @owner_id = owner_id + @active = active + @archived = archived + @images = images + @amenities = amenities + end + + def active? + @active + end + + def archived? + @archived + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb index 50051e4b1..586430521 100644 --- a/lib/concierge/suppliers/rentals_united/importer.rb +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -63,24 +63,23 @@ def fetch_property_ids(location_id) # Retrieves property by its id. # - # Returns a +Result+ wrapping +Roomorama::Property+ object + # Returns a +Result+ wrapping +Entities::Property+ object # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_property(property_id, location) + def fetch_property(property_id) property_fetcher = Commands::PropertyFetcher.new( credentials, - property_id, - location + property_id ) property_fetcher.fetch_property end # Retrieves properties by given ids # - # Returns a +Result+ wrapping +Array+ of +Roomorama::Property+ objects + # Returns a +Result+ wrapping +Array+ of +Entities::Property+ objects # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_properties_by_ids(property_ids, location) + def fetch_properties_by_ids(property_ids) properties = property_ids.map do |property_id| - result = fetch_property(property_id, location) + result = fetch_property(property_id) return result unless result.success? @@ -89,5 +88,14 @@ def fetch_properties_by_ids(property_ids, location) Result.new(properties) end + + # Retrieves owners + # + # Returns a +Result+ wrapping +Array+ of +Entities::Owner+ objects + # Returns a +Result+ with +Result::Error+ when operation fails + def fetch_owners + fetcher = Commands::OwnersFetcher.new(credentials) + fetcher.fetch_owners + end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/owner.rb b/lib/concierge/suppliers/rentals_united/mappers/owner.rb new file mode 100644 index 000000000..2f177d33e --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/owner.rb @@ -0,0 +1,29 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Owner+ + # + # This class is responsible for building an owner object. + class Owner + attr_reader :owner_hash + + # Initialize +RentalsUnited::Mappers::Owner+ mapper + # + # Arguments: + # + # * +owner_hash+ [Concierge::SafeAccessHash] owner hash + def initialize(owner_hash) + @owner_hash = owner_hash + end + + def build_owner + Entities::Owner.new( + id: owner_hash.get("@OwnerID"), + first_name: owner_hash.get("FirstName"), + last_name: owner_hash.get("SurName"), + email: owner_hash.get("Email"), + phone: owner_hash.get("Phone") + ) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index 5d829a7cd..1ab8b79fa 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -6,90 +6,58 @@ module Mappers # +RentalsUnited::Entities::Property+ object from a hash which was fetched # from the RentalsUnited API. class Property - attr_reader :property_hash, :location, :amenities_dictionary, - :property_type + attr_reader :property_hash EN_DESCRIPTION_LANG_CODE = "1" - CANCELLATION_POLICY = "super_elite" - DEFAULT_PROPERTY_RATE = "9999" - MINIMUM_STAY = 1 - SURFACE_UNIT = "metric" # Initialize +RentalsUnited::Mappers::Property+ # # Arguments: # # * +property_hash+ [Concierge::SafeAccessHash] property hash object - # * +location+ [Entities::Location] location object - def initialize(property_hash, location) + def initialize(property_hash) @property_hash = property_hash - @location = location - @amenities_dictionary = Dictionaries::Amenities.new( - property_hash.get("Amenities.Amenity") - ) - @property_type ||= Dictionaries::PropertyTypes.find( - property_hash.get("ObjectTypeID") - ) end # Builds a property # # Returns [RentalsUnited::Entities::Property] def build_property - return nil if not_active? || archived? || not_supported_property_type? - - property = Roomorama::Property.new(property_hash.get("ID")) - property.title = property_hash.get("Name") - property.description = en_description(property_hash) - property.lat = property_hash.get("Coordinates.Latitude").to_f - property.lng = property_hash.get("Coordinates.Longitude").to_f - property.address = property_hash.get("Street") - property.postal_code = property_hash.get("ZipCode").to_s.strip - property.check_in_time = check_in_time - property.check_out_time = check_out_time - property.country_code = country_code(location) - property.currency = location.currency - property.city = location.city - property.neighborhood = location.neighborhood - property.max_guests = property_hash.get("CanSleepMax").to_i - property.surface = property_hash.get("Space").to_i - property.surface_unit = SURFACE_UNIT - property.number_of_bedrooms = number_of_bedrooms - property.floor = floor - property.amenities = amenities_dictionary.convert - property.pets_allowed = amenities_dictionary.pets_allowed? - property.smoking_allowed = amenities_dictionary.smoking_allowed? - property.nightly_rate = DEFAULT_PROPERTY_RATE - property.weekly_rate = DEFAULT_PROPERTY_RATE - property.monthly_rate = DEFAULT_PROPERTY_RATE - property.minimum_stay = MINIMUM_STAY - property.cancellation_policy = CANCELLATION_POLICY - property.default_to_available = false - property.instant_booking! - - set_property_type_and_subtype!(property) if property_type - set_images!(property) + property = Entities::Property.new( + id: property_hash.get("ID"), + title: property_hash.get("Name"), + lat: property_hash.get("Coordinates.Latitude").to_f, + lng: property_hash.get("Coordinates.Longitude").to_f, + address: property_hash.get("Street"), + postal_code: property_hash.get("ZipCode").to_s.strip, + max_guests: property_hash.get("CanSleepMax").to_i, + bedroom_type_id: property_hash.get("PropertyTypeID"), + property_type_id: property_hash.get("ObjectTypeID"), + active: property_hash.get("IsActive"), + archived: property_hash.get("IsArchived"), + surface: property_hash.get("Space").to_i, + owner_id: property_hash.get("OwnerID"), + check_in_time: check_in_time, + check_out_time: check_out_time, + floor: floor, + description: en_description(property_hash), + images: build_images, + amenities: build_amenities + ) property end private - def set_property_type_and_subtype!(property) - property.type = property_type.roomorama_name - property.subtype = property_type.roomorama_subtype_name + def build_amenities + Array(property_hash.get("Amenities.Amenity")) end - def set_images!(property) + def build_images raw_images = Array(property_hash.get("Images.Image")) mapper = Mappers::ImageSet.new(raw_images) - mapper.build_images.each { |image| property.add_image(image) } - end - - def number_of_bedrooms - RentalsUnited::Dictionaries::Bedrooms.count_by_type_id( - property_hash.get("PropertyTypeID") - ) + mapper.build_images end # RU sends -1000 for Basement @@ -123,22 +91,6 @@ def check_in_time def check_out_time property_hash.get("CheckInOut.CheckOutUntil") end - - def country_code(location) - Converters::CountryCode.code_by_name(location.country) - end - - def not_active? - property_hash.get("IsActive") == false - end - - def archived? - property_hash.get("IsArchived") == true - end - - def not_supported_property_type? - property_type.nil? - end end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb new file mode 100644 index 000000000..b32862078 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb @@ -0,0 +1,121 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::RoomoramaProperty+ + # + # This class is responsible for building a +Roomorama::Property+ object + class RoomoramaProperty + attr_reader :ru_property, :location, :owner + + EN_DESCRIPTION_LANG_CODE = "1" + CANCELLATION_POLICY = "super_elite" + DEFAULT_PROPERTY_RATE = "9999" + MINIMUM_STAY = 1 + SURFACE_UNIT = "metric" + + # Initialize +RentalsUnited::Mappers::Property+ + # + # Arguments: + # + # * +ru_property+ [Entities::Property] RU property object + # * +location+ [Entities::Location] location object + # * +owner+ [Entities::OwnerID] owner object + def initialize(ru_property, location, owner) + @ru_property = ru_property + @location = location + @owner = owner + end + + # Builds a property + def build_roomorama_property + return archived_error if ru_property.archived? + return not_active_error unless ru_property.active? + return property_type_error unless supported_property_type? + + property = Roomorama::Property.new(ru_property.id) + property.title = ru_property.title + property.description = ru_property.description + property.lat = ru_property.lat + property.lng = ru_property.lng + property.address = ru_property.address + property.postal_code = ru_property.postal_code + property.check_in_time = ru_property.check_in_time + property.check_out_time = ru_property.check_out_time + property.max_guests = ru_property.max_guests + property.surface = ru_property.surface + property.floor = ru_property.floor + property.country_code = country_code(location) + property.currency = location.currency + property.city = location.city + property.neighborhood = location.neighborhood + property.owner_name = full_name(owner) + property.owner_email = owner.email + property.owner_phone_number = owner.phone + property.number_of_bedrooms = number_of_bedrooms + property.amenities = amenities_dictionary.convert + property.pets_allowed = amenities_dictionary.pets_allowed? + property.smoking_allowed = amenities_dictionary.smoking_allowed? + property.type = property_type.roomorama_name + property.subtype = property_type.roomorama_subtype_name + property.surface_unit = SURFACE_UNIT + property.nightly_rate = DEFAULT_PROPERTY_RATE + property.weekly_rate = DEFAULT_PROPERTY_RATE + property.monthly_rate = DEFAULT_PROPERTY_RATE + property.minimum_stay = MINIMUM_STAY + property.cancellation_policy = CANCELLATION_POLICY + property.default_to_available = false + property.instant_booking! + + set_images!(property) + + Result.new(property) + end + + private + def set_images!(property) + ru_property.images.each { |image| property.add_image(image) } + end + + def full_name(owner) + [owner.first_name, owner.last_name].join(" ").strip + end + + def number_of_bedrooms + RentalsUnited::Dictionaries::Bedrooms.count_by_type_id( + ru_property.bedroom_type_id + ) + end + + def country_code(location) + Converters::CountryCode.code_by_name(location.country) + end + + def supported_property_type? + !!property_type + end + + def property_type + @property_type ||= Dictionaries::PropertyTypes.find( + ru_property.property_type_id + ) + end + + def property_type_error + Result.error(:property_type_not_supported) + end + + def archived_error + Result.error(:attempt_to_build_archived_property) + end + + def not_active_error + Result.error(:attempt_to_build_not_active_property) + end + + def amenities_dictionary + @amenities_dictionary ||= Dictionaries::Amenities.new( + ru_property.amenities + ) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index 4ffb94c0e..587a3ccb7 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -44,6 +44,11 @@ def build_property_fetch_payload(property_id) render(:property_fetch, template_locals) end + def build_owners_fetch_payload + template_locals = { credentials: credentials } + render(:owners_fetch, template_locals) + end + private def render(template_name, local_vars) path = Hanami.root.join(TEMPLATES_PATH, "#{template_name}.xml.erb") diff --git a/lib/concierge/suppliers/rentals_united/templates/owners_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/owners_fetch.xml.erb new file mode 100644 index 000000000..c87b41951 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/owners_fetch.xml.erb @@ -0,0 +1,6 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + diff --git a/spec/fixtures/rentals_united/owners/empty.xml b/spec/fixtures/rentals_united/owners/empty.xml new file mode 100644 index 000000000..488e424e7 --- /dev/null +++ b/spec/fixtures/rentals_united/owners/empty.xml @@ -0,0 +1,4 @@ + + Success + + diff --git a/spec/fixtures/rentals_united/owners/error_status.xml b/spec/fixtures/rentals_united/owners/error_status.xml new file mode 100644 index 000000000..60223bc04 --- /dev/null +++ b/spec/fixtures/rentals_united/owners/error_status.xml @@ -0,0 +1,3 @@ + + Test Error + diff --git a/spec/fixtures/rentals_united/owners/owners.xml b/spec/fixtures/rentals_united/owners/owners.xml new file mode 100644 index 000000000..591fe8bf9 --- /dev/null +++ b/spec/fixtures/rentals_united/owners/owners.xml @@ -0,0 +1,19 @@ + + Success + + + Foo + Bar + RU Test + foobar@gmail.com + 519461272 + + + John + Doe + + john.doe@gmail.com + 6092932393 + + + diff --git a/spec/fixtures/rentals_united/properties/not_supported_boat_property.xml b/spec/fixtures/rentals_united/properties/not_supported_boat_property.xml deleted file mode 100644 index 7d0fbe59d..000000000 --- a/spec/fixtures/rentals_united/properties/not_supported_boat_property.xml +++ /dev/null @@ -1,10 +0,0 @@ - - Success - - -1 - 519688 - true - false - 64 - - diff --git a/spec/fixtures/rentals_united/properties/not_supported_camping_property.xml b/spec/fixtures/rentals_united/properties/not_supported_camping_property.xml deleted file mode 100644 index ad06ebff7..000000000 --- a/spec/fixtures/rentals_united/properties/not_supported_camping_property.xml +++ /dev/null @@ -1,10 +0,0 @@ - - Success - - -1 - 519688 - true - false - 66 - - diff --git a/spec/fixtures/rentals_united/properties/not_supported_hotel_property.xml b/spec/fixtures/rentals_united/properties/not_supported_hotel_property.xml deleted file mode 100644 index 41d8872ba..000000000 --- a/spec/fixtures/rentals_united/properties/not_supported_hotel_property.xml +++ /dev/null @@ -1,10 +0,0 @@ - - Success - - -1 - 519688 - true - false - 20 - - diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/owners_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/owners_fetcher_spec.rb new file mode 100644 index 000000000..2b4e1133c --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/owners_fetcher_spec.rb @@ -0,0 +1,90 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::OwnersFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:subject) { described_class.new(credentials) } + let(:url) { credentials.url } + + it "fetches owners" do + stub_data = read_fixture("rentals_united/owners/owners.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_owners + expect(result).to be_success + + owners = result.value + expect(owners).to all(be_kind_of(RentalsUnited::Entities::Owner)) + expect(owners.size).to eq(2) + + expected_owner_ids = %w(427698 419680) + expected_owner_ids.each do |owner_id| + expect(owners.map(&:id).include?(owner_id)).to be true + end + end + + it "returns [] when there is no owners" do + stub_data = read_fixture("rentals_united/owners/empty.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_owners + expect(result).to be_success + + owners = result.value + expect(owners).to eq([]) + end + + context "when response from the api has error status" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/owners/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_owners + + expect(result).not_to be_success + expect(result.error.code).to eq("9999") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `9999`, and description ``" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_owners + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.fetch_owners + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 9ed42f8c6..502dbe712 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -6,16 +6,7 @@ let(:credentials) { Concierge::Credentials.for("rentals_united") } let(:property_id) { "1234" } - let(:location) do - double( - id: '1505', - city: 'Paris', - neighborhood: 'Ile-de-France', - country: 'France', - currency: 'EUR' - ) - end - let(:subject) { described_class.new(credentials, property_id, location) } + let(:subject) { described_class.new(credentials, property_id) } let(:url) { credentials.url } it "returns an error if property does not exist" do @@ -50,272 +41,11 @@ end it "returns property object" do - expect(property).to be_kind_of(Roomorama::Property) + expect(property).to be_kind_of(RentalsUnited::Entities::Property) end it "sets id to the property" do - expect(property.identifier).to eq("519688") - end - - it "sets title to the property" do - expect(property.title).to eq("Test property") - end - - it "sets type to the property" do - expect(property.type).to eq("house") - end - - it "sets subtype to the property" do - expect(property.subtype).to eq("villa") - end - - it "sets max_guests to the property" do - expect(property.max_guests).to eq(2) - end - - it "sets number_of_bedrooms to the property" do - expect(property.number_of_bedrooms).to eq(3) - end - - it "sets surface to the property" do - expect(property.surface).to eq(39) - end - - it "sets surface_unit to the property" do - expect(property.surface_unit).to eq("metric") - end - - it "sets address information to the property" do - expect(property.lat).to eq(55.0003426) - expect(property.lng).to eq(73.2965942999999) - expect(property.address).to eq("Test street address") - expect(property.city).to eq("Paris") - expect(property.neighborhood).to eq("Ile-de-France") - expect(property.postal_code).to eq("644119") - expect(property.country_code).to eq("FR") - end - - it "sets en description to the property" do - expect(property.description).to eq("Test description") - end - - it "sets check_in_time to the property" do - expect(property.check_in_time).to eq("13:00-17:00") - end - - it "sets check_out_time to the property" do - expect(property.check_out_time).to eq("11:00") - end - - it "sets currency to the property" do - expect(property.currency).to eq("EUR") - end - - it "sets cancellation_policy to the property" do - expect(property.cancellation_policy).to eq("super_elite") - end - - it "sets default_to_available flag to false" do - expect(property.default_to_available).to eq(false) - end - - it "does not set multi-unit flag" do - expect(property.multi_unit).to be_nil - end - - it "sets instant_booking flag" do - expect(property.instant_booking?).to eq(true) - end - - context "when multiple descriptions are available" do - let(:file_name) { "rentals_united/properties/property_with_multiple_descriptions.xml" } - - it "sets en description to the property" do - expect(property.description).to eq("Yet another one description") - end - end - - context "when no available descriptions" do - let(:file_name) { "rentals_united/properties/property_without_descriptions.xml" } - - it "sets description to nil" do - expect(property.description).to be_nil - end - end - - context "when mapping amenities" do - it "adds amenities to property" do - expect(property.amenities).to eq( - ["bed_linen_and_towels", "airconditioning", "pool", "wheelchairaccess", "elevator", "parking"] - ) - end - - context "and when there is no amenities" do - let(:file_name) { "rentals_united/properties/property_without_amenities.xml" } - - it "sets empty amenities" do - expect(property.amenities).to eq([]) - end - end - - it "sets smoking_allowed to false" do - amenities_dict = RentalsUnited::Dictionaries::Amenities - - expect_any_instance_of(amenities_dict) - .to(receive(:smoking_allowed?)) - .and_return(false) - - - expect(property.smoking_allowed).to eq(false) - end - - it "sets smoking_allowed to true" do - amenities_dict = RentalsUnited::Dictionaries::Amenities - - expect_any_instance_of(amenities_dict) - .to(receive(:smoking_allowed?)) - .and_return(true) - - expect(property.smoking_allowed).to eq(true) - end - - it "sets pets_allowed to false" do - amenities_dict = RentalsUnited::Dictionaries::Amenities - - expect_any_instance_of(amenities_dict) - .to(receive(:pets_allowed?)) - .and_return(false) - - expect(property.pets_allowed).to eq(false) - end - - it "sets pets_allowed to true" do - amenities_dict = RentalsUnited::Dictionaries::Amenities - - expect_any_instance_of(amenities_dict) - .to(receive(:pets_allowed?)) - .and_return(true) - - expect(property.pets_allowed).to eq(true) - end - end - - context "when mapping single image to property" do - let(:file_name) { "rentals_united/properties/property_with_one_image.xml" } - - let(:expected_images) do - { - "a0c68acc113db3b58376155c283dfd59" => { - url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg", - caption: 'Main image' - } - } - end - - it "adds array of with one image to the property" do - expect(property.images.size).to eq(1) - expect(property.images).to all(be_kind_of(Roomorama::Image)) - expect(property.images.map(&:identifier)).to eq(expected_images.keys) - - property.images.each do |image| - expect(image.url).to eq(expected_images[image.identifier][:url]) - expect(image.caption).to eq(expected_images[image.identifier][:caption]) - end - end - end - - context "when mapping multiple images to property" do - let(:expected_images) do - { - "62fc304eb20a25669b84d2ca2ea61308" => { - url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082398988145159.jpg", - caption: 'Interior' - }, - "a0c68acc113db3b58376155c283dfd59" => { - url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg", - caption: 'Main image' - } - } - end - - it "adds array of images to the property" do - expect(property.images.size).to eq(2) - expect(property.images).to all(be_kind_of(Roomorama::Image)) - expect(property.images.map(&:identifier)).to eq(expected_images.keys) - - property.images.each do |image| - expect(image.url).to eq(expected_images[image.identifier][:url]) - expect(image.caption).to eq(expected_images[image.identifier][:caption]) - end - end - end - - context "when there is no images for property" do - let(:file_name) { "rentals_united/properties/property_without_images.xml" } - - it "returns an empty array for property images" do - expect(property.images).to eq([]) - end - end - end - - context "when property is not active" do - it "returns nil" do - stub_data = read_fixture("rentals_united/properties/not_active.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property - - expect(result).to be_success - expect(result.value).to be_nil - end - end - - context "when property is archived" do - it "returns nil" do - stub_data = read_fixture("rentals_united/properties/archived.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property - - expect(result).to be_success - expect(result.value).to be_nil - end - end - - context "when property is hotel-typed" do - it "returns nil" do - stub_data = read_fixture("rentals_united/properties/not_supported_hotel_property.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property - - expect(result).to be_success - expect(result.value).to be_nil - end - end - - context "when property is boat-typed" do - it "returns nil" do - stub_data = read_fixture("rentals_united/properties/not_supported_boat_property.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property - - expect(result).to be_success - expect(result.value).to be_nil - end - end - - context "when property is camping-typed" do - it "returns nil" do - stub_data = read_fixture("rentals_united/properties/not_supported_camping_property.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property - - expect(result).to be_success - expect(result.value).to be_nil + expect(property.id).to eq("519688") end end @@ -351,28 +81,4 @@ expect(event[:message]).to eq("timeout") end end - - context "when mapping floors" do - it "sets floor to the property if it's usual floor value" do - stub_data = read_fixture("rentals_united/properties/property.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property - expect(result).to be_success - - property = result.value - expect(property.floor).to eq(3) - end - - it "sets floor -1 to the property if it's Basement encoded by -1000" do - stub_data = read_fixture("rentals_united/properties/basement_floor.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property - expect(result).to be_success - - property = result.value - expect(property.floor).to eq(-1) - end - end end diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb index e20b82886..3a331b857 100644 --- a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -51,76 +51,71 @@ describe "#fetch_property" do let(:property_id) { "588788" } - let(:location) { double(id: '1') } it "calls fetcher class to load property" do fetcher_class = RentalsUnited::Commands::PropertyFetcher expect_any_instance_of(fetcher_class).to(receive(:fetch_property)) - importer.fetch_property(property_id, location) + importer.fetch_property(property_id) end end describe "#fetch_properties_by_ids" do let(:property_ids) { ["222", "333"] } - let(:location) { double(id: '1') } it "calls #fetch_property for every property id" do property_ids.each do |id| expect_any_instance_of(described_class).to( - receive(:fetch_property).with(id, location) do - Result.new("success") - end + receive(:fetch_property).with(id) { Result.new("success") } ) end - result = importer.fetch_properties_by_ids(property_ids, location) + result = importer.fetch_properties_by_ids(property_ids) expect(result).to be_success end it "ignores nil results" do expect_any_instance_of(described_class).to( - receive(:fetch_property).with("222", location) do - Result.new("success") - end + receive(:fetch_property).with("222") { Result.new("success") } ) expect_any_instance_of(described_class).to( - receive(:fetch_property).with("333", location) do - Result.new(nil) - end + receive(:fetch_property).with("333") { Result.new(nil) } ) - result = importer.fetch_properties_by_ids(property_ids, location) + result = importer.fetch_properties_by_ids(property_ids) expect(result).to be_success expect(result.value.size).to eq(1) end it "returns error if fetching property_id fails" do expect_any_instance_of(described_class).to( - receive(:fetch_property).with("222", location) do - Result.error("fail") - end + receive(:fetch_property).with("222") { Result.error("fail") } ) - result = importer.fetch_properties_by_ids(property_ids, location) + result = importer.fetch_properties_by_ids(property_ids) expect(result).not_to be_success end it "returns error on fail even if previous fetchers returned success" do expect_any_instance_of(described_class).to( - receive(:fetch_property).with("222", location) do - Result.new("success") - end + receive(:fetch_property).with("222") { Result.new("success") } ) expect_any_instance_of(described_class).to( - receive(:fetch_property).with("333", location) do - Result.error("fail") - end + receive(:fetch_property).with("333") { Result.error("fail") } ) - result = importer.fetch_properties_by_ids(property_ids, location) + result = importer.fetch_properties_by_ids(property_ids) expect(result).not_to be_success end end + + describe "#fetch_owners" do + it "calls fetcher class to load owners" do + fetcher_class = RentalsUnited::Commands::OwnersFetcher + + expect_any_instance_of(fetcher_class).to(receive(:fetch_owners)) + importer.fetch_owners + end + end end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/owners_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/owners_spec.rb new file mode 100644 index 000000000..fa329be5a --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/owners_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Mappers::Owner do + let(:owner_hash) do + { + "FirstName" => "Foo", + "SurName" => "Bar", + "CompanyName" => "RU Test", + "Email" => "foobar@gmail.com", + "Phone" => "519461272", + "@OwnerID" => "419680" + } + end + + let(:safe_hash) { Concierge::SafeAccessHash.new(owner_hash) } + let(:subject) { described_class.new(safe_hash) } + + it "builds owner object" do + owner = subject.build_owner + expect(owner).to be_kind_of(RentalsUnited::Entities::Owner) + expect(owner.id).to eq("419680") + expect(owner.first_name).to eq("Foo") + expect(owner.last_name).to eq("Bar") + expect(owner.email).to eq("foobar@gmail.com") + expect(owner.phone).to eq("519461272") + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/property_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/property_spec.rb new file mode 100644 index 000000000..23c07664f --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/property_spec.rb @@ -0,0 +1,202 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Mappers::Property do + include Support::Fixtures + + let(:file_name) { "rentals_united/properties/property.xml" } + let(:property_hash) do + stub_data = read_fixture(file_name) + safe_hash = RentalsUnited::ResponseParser.new.to_hash(stub_data) + safe_hash.get("Pull_ListSpecProp_RS.Property") + end + + let(:subject) { described_class.new(property_hash) } + let(:property) { subject.build_property } + + context "when hash contains property data" do + it "builds property object" do + expect(property).to be_kind_of(RentalsUnited::Entities::Property) + end + + it "sets id to the property" do + expect(property.id).to eq("519688") + end + + it "sets title to the property" do + expect(property.title).to eq("Test property") + end + + it "sets property_type_id to the property" do + expect(property.property_type_id).to eq("35") + end + + it "sets max_guests to the property" do + expect(property.max_guests).to eq(2) + end + + it "sets bedroom_type_id to the property" do + expect(property.bedroom_type_id).to eq("4") + end + + it "sets surface to the property" do + expect(property.surface).to eq(39) + end + + it "sets address information to the property" do + expect(property.lat).to eq(55.0003426) + expect(property.lng).to eq(73.2965942999999) + expect(property.address).to eq("Test street address") + expect(property.postal_code).to eq("644119") + end + + it "sets en description to the property" do + expect(property.description).to eq("Test description") + end + + it "sets check_in_time to the property" do + expect(property.check_in_time).to eq("13:00-17:00") + end + + it "sets check_out_time to the property" do + expect(property.check_out_time).to eq("11:00") + end + + it "sets active flag to the property" do + expect(property.active?).to eq(true) + end + + it "sets archived flag to the property" do + expect(property.archived?).to eq(false) + end + + it "sets owner_id to the property" do + expect(property.owner_id).to eq("427698") + end + + context "when property is not active" do + let(:file_name) { "rentals_united/properties/not_active.xml" } + + it "returns not active property" do + expect(property.active?).to eq(false) + end + end + + context "when property is archived" do + let(:file_name) { "rentals_united/properties/archived.xml" } + + it "returnes archived property" do + expect(property.archived?).to eq(true) + end + end + end + + context "when multiple descriptions are available" do + let(:file_name) { "rentals_united/properties/property_with_multiple_descriptions.xml" } + + it "sets en description to the property" do + expect(property.description).to eq("Yet another one description") + end + end + + context "when no available descriptions" do + let(:file_name) { "rentals_united/properties/property_without_descriptions.xml" } + + it "sets description to nil" do + expect(property.description).to be_nil + end + end + + context "when mapping single image to property" do + let(:file_name) { "rentals_united/properties/property_with_one_image.xml" } + + let(:expected_images) do + { + "a0c68acc113db3b58376155c283dfd59" => { + url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg", + caption: 'Main image' + } + } + end + + it "adds array of with one image to the property" do + expect(property.images.size).to eq(1) + expect(property.images).to all(be_kind_of(Roomorama::Image)) + expect(property.images.map(&:identifier)).to eq(expected_images.keys) + + property.images.each do |image| + expect(image.url).to eq(expected_images[image.identifier][:url]) + expect(image.caption).to eq(expected_images[image.identifier][:caption]) + end + end + end + + context "when mapping multiple images to property" do + let(:expected_images) do + { + "62fc304eb20a25669b84d2ca2ea61308" => { + url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082398988145159.jpg", + caption: 'Interior' + }, + "a0c68acc113db3b58376155c283dfd59" => { + url: "https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg", + caption: 'Main image' + } + } + end + + it "adds array of images to the property" do + expect(property.images.size).to eq(2) + expect(property.images).to all(be_kind_of(Roomorama::Image)) + expect(property.images.map(&:identifier)).to eq(expected_images.keys) + + property.images.each do |image| + expect(image.url).to eq(expected_images[image.identifier][:url]) + expect(image.caption).to eq(expected_images[image.identifier][:caption]) + end + end + end + + context "when there is no images for property" do + let(:file_name) { "rentals_united/properties/property_without_images.xml" } + + it "returns an empty array for property images" do + expect(property.images).to eq([]) + end + end + + context "when there is existing amenities for property" do + let(:file_name) { "rentals_united/properties/property.xml" } + + it "returns an array with property amenities" do + expect(property.amenities).to eq( + ["7", "100", "180", "187", "227", "281", "368", "596", "689", "802", "803"] + ) + end + end + + context "when there is no amenities for property" do + let(:file_name) { "rentals_united/properties/property_without_amenities.xml" } + + it "returns an empty array for property amenities" do + expect(property.amenities).to eq([]) + end + end + + context "when mapping floors" do + context "when it's usual floor" do + let(:file_name) { "rentals_united/properties/property.xml" } + + it "sets floor to the property" do + expect(property.floor).to eq(3) + end + end + + context "when it's Basement encoded by -1000" do + let(:file_name) { "rentals_united/properties/basement_floor.xml" } + + it "sets floor -1 to the property" do + expect(property.floor).to eq(-1) + end + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb new file mode 100644 index 000000000..82847c43d --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb @@ -0,0 +1,284 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Mappers::RoomoramaProperty do + include Support::Fixtures + + let(:ru_property_hash) do + { + id: "519688", + title: "Test Property", + lat: 55.0003426, + lng: 73.2965942999999, + address: "Test street address", + postal_code: "644119", + max_guests: 2, + bedroom_type_id: "4", + property_type_id: "35", + active: true, + archived: false, + surface: 39, + owner_id: "378000", + check_in_time: "13:00-17:00", + check_out_time: "11:00", + floor: 5, + description: "Test Description", + images: [], + amenities: [] + } + end + + let(:ru_property) { RentalsUnited::Entities::Property.new(ru_property_hash) } + + let(:location) do + double( + id: '1505', + city: 'Paris', + neighborhood: 'Ile-de-France', + country: 'France', + currency: 'EUR' + ) + end + + let(:owner) do + double( + id: '478000', + first_name: 'John', + last_name: 'Doe', + email: 'john.doe@gmail.com', + phone: '3128329138' + ) + end + + let(:subject) { described_class.new(ru_property, location, owner) } + let(:result) { subject.build_roomorama_property } + let(:property) { result.value } + + it "builds property object" do + expect(property).to be_kind_of(Roomorama::Property) + end + + it "sets id to the property" do + expect(property.identifier).to eq(ru_property.id) + end + + it "sets title to the property" do + expect(property.title).to eq(ru_property.title) + end + + it "sets description to the property" do + expect(property.description).to eq(ru_property.description) + end + + it "sets type to the property" do + expect(property.type).to eq("house") + end + + it "sets subtype to the property" do + expect(property.subtype).to eq("villa") + end + + it "sets max_guests to the property" do + expect(property.max_guests).to eq(ru_property.max_guests) + end + + it "sets number_of_bedrooms to the property" do + expect(property.number_of_bedrooms).to eq(3) + end + + it "sets floor to the property" do + expect(property.floor).to eq(5) + end + + it "sets surface to the property" do + expect(property.surface).to eq(ru_property.surface) + end + + it "sets surface_unit to the property" do + expect(property.surface_unit).to eq("metric") + end + + it "sets address information to the property" do + expect(property.lat).to eq(55.0003426) + expect(property.lng).to eq(73.2965942999999) + expect(property.address).to eq("Test street address") + expect(property.city).to eq("Paris") + expect(property.neighborhood).to eq("Ile-de-France") + expect(property.postal_code).to eq("644119") + expect(property.country_code).to eq("FR") + end + + it "sets check_in_time to the property" do + expect(property.check_in_time).to eq("13:00-17:00") + end + + it "sets check_out_time to the property" do + expect(property.check_out_time).to eq("11:00") + end + + it "sets currency to the property" do + expect(property.currency).to eq("EUR") + end + + it "sets cancellation_policy to the property" do + expect(property.cancellation_policy).to eq("super_elite") + end + + it "sets default_to_available flag to false" do + expect(property.default_to_available).to eq(false) + end + + it "does not set multi-unit flag" do + expect(property.multi_unit).to be_nil + end + + it "sets instant_booking flag" do + expect(property.instant_booking?).to eq(true) + end + + it "sets owner name" do + expect(property.owner_name).to eq("John Doe") + end + + it "sets owner email" do + expect(property.owner_email).to eq("john.doe@gmail.com") + end + + it "sets owner phone number" do + expect(property.owner_phone_number).to eq("3128329138") + end + + context "when property is archived" do + it "returns error" do + ru_property_hash[:archived] = true + + expect(result).not_to be_success + expect(result.error.code).to eq(:attempt_to_build_archived_property) + end + end + + context "when property is not active" do + it "returns error" do + ru_property_hash[:active] = false + + expect(result).not_to be_success + expect(result.error.code).to eq(:attempt_to_build_not_active_property) + end + end + + context "when property is hotel-typed" do + it "returns error" do + ru_property_hash[:property_type_id] = 20 + + expect(result).not_to be_success + expect(result.error.code).to eq(:property_type_not_supported) + end + end + + context "when property is boat-typed" do + it "returns error" do + ru_property_hash[:property_type_id] = 64 + + expect(result).not_to be_success + expect(result.error.code).to eq(:property_type_not_supported) + end + end + + context "when property is camping-typed" do + it "returns error" do + ru_property_hash[:property_type_id] = 66 + + expect(result).not_to be_success + expect(result.error.code).to eq(:property_type_not_supported) + end + end + + context "when mapping amenities" do + let(:amenities) do + ["7", "100", "180", "187", "227", "281", "368", "596", "689", "802", "803"] + end + + it "adds amenities to property" do + ru_property_hash[:amenities] = amenities + + expect(property.amenities).to eq( + ["bed_linen_and_towels", "airconditioning", "pool", "wheelchairaccess", "elevator", "parking"] + ) + end + + it "sets smoking_allowed to false" do + amenities_dict = RentalsUnited::Dictionaries::Amenities + + expect_any_instance_of(amenities_dict) + .to(receive(:smoking_allowed?)) + .and_return(false) + + + expect(property.smoking_allowed).to eq(false) + end + + it "sets smoking_allowed to true" do + amenities_dict = RentalsUnited::Dictionaries::Amenities + + expect_any_instance_of(amenities_dict) + .to(receive(:smoking_allowed?)) + .and_return(true) + + expect(property.smoking_allowed).to eq(true) + end + + it "sets pets_allowed to false" do + amenities_dict = RentalsUnited::Dictionaries::Amenities + + expect_any_instance_of(amenities_dict) + .to(receive(:pets_allowed?)) + .and_return(false) + + expect(property.pets_allowed).to eq(false) + end + + it "sets pets_allowed to true" do + amenities_dict = RentalsUnited::Dictionaries::Amenities + + expect_any_instance_of(amenities_dict) + .to(receive(:pets_allowed?)) + .and_return(true) + + expect(property.pets_allowed).to eq(true) + end + end + + context "when there is no amenities" do + let(:amenities) { [] } + + it "keeps amenities empty" do + expect(property.amenities).to eq([]) + end + end + + context "when mapping images" do + let(:image) do + image = Roomorama::Image.new("1") + image.url = "http://url.com/123.jpg" + image.caption = "house" + image + end + + let(:images) do + [image] + end + + it "adds images to property" do + ru_property_hash[:images] = images + + expect(property.images).to eq(images) + end + end + + context "when there is no images" do + it "keeps amenities empty" do + ru_property_hash[:images] = [] + + expect(property.images).to eq([]) + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index ea9f73cf4..d77f4695c 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -48,6 +48,17 @@ end end + describe '#build_owners_fetch_payload' do + it 'embedds username and password to request' do + xml = builder.build_owners_fetch_payload + hash = to_hash(xml) + + authentication = hash.get("Pull_ListAllOwners_RQ.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + end + describe '#build_locations_fetch_payload' do it 'embedds username and password to request' do xml = builder.build_locations_fetch_payload diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index 8a138d482..63b49dcdc 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -83,39 +83,17 @@ end describe "when #fetch_location_currencies is working" do + let(:location_currencies) {{"1506" => "EUR", "1606" => "USD"}} before do expect_any_instance_of(RentalsUnited::Importer).to( receive(:fetch_location_currencies) ).and_return( - Result.new({"1506" => "EUR", "1606" => "USD"}) + Result.new(location_currencies) ) end - it "fails when there is no currency for location" do - result = worker.perform - expect(result).to be_nil - - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to find currency for location with id `1505`" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - end - - describe "when currency for location exists" do - before do - expect_any_instance_of(RentalsUnited::Importer).to( - receive(:fetch_location_currencies) - ).and_return( - Result.new({"1505" => "EUR", "1606" => "USD"}) - ) - end - - it "fails when fetching property ids for location returns an error" do - stub_data = read_fixture("rentals_united/property_ids/error_status.xml") + it "fails when fetching owners returns an error" do + stub_data = read_fixture("rentals_united/owners/error_status.xml") stub_call(:post, url) { [200, {}, stub_data] } result = worker.perform @@ -123,69 +101,145 @@ event = Concierge.context.events.last.to_h expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to fetch properties for location `1505`" - ) + expect(event[:message]).to eq("Failed to fetch owners") expect(event[:backtrace]).to be_kind_of(Array) expect(event[:backtrace].any?).to be true end - describe "when #fetch_property_ids is working" do + describe "when #fetch_owners is working" do + let(:owner) do + double( + id: '427698', + first_name: 'John', + last_name: 'Doe', + email: 'john.doe@gmail.com', + phone: '3128329138' + ) + end + before do expect_any_instance_of(RentalsUnited::Importer).to( - receive(:fetch_property_ids) + receive(:fetch_owners) ).and_return( - Result.new(["1234"]) + Result.new([owner]) ) end - it "fails when #fetch_properties_by_ids returns an error" do - allow_any_instance_of(RentalsUnited::Importer).to receive(:fetch_properties_by_ids) { Result.error('fail') } - + it "fails when there is no currency for location" do result = worker.perform expect(result).to be_nil event = Concierge.context.events.last.to_h expect(event[:label]).to eq("Synchronisation Failure") expect(event[:message]).to eq( - "Failed to fetch properties for ids `[\"1234\"]` in location `1505`" + "Failed to find currency for location with id `1505`" ) expect(event[:backtrace]).to be_kind_of(Array) expect(event[:backtrace].any?).to be true end - describe "when #fetch_properties_by_ids is working" do - before do - stub_data = read_fixture("rentals_united/properties/property.xml") - stub_call(:post, url) { [200, {}, stub_data] } - end - - it "calls synchronisation block for every property id" do - expected_property_ids = ["519688"] + describe "when currency for location exists" do + let(:location_currencies) {{"1505" => "EUR", "1606" => "USD"}} - expected_property_ids.each do |property_id| - expect(worker.synchronisation).to receive(:start).with(property_id) - end + it "fails when fetching property ids for location returns an error" do + stub_data = read_fixture("rentals_united/property_ids/error_status.xml") + stub_call(:post, url) { [200, {}, stub_data] } result = worker.perform - expect(result).to be_kind_of(SyncProcess) - expect(result.to_h[:successful]).to be true + expect(result).to be_nil + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq( + "Failed to fetch properties for location `1505`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true end - it "creates record in the database" do - allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.new('success') } + describe "when #fetch_property_ids is working" do + before do + expect_any_instance_of(RentalsUnited::Importer).to( + receive(:fetch_property_ids) + ).and_return( + Result.new(["1234"]) + ) + end + + it "fails when #fetch_properties_by_ids returns an error" do + allow_any_instance_of(RentalsUnited::Importer).to receive(:fetch_properties_by_ids) { Result.error('fail') } - expect { - worker.perform - }.to change { PropertyRepository.count }.by(1) - end + result = worker.perform + expect(result).to be_nil - it 'doesnt create property with unsuccessful publishing' do - allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.error('fail') } + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq( + "Failed to fetch properties for ids `[\"1234\"]` in location `1505`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end - expect { - worker.perform - }.to_not change { PropertyRepository.count } + describe "when #fetch_properties_by_ids is working" do + before do + stub_data = read_fixture("rentals_united/properties/property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + end + + describe "when there is no owner for property" do + let(:owner) do + double( + id: '550000', + file_name: 'John', + last_name: 'Doe', + empty: 'john.doe@gmail.com', + phone: '3128329138' + ) + end + + it "fails with owner error" do + result = worker.perform + expect(result).to be_nil + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq( + "Failed to find owner for property id `519688`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + it "calls synchronisation block for every property id" do + expected_property_ids = ["519688"] + + expected_property_ids.each do |property_id| + expect(worker.synchronisation).to receive(:start).with(property_id) + end + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + expect(result.to_h[:successful]).to be true + end + + it "creates record in the database" do + allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.new('success') } + + expect { + worker.perform + }.to change { PropertyRepository.count }.by(1) + end + + it 'doesnt create property with unsuccessful publishing' do + allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.error('fail') } + + expect { + worker.perform + }.to_not change { PropertyRepository.count } + end + end end end end From 4d1fa17d15af1a10fdd3c45d3955d1b293542419 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 09:50:33 +0600 Subject: [PATCH 059/118] RU: update documentation --- .../suppliers/rentals_united/mappers/roomorama_property.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb index b32862078..01ecf7aaa 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb @@ -26,6 +26,9 @@ def initialize(ru_property, location, owner) end # Builds a property + # + # Returns a +Result+ wrapping +Roomorama::Property+ object + # Returns a +Result+ with +Result::Error+ when operation fails def build_roomorama_property return archived_error if ru_property.archived? return not_active_error unless ru_property.active? From 244ae604ef526511c7b4dccda9a61c929f0e5cbd Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 10:15:11 +0600 Subject: [PATCH 060/118] RU: increase timeout --- lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb index 42553ccfa..207b0c5f5 100644 --- a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb @@ -80,7 +80,7 @@ def unrecognised_response_event(backtrace) private def http - @http_client ||= Concierge::HTTPClient.new(credentials.url) + @http_client ||= Concierge::HTTPClient.new(credentials.url, timeout: 600) end def headers From 1b087c1783c79c928e89e3b03072e82f2325cedd Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 10:15:28 +0600 Subject: [PATCH 061/118] RU: metadata worker new_context changes --- apps/workers/suppliers/rentals_united/metadata.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index 71013196c..5b0bab40b 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -16,17 +16,17 @@ def perform location_ids = result.value - result = synchronisation.new_context { fetch_locations(location_ids) } + result = fetch_locations(location_ids) return unless result.success? locations = result.value - result = synchronisation.new_context { fetch_location_currencies } + result = fetch_location_currencies return unless result.success? currencies = result.value - result = synchronisation.new_context { fetch_owners } + result = fetch_owners return unless result.success? owners = result.value @@ -40,7 +40,7 @@ def perform property_ids = result.value - result = synchronisation.new_context { fetch_properties_by_ids(property_ids) } + result = fetch_properties_by_ids(property_ids) if result.success? properties = result.value From 3cd45c805f7e65246c44d000a98887382590417f Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 10:25:56 +0600 Subject: [PATCH 062/118] RU: let worker continue when possible --- apps/workers/suppliers/rentals_united/metadata.rb | 6 +++--- .../suppliers/rentals_united/metadata_spec.rb | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index 5b0bab40b..b6e34f25b 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -60,18 +60,18 @@ def perform else message = "Failed to find owner for property id `#{property.id}`" announce_context_error(message, result) - return + next end end else message = "Failed to fetch properties for ids `#{property_ids}` in location `#{location.id}`" announce_context_error(message, result) - return + next end else message = "Failed to find currency for location with id `#{location.id}`" announce_context_error(message, result) - return + next end end diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index 63b49dcdc..a1b4cc440 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -125,9 +125,9 @@ ) end - it "fails when there is no currency for location" do + it "fails when there is no currency for location and continues worker process" do result = worker.perform - expect(result).to be_nil + expect(result).to be_kind_of(SyncProcess) event = Concierge.context.events.last.to_h expect(event[:label]).to eq("Synchronisation Failure") @@ -166,11 +166,11 @@ ) end - it "fails when #fetch_properties_by_ids returns an error" do + it "fails when #fetch_properties_by_ids returns an error and continues worker process" do allow_any_instance_of(RentalsUnited::Importer).to receive(:fetch_properties_by_ids) { Result.error('fail') } result = worker.perform - expect(result).to be_nil + expect(result).to be_kind_of(SyncProcess) event = Concierge.context.events.last.to_h expect(event[:label]).to eq("Synchronisation Failure") @@ -198,9 +198,9 @@ ) end - it "fails with owner error" do + it "fails with owner error and continues worker process" do result = worker.perform - expect(result).to be_nil + expect(result).to be_kind_of(SyncProcess) event = Concierge.context.events.last.to_h expect(event[:label]).to eq("Synchronisation Failure") From 93a61f3acd962a2956fb5488e462023656102ad0 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 12:54:29 +0600 Subject: [PATCH 063/118] RU: parse security deposits --- .../rentals_united/entities/property.rb | 46 ++++++----- .../rentals_united/mappers/property.rb | 48 ++++++----- .../mappers/roomorama_property.rb | 35 +++++++- .../properties/basement_floor.xml | 2 +- .../rentals_united/properties/property.xml | 2 +- .../property_with_multiple_descriptions.xml | 2 + .../properties/property_with_one_image.xml | 2 + .../properties/property_without_amenities.xml | 2 + .../property_without_descriptions.xml | 2 + .../properties/property_without_images.xml | 2 + .../rentals_united/mappers/property_spec.rb | 8 ++ .../mappers/roomorama_property_spec.rb | 79 ++++++++++++++----- 12 files changed, 166 insertions(+), 64 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/entities/property.rb b/lib/concierge/suppliers/rentals_united/entities/property.rb index f77fa1374..6b97ca919 100644 --- a/lib/concierge/suppliers/rentals_united/entities/property.rb +++ b/lib/concierge/suppliers/rentals_united/entities/property.rb @@ -7,33 +7,37 @@ class Property attr_accessor :id, :title, :description, :lat, :lng, :address, :postal_code, :check_in_time, :check_out_time, :max_guests, :surface, :bedroom_type_id, :property_type_id, :floor, - :images, :amenities, :owner_id + :images, :amenities, :owner_id, :security_deposit_type, + :security_deposit_amount attr_writer :active, :archived def initialize(id:, title:, description:, lat:, lng:, address:, postal_code:, check_in_time:, check_out_time:, max_guests:, surface:, bedroom_type_id:, property_type_id:, - floor:, images:, amenities:, active:, archived:, owner_id:) - @id = id - @title = title - @description = description - @lat = lat - @lng = lng - @address = address - @postal_code = postal_code - @check_in_time = check_in_time - @check_out_time = check_out_time - @max_guests = max_guests - @surface = surface - @bedroom_type_id = bedroom_type_id - @property_type_id = property_type_id - @floor = floor - @owner_id = owner_id - @active = active - @archived = archived - @images = images - @amenities = amenities + floor:, images:, amenities:, active:, archived:, owner_id:, + security_deposit_type:, security_deposit_amount:) + @id = id + @title = title + @description = description + @lat = lat + @lng = lng + @address = address + @postal_code = postal_code + @check_in_time = check_in_time + @check_out_time = check_out_time + @max_guests = max_guests + @surface = surface + @bedroom_type_id = bedroom_type_id + @property_type_id = property_type_id + @owner_id = owner_id + @security_deposit_type = security_deposit_type + @security_deposit_amount = security_deposit_amount + @floor = floor + @active = active + @archived = archived + @images = images + @amenities = amenities end def active? diff --git a/lib/concierge/suppliers/rentals_united/mappers/property.rb b/lib/concierge/suppliers/rentals_united/mappers/property.rb index 1ab8b79fa..68262e78e 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/property.rb @@ -24,25 +24,27 @@ def initialize(property_hash) # Returns [RentalsUnited::Entities::Property] def build_property property = Entities::Property.new( - id: property_hash.get("ID"), - title: property_hash.get("Name"), - lat: property_hash.get("Coordinates.Latitude").to_f, - lng: property_hash.get("Coordinates.Longitude").to_f, - address: property_hash.get("Street"), - postal_code: property_hash.get("ZipCode").to_s.strip, - max_guests: property_hash.get("CanSleepMax").to_i, - bedroom_type_id: property_hash.get("PropertyTypeID"), - property_type_id: property_hash.get("ObjectTypeID"), - active: property_hash.get("IsActive"), - archived: property_hash.get("IsArchived"), - surface: property_hash.get("Space").to_i, - owner_id: property_hash.get("OwnerID"), - check_in_time: check_in_time, - check_out_time: check_out_time, - floor: floor, - description: en_description(property_hash), - images: build_images, - amenities: build_amenities + id: property_hash.get("ID"), + title: property_hash.get("Name"), + lat: property_hash.get("Coordinates.Latitude").to_f, + lng: property_hash.get("Coordinates.Longitude").to_f, + address: property_hash.get("Street"), + postal_code: property_hash.get("ZipCode").to_s.strip, + max_guests: property_hash.get("CanSleepMax").to_i, + bedroom_type_id: property_hash.get("PropertyTypeID"), + property_type_id: property_hash.get("ObjectTypeID"), + active: property_hash.get("IsActive"), + archived: property_hash.get("IsArchived"), + surface: property_hash.get("Space").to_i, + owner_id: property_hash.get("OwnerID"), + security_deposit_amount: property_hash.get("SecurityDeposit").to_f, + security_deposit_type: security_deposit_type, + check_in_time: check_in_time, + check_out_time: check_out_time, + floor: floor, + description: en_description(property_hash), + images: build_images, + amenities: build_amenities ) property @@ -81,6 +83,14 @@ def en_description(hash) en_description["Text"] if en_description end + def security_deposit_type + security_deposit = property_hash.get("SecurityDeposit") + + if security_deposit + security_deposit.attributes["DepositTypeID"] + end + end + def check_in_time from = property_hash.get("CheckInOut.CheckInFrom") to = property_hash.get("CheckInOut.CheckInTo") diff --git a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb index 01ecf7aaa..c13b2bdb6 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb @@ -12,6 +12,15 @@ class RoomoramaProperty MINIMUM_STAY = 1 SURFACE_UNIT = "metric" + # List of supported by Roomorama security deposit types + NO_DEPOSIT_ID = "1" + FLAT_AMOUNT_PER_STAY_ID = "5" + SUPPORTED_SECURITY_DEPOSIT_TYPES = [ + NO_DEPOSIT_ID, + FLAT_AMOUNT_PER_STAY_ID + ] + SECURITY_DEPOSIT_PAYMENT_TYPE = 'unknown' + # Initialize +RentalsUnited::Mappers::Property+ # # Arguments: @@ -30,9 +39,10 @@ def initialize(ru_property, location, owner) # Returns a +Result+ wrapping +Roomorama::Property+ object # Returns a +Result+ with +Result::Error+ when operation fails def build_roomorama_property - return archived_error if ru_property.archived? - return not_active_error unless ru_property.active? - return property_type_error unless supported_property_type? + return archived_error if ru_property.archived? + return not_active_error unless ru_property.active? + return property_type_error unless supported_property_type? + return security_deposit_error unless supported_security_deposit? property = Roomorama::Property.new(ru_property.id) property.title = ru_property.title @@ -69,6 +79,7 @@ def build_roomorama_property property.instant_booking! set_images!(property) + set_security_deposit!(property) Result.new(property) end @@ -78,6 +89,14 @@ def set_images!(property) ru_property.images.each { |image| property.add_image(image) } end + def set_security_deposit!(property) + if ru_property.security_deposit_type == FLAT_AMOUNT_PER_STAY_ID + property.security_deposit_amount = ru_property.security_deposit_amount + property.security_deposit_currency_code = property.currency + property.security_deposit_type = SECURITY_DEPOSIT_PAYMENT_TYPE + end + end + def full_name(owner) [owner.first_name, owner.last_name].join(" ").strip end @@ -96,6 +115,12 @@ def supported_property_type? !!property_type end + def supported_security_deposit? + SUPPORTED_SECURITY_DEPOSIT_TYPES.include?( + ru_property.security_deposit_type + ) + end + def property_type @property_type ||= Dictionaries::PropertyTypes.find( ru_property.property_type_id @@ -114,6 +139,10 @@ def not_active_error Result.error(:attempt_to_build_not_active_property) end + def security_deposit_error + Result.error(:security_deposit_not_supported) + end + def amenities_dictionary @amenities_dictionary ||= Dictionaries::Amenities.new( ru_property.amenities diff --git a/spec/fixtures/rentals_united/properties/basement_floor.xml b/spec/fixtures/rentals_united/properties/basement_floor.xml index df31e0a1d..ec2fccce8 100644 --- a/spec/fixtures/rentals_united/properties/basement_floor.xml +++ b/spec/fixtures/rentals_united/properties/basement_floor.xml @@ -43,7 +43,7 @@ 99.6000 - 5.50 + 5.50 diff --git a/spec/fixtures/rentals_united/properties/property.xml b/spec/fixtures/rentals_united/properties/property.xml index 351aaf5a7..6a291cb2b 100644 --- a/spec/fixtures/rentals_united/properties/property.xml +++ b/spec/fixtures/rentals_united/properties/property.xml @@ -43,7 +43,7 @@ 99.6000 - 5.50 + 5.50 diff --git a/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml b/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml index 2ca00fd2e..0b16f5d0f 100644 --- a/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml +++ b/spec/fixtures/rentals_united/properties/property_with_multiple_descriptions.xml @@ -5,6 +5,8 @@ true false 35 + 99.6000 + 5.50 diff --git a/spec/fixtures/rentals_united/properties/property_with_one_image.xml b/spec/fixtures/rentals_united/properties/property_with_one_image.xml index d3e2ea2f8..682465c85 100644 --- a/spec/fixtures/rentals_united/properties/property_with_one_image.xml +++ b/spec/fixtures/rentals_united/properties/property_with_one_image.xml @@ -6,6 +6,8 @@ true false 35 + 99.6000 + 5.50 https://dwe6atvmvow8k.cloudfront.net/ru/427698/519688/636082399089701851.jpg diff --git a/spec/fixtures/rentals_united/properties/property_without_amenities.xml b/spec/fixtures/rentals_united/properties/property_without_amenities.xml index a030892da..f41fac1b7 100644 --- a/spec/fixtures/rentals_united/properties/property_without_amenities.xml +++ b/spec/fixtures/rentals_united/properties/property_without_amenities.xml @@ -6,5 +6,7 @@ true false 35 + 99.6000 + 5.50 diff --git a/spec/fixtures/rentals_united/properties/property_without_descriptions.xml b/spec/fixtures/rentals_united/properties/property_without_descriptions.xml index a030892da..f41fac1b7 100644 --- a/spec/fixtures/rentals_united/properties/property_without_descriptions.xml +++ b/spec/fixtures/rentals_united/properties/property_without_descriptions.xml @@ -6,5 +6,7 @@ true false 35 + 99.6000 + 5.50 diff --git a/spec/fixtures/rentals_united/properties/property_without_images.xml b/spec/fixtures/rentals_united/properties/property_without_images.xml index a030892da..f41fac1b7 100644 --- a/spec/fixtures/rentals_united/properties/property_without_images.xml +++ b/spec/fixtures/rentals_united/properties/property_without_images.xml @@ -6,5 +6,7 @@ true false 35 + 99.6000 + 5.50 diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/property_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/property_spec.rb index 23c07664f..9a9b21e90 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/property_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/property_spec.rb @@ -73,6 +73,14 @@ expect(property.owner_id).to eq("427698") end + it "sets security_deposit_type to the property" do + expect(property.security_deposit_type).to eq("5") + end + + it "sets security_deposit_amount to the property" do + expect(property.security_deposit_amount).to eq(5.50) + end + context "when property is not active" do let(:file_name) { "rentals_united/properties/not_active.xml" } diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb index 82847c43d..314d52437 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb @@ -5,25 +5,27 @@ let(:ru_property_hash) do { - id: "519688", - title: "Test Property", - lat: 55.0003426, - lng: 73.2965942999999, - address: "Test street address", - postal_code: "644119", - max_guests: 2, - bedroom_type_id: "4", - property_type_id: "35", - active: true, - archived: false, - surface: 39, - owner_id: "378000", - check_in_time: "13:00-17:00", - check_out_time: "11:00", - floor: 5, - description: "Test Description", - images: [], - amenities: [] + id: "519688", + title: "Test Property", + lat: 55.0003426, + lng: 73.2965942999999, + address: "Test street address", + postal_code: "644119", + max_guests: 2, + bedroom_type_id: "4", + property_type_id: "35", + active: true, + archived: false, + surface: 39, + owner_id: "378000", + security_deposit_type: "5", + security_deposit_amount: 5.50, + check_in_time: "13:00-17:00", + check_out_time: "11:00", + floor: 5, + description: "Test Description", + images: [], + amenities: [] } end @@ -147,6 +149,45 @@ expect(property.owner_phone_number).to eq("3128329138") end + it "sets security_deposit_amount" do + expect(property.security_deposit_amount).to eq(5.5) + end + + it "sets security_deposit_currency_code" do + expect(property.security_deposit_currency_code).to eq("EUR") + end + + it "sets security_deposit_currency_code" do + expect(property.security_deposit_type).to eq("unknown") + end + + context "when property has no security_deposit" do + it "sets security_deposit_amount to nil" do + ru_property_hash[:security_deposit_type] = "1" + expect(property.security_deposit_amount).to be_nil + end + + it "sets security_deposit_currency_code to nil" do + ru_property_hash[:security_deposit_type] = "1" + expect(property.security_deposit_currency_code).to be_nil + end + + it "sets security_deposit_type to nil" do + ru_property_hash[:security_deposit_type] = "1" + expect(property.security_deposit_type).to be_nil + end + end + + context "when property has not supported security_deposit" do + ["2", "3", "4"].each do |type_id| + it "returns security_deposit_error" do + ru_property_hash[:security_deposit_type] = type_id + expect(result).not_to be_success + expect(result.error.code).to eq(:security_deposit_not_supported) + end + end + end + context "when property is archived" do it "returns error" do ru_property_hash[:archived] = true From 1106c5722e3435626cd19d3293e8fde2492f12fe Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 15:29:11 +0600 Subject: [PATCH 064/118] RU: skip errors --- .../suppliers/rentals_united/metadata.rb | 37 +++++++++++++++---- .../suppliers/rentals_united/metadata_spec.rb | 14 +++++++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index b6e34f25b..bba95850f 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -5,6 +5,14 @@ module Workers::Suppliers::RentalsUnited class Metadata attr_reader :synchronisation, :host + # Prevent from publishing results containing error codes below: + IGNORABLE_ERROR_CODES = [ + :attempt_to_build_archived_property, + :attempt_to_build_not_active_property, + :security_deposit_not_supported, + :property_type_not_supported + ] + def initialize(host) @host = host @synchronisation = Workers::PropertySynchronisation.new(host) @@ -49,14 +57,10 @@ def perform owner = find_owner(owners, property.owner_id) if owner - synchronisation.start(property.id) do - mapper = ::RentalsUnited::Mappers::RoomoramaProperty.new( - property, - location, - owner - ) - mapper.build_roomorama_property - end + result = build_roomorama_property(property, location, owner) + next if skip?(result, property) + + synchronisation.start(property.id) { result } else message = "Failed to find owner for property id `#{property.id}`" announce_context_error(message, result) @@ -94,6 +98,15 @@ def find_owner(owners, owner_id) owners.find { |o| o.id == owner_id } end + def build_roomorama_property(property, location, owner) + mapper = ::RentalsUnited::Mappers::RoomoramaProperty.new( + property, + location, + owner + ) + mapper.build_roomorama_property + end + def fetch_location_ids announce_error("Failed to fetch location ids") do importer.fetch_location_ids @@ -130,6 +143,14 @@ def fetch_properties_by_ids(property_ids) end end + def skip?(result, property) + if !result.success? && IGNORABLE_ERROR_CODES.include?(result.error.code) + synchronisation.skip_property(property.id, result.error.code) + return true + end + return false + end + def announce_error(message) yield.tap do |result| announce_context_error(message, result) unless result.success? diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index a1b4cc440..741e55fdd 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -232,6 +232,20 @@ }.to change { PropertyRepository.count }.by(1) end + described_class::IGNORABLE_ERROR_CODES.each do |code| + it "skips property from publishing when there was #{code} error" do + allow_any_instance_of(RentalsUnited::Mappers::RoomoramaProperty) + .to receive(:build_roomorama_property) { Result.error(code) } + + expect { + sync_process = worker.perform + expect(sync_process.stats.get("properties_skipped")).to eq( + [{ "reason" => code, "ids" => ["519688"] }] + ) + }.to change { PropertyRepository.count }.by(0) + end + end + it 'doesnt create property with unsuccessful publishing' do allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.error('fail') } From 127f423cef78945398fcaed547c589ed3b0cc88f Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 7 Sep 2016 12:08:10 +0600 Subject: [PATCH 065/118] RU: calendar sync --- .../rentals_united/availabilities.rb | 84 ++++++++++++ .../commands/availabilities_fetcher.rb | 75 +++++++++++ .../rentals_united/commands/rates_fetcher.rb | 73 ++++++++++ .../rentals_united/entities/availability.rb | 17 +++ .../suppliers/rentals_united/entities/rate.rb | 20 +++ .../suppliers/rentals_united/importer.rb | 18 +++ .../rentals_united/mappers/availability.rb | 23 ++++ .../rentals_united/mappers/calendar.rb | 54 ++++++++ .../suppliers/rentals_united/mappers/rate.rb | 35 +++++ .../rentals_united/payload_builder.rb | 20 +++ .../templates/availabilities_fetch.xml.erb | 9 ++ .../templates/rates_fetch.xml.erb | 9 ++ .../availabilities/not_found.xml | 3 + .../rentals_united/availabilities/success.xml | 125 ++++++++++++++++++ .../rentals_united/rates/no_seasons.xml | 4 + .../rentals_united/rates/not_found.xml | 3 + .../fixtures/rentals_united/rates/success.xml | 13 ++ .../commands/availabilities_fetcher_spec.rb | 78 +++++++++++ .../commands/rates_fetcher_spec.rb | 85 ++++++++++++ .../rentals_united/entities/rate_spec.rb | 42 ++++++ .../suppliers/rentals_united/importer_spec.rb | 11 ++ .../mappers/availability_spec.rb | 26 ++++ .../rentals_united/mappers/calendar_spec.rb | 67 ++++++++++ .../rentals_united/mappers/rate_spec.rb | 26 ++++ .../rentals_united/payload_builder_spec.rb | 102 ++++++++++++++ 25 files changed, 1022 insertions(+) create mode 100644 apps/workers/suppliers/rentals_united/availabilities.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/availability.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/rate.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/availability.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/calendar.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/rate.rb create mode 100644 lib/concierge/suppliers/rentals_united/templates/availabilities_fetch.xml.erb create mode 100644 lib/concierge/suppliers/rentals_united/templates/rates_fetch.xml.erb create mode 100644 spec/fixtures/rentals_united/availabilities/not_found.xml create mode 100644 spec/fixtures/rentals_united/availabilities/success.xml create mode 100644 spec/fixtures/rentals_united/rates/no_seasons.xml create mode 100644 spec/fixtures/rentals_united/rates/not_found.xml create mode 100644 spec/fixtures/rentals_united/rates/success.xml create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/rates_fetcher_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/entities/rate_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/availability_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/rate_spec.rb diff --git a/apps/workers/suppliers/rentals_united/availabilities.rb b/apps/workers/suppliers/rentals_united/availabilities.rb new file mode 100644 index 000000000..aecb738ed --- /dev/null +++ b/apps/workers/suppliers/rentals_united/availabilities.rb @@ -0,0 +1,84 @@ +module Workers::Suppliers::RentalsUnited + # +Workers::Suppliers::RentalsUnited::Calendar+ + # + # Performs properties availabilities synchronisation with supplier + class Availabilities + attr_reader :synchronisation, :host + + def initialize(host) + @host = host + @synchronisation = Workers::CalendarSynchronisation.new(host) + end + + def perform + identifiers = all_identifiers + + identifiers.each do |property_id| + synchronisation.start(property_id) do + result = fetch_rates(property_id) + next result unless result.success? + rates = result.value + + result = fetch_availabilities(property_id) + next result unless result.success? + availabilities = result.value + + mapper = ::RentalsUnited::Mappers::Calendar.new( + property_id, + rates, + availabilities + ) + Result.new(mapper.build_calendar) + end + end + synchronisation.finish! + end + + private + + def report_error(message) + yield.tap do |result| + augment_context_error(message) unless result.success? + end + end + + def fetch_rates(property_id) + report_error("Failed to fetch rates for property `#{property_id}`") do + importer.fetch_rates(property_id) + end + end + + def fetch_availabilities(property_id) + report_error("Failed to fetch availabilities for property `#{property_id}`") do + importer.fetch_availabilities(property_id) + end + end + + def all_identifiers + PropertyRepository.from_host(host).only(:identifier).map(&:identifier) + end + + def importer + @importer ||= ::RentalsUnited::Importer.new(credentials) + end + + def credentials + Concierge::Credentials.for(RentalsUnited::Client::SUPPLIER_NAME) + end + + def augment_context_error(message) + message = { + label: 'Synchronisation Failure', + message: message, + backtrace: caller + } + context = Concierge::Context::Message.new(message) + Concierge.context.augment(context) + end + end +end + +Concierge::Announcer.on('availabilities.RentalsUnited') do |host, args| + Workers::Suppliers::RentalsUnited::Availabilities.new(host).perform + Result.new({}) +end diff --git a/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher.rb new file mode 100644 index 000000000..37cf52a18 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher.rb @@ -0,0 +1,75 @@ +require_relative 'base_fetcher' + +module RentalsUnited + module Commands + # +RentalsUnited::Commands::AvailabilitiesFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # property availabilities from RentalsUnited + class AvailabilitiesFetcher < BaseFetcher + attr_reader :property_id + + ROOT_TAG = "Pull_ListPropertyAvailabilityCalendar_RS" + YEARS_COUNT_TO_FETCH = 1 + + # Initialize +AvailabilitiesFetcher+ command. + # + # Arguments + # + # * +credentials+ + # * +property_id+ [String] + # + # Usage: + # + # RentalsUnited::Commands::AvailabilitiesFetcher.new( + # credentials, + # property_id + # ) + def initialize(credentials, property_id) + super(credentials) + + @property_id = property_id + end + + def fetch_availabilities + payload = payload_builder.build_availabilities_fetch_payload( + property_id, + date_from, + date_to + ) + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_availabilities(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def date_from + Time.now.strftime("%Y-%m-%d") + end + + def date_to + current = Time.now + + year = current.year + YEARS_COUNT_TO_FETCH + date = Time.new(year, current.month, current.day) + date.strftime("%Y-%m-%d") + end + + def build_availabilities(hash) + days = Array(hash.get("#{ROOT_TAG}.PropertyCalendar.CalDay")) + days.map do |day| + mapper = Mappers::Availability.new(day) + mapper.build_availability + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb new file mode 100644 index 000000000..d623a679d --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb @@ -0,0 +1,73 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::RatesFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # property rates from RentalsUnited + class RatesFetcher < BaseFetcher + attr_reader :property_id + + ROOT_TAG = "Pull_ListPropertyPrices_RS" + YEARS_COUNT_TO_FETCH = 1 + + # Initialize +RatesFetcher+ command. + # + # Arguments + # + # * +credentials+ + # * +property_id+ [String] + # + # Usage: + # + # RentalsUnited::Commands::RatesFetcher.new( + # credentials, + # property_id + # ) + def initialize(credentials, property_id) + super(credentials) + + @property_id = property_id + end + + def fetch_rates + payload = payload_builder.build_rates_fetch_payload( + property_id, + date_from, + date_to + ) + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_rates(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def date_from + Time.now.strftime("%Y-%m-%d") + end + + def date_to + current = Time.now + + year = current.year + YEARS_COUNT_TO_FETCH + date = Time.new(year, current.month, current.day) + date.strftime("%Y-%m-%d") + end + + def build_rates(hash) + seasons = Array(hash.get("#{ROOT_TAG}.Prices.Season")) + seasons.map do |season| + mapper = Mappers::Rate.new(season) + mapper.build_rate + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/entities/availability.rb b/lib/concierge/suppliers/rentals_united/entities/availability.rb new file mode 100644 index 000000000..6cd46f316 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/availability.rb @@ -0,0 +1,17 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::Availability+ + # + # This entity represents an availability type object. + class Availability + attr_accessor :date, :available, :minimum_stay, :changeover + + def initialize(date:, available:, minimum_stay:, changeover:) + @date = date + @available = available + @minimum_stay = minimum_stay + @changeover = changeover + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/entities/rate.rb b/lib/concierge/suppliers/rentals_united/entities/rate.rb new file mode 100644 index 000000000..e33e07880 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/rate.rb @@ -0,0 +1,20 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::Rate+ + # + # This entity represents an rate object. + class Rate + attr_accessor :date_from, :date_to, :price + + def initialize(date_from:, date_to:, price:) + @date_from = date_from + @date_to = date_to + @price = price + end + + def has_price_for_date?(date) + (date_from..date_to).include?(date) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb index 586430521..f3d97da31 100644 --- a/lib/concierge/suppliers/rentals_united/importer.rb +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -97,5 +97,23 @@ def fetch_owners fetcher = Commands::OwnersFetcher.new(credentials) fetcher.fetch_owners end + + # Retrieves availabilities for property by its id. + # + # Returns [Array] array with availabilities + def fetch_availabilities(property_id) + fetcher = Commands::AvailabilitiesFetcher.new( + credentials, + property_id + ) + + fetcher.fetch_availabilities + end + + # Retrieves rates for property by its id. + def fetch_rates(property_id) + fetcher = Commands::RatesFetcher.new(credentials, property_id) + fetcher.fetch_rates + end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/availability.rb b/lib/concierge/suppliers/rentals_united/mappers/availability.rb new file mode 100644 index 000000000..bb03db2dd --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/availability.rb @@ -0,0 +1,23 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Availability+ + # + # This class is responsible for building an availability object. + class Availability + attr_reader :hash + + def initialize(hash) + @hash = hash + end + + def build_availability + Entities::Availability.new( + date: Date.parse(hash["@Date"]), + available: hash["IsBlocked"] == false, + minimum_stay: hash["MinStay"].to_i, + changeover: hash["Changeover"].to_i + ) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb new file mode 100644 index 000000000..030aff0c9 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb @@ -0,0 +1,54 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Calendar+ + # + # This class is responsible for building a calendar for property. + class Calendar + attr_reader :property_id, :rates, :availabilities + + # Initialize +RentalsUnited::Mappers::Calendar+ + # + # Arguments + # + # * +propertyid+ [String] id of property + # * +rates+ [Array] rates + # * +availabilities+ [Array] availabilities + def initialize(property_id, rates, availabilities) + @property_id = property_id + @rates = rates + @availabilities = availabilities + end + + # Builds calendar. + # + # Returns [Roomorama::Calendar] property calendar object + def build_calendar + calendar = Roomorama::Calendar.new(property_id) + + entries.each { |entry| calendar.add(entry) } + + calendar + end + + private + def entries + availabilities.map do |availability| + Roomorama::Calendar::Entry.new( + date: availability.date.to_s, + available: availability.available, + nightly_rate: rate_by_date(availability.date), + minimum_stay: availability.minimum_stay + ) + end + end + + def rate_by_date(date) + rates.each do |rate| + return rate.price if rate.has_price_for_date?(date) + end + + return nil + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/rate.rb b/lib/concierge/suppliers/rentals_united/mappers/rate.rb new file mode 100644 index 000000000..73be7ceec --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/rate.rb @@ -0,0 +1,35 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Rate+ + # + # This class is responsible for building a rate object. + class Rate + attr_reader :hash + + # Initialize +RentalsUnited::Mappers::Rate+. + # + # Arguments + # * +hash+ rate object hash + # + # Usage + # + # RentalsUnited::Mappers::Rate.new({ + # "Price"=>"200.0000", + # "Extra"=>"10.0000", + # "@DateFrom"=>"2016-09-07", + # "@DateTo"=>"2016-09-30" + # }) + def initialize(hash) + @hash = hash + end + + def build_rate + Entities::Rate.new( + date_from: Date.parse(hash["@DateFrom"]), + date_to: Date.parse(hash["@DateTo"]), + price: hash["Price"] + ) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index 587a3ccb7..e52f98c23 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -49,6 +49,26 @@ def build_owners_fetch_payload render(:owners_fetch, template_locals) end + def build_availabilities_fetch_payload(property_id, date_from, date_to) + template_locals = { + credentials: credentials, + property_id: property_id, + date_from: date_from, + date_to: date_to + } + render(:availabilities_fetch, template_locals) + end + + def build_rates_fetch_payload(property_id, date_from, date_to) + template_locals = { + credentials: credentials, + property_id: property_id, + date_from: date_from, + date_to: date_to + } + render(:rates_fetch, template_locals) + end + private def render(template_name, local_vars) path = Hanami.root.join(TEMPLATES_PATH, "#{template_name}.xml.erb") diff --git a/lib/concierge/suppliers/rentals_united/templates/availabilities_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/availabilities_fetch.xml.erb new file mode 100644 index 000000000..0c00b7c35 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/availabilities_fetch.xml.erb @@ -0,0 +1,9 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + <%= property_id %> + <%= date_from %> + <%= date_to %> + diff --git a/lib/concierge/suppliers/rentals_united/templates/rates_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/rates_fetch.xml.erb new file mode 100644 index 000000000..acd987f6f --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/rates_fetch.xml.erb @@ -0,0 +1,9 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + <%= property_id %> + <%= date_from %> + <%= date_to %> + diff --git a/spec/fixtures/rentals_united/availabilities/not_found.xml b/spec/fixtures/rentals_united/availabilities/not_found.xml new file mode 100644 index 000000000..323837b71 --- /dev/null +++ b/spec/fixtures/rentals_united/availabilities/not_found.xml @@ -0,0 +1,3 @@ + + Property does not exist. + diff --git a/spec/fixtures/rentals_united/availabilities/success.xml b/spec/fixtures/rentals_united/availabilities/success.xml new file mode 100644 index 000000000..31974d28b --- /dev/null +++ b/spec/fixtures/rentals_united/availabilities/success.xml @@ -0,0 +1,125 @@ + + Success + + + true + 1 + 4 + + + false + 2 + 4 + + + false + 1 + 4 + + + true + 1 + 4 + + + false + 1 + 4 + + + false + 1 + 4 + + + false + 1 + 4 + + + true + 1 + 4 + + + true + 1 + 4 + + + false + 1 + 4 + + + false + 1 + 4 + + + true + 1 + 4 + + + false + 1 + 4 + + + false + 1 + 4 + + + false + 1 + 4 + + + false + 1 + 4 + + + true + 1 + 4 + + + true + 1 + 4 + + + true + 1 + 4 + + + true + 1 + 4 + + + true + 1 + 4 + + + true + 1 + 4 + + + false + 1 + 4 + + + true + 1 + 4 + + + diff --git a/spec/fixtures/rentals_united/rates/no_seasons.xml b/spec/fixtures/rentals_united/rates/no_seasons.xml new file mode 100644 index 000000000..698e09bcb --- /dev/null +++ b/spec/fixtures/rentals_united/rates/no_seasons.xml @@ -0,0 +1,4 @@ + + Success + + diff --git a/spec/fixtures/rentals_united/rates/not_found.xml b/spec/fixtures/rentals_united/rates/not_found.xml new file mode 100644 index 000000000..635ee2c76 --- /dev/null +++ b/spec/fixtures/rentals_united/rates/not_found.xml @@ -0,0 +1,3 @@ + + Property does not exist. + diff --git a/spec/fixtures/rentals_united/rates/success.xml b/spec/fixtures/rentals_united/rates/success.xml new file mode 100644 index 000000000..a2f717115 --- /dev/null +++ b/spec/fixtures/rentals_united/rates/success.xml @@ -0,0 +1,13 @@ + + Success + + + 200.0000 + 10.0000 + + + 170.0000 + 5.0000 + + + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher_spec.rb new file mode 100644 index 000000000..8a456a798 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher_spec.rb @@ -0,0 +1,78 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::AvailabilitiesFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:property_id) { "1234" } + let(:subject) { described_class.new(credentials, property_id) } + let(:url) { credentials.url } + + it "returns an error if property does not exist" do + stub_data = read_fixture("rentals_united/availabilities/not_found.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_availabilities + expect(result).not_to be_success + expect(result.error.code).to eq("56") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `56`, and description `Property does not exist.`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + context "when response contains availability data" do + let(:file_name) { "rentals_united/availabilities/success.xml" } + + before do + stub_data = read_fixture(file_name) + stub_call(:post, url) { [200, {}, stub_data] } + end + + it "returns availabilities" do + result = subject.fetch_availabilities + expect(result).to be_success + expect(result.value.size).to eq(24) + expect(result.value).to all( + be_kind_of(RentalsUnited::Entities::Availability) + ) + end + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_availabilities + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.fetch_availabilities + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/rates_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/rates_fetcher_spec.rb new file mode 100644 index 000000000..e59871948 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/rates_fetcher_spec.rb @@ -0,0 +1,85 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::RatesFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:property_id) { "1234" } + let(:subject) { described_class.new(credentials, property_id) } + let(:url) { credentials.url } + + it "returns an error if property does not exist" do + stub_data = read_fixture("rentals_united/rates/not_found.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_rates + expect(result).not_to be_success + expect(result.error.code).to eq("56") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `56`, and description `Property does not exist.`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + it "returns an empty array if property have no rates seasons" do + stub_data = read_fixture("rentals_united/rates/no_seasons.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_rates + expect(result).to be_success + expect(result.value).to eq([]) + end + + context "when response contains rates data" do + let(:file_name) { "rentals_united/rates/success.xml" } + + before do + stub_data = read_fixture(file_name) + stub_call(:post, url) { [200, {}, stub_data] } + end + + it "returns rates" do + result = subject.fetch_rates + expect(result).to be_success + expect(result.value.size).to eq(2) + expect(result.value).to all(be_kind_of(RentalsUnited::Entities::Rate)) + end + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.fetch_rates + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.fetch_rates + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/entities/rate_spec.rb b/spec/lib/concierge/suppliers/rentals_united/entities/rate_spec.rb new file mode 100644 index 000000000..a4ab8560f --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/entities/rate_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Entities::Rate do + let(:attributes) do + { + date_from: Date.parse("2016-09-01"), + date_to: Date.parse("2016-09-15"), + price: "200.00" + } + end + + let(:rate) { Entities::Rate.new(attributes) } + + describe "has_price_for_date?" do + it "returns true when given date is between from and to dates" do + date = Date.parse("2016-09-04") + expect(rate.has_price_for_date?(date)).to eq(true) + end + + it "returns true when given date matches to date_from" do + date = Date.parse("2016-09-01") + expect(rate.has_price_for_date?(date)).to eq(true) + end + + it "returns true when given date matches to date_to" do + date = Date.parse("2016-09-15") + expect(rate.has_price_for_date?(date)).to eq(true) + end + + it "returns false when given date is less than date_from" do + date = Date.parse("2016-08-15") + expect(rate.has_price_for_date?(date)).to eq(false) + end + + it "returns false when given date is greater than date_to" do + date = Date.parse("2016-09-16") + expect(rate.has_price_for_date?(date)).to eq(false) + end + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb index 3a331b857..c2e1c08a5 100644 --- a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -118,4 +118,15 @@ importer.fetch_owners end end + + describe "#fetch_availabilities" do + let(:property_id) { "588788" } + + it "calls fetcher class to load availabilities" do + fetcher_class = RentalsUnited::Commands::AvailabilitiesFetcher + + expect_any_instance_of(fetcher_class).to(receive(:fetch_availabilities)) + importer.fetch_availabilities(property_id) + end + end end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/availability_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/availability_spec.rb new file mode 100644 index 000000000..6dec31389 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/availability_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Mappers::Availability do + let(:availability_hash) do + { + "IsBlocked"=>true, + "MinStay"=>"1", + "Changeover"=>"4", + "@Date"=>"2016-09-07" + } + end + + it "builds availability object" do + mapper = described_class.new(availability_hash) + availability = mapper.build_availability + + expect(availability).to be_kind_of(RentalsUnited::Entities::Availability) + expect(availability.date).to be_kind_of(Date) + expect(availability.date.to_s).to eq("2016-09-07") + expect(availability.available).to eq(false) + expect(availability.minimum_stay).to eq(1) + expect(availability.changeover).to eq(4) + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb new file mode 100644 index 000000000..8c75afab9 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Mappers::Calendar do + let(:property_id) { "1234" } + let(:rates) do + [ + RentalsUnited::Entities::Rate.new( + date_from: Date.parse("2016-09-01"), + date_to: Date.parse("2016-09-15"), + price: "150.00" + ), + RentalsUnited::Entities::Rate.new( + date_from: Date.parse("2016-10-01"), + date_to: Date.parse("2016-10-15"), + price: "200.00" + ) + ] + end + + let(:availabilities) do + [ + RentalsUnited::Entities::Availability.new( + date: Date.parse("2016-09-01"), + available: false, + minimum_stay: 2, + changeover: 4 + ), + RentalsUnited::Entities::Availability.new( + date: Date.parse("2016-10-14"), + available: true, + minimum_stay: 1, + changeover: 4 + ) + ] + end + + it "builds empty property calendar" do + mapper = described_class.new(property_id, [], []) + calendar = mapper.build_calendar + + expect(calendar).to be_kind_of(Roomorama::Calendar) + expect(calendar.identifier).to eq(property_id) + expect(calendar.entries).to eq([]) + end + + it "builds calendar with entries" do + mapper = described_class.new(property_id, rates, availabilities) + calendar = mapper.build_calendar + + expect(calendar).to be_kind_of(Roomorama::Calendar) + expect(calendar.identifier).to eq(property_id) + expect(calendar.entries.size).to eq(2) + expect(calendar.entries).to all(be_kind_of(Roomorama::Calendar::Entry)) + + sep_entry = calendar.entries.find { |e| e.date.to_s == "2016-09-01" } + expect(sep_entry.available).to eq(false) + expect(sep_entry.minimum_stay).to eq(2) + expect(sep_entry.nightly_rate).to eq("150.00") + + oct_entry = calendar.entries.find { |e| e.date.to_s == "2016-10-14" } + expect(oct_entry.available).to eq(true) + expect(oct_entry.minimum_stay).to eq(1) + expect(oct_entry.nightly_rate).to eq("200.00") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/rate_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/rate_spec.rb new file mode 100644 index 000000000..f1053d07c --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/rate_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Mappers::Rate do + let(:rate_hash) do + { + "Price"=>"200.0000", + "Extra"=>"10.0000", + "@DateFrom"=>"2016-09-07", + "@DateTo"=>"2016-09-30" + } + end + + it "builds rate object" do + mapper = described_class.new(rate_hash) + rate = mapper.build_rate + + expect(rate).to be_kind_of(RentalsUnited::Entities::Rate) + expect(rate.date_from).to be_kind_of(Date) + expect(rate.date_from.to_s).to eq("2016-09-07") + expect(rate.date_to).to be_kind_of(Date) + expect(rate.date_to.to_s).to eq("2016-09-30") + expect(rate.price).to eq("200.0000") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index d77f4695c..a3a450a92 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -106,6 +106,108 @@ end end + describe '#build_availabilities_fetch_payload' do + let(:params) do + { + property_id: "123", + date_from: "2016-09-01", + date_to: "2017-09-01" + } + end + + let(:root_tag) { "Pull_ListPropertyAvailabilityCalendar_RQ" } + + it 'embedds username and password to request' do + xml = builder.build_availabilities_fetch_payload( + params[:property_id], + params[:date_from], + params[:date_to] + ) + hash = to_hash(xml) + + authentication = hash.get("#{root_tag}.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + + it 'adds property_id to request' do + xml = builder.build_availabilities_fetch_payload( + params[:property_id], + params[:date_from], + params[:date_to] + ) + hash = to_hash(xml) + + property_id = hash.get("#{root_tag}.PropertyID") + expect(property_id).to eq(params[:property_id]) + end + + it 'adds date range to request' do + xml = builder.build_availabilities_fetch_payload( + params[:property_id], + params[:date_from], + params[:date_to] + ) + hash = to_hash(xml) + + date_from = hash.get("#{root_tag}.DateFrom") + date_to = hash.get("#{root_tag}.DateTo") + expect(date_from.to_s).to eq(params[:date_from]) + expect(date_to.to_s).to eq(params[:date_to]) + end + end + + describe '#build_rates_fetch_payload' do + let(:params) do + { + property_id: "123", + date_from: "2016-09-01", + date_to: "2017-09-01" + } + end + + let(:root_tag) { "Pull_ListPropertyPrices_RQ" } + + it 'embedds username and password to request' do + xml = builder.build_rates_fetch_payload( + params[:property_id], + params[:date_from], + params[:date_to] + ) + hash = to_hash(xml) + + authentication = hash.get("#{root_tag}.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + + it 'adds property_id to request' do + xml = builder.build_rates_fetch_payload( + params[:property_id], + params[:date_from], + params[:date_to] + ) + hash = to_hash(xml) + + property_id = hash.get("#{root_tag}.PropertyID") + expect(property_id).to eq(params[:property_id]) + end + + it 'adds date range to request' do + xml = builder.build_rates_fetch_payload( + params[:property_id], + params[:date_from], + params[:date_to] + ) + hash = to_hash(xml) + + date_from = hash.get("#{root_tag}.DateFrom") + date_to = hash.get("#{root_tag}.DateTo") + expect(date_from.to_s).to eq(params[:date_from]) + expect(date_to.to_s).to eq(params[:date_to]) + end + end + private def to_hash(xml) Concierge::SafeAccessHash.new(Nori.new.parse(xml)) From fe81a5f7e8306935ad6d770f23fb1c68640911dc Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 21:11:15 +0600 Subject: [PATCH 066/118] RU: do not try to add entries without prices --- .../rentals_united/mappers/calendar.rb | 3 ++- .../rentals_united/mappers/calendar_spec.rb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb index 030aff0c9..37390ccff 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb @@ -25,7 +25,8 @@ def initialize(property_id, rates, availabilities) def build_calendar calendar = Roomorama::Calendar.new(property_id) - entries.each { |entry| calendar.add(entry) } + valid_entries = entries.select { |entry| entry.valid? } + valid_entries.each { |entry| calendar.add(entry) } calendar end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb index 8c75afab9..81b7c9aaf 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb @@ -48,6 +48,8 @@ module RentalsUnited mapper = described_class.new(property_id, rates, availabilities) calendar = mapper.build_calendar + expect(calendar.validate!).to eq(true) + expect(calendar).to be_kind_of(Roomorama::Calendar) expect(calendar.identifier).to eq(property_id) expect(calendar.entries.size).to eq(2) @@ -63,5 +65,21 @@ module RentalsUnited expect(oct_entry.minimum_stay).to eq(1) expect(oct_entry.nightly_rate).to eq("200.00") end + + it "keeps only calendar entries which have prices" do + availabilities << RentalsUnited::Entities::Availability.new( + date: Date.parse("2017-01-01"), + available: true, + minimum_stay: 5, + changeover: 4 + ) + mapper = described_class.new(property_id, rates, availabilities) + calendar = mapper.build_calendar + + entry = calendar.entries.find { |e| e.date.to_s == "2017-01-01" } + expect(entry).to be_nil + + expect(calendar.validate!).to eq(true) + end end end From b2e2f392e7e8b35d462f68aae26c4985384fec5c Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 13 Sep 2016 16:16:48 +0600 Subject: [PATCH 067/118] RU: documentation update for availabilities & rates --- .../rentals_united/commands/availabilities_fetcher.rb | 4 ++++ .../suppliers/rentals_united/commands/rates_fetcher.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher.rb index 37cf52a18..d95f49624 100644 --- a/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher.rb @@ -31,6 +31,10 @@ def initialize(credentials, property_id) @property_id = property_id end + # Retrieves property availabilities. + # + # Returns a +Result+ wrapping +Array+ of +Entities::Availability+ + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_availabilities payload = payload_builder.build_availabilities_fetch_payload( property_id, diff --git a/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb index d623a679d..5a1981cad 100644 --- a/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb @@ -29,6 +29,10 @@ def initialize(credentials, property_id) @property_id = property_id end + # Retrieves property rates. + # + # Returns a +Result+ wrapping +Array+ of +Entities::Rate+ objects + # Returns a +Result+ with +Result::Error+ when operation fails def fetch_rates payload = payload_builder.build_rates_fetch_payload( property_id, From 1501860563f33d1851946792b487e899e3932081 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 01:42:50 +0600 Subject: [PATCH 068/118] RU: swap each with returns to find with safe navigation operator --- lib/concierge/suppliers/rentals_united/mappers/calendar.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb index 37390ccff..209842dc0 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb @@ -44,11 +44,8 @@ def entries end def rate_by_date(date) - rates.each do |rate| - return rate.price if rate.has_price_for_date?(date) - end - - return nil + rate_for_date = rates.find { |rate| rate.has_price_for_date?(date) } + rate_for_date&.price end end end From afa07bffa9b3c9bd2698209be44be18ca31658a9 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 09:34:32 +0600 Subject: [PATCH 069/118] RU: add changeover to calendar entry --- .../rentals_united/mappers/calendar.rb | 21 ++++- .../rentals_united/mappers/calendar_spec.rb | 78 +++++++++++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb index 209842dc0..a7e60c8da 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb @@ -6,6 +6,9 @@ module Mappers class Calendar attr_reader :property_id, :rates, :availabilities + CHECK_IN_ALLOWED_CHANGEOVER_TYPE_IDS = [1, 4] + CHECK_OUT_ALLOWED_CHANGEOVER_TYPE_IDS = [2, 4] + # Initialize +RentalsUnited::Mappers::Calendar+ # # Arguments @@ -35,10 +38,12 @@ def build_calendar def entries availabilities.map do |availability| Roomorama::Calendar::Entry.new( - date: availability.date.to_s, - available: availability.available, - nightly_rate: rate_by_date(availability.date), - minimum_stay: availability.minimum_stay + date: availability.date.to_s, + available: availability.available, + nightly_rate: rate_by_date(availability.date), + minimum_stay: availability.minimum_stay, + checkin_allowed: checkin_allowed?(availability.changeover), + checkout_allowed: checkout_allowed?(availability.changeover) ) end end @@ -47,6 +52,14 @@ def rate_by_date(date) rate_for_date = rates.find { |rate| rate.has_price_for_date?(date) } rate_for_date&.price end + + def checkin_allowed?(changeover) + CHECK_IN_ALLOWED_CHANGEOVER_TYPE_IDS.include?(changeover) + end + + def checkout_allowed?(changeover) + CHECK_OUT_ALLOWED_CHANGEOVER_TYPE_IDS.include?(changeover) + end end end end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb index 81b7c9aaf..5ec316e0d 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb @@ -59,11 +59,15 @@ module RentalsUnited expect(sep_entry.available).to eq(false) expect(sep_entry.minimum_stay).to eq(2) expect(sep_entry.nightly_rate).to eq("150.00") + expect(sep_entry.checkin_allowed).to eq(true) + expect(sep_entry.checkout_allowed).to eq(true) oct_entry = calendar.entries.find { |e| e.date.to_s == "2016-10-14" } expect(oct_entry.available).to eq(true) expect(oct_entry.minimum_stay).to eq(1) expect(oct_entry.nightly_rate).to eq("200.00") + expect(sep_entry.checkin_allowed).to eq(true) + expect(sep_entry.checkout_allowed).to eq(true) end it "keeps only calendar entries which have prices" do @@ -81,5 +85,79 @@ module RentalsUnited expect(calendar.validate!).to eq(true) end + + context "while availabilities changeover mapping" do + let(:date) { Date.parse("2016-09-01") } + let(:availabilities) do + [ + RentalsUnited::Entities::Availability.new( + date: date, + available: false, + minimum_stay: 2, + changeover: changeover + ) + ] + end + + context "when changeover type id is 1" do + let(:changeover) { 1 } + + it "sets checkin to be allowed and checkout to be denied" do + mapper = described_class.new(property_id, rates, availabilities) + calendar = mapper.build_calendar + + expect(calendar.validate!).to eq(true) + + entry = calendar.entries.find { |e| e.date.to_s == date.to_s } + expect(entry.checkin_allowed).to eq(true) + expect(entry.checkout_allowed).to eq(false) + end + end + + context "when changeover type id is 2" do + let(:changeover) { 2 } + + it "sets checkin to be denied and checkout to be allowed" do + mapper = described_class.new(property_id, rates, availabilities) + calendar = mapper.build_calendar + + expect(calendar.validate!).to eq(true) + + entry = calendar.entries.find { |e| e.date.to_s == date.to_s } + expect(entry.checkin_allowed).to eq(false) + expect(entry.checkout_allowed).to eq(true) + end + end + + context "when changeover type id is 3" do + let(:changeover) { 3 } + + it "sets both checkin and checkout to be denied" do + mapper = described_class.new(property_id, rates, availabilities) + calendar = mapper.build_calendar + + expect(calendar.validate!).to eq(true) + + entry = calendar.entries.find { |e| e.date.to_s == date.to_s } + expect(entry.checkin_allowed).to eq(false) + expect(entry.checkout_allowed).to eq(false) + end + end + + context "when changeover type id is 4" do + let(:changeover) { 4 } + + it "sets both checkin and checkout to be allowed" do + mapper = described_class.new(property_id, rates, availabilities) + calendar = mapper.build_calendar + + expect(calendar.validate!).to eq(true) + + entry = calendar.entries.find { |e| e.date.to_s == date.to_s } + expect(entry.checkin_allowed).to eq(true) + expect(entry.checkout_allowed).to eq(true) + end + end + end end end From 6d714b263e89c5a1c4f4bb949ae5a906ceadc325 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 10:03:02 +0600 Subject: [PATCH 070/118] RU: rename Entities::Rate -> Entities::Season --- .../rentals_united/availabilities.rb | 12 ++++----- .../{rates_fetcher.rb => seasons_fetcher.rb} | 26 +++++++++---------- .../entities/{rate.rb => season.rb} | 6 ++--- .../suppliers/rentals_united/importer.rb | 10 ++++--- .../rentals_united/mappers/calendar.rb | 12 ++++----- .../mappers/{rate.rb => season.rb} | 16 ++++++------ .../rentals_united/payload_builder.rb | 4 +-- ...es_fetch.xml.erb => seasons_fetch.xml.erb} | 0 .../{rates => seasons}/no_seasons.xml | 0 .../{rates => seasons}/not_found.xml | 0 .../{rates => seasons}/success.xml | 0 ...etcher_spec.rb => seasons_fetcher_spec.rb} | 26 +++++++++---------- .../entities/{rate_spec.rb => season_spec.rb} | 14 +++++----- .../rentals_united/mappers/calendar_spec.rb | 18 ++++++------- .../rentals_united/mappers/rate_spec.rb | 26 ------------------- .../rentals_united/mappers/season_spec.rb | 26 +++++++++++++++++++ .../rentals_united/payload_builder_spec.rb | 8 +++--- 17 files changed, 103 insertions(+), 101 deletions(-) rename lib/concierge/suppliers/rentals_united/commands/{rates_fetcher.rb => seasons_fetcher.rb} (70%) rename lib/concierge/suppliers/rentals_united/entities/{rate.rb => season.rb} (76%) rename lib/concierge/suppliers/rentals_united/mappers/{rate.rb => season.rb} (62%) rename lib/concierge/suppliers/rentals_united/templates/{rates_fetch.xml.erb => seasons_fetch.xml.erb} (100%) rename spec/fixtures/rentals_united/{rates => seasons}/no_seasons.xml (100%) rename spec/fixtures/rentals_united/{rates => seasons}/not_found.xml (100%) rename spec/fixtures/rentals_united/{rates => seasons}/success.xml (100%) rename spec/lib/concierge/suppliers/rentals_united/commands/{rates_fetcher_spec.rb => seasons_fetcher_spec.rb} (78%) rename spec/lib/concierge/suppliers/rentals_united/entities/{rate_spec.rb => season_spec.rb} (68%) delete mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/rate_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/season_spec.rb diff --git a/apps/workers/suppliers/rentals_united/availabilities.rb b/apps/workers/suppliers/rentals_united/availabilities.rb index aecb738ed..3c7fa94f4 100644 --- a/apps/workers/suppliers/rentals_united/availabilities.rb +++ b/apps/workers/suppliers/rentals_united/availabilities.rb @@ -15,9 +15,9 @@ def perform identifiers.each do |property_id| synchronisation.start(property_id) do - result = fetch_rates(property_id) + result = fetch_seasons(property_id) next result unless result.success? - rates = result.value + seasons = result.value result = fetch_availabilities(property_id) next result unless result.success? @@ -25,7 +25,7 @@ def perform mapper = ::RentalsUnited::Mappers::Calendar.new( property_id, - rates, + seasons, availabilities ) Result.new(mapper.build_calendar) @@ -42,9 +42,9 @@ def report_error(message) end end - def fetch_rates(property_id) - report_error("Failed to fetch rates for property `#{property_id}`") do - importer.fetch_rates(property_id) + def fetch_seasons(property_id) + report_error("Failed to fetch seasons for property `#{property_id}`") do + importer.fetch_seasons(property_id) end end diff --git a/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher.rb similarity index 70% rename from lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb rename to lib/concierge/suppliers/rentals_united/commands/seasons_fetcher.rb index 5a1981cad..6cc83fea7 100644 --- a/lib/concierge/suppliers/rentals_united/commands/rates_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher.rb @@ -1,16 +1,16 @@ module RentalsUnited module Commands - # +RentalsUnited::Commands::RatesFetcher+ + # +RentalsUnited::Commands::SeasonsFetcher+ # # This class is responsible for wrapping the logic related to fetching - # property rates from RentalsUnited - class RatesFetcher < BaseFetcher + # property rate seasons from RentalsUnited + class SeasonsFetcher < BaseFetcher attr_reader :property_id ROOT_TAG = "Pull_ListPropertyPrices_RS" YEARS_COUNT_TO_FETCH = 1 - # Initialize +RatesFetcher+ command. + # Initialize +SeasonsFetcher+ command. # # Arguments # @@ -19,7 +19,7 @@ class RatesFetcher < BaseFetcher # # Usage: # - # RentalsUnited::Commands::RatesFetcher.new( + # RentalsUnited::Commands::SeasonsFetcher.new( # credentials, # property_id # ) @@ -29,12 +29,12 @@ def initialize(credentials, property_id) @property_id = property_id end - # Retrieves property rates. + # Retrieves property rate seasons. # - # Returns a +Result+ wrapping +Array+ of +Entities::Rate+ objects + # Returns a +Result+ wrapping +Array+ of +Entities::Season+ objects # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_rates - payload = payload_builder.build_rates_fetch_payload( + def fetch_seasons + payload = payload_builder.build_seasons_fetch_payload( property_id, date_from, date_to @@ -46,7 +46,7 @@ def fetch_rates result_hash = response_parser.to_hash(result.value.body) if valid_status?(result_hash, ROOT_TAG) - Result.new(build_rates(result_hash)) + Result.new(build_seasons(result_hash)) else error_result(result_hash, ROOT_TAG) end @@ -65,11 +65,11 @@ def date_to date.strftime("%Y-%m-%d") end - def build_rates(hash) + def build_seasons(hash) seasons = Array(hash.get("#{ROOT_TAG}.Prices.Season")) seasons.map do |season| - mapper = Mappers::Rate.new(season) - mapper.build_rate + mapper = Mappers::Season.new(season) + mapper.build_season end end end diff --git a/lib/concierge/suppliers/rentals_united/entities/rate.rb b/lib/concierge/suppliers/rentals_united/entities/season.rb similarity index 76% rename from lib/concierge/suppliers/rentals_united/entities/rate.rb rename to lib/concierge/suppliers/rentals_united/entities/season.rb index e33e07880..c977257bd 100644 --- a/lib/concierge/suppliers/rentals_united/entities/rate.rb +++ b/lib/concierge/suppliers/rentals_united/entities/season.rb @@ -1,9 +1,9 @@ module RentalsUnited module Entities - # +RentalsUnited::Entities::Rate+ + # +RentalsUnited::Entities::Season+ # - # This entity represents an rate object. - class Rate + # This entity represents a rates season object. + class Season attr_accessor :date_from, :date_to, :price def initialize(date_from:, date_to:, price:) diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb index f3d97da31..e68bb84ad 100644 --- a/lib/concierge/suppliers/rentals_united/importer.rb +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -110,10 +110,12 @@ def fetch_availabilities(property_id) fetcher.fetch_availabilities end - # Retrieves rates for property by its id. - def fetch_rates(property_id) - fetcher = Commands::RatesFetcher.new(credentials, property_id) - fetcher.fetch_rates + # Retrieves season rates for property by its id. + # + # Returns [Array] array with season rate objects + def fetch_seasons(property_id) + fetcher = Commands::SeasonsFetcher.new(credentials, property_id) + fetcher.fetch_seasons end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb index a7e60c8da..a8e4a388b 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb @@ -4,7 +4,7 @@ module Mappers # # This class is responsible for building a calendar for property. class Calendar - attr_reader :property_id, :rates, :availabilities + attr_reader :property_id, :seasons, :availabilities CHECK_IN_ALLOWED_CHANGEOVER_TYPE_IDS = [1, 4] CHECK_OUT_ALLOWED_CHANGEOVER_TYPE_IDS = [2, 4] @@ -14,11 +14,11 @@ class Calendar # Arguments # # * +propertyid+ [String] id of property - # * +rates+ [Array] rates + # * +seasons+ [Array] seasons # * +availabilities+ [Array] availabilities - def initialize(property_id, rates, availabilities) + def initialize(property_id, seasons, availabilities) @property_id = property_id - @rates = rates + @seasons = seasons @availabilities = availabilities end @@ -49,8 +49,8 @@ def entries end def rate_by_date(date) - rate_for_date = rates.find { |rate| rate.has_price_for_date?(date) } - rate_for_date&.price + season = seasons.find { |s| s.has_price_for_date?(date) } + season&.price end def checkin_allowed?(changeover) diff --git a/lib/concierge/suppliers/rentals_united/mappers/rate.rb b/lib/concierge/suppliers/rentals_united/mappers/season.rb similarity index 62% rename from lib/concierge/suppliers/rentals_united/mappers/rate.rb rename to lib/concierge/suppliers/rentals_united/mappers/season.rb index 73be7ceec..f2190fb9e 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/rate.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/season.rb @@ -1,19 +1,19 @@ module RentalsUnited module Mappers - # +RentalsUnited::Mappers::Rate+ + # +RentalsUnited::Mappers::Season+ # - # This class is responsible for building a rate object. - class Rate + # This class is responsible for building a season object. + class Season attr_reader :hash - # Initialize +RentalsUnited::Mappers::Rate+. + # Initialize +RentalsUnited::Mappers::Season+. # # Arguments - # * +hash+ rate object hash + # * +hash+ season object hash # # Usage # - # RentalsUnited::Mappers::Rate.new({ + # RentalsUnited::Mappers::Season.new({ # "Price"=>"200.0000", # "Extra"=>"10.0000", # "@DateFrom"=>"2016-09-07", @@ -23,8 +23,8 @@ def initialize(hash) @hash = hash end - def build_rate - Entities::Rate.new( + def build_season + Entities::Season.new( date_from: Date.parse(hash["@DateFrom"]), date_to: Date.parse(hash["@DateTo"]), price: hash["Price"] diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index e52f98c23..39a927af0 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -59,14 +59,14 @@ def build_availabilities_fetch_payload(property_id, date_from, date_to) render(:availabilities_fetch, template_locals) end - def build_rates_fetch_payload(property_id, date_from, date_to) + def build_seasons_fetch_payload(property_id, date_from, date_to) template_locals = { credentials: credentials, property_id: property_id, date_from: date_from, date_to: date_to } - render(:rates_fetch, template_locals) + render(:seasons_fetch, template_locals) end private diff --git a/lib/concierge/suppliers/rentals_united/templates/rates_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/seasons_fetch.xml.erb similarity index 100% rename from lib/concierge/suppliers/rentals_united/templates/rates_fetch.xml.erb rename to lib/concierge/suppliers/rentals_united/templates/seasons_fetch.xml.erb diff --git a/spec/fixtures/rentals_united/rates/no_seasons.xml b/spec/fixtures/rentals_united/seasons/no_seasons.xml similarity index 100% rename from spec/fixtures/rentals_united/rates/no_seasons.xml rename to spec/fixtures/rentals_united/seasons/no_seasons.xml diff --git a/spec/fixtures/rentals_united/rates/not_found.xml b/spec/fixtures/rentals_united/seasons/not_found.xml similarity index 100% rename from spec/fixtures/rentals_united/rates/not_found.xml rename to spec/fixtures/rentals_united/seasons/not_found.xml diff --git a/spec/fixtures/rentals_united/rates/success.xml b/spec/fixtures/rentals_united/seasons/success.xml similarity index 100% rename from spec/fixtures/rentals_united/rates/success.xml rename to spec/fixtures/rentals_united/seasons/success.xml diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/rates_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher_spec.rb similarity index 78% rename from spec/lib/concierge/suppliers/rentals_united/commands/rates_fetcher_spec.rb rename to spec/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher_spec.rb index e59871948..2eb896f55 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/rates_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -RSpec.describe RentalsUnited::Commands::RatesFetcher do +RSpec.describe RentalsUnited::Commands::SeasonsFetcher do include Support::HTTPStubbing include Support::Fixtures @@ -10,10 +10,10 @@ let(:url) { credentials.url } it "returns an error if property does not exist" do - stub_data = read_fixture("rentals_united/rates/not_found.xml") + stub_data = read_fixture("rentals_united/seasons/not_found.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_rates + result = subject.fetch_seasons expect(result).not_to be_success expect(result.error.code).to eq("56") @@ -25,28 +25,28 @@ expect(event[:backtrace].any?).to be true end - it "returns an empty array if property have no rates seasons" do - stub_data = read_fixture("rentals_united/rates/no_seasons.xml") + it "returns an empty array if property have no seasons seasons" do + stub_data = read_fixture("rentals_united/seasons/no_seasons.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_rates + result = subject.fetch_seasons expect(result).to be_success expect(result.value).to eq([]) end - context "when response contains rates data" do - let(:file_name) { "rentals_united/rates/success.xml" } + context "when response contains seasons data" do + let(:file_name) { "rentals_united/seasons/success.xml" } before do stub_data = read_fixture(file_name) stub_call(:post, url) { [200, {}, stub_data] } end - it "returns rates" do - result = subject.fetch_rates + it "returns seasons" do + result = subject.fetch_seasons expect(result).to be_success expect(result.value.size).to eq(2) - expect(result.value).to all(be_kind_of(RentalsUnited::Entities::Rate)) + expect(result.value).to all(be_kind_of(RentalsUnited::Entities::Season)) end end @@ -55,7 +55,7 @@ stub_data = read_fixture("rentals_united/bad_xml.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_rates + result = subject.fetch_seasons expect(result).not_to be_success expect(result.error.code).to eq(:unrecognised_response) @@ -73,7 +73,7 @@ it "returns a result with an appropriate error" do stub_call(:post, url) { raise Faraday::TimeoutError } - result = subject.fetch_rates + result = subject.fetch_seasons expect(result).not_to be_success expect(result.error.code).to eq :connection_timeout diff --git a/spec/lib/concierge/suppliers/rentals_united/entities/rate_spec.rb b/spec/lib/concierge/suppliers/rentals_united/entities/season_spec.rb similarity index 68% rename from spec/lib/concierge/suppliers/rentals_united/entities/rate_spec.rb rename to spec/lib/concierge/suppliers/rentals_united/entities/season_spec.rb index a4ab8560f..cdf706db8 100644 --- a/spec/lib/concierge/suppliers/rentals_united/entities/rate_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/entities/season_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' module RentalsUnited - RSpec.describe Entities::Rate do + RSpec.describe Entities::Season do let(:attributes) do { date_from: Date.parse("2016-09-01"), @@ -10,32 +10,32 @@ module RentalsUnited } end - let(:rate) { Entities::Rate.new(attributes) } + let(:season) { Entities::Season.new(attributes) } describe "has_price_for_date?" do it "returns true when given date is between from and to dates" do date = Date.parse("2016-09-04") - expect(rate.has_price_for_date?(date)).to eq(true) + expect(season.has_price_for_date?(date)).to eq(true) end it "returns true when given date matches to date_from" do date = Date.parse("2016-09-01") - expect(rate.has_price_for_date?(date)).to eq(true) + expect(season.has_price_for_date?(date)).to eq(true) end it "returns true when given date matches to date_to" do date = Date.parse("2016-09-15") - expect(rate.has_price_for_date?(date)).to eq(true) + expect(season.has_price_for_date?(date)).to eq(true) end it "returns false when given date is less than date_from" do date = Date.parse("2016-08-15") - expect(rate.has_price_for_date?(date)).to eq(false) + expect(season.has_price_for_date?(date)).to eq(false) end it "returns false when given date is greater than date_to" do date = Date.parse("2016-09-16") - expect(rate.has_price_for_date?(date)).to eq(false) + expect(season.has_price_for_date?(date)).to eq(false) end end end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb index 5ec316e0d..89a824922 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb @@ -3,14 +3,14 @@ module RentalsUnited RSpec.describe Mappers::Calendar do let(:property_id) { "1234" } - let(:rates) do + let(:seasons) do [ - RentalsUnited::Entities::Rate.new( + RentalsUnited::Entities::Season.new( date_from: Date.parse("2016-09-01"), date_to: Date.parse("2016-09-15"), price: "150.00" ), - RentalsUnited::Entities::Rate.new( + RentalsUnited::Entities::Season.new( date_from: Date.parse("2016-10-01"), date_to: Date.parse("2016-10-15"), price: "200.00" @@ -45,7 +45,7 @@ module RentalsUnited end it "builds calendar with entries" do - mapper = described_class.new(property_id, rates, availabilities) + mapper = described_class.new(property_id, seasons, availabilities) calendar = mapper.build_calendar expect(calendar.validate!).to eq(true) @@ -77,7 +77,7 @@ module RentalsUnited minimum_stay: 5, changeover: 4 ) - mapper = described_class.new(property_id, rates, availabilities) + mapper = described_class.new(property_id, seasons, availabilities) calendar = mapper.build_calendar entry = calendar.entries.find { |e| e.date.to_s == "2017-01-01" } @@ -103,7 +103,7 @@ module RentalsUnited let(:changeover) { 1 } it "sets checkin to be allowed and checkout to be denied" do - mapper = described_class.new(property_id, rates, availabilities) + mapper = described_class.new(property_id, seasons, availabilities) calendar = mapper.build_calendar expect(calendar.validate!).to eq(true) @@ -118,7 +118,7 @@ module RentalsUnited let(:changeover) { 2 } it "sets checkin to be denied and checkout to be allowed" do - mapper = described_class.new(property_id, rates, availabilities) + mapper = described_class.new(property_id, seasons, availabilities) calendar = mapper.build_calendar expect(calendar.validate!).to eq(true) @@ -133,7 +133,7 @@ module RentalsUnited let(:changeover) { 3 } it "sets both checkin and checkout to be denied" do - mapper = described_class.new(property_id, rates, availabilities) + mapper = described_class.new(property_id, seasons, availabilities) calendar = mapper.build_calendar expect(calendar.validate!).to eq(true) @@ -148,7 +148,7 @@ module RentalsUnited let(:changeover) { 4 } it "sets both checkin and checkout to be allowed" do - mapper = described_class.new(property_id, rates, availabilities) + mapper = described_class.new(property_id, seasons, availabilities) calendar = mapper.build_calendar expect(calendar.validate!).to eq(true) diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/rate_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/rate_spec.rb deleted file mode 100644 index f1053d07c..000000000 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/rate_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'spec_helper' - -module RentalsUnited - RSpec.describe Mappers::Rate do - let(:rate_hash) do - { - "Price"=>"200.0000", - "Extra"=>"10.0000", - "@DateFrom"=>"2016-09-07", - "@DateTo"=>"2016-09-30" - } - end - - it "builds rate object" do - mapper = described_class.new(rate_hash) - rate = mapper.build_rate - - expect(rate).to be_kind_of(RentalsUnited::Entities::Rate) - expect(rate.date_from).to be_kind_of(Date) - expect(rate.date_from.to_s).to eq("2016-09-07") - expect(rate.date_to).to be_kind_of(Date) - expect(rate.date_to.to_s).to eq("2016-09-30") - expect(rate.price).to eq("200.0000") - end - end -end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/season_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/season_spec.rb new file mode 100644 index 000000000..a1265b53f --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/season_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Mappers::Season do + let(:season_hash) do + { + "Price"=>"200.0000", + "Extra"=>"10.0000", + "@DateFrom"=>"2016-09-07", + "@DateTo"=>"2016-09-30" + } + end + + it "builds season object" do + mapper = described_class.new(season_hash) + season = mapper.build_season + + expect(season).to be_kind_of(RentalsUnited::Entities::Season) + expect(season.date_from).to be_kind_of(Date) + expect(season.date_from.to_s).to eq("2016-09-07") + expect(season.date_to).to be_kind_of(Date) + expect(season.date_to.to_s).to eq("2016-09-30") + expect(season.price).to eq("200.0000") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index a3a450a92..760c7eac9 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -157,7 +157,7 @@ end end - describe '#build_rates_fetch_payload' do + describe '#build_seasons_fetch_payload' do let(:params) do { property_id: "123", @@ -169,7 +169,7 @@ let(:root_tag) { "Pull_ListPropertyPrices_RQ" } it 'embedds username and password to request' do - xml = builder.build_rates_fetch_payload( + xml = builder.build_seasons_fetch_payload( params[:property_id], params[:date_from], params[:date_to] @@ -182,7 +182,7 @@ end it 'adds property_id to request' do - xml = builder.build_rates_fetch_payload( + xml = builder.build_seasons_fetch_payload( params[:property_id], params[:date_from], params[:date_to] @@ -194,7 +194,7 @@ end it 'adds date range to request' do - xml = builder.build_rates_fetch_payload( + xml = builder.build_seasons_fetch_payload( params[:property_id], params[:date_from], params[:date_to] From 7c677cdcda0e3353a71c0da2d69a3e75b7adeed1 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 16:46:08 +0600 Subject: [PATCH 071/118] RU: do not skip invalid calendar entries forcing mapper to create unavailable entry instead --- .../rentals_united/mappers/calendar.rb | 25 +++++++++++++------ .../rentals_united/mappers/season.rb | 2 +- .../rentals_united/mappers/calendar_spec.rb | 16 +++++++----- .../rentals_united/mappers/season_spec.rb | 2 +- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb index a8e4a388b..28fc5f56c 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb @@ -28,8 +28,7 @@ def initialize(property_id, seasons, availabilities) def build_calendar calendar = Roomorama::Calendar.new(property_id) - valid_entries = entries.select { |entry| entry.valid? } - valid_entries.each { |entry| calendar.add(entry) } + entries.each { |entry| calendar.add(entry) } calendar end @@ -37,20 +36,32 @@ def build_calendar private def entries availabilities.map do |availability| + nightly_rate = rate_by_date(availability.date) + + if nightly_rate.zero? + available = false + checkin_allowed = false + checkout_allowed = false + else + available = availability.available + checkin_allowed = checkin_allowed?(availability.changeover) + checkout_allowed = checkout_allowed?(availability.changeover) + end + Roomorama::Calendar::Entry.new( date: availability.date.to_s, - available: availability.available, - nightly_rate: rate_by_date(availability.date), + available: available, + nightly_rate: nightly_rate, minimum_stay: availability.minimum_stay, - checkin_allowed: checkin_allowed?(availability.changeover), - checkout_allowed: checkout_allowed?(availability.changeover) + checkin_allowed: checkin_allowed, + checkout_allowed: checkout_allowed ) end end def rate_by_date(date) season = seasons.find { |s| s.has_price_for_date?(date) } - season&.price + season&.price.to_f end def checkin_allowed?(changeover) diff --git a/lib/concierge/suppliers/rentals_united/mappers/season.rb b/lib/concierge/suppliers/rentals_united/mappers/season.rb index f2190fb9e..dba4a5611 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/season.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/season.rb @@ -27,7 +27,7 @@ def build_season Entities::Season.new( date_from: Date.parse(hash["@DateFrom"]), date_to: Date.parse(hash["@DateTo"]), - price: hash["Price"] + price: hash["Price"].to_f ) end end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb index 89a824922..950b5de9c 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb @@ -8,12 +8,12 @@ module RentalsUnited RentalsUnited::Entities::Season.new( date_from: Date.parse("2016-09-01"), date_to: Date.parse("2016-09-15"), - price: "150.00" + price: 150.0 ), RentalsUnited::Entities::Season.new( date_from: Date.parse("2016-10-01"), date_to: Date.parse("2016-10-15"), - price: "200.00" + price: 200.0 ) ] end @@ -58,19 +58,19 @@ module RentalsUnited sep_entry = calendar.entries.find { |e| e.date.to_s == "2016-09-01" } expect(sep_entry.available).to eq(false) expect(sep_entry.minimum_stay).to eq(2) - expect(sep_entry.nightly_rate).to eq("150.00") + expect(sep_entry.nightly_rate).to eq(150.0) expect(sep_entry.checkin_allowed).to eq(true) expect(sep_entry.checkout_allowed).to eq(true) oct_entry = calendar.entries.find { |e| e.date.to_s == "2016-10-14" } expect(oct_entry.available).to eq(true) expect(oct_entry.minimum_stay).to eq(1) - expect(oct_entry.nightly_rate).to eq("200.00") + expect(oct_entry.nightly_rate).to eq(200.0) expect(sep_entry.checkin_allowed).to eq(true) expect(sep_entry.checkout_allowed).to eq(true) end - it "keeps only calendar entries which have prices" do + it "keeps even not valid calendar entries setting nightly_rate to 0" do availabilities << RentalsUnited::Entities::Availability.new( date: Date.parse("2017-01-01"), available: true, @@ -81,7 +81,11 @@ module RentalsUnited calendar = mapper.build_calendar entry = calendar.entries.find { |e| e.date.to_s == "2017-01-01" } - expect(entry).to be_nil + expect(entry.available).to eq(false) + expect(entry.minimum_stay).to eq(5) + expect(entry.nightly_rate).to eq(0.0) + expect(entry.checkin_allowed).to eq(false) + expect(entry.checkout_allowed).to eq(false) expect(calendar.validate!).to eq(true) end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/season_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/season_spec.rb index a1265b53f..2ed380408 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/season_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/season_spec.rb @@ -20,7 +20,7 @@ module RentalsUnited expect(season.date_from.to_s).to eq("2016-09-07") expect(season.date_to).to be_kind_of(Date) expect(season.date_to.to_s).to eq("2016-09-30") - expect(season.price).to eq("200.0000") + expect(season.price).to eq(200.0) end end end From afdfb4e18e1e275c497f940dddc4f85feb4f0feb Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 21 Sep 2016 17:35:43 +0600 Subject: [PATCH 072/118] RU: protection against the case when we meet not supported changeover type for availability --- .../rentals_united/availabilities.rb | 2 +- .../rentals_united/mappers/calendar.rb | 23 +++++++-- .../rentals_united/mappers/calendar_spec.rb | 47 ++++++++++++++++--- 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/availabilities.rb b/apps/workers/suppliers/rentals_united/availabilities.rb index 3c7fa94f4..798b741cd 100644 --- a/apps/workers/suppliers/rentals_united/availabilities.rb +++ b/apps/workers/suppliers/rentals_united/availabilities.rb @@ -28,7 +28,7 @@ def perform seasons, availabilities ) - Result.new(mapper.build_calendar) + mapper.build_calendar end end synchronisation.finish! diff --git a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb index 28fc5f56c..a9786011c 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/calendar.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/calendar.rb @@ -6,6 +6,7 @@ module Mappers class Calendar attr_reader :property_id, :seasons, :availabilities + SUPPORTED_CHANGEOVER_TYPE_IDS = [1, 2, 3, 4] CHECK_IN_ALLOWED_CHANGEOVER_TYPE_IDS = [1, 4] CHECK_OUT_ALLOWED_CHANGEOVER_TYPE_IDS = [2, 4] @@ -24,18 +25,27 @@ def initialize(property_id, seasons, availabilities) # Builds calendar. # - # Returns [Roomorama::Calendar] property calendar object + # Returns a +Result+ wrapping +Roomorama::Calendar+ + # Returns a +Result+ with +Result::Error+ when operation fails def build_calendar calendar = Roomorama::Calendar.new(property_id) + entries_result = build_entries + return entries_result unless entries_result.success? + + entries = entries_result.value entries.each { |entry| calendar.add(entry) } - calendar + Result.new(calendar) end private - def entries - availabilities.map do |availability| + def build_entries + entries = availabilities.map do |availability| + unless supported_changeover?(availability.changeover) + return Result.error(:not_supported_changeover) + end + nightly_rate = rate_by_date(availability.date) if nightly_rate.zero? @@ -57,6 +67,7 @@ def entries checkout_allowed: checkout_allowed ) end + Result.new(entries) end def rate_by_date(date) @@ -64,6 +75,10 @@ def rate_by_date(date) season&.price.to_f end + def supported_changeover?(changeover) + SUPPORTED_CHANGEOVER_TYPE_IDS.include?(changeover) + end + def checkin_allowed?(changeover) CHECK_IN_ALLOWED_CHANGEOVER_TYPE_IDS.include?(changeover) end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb index 950b5de9c..3f50cfe0f 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/calendar_spec.rb @@ -37,8 +37,11 @@ module RentalsUnited it "builds empty property calendar" do mapper = described_class.new(property_id, [], []) - calendar = mapper.build_calendar + result = mapper.build_calendar + expect(result).to be_kind_of(Result) + expect(result).to be_success + calendar = result.value expect(calendar).to be_kind_of(Roomorama::Calendar) expect(calendar.identifier).to eq(property_id) expect(calendar.entries).to eq([]) @@ -46,8 +49,11 @@ module RentalsUnited it "builds calendar with entries" do mapper = described_class.new(property_id, seasons, availabilities) - calendar = mapper.build_calendar + result = mapper.build_calendar + expect(result).to be_kind_of(Result) + expect(result).to be_success + calendar = result.value expect(calendar.validate!).to eq(true) expect(calendar).to be_kind_of(Roomorama::Calendar) @@ -78,7 +84,11 @@ module RentalsUnited changeover: 4 ) mapper = described_class.new(property_id, seasons, availabilities) - calendar = mapper.build_calendar + result = mapper.build_calendar + expect(result).to be_kind_of(Result) + expect(result).to be_success + + calendar = result.value entry = calendar.entries.find { |e| e.date.to_s == "2017-01-01" } expect(entry.available).to eq(false) @@ -108,8 +118,11 @@ module RentalsUnited it "sets checkin to be allowed and checkout to be denied" do mapper = described_class.new(property_id, seasons, availabilities) - calendar = mapper.build_calendar + result = mapper.build_calendar + expect(result).to be_kind_of(Result) + expect(result).to be_success + calendar = result.value expect(calendar.validate!).to eq(true) entry = calendar.entries.find { |e| e.date.to_s == date.to_s } @@ -123,8 +136,11 @@ module RentalsUnited it "sets checkin to be denied and checkout to be allowed" do mapper = described_class.new(property_id, seasons, availabilities) - calendar = mapper.build_calendar + result = mapper.build_calendar + expect(result).to be_kind_of(Result) + expect(result).to be_success + calendar = result.value expect(calendar.validate!).to eq(true) entry = calendar.entries.find { |e| e.date.to_s == date.to_s } @@ -138,8 +154,11 @@ module RentalsUnited it "sets both checkin and checkout to be denied" do mapper = described_class.new(property_id, seasons, availabilities) - calendar = mapper.build_calendar + result = mapper.build_calendar + expect(result).to be_kind_of(Result) + expect(result).to be_success + calendar = result.value expect(calendar.validate!).to eq(true) entry = calendar.entries.find { |e| e.date.to_s == date.to_s } @@ -153,8 +172,11 @@ module RentalsUnited it "sets both checkin and checkout to be allowed" do mapper = described_class.new(property_id, seasons, availabilities) - calendar = mapper.build_calendar + result = mapper.build_calendar + expect(result).to be_kind_of(Result) + expect(result).to be_success + calendar = result.value expect(calendar.validate!).to eq(true) entry = calendar.entries.find { |e| e.date.to_s == date.to_s } @@ -162,6 +184,17 @@ module RentalsUnited expect(entry.checkout_allowed).to eq(true) end end + + context "when changeover type id is unknown" do + let(:changeover) { 5 } + + it "returns not supported changeover error" do + mapper = described_class.new(property_id, seasons, availabilities) + result = mapper.build_calendar + expect(result).not_to be_success + expect(result.error.code).to eq(:not_supported_changeover) + end + end end end end From fde15b25eb2c685018d20974dfc10f829a2e56f9 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 17:06:05 +0600 Subject: [PATCH 073/118] RU: small improvements for worker --- .../suppliers/rentals_united/metadata.rb | 80 ++++++++++--------- .../suppliers/rentals_united/metadata_spec.rb | 6 +- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index bba95850f..93942ce27 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -1,9 +1,16 @@ module Workers::Suppliers::RentalsUnited # +Workers::Suppliers::RentalsUnited+ # - # Performs synchronisation with supplier + # Performs property & calendar synchronisation with supplier. + # + # Decision to merge two workers in one was made because of the prices + # issue when we needed to fetch prices with the same API calls in both + # metadata and calendar sync workers. + # + # See more in corresponding PR discussion: + # https://github.com/roomorama/concierge/pull/309#pullrequestreview-682041 class Metadata - attr_reader :synchronisation, :host + attr_reader :property_sync, :calendar_sync, :host # Prevent from publishing results containing error codes below: IGNORABLE_ERROR_CODES = [ @@ -15,11 +22,12 @@ class Metadata def initialize(host) @host = host - @synchronisation = Workers::PropertySynchronisation.new(host) + @property_sync = Workers::PropertySynchronisation.new(host) + @calendar_sync = Workers::CalendarSynchronisation.new(host) end def perform - result = synchronisation.new_context { fetch_location_ids } + result = property_sync.new_context { fetch_location_ids } return unless result.success? location_ids = result.value @@ -43,43 +51,42 @@ def perform location.currency = currencies[location.id] if location.currency - result = synchronisation.new_context { fetch_property_ids(location.id) } - return unless result.success? + result = property_sync.new_context { fetch_property_ids(location.id) } + next unless result.success? property_ids = result.value - result = fetch_properties_by_ids(property_ids) - - if result.success? - properties = result.value - - properties.each do |property| - owner = find_owner(owners, property.owner_id) - - if owner - result = build_roomorama_property(property, location, owner) - next if skip?(result, property) - - synchronisation.start(property.id) { result } - else - message = "Failed to find owner for property id `#{property.id}`" - announce_context_error(message, result) - next - end - end - else - message = "Failed to fetch properties for ids `#{property_ids}` in location `#{location.id}`" - announce_context_error(message, result) - next - end + result = fetch_properties_by_ids(property_ids, location) + next unless result.success? + + sync_properties(result.value, location, owners) else message = "Failed to find currency for location with id `#{location.id}`" - announce_context_error(message, result) + announce_context_error(message, Result.error(:currency_not_found)) next end end - synchronisation.finish! + property_sync.finish! + end + + def sync_properties(properties, location, owners) + properties.each do |property| + owner = find_owner(owners, property.owner_id) + + if owner + result = build_roomorama_property(property, location, owner) + next if skip?(result, property) + + property_sync.start(property.id) do + result + end + else + message = "Failed to find owner for property id `#{property.id}`" + announce_context_error(message, Result.error(:owner_not_found)) + next + end + end end private @@ -132,20 +139,21 @@ def fetch_owners end def fetch_property_ids(location_id) - announce_error("Failed to fetch properties for location `#{location_id}`") do + announce_error("Failed to fetch property ids for location `#{location_id}`") do importer.fetch_property_ids(location_id) end end - def fetch_properties_by_ids(property_ids) - announce_error("Failed to fetch properties by ids `#{property_ids}`") do + def fetch_properties_by_ids(property_ids, location) + message = "Failed to fetch properties for ids `#{property_ids}` in location `#{location.id}`" + announce_error(message) do importer.fetch_properties_by_ids(property_ids) end end def skip?(result, property) if !result.success? && IGNORABLE_ERROR_CODES.include?(result.error.code) - synchronisation.skip_property(property.id, result.error.code) + property_sync.skip_property(property.id, result.error.code) return true end return false diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index 741e55fdd..74812e06f 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -146,12 +146,12 @@ stub_call(:post, url) { [200, {}, stub_data] } result = worker.perform - expect(result).to be_nil + expect(result).to be_kind_of(SyncProcess) event = Concierge.context.events.last.to_h expect(event[:label]).to eq("Synchronisation Failure") expect(event[:message]).to eq( - "Failed to fetch properties for location `1505`" + "Failed to fetch property ids for location `1505`" ) expect(event[:backtrace]).to be_kind_of(Array) expect(event[:backtrace].any?).to be true @@ -216,7 +216,7 @@ expected_property_ids = ["519688"] expected_property_ids.each do |property_id| - expect(worker.synchronisation).to receive(:start).with(property_id) + expect(worker.property_sync).to receive(:start).with(property_id) end result = worker.perform From 352d2dc8c32dbbff6551116ca668d2e227aff495 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 27 Sep 2016 18:10:55 +0600 Subject: [PATCH 074/118] RU: set prices for properties while sync --- .../rentals_united/availabilities.rb | 10 --- .../suppliers/rentals_united/metadata.rb | 19 +++- .../rentals_united/entities/season.rb | 11 ++- .../mappers/roomorama_property.rb | 35 +++++++- .../rentals_united/entities/season_spec.rb | 8 +- .../mappers/roomorama_property_spec.rb | 23 ++++- .../suppliers/rentals_united/metadata_spec.rb | 88 +++++++++++++------ 7 files changed, 146 insertions(+), 48 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/availabilities.rb b/apps/workers/suppliers/rentals_united/availabilities.rb index 798b741cd..95061abb0 100644 --- a/apps/workers/suppliers/rentals_united/availabilities.rb +++ b/apps/workers/suppliers/rentals_united/availabilities.rb @@ -15,10 +15,6 @@ def perform identifiers.each do |property_id| synchronisation.start(property_id) do - result = fetch_seasons(property_id) - next result unless result.success? - seasons = result.value - result = fetch_availabilities(property_id) next result unless result.success? availabilities = result.value @@ -42,12 +38,6 @@ def report_error(message) end end - def fetch_seasons(property_id) - report_error("Failed to fetch seasons for property `#{property_id}`") do - importer.fetch_seasons(property_id) - end - end - def fetch_availabilities(property_id) report_error("Failed to fetch availabilities for property `#{property_id}`") do importer.fetch_availabilities(property_id) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index 93942ce27..e8525360b 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -14,6 +14,7 @@ class Metadata # Prevent from publishing results containing error codes below: IGNORABLE_ERROR_CODES = [ + :empty_seasons, :attempt_to_build_archived_property, :attempt_to_build_not_active_property, :security_deposit_not_supported, @@ -66,7 +67,6 @@ def perform next end end - property_sync.finish! end @@ -75,7 +75,11 @@ def sync_properties(properties, location, owners) owner = find_owner(owners, property.owner_id) if owner - result = build_roomorama_property(property, location, owner) + result = fetch_seasons(property.id) + next result unless result.success? + seasons = result.value + + result = build_roomorama_property(property, location, owner, seasons) next if skip?(result, property) property_sync.start(property.id) do @@ -105,11 +109,12 @@ def find_owner(owners, owner_id) owners.find { |o| o.id == owner_id } end - def build_roomorama_property(property, location, owner) + def build_roomorama_property(property, location, owner, seasons) mapper = ::RentalsUnited::Mappers::RoomoramaProperty.new( property, location, - owner + owner, + seasons ) mapper.build_roomorama_property end @@ -151,6 +156,12 @@ def fetch_properties_by_ids(property_ids, location) end end + def fetch_seasons(property_id) + report_error("Failed to fetch seasons for property `#{property_id}`") do + importer.fetch_seasons(property_id) + end + end + def skip?(result, property) if !result.success? && IGNORABLE_ERROR_CODES.include?(result.error.code) property_sync.skip_property(property.id, result.error.code) diff --git a/lib/concierge/suppliers/rentals_united/entities/season.rb b/lib/concierge/suppliers/rentals_united/entities/season.rb index c977257bd..6ad37656a 100644 --- a/lib/concierge/suppliers/rentals_united/entities/season.rb +++ b/lib/concierge/suppliers/rentals_united/entities/season.rb @@ -13,7 +13,16 @@ def initialize(date_from:, date_to:, price:) end def has_price_for_date?(date) - (date_from..date_to).include?(date) + date_range.include?(date) + end + + def number_of_days + date_range.count + end + + private + def date_range + date_from..date_to end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb index c13b2bdb6..79c7f1ea3 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb @@ -4,7 +4,7 @@ module Mappers # # This class is responsible for building a +Roomorama::Property+ object class RoomoramaProperty - attr_reader :ru_property, :location, :owner + attr_reader :ru_property, :location, :owner, :seasons EN_DESCRIPTION_LANG_CODE = "1" CANCELLATION_POLICY = "super_elite" @@ -28,10 +28,12 @@ class RoomoramaProperty # * +ru_property+ [Entities::Property] RU property object # * +location+ [Entities::Location] location object # * +owner+ [Entities::OwnerID] owner object - def initialize(ru_property, location, owner) + # * +seasons+ [Array code, "ids" => ["519688"] }] - ) - }.to change { PropertyRepository.count }.by(0) + worker.perform + }.to change { PropertyRepository.count }.by(1) end - end - it 'doesnt create property with unsuccessful publishing' do - allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.error('fail') } + described_class::IGNORABLE_ERROR_CODES.each do |code| + it "skips property from publishing when there was #{code} error" do + allow_any_instance_of(RentalsUnited::Mappers::RoomoramaProperty) + .to receive(:build_roomorama_property) { Result.error(code) } + + expect { + sync_process = worker.perform + expect(sync_process.stats.get("properties_skipped")).to eq( + [{ "reason" => code, "ids" => ["519688"] }] + ) + }.to change { PropertyRepository.count }.by(0) + end + end + + it 'doesnt create property with unsuccessful publishing' do + allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.error('fail') } - expect { - worker.perform - }.to_not change { PropertyRepository.count } + expect { + worker.perform + }.to_not change { PropertyRepository.count } + end end end end From 52ea83213ff9cb41ddc661859f7505df6f3b90ac Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 28 Sep 2016 00:00:24 +0600 Subject: [PATCH 075/118] RU: remove calendar worker --- .../rentals_united/availabilities.rb | 74 ------------------- .../suppliers/rentals_united/metadata.rb | 25 ++++++- 2 files changed, 24 insertions(+), 75 deletions(-) delete mode 100644 apps/workers/suppliers/rentals_united/availabilities.rb diff --git a/apps/workers/suppliers/rentals_united/availabilities.rb b/apps/workers/suppliers/rentals_united/availabilities.rb deleted file mode 100644 index 95061abb0..000000000 --- a/apps/workers/suppliers/rentals_united/availabilities.rb +++ /dev/null @@ -1,74 +0,0 @@ -module Workers::Suppliers::RentalsUnited - # +Workers::Suppliers::RentalsUnited::Calendar+ - # - # Performs properties availabilities synchronisation with supplier - class Availabilities - attr_reader :synchronisation, :host - - def initialize(host) - @host = host - @synchronisation = Workers::CalendarSynchronisation.new(host) - end - - def perform - identifiers = all_identifiers - - identifiers.each do |property_id| - synchronisation.start(property_id) do - result = fetch_availabilities(property_id) - next result unless result.success? - availabilities = result.value - - mapper = ::RentalsUnited::Mappers::Calendar.new( - property_id, - seasons, - availabilities - ) - mapper.build_calendar - end - end - synchronisation.finish! - end - - private - - def report_error(message) - yield.tap do |result| - augment_context_error(message) unless result.success? - end - end - - def fetch_availabilities(property_id) - report_error("Failed to fetch availabilities for property `#{property_id}`") do - importer.fetch_availabilities(property_id) - end - end - - def all_identifiers - PropertyRepository.from_host(host).only(:identifier).map(&:identifier) - end - - def importer - @importer ||= ::RentalsUnited::Importer.new(credentials) - end - - def credentials - Concierge::Credentials.for(RentalsUnited::Client::SUPPLIER_NAME) - end - - def augment_context_error(message) - message = { - label: 'Synchronisation Failure', - message: message, - backtrace: caller - } - context = Concierge::Context::Message.new(message) - Concierge.context.augment(context) - end - end -end - -Concierge::Announcer.on('availabilities.RentalsUnited') do |host, args| - Workers::Suppliers::RentalsUnited::Availabilities.new(host).perform - Result.new({}) -end diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index e8525360b..2eef0197d 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -82,7 +82,24 @@ def sync_properties(properties, location, owners) result = build_roomorama_property(property, location, owner, seasons) next if skip?(result, property) - property_sync.start(property.id) do + if result.success? + property_sync.start(property.id) { result } + + calendar_sync.start(property.id) do + result = fetch_availabilities(property.id) + next result unless result.success? + availabilities = result.value + + mapper = ::RentalsUnited::Mappers::Calendar.new( + property.id, + seasons, + availabilities + ) + mapper.build_calendar + end + + calendar_sync.finish! + else result end else @@ -162,6 +179,12 @@ def fetch_seasons(property_id) end end + def fetch_availabilities(property_id) + report_error("Failed to fetch availabilities for property `#{property_id}`") do + importer.fetch_availabilities(property_id) + end + end + def skip?(result, property) if !result.success? && IGNORABLE_ERROR_CODES.include?(result.error.code) property_sync.skip_property(property.id, result.error.code) From 996d1c3b8bf737f03ccfb3263f2ffa17fa3bbc92 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 28 Sep 2016 00:08:42 +0600 Subject: [PATCH 076/118] RU: round(2) property prices --- .../mappers/roomorama_property.rb | 6 ++--- .../mappers/roomorama_property_spec.rb | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb index 79c7f1ea3..8f42a92a9 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb @@ -102,9 +102,9 @@ def set_security_deposit!(property) end def set_rates!(property) - property.nightly_rate = avg_price_per_day - property.weekly_rate = avg_price_per_day * 7 - property.monthly_rate = avg_price_per_day * 30 + property.nightly_rate = avg_price_per_day.round(2) + property.weekly_rate = (avg_price_per_day * 7).round(2) + property.monthly_rate = (avg_price_per_day * 30).round(2) end def avg_price_per_day diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb index 7f2714e1f..81769338e 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb @@ -182,6 +182,29 @@ expect(property.monthly_rate).to eq(7500.0) end + context "when given avg price is not integer" do + let(:seasons) do + [ + RentalsUnited::Entities::Season.new( + date_from: Date.parse("2016-09-01"), + date_to: Date.parse("2016-09-04"), + price: 199.99 + ), + RentalsUnited::Entities::Season.new( + date_from: Date.parse("2016-10-01"), + date_to: Date.parse("2016-10-13"), + price: 1000.00 + ), + ] + end + + it "sets rounded property rates" do + expect(property.nightly_rate).to eq(811.76) + expect(property.weekly_rate).to eq(5682.34) + expect(property.monthly_rate).to eq(24352.87) + end + end + context "when property has no security_deposit" do it "sets security_deposit_amount to nil" do ru_property_hash[:security_deposit_type] = "1" From 61aff81f92ace40be476d44bf9fd120ce2060a9d Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 28 Sep 2016 00:17:18 +0600 Subject: [PATCH 077/118] RU: add missing spec --- .../suppliers/rentals_united/importer_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb index c2e1c08a5..266531a32 100644 --- a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -129,4 +129,15 @@ importer.fetch_availabilities(property_id) end end + + describe "#fetch_seasons" do + let(:property_id) { "588788" } + + it "calls fetcher class to load availabilities" do + fetcher_class = RentalsUnited::Commands::SeasonsFetcher + + expect_any_instance_of(fetcher_class).to(receive(:fetch_seasons)) + importer.fetch_seasons(property_id) + end + end end From a71e8aff01730f38c3ddc8705dd7672281c33a36 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 28 Sep 2016 14:52:01 +0600 Subject: [PATCH 078/118] RU: start calendar sync when property was synced --- .../suppliers/rentals_united/metadata.rb | 86 ++++++++++++++----- .../suppliers/rentals_united/metadata_spec.rb | 50 +++++------ 2 files changed, 88 insertions(+), 48 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index 2eef0197d..f874d4ad4 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -12,7 +12,10 @@ module Workers::Suppliers::RentalsUnited class Metadata attr_reader :property_sync, :calendar_sync, :host - # Prevent from publishing results containing error codes below: + # Prevent from publishing property results containing error codes below. + # + # If property sync is skiped because of one of these errors, calendar + # update won't be started too. IGNORABLE_ERROR_CODES = [ :empty_seasons, :attempt_to_build_archived_property, @@ -60,7 +63,9 @@ def perform result = fetch_properties_by_ids(property_ids, location) next unless result.success? - sync_properties(result.value, location, owners) + location_properties = result.value + + sync_metadata_and_calendar(location_properties, location, owners) else message = "Failed to find currency for location with id `#{location.id}`" announce_context_error(message, Result.error(:currency_not_found)) @@ -70,35 +75,31 @@ def perform property_sync.finish! end - def sync_properties(properties, location, owners) + private + # Performs metadata and calendar synchronisation for given properties. + # + # For each property it checks whether property was ever synced and if + # that's true it also starts calendar synchronisation. + # + # Even if current metadata sync is failed, it's possible that property was + # synced before, that's why we still perform update for calendar. + # + # If result has errors which skips publishing properties, calendar update + # won't be started. + def sync_metadata_and_calendar(properties, location, owners) properties.each do |property| + result = fetch_seasons(property.id) + next result unless result.success? + seasons = result.value + owner = find_owner(owners, property.owner_id) if owner - result = fetch_seasons(property.id) - next result unless result.success? - seasons = result.value - result = build_roomorama_property(property, location, owner, seasons) next if skip?(result, property) if result.success? property_sync.start(property.id) { result } - - calendar_sync.start(property.id) do - result = fetch_availabilities(property.id) - next result unless result.success? - availabilities = result.value - - mapper = ::RentalsUnited::Mappers::Calendar.new( - property.id, - seasons, - availabilities - ) - mapper.build_calendar - end - - calendar_sync.finish! else result end @@ -107,10 +108,49 @@ def sync_properties(properties, location, owners) announce_context_error(message, Result.error(:owner_not_found)) next end + + if synced_property?(property.id) + sync_calendar(property.id, seasons) + end end end - private + # Performs calendar (availabilities + seasons) synchronisation for + # given property_id. + def sync_calendar(property_id, seasons) + calendar_sync.start(property_id) do + result = fetch_availabilities(property_id) + next result unless result.success? + availabilities = result.value + + mapper = ::RentalsUnited::Mappers::Calendar.new( + property_id, + seasons, + availabilities + ) + mapper.build_calendar + end + + calendar_sync.finish! + end + + # Checks whether property exists in database or not. + # Even if property was not synced in current synchronisation process, it's + # possible that it was synced before. + # + # Performs query every time without caching identifiers. + # + # We can't cache identifiers because calendar sync should be started right + # after each property sync and not when sync of all properties finished. + # + # (Otherwise we'll lose some data fetched inside the first sync process and + # then we'll need to cache all data in memory so then we can reuse it) + # + # We can switch to the different strategy if memory usage will not be high + # and we'll need to save database queries. + def synced_property?(property_id) + PropertyRepository.from_host(host).identified_by(property_id).count > 0 + end def importer @properties ||= ::RentalsUnited::Importer.new(credentials) diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index 977b59411..8175e9ab9 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -187,31 +187,6 @@ stub_call(:post, url) { [200, {}, stub_data] } end - describe "when there is no owner for property" do - let(:owner) do - double( - id: '550000', - file_name: 'John', - last_name: 'Doe', - empty: 'john.doe@gmail.com', - phone: '3128329138' - ) - end - - it "fails with owner error and continues worker process" do - result = worker.perform - expect(result).to be_kind_of(SyncProcess) - - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to find owner for property id `519688`" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - end - it "fails when #fetch_seasons returns an error and continues worker process" do result = worker.perform expect(result).to be_kind_of(SyncProcess) @@ -241,6 +216,31 @@ ) end + describe "when there is no owner for property" do + let(:owner) do + double( + id: '550000', + file_name: 'John', + last_name: 'Doe', + empty: 'john.doe@gmail.com', + phone: '3128329138' + ) + end + + it "fails with owner error and continues worker process" do + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + + event = Concierge.context.events.last.to_h + expect(event[:label]).to eq("Synchronisation Failure") + expect(event[:message]).to eq( + "Failed to find owner for property id `519688`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + it "calls synchronisation block for every property id" do expected_property_ids = ["519688"] From 242fba7de4882128dafb426d0aa0e4ee9a35bdb9 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 29 Sep 2016 09:41:05 +0600 Subject: [PATCH 079/118] RU: changes from fetching properties by locations to fetching by owners --- .../suppliers/rentals_united/metadata.rb | 123 ++-- .../commands/location_ids_fetcher.rb | 41 -- .../rentals_united/commands/owner_fetcher.rb | 60 ++ .../rentals_united/commands/owners_fetcher.rb | 41 -- .../commands/properties_collection_fetcher.rb | 61 ++ .../commands/property_ids_fetcher.rb | 62 -- .../entities/properties_collection.rb | 34 + .../suppliers/rentals_united/importer.rb | 55 +- .../mappers/properties_collection.rb | 36 + .../rentals_united/payload_builder.rb | 20 +- .../templates/location_ids_fetch.xml.erb | 6 - ...ners_fetch.xml.erb => owner_fetch.xml.erb} | 5 +- ...rb => properties_collection_fetch.xml.erb} | 7 +- .../location_ids/empty_list.xml | 4 - .../location_ids/error_status.xml | 3 - .../location_ids/multiple_locations.xml | 8 - .../location_ids/one_location.xml | 6 - .../rentals_united/owner/error_status.xml | 3 + .../rentals_united/owner/not_found.xml | 1 + spec/fixtures/rentals_united/owner/owner.xml | 10 + spec/fixtures/rentals_united/owners/empty.xml | 4 - .../rentals_united/owners/error_status.xml | 3 - .../fixtures/rentals_united/owners/owners.xml | 19 - .../empty_list.xml | 4 +- .../properties_collection/error_status.xml | 3 + .../multiple_properties.xml | 4 +- .../one_property.xml | 4 +- .../property_ids/error_status.xml | 3 - ..._fetcher_spec.rb => owner_fetcher_spec.rb} | 46 +- ... => properties_collection_fetcher_spec.rb} | 65 +- .../commands/property_ids_fetcher_spec.rb | 100 --- .../entities/properties_collection_spec.rb | 58 ++ .../suppliers/rentals_united/importer_spec.rb | 82 +-- .../rentals_united/payload_builder_spec.rb | 49 +- .../suppliers/rentals_united/metadata_spec.rb | 626 +++++++++++------- 35 files changed, 814 insertions(+), 842 deletions(-) delete mode 100644 lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/owner_fetcher.rb delete mode 100644 lib/concierge/suppliers/rentals_united/commands/owners_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher.rb delete mode 100644 lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/properties_collection.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/properties_collection.rb delete mode 100644 lib/concierge/suppliers/rentals_united/templates/location_ids_fetch.xml.erb rename lib/concierge/suppliers/rentals_united/templates/{owners_fetch.xml.erb => owner_fetch.xml.erb} (61%) rename lib/concierge/suppliers/rentals_united/templates/{property_ids_fetch.xml.erb => properties_collection_fetch.xml.erb} (67%) delete mode 100644 spec/fixtures/rentals_united/location_ids/empty_list.xml delete mode 100644 spec/fixtures/rentals_united/location_ids/error_status.xml delete mode 100644 spec/fixtures/rentals_united/location_ids/multiple_locations.xml delete mode 100644 spec/fixtures/rentals_united/location_ids/one_location.xml create mode 100644 spec/fixtures/rentals_united/owner/error_status.xml create mode 100644 spec/fixtures/rentals_united/owner/not_found.xml create mode 100644 spec/fixtures/rentals_united/owner/owner.xml delete mode 100644 spec/fixtures/rentals_united/owners/empty.xml delete mode 100644 spec/fixtures/rentals_united/owners/error_status.xml delete mode 100644 spec/fixtures/rentals_united/owners/owners.xml rename spec/fixtures/rentals_united/{property_ids => properties_collection}/empty_list.xml (55%) create mode 100644 spec/fixtures/rentals_united/properties_collection/error_status.xml rename spec/fixtures/rentals_united/{property_ids => properties_collection}/multiple_properties.xml (94%) rename spec/fixtures/rentals_united/{property_ids => properties_collection}/one_property.xml (89%) delete mode 100644 spec/fixtures/rentals_united/property_ids/error_status.xml rename spec/lib/concierge/suppliers/rentals_united/commands/{owners_fetcher_spec.rb => owner_fetcher_spec.rb} (65%) rename spec/lib/concierge/suppliers/rentals_united/commands/{location_ids_fetcher_spec.rb => properties_collection_fetcher_spec.rb} (51%) delete mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/entities/properties_collection_spec.rb diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index f874d4ad4..80364a3a9 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -31,90 +31,61 @@ def initialize(host) end def perform - result = property_sync.new_context { fetch_location_ids } + result = fetch_owner(host.identifier) return unless result.success? + owner = result.value - location_ids = result.value - - result = fetch_locations(location_ids) + result = fetch_properties_collection_for_owner(owner.id) return unless result.success? + properties_collection = result.value + result = fetch_locations(properties_collection.location_ids) + return unless result.success? locations = result.value result = fetch_location_currencies return unless result.success? - currencies = result.value - result = fetch_owners - return unless result.success? + properties_collection.each_entry do |property_id, location_id| + location = locations.find { |l| l.id == location_id } - owners = result.value + unless location + message = "Failed to find location with id `#{location_id}`" + announce_context_error(message, Result.error(:location_not_found)) + next + end - locations.each do |location| location.currency = currencies[location.id] - if location.currency - result = property_sync.new_context { fetch_property_ids(location.id) } - next unless result.success? - - property_ids = result.value - - result = fetch_properties_by_ids(property_ids, location) - next unless result.success? - - location_properties = result.value - - sync_metadata_and_calendar(location_properties, location, owners) - else - message = "Failed to find currency for location with id `#{location.id}`" + unless location.currency + message = "Failed to find currency for location with id `#{location_id}`" announce_context_error(message, Result.error(:currency_not_found)) next end - end - property_sync.finish! - end - private - # Performs metadata and calendar synchronisation for given properties. - # - # For each property it checks whether property was ever synced and if - # that's true it also starts calendar synchronisation. - # - # Even if current metadata sync is failed, it's possible that property was - # synced before, that's why we still perform update for calendar. - # - # If result has errors which skips publishing properties, calendar update - # won't be started. - def sync_metadata_and_calendar(properties, location, owners) - properties.each do |property| - result = fetch_seasons(property.id) - next result unless result.success? + result = fetch_property(property_id) + next unless result.success? + property = result.value + + result = fetch_seasons(property_id) + next unless result.success? seasons = result.value - owner = find_owner(owners, property.owner_id) + result = build_roomorama_property(property, location, owner, seasons) + next if skip?(result, property) - if owner - result = build_roomorama_property(property, location, owner, seasons) - next if skip?(result, property) + property_sync.start(property_id) { result } if result.success? - if result.success? - property_sync.start(property.id) { result } - else - result - end - else - message = "Failed to find owner for property id `#{property.id}`" - announce_context_error(message, Result.error(:owner_not_found)) - next - end - - if synced_property?(property.id) - sync_calendar(property.id, seasons) + if synced_property?(property_id) + sync_calendar(property_id, seasons) end end + + property_sync.finish! end + private # Performs calendar (availabilities + seasons) synchronisation for # given property_id. def sync_calendar(property_id, seasons) @@ -162,10 +133,6 @@ def credentials ) end - def find_owner(owners, owner_id) - owners.find { |o| o.id == owner_id } - end - def build_roomorama_property(property, location, owner, seasons) mapper = ::RentalsUnited::Mappers::RoomoramaProperty.new( property, @@ -176,14 +143,20 @@ def build_roomorama_property(property, location, owner, seasons) mapper.build_roomorama_property end - def fetch_location_ids - announce_error("Failed to fetch location ids") do - importer.fetch_location_ids + def fetch_owner(owner_id) + announce_error("Failed to fetch owner with owner_id `#{owner_id}`") do + importer.fetch_owner(owner_id) + end + end + + def fetch_properties_collection_for_owner(owner_id) + announce_error("Failed to fetch property ids collection for owner `#{owner_id}`") do + importer.fetch_properties_collection_for_owner(owner_id) end end def fetch_locations(location_ids) - announce_error("Failed to fetch locations") do + announce_error("Failed to fetch locations with ids `#{location_ids}`") do importer.fetch_locations(location_ids) end end @@ -194,22 +167,10 @@ def fetch_location_currencies end end - def fetch_owners - announce_error("Failed to fetch owners") do - importer.fetch_owners - end - end - - def fetch_property_ids(location_id) - announce_error("Failed to fetch property ids for location `#{location_id}`") do - importer.fetch_property_ids(location_id) - end - end - - def fetch_properties_by_ids(property_ids, location) - message = "Failed to fetch properties for ids `#{property_ids}` in location `#{location.id}`" + def fetch_property(property_id) + message = "Failed to fetch property with property_id `#{property_id}`" announce_error(message) do - importer.fetch_properties_by_ids(property_ids) + importer.fetch_property(property_id) end end diff --git a/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb deleted file mode 100644 index ccf2b2273..000000000 --- a/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher.rb +++ /dev/null @@ -1,41 +0,0 @@ -module RentalsUnited - module Commands - # +RentalsUnited::Commands::LocationIdsFetcher+ - # - # This class is responsible for wrapping the logic related to fetching - # location ids from RentalsUnited - class LocationIdsFetcher < BaseFetcher - attr_reader :location - - ROOT_TAG = "Pull_ListCitiesProps_RS" - - # Retrieves location ids with active properties. - # - # Locations without active properties are ignored. - # - # Returns a +Result+ wrapping +Array+ of +String+ location ids - # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_location_ids - payload = payload_builder.build_location_ids_fetch_payload - result = api_call(payload) - - return result unless result.success? - - result_hash = response_parser.to_hash(result.value.body) - - if valid_status?(result_hash, ROOT_TAG) - Result.new(parse_location_ids(result_hash)) - else - error_result(result_hash, ROOT_TAG) - end - end - - private - def parse_location_ids(hash) - locations = hash.get("#{ROOT_TAG}.CitiesProps.CityProps") - - Array(locations).map { |location| location.attributes["LocationID"] } - end - end - end -end diff --git a/lib/concierge/suppliers/rentals_united/commands/owner_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/owner_fetcher.rb new file mode 100644 index 000000000..0c7c3334a --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/owner_fetcher.rb @@ -0,0 +1,60 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::OwnerFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # owner from RentalsUnited + class OwnerFetcher < BaseFetcher + ROOT_TAG = "Pull_GetOwnerDetails_RS" + + attr_reader :owner_id + + # Initialize +OwnerFetcher+ command. + # + # Arguments + # + # * +credentials+ + # * +owner_id+ [String] + # + # Usage: + # + # RentalsUnited::Commands::OwnerFetcher.new(credentials, owner_id) + def initialize(credentials, owner_id) + super(credentials) + + @owner_id = owner_id + end + + # Retrieves owner. + # + # Returns a +Result+ wrapping +Entities::Owner+ + # Returns a +Result+ with +Result::Error+ when operation fails + def fetch_owner + payload = payload_builder.build_owner_fetch_payload(owner_id) + result = api_call(payload) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_owner(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_owner(hash) + owner_hash = hash.get("#{ROOT_TAG}.Owner") + + if owner_hash + mapper = RentalsUnited::Mappers::Owner.new(owner_hash) + mapper.build_owner + else + Result.error(:owner_not_found) + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/owners_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/owners_fetcher.rb deleted file mode 100644 index a535dd2ab..000000000 --- a/lib/concierge/suppliers/rentals_united/commands/owners_fetcher.rb +++ /dev/null @@ -1,41 +0,0 @@ -module RentalsUnited - module Commands - # +RentalsUnited::Commands::OwnersFetcher+ - # - # This class is responsible for wrapping the logic related to fetching - # owners from RentalsUnited - class OwnersFetcher < BaseFetcher - ROOT_TAG = "Pull_ListAllOwners_RS" - - # Retrieves owners. - # - # Returns a +Result+ wrapping +Array+ of +Entities::Owner+ - # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_owners - payload = payload_builder.build_owners_fetch_payload - result = api_call(payload) - - return result unless result.success? - - result_hash = response_parser.to_hash(result.value.body) - - if valid_status?(result_hash, ROOT_TAG) - Result.new(build_owners(result_hash)) - else - error_result(result_hash, ROOT_TAG) - end - end - - private - def build_owners(hash) - owners = hash.get("#{ROOT_TAG}.Owners.Owner") - - Array(owners).map do |owner_hash| - safe_hash = Concierge::SafeAccessHash.new(owner_hash) - mapper = RentalsUnited::Mappers::Owner.new(safe_hash) - mapper.build_owner - end - end - end - end -end diff --git a/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher.rb new file mode 100644 index 000000000..cd94ac5d0 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher.rb @@ -0,0 +1,61 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::PropertiesCollectionFetcher+ + # + # This class is responsible for wrapping the logic related to fetching + # properties collection for a given owner from RentalsUnited + class PropertiesCollectionFetcher < BaseFetcher + ROOT_TAG = "Pull_ListOwnerProp_RS" + + attr_reader :owner_id + + # Initialize +PropertiesCollectionFetcher+ command. + # + # Arguments + # + # * +credentials+ + # * +owner_id+ [String] + # + # Usage: + # + # RentalsUnited::Commands::PropertiesCollectionFetcher.new( + # credentials, + # owner_id + # ) + def initialize(credentials, owner_id) + super(credentials) + + @owner_id = owner_id + end + + # Retrieves properties collection. + # + # Returns a +Result+ wrapping +Entities::PropertiesCollection+ + # Returns a +Result+ with +Result::Error+ when operation fails + def fetch_properties_collection_for_owner + payload = payload_builder.build_properties_collection_fetch_payload( + owner_id + ) + result = api_call(payload) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_properties_collection(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_properties_collection(hash) + properties = Array(hash.get("#{ROOT_TAG}.Properties.Property")) + + mapper = RentalsUnited::Mappers::PropertiesCollection.new(properties) + mapper.build_properties_collection + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb deleted file mode 100644 index 0dda47bc0..000000000 --- a/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher.rb +++ /dev/null @@ -1,62 +0,0 @@ -module RentalsUnited - module Commands - # +RentalsUnited::Commands::PropertyIdsFetcher+ - # - # This class is responsible for wrapping the logic related to fetching - # property ids from RentalsUnited, parsing the response, and building - # +Result+ object. - class PropertyIdsFetcher < BaseFetcher - attr_reader :location_id - - ROOT_TAG = "Pull_ListProp_RS" - - # Initialize +PropertyIdsFetcher+ command. - # - # Arguments - # - # * +credentials+ - # * +location_id+ - # - # Usage: - # - # RentalsUnited::Commands::PropertyIdsFetcher.new( - # credentials, - # location_id - # ) - def initialize(credentials, location_id) - super(credentials) - - @location_id = location_id - end - - # Retrieves property ids - # - # IDs of properties which are no longer available will be filtered out. - # - # Returns a +Result+ wrapping +Array+ of +String+ property ids - # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_property_ids - payload = payload_builder.build_property_ids_fetch_payload(location_id) - result = api_call(payload) - - return result unless result.success? - - result_hash = response_parser.to_hash(result.value.body) - - if valid_status?(result_hash, ROOT_TAG) - Result.new(build_property_ids(result_hash)) - else - error_result(result_hash, ROOT_TAG) - end - end - - private - def build_property_ids(hash) - properties = hash.get("#{ROOT_TAG}.Properties.Property") - return [] unless properties - - Array(properties).map { |property| property["ID"] } - end - end - end -end diff --git a/lib/concierge/suppliers/rentals_united/entities/properties_collection.rb b/lib/concierge/suppliers/rentals_united/entities/properties_collection.rb new file mode 100644 index 000000000..0c0762d58 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/properties_collection.rb @@ -0,0 +1,34 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::PropertiesCollection+ + # + # This entity represents a properties collection object. + class PropertiesCollection + def initialize(entries) + @entries = entries + end + + def each_entry + if block_given? + @entries.each do |e| + yield(e[:property_id], e[:location_id]) + end + else + return @entries.each + end + end + + def size + @entries.size + end + + def property_ids + @entries.map { |e| e[:property_id] } + end + + def location_ids + @entries.map { |e| e[:location_id] }.uniq + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/importer.rb b/lib/concierge/suppliers/rentals_united/importer.rb index e68bb84ad..daadca580 100644 --- a/lib/concierge/suppliers/rentals_united/importer.rb +++ b/lib/concierge/suppliers/rentals_united/importer.rb @@ -14,15 +14,18 @@ def initialize(credentials) @credentials = credentials end - # Retrieves location ids with active properties. + # Retrieves properties collection for a given owner by +owner_id+ # - # Locations without active properties will be filtered out. + # Properties which are no longer available will be filtered out. # - # Returns a +Result+ wrapping +Array+ of +String+ location ids + # Returns a +Result+ wrapping +Entities::PropertiesCollection+ object # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_location_ids - fetcher = Commands::LocationIdsFetcher.new(credentials) - fetcher.fetch_location_ids + def fetch_properties_collection_for_owner(owner_id) + fetcher = Commands::PropertiesCollectionFetcher.new( + credentials, + owner_id + ) + fetcher.fetch_properties_collection_for_owner end # Retrieves locations by given location_ids. @@ -47,20 +50,6 @@ def fetch_location_currencies fetcher.fetch_location_currencies end - # Retrieves property ids by location id. - # - # IDs of properties which are no longer available will be filtered out. - # - # Returns a +Result+ wrapping +Array+ of +String+ property ids - # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_property_ids(location_id) - properties_fetcher = Commands::PropertyIdsFetcher.new( - credentials, - location_id - ) - properties_fetcher.fetch_property_ids - end - # Retrieves property by its id. # # Returns a +Result+ wrapping +Entities::Property+ object @@ -73,29 +62,13 @@ def fetch_property(property_id) property_fetcher.fetch_property end - # Retrieves properties by given ids - # - # Returns a +Result+ wrapping +Array+ of +Entities::Property+ objects - # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_properties_by_ids(property_ids) - properties = property_ids.map do |property_id| - result = fetch_property(property_id) - - return result unless result.success? - - result.value - end.compact - - Result.new(properties) - end - - # Retrieves owners + # Retrieves owner by id # - # Returns a +Result+ wrapping +Array+ of +Entities::Owner+ objects + # Returns a +Result+ wrapping +Entities::Owner+ object # Returns a +Result+ with +Result::Error+ when operation fails - def fetch_owners - fetcher = Commands::OwnersFetcher.new(credentials) - fetcher.fetch_owners + def fetch_owner(owner_id) + fetcher = Commands::OwnerFetcher.new(credentials, owner_id) + fetcher.fetch_owner end # Retrieves availabilities for property by its id. diff --git a/lib/concierge/suppliers/rentals_united/mappers/properties_collection.rb b/lib/concierge/suppliers/rentals_united/mappers/properties_collection.rb new file mode 100644 index 000000000..96c0aae97 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/properties_collection.rb @@ -0,0 +1,36 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::propertiescollection+ + # + # This class is responsible for building a properties collection object + class PropertiesCollection + attr_reader :properties + + # Initialize +RentalsUnited::Mappers::PropertiesCollection+ mapper + # + # Arguments: + # + # * +properties+ [Array] array with property collection hashes + def initialize(properties) + @properties = properties + end + + def build_properties_collection + entries = build_entries + + Entities::PropertiesCollection.new(entries) + end + + private + def build_entries + properties.map do |hash| + safe_hash = Concierge::SafeAccessHash.new(hash) + { + property_id: safe_hash.get("ID"), + location_id: safe_hash.get("DetailedLocationID") + } + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index 39a927af0..bf968e9c3 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -13,17 +13,12 @@ def initialize(credentials) @credentials = credentials end - def build_property_ids_fetch_payload(location_id) + def build_properties_collection_fetch_payload(owner_id) template_locals = { credentials: credentials, - location_id: location_id + owner_id: owner_id } - render(:property_ids_fetch, template_locals) - end - - def build_location_ids_fetch_payload - template_locals = { credentials: credentials } - render(:location_ids_fetch, template_locals) + render(:properties_collection_fetch, template_locals) end def build_locations_fetch_payload @@ -44,9 +39,12 @@ def build_property_fetch_payload(property_id) render(:property_fetch, template_locals) end - def build_owners_fetch_payload - template_locals = { credentials: credentials } - render(:owners_fetch, template_locals) + def build_owner_fetch_payload(owner_id) + template_locals = { + credentials: credentials, + owner_id: owner_id + } + render(:owner_fetch, template_locals) end def build_availabilities_fetch_payload(property_id, date_from, date_to) diff --git a/lib/concierge/suppliers/rentals_united/templates/location_ids_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/location_ids_fetch.xml.erb deleted file mode 100644 index 7b7ca3948..000000000 --- a/lib/concierge/suppliers/rentals_united/templates/location_ids_fetch.xml.erb +++ /dev/null @@ -1,6 +0,0 @@ - - - <%= credentials.username %> - <%= credentials.password %> - - diff --git a/lib/concierge/suppliers/rentals_united/templates/owners_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/owner_fetch.xml.erb similarity index 61% rename from lib/concierge/suppliers/rentals_united/templates/owners_fetch.xml.erb rename to lib/concierge/suppliers/rentals_united/templates/owner_fetch.xml.erb index c87b41951..77a321414 100644 --- a/lib/concierge/suppliers/rentals_united/templates/owners_fetch.xml.erb +++ b/lib/concierge/suppliers/rentals_united/templates/owner_fetch.xml.erb @@ -1,6 +1,7 @@ - + <%= credentials.username %> <%= credentials.password %> - + <%= owner_id %> + diff --git a/lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/properties_collection_fetch.xml.erb similarity index 67% rename from lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb rename to lib/concierge/suppliers/rentals_united/templates/properties_collection_fetch.xml.erb index a6d825667..84d40b8d9 100644 --- a/lib/concierge/suppliers/rentals_united/templates/property_ids_fetch.xml.erb +++ b/lib/concierge/suppliers/rentals_united/templates/properties_collection_fetch.xml.erb @@ -1,9 +1,8 @@ - + <%= credentials.username %> <%= credentials.password %> - - <%= location_id %> + <%= owner_id %> false - + diff --git a/spec/fixtures/rentals_united/location_ids/empty_list.xml b/spec/fixtures/rentals_united/location_ids/empty_list.xml deleted file mode 100644 index b6adb503b..000000000 --- a/spec/fixtures/rentals_united/location_ids/empty_list.xml +++ /dev/null @@ -1,4 +0,0 @@ - - Success - - diff --git a/spec/fixtures/rentals_united/location_ids/error_status.xml b/spec/fixtures/rentals_united/location_ids/error_status.xml deleted file mode 100644 index 0c04ff0de..000000000 --- a/spec/fixtures/rentals_united/location_ids/error_status.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Test Error - diff --git a/spec/fixtures/rentals_united/location_ids/multiple_locations.xml b/spec/fixtures/rentals_united/location_ids/multiple_locations.xml deleted file mode 100644 index ea1c333e8..000000000 --- a/spec/fixtures/rentals_united/location_ids/multiple_locations.xml +++ /dev/null @@ -1,8 +0,0 @@ - - Success - - 1 - 1 - 2 - - diff --git a/spec/fixtures/rentals_united/location_ids/one_location.xml b/spec/fixtures/rentals_united/location_ids/one_location.xml deleted file mode 100644 index 477924481..000000000 --- a/spec/fixtures/rentals_united/location_ids/one_location.xml +++ /dev/null @@ -1,6 +0,0 @@ - - Success - - 1 - - diff --git a/spec/fixtures/rentals_united/owner/error_status.xml b/spec/fixtures/rentals_united/owner/error_status.xml new file mode 100644 index 000000000..ef296d223 --- /dev/null +++ b/spec/fixtures/rentals_united/owner/error_status.xml @@ -0,0 +1,3 @@ + + Test Error + diff --git a/spec/fixtures/rentals_united/owner/not_found.xml b/spec/fixtures/rentals_united/owner/not_found.xml new file mode 100644 index 000000000..9d7ec7a7b --- /dev/null +++ b/spec/fixtures/rentals_united/owner/not_found.xml @@ -0,0 +1 @@ + diff --git a/spec/fixtures/rentals_united/owner/owner.xml b/spec/fixtures/rentals_united/owner/owner.xml new file mode 100644 index 000000000..343283e79 --- /dev/null +++ b/spec/fixtures/rentals_united/owner/owner.xml @@ -0,0 +1,10 @@ + + Success + + Foo + Bar + RU Test + foobar@gmail.com + 519461272 + + diff --git a/spec/fixtures/rentals_united/owners/empty.xml b/spec/fixtures/rentals_united/owners/empty.xml deleted file mode 100644 index 488e424e7..000000000 --- a/spec/fixtures/rentals_united/owners/empty.xml +++ /dev/null @@ -1,4 +0,0 @@ - - Success - - diff --git a/spec/fixtures/rentals_united/owners/error_status.xml b/spec/fixtures/rentals_united/owners/error_status.xml deleted file mode 100644 index 60223bc04..000000000 --- a/spec/fixtures/rentals_united/owners/error_status.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Test Error - diff --git a/spec/fixtures/rentals_united/owners/owners.xml b/spec/fixtures/rentals_united/owners/owners.xml deleted file mode 100644 index 591fe8bf9..000000000 --- a/spec/fixtures/rentals_united/owners/owners.xml +++ /dev/null @@ -1,19 +0,0 @@ - - Success - - - Foo - Bar - RU Test - foobar@gmail.com - 519461272 - - - John - Doe - - john.doe@gmail.com - 6092932393 - - - diff --git a/spec/fixtures/rentals_united/property_ids/empty_list.xml b/spec/fixtures/rentals_united/properties_collection/empty_list.xml similarity index 55% rename from spec/fixtures/rentals_united/property_ids/empty_list.xml rename to spec/fixtures/rentals_united/properties_collection/empty_list.xml index f3b4714ad..4bbdbfeb8 100644 --- a/spec/fixtures/rentals_united/property_ids/empty_list.xml +++ b/spec/fixtures/rentals_united/properties_collection/empty_list.xml @@ -1,4 +1,4 @@ - + Success - + diff --git a/spec/fixtures/rentals_united/properties_collection/error_status.xml b/spec/fixtures/rentals_united/properties_collection/error_status.xml new file mode 100644 index 000000000..691dafbb3 --- /dev/null +++ b/spec/fixtures/rentals_united/properties_collection/error_status.xml @@ -0,0 +1,3 @@ + + Test Error + diff --git a/spec/fixtures/rentals_united/property_ids/multiple_properties.xml b/spec/fixtures/rentals_united/properties_collection/multiple_properties.xml similarity index 94% rename from spec/fixtures/rentals_united/property_ids/multiple_properties.xml rename to spec/fixtures/rentals_united/properties_collection/multiple_properties.xml index bd65e7063..8f47efc0a 100644 --- a/spec/fixtures/rentals_united/property_ids/multiple_properties.xml +++ b/spec/fixtures/rentals_united/properties_collection/multiple_properties.xml @@ -1,4 +1,4 @@ - + Success @@ -24,4 +24,4 @@ 0 - + diff --git a/spec/fixtures/rentals_united/property_ids/one_property.xml b/spec/fixtures/rentals_united/properties_collection/one_property.xml similarity index 89% rename from spec/fixtures/rentals_united/property_ids/one_property.xml rename to spec/fixtures/rentals_united/properties_collection/one_property.xml index 85ba4f40a..77bff5537 100644 --- a/spec/fixtures/rentals_united/property_ids/one_property.xml +++ b/spec/fixtures/rentals_united/properties_collection/one_property.xml @@ -1,4 +1,4 @@ - + Success @@ -13,4 +13,4 @@ 0 - + diff --git a/spec/fixtures/rentals_united/property_ids/error_status.xml b/spec/fixtures/rentals_united/property_ids/error_status.xml deleted file mode 100644 index 328c23348..000000000 --- a/spec/fixtures/rentals_united/property_ids/error_status.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Test Error - diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/owners_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/owner_fetcher_spec.rb similarity index 65% rename from spec/lib/concierge/suppliers/rentals_united/commands/owners_fetcher_spec.rb rename to spec/lib/concierge/suppliers/rentals_united/commands/owner_fetcher_spec.rb index 2b4e1133c..5a34dcce7 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/owners_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/owner_fetcher_spec.rb @@ -1,47 +1,45 @@ require "spec_helper" -RSpec.describe RentalsUnited::Commands::OwnersFetcher do +RSpec.describe RentalsUnited::Commands::OwnerFetcher do include Support::HTTPStubbing include Support::Fixtures let(:credentials) { Concierge::Credentials.for("rentals_united") } - let(:subject) { described_class.new(credentials) } + let(:owner_id) { '123' } + let(:subject) { described_class.new(credentials, owner_id) } let(:url) { credentials.url } - it "fetches owners" do - stub_data = read_fixture("rentals_united/owners/owners.xml") + it "fetches owner" do + stub_data = read_fixture("rentals_united/owner/owner.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_owners + result = subject.fetch_owner expect(result).to be_success - owners = result.value - expect(owners).to all(be_kind_of(RentalsUnited::Entities::Owner)) - expect(owners.size).to eq(2) - - expected_owner_ids = %w(427698 419680) - expected_owner_ids.each do |owner_id| - expect(owners.map(&:id).include?(owner_id)).to be true - end + owner = result.value + expect(owner).to be_kind_of(RentalsUnited::Entities::Owner) + expect(owner.id).to eq("419680") + expect(owner.first_name).to eq("Foo") + expect(owner.last_name).to eq("Bar") + expect(owner.email).to eq("foobar@gmail.com") + expect(owner.phone).to eq("519461272") end - it "returns [] when there is no owners" do - stub_data = read_fixture("rentals_united/owners/empty.xml") + it "returns error when there is no requested owner" do + stub_data = read_fixture("rentals_united/owner/not_found.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_owners - expect(result).to be_success - - owners = result.value - expect(owners).to eq([]) + result = subject.fetch_owner + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) end context "when response from the api has error status" do it "returns a result with an appropriate error" do - stub_data = read_fixture("rentals_united/owners/error_status.xml") + stub_data = read_fixture("rentals_united/owner/error_status.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_owners + result = subject.fetch_owner expect(result).not_to be_success expect(result.error.code).to eq("9999") @@ -60,7 +58,7 @@ stub_data = read_fixture("rentals_united/bad_xml.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_owners + result = subject.fetch_owner expect(result).not_to be_success expect(result.error.code).to eq(:unrecognised_response) @@ -78,7 +76,7 @@ it "returns a result with an appropriate error" do stub_call(:post, url) { raise Faraday::TimeoutError } - result = subject.fetch_owners + result = subject.fetch_owner expect(result).not_to be_success expect(result.error.code).to eq :connection_timeout diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher_spec.rb similarity index 51% rename from spec/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher_spec.rb rename to spec/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher_spec.rb index 948f4d840..9d117d08e 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/location_ids_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher_spec.rb @@ -1,57 +1,68 @@ require "spec_helper" -RSpec.describe RentalsUnited::Commands::LocationIdsFetcher do +RSpec.describe RentalsUnited::Commands::PropertiesCollectionFetcher do include Support::HTTPStubbing include Support::Fixtures let(:credentials) { Concierge::Credentials.for("rentals_united") } - let(:subject) { described_class.new(credentials) } + let(:owner_id) { "1234" } + let(:subject) { described_class.new(credentials, owner_id) } let(:url) { credentials.url } - let(:expected_locations) { ['1505', '2503', '1144'] } - it "returns an empty array when there is no active properties" do - stub_data = read_fixture("rentals_united/location_ids/empty_list.xml") + it "returns an empty collection when there is no properties in location" do + stub_data = read_fixture("rentals_united/properties_collection/empty_list.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_location_ids + result = subject.fetch_properties_collection_for_owner expect(result).to be_success - expect(result.value).to eq([]) + + collection = result.value + expect(collection).to be_kind_of( + RentalsUnited::Entities::PropertiesCollection + ) + expect(collection.size).to eq(0) + expect(collection.property_ids).to eq([]) + expect(collection.location_ids).to eq([]) end - it "returns array with location id when there is one location" do - stub_data = read_fixture("rentals_united/location_ids/one_location.xml") + it "returns collection when there is only one property" do + stub_data = read_fixture("rentals_united/properties_collection/one_property.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_location_ids + result = subject.fetch_properties_collection_for_owner expect(result).to be_success - expect(result.value).to be_kind_of(Array) - expect(result.value.size).to eq(1) - expect(result.value).to all(be_kind_of(String)) - location_id = result.value.first - expect(location_id).to eq("1505") + collection = result.value + expect(collection).to be_kind_of( + RentalsUnited::Entities::PropertiesCollection + ) + expect(collection.size).to eq(1) + expect(collection.property_ids).to eq(["519688"]) + expect(collection.location_ids).to eq(["24958"]) end - it "returns multiple location ids" do - stub_data = read_fixture("rentals_united/location_ids/multiple_locations.xml") + it "returns collection with multiple objects" do + stub_data = read_fixture("rentals_united/properties_collection/multiple_properties.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_location_ids + result = subject.fetch_properties_collection_for_owner expect(result).to be_success - location_ids = result.value - expect(location_ids).to be_kind_of(Array) - expect(location_ids.size).to eq(3) - expect(location_ids).to all(be_kind_of(String)) - expect(location_ids.sort).to eq(expected_locations.sort) + collection = result.value + expect(collection).to be_kind_of( + RentalsUnited::Entities::PropertiesCollection + ) + expect(collection.size).to eq(2) + expect(collection.property_ids).to eq(["519688", "519689"]) + expect(collection.location_ids).to eq(["24958"]) end context "when response from the api has error status" do it "returns a result with an appropriate error" do - stub_data = read_fixture("rentals_united/location_ids/error_status.xml") + stub_data = read_fixture("rentals_united/properties_collection/error_status.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_location_ids + result = subject.fetch_properties_collection_for_owner expect(result).not_to be_success expect(result.error.code).to eq("9999") @@ -70,7 +81,7 @@ stub_data = read_fixture("rentals_united/bad_xml.xml") stub_call(:post, url) { [200, {}, stub_data] } - result = subject.fetch_location_ids + result = subject.fetch_properties_collection_for_owner expect(result).not_to be_success expect(result.error.code).to eq(:unrecognised_response) @@ -88,7 +99,7 @@ it "returns a result with an appropriate error" do stub_call(:post, url) { raise Faraday::TimeoutError } - result = subject.fetch_location_ids + result = subject.fetch_properties_collection_for_owner expect(result).not_to be_success expect(result.error.code).to eq :connection_timeout diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb deleted file mode 100644 index 3b5269d68..000000000 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_ids_fetcher_spec.rb +++ /dev/null @@ -1,100 +0,0 @@ -require "spec_helper" - -RSpec.describe RentalsUnited::Commands::PropertyIdsFetcher do - include Support::HTTPStubbing - include Support::Fixtures - - let(:credentials) { Concierge::Credentials.for("rentals_united") } - let(:location_id) { "1234" } - let(:subject) { described_class.new(credentials, location_id) } - let(:url) { credentials.url } - - it "returns an empty array when there is no properties in location" do - stub_data = read_fixture("rentals_united/property_ids/empty_list.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property_ids - expect(result).to be_success - expect(result.value).to eq([]) - end - - it "returns property idwhen there is only one property" do - stub_data = read_fixture("rentals_united/property_ids/one_property.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property_ids - expect(result).to be_success - expect(result.value).to be_kind_of(Array) - expect(result.value.size).to eq(1) - expect(result.value).to all(be_kind_of(String)) - - property_id = result.value.first - expect(property_id).to eq("519688") - end - - it "returns multiple city objects" do - stub_data = read_fixture("rentals_united/property_ids/multiple_properties.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property_ids - expect(result).to be_success - - properties = result.value - expect(properties).to be_kind_of(Array) - expect(properties.size).to eq(2) - expect(properties).to all(be_kind_of(String)) - expect(properties).to eq(["519688", "519689"]) - end - - context "when response from the api has error status" do - it "returns a result with an appropriate error" do - stub_data = read_fixture("rentals_united/property_ids/error_status.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property_ids - - expect(result).not_to be_success - expect(result.error.code).to eq("9999") - - event = Concierge.context.events.last.to_h - expect(event[:message]).to eq( - "Response indicating the Status with ID `9999`, and description ``" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - end - - context "when response from the api is not well-formed xml" do - it "returns a result with an appropriate error" do - stub_data = read_fixture("rentals_united/bad_xml.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = subject.fetch_property_ids - - expect(result).not_to be_success - expect(result.error.code).to eq(:unrecognised_response) - - event = Concierge.context.events.last.to_h - expect(event[:message]).to eq( - "Error response could not be recognised (no `Status` tag in the response)" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - end - - context "when request fails due to timeout error" do - it "returns a result with an appropriate error" do - stub_call(:post, url) { raise Faraday::TimeoutError } - - result = subject.fetch_property_ids - - expect(result).not_to be_success - expect(result.error.code).to eq :connection_timeout - - event = Concierge.context.events.last.to_h - expect(event[:message]).to eq("timeout") - end - end -end diff --git a/spec/lib/concierge/suppliers/rentals_united/entities/properties_collection_spec.rb b/spec/lib/concierge/suppliers/rentals_united/entities/properties_collection_spec.rb new file mode 100644 index 000000000..a17cbb8ab --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/entities/properties_collection_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +module RentalsUnited + RSpec.describe Entities::PropertiesCollection do + let(:entries) do + [ + { property_id: '10', location_id: '100' }, + { property_id: '20', location_id: '200' } + ] + end + + let(:collection) { described_class.new(entries) } + let(:empty_collection) { described_class.new([]) } + + describe "size" do + it "returns size of collection" do + expect(collection.size).to eq(2) + end + + it "returns size of empty collection" do + expect(empty_collection.size).to eq(0) + end + end + + describe "#property_ids" do + it "returns array with property ids of entries in collection" do + expect(collection.property_ids).to eq(["10", "20"]) + end + + it "returns array with property ids of entries in empty collection" do + expect(empty_collection.property_ids).to eq([]) + end + end + + describe "#location_ids" do + it "returns array with location ids of entries in collection" do + expect(collection.location_ids).to eq(["100", "200"]) + end + + it "returns array with location ids of entries in empty collection" do + expect(empty_collection.location_ids).to eq([]) + end + + context "with duplicate locations" do + let(:entries) do + [ + { property_id: '10', location_id: '100' }, + { property_id: '20', location_id: '100' } + ] + end + + it "returns array with uniq location ids of entries in collection" do + expect(collection.location_ids).to eq(["100"]) + end + end + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb index 266531a32..a87865d10 100644 --- a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -7,12 +7,15 @@ let(:credentials) { Concierge::Credentials.for("rentals_united") } let(:importer) { described_class.new(credentials) } - describe "#fetch_location_ids" do - it "calls fetcher class to load location ids" do - fetcher_class = RentalsUnited::Commands::LocationIdsFetcher + describe "#fetch_properties_collection_for_owner" do + let(:owner_id) { "588788" } - expect_any_instance_of(fetcher_class).to(receive(:fetch_location_ids)) - importer.fetch_location_ids + it "calls fetcher class to load properties collection" do + fetcher_class = RentalsUnited::Commands::PropertiesCollectionFetcher + + expect_any_instance_of(fetcher_class). + to(receive(:fetch_properties_collection_for_owner)) + importer.fetch_properties_collection_for_owner(owner_id) end end @@ -38,17 +41,6 @@ end end - describe "#fetch_property_ids" do - let(:location_id) { "1234" } - - it "calls fetcher class to load property ids" do - fetcher_class = RentalsUnited::Commands::PropertyIdsFetcher - - expect_any_instance_of(fetcher_class).to(receive(:fetch_property_ids)) - importer.fetch_property_ids(location_id) - end - end - describe "#fetch_property" do let(:property_id) { "588788" } @@ -60,62 +52,14 @@ end end - describe "#fetch_properties_by_ids" do - let(:property_ids) { ["222", "333"] } - - it "calls #fetch_property for every property id" do - property_ids.each do |id| - expect_any_instance_of(described_class).to( - receive(:fetch_property).with(id) { Result.new("success") } - ) - end - - result = importer.fetch_properties_by_ids(property_ids) - expect(result).to be_success - end - - it "ignores nil results" do - expect_any_instance_of(described_class).to( - receive(:fetch_property).with("222") { Result.new("success") } - ) - - expect_any_instance_of(described_class).to( - receive(:fetch_property).with("333") { Result.new(nil) } - ) - - result = importer.fetch_properties_by_ids(property_ids) - expect(result).to be_success - expect(result.value.size).to eq(1) - end - - it "returns error if fetching property_id fails" do - expect_any_instance_of(described_class).to( - receive(:fetch_property).with("222") { Result.error("fail") } - ) - - result = importer.fetch_properties_by_ids(property_ids) - expect(result).not_to be_success - end - - it "returns error on fail even if previous fetchers returned success" do - expect_any_instance_of(described_class).to( - receive(:fetch_property).with("222") { Result.new("success") } - ) - expect_any_instance_of(described_class).to( - receive(:fetch_property).with("333") { Result.error("fail") } - ) - - result = importer.fetch_properties_by_ids(property_ids) - expect(result).not_to be_success - end - end + describe "#fetch_owner" do + let(:owner_id) { "123" } - describe "#fetch_owners" do it "calls fetcher class to load owners" do - fetcher_class = RentalsUnited::Commands::OwnersFetcher + fetcher_class = RentalsUnited::Commands::OwnerFetcher - expect_any_instance_of(fetcher_class).to(receive(:fetch_owners)) - importer.fetch_owners + expect_any_instance_of(fetcher_class).to(receive(:fetch_owner)) + importer.fetch_owner(owner_id) end end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index 760c7eac9..d0b000e25 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -4,58 +4,67 @@ let(:credentials) { Concierge::Credentials.for("rentals_united") } let(:builder) { described_class.new(credentials) } - describe '#build_property_ids_fetch_payload' do + describe '#build_properties_collection_fetch_payload' do let(:params) do { - location_id: "123" + owner_id: "123" } end it 'embedds username and password to request' do - xml = builder.build_property_ids_fetch_payload(params[:location_id]) + xml = builder.build_properties_collection_fetch_payload( + params[:owner_id] + ) hash = to_hash(xml) - authentication = hash.get("Pull_ListProp_RQ.Authentication") + authentication = hash.get("Pull_ListOwnerProp_RQ.Authentication") expect(authentication.get("UserName")).to eq(credentials.username) expect(authentication.get("Password")).to eq(credentials.password) end - it 'adds location_id to request' do - xml = builder.build_property_ids_fetch_payload(params[:location_id]) + it 'adds owner_id request' do + xml = builder.build_properties_collection_fetch_payload( + params[:owner_id] + ) hash = to_hash(xml) - location_id = hash.get("Pull_ListProp_RQ.LocationID") - expect(location_id).to eq(params[:location_id]) + owner_id = hash.get("Pull_ListOwnerProp_RQ.OwnerID") + expect(owner_id).to eq(params[:owner_id]) end it 'adds include_nla flag to request' do - xml = builder.build_property_ids_fetch_payload(params[:property_id]) + xml = builder.build_properties_collection_fetch_payload( + params[:owner_id] + ) hash = to_hash(xml) - include_nla = hash.get("Pull_ListProp_RQ.IncludeNLA") + include_nla = hash.get("Pull_ListOwnerProp_RQ.IncludeNLA") expect(include_nla).to eq(false) end end - describe '#build_location_ids_fetch_payload' do + describe '#build_owner_fetch_payload' do + let(:params) do + { + owner_id: "123" + } + end + it 'embedds username and password to request' do - xml = builder.build_location_ids_fetch_payload + xml = builder.build_owner_fetch_payload(params[:owner_id]) hash = to_hash(xml) - authentication = hash.get("Pull_ListCitiesProps_RQ.Authentication") + authentication = hash.get("Pull_GetOwnerDetails_RQ.Authentication") expect(authentication.get("UserName")).to eq(credentials.username) expect(authentication.get("Password")).to eq(credentials.password) end - end - describe '#build_owners_fetch_payload' do - it 'embedds username and password to request' do - xml = builder.build_owners_fetch_payload + it 'adds owner_id request' do + xml = builder.build_owner_fetch_payload(params[:owner_id]) hash = to_hash(xml) - authentication = hash.get("Pull_ListAllOwners_RQ.Authentication") - expect(authentication.get("UserName")).to eq(credentials.username) - expect(authentication.get("Password")).to eq(credentials.password) + owner_id = hash.get("Pull_GetOwnerDetails_RQ.OwnerID") + expect(owner_id).to eq(params[:owner_id]) end end diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index 8175e9ab9..fb614665c 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -14,281 +14,393 @@ described_class.new(host) end - it "fails when fetching location ids returns an error" do - stub_data = read_fixture("rentals_united/location_ids/error_status.xml") - stub_call(:post, url) { [200, {}, stub_data] } + it "fails when fetching owner returns an error" do + failing_owner_fetch! result = worker.perform expect(result).to be_nil - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq("Failed to fetch location ids") - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true + expect_sync_error("Failed to fetch owner with owner_id `host`") end - describe "when #fetch_location_ids is working" do - before do - expect_any_instance_of(RentalsUnited::Importer).to( - receive(:fetch_location_ids) - ).and_return( - Result.new(["1505"]) - ) - end + it "fails when fetching properties collection for owner returns an error" do + successful_owner_fetch! + failing_properties_collection_fetch! - it "fails when fetching locations by location_ids returns an error" do - stub_data = read_fixture("rentals_united/locations/error_status.xml") - stub_call(:post, url) { [200, {}, stub_data] } + result = worker.perform + expect(result).to be_nil - result = worker.perform - expect(result).to be_nil + expect_sync_error("Failed to fetch property ids collection for owner `host`") + end - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq("Failed to fetch locations") - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end + it "fails when fetching locations by location_ids returns an error" do + successful_owner_fetch! + successful_properties_collection_fetch! + failing_locations_fetch! - describe "when #fetch_locations is working" do - let(:location) do - location = RentalsUnited::Entities::Location.new("1505") - location.country = "France" - location - end + result = worker.perform + expect(result).to be_nil - before do - expect_any_instance_of(RentalsUnited::Importer).to( - receive(:fetch_locations) - ).and_return( - Result.new([location]) - ) - end + expect_sync_error("Failed to fetch locations with ids `[\"1505\"]`") + end - it "fails when fetching location currencies returns an error" do - stub_data = read_fixture("rentals_united/location_currencies/error_status.xml") - stub_call(:post, url) { [200, {}, stub_data] } + it "fails when fetching location currencies returns an error" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + failing_location_currencies_fetch! - result = worker.perform - expect(result).to be_nil + result = worker.perform + expect(result).to be_nil - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to fetch locations-currencies mapping" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end + expect_sync_error("Failed to fetch locations-currencies mapping") + end + + it "finishes sync when there is no properties to iterate on" do + successful_owner_fetch! + successful_but_empty_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + end + + it "fails when there is no location for property" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_but_wrong_locations_fetch! + successful_location_currencies_fetch! + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + + expect_sync_error("Failed to find location with id `1505`") + end + + it "fails when there is no currency for location and continues worker process" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_but_wrong_location_currencies_fetch! + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + + expect_sync_error("Failed to find currency for location with id `1505`") + end + + it "fails when #fetch_property returns an error" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + failing_property_fetch! - describe "when #fetch_location_currencies is working" do - let(:location_currencies) {{"1506" => "EUR", "1606" => "USD"}} - before do - expect_any_instance_of(RentalsUnited::Importer).to( - receive(:fetch_location_currencies) - ).and_return( - Result.new(location_currencies) + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + + expect_sync_error("Failed to fetch property with property_id `519688`") + end + + it "fails when #fetch_seasons returns an error" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + successful_property_fetch! + failing_seasons_fetch! + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + + expect_sync_error("Failed to fetch seasons for property `519688`") + end + + described_class::IGNORABLE_ERROR_CODES.each do |code| + it "skips property from publishing when there was #{code} error" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + successful_property_fetch! + successful_seasons_fetch! + failing_property_build!(code) + + expected_property_ids = ["519688"] + expected_property_ids.each do |property_id| + expect { + sync_process = worker.perform + + expect(sync_process.stats.get("properties_skipped")).to eq( + [{ "reason" => code, "ids" => [property_id] }] + ) + expect(worker.property_sync).not_to( + receive(:start).with(property_id) ) - end - - it "fails when fetching owners returns an error" do - stub_data = read_fixture("rentals_united/owners/error_status.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = worker.perform - expect(result).to be_nil - - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq("Failed to fetch owners") - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - - describe "when #fetch_owners is working" do - let(:owner) do - double( - id: '427698', - first_name: 'John', - last_name: 'Doe', - email: 'john.doe@gmail.com', - phone: '3128329138' - ) - end - - before do - expect_any_instance_of(RentalsUnited::Importer).to( - receive(:fetch_owners) - ).and_return( - Result.new([owner]) - ) - end - - it "fails when there is no currency for location and continues worker process" do - result = worker.perform - expect(result).to be_kind_of(SyncProcess) - - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to find currency for location with id `1505`" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - - describe "when currency for location exists" do - let(:location_currencies) {{"1505" => "EUR", "1606" => "USD"}} - - it "fails when fetching property ids for location returns an error" do - stub_data = read_fixture("rentals_united/property_ids/error_status.xml") - stub_call(:post, url) { [200, {}, stub_data] } - - result = worker.perform - expect(result).to be_kind_of(SyncProcess) - - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to fetch property ids for location `1505`" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - - describe "when #fetch_property_ids is working" do - before do - expect_any_instance_of(RentalsUnited::Importer).to( - receive(:fetch_property_ids) - ).and_return( - Result.new(["1234"]) - ) - end - - it "fails when #fetch_properties_by_ids returns an error and continues worker process" do - allow_any_instance_of(RentalsUnited::Importer).to receive(:fetch_properties_by_ids) { Result.error('fail') } - - result = worker.perform - expect(result).to be_kind_of(SyncProcess) - - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to fetch properties for ids `[\"1234\"]` in location `1505`" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - - describe "when #fetch_properties_by_ids is working" do - before do - stub_data = read_fixture("rentals_united/properties/property.xml") - stub_call(:post, url) { [200, {}, stub_data] } - end - - it "fails when #fetch_seasons returns an error and continues worker process" do - result = worker.perform - expect(result).to be_kind_of(SyncProcess) - - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to fetch seasons for property `519688`" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - - describe "when #fetch_seasons is working" do - let(:season) do - RentalsUnited::Entities::Season.new( - date_from: Date.parse("2016-09-01"), - date_to: Date.parse("2016-09-30"), - price: 200.00 - ) - end - before do - expect_any_instance_of(RentalsUnited::Importer).to( - receive(:fetch_seasons) - ).and_return( - Result.new([season]) - ) - end - - describe "when there is no owner for property" do - let(:owner) do - double( - id: '550000', - file_name: 'John', - last_name: 'Doe', - empty: 'john.doe@gmail.com', - phone: '3128329138' - ) - end - - it "fails with owner error and continues worker process" do - result = worker.perform - expect(result).to be_kind_of(SyncProcess) - - event = Concierge.context.events.last.to_h - expect(event[:label]).to eq("Synchronisation Failure") - expect(event[:message]).to eq( - "Failed to find owner for property id `519688`" - ) - expect(event[:backtrace]).to be_kind_of(Array) - expect(event[:backtrace].any?).to be true - end - end - - it "calls synchronisation block for every property id" do - expected_property_ids = ["519688"] - - expected_property_ids.each do |property_id| - expect(worker.property_sync).to receive(:start).with(property_id) - end - - result = worker.perform - expect(result).to be_kind_of(SyncProcess) - expect(result.to_h[:successful]).to be true - end - - it "creates record in the database" do - allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.new('success') } - - expect { - worker.perform - }.to change { PropertyRepository.count }.by(1) - end - - described_class::IGNORABLE_ERROR_CODES.each do |code| - it "skips property from publishing when there was #{code} error" do - allow_any_instance_of(RentalsUnited::Mappers::RoomoramaProperty) - .to receive(:build_roomorama_property) { Result.error(code) } - - expect { - sync_process = worker.perform - expect(sync_process.stats.get("properties_skipped")).to eq( - [{ "reason" => code, "ids" => ["519688"] }] - ) - }.to change { PropertyRepository.count }.by(0) - end - end - - it 'doesnt create property with unsuccessful publishing' do - allow_any_instance_of(Roomorama::Client).to receive(:perform) { Result.error('fail') } - - expect { - worker.perform - }.to_not change { PropertyRepository.count } - end - end - end - end - end - end + }.to change { PropertyRepository.count }.by(0) end end end + + it "calls synchronisation block for every property id" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + successful_property_fetch! + successful_seasons_fetch! + + expected_property_ids = ["519688"] + expected_property_ids.each do |property_id| + expect(worker.property_sync).to receive(:start).with(property_id) + end + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + expect(result.to_h[:successful]).to be true + end + + it "creates record in the database" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + successful_property_fetch! + successful_seasons_fetch! + successful_publishing_to_roomorama! + + expect { + worker.perform + }.to change { PropertyRepository.count }.by(1) + end + + it "doesnt create property with unsuccessful publishing" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + successful_property_fetch! + successful_seasons_fetch! + failing_publishing_to_roomorama! + + expect { + worker.perform + }.to_not change { PropertyRepository.count } + end + + it "starts calendar sync when property" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + successful_property_fetch! + successful_seasons_fetch! + successful_publishing_to_roomorama! + already_synced_property! + + expected_property_ids = ["519688"] + expected_property_ids.each do |property_id| + expect(worker.calendar_sync).to receive(:start).with(property_id) + end + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + expect(result.to_h[:successful]).to be true + end + + it "fails when #fetch_availabilities returns an error" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + successful_property_fetch! + successful_seasons_fetch! + successful_publishing_to_roomorama! + already_synced_property! + failing_availabilities_fetch! + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + + expect_sync_error("Failed to fetch availabilities for property `519688`") + end + + it "finishes everything" do + successful_owner_fetch! + successful_properties_collection_fetch! + successful_locations_fetch! + successful_location_currencies_fetch! + successful_property_fetch! + successful_seasons_fetch! + successful_publishing_to_roomorama! + already_synced_property! + successful_availabilities_fetch! + + result = worker.perform + expect(result).to be_kind_of(SyncProcess) + expect(result.to_h[:successful]).to be true + end + + private + def expect_sync_error(message) + context_event = Concierge.context.events.last.to_h + + expect(context_event[:label]).to eq("Synchronisation Failure") + expect(context_event[:message]).to eq(message) + expect(context_event[:backtrace]).to be_kind_of(Array) + expect(context_event[:backtrace].any?).to be true + end + + def failing_owner_fetch! + stub_importer_action!(:fetch_owner, Result.error('fail')) + end + + def failing_properties_collection_fetch! + stub_importer_action!( + :fetch_properties_collection_for_owner, + Result.error('fail') + ) + end + + def failing_locations_fetch! + stub_importer_action!(:fetch_locations, Result.error('fail')) + end + + def failing_location_currencies_fetch! + stub_importer_action!(:fetch_location_currencies, Result.error('fail')) + end + + def failing_property_fetch! + stub_importer_action!(:fetch_property, Result.error('fail')) + end + + def failing_seasons_fetch! + stub_importer_action!(:fetch_seasons, Result.error('fail')) + end + + def failing_availabilities_fetch! + stub_importer_action!(:fetch_availabilities, Result.error('fail')) + end + + def failing_publishing_to_roomorama! + stub_publishing_to_roomorama!(Result.error('fail')) + end + + def failing_property_build!(code) + allow_any_instance_of(RentalsUnited::Mappers::RoomoramaProperty) + .to receive(:build_roomorama_property) { Result.error(code) } + end + + def successful_owner_fetch! + owner = double( + id: 'host', + first_name: 'John', + last_name: 'Doe', + email: 'john.doe@gmail.com', + phone: '3128329138' + ) + + stub_importer_action!(:fetch_owner, Result.new(owner)) + end + + def successful_properties_collection_fetch! + collection = RentalsUnited::Entities::PropertiesCollection.new( + [ + { property_id: '519688', location_id: '1505' } + ] + ) + + stub_importer_action!( + :fetch_properties_collection_for_owner, + Result.new(collection) + ) + end + + def successful_but_empty_properties_collection_fetch! + collection = RentalsUnited::Entities::PropertiesCollection.new([]) + + stub_importer_action!( + :fetch_properties_collection_for_owner, + Result.new(collection) + ) + end + + def successful_locations_fetch! + location = RentalsUnited::Entities::Location.new("1505") + location.country = "France" + + stub_importer_action!(:fetch_locations, Result.new([location])) + end + + def successful_but_wrong_locations_fetch! + location = RentalsUnited::Entities::Location.new("1506") + location.country = "France" + + stub_importer_action!(:fetch_locations, Result.new([location])) + end + + def successful_location_currencies_fetch! + location_currencies = {"1505" => "EUR", "1606" => "USD"} + + stub_importer_action!( + :fetch_location_currencies, + Result.new(location_currencies) + ) + end + + def successful_but_wrong_location_currencies_fetch! + location_currencies = {"2505" => "EUR", "2606" => "USD"} + + stub_importer_action!( + :fetch_location_currencies, + Result.new(location_currencies) + ) + end + + def successful_property_fetch! + stub_data = read_fixture("rentals_united/properties/property.xml") + stub_call(:post, url) { [200, {}, stub_data] } + end + + def successful_seasons_fetch! + season = RentalsUnited::Entities::Season.new( + date_from: Date.parse("2016-09-01"), + date_to: Date.parse("2016-09-30"), + price: 200.00 + ) + stub_importer_action!(:fetch_seasons, Result.new([season])) + end + + def successful_availabilities_fetch! + availability = RentalsUnited::Entities::Availability.new( + date: Date.parse("2016-09-01"), + available: true, + minimum_stay: 1, + changeover: "4" + ) + stub_importer_action!(:fetch_availabilities, Result.new([availability])) + end + + def successful_publishing_to_roomorama! + stub_publishing_to_roomorama!(Result.new('success')) + end + + def already_synced_property! + allow_any_instance_of(Hanami::Model::Adapters::Sql::Query) + .to receive(:count) { 1 } + end + + def stub_publishing_to_roomorama!(result) + allow_any_instance_of(Roomorama::Client).to receive(:perform) do + result + end + end + + def stub_importer_action!(action, result) + expect_any_instance_of(RentalsUnited::Importer) + .to(receive(action)) + .and_return(result) + end end end From 138b5c6c04d0eba49ecc7cd8f0c7e55ae42f28c0 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 29 Sep 2016 10:41:13 +0600 Subject: [PATCH 080/118] RU: add contexts --- apps/workers/suppliers/rentals_united/metadata.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index 80364a3a9..db99b0d97 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -31,7 +31,7 @@ def initialize(host) end def perform - result = fetch_owner(host.identifier) + result = property_sync.new_context { fetch_owner(host.identifier) } return unless result.success? owner = result.value @@ -64,7 +64,7 @@ def perform next end - result = fetch_property(property_id) + result = property_sync.new_context { fetch_property(property_id) } next unless result.success? property = result.value From 644e6d69026d3a6b454f945d2802433a8ae11f82 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 29 Sep 2016 17:45:13 +0600 Subject: [PATCH 081/118] RU: propert supplier naming to prevent issues while having multi-host logic --- config/credentials/production.yml | 2 +- config/credentials/staging.yml | 2 +- config/credentials/test.yml | 2 +- lib/concierge/suppliers/rentals_united/client.rb | 2 +- .../rentals_united/commands/availabilities_fetcher_spec.rb | 3 ++- .../commands/location_currencies_fetcher_spec.rb | 3 ++- .../rentals_united/commands/locations_fetcher_spec.rb | 3 ++- .../suppliers/rentals_united/commands/owner_fetcher_spec.rb | 3 ++- .../commands/properties_collection_fetcher_spec.rb | 3 ++- .../suppliers/rentals_united/commands/property_fetcher_spec.rb | 3 ++- .../suppliers/rentals_united/commands/seasons_fetcher_spec.rb | 3 ++- spec/lib/concierge/suppliers/rentals_united/importer_spec.rb | 3 ++- .../concierge/suppliers/rentals_united/payload_builder_spec.rb | 3 ++- spec/workers/suppliers/rentals_united/metadata_spec.rb | 3 ++- 14 files changed, 24 insertions(+), 14 deletions(-) diff --git a/config/credentials/production.yml b/config/credentials/production.yml index 43450afd7..f17371000 100644 --- a/config/credentials/production.yml +++ b/config/credentials/production.yml @@ -45,7 +45,7 @@ poplidays: client_key: <%= ENV["POPLIDAYS_CLIENT_KEY"] %> passphrase: <%= ENV["POPLIDAYS_PASSPHRASE"] %> -rentals_united: +rentalsunited: username: <%= ENV["RENTALS_UNITED_USERNAME"] %> password: <%= ENV["RENTALS_UNITED_PASSWORD"] %> url: <%= ENV["RENTALS_UNITED_URL"] %> diff --git a/config/credentials/staging.yml b/config/credentials/staging.yml index 812599d99..198dcc032 100644 --- a/config/credentials/staging.yml +++ b/config/credentials/staging.yml @@ -45,7 +45,7 @@ poplidays: client_key: <%= ENV["POPLIDAYS_CLIENT_KEY"] %> passphrase: <%= ENV["POPLIDAYS_PASSPHRASE"] %> -rentals_united: +rentalsunited: username: <%= ENV["RENTALS_UNITED_USERNAME"] %> password: <%= ENV["RENTALS_UNITED_PASSWORD"] %> url: <%= ENV["RENTALS_UNITED_URL"] %> diff --git a/config/credentials/test.yml b/config/credentials/test.yml index a7e980084..884cde75a 100644 --- a/config/credentials/test.yml +++ b/config/credentials/test.yml @@ -53,7 +53,7 @@ poplidays: client_key: "123" passphrase: "123" -rentals_united: +rentalsunited: username: "roomorama-user" password: "roomorama-pass" url: "http://www.example.org" diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb index 179bfa4ca..4d6e28cad 100644 --- a/lib/concierge/suppliers/rentals_united/client.rb +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -1,7 +1,7 @@ module RentalsUnited # +RentalsUnited::Client+ class Client - SUPPLIER_NAME = "rentals_united" + SUPPLIER_NAME = "RentalsUnited" attr_reader :credentials diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher_spec.rb index 8a456a798..6fd069a22 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/availabilities_fetcher_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:property_id) { "1234" } let(:subject) { described_class.new(credentials, property_id) } let(:url) { credentials.url } diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/location_currencies_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/location_currencies_fetcher_spec.rb index c38d18f98..d464198a0 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/location_currencies_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/location_currencies_fetcher_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:subject) { described_class.new(credentials) } let(:url) { credentials.url } diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb index ffe30d36a..f9c07d067 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/locations_fetcher_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:url) { credentials.url } context "when location does not exist" do diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/owner_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/owner_fetcher_spec.rb index 5a34dcce7..e9ddc735d 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/owner_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/owner_fetcher_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:owner_id) { '123' } let(:subject) { described_class.new(credentials, owner_id) } let(:url) { credentials.url } diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher_spec.rb index 9d117d08e..777bbb225 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/properties_collection_fetcher_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:owner_id) { "1234" } let(:subject) { described_class.new(credentials, owner_id) } let(:url) { credentials.url } diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb index 502dbe712..6ec247784 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/property_fetcher_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:property_id) { "1234" } let(:subject) { described_class.new(credentials, property_id) } let(:url) { credentials.url } diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher_spec.rb index 2eb896f55..470163a5f 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/seasons_fetcher_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:property_id) { "1234" } let(:subject) { described_class.new(credentials, property_id) } let(:url) { credentials.url } diff --git a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb index a87865d10..45c048452 100644 --- a/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/importer_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:importer) { described_class.new(credentials) } describe "#fetch_properties_collection_for_owner" do diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index d0b000e25..cf4c91d21 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -1,7 +1,8 @@ require "spec_helper" RSpec.describe RentalsUnited::PayloadBuilder do - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:builder) { described_class.new(credentials) } describe '#build_properties_collection_fetch_payload' do diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index fb614665c..37e072eab 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -6,7 +6,8 @@ include Support::Fixtures let(:host) { create_host } - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:url) { credentials.url } describe "#perform operation" do From b4609ad9262b75a97ed70da5024a51e9bf4e5ec3 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 30 Sep 2016 15:42:25 +0600 Subject: [PATCH 082/118] RU: set security deposit type to cash --- .../suppliers/rentals_united/mappers/roomorama_property.rb | 2 +- .../suppliers/rentals_united/mappers/roomorama_property_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb index 8f42a92a9..5dbac1c0d 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb @@ -19,7 +19,7 @@ class RoomoramaProperty NO_DEPOSIT_ID, FLAT_AMOUNT_PER_STAY_ID ] - SECURITY_DEPOSIT_PAYMENT_TYPE = 'unknown' + SECURITY_DEPOSIT_PAYMENT_TYPE = 'cash' # Initialize +RentalsUnited::Mappers::Property+ # diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb index 81769338e..adedb1ff8 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb @@ -173,7 +173,7 @@ end it "sets security_deposit_currency_code" do - expect(property.security_deposit_type).to eq("unknown") + expect(property.security_deposit_type).to eq("cash") end it "sets property rates" do From 1a9562f1517f498509c3021531927b07abf7f00f Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 4 Oct 2016 11:45:00 +0600 Subject: [PATCH 083/118] RU: add sync failed to worker --- .../suppliers/rentals_united/metadata.rb | 61 ++++++++++++------- .../suppliers/rentals_united/metadata_spec.rb | 16 ++++- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index db99b0d97..55dd65ed1 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -64,18 +64,19 @@ def perform next end - result = property_sync.new_context { fetch_property(property_id) } - next unless result.success? - property = result.value + property_result = property_sync.new_context { fetch_property(property_id) } + next unless property_result.success? + property = property_result.value - result = fetch_seasons(property_id) - next unless result.success? - seasons = result.value + seasons_result = fetch_seasons(property_id) + next unless seasons_result.success? + seasons = seasons_result.value result = build_roomorama_property(property, location, owner, seasons) - next if skip?(result, property) - property_sync.start(property_id) { result } if result.success? + unless skip?(result, property) + property_sync.start(property_id) { result } if result.success? + end if synced_property?(property_id) sync_calendar(property_id, seasons) @@ -144,39 +145,51 @@ def build_roomorama_property(property, location, owner, seasons) end def fetch_owner(owner_id) - announce_error("Failed to fetch owner with owner_id `#{owner_id}`") do - importer.fetch_owner(owner_id) + sync_failed do + announce_error("Failed to fetch owner with owner_id `#{owner_id}`") do + importer.fetch_owner(owner_id) + end end end def fetch_properties_collection_for_owner(owner_id) - announce_error("Failed to fetch property ids collection for owner `#{owner_id}`") do - importer.fetch_properties_collection_for_owner(owner_id) + sync_failed do + announce_error("Failed to fetch property ids collection for owner `#{owner_id}`") do + importer.fetch_properties_collection_for_owner(owner_id) + end end end def fetch_locations(location_ids) - announce_error("Failed to fetch locations with ids `#{location_ids}`") do - importer.fetch_locations(location_ids) + sync_failed do + announce_error("Failed to fetch locations with ids `#{location_ids}`") do + importer.fetch_locations(location_ids) + end end end def fetch_location_currencies - announce_error("Failed to fetch locations-currencies mapping") do - importer.fetch_location_currencies + sync_failed do + announce_error("Failed to fetch locations-currencies mapping") do + importer.fetch_location_currencies + end end end def fetch_property(property_id) - message = "Failed to fetch property with property_id `#{property_id}`" - announce_error(message) do - importer.fetch_property(property_id) + sync_failed do + message = "Failed to fetch property with property_id `#{property_id}`" + announce_error(message) do + importer.fetch_property(property_id) + end end end def fetch_seasons(property_id) - report_error("Failed to fetch seasons for property `#{property_id}`") do - importer.fetch_seasons(property_id) + sync_failed do + report_error("Failed to fetch seasons for property `#{property_id}`") do + importer.fetch_seasons(property_id) + end end end @@ -200,6 +213,12 @@ def announce_error(message) end end + def sync_failed + yield.tap do |result| + property_sync.failed! unless result.success? + end + end + def report_error(message) yield.tap do |result| augment_context_error(message) unless result.success? diff --git a/spec/workers/suppliers/rentals_united/metadata_spec.rb b/spec/workers/suppliers/rentals_united/metadata_spec.rb index 37e072eab..56df988bc 100644 --- a/spec/workers/suppliers/rentals_united/metadata_spec.rb +++ b/spec/workers/suppliers/rentals_united/metadata_spec.rb @@ -20,6 +20,7 @@ result = worker.perform expect(result).to be_nil + expect(worker.property_sync.sync_record.successful).to be false expect_sync_error("Failed to fetch owner with owner_id `host`") end @@ -30,6 +31,7 @@ result = worker.perform expect(result).to be_nil + expect(worker.property_sync.sync_record.successful).to be false expect_sync_error("Failed to fetch property ids collection for owner `host`") end @@ -41,6 +43,7 @@ result = worker.perform expect(result).to be_nil + expect(worker.property_sync.sync_record.successful).to be false expect_sync_error("Failed to fetch locations with ids `[\"1505\"]`") end @@ -53,6 +56,7 @@ result = worker.perform expect(result).to be_nil + expect(worker.property_sync.sync_record.successful).to be false expect_sync_error("Failed to fetch locations-currencies mapping") end @@ -65,9 +69,10 @@ result = worker.perform expect(result).to be_kind_of(SyncProcess) + expect(worker.property_sync.sync_record.successful).to be true end - it "fails when there is no location for property" do + it "fails when there is no location for property and continues worker process" do successful_owner_fetch! successful_properties_collection_fetch! successful_but_wrong_locations_fetch! @@ -75,6 +80,7 @@ result = worker.perform expect(result).to be_kind_of(SyncProcess) + expect(worker.property_sync.sync_record.successful).to be true expect_sync_error("Failed to find location with id `1505`") end @@ -87,6 +93,7 @@ result = worker.perform expect(result).to be_kind_of(SyncProcess) + expect(worker.property_sync.sync_record.successful).to be true expect_sync_error("Failed to find currency for location with id `1505`") end @@ -100,6 +107,7 @@ result = worker.perform expect(result).to be_kind_of(SyncProcess) + expect(worker.property_sync.sync_record.successful).to be false expect_sync_error("Failed to fetch property with property_id `519688`") end @@ -114,6 +122,7 @@ result = worker.perform expect(result).to be_kind_of(SyncProcess) + expect(worker.property_sync.sync_record.successful).to be false expect_sync_error("Failed to fetch seasons for property `519688`") end @@ -141,6 +150,7 @@ ) }.to change { PropertyRepository.count }.by(0) end + expect(worker.property_sync.sync_record.successful).to be true end end @@ -160,6 +170,7 @@ result = worker.perform expect(result).to be_kind_of(SyncProcess) expect(result.to_h[:successful]).to be true + expect(worker.property_sync.sync_record.successful).to be true end it "creates record in the database" do @@ -174,6 +185,7 @@ expect { worker.perform }.to change { PropertyRepository.count }.by(1) + expect(worker.property_sync.sync_record.successful).to be true end it "doesnt create property with unsuccessful publishing" do @@ -188,6 +200,7 @@ expect { worker.perform }.to_not change { PropertyRepository.count } + expect(worker.property_sync.sync_record.successful).to be true end it "starts calendar sync when property" do @@ -208,6 +221,7 @@ result = worker.perform expect(result).to be_kind_of(SyncProcess) expect(result.to_h[:successful]).to be true + expect(worker.calendar_sync.sync_record.successful).to be true end it "fails when #fetch_availabilities returns an error" do From 0149b2e901374e17ef7d8844d9a67c7637e55961 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 4 Oct 2016 12:20:56 +0600 Subject: [PATCH 084/118] RU: set empty cleaning fields --- .../rentals_united/mappers/roomorama_property.rb | 9 +++++++++ .../rentals_united/mappers/roomorama_property_spec.rb | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb index 5dbac1c0d..c3e235bbe 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/roomorama_property.rb @@ -84,6 +84,7 @@ def build_roomorama_property set_images!(property) set_security_deposit!(property) set_rates!(property) + set_cleaning!(property) Result.new(property) end @@ -107,6 +108,14 @@ def set_rates!(property) property.monthly_rate = (avg_price_per_day * 30).round(2) end + # Cleaning is always included to total price while quote/booking + # There is no need for user to pay cleaning additionally + def set_cleaning!(property) + property.services_cleaning = false + property.services_cleaning_required = nil + property.services_cleaning_rate = nil + end + def avg_price_per_day @avg_price ||= calculate_avg_price_per_day end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb index adedb1ff8..917b732b2 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/roomorama_property_spec.rb @@ -164,6 +164,12 @@ expect(property.owner_phone_number).to eq("3128329138") end + it "sets cleaning fields" do + expect(property.services_cleaning).to be false + expect(property.services_cleaning_required).to be nil + expect(property.services_cleaning_rate).to be nil + end + it "sets security_deposit_amount" do expect(property.security_deposit_amount).to eq(5.5) end From 9630cec8184d954387afc2d257562e4705a53cc6 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 15:59:02 +0600 Subject: [PATCH 085/118] RU: add controller routing for quote --- apps/api/config/routes.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/api/config/routes.rb b/apps/api/config/routes.rb index a854bd0d1..577f25106 100644 --- a/apps/api/config/routes.rb +++ b/apps/api/config/routes.rb @@ -1,11 +1,12 @@ -post '/atleisure/quote', to: 'at_leisure#quote' -post '/jtb/quote', to: 'j_t_b#quote' -post '/kigo/quote', to: 'kigo#quote' -post '/kigo/legacy/quote', to: 'kigo/legacy#quote' -post '/poplidays/quote', to: 'poplidays#quote' -post '/ciirus/quote', to: 'ciirus#quote' -post '/waytostay/quote', to: 'waytostay#quote' -post '/saw/quote', to: 's_a_w#quote' +post '/atleisure/quote', to: 'at_leisure#quote' +post '/jtb/quote', to: 'j_t_b#quote' +post '/kigo/quote', to: 'kigo#quote' +post '/kigo/legacy/quote', to: 'kigo/legacy#quote' +post '/poplidays/quote', to: 'poplidays#quote' +post '/ciirus/quote', to: 'ciirus#quote' +post '/waytostay/quote', to: 'waytostay#quote' +post '/saw/quote', to: 's_a_w#quote' +post '/rentals_united/quote', to: 'rentals_united#quote' post '/jtb/booking', to: 'j_t_b#booking' post '/atleisure/booking', to: 'at_leisure#booking' From 4d9d8f7aa36bc9bee43c85e128831621303b6b51 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 16:01:26 +0600 Subject: [PATCH 086/118] RU: add controller action for quote --- apps/api/controllers/rentals_united/quote.rb | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 apps/api/controllers/rentals_united/quote.rb diff --git a/apps/api/controllers/rentals_united/quote.rb b/apps/api/controllers/rentals_united/quote.rb new file mode 100644 index 000000000..1104b1367 --- /dev/null +++ b/apps/api/controllers/rentals_united/quote.rb @@ -0,0 +1,29 @@ +require_relative "../quote" + +module API::Controllers::RentalsUnited + # API::Controllers::RentalsUnited::Quote + # + # Performs booking quotations for properties from RentalsUnited. + class Quote + include API::Controllers::Quote + + params API::Controllers::Params::Quote + + # Make price (property rate) request + # + # Usage + # + # It returns a Quotation object in both success and fail cases: + # + # API::Controllers::RentalsUnited::Quote.quote_price(selected_params) + # => Quotation(..) + def quote_price(params) + credentials = Concierge::Credentials.for(supplier_name) + RentalsUnited::Client.new(credentials).quote(params) + end + + def supplier_name + RentalsUnited::Client::SUPPLIER_NAME + end + end +end From f4034333498d11f67f11bb6b0849a29a407d7142 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 17:41:59 +0600 Subject: [PATCH 087/118] RU: quote price --- .../suppliers/rentals_united/client.rb | 23 +++ .../rentals_united/commands/base_fetcher.rb | 10 +- .../commands/quotation_fetcher.rb | 82 ++++++++++ .../rentals_united/mappers/quotation.rb | 40 +++++ .../rentals_united/payload_builder.rb | 11 ++ .../templates/quotation_fetch.xml.erb | 10 ++ .../quotations/invalid_date_from.xml | 3 + .../quotations/invalid_max_guests.xml | 3 + .../quotations/not_available.xml | 3 + .../rentals_united/quotations/success.xml | 6 + .../quotations/too_many_guests.xml | 3 + .../suppliers/rentals_united/client_spec.rb | 17 +++ .../commands/quotation_fetcher_spec.rb | 142 ++++++++++++++++++ .../rentals_united/payload_builder_spec.rb | 46 ++++++ 14 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/quotation.rb create mode 100644 lib/concierge/suppliers/rentals_united/templates/quotation_fetch.xml.erb create mode 100644 spec/fixtures/rentals_united/quotations/invalid_date_from.xml create mode 100644 spec/fixtures/rentals_united/quotations/invalid_max_guests.xml create mode 100644 spec/fixtures/rentals_united/quotations/not_available.xml create mode 100644 spec/fixtures/rentals_united/quotations/success.xml create mode 100644 spec/fixtures/rentals_united/quotations/too_many_guests.xml create mode 100644 spec/lib/concierge/suppliers/rentals_united/client_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb index 4d6e28cad..cb5970988 100644 --- a/lib/concierge/suppliers/rentals_united/client.rb +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -8,5 +8,28 @@ class Client def initialize(credentials) @credentials = credentials end + + # Quote RentalsUnited properties prices + # If an error happens in any step in the process of getting a response back + # from RentalsUnited, a result object with error is returned + # + # Usage + # + # comamnd = RentalsUnited::Client.new(credentials) + # result = command.quote(params) + # + # if result.success? + # # ... + # end + # + # Returns a +Result+ wrapping a +Quotation+ when operation succeeds + # Returns a +Result+ with +Result::Error+ when operation fails + def quote(quotation_params) + command = RentalsUnited::Commands::QuotationFetcher.new( + credentials, + quotation_params + ) + command.call + end end end diff --git a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb index 207b0c5f5..ade6421de 100644 --- a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb @@ -3,7 +3,15 @@ module Commands class BaseFetcher attr_reader :credentials - VALID_RU_STATUS_CODES = ["0"] + # Some statuses from RentalsUnited API are not actually errors, so this + # constant is a list of whitelisted codes, which we consider as normal + # behaviour. + # + # Whitelisted RentalsUnited API statuses: + # 0 - Success + # 1 - Property is not available for a given dates + + VALID_RU_STATUS_CODES = ["0", "1"] def initialize(credentials) @credentials = credentials diff --git a/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb new file mode 100644 index 000000000..60f40a292 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb @@ -0,0 +1,82 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::QuotationFetcher+ + # + # This class is responsible for wrapping the logic related to making a + # price quotation to RentalsUnited, parsing the response, and building the + # +Quotation+ object with the data returned from their API. + # + # Usage + # + # command = RentalsUnited::Commands::QuotationFetcher.new(credentials) + # result = command.call(stay_params) + # + # if result.success? + # process_quotation(result.value) + # else + # handle_error(result.error) + # end + class QuotationFetcher < BaseFetcher + attr_reader :quotation_params + + ROOT_TAG = "Pull_GetPropertyAvbPrice_RS" + + # Initialize +QuotationFetcher+ command. + # + # Arguments + # + # * +credentials+ + # * +quotation_params+ [Concierge::SafeAccessHash] stay parameters + # + # Stay parameters are defined by the set of attributes from + # +API::Controllers::Params::MultiUnitQuote+ params object. + # + # +quotation_params+ object includes: + # + # * +property_id+ + # * +check_in+ + # * +check_out+ + # * +guests+ + def initialize(credentials, quotation_params) + super(credentials) + @quotation_params = quotation_params + end + + # Calls the RentalsUnited API method using the HTTP client. + # + # The +call+ method returns a +Result+ object that, when successful, + # encapsulates the resulting +Quotation+ object. + def call + payload = build_payload + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_quotation(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_payload + payload_builder.build_quotation_fetch_payload( + property_id: quotation_params[:property_id], + check_in: quotation_params[:check_in], + check_out: quotation_params[:check_out], + num_guests: quotation_params[:guests] + ) + end + + def build_quotation(result_hash) + price = result_hash.get("#{ROOT_TAG}.PropertyPrices.PropertyPrice") + + mapper = Mappers::Quotation.new(quotation_params, price) + mapper.build_quotation + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb new file mode 100644 index 000000000..d06cadd70 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb @@ -0,0 +1,40 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Quotation+ + # + # This class is responsible for building a +Quotation+ object + class Quotation + attr_reader :quotation_params, :price + + # Initialize Quotation mapper + # + # Arguments: + # + # * +quotation_params+ [Concierge::SafeAccessHash] quotation parameters + # * +price+ [String] quotation price + def initialize(quotation_params, price) + @quotation_params = quotation_params + @price = price + end + + # Builds available quotation + # + # Returns [Quotation] + def build_quotation + ::Quotation.new( + property_id: quotation_params[:property_id], + check_in: quotation_params[:check_in].to_s, + check_out: quotation_params[:check_out].to_s, + guests: quotation_params[:guests], + currency: currency, + total: price ? price : 0, + available: price ? true : false + ) + end + + def currency + nil + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index bf968e9c3..ab7c95c46 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -67,6 +67,17 @@ def build_seasons_fetch_payload(property_id, date_from, date_to) render(:seasons_fetch, template_locals) end + def build_quotation_fetch_payload(property_id:, check_in:, check_out:, num_guests:) + template_locals = { + credentials: credentials, + property_id: property_id, + check_in: check_in, + check_out: check_out, + num_guests: num_guests + } + render(:quotation_fetch, template_locals) + end + private def render(template_name, local_vars) path = Hanami.root.join(TEMPLATES_PATH, "#{template_name}.xml.erb") diff --git a/lib/concierge/suppliers/rentals_united/templates/quotation_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/quotation_fetch.xml.erb new file mode 100644 index 000000000..2a6af3665 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/quotation_fetch.xml.erb @@ -0,0 +1,10 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + <%= property_id %> + <%= check_in %> + <%= check_out %> + <%= num_guests %> + diff --git a/spec/fixtures/rentals_united/quotations/invalid_date_from.xml b/spec/fixtures/rentals_united/quotations/invalid_date_from.xml new file mode 100644 index 000000000..e33af1996 --- /dev/null +++ b/spec/fixtures/rentals_united/quotations/invalid_date_from.xml @@ -0,0 +1,3 @@ + + DateFrom has to be earlier than DateTo. + diff --git a/spec/fixtures/rentals_united/quotations/invalid_max_guests.xml b/spec/fixtures/rentals_united/quotations/invalid_max_guests.xml new file mode 100644 index 000000000..befe6008f --- /dev/null +++ b/spec/fixtures/rentals_united/quotations/invalid_max_guests.xml @@ -0,0 +1,3 @@ + + NOP: positive value required. + diff --git a/spec/fixtures/rentals_united/quotations/not_available.xml b/spec/fixtures/rentals_united/quotations/not_available.xml new file mode 100644 index 000000000..ad17ad633 --- /dev/null +++ b/spec/fixtures/rentals_united/quotations/not_available.xml @@ -0,0 +1,3 @@ + + Property is not available for a given dates + diff --git a/spec/fixtures/rentals_united/quotations/success.xml b/spec/fixtures/rentals_united/quotations/success.xml new file mode 100644 index 000000000..4547c0ad3 --- /dev/null +++ b/spec/fixtures/rentals_united/quotations/success.xml @@ -0,0 +1,6 @@ + + Success + + 284.50 + + diff --git a/spec/fixtures/rentals_united/quotations/too_many_guests.xml b/spec/fixtures/rentals_united/quotations/too_many_guests.xml new file mode 100644 index 000000000..a61fdcaa0 --- /dev/null +++ b/spec/fixtures/rentals_united/quotations/too_many_guests.xml @@ -0,0 +1,3 @@ + + Number of guests exceedes the maximum allowed. + diff --git a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb new file mode 100644 index 000000000..58f20c0ed --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb @@ -0,0 +1,17 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Client do + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:client) { described_class.new(credentials) } + + describe "#quote" do + let(:params) { {} } + + it "calls quotation fetcher class" do + fetcher_class = RentalsUnited::Commands::QuotationFetcher + + expect_any_instance_of(fetcher_class).to(receive(:call)) + client.quote(params) + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb new file mode 100644 index 000000000..05b6af23a --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb @@ -0,0 +1,142 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::QuotationFetcher do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:property_id) { "1234" } + let(:quotation_params) do + API::Controllers::Params::Quote.new( + property_id: '1234', + check_in: "2016-09-19", + check_out: "2016-09-20", + guests: 3 + ) + end + let(:subject) { described_class.new(credentials, quotation_params) } + let(:url) { credentials.url } + + it "performs successful request returning Quotation object" do + stub_data = read_fixture("rentals_united/quotations/success.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).to be_kind_of(Result) + expect(result).to be_success + + quotation = result.value + expect(quotation).to be_kind_of(Quotation) + expect(quotation.property_id).to eq(quotation_params[:property_id]) + expect(quotation.check_in).to eq(quotation_params[:check_in]) + expect(quotation.check_out).to eq(quotation_params[:check_out]) + expect(quotation.guests).to eq(quotation_params[:guests]) + expect(quotation.total).to eq(284.5) + expect(quotation.currency).to eq('') + expect(quotation.available).to be true + end + + it "returns unavailable Quotation when property is not available" do + stub_data = read_fixture("rentals_united/quotations/not_available.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).to be_kind_of(Result) + expect(result).to be_success + + quotation = result.value + expect(quotation).to be_kind_of(Quotation) + expect(quotation.property_id).to eq(quotation_params[:property_id]) + expect(quotation.check_in).to eq(quotation_params[:check_in]) + expect(quotation.check_out).to eq(quotation_params[:check_out]) + expect(quotation.guests).to eq(quotation_params[:guests]) + expect(quotation.total).to eq(0) + expect(quotation.available).to be false + end + + it "returns an error when check_in is invalid" do + stub_data = read_fixture("rentals_united/quotations/invalid_date_from.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq("74") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `74`, and description `DateFrom has to be earlier than DateTo.`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + it "returns an error when num_guests are greater than allowed" do + stub_data = read_fixture("rentals_united/quotations/too_many_guests.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq("76") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `76`, and description `Number of guests exceedes the maximum allowed.`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + it "returns an error when num_guests are invalid" do + stub_data = read_fixture("rentals_united/quotations/invalid_max_guests.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq("77") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `77`, and description `NOP: positive value required.`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index cf4c91d21..591c779d1 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -218,6 +218,52 @@ end end + describe '#build_quotation_fetch_payload' do + let(:quotaion_params) do + { + property_id: "123", + check_in: "2016-09-01", + check_out: "2016-09-02", + num_guests: 3 + } + end + + it 'embedds username and password to request' do + xml = builder.build_quotation_fetch_payload(quotaion_params) + hash = to_hash(xml) + + authentication = hash.get("Pull_GetPropertyAvbPrice_RQ.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + + it 'adds property_id to request' do + xml = builder.build_quotation_fetch_payload(quotaion_params) + hash = to_hash(xml) + + property_id = hash.get("Pull_GetPropertyAvbPrice_RQ.PropertyID") + expect(property_id).to eq(quotaion_params[:property_id]) + end + + it 'adds check in / check out dates to request' do + xml = builder.build_quotation_fetch_payload(quotaion_params) + hash = to_hash(xml) + + check_in = hash.get("Pull_GetPropertyAvbPrice_RQ.DateFrom").to_s + check_out = hash.get("Pull_GetPropertyAvbPrice_RQ.DateTo").to_s + expect(check_in).to eq(quotaion_params[:check_in]) + expect(check_out).to eq(quotaion_params[:check_out]) + end + + it 'adds num_guests to request' do + xml = builder.build_quotation_fetch_payload(quotaion_params) + hash = to_hash(xml) + + num_guests = hash.get("Pull_GetPropertyAvbPrice_RQ.NOP") + expect(num_guests).to eq(quotaion_params[:num_guests].to_s) + end + end + private def to_hash(xml) Concierge::SafeAccessHash.new(Nori.new.parse(xml)) From 7dd1bc4ea98201848e207ace57c4150b8f423870 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 8 Sep 2016 18:09:44 +0600 Subject: [PATCH 088/118] RU: specs for quote controller --- .../controllers/rentals_united/quote_spec.rb | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 spec/api/controllers/rentals_united/quote_spec.rb diff --git a/spec/api/controllers/rentals_united/quote_spec.rb b/spec/api/controllers/rentals_united/quote_spec.rb new file mode 100644 index 000000000..fd556dfb1 --- /dev/null +++ b/spec/api/controllers/rentals_united/quote_spec.rb @@ -0,0 +1,68 @@ +require "spec_helper" +require_relative "../shared/quote_validations" +require_relative "../shared/external_error_reporting" + +RSpec.describe API::Controllers::RentalsUnited::Quote do + include Support::HTTPStubbing + include Support::Fixtures + + let(:params) do + { + property_id: "321", + check_in: "2016-03-22", + check_out: "2016-03-25", + guests: 2 + } + end + + it_behaves_like "performing parameter validations", controller_generator: -> { described_class.new } do + let(:valid_params) { params } + end + + it_behaves_like "external error reporting" do + let(:supplier_name) { "rentals_united" } + let(:credentials) { Concierge::Credentials.for(supplier_name) } + + def provoke_failure! + stub_call(:post, credentials.url) do + raise Faraday::TimeoutError + end + Struct.new(:code).new("connection_timeout") + end + end + + describe "#call" do + let(:credentials) { Concierge::Credentials.for('rentals_united') } + + context "when params are valid" do + let(:params) do + { + property_id: "321", + check_in: "2016-03-22", + check_out: "2016-03-25", + guests: 2 + } + end + + it "respond with successfull response" do + stub_data = read_fixture("rentals_united/quotations/success.xml") + stub_call(:post, credentials.url) { [200, {}, stub_data] } + + response = parse_response(subject.call(params)) + expect(response.status).to eq 200 + expect(response.body['status']).to eq("ok") + expect(response.body['available']).to be true + expect(response.body['property_id']).to eq("321") + expect(response.body['check_in']).to eq("2016-03-22") + expect(response.body['check_out']).to eq("2016-03-25") + expect(response.body['guests']).to eq(2) + expect(response.body['currency']).to eq("") + expect(response.body['total']).to eq(284.5) + expect(response.body['net_rate']).to eq(284.5) + expect(response.body['host_fee']).to eq(0.0) + expect(response.body['host_fee_percentage']).to be_nil + end + end + end +end + From 28a1da6b51f1190d686a9cfe3fd8bae1cc119d57 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 11:09:17 +0600 Subject: [PATCH 089/118] RU: documentation update --- apps/api/controllers/rentals_united/quote.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/api/controllers/rentals_united/quote.rb b/apps/api/controllers/rentals_united/quote.rb index 1104b1367..59ec17d70 100644 --- a/apps/api/controllers/rentals_united/quote.rb +++ b/apps/api/controllers/rentals_united/quote.rb @@ -11,12 +11,8 @@ class Quote # Make price (property rate) request # - # Usage - # - # It returns a Quotation object in both success and fail cases: - # - # API::Controllers::RentalsUnited::Quote.quote_price(selected_params) - # => Quotation(..) + # Returns a +Result+ wrapping a +Quotation+ when operation succeeds + # Returns a +Result+ with +Result::Error+ when operation fails def quote_price(params) credentials = Concierge::Credentials.for(supplier_name) RentalsUnited::Client.new(credentials).quote(params) From 79fe2ee9c98b42b0970bd5e7bae5b0cedd7f105e Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 11:12:53 +0600 Subject: [PATCH 090/118] RU: documentation update --- .../commands/quotation_fetcher.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb index 60f40a292..4ef5186a0 100644 --- a/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb @@ -8,14 +8,11 @@ module Commands # # Usage # - # command = RentalsUnited::Commands::QuotationFetcher.new(credentials) - # result = command.call(stay_params) - # - # if result.success? - # process_quotation(result.value) - # else - # handle_error(result.error) - # end + # command = RentalsUnited::Commands::QuotationFetcher.new( + # credentials, + # quotation_params + # ) + # result = command.call class QuotationFetcher < BaseFetcher attr_reader :quotation_params @@ -44,8 +41,8 @@ def initialize(credentials, quotation_params) # Calls the RentalsUnited API method using the HTTP client. # - # The +call+ method returns a +Result+ object that, when successful, - # encapsulates the resulting +Quotation+ object. + # Returns a +Result+ wrapping a +Quotation+ when operation succeeds + # Returns a +Result+ with +Result::Error+ when operation fails def call payload = build_payload result = http.post(credentials.url, payload, headers) From 5b3e227e5df1ad121c564204e93b655cf6ad5957 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 11:14:15 +0600 Subject: [PATCH 091/118] RU: documentation update --- lib/concierge/suppliers/rentals_united/mappers/quotation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb index d06cadd70..ff7e0260b 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb @@ -17,7 +17,7 @@ def initialize(quotation_params, price) @price = price end - # Builds available quotation + # Builds quotation # # Returns [Quotation] def build_quotation From 04214584135c0e9bbbd4a86cecf030a44333046d Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 29 Sep 2016 12:24:22 +0600 Subject: [PATCH 092/118] RU: pick up currency code for Quotation from the database --- .../suppliers/rentals_united/client.rb | 25 +++++++++++++++- .../commands/quotation_fetcher.rb | 8 +++-- .../rentals_united/mappers/quotation.rb | 12 ++++---- .../controllers/rentals_united/quote_spec.rb | 10 ++++++- .../suppliers/rentals_united/client_spec.rb | 30 +++++++++++++++++-- .../commands/quotation_fetcher_spec.rb | 11 +++++-- 6 files changed, 79 insertions(+), 17 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb index cb5970988..89fb409aa 100644 --- a/lib/concierge/suppliers/rentals_united/client.rb +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -12,6 +12,20 @@ def initialize(credentials) # Quote RentalsUnited properties prices # If an error happens in any step in the process of getting a response back # from RentalsUnited, a result object with error is returned + + # Arguments + # + # * +quotation_params+ [Concierge::SafeAccessHash] stay parameters + # + # Stay parameters are defined by the set of attributes from + # +API::Controllers::Params::MultiUnitQuote+ params object. + # + # +quotation_params+ object includes: + # + # * +property_id+ + # * +check_in+ + # * +check_out+ + # * +guests+ # # Usage # @@ -25,11 +39,20 @@ def initialize(credentials) # Returns a +Result+ wrapping a +Quotation+ when operation succeeds # Returns a +Result+ with +Result::Error+ when operation fails def quote(quotation_params) + property = find_property(quotation_params[:property_id]) + return Result.error(:property_not_found) unless property + command = RentalsUnited::Commands::QuotationFetcher.new( credentials, - quotation_params + quotation_params, + property.data.get("currency") ) command.call end + + private + def find_property(property_id) + PropertyRepository.identified_by(property_id).first + end end end diff --git a/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb index 4ef5186a0..0a666140f 100644 --- a/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb @@ -14,7 +14,7 @@ module Commands # ) # result = command.call class QuotationFetcher < BaseFetcher - attr_reader :quotation_params + attr_reader :quotation_params, :currency_code ROOT_TAG = "Pull_GetPropertyAvbPrice_RS" @@ -24,6 +24,7 @@ class QuotationFetcher < BaseFetcher # # * +credentials+ # * +quotation_params+ [Concierge::SafeAccessHash] stay parameters + # * +currency_code+ [String] currency code # # Stay parameters are defined by the set of attributes from # +API::Controllers::Params::MultiUnitQuote+ params object. @@ -34,9 +35,10 @@ class QuotationFetcher < BaseFetcher # * +check_in+ # * +check_out+ # * +guests+ - def initialize(credentials, quotation_params) + def initialize(credentials, quotation_params, currency_code) super(credentials) @quotation_params = quotation_params + @currency_code = currency_code end # Calls the RentalsUnited API method using the HTTP client. @@ -71,7 +73,7 @@ def build_payload def build_quotation(result_hash) price = result_hash.get("#{ROOT_TAG}.PropertyPrices.PropertyPrice") - mapper = Mappers::Quotation.new(quotation_params, price) + mapper = Mappers::Quotation.new(quotation_params, price, currency_code) mapper.build_quotation end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb index ff7e0260b..f9acab848 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb @@ -4,7 +4,7 @@ module Mappers # # This class is responsible for building a +Quotation+ object class Quotation - attr_reader :quotation_params, :price + attr_reader :quotation_params, :price, :currency_code # Initialize Quotation mapper # @@ -12,8 +12,10 @@ class Quotation # # * +quotation_params+ [Concierge::SafeAccessHash] quotation parameters # * +price+ [String] quotation price - def initialize(quotation_params, price) + # * +currency_code+ [String] currency code + def initialize(quotation_params, price, currency_code) @quotation_params = quotation_params + @currency_code = currency_code @price = price end @@ -26,15 +28,11 @@ def build_quotation check_in: quotation_params[:check_in].to_s, check_out: quotation_params[:check_out].to_s, guests: quotation_params[:guests], - currency: currency, + currency: currency_code, total: price ? price : 0, available: price ? true : false ) end - - def currency - nil - end end end end diff --git a/spec/api/controllers/rentals_united/quote_spec.rb b/spec/api/controllers/rentals_united/quote_spec.rb index fd556dfb1..b3c35154d 100644 --- a/spec/api/controllers/rentals_united/quote_spec.rb +++ b/spec/api/controllers/rentals_united/quote_spec.rb @@ -5,6 +5,14 @@ RSpec.describe API::Controllers::RentalsUnited::Quote do include Support::HTTPStubbing include Support::Fixtures + include Support::Factories + + before do + create_property( + identifier: '321', + data: { :currency => "USD" } + ) + end let(:params) do { @@ -56,7 +64,7 @@ def provoke_failure! expect(response.body['check_in']).to eq("2016-03-22") expect(response.body['check_out']).to eq("2016-03-25") expect(response.body['guests']).to eq(2) - expect(response.body['currency']).to eq("") + expect(response.body['currency']).to eq("USD") expect(response.body['total']).to eq(284.5) expect(response.body['net_rate']).to eq(284.5) expect(response.body['host_fee']).to eq(0.0) diff --git a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb index 58f20c0ed..5ba99c841 100644 --- a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb @@ -1,17 +1,41 @@ require "spec_helper" RSpec.describe RentalsUnited::Client do + include Support::Factories + let(:credentials) { Concierge::Credentials.for("rentals_united") } let(:client) { described_class.new(credentials) } describe "#quote" do - let(:params) { {} } + let(:property) do + create_property(identifier: '1234', data: { :currency => "USD" }) + end + + let(:quotation_params) do + { + property_id: property.identifier, + check_in: '2016-02-02', + check_out: '2016-02-03', + guests: 2 + } + end + + it "returns error if property does not exist" do + quotation_params[:property_id] = "unknown" - it "calls quotation fetcher class" do fetcher_class = RentalsUnited::Commands::QuotationFetcher + expect_any_instance_of(fetcher_class).not_to(receive(:call)) + result = client.quote(quotation_params) + expect(result.success?).to be false + expect(result.error.code).to eq(:property_not_found) + end + + it "calls quotation fetcher class if property exists" do + fetcher_class = RentalsUnited::Commands::QuotationFetcher expect_any_instance_of(fetcher_class).to(receive(:call)) - client.quote(params) + + client.quote(quotation_params) end end end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb index 05b6af23a..e3d2e0578 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb @@ -14,7 +14,14 @@ guests: 3 ) end - let(:subject) { described_class.new(credentials, quotation_params) } + let(:currency_code) { "EUR" } + let(:subject) do + described_class.new( + credentials, + quotation_params, + currency_code + ) + end let(:url) { credentials.url } it "performs successful request returning Quotation object" do @@ -33,7 +40,7 @@ expect(quotation.check_out).to eq(quotation_params[:check_out]) expect(quotation.guests).to eq(quotation_params[:guests]) expect(quotation.total).to eq(284.5) - expect(quotation.currency).to eq('') + expect(quotation.currency).to eq("EUR") expect(quotation.available).to be true end From d7e46146883822a3e1d4f9be245b52314b4c332d Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 29 Sep 2016 15:52:35 +0600 Subject: [PATCH 093/118] RU: price fetch rework --- .../suppliers/rentals_united/client.rb | 35 ++++++-- .../rentals_united/commands/price_fetcher.rb | 79 ++++++++++++++++++ .../commands/quotation_fetcher.rb | 81 ------------------- .../rentals_united/entities/price.rb | 19 +++++ .../suppliers/rentals_united/mappers/price.rb | 29 +++++++ .../rentals_united/mappers/quotation.rb | 29 ++++--- .../rentals_united/payload_builder.rb | 4 +- ...tion_fetch.xml.erb => price_fetch.xml.erb} | 0 .../controllers/rentals_united/quote_spec.rb | 26 +++++- .../suppliers/rentals_united/client_spec.rb | 27 +++++-- ..._fetcher_spec.rb => price_fetcher_spec.rb} | 42 +++------- .../rentals_united/mappers/price_spec.rb | 28 +++++++ .../rentals_united/mappers/quotation_spec.rb | 38 +++++++++ .../rentals_united/payload_builder_spec.rb | 20 ++--- 14 files changed, 308 insertions(+), 149 deletions(-) create mode 100644 lib/concierge/suppliers/rentals_united/commands/price_fetcher.rb delete mode 100644 lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb create mode 100644 lib/concierge/suppliers/rentals_united/entities/price.rb create mode 100644 lib/concierge/suppliers/rentals_united/mappers/price.rb rename lib/concierge/suppliers/rentals_united/templates/{quotation_fetch.xml.erb => price_fetch.xml.erb} (100%) rename spec/lib/concierge/suppliers/rentals_united/commands/{quotation_fetcher_spec.rb => price_fetcher_spec.rb} (75%) create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/price_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/mappers/quotation_spec.rb diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb index 89fb409aa..ef48ae48b 100644 --- a/lib/concierge/suppliers/rentals_united/client.rb +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -39,20 +39,45 @@ def initialize(credentials) # Returns a +Result+ wrapping a +Quotation+ when operation succeeds # Returns a +Result+ with +Result::Error+ when operation fails def quote(quotation_params) + host = find_host + return host_not_found unless host + property = find_property(quotation_params[:property_id]) - return Result.error(:property_not_found) unless property + return property_not_found unless property - command = RentalsUnited::Commands::QuotationFetcher.new( + command = RentalsUnited::Commands::PriceFetcher.new( credentials, - quotation_params, - property.data.get("currency") + quotation_params + ) + result = command.call + return result unless result.success? + price = result.value + + mapper = RentalsUnited::Mappers::Quotation.new( + price, + property.data.get("currency"), + host.fee_percentage, + quotation_params ) - command.call + Result.new(mapper.build_quotation) end private + def find_host + supplier = SupplierRepository.named(SUPPLIER_NAME) + HostRepository.from_supplier(supplier).first + end + def find_property(property_id) PropertyRepository.identified_by(property_id).first end + + def host_not_found + Result.error(:host_not_found) + end + + def property_not_found + Result.error(:property_not_found) + end end end diff --git a/lib/concierge/suppliers/rentals_united/commands/price_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/price_fetcher.rb new file mode 100644 index 000000000..fa9ed022a --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/price_fetcher.rb @@ -0,0 +1,79 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::PriceFetcher+ + # + # This class is responsible for wrapping the logic related to making a + # price fetch to RentalsUnited, parsing the response, and building the + # +Entities::Price with the data returned from their API. + # + # Usage + # + # command = RentalsUnited::Commands::PriceFetcher.new( + # credentials, + # stay_params + # ) + # result = command.call + class PriceFetcher < BaseFetcher + attr_reader :stay_params + + ROOT_TAG = "Pull_GetPropertyAvbPrice_RS" + + # Initialize +PriceFetcher+ command. + # + # Arguments + # + # * +credentials+ + # * +stay_params+ [Concierge::SafeAccessHash] stay parameters + # + # Stay parameters are defined by the set of attributes from + # +API::Controllers::Params::MultiUnitQuote+ params object. + # + # +stay_params+ object includes: + # + # * +property_id+ + # * +check_in+ + # * +check_out+ + # * +guests+ + def initialize(credentials, stay_params) + super(credentials) + @stay_params = stay_params + end + + # Calls the RentalsUnited API method using the HTTP client. + # + # Returns a +Result+ wrapping a +Entities::Price+ when operation succeeds + # Returns a +Result+ with +Result::Error+ when operation fails + def call + payload = build_payload + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_price(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_payload + payload_builder.build_price_fetch_payload( + property_id: stay_params[:property_id], + check_in: stay_params[:check_in], + check_out: stay_params[:check_out], + num_guests: stay_params[:guests] + ) + end + + def build_price(result_hash) + price = result_hash.get("#{ROOT_TAG}.PropertyPrices.PropertyPrice") + + mapper = Mappers::Price.new(price) + mapper.build_price + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb deleted file mode 100644 index 0a666140f..000000000 --- a/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher.rb +++ /dev/null @@ -1,81 +0,0 @@ -module RentalsUnited - module Commands - # +RentalsUnited::Commands::QuotationFetcher+ - # - # This class is responsible for wrapping the logic related to making a - # price quotation to RentalsUnited, parsing the response, and building the - # +Quotation+ object with the data returned from their API. - # - # Usage - # - # command = RentalsUnited::Commands::QuotationFetcher.new( - # credentials, - # quotation_params - # ) - # result = command.call - class QuotationFetcher < BaseFetcher - attr_reader :quotation_params, :currency_code - - ROOT_TAG = "Pull_GetPropertyAvbPrice_RS" - - # Initialize +QuotationFetcher+ command. - # - # Arguments - # - # * +credentials+ - # * +quotation_params+ [Concierge::SafeAccessHash] stay parameters - # * +currency_code+ [String] currency code - # - # Stay parameters are defined by the set of attributes from - # +API::Controllers::Params::MultiUnitQuote+ params object. - # - # +quotation_params+ object includes: - # - # * +property_id+ - # * +check_in+ - # * +check_out+ - # * +guests+ - def initialize(credentials, quotation_params, currency_code) - super(credentials) - @quotation_params = quotation_params - @currency_code = currency_code - end - - # Calls the RentalsUnited API method using the HTTP client. - # - # Returns a +Result+ wrapping a +Quotation+ when operation succeeds - # Returns a +Result+ with +Result::Error+ when operation fails - def call - payload = build_payload - result = http.post(credentials.url, payload, headers) - - return result unless result.success? - - result_hash = response_parser.to_hash(result.value.body) - - if valid_status?(result_hash, ROOT_TAG) - Result.new(build_quotation(result_hash)) - else - error_result(result_hash, ROOT_TAG) - end - end - - private - def build_payload - payload_builder.build_quotation_fetch_payload( - property_id: quotation_params[:property_id], - check_in: quotation_params[:check_in], - check_out: quotation_params[:check_out], - num_guests: quotation_params[:guests] - ) - end - - def build_quotation(result_hash) - price = result_hash.get("#{ROOT_TAG}.PropertyPrices.PropertyPrice") - - mapper = Mappers::Quotation.new(quotation_params, price, currency_code) - mapper.build_quotation - end - end - end -end diff --git a/lib/concierge/suppliers/rentals_united/entities/price.rb b/lib/concierge/suppliers/rentals_united/entities/price.rb new file mode 100644 index 000000000..59280d1f1 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/entities/price.rb @@ -0,0 +1,19 @@ +module RentalsUnited + module Entities + # +RentalsUnited::Entities::Price+ + # + # This entity represents a price object. + class Price + attr_accessor :total + + def initialize(total:, available:) + @total = total + @available = available + end + + def available? + @available + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/price.rb b/lib/concierge/suppliers/rentals_united/mappers/price.rb new file mode 100644 index 000000000..b3ac0aed9 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/price.rb @@ -0,0 +1,29 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Price+ + # + # This class is responsible for building a +Entities::Price+ object + class Price + attr_reader :price + + # Initialize Price mapper + # + # Arguments: + # + # * +price+ [String] price + def initialize(price) + @price = price.to_s + end + + # Builds price + # + # Returns [Entities::Price] + def build_price + Entities::Price.new( + total: price.to_f, + available: price.empty? ? false : true + ) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb index f9acab848..aa247de50 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb @@ -4,19 +4,21 @@ module Mappers # # This class is responsible for building a +Quotation+ object class Quotation - attr_reader :quotation_params, :price, :currency_code + attr_reader :price, :currency, :host_fee_percentage, :quotation_params # Initialize Quotation mapper # # Arguments: # + # * +price+ [Entities::Price] price + # * +currency+ [String] currency code + # * +host_fee_percentage+ [Integer] # * +quotation_params+ [Concierge::SafeAccessHash] quotation parameters - # * +price+ [String] quotation price - # * +currency_code+ [String] currency code - def initialize(quotation_params, price, currency_code) - @quotation_params = quotation_params - @currency_code = currency_code + def initialize(price, currency, host_fee_percentage, quotation_params) @price = price + @currency = currency + @host_fee_percentage = host_fee_percentage + @quotation_params = quotation_params end # Builds quotation @@ -24,13 +26,14 @@ def initialize(quotation_params, price, currency_code) # Returns [Quotation] def build_quotation ::Quotation.new( - property_id: quotation_params[:property_id], - check_in: quotation_params[:check_in].to_s, - check_out: quotation_params[:check_out].to_s, - guests: quotation_params[:guests], - currency: currency_code, - total: price ? price : 0, - available: price ? true : false + property_id: quotation_params[:property_id], + check_in: quotation_params[:check_in].to_s, + check_out: quotation_params[:check_out].to_s, + guests: quotation_params[:guests], + total: price.total, + available: price.available?, + currency: currency, + host_fee_percentage: host_fee_percentage ) end end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index ab7c95c46..bfde58014 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -67,7 +67,7 @@ def build_seasons_fetch_payload(property_id, date_from, date_to) render(:seasons_fetch, template_locals) end - def build_quotation_fetch_payload(property_id:, check_in:, check_out:, num_guests:) + def build_price_fetch_payload(property_id:, check_in:, check_out:, num_guests:) template_locals = { credentials: credentials, property_id: property_id, @@ -75,7 +75,7 @@ def build_quotation_fetch_payload(property_id:, check_in:, check_out:, num_guest check_out: check_out, num_guests: num_guests } - render(:quotation_fetch, template_locals) + render(:price_fetch, template_locals) end private diff --git a/lib/concierge/suppliers/rentals_united/templates/quotation_fetch.xml.erb b/lib/concierge/suppliers/rentals_united/templates/price_fetch.xml.erb similarity index 100% rename from lib/concierge/suppliers/rentals_united/templates/quotation_fetch.xml.erb rename to lib/concierge/suppliers/rentals_united/templates/price_fetch.xml.erb diff --git a/spec/api/controllers/rentals_united/quote_spec.rb b/spec/api/controllers/rentals_united/quote_spec.rb index b3c35154d..5e4cdf7b8 100644 --- a/spec/api/controllers/rentals_united/quote_spec.rb +++ b/spec/api/controllers/rentals_united/quote_spec.rb @@ -8,8 +8,11 @@ include Support::Factories before do + supplier = create_supplier(name: "rentals_united") + host = create_host(identifier: "ru-host", supplier_id: supplier.id) create_property( - identifier: '321', + identifier: "321", + host_id: host.id, data: { :currency => "USD" } ) end @@ -68,7 +71,26 @@ def provoke_failure! expect(response.body['total']).to eq(284.5) expect(response.body['net_rate']).to eq(284.5) expect(response.body['host_fee']).to eq(0.0) - expect(response.body['host_fee_percentage']).to be_nil + expect(response.body['host_fee_percentage']).to eq(0.0) + end + + it "respond with successfull response but unavailable quotation" do + stub_data = read_fixture("rentals_united/quotations/not_available.xml") + stub_call(:post, credentials.url) { [200, {}, stub_data] } + + response = parse_response(subject.call(params)) + expect(response.status).to eq 200 + expect(response.body['status']).to eq("ok") + expect(response.body['available']).to be false + expect(response.body['property_id']).to eq("321") + expect(response.body['check_in']).to eq("2016-03-22") + expect(response.body['check_out']).to eq("2016-03-25") + expect(response.body['guests']).to eq(2) + expect(response.body['currency']).to eq(nil) + expect(response.body['total']).to eq(nil) + expect(response.body['net_rate']).to eq(nil) + expect(response.body['host_fee']).to eq(nil) + expect(response.body['host_fee_percentage']).to eq(nil) end end end diff --git a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb index 5ba99c841..a81a4797f 100644 --- a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb @@ -7,13 +7,15 @@ let(:client) { described_class.new(credentials) } describe "#quote" do - let(:property) do + before do + supplier = create_supplier(name: "rentals_united") + host = create_host(identifier: "ru-host", supplier_id: supplier.id) create_property(identifier: '1234', data: { :currency => "USD" }) end let(:quotation_params) do { - property_id: property.identifier, + property_id: '1234', check_in: '2016-02-02', check_out: '2016-02-03', guests: 2 @@ -23,7 +25,7 @@ it "returns error if property does not exist" do quotation_params[:property_id] = "unknown" - fetcher_class = RentalsUnited::Commands::QuotationFetcher + fetcher_class = RentalsUnited::Commands::PriceFetcher expect_any_instance_of(fetcher_class).not_to(receive(:call)) result = client.quote(quotation_params) @@ -31,11 +33,22 @@ expect(result.error.code).to eq(:property_not_found) end - it "calls quotation fetcher class if property exists" do - fetcher_class = RentalsUnited::Commands::QuotationFetcher - expect_any_instance_of(fetcher_class).to(receive(:call)) + it "calls price fetcher class if property exists" do + price = RentalsUnited::Entities::Price.new( + total: 123.45, + available: true + ) - client.quote(quotation_params) + fetcher_class = RentalsUnited::Commands::PriceFetcher + expect_any_instance_of(fetcher_class) + .to(receive(:call)) + .and_return(Result.new(price)) + + result = client.quote(quotation_params) + expect(result.success?).to be true + + quotation = result.value + expect(quotation).to be_kind_of(Quotation) end end end diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/price_fetcher_spec.rb similarity index 75% rename from spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb rename to spec/lib/concierge/suppliers/rentals_united/commands/price_fetcher_spec.rb index e3d2e0578..04c07c802 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/quotation_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/price_fetcher_spec.rb @@ -1,12 +1,12 @@ require "spec_helper" -RSpec.describe RentalsUnited::Commands::QuotationFetcher do +RSpec.describe RentalsUnited::Commands::PriceFetcher do include Support::HTTPStubbing include Support::Fixtures let(:credentials) { Concierge::Credentials.for("rentals_united") } let(:property_id) { "1234" } - let(:quotation_params) do + let(:stay_params) do API::Controllers::Params::Quote.new( property_id: '1234', check_in: "2016-09-19", @@ -14,17 +14,10 @@ guests: 3 ) end - let(:currency_code) { "EUR" } - let(:subject) do - described_class.new( - credentials, - quotation_params, - currency_code - ) - end + let(:subject) { described_class.new(credentials, stay_params) } let(:url) { credentials.url } - it "performs successful request returning Quotation object" do + it "performs successful request returning Entities::Price object" do stub_data = read_fixture("rentals_united/quotations/success.xml") stub_call(:post, url) { [200, {}, stub_data] } @@ -33,15 +26,10 @@ expect(result).to be_kind_of(Result) expect(result).to be_success - quotation = result.value - expect(quotation).to be_kind_of(Quotation) - expect(quotation.property_id).to eq(quotation_params[:property_id]) - expect(quotation.check_in).to eq(quotation_params[:check_in]) - expect(quotation.check_out).to eq(quotation_params[:check_out]) - expect(quotation.guests).to eq(quotation_params[:guests]) - expect(quotation.total).to eq(284.5) - expect(quotation.currency).to eq("EUR") - expect(quotation.available).to be true + price = result.value + expect(price).to be_kind_of(RentalsUnited::Entities::Price) + expect(price.total).to eq(284.5) + expect(price.available?).to eq(true) end it "returns unavailable Quotation when property is not available" do @@ -52,15 +40,11 @@ expect(result).to be_kind_of(Result) expect(result).to be_success - - quotation = result.value - expect(quotation).to be_kind_of(Quotation) - expect(quotation.property_id).to eq(quotation_params[:property_id]) - expect(quotation.check_in).to eq(quotation_params[:check_in]) - expect(quotation.check_out).to eq(quotation_params[:check_out]) - expect(quotation.guests).to eq(quotation_params[:guests]) - expect(quotation.total).to eq(0) - expect(quotation.available).to be false + + price = result.value + expect(price).to be_kind_of(RentalsUnited::Entities::Price) + expect(price.total).to eq(0) + expect(price.available?).to eq(false) end it "returns an error when check_in is invalid" do diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/price_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/price_spec.rb new file mode 100644 index 000000000..d73e5b0be --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/price_spec.rb @@ -0,0 +1,28 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Mappers::Price do + context "when price exists" do + let(:value) { 123.50 } + let(:subject) { described_class.new(value) } + + it "builds price object" do + price = subject.build_price + expect(price).to be_kind_of(RentalsUnited::Entities::Price) + expect(price.total).to eq(value) + expect(price.available?).to be true + end + end + + context "when price does not exist" do + [nil, ""].each do |value| + let(:subject) { described_class.new(value) } + + it "builds price object" do + price = subject.build_price + expect(price).to be_kind_of(RentalsUnited::Entities::Price) + expect(price.total).to eq(0.0) + expect(price.available?).to be false + end + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/quotation_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/quotation_spec.rb new file mode 100644 index 000000000..4ca8a55a1 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/quotation_spec.rb @@ -0,0 +1,38 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Mappers::Quotation do + context "when price exists" do + let(:price) { RentalsUnited::Entities::Price.new(total: 123.45, available: true) } + let(:currency) { "USD" } + let(:host_fee_percentage) { 0 } + let(:quotation_params) do + API::Controllers::Params::Quote.new( + property_id: '1234', + check_in: "2016-09-19", + check_out: "2016-09-20", + guests: 3 + ) + end + let(:subject) do + described_class.new( + price, + currency, + host_fee_percentage, + quotation_params + ) + end + + it "builds quotation object" do + quotation = subject.build_quotation + expect(quotation).to be_kind_of(Quotation) + expect(quotation.property_id).to eq(quotation_params[:property_id]) + expect(quotation.check_in).to eq(quotation_params[:check_in]) + expect(quotation.check_out).to eq(quotation_params[:check_out]) + expect(quotation.guests).to eq(quotation_params[:guests]) + expect(quotation.total).to eq(price.total) + expect(quotation.available).to eq(price.available?) + expect(quotation.currency).to eq(currency) + expect(quotation.host_fee_percentage).to eq(host_fee_percentage) + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index 591c779d1..61317d77b 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -218,8 +218,8 @@ end end - describe '#build_quotation_fetch_payload' do - let(:quotaion_params) do + describe '#build_price_fetch_payload' do + let(:stay_params) do { property_id: "123", check_in: "2016-09-01", @@ -229,7 +229,7 @@ end it 'embedds username and password to request' do - xml = builder.build_quotation_fetch_payload(quotaion_params) + xml = builder.build_price_fetch_payload(stay_params) hash = to_hash(xml) authentication = hash.get("Pull_GetPropertyAvbPrice_RQ.Authentication") @@ -238,29 +238,29 @@ end it 'adds property_id to request' do - xml = builder.build_quotation_fetch_payload(quotaion_params) + xml = builder.build_price_fetch_payload(stay_params) hash = to_hash(xml) property_id = hash.get("Pull_GetPropertyAvbPrice_RQ.PropertyID") - expect(property_id).to eq(quotaion_params[:property_id]) + expect(property_id).to eq(stay_params[:property_id]) end it 'adds check in / check out dates to request' do - xml = builder.build_quotation_fetch_payload(quotaion_params) + xml = builder.build_price_fetch_payload(stay_params) hash = to_hash(xml) check_in = hash.get("Pull_GetPropertyAvbPrice_RQ.DateFrom").to_s check_out = hash.get("Pull_GetPropertyAvbPrice_RQ.DateTo").to_s - expect(check_in).to eq(quotaion_params[:check_in]) - expect(check_out).to eq(quotaion_params[:check_out]) + expect(check_in).to eq(stay_params[:check_in]) + expect(check_out).to eq(stay_params[:check_out]) end it 'adds num_guests to request' do - xml = builder.build_quotation_fetch_payload(quotaion_params) + xml = builder.build_price_fetch_payload(stay_params) hash = to_hash(xml) num_guests = hash.get("Pull_GetPropertyAvbPrice_RQ.NOP") - expect(num_guests).to eq(quotaion_params[:num_guests].to_s) + expect(num_guests).to eq(stay_params[:num_guests].to_s) end end From a590e801bdb4d28849338a4825a1c0af7148aa73 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 29 Sep 2016 17:56:19 +0600 Subject: [PATCH 094/118] RU: use SUPPLIER_NAME constant --- spec/api/controllers/rentals_united/quote_spec.rb | 10 ++++------ .../concierge/suppliers/rentals_united/client_spec.rb | 7 ++++--- .../rentals_united/commands/price_fetcher_spec.rb | 3 ++- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/api/controllers/rentals_united/quote_spec.rb b/spec/api/controllers/rentals_united/quote_spec.rb index 5e4cdf7b8..be91d2b0b 100644 --- a/spec/api/controllers/rentals_united/quote_spec.rb +++ b/spec/api/controllers/rentals_united/quote_spec.rb @@ -7,8 +7,11 @@ include Support::Fixtures include Support::Factories + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } + before do - supplier = create_supplier(name: "rentals_united") + supplier = create_supplier(name: supplier_name) host = create_host(identifier: "ru-host", supplier_id: supplier.id) create_property( identifier: "321", @@ -31,9 +34,6 @@ end it_behaves_like "external error reporting" do - let(:supplier_name) { "rentals_united" } - let(:credentials) { Concierge::Credentials.for(supplier_name) } - def provoke_failure! stub_call(:post, credentials.url) do raise Faraday::TimeoutError @@ -43,8 +43,6 @@ def provoke_failure! end describe "#call" do - let(:credentials) { Concierge::Credentials.for('rentals_united') } - context "when params are valid" do let(:params) do { diff --git a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb index a81a4797f..8b9e03772 100644 --- a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb @@ -3,14 +3,15 @@ RSpec.describe RentalsUnited::Client do include Support::Factories - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:client) { described_class.new(credentials) } describe "#quote" do before do - supplier = create_supplier(name: "rentals_united") + supplier = create_supplier(name: supplier_name) host = create_host(identifier: "ru-host", supplier_id: supplier.id) - create_property(identifier: '1234', data: { :currency => "USD" }) + create_property(identifier: '1234', host_id: host.id, data: { :currency => "USD" }) end let(:quotation_params) do diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/price_fetcher_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/price_fetcher_spec.rb index 04c07c802..28f6c873f 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/price_fetcher_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/price_fetcher_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:property_id) { "1234" } let(:stay_params) do API::Controllers::Params::Quote.new( From b8e18980efc6e7c3a8818d5023e6766a7795426e Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Mon, 3 Oct 2016 17:57:11 +0600 Subject: [PATCH 095/118] RU: do not pass host.fee_percentage to mapper --- lib/concierge/suppliers/rentals_united/client.rb | 1 - .../suppliers/rentals_united/mappers/quotation.rb | 9 +++------ .../suppliers/rentals_united/mappers/quotation_spec.rb | 10 ++++++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb index ef48ae48b..f278a4786 100644 --- a/lib/concierge/suppliers/rentals_united/client.rb +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -56,7 +56,6 @@ def quote(quotation_params) mapper = RentalsUnited::Mappers::Quotation.new( price, property.data.get("currency"), - host.fee_percentage, quotation_params ) Result.new(mapper.build_quotation) diff --git a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb index aa247de50..5db2a0ff1 100644 --- a/lib/concierge/suppliers/rentals_united/mappers/quotation.rb +++ b/lib/concierge/suppliers/rentals_united/mappers/quotation.rb @@ -4,7 +4,7 @@ module Mappers # # This class is responsible for building a +Quotation+ object class Quotation - attr_reader :price, :currency, :host_fee_percentage, :quotation_params + attr_reader :price, :currency, :quotation_params # Initialize Quotation mapper # @@ -12,12 +12,10 @@ class Quotation # # * +price+ [Entities::Price] price # * +currency+ [String] currency code - # * +host_fee_percentage+ [Integer] # * +quotation_params+ [Concierge::SafeAccessHash] quotation parameters - def initialize(price, currency, host_fee_percentage, quotation_params) + def initialize(price, currency, quotation_params) @price = price @currency = currency - @host_fee_percentage = host_fee_percentage @quotation_params = quotation_params end @@ -32,8 +30,7 @@ def build_quotation guests: quotation_params[:guests], total: price.total, available: price.available?, - currency: currency, - host_fee_percentage: host_fee_percentage + currency: currency ) end end diff --git a/spec/lib/concierge/suppliers/rentals_united/mappers/quotation_spec.rb b/spec/lib/concierge/suppliers/rentals_united/mappers/quotation_spec.rb index 4ca8a55a1..6472364a3 100644 --- a/spec/lib/concierge/suppliers/rentals_united/mappers/quotation_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/mappers/quotation_spec.rb @@ -1,13 +1,16 @@ require "spec_helper" RSpec.describe RentalsUnited::Mappers::Quotation do + include Support::Factories + context "when price exists" do + let!(:host) { create_host(fee_percentage: 7.0) } + let!(:property) { create_property(identifier: "567", host_id: host.id) } let(:price) { RentalsUnited::Entities::Price.new(total: 123.45, available: true) } let(:currency) { "USD" } - let(:host_fee_percentage) { 0 } let(:quotation_params) do API::Controllers::Params::Quote.new( - property_id: '1234', + property_id: property.identifier, check_in: "2016-09-19", check_out: "2016-09-20", guests: 3 @@ -17,7 +20,6 @@ described_class.new( price, currency, - host_fee_percentage, quotation_params ) end @@ -32,7 +34,7 @@ expect(quotation.total).to eq(price.total) expect(quotation.available).to eq(price.available?) expect(quotation.currency).to eq(currency) - expect(quotation.host_fee_percentage).to eq(host_fee_percentage) + expect(quotation.host_fee_percentage).to eq(7.0) end end end From f619498e4e8dfa9d282f09fdd145275d894e4e9a Mon Sep 17 00:00:00 2001 From: keang Date: Mon, 3 Oct 2016 16:05:32 +0800 Subject: [PATCH 096/118] Fix credential key --- .../validate_supplier_credentials.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/api/config/initializers/validate_supplier_credentials.rb b/apps/api/config/initializers/validate_supplier_credentials.rb index 5b930e4cc..d5dca784e 100644 --- a/apps/api/config/initializers/validate_supplier_credentials.rb +++ b/apps/api/config/initializers/validate_supplier_credentials.rb @@ -2,14 +2,14 @@ if enforce_on_envs.include?(Hanami.env) Concierge::Credentials.validate_credentials!({ - atleisure: %w(username password test_mode), - jtb: %w(id user password company url), - kigo: %w(subscription_key), - kigolegacy: %w(username password), - waytostay: %w(client_id client_secret url token_url), - ciirus: %w(url username password), - saw: %w(username password url), - poplidays: %w(url client_key passphrase), - rentals_united: %w(username password url) + atleisure: %w(username password test_mode), + jtb: %w(id user password company url), + kigo: %w(subscription_key), + kigolegacy: %w(username password), + waytostay: %w(client_id client_secret url token_url), + ciirus: %w(url username password), + saw: %w(username password url), + poplidays: %w(url client_key passphrase), + rentalsunited: %w(username password url) }) end From f1b6cc2a85452dc5fb3716c66525100c16b10088 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 08:08:28 +0600 Subject: [PATCH 097/118] RU: add controller route for booking --- apps/api/config/routes.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/api/config/routes.rb b/apps/api/config/routes.rb index a854bd0d1..377fd6b7c 100644 --- a/apps/api/config/routes.rb +++ b/apps/api/config/routes.rb @@ -7,14 +7,15 @@ post '/waytostay/quote', to: 'waytostay#quote' post '/saw/quote', to: 's_a_w#quote' -post '/jtb/booking', to: 'j_t_b#booking' -post '/atleisure/booking', to: 'at_leisure#booking' -post '/waytostay/booking', to: 'waytostay#booking' -post '/ciirus/booking', to: 'ciirus#booking' -post '/kigo/booking', to: 'kigo#booking' -post '/kigo/legacy/booking', to: 'kigo/legacy#booking' -post '/saw/booking', to: 's_a_w#booking' -post '/poplidays/booking', to: 'poplidays#booking' +post '/jtb/booking', to: 'j_t_b#booking' +post '/atleisure/booking', to: 'at_leisure#booking' +post '/waytostay/booking', to: 'waytostay#booking' +post '/ciirus/booking', to: 'ciirus#booking' +post '/kigo/booking', to: 'kigo#booking' +post '/kigo/legacy/booking', to: 'kigo/legacy#booking' +post '/saw/booking', to: 's_a_w#booking' +post '/poplidays/booking', to: 'poplidays#booking' +post '/rentals_united/booking', to: 'rentals_united#booking' post '/waytostay/cancel', to: 'waytostay#cancel' post '/ciirus/cancel', to: 'ciirus#cancel' From a1bf91b6592105c526852737f85222a244601cc8 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 10:04:59 +0600 Subject: [PATCH 098/118] RU: implement booking --- .../api/controllers/rentals_united/booking.rb | 31 +++++++ .../suppliers/rentals_united/client.rb | 24 +++++ .../rentals_united/commands/booking.rb | 89 ++++++++++++++++++ .../rentals_united/payload_builder.rb | 18 ++++ .../rentals_united/templates/booking.xml.erb | 30 +++++++ .../reservations/not_available.xml | 4 + .../rentals_united/reservations/success.xml | 4 + .../rentals_united/commands/booking_spec.rb | 85 ++++++++++++++++++ .../rentals_united/payload_builder_spec.rb | 90 +++++++++++++++++++ 9 files changed, 375 insertions(+) create mode 100644 apps/api/controllers/rentals_united/booking.rb create mode 100644 lib/concierge/suppliers/rentals_united/commands/booking.rb create mode 100644 lib/concierge/suppliers/rentals_united/templates/booking.xml.erb create mode 100644 spec/fixtures/rentals_united/reservations/not_available.xml create mode 100644 spec/fixtures/rentals_united/reservations/success.xml create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb diff --git a/apps/api/controllers/rentals_united/booking.rb b/apps/api/controllers/rentals_united/booking.rb new file mode 100644 index 000000000..c6648bf7b --- /dev/null +++ b/apps/api/controllers/rentals_united/booking.rb @@ -0,0 +1,31 @@ +require_relative "../booking" +require_relative "../params/booking" + +module API::Controllers::RentalsUnited + + # +API::Controllers::RentalsUnited::Booking+ + # + # Performs create booking for properties from RentalsUnited. + class Booking + include API::Controllers::Booking + + params API::Controllers::Params::Booking + + # Make property booking request + # + # Usage + # + # It returns a +Reservation+ object in both success and fail cases: + # + # API::Controllers::RentalsUnited::Booking.create_booking(selected_params) + # => Reservation(..) + def create_booking(params) + credentials = Concierge::Credentials.for(supplier_name) + RentalsUnited::Client.new(credentials).book(params) + end + + def supplier_name + RentalsUnited::Client::SUPPLIER_NAME + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb index 4d6e28cad..e7ae47c39 100644 --- a/lib/concierge/suppliers/rentals_united/client.rb +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -8,5 +8,29 @@ class Client def initialize(credentials) @credentials = credentials end + + # RentalsUnited properties booking. + # + # If an error happens in any step in the process of getting a response back + # from RentalsUnited, a result object with error is returned + # + # Usage + # + # comamnd = RentalsUnited::Client.new(credentials, reservation_params) + # result = command.book + # + # if result.sucess? + # # ... + # end + # + # Returns a +Result+ wrapping a +Reservation+ when operation succeeds + # Returns a +Result+ with +Result::Error+ when operation fails + def book(reservation_params) + command = RentalsUnited::Commands::Booking.new( + credentials, + reservation_params + ) + command.call + end end end diff --git a/lib/concierge/suppliers/rentals_united/commands/booking.rb b/lib/concierge/suppliers/rentals_united/commands/booking.rb new file mode 100644 index 000000000..188837a51 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/booking.rb @@ -0,0 +1,89 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::Booking+ + # + # This class is responsible for wrapping the logic related to making a + # reservation to RentalsUnited, parsing the response, and building the + # +Reservation+ object with the data returned from their API. + # + # Usage + # + # command = RentalsUnited::Commands::Booking.new(credentials, params) + # result = command.call + # + # if result.success? + # process_reservation(result.value) + # else + # handle_error(result.error) + # end + class Booking < BaseFetcher + attr_reader :reservation_params + + ROOT_TAG = "Push_PutConfirmedReservationMulti_RS" + + # Initialize +Booking+ command. + # + # Arguments + # + # * +credentials+ + # * +reservation_params+ [Concierge::SafeAccessHash] stay parameters + # + # Stay parameters are defined by the set of attributes from + # +API::Controllers::Params::Booking+ params object. + # + # +reservation_params+ object includes: + # + # * +property_id+ + # * +check_in+ + # * +check_out+ + # * +guests+ + def initialize(credentials, reservation_params) + super(credentials) + @reservation_params = reservation_params + end + + # Calls the RentalsUnited API method using the HTTP client. + # + # The +call+ method returns a +Result+ object that, when successful, + # encapsulates the resulting +Reservation+ object. + def call + payload = build_payload + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(build_reservation(result_hash)) + else + error_result(result_hash, ROOT_TAG) + end + end + + private + def build_payload + payload_builder.build_booking_payload( + property_id: reservation_params[:property_id], + check_in: reservation_params[:check_in], + check_out: reservation_params[:check_out], + num_guests: reservation_params[:guests], + total: reservation_params[:subtotal], + user: { + first_name: reservation_params[:customer][:first_name], + last_name: reservation_params[:customer][:last_name], + email: reservation_params[:customer][:email], + phone: reservation_params[:customer][:phone], + address: reservation_params[:customer][:address], + postal_code: reservation_params[:customer][:postal_code] + } + ) + end + + def build_reservation(result_hash) + reservation_code = result_hash.get("#{ROOT_TAG}.ReservationID") + ::Reservation.new(reference_number: reservation_code) + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index bf968e9c3..7d22b74ba 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -67,6 +67,24 @@ def build_seasons_fetch_payload(property_id, date_from, date_to) render(:seasons_fetch, template_locals) end + def build_booking_payload(property_id:, check_in:, check_out:, num_guests:, total:, user:) + template_locals = { + credentials: credentials, + property_id: property_id, + check_in: check_in, + check_out: check_out, + num_guests: num_guests, + total: total, + first_name: user.fetch(:first_name), + last_name: user.fetch(:last_name), + email: user.fetch(:email), + phone: user.fetch(:phone), + address: user.fetch(:address), + postal_code: user.fetch(:postal_code) + } + render(:booking, template_locals) + end + private def render(template_name, local_vars) path = Hanami.root.join(TEMPLATES_PATH, "#{template_name}.xml.erb") diff --git a/lib/concierge/suppliers/rentals_united/templates/booking.xml.erb b/lib/concierge/suppliers/rentals_united/templates/booking.xml.erb new file mode 100644 index 000000000..f2c361b5d --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/booking.xml.erb @@ -0,0 +1,30 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + + + + <%= property_id %> + <%= check_in %> + <%= check_out %> + <%= num_guests %> + + <%= total %> + <%= total %> + <%= total %> + + + + + <%= first_name %> + <%= last_name %> + <%= email %> + <%= phone %> +
<%= address %>
+ <%= postal_code %> +
+ +
+
diff --git a/spec/fixtures/rentals_united/reservations/not_available.xml b/spec/fixtures/rentals_united/reservations/not_available.xml new file mode 100644 index 000000000..74610004e --- /dev/null +++ b/spec/fixtures/rentals_united/reservations/not_available.xml @@ -0,0 +1,4 @@ + + Property is not available for a given dates + 0 + diff --git a/spec/fixtures/rentals_united/reservations/success.xml b/spec/fixtures/rentals_united/reservations/success.xml new file mode 100644 index 000000000..208c98922 --- /dev/null +++ b/spec/fixtures/rentals_united/reservations/success.xml @@ -0,0 +1,4 @@ + + Success + 90377000 + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb new file mode 100644 index 000000000..54024ea69 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb @@ -0,0 +1,85 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::Booking do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:reservation_params) do + API::Controllers::Params::Booking.new( + property_id: '1', + check_in: '02/02/2016', + check_out: '03/02/2016', + guests: 1, + currency_code: 'EUR', + subtotal: '123.45', + customer: { + first_name: 'Test', + last_name: 'User', + email: 'testuser@example.com', + phone: '111-222-3333', + display: 'Test User' + } + ) + end + let(:subject) { described_class.new(credentials, reservation_params) } + + it "successfully creates a reservation" do + stub_data = read_fixture("rentals_united/reservations/success.xml") + stub_call(:post, credentials.url) { [200, {}, stub_data] } + + result = subject.call + expect(result.success?).to be true + expect(result.value).to be_kind_of(Reservation) + expect(result.value.reference_number).to eq("90377000") + end + + it "fails when property is not available for a given dates" do + stub_data = read_fixture("rentals_united/reservations/not_available.xml") + stub_call(:post, credentials.url) { [200, {}, stub_data] } + + result = subject.call + expect(result).not_to be_success + expect(result.error.code).to eq("1") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `1`, and description `Property is not available for a given dates`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, credentials.url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, credentials.url) { raise Faraday::TimeoutError } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index cf4c91d21..9c23fd0c7 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -218,6 +218,96 @@ end end + describe '#build_booking_payload' do + let(:reservation_params) do + { + property_id: "123", + check_in: "2016-09-01", + check_out: "2016-09-02", + num_guests: 3, + total: "125.40", + user: { + first_name: "Test", + last_name: "User", + email: "testuser@example.com", + phone: "111-222-333", + address: "Address st 45", + postal_code: "98456" + } + } + end + + let(:root) { "Push_PutConfirmedReservationMulti_RQ" } + let(:reservation_root) { "#{root}.Reservation" } + let(:stay_info) { "#{reservation_root}.StayInfos.StayInfo" } + + it 'embedds username and password to request' do + xml = builder.build_booking_payload(reservation_params) + hash = to_hash(xml) + + authentication = hash.get("#{root}.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + + it 'adds property_id to request' do + xml = builder.build_booking_payload(reservation_params) + hash = to_hash(xml) + + property_id = hash.get("#{stay_info}.PropertyID") + expect(property_id).to eq(reservation_params[:property_id]) + end + + it 'adds check in / check out dates to request' do + xml = builder.build_booking_payload(reservation_params) + hash = to_hash(xml) + + check_in = hash.get("#{stay_info}.DateFrom").to_s + check_out = hash.get("#{stay_info}.DateTo").to_s + expect(check_in).to eq(reservation_params[:check_in]) + expect(check_out).to eq(reservation_params[:check_out]) + end + + it 'adds num_guests to request' do + xml = builder.build_booking_payload(reservation_params) + hash = to_hash(xml) + + num_guests = hash.get("#{stay_info}.NumberOfGuests") + expect(num_guests).to eq(reservation_params[:num_guests].to_s) + end + + it 'adds total price to request' do + xml = builder.build_booking_payload(reservation_params) + hash = to_hash(xml) + + ru_price = hash.get("#{stay_info}.Costs.RUPrice") + client_price = hash.get("#{stay_info}.Costs.ClientPrice") + already_paid = hash.get("#{stay_info}.Costs.AlreadyPaid") + expect(ru_price).to eq(reservation_params[:total]) + expect(client_price).to eq(reservation_params[:total]) + expect(already_paid).to eq(reservation_params[:total]) + end + + it 'adds user information to request' do + xml = builder.build_booking_payload(reservation_params) + hash = to_hash(xml) + + first_name = hash.get("#{reservation_root}.CustomerInfo.Name") + last_name = hash.get("#{reservation_root}.CustomerInfo.SurName") + email = hash.get("#{reservation_root}.CustomerInfo.Email") + phone = hash.get("#{reservation_root}.CustomerInfo.Phone") + address = hash.get("#{reservation_root}.CustomerInfo.Address") + postal_code = hash.get("#{reservation_root}.CustomerInfo.ZipCode") + + expect(first_name).to eq(reservation_params[:user][:first_name]) + expect(last_name).to eq(reservation_params[:user][:last_name]) + expect(email).to eq(reservation_params[:user][:email]) + expect(phone).to eq(reservation_params[:user][:phone]) + expect(address).to eq(reservation_params[:user][:address]) + expect(postal_code).to eq(reservation_params[:user][:postal_code]) + end + end + private def to_hash(xml) Concierge::SafeAccessHash.new(Nori.new.parse(xml)) From 68de4e7011a0d8d2a1062ef717aa8552efb5a5fd Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 10:09:36 +0600 Subject: [PATCH 099/118] RU: client spec --- .../suppliers/rentals_united/client_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 spec/lib/concierge/suppliers/rentals_united/client_spec.rb diff --git a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb new file mode 100644 index 000000000..7042c8507 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb @@ -0,0 +1,18 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Client do + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:client) { described_class.new(credentials) } + + describe "#book" do + let(:params) { {} } + + it "calls quotation fetcher class" do + fetcher_class = RentalsUnited::Commands::Booking + + expect_any_instance_of(fetcher_class).to(receive(:call)) + client.book(params) + end + end +end + From 2f9421606cab470c20bba99497eb88430ca719cf Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 10:41:06 +0600 Subject: [PATCH 100/118] RU: add controller booking specs --- .../rentals_united/commands/booking.rb | 4 +- .../rentals_united/mappers/reservation.rb | 32 +++++++++++ .../rentals_united/booking_spec.rb | 56 +++++++++++++++++++ 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 lib/concierge/suppliers/rentals_united/mappers/reservation.rb create mode 100644 spec/api/controllers/rentals_united/booking_spec.rb diff --git a/lib/concierge/suppliers/rentals_united/commands/booking.rb b/lib/concierge/suppliers/rentals_united/commands/booking.rb index 188837a51..e8953b106 100644 --- a/lib/concierge/suppliers/rentals_united/commands/booking.rb +++ b/lib/concierge/suppliers/rentals_united/commands/booking.rb @@ -82,7 +82,9 @@ def build_payload def build_reservation(result_hash) reservation_code = result_hash.get("#{ROOT_TAG}.ReservationID") - ::Reservation.new(reference_number: reservation_code) + + mapper = Mappers::Reservation.new(reservation_code, reservation_params) + mapper.build_reservation end end end diff --git a/lib/concierge/suppliers/rentals_united/mappers/reservation.rb b/lib/concierge/suppliers/rentals_united/mappers/reservation.rb new file mode 100644 index 000000000..93e18c301 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/mappers/reservation.rb @@ -0,0 +1,32 @@ +module RentalsUnited + module Mappers + # +RentalsUnited::Mappers::Reservation+ + # + # This class is responsible for building a +Reservation+ object + class Reservation + attr_reader :reservation_code, :reservation_params + + # Initialize Reservation mapper + # + # Arguments: + # + # * +reservation_code+ [String] reservation code + # * +reservation_params+ [Concierge::SafeAccessHash] parameters + def initialize(reservation_code, reservation_params) + @reservation_code = reservation_code + @reservation_params = reservation_params + end + + # Builds reservation + # + # Returns [Reservation] + def build_reservation + ::Reservation.new( + reservation_params.to_h.merge!( + reference_number: reservation_code + ) + ) + end + end + end +end diff --git a/spec/api/controllers/rentals_united/booking_spec.rb b/spec/api/controllers/rentals_united/booking_spec.rb new file mode 100644 index 000000000..2b80abeab --- /dev/null +++ b/spec/api/controllers/rentals_united/booking_spec.rb @@ -0,0 +1,56 @@ +require "spec_helper" +require_relative "../shared/booking_validations" + +RSpec.describe API::Controllers::RentalsUnited::Booking do + include Support::HTTPStubbing + include Support::Fixtures + + let(:params) do + { + property_id: '588999', + check_in: '2016-02-02', + check_out: '2016-02-03', + guests: 1, + currency_code: 'EUR', + subtotal: '123.45', + customer: { + first_name: 'Test', + last_name: 'User', + email: 'testuser@example.com' + } + } + end + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:safe_params) { Concierge::SafeAccessHash.new(params) } + let(:controller) { described_class.new } + + it_behaves_like "performing booking parameters validations", controller_generator: -> { described_class.new } + + it "returns success response if booking request is completed successfully" do + stub_data = read_fixture("rentals_united/reservations/success.xml") + stub_call(:post, credentials.url) { [200, {}, stub_data] } + + response = parse_response(controller.call(params)) + expect(response.status).to eq 200 + expect(response.body['status']).to eq('ok') + expect(response.body['reference_number']).to eq('90377000') + expect(response.body['property_id']).to eq('588999') + expect(response.body['check_in']).to eq('2016-02-02') + expect(response.body['check_out']).to eq('2016-02-03') + expect(response.body['guests']).to eq(1) + expect(response.body['customer']).to eq(params[:customer]) + end + + it "returns error response if booking request failed" do + stub_data = read_fixture("rentals_united/reservations/not_available.xml") + stub_call(:post, credentials.url) { [200, {}, stub_data] } + + response = parse_response(controller.call(params)) + expect(response.status).to eq 503 + expect(response.body['status']).to eq('error') + expect(response["body"]["errors"]["booking"]).to eq( + "Could not create booking with remote supplier" + ) + end +end From c538f92e5a41bfacbbf222d36b4f82c8abff40d9 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 10:47:55 +0600 Subject: [PATCH 101/118] RU: add more expectations for booking command spec --- .../suppliers/rentals_united/commands/booking_spec.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb index 54024ea69..4e187cdd2 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb @@ -8,8 +8,8 @@ let(:reservation_params) do API::Controllers::Params::Booking.new( property_id: '1', - check_in: '02/02/2016', - check_out: '03/02/2016', + check_in: '2016-02-02', + check_out: '2016-02-03', guests: 1, currency_code: 'EUR', subtotal: '123.45', @@ -32,6 +32,10 @@ expect(result.success?).to be true expect(result.value).to be_kind_of(Reservation) expect(result.value.reference_number).to eq("90377000") + expect(result.value.property_id).to eq("1") + expect(result.value.check_in).to eq("2016-02-02") + expect(result.value.check_out).to eq("2016-02-03") + expect(result.value.guests).to eq(1) end it "fails when property is not available for a given dates" do From b0bf24fcca4e644cea8e29ee7dc1030c14e99b64 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 10:56:23 +0600 Subject: [PATCH 102/118] RU: documentation update --- lib/concierge/suppliers/rentals_united/client.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb index e7ae47c39..c683b3f7f 100644 --- a/lib/concierge/suppliers/rentals_united/client.rb +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -16,12 +16,8 @@ def initialize(credentials) # # Usage # - # comamnd = RentalsUnited::Client.new(credentials, reservation_params) - # result = command.book - # - # if result.sucess? - # # ... - # end + # client = RentalsUnited::Client.new(credentials) + # result = client.book(reservation_params) # # Returns a +Result+ wrapping a +Reservation+ when operation succeeds # Returns a +Result+ with +Result::Error+ when operation fails From 21046617b8264f5b63a52b41d706f5a9cbbcf166 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 10:57:47 +0600 Subject: [PATCH 103/118] RU: documentation update --- lib/concierge/suppliers/rentals_united/commands/booking.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/commands/booking.rb b/lib/concierge/suppliers/rentals_united/commands/booking.rb index e8953b106..5ac5bfacd 100644 --- a/lib/concierge/suppliers/rentals_united/commands/booking.rb +++ b/lib/concierge/suppliers/rentals_united/commands/booking.rb @@ -44,8 +44,8 @@ def initialize(credentials, reservation_params) # Calls the RentalsUnited API method using the HTTP client. # - # The +call+ method returns a +Result+ object that, when successful, - # encapsulates the resulting +Reservation+ object. + # Returns a +Result+ wrapping a +Reservation+ when operation succeeds + # Returns a +Result+ with +Result::Error+ when operation fails def call payload = build_payload result = http.post(credentials.url, payload, headers) From bb2e244872f9cdfbd809f2d05a8de61dfc469292 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 11:00:25 +0600 Subject: [PATCH 104/118] RU: use SafeAccessHash's get() --- .../rentals_united/commands/booking.rb | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/concierge/suppliers/rentals_united/commands/booking.rb b/lib/concierge/suppliers/rentals_united/commands/booking.rb index 5ac5bfacd..787da7eee 100644 --- a/lib/concierge/suppliers/rentals_united/commands/booking.rb +++ b/lib/concierge/suppliers/rentals_united/commands/booking.rb @@ -64,18 +64,18 @@ def call private def build_payload payload_builder.build_booking_payload( - property_id: reservation_params[:property_id], - check_in: reservation_params[:check_in], - check_out: reservation_params[:check_out], - num_guests: reservation_params[:guests], - total: reservation_params[:subtotal], + property_id: reservation_params.get("property_id"), + check_in: reservation_params.get("check_in"), + check_out: reservation_params.get("check_out"), + num_guests: reservation_params.get("guests"), + total: reservation_params.get("subtotal"), user: { - first_name: reservation_params[:customer][:first_name], - last_name: reservation_params[:customer][:last_name], - email: reservation_params[:customer][:email], - phone: reservation_params[:customer][:phone], - address: reservation_params[:customer][:address], - postal_code: reservation_params[:customer][:postal_code] + first_name: reservation_params.get("customer.first_name"), + last_name: reservation_params.get("customer.last_name"), + email: reservation_params.get("customer.email"), + phone: reservation_params.get("customer.phone"), + address: reservation_params.get("customer.address"), + postal_code: reservation_params.get("customer.postal_code") } ) end From 10f89c786fb7f411501dab5fae532f709a320067 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 11:03:15 +0600 Subject: [PATCH 105/118] RU: documentation update --- lib/concierge/suppliers/rentals_united/commands/booking.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/concierge/suppliers/rentals_united/commands/booking.rb b/lib/concierge/suppliers/rentals_united/commands/booking.rb index 787da7eee..cf84633cf 100644 --- a/lib/concierge/suppliers/rentals_united/commands/booking.rb +++ b/lib/concierge/suppliers/rentals_united/commands/booking.rb @@ -37,6 +37,8 @@ class Booking < BaseFetcher # * +check_in+ # * +check_out+ # * +guests+ + # * +subtotal+ + # * +customer+ def initialize(credentials, reservation_params) super(credentials) @reservation_params = reservation_params From e83dc6c9ae4f1baae9a472bd6957ca36394cb2f3 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Thu, 29 Sep 2016 18:01:29 +0600 Subject: [PATCH 106/118] RU: use SUPPLIER_NAME constant --- spec/api/controllers/rentals_united/booking_spec.rb | 3 ++- spec/lib/concierge/suppliers/rentals_united/client_spec.rb | 3 ++- .../suppliers/rentals_united/commands/booking_spec.rb | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/api/controllers/rentals_united/booking_spec.rb b/spec/api/controllers/rentals_united/booking_spec.rb index 2b80abeab..d0604fc72 100644 --- a/spec/api/controllers/rentals_united/booking_spec.rb +++ b/spec/api/controllers/rentals_united/booking_spec.rb @@ -21,7 +21,8 @@ } end - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:safe_params) { Concierge::SafeAccessHash.new(params) } let(:controller) { described_class.new } diff --git a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb index 7042c8507..e41f4ebd9 100644 --- a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb @@ -1,7 +1,8 @@ require "spec_helper" RSpec.describe RentalsUnited::Client do - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:client) { described_class.new(credentials) } describe "#book" do diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb index 4e187cdd2..735d2b0eb 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/booking_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:reservation_params) do API::Controllers::Params::Booking.new( property_id: '1', From 3ef04fb6f202838815fe6912a92e48feddf44824 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 17:23:54 +0600 Subject: [PATCH 107/118] RU: add cancel controller route --- apps/api/config/routes.rb | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/api/config/routes.rb b/apps/api/config/routes.rb index a854bd0d1..546f1ed15 100644 --- a/apps/api/config/routes.rb +++ b/apps/api/config/routes.rb @@ -16,13 +16,14 @@ post '/saw/booking', to: 's_a_w#booking' post '/poplidays/booking', to: 'poplidays#booking' -post '/waytostay/cancel', to: 'waytostay#cancel' -post '/ciirus/cancel', to: 'ciirus#cancel' -post '/saw/cancel', to: 's_a_w#cancel' -post '/kigo/cancel', to: 'kigo#cancel' -post '/kigo/legacy/cancel', to: 'kigo/legacy#cancel' -post '/poplidays/cancel', to: 'poplidays#cancel' -post '/atleisure/cancel', to: 'at_leisure#cancel' +post '/waytostay/cancel', to: 'waytostay#cancel' +post '/ciirus/cancel', to: 'ciirus#cancel' +post '/saw/cancel', to: 's_a_w#cancel' +post '/kigo/cancel', to: 'kigo#cancel' +post '/kigo/legacy/cancel', to: 'kigo/legacy#cancel' +post '/poplidays/cancel', to: 'poplidays#cancel' +post '/atleisure/cancel', to: 'at_leisure#cancel' +post 'rentals_united/cancel', to: 'rentals_united#cancel' post '/checkout', to: 'static#checkout' get '/kigo/image/:property_id/:image_id', to: 'kigo#image' From fb4183439b39cbf9ea4f7b9cfda371640ceabebf Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 17:25:53 +0600 Subject: [PATCH 108/118] RU: add cancel controller --- apps/api/controllers/rentals_united/cancel.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 apps/api/controllers/rentals_united/cancel.rb diff --git a/apps/api/controllers/rentals_united/cancel.rb b/apps/api/controllers/rentals_united/cancel.rb new file mode 100644 index 000000000..fb3d0fa61 --- /dev/null +++ b/apps/api/controllers/rentals_united/cancel.rb @@ -0,0 +1,21 @@ +require_relative "../cancel" + +module API::Controllers::RentalsUnited + # +API::Controllers::RentalsUnited::Cancel+ + # + # Cancels reservation from RentalsUnited. + class Cancel + include API::Controllers::Cancel + + params API::Controllers::Params::Cancel + + def cancel_reservation(params) + credentials = Concierge::Credentials.for(supplier_name) + RentalsUnited::Client.new(credentials).cancel(params) + end + + def supplier_name + RentalsUnited::Client::SUPPLIER_NAME + end + end +end From 0e7821610cba33e7779bd3f38c83d2be7a041d05 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 18:01:23 +0600 Subject: [PATCH 109/118] RU: add cancel command --- .../suppliers/rentals_united/client.rb | 18 +++++ .../rentals_united/commands/cancel.rb | 48 ++++++++++++ .../rentals_united/payload_builder.rb | 8 ++ .../rentals_united/templates/cancel.xml.erb | 7 ++ .../rentals_united/cancel/does_not_exist.xml | 3 + .../rentals_united/cancel/success.xml | 3 + .../rentals_united/commands/cancel_spec.rb | 74 +++++++++++++++++++ .../rentals_united/payload_builder_spec.rb | 25 +++++++ 8 files changed, 186 insertions(+) create mode 100644 lib/concierge/suppliers/rentals_united/commands/cancel.rb create mode 100644 lib/concierge/suppliers/rentals_united/templates/cancel.xml.erb create mode 100644 spec/fixtures/rentals_united/cancel/does_not_exist.xml create mode 100644 spec/fixtures/rentals_united/cancel/success.xml create mode 100644 spec/lib/concierge/suppliers/rentals_united/commands/cancel_spec.rb diff --git a/lib/concierge/suppliers/rentals_united/client.rb b/lib/concierge/suppliers/rentals_united/client.rb index 4d6e28cad..6981b6b54 100644 --- a/lib/concierge/suppliers/rentals_united/client.rb +++ b/lib/concierge/suppliers/rentals_united/client.rb @@ -8,5 +8,23 @@ class Client def initialize(credentials) @credentials = credentials end + + # Cancels a reservation by given reference_number + # + # Usage + # + # client = RentalsUnited::Client.new(credentials) + # result = client.cancel(params) + # + # Returns a +Result+ wrapping a +String+ with reference_number number when + # operation succeeds + # Returns a +Result+ with +Result::Error+ when operation fails + def cancel(params) + command = RentalsUnited::Commands::Cancel.new( + credentials, + params[:reference_number] + ) + command.call + end end end diff --git a/lib/concierge/suppliers/rentals_united/commands/cancel.rb b/lib/concierge/suppliers/rentals_united/commands/cancel.rb new file mode 100644 index 000000000..034b11ad4 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/commands/cancel.rb @@ -0,0 +1,48 @@ +module RentalsUnited + module Commands + # +RentalsUnited::Commands::Cancel+ + # + # This class is responsible for wrapping the logic related to cancellations + # properties for RentalsUnited. + class Cancel < BaseFetcher + attr_reader :reference_number + + ROOT_TAG = "Push_CancelReservation_RS" + + # Initialize +Cancel+ command. + # + # Arguments + # + # * +credentials+ + # * +reference_number+ [String] id of reservation to cancel + def initialize(credentials, reference_number) + super(credentials) + @reference_number = reference_number + end + + # Cancels reservation by its id (reservation code) + # + # RentalsUnited API returns just simple success response, so by calling + # `valid_status()` we actually checking whether cancellation is + # successful or not. + # + # Returns a +Result+ wrapping a +reference_number+ of the cancelled + # reservation when operation succeeds. + # Returns a +Result+ with +Result::Error+ when operation fails + def call + payload = payload_builder.build_cancel_payload(reference_number) + result = http.post(credentials.url, payload, headers) + + return result unless result.success? + + result_hash = response_parser.to_hash(result.value.body) + + if valid_status?(result_hash, ROOT_TAG) + Result.new(reference_number) + else + error_result(result_hash, ROOT_TAG) + end + end + end + end +end diff --git a/lib/concierge/suppliers/rentals_united/payload_builder.rb b/lib/concierge/suppliers/rentals_united/payload_builder.rb index bf968e9c3..ca5178f0c 100644 --- a/lib/concierge/suppliers/rentals_united/payload_builder.rb +++ b/lib/concierge/suppliers/rentals_united/payload_builder.rb @@ -67,6 +67,14 @@ def build_seasons_fetch_payload(property_id, date_from, date_to) render(:seasons_fetch, template_locals) end + def build_cancel_payload(reference_number) + template_locals = { + credentials: credentials, + reference_number: reference_number + } + render(:cancel, template_locals) + end + private def render(template_name, local_vars) path = Hanami.root.join(TEMPLATES_PATH, "#{template_name}.xml.erb") diff --git a/lib/concierge/suppliers/rentals_united/templates/cancel.xml.erb b/lib/concierge/suppliers/rentals_united/templates/cancel.xml.erb new file mode 100644 index 000000000..a010ff9e6 --- /dev/null +++ b/lib/concierge/suppliers/rentals_united/templates/cancel.xml.erb @@ -0,0 +1,7 @@ + + + <%= credentials.username %> + <%= credentials.password %> + + <%= reference_number %> + diff --git a/spec/fixtures/rentals_united/cancel/does_not_exist.xml b/spec/fixtures/rentals_united/cancel/does_not_exist.xml new file mode 100644 index 000000000..fabbbc663 --- /dev/null +++ b/spec/fixtures/rentals_united/cancel/does_not_exist.xml @@ -0,0 +1,3 @@ + + Reservation does not exist. + diff --git a/spec/fixtures/rentals_united/cancel/success.xml b/spec/fixtures/rentals_united/cancel/success.xml new file mode 100644 index 000000000..1d52971d7 --- /dev/null +++ b/spec/fixtures/rentals_united/cancel/success.xml @@ -0,0 +1,3 @@ + + Success + diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/cancel_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/cancel_spec.rb new file mode 100644 index 000000000..8f1779a81 --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/commands/cancel_spec.rb @@ -0,0 +1,74 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Commands::Cancel do + include Support::HTTPStubbing + include Support::Fixtures + + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:reference_number) { "888999777" } + let(:subject) { described_class.new(credentials, reference_number) } + let(:url) { credentials.url } + + it "performs successful cancel request" do + stub_data = read_fixture("rentals_united/cancel/success.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).to be_kind_of(Result) + expect(result).to be_success + + expect(result.value).to eq(reference_number) + end + + it "returns error if reservation does not exist" do + stub_data = read_fixture("rentals_united/cancel/does_not_exist.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).to be_kind_of(Result) + expect(result).not_to be_success + expect(result.error.code).to eq("28") + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Response indicating the Status with ID `28`, and description `Reservation does not exist.`" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + + context "when response from the api is not well-formed xml" do + it "returns a result with an appropriate error" do + stub_data = read_fixture("rentals_united/bad_xml.xml") + stub_call(:post, url) { [200, {}, stub_data] } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq(:unrecognised_response) + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq( + "Error response could not be recognised (no `Status` tag in the response)" + ) + expect(event[:backtrace]).to be_kind_of(Array) + expect(event[:backtrace].any?).to be true + end + end + + context "when request fails due to timeout error" do + it "returns a result with an appropriate error" do + stub_call(:post, url) { raise Faraday::TimeoutError } + + result = subject.call + + expect(result).not_to be_success + expect(result.error.code).to eq :connection_timeout + + event = Concierge.context.events.last.to_h + expect(event[:message]).to eq("timeout") + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb index cf4c91d21..57948fd93 100644 --- a/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/payload_builder_spec.rb @@ -218,6 +218,31 @@ end end + describe '#build_cancel_payload' do + let(:params) do + { + reference_number: "999123" + } + end + + it 'embedds username and password to request' do + xml = builder.build_cancel_payload(params[:reference_number]) + hash = to_hash(xml) + + authentication = hash.get("Push_CancelReservation_RQ.Authentication") + expect(authentication.get("UserName")).to eq(credentials.username) + expect(authentication.get("Password")).to eq(credentials.password) + end + + it 'adds reference_number to request' do + xml = builder.build_cancel_payload(params[:reference_number]) + hash = to_hash(xml) + + reservation_id = hash.get("Push_CancelReservation_RQ.ReservationID") + expect(reservation_id).to eq(params[:reference_number]) + end + end + private def to_hash(xml) Concierge::SafeAccessHash.new(Nori.new.parse(xml)) From 3802097413e55becb76c44919df0c2f632502f00 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 9 Sep 2016 18:06:32 +0600 Subject: [PATCH 110/118] RU: add more specs for cancellation --- .../controllers/rentals_united/cancel_spec.rb | 38 +++++++++++++++++++ .../suppliers/rentals_united/client_spec.rb | 17 +++++++++ 2 files changed, 55 insertions(+) create mode 100644 spec/api/controllers/rentals_united/cancel_spec.rb create mode 100644 spec/lib/concierge/suppliers/rentals_united/client_spec.rb diff --git a/spec/api/controllers/rentals_united/cancel_spec.rb b/spec/api/controllers/rentals_united/cancel_spec.rb new file mode 100644 index 000000000..aa71064e7 --- /dev/null +++ b/spec/api/controllers/rentals_united/cancel_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require_relative "../shared/cancel" + +RSpec.describe API::Controllers::RentalsUnited::Cancel do + it_behaves_like "cancel action" do + let(:success_cases) { + [ + { params: {reference_number: "A023"}, cancelled_reference_number: "XYZ" }, + { params: {reference_number: "A024"}, cancelled_reference_number: "ASD" }, + ] + } + let(:error_cases) { + [ + { params: {reference_number: "A123"}, error: {"cancellation" => "Could not cancel with remote supplier"} }, + { params: {reference_number: "A124"}, error: {"cancellation" => "Already cancelled"} }, + ] + } + + before do + allow_any_instance_of(RentalsUnited::Client).to receive(:cancel) do |instance, par| + result = nil + error_cases.each do |kase| + if par.reference_number == kase[:params][:reference_number] + result = Result.error(:already_cancelled, kase[:error]) + break + end + end + success_cases.each do |kase| + if par.reference_number == kase[:params][:reference_number] + result = Result.new(kase[:cancelled_reference_number]) + break + end + end + result + end + end + end +end diff --git a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb new file mode 100644 index 000000000..f24f3764c --- /dev/null +++ b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb @@ -0,0 +1,17 @@ +require "spec_helper" + +RSpec.describe RentalsUnited::Client do + let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:client) { described_class.new(credentials) } + + describe "#cancel" do + let(:params) { { reference_number: '555444' } } + + it "calls cancel command class" do + fetcher_class = RentalsUnited::Commands::Cancel + + expect_any_instance_of(fetcher_class).to(receive(:call)) + client.cancel(params) + end + end +end From 417667ed77cc0784a5587f725143a2dde5a8f3e4 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 30 Sep 2016 09:35:49 +0600 Subject: [PATCH 111/118] RU: use SUPPLIER_NAME constant --- spec/lib/concierge/suppliers/rentals_united/client_spec.rb | 3 ++- .../concierge/suppliers/rentals_united/commands/cancel_spec.rb | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb index f24f3764c..6506c079f 100644 --- a/spec/lib/concierge/suppliers/rentals_united/client_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/client_spec.rb @@ -1,7 +1,8 @@ require "spec_helper" RSpec.describe RentalsUnited::Client do - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:client) { described_class.new(credentials) } describe "#cancel" do diff --git a/spec/lib/concierge/suppliers/rentals_united/commands/cancel_spec.rb b/spec/lib/concierge/suppliers/rentals_united/commands/cancel_spec.rb index 8f1779a81..6cc8cbe1e 100644 --- a/spec/lib/concierge/suppliers/rentals_united/commands/cancel_spec.rb +++ b/spec/lib/concierge/suppliers/rentals_united/commands/cancel_spec.rb @@ -4,7 +4,8 @@ include Support::HTTPStubbing include Support::Fixtures - let(:credentials) { Concierge::Credentials.for("rentals_united") } + let(:supplier_name) { RentalsUnited::Client::SUPPLIER_NAME } + let(:credentials) { Concierge::Credentials.for(supplier_name) } let(:reference_number) { "888999777" } let(:subject) { described_class.new(credentials, reference_number) } let(:url) { credentials.url } From b615807e7f7fe9e5bba794317aec505e870fc3fd Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Fri, 30 Sep 2016 09:49:24 +0600 Subject: [PATCH 112/118] RU: apply needed inquiry.id changes for specs after rebase --- spec/api/controllers/rentals_united/cancel_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/api/controllers/rentals_united/cancel_spec.rb b/spec/api/controllers/rentals_united/cancel_spec.rb index aa71064e7..9ed58a47f 100644 --- a/spec/api/controllers/rentals_united/cancel_spec.rb +++ b/spec/api/controllers/rentals_united/cancel_spec.rb @@ -5,14 +5,14 @@ it_behaves_like "cancel action" do let(:success_cases) { [ - { params: {reference_number: "A023"}, cancelled_reference_number: "XYZ" }, - { params: {reference_number: "A024"}, cancelled_reference_number: "ASD" }, + { params: {reference_number: "A023", inquiry_id: "303"}, cancelled_reference_number: "XYZ" }, + { params: {reference_number: "A024", inquiry_id: "308"}, cancelled_reference_number: "ASD" }, ] } let(:error_cases) { [ - { params: {reference_number: "A123"}, error: {"cancellation" => "Could not cancel with remote supplier"} }, - { params: {reference_number: "A124"}, error: {"cancellation" => "Already cancelled"} }, + { params: {reference_number: "A123", inquiry_id: "392"}, error: {"cancellation" => "Could not cancel with remote supplier"} }, + { params: {reference_number: "A124", inquiry_id: "399"}, error: {"cancellation" => "Already cancelled"} }, ] } From 6d0ed9d95a75481193cc1bd249d7d3f28c5525dc Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Tue, 4 Oct 2016 16:29:55 +0600 Subject: [PATCH 113/118] RU: fix bug when different endpoints should consider status code by various ways --- .../rentals_united/commands/base_fetcher.rb | 4 ++++ .../suppliers/rentals_united/commands/booking.rb | 12 +++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb index ade6421de..619f30bd4 100644 --- a/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb +++ b/lib/concierge/suppliers/rentals_united/commands/base_fetcher.rb @@ -10,6 +10,10 @@ class BaseFetcher # Whitelisted RentalsUnited API statuses: # 0 - Success # 1 - Property is not available for a given dates + # + # Some status codes are ambiguous: they considered as valid for one + # endpoint/command and not valid for other. Specific cases like this are + # handled in particular command classes. VALID_RU_STATUS_CODES = ["0", "1"] diff --git a/lib/concierge/suppliers/rentals_united/commands/booking.rb b/lib/concierge/suppliers/rentals_united/commands/booking.rb index cf84633cf..56546ca52 100644 --- a/lib/concierge/suppliers/rentals_united/commands/booking.rb +++ b/lib/concierge/suppliers/rentals_united/commands/booking.rb @@ -21,6 +21,9 @@ class Booking < BaseFetcher ROOT_TAG = "Push_PutConfirmedReservationMulti_RS" + # Specifically this command consider these status codes like "error" + INVALID_PRICE_FETCH_STATUS_CODES = ["1"] + # Initialize +Booking+ command. # # Arguments @@ -57,7 +60,14 @@ def call result_hash = response_parser.to_hash(result.value.body) if valid_status?(result_hash, ROOT_TAG) - Result.new(build_reservation(result_hash)) + status = get_status(result_hash, ROOT_TAG) + code = get_status_code(status) + + if INVALID_PRICE_FETCH_STATUS_CODES.include?(code) + error_result(result_hash, ROOT_TAG) + else + Result.new(build_reservation(result_hash)) + end else error_result(result_hash, ROOT_TAG) end From ed4b3aa4ccf7c0d49095a70721d97cf195145d8f Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 5 Oct 2016 10:04:18 +0600 Subject: [PATCH 114/118] RU: change routes --- apps/api/config/routes.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/config/routes.rb b/apps/api/config/routes.rb index da1fd6c9a..5b95deb46 100644 --- a/apps/api/config/routes.rb +++ b/apps/api/config/routes.rb @@ -6,7 +6,7 @@ post '/ciirus/quote', to: 'ciirus#quote' post '/waytostay/quote', to: 'waytostay#quote' post '/saw/quote', to: 's_a_w#quote' -post '/rentals_united/quote', to: 'rentals_united#quote' +post '/rentalsunited/quote', to: 'rentals_united#quote' post '/jtb/booking', to: 'j_t_b#booking' post '/atleisure/booking', to: 'at_leisure#booking' @@ -16,7 +16,7 @@ post '/kigo/legacy/booking', to: 'kigo/legacy#booking' post '/saw/booking', to: 's_a_w#booking' post '/poplidays/booking', to: 'poplidays#booking' -post '/rentals_united/booking', to: 'rentals_united#booking' +post '/rentalsunited/booking', to: 'rentals_united#booking' post '/waytostay/cancel', to: 'waytostay#cancel' post '/ciirus/cancel', to: 'ciirus#cancel' @@ -25,7 +25,7 @@ post '/kigo/legacy/cancel', to: 'kigo/legacy#cancel' post '/poplidays/cancel', to: 'poplidays#cancel' post '/atleisure/cancel', to: 'at_leisure#cancel' -post 'rentals_united/cancel', to: 'rentals_united#cancel' +post '/rentalsunited/cancel', to: 'rentals_united#cancel' post '/checkout', to: 'static#checkout' get '/kigo/image/:property_id/:image_id', to: 'kigo#image' From 64a36ac22254e3c8250f4c4bd309ac7840aed1fe Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 5 Oct 2016 10:22:40 +0600 Subject: [PATCH 115/118] RU: update documentation --- apps/workers/suppliers/rentals_united/metadata.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/workers/suppliers/rentals_united/metadata.rb b/apps/workers/suppliers/rentals_united/metadata.rb index 55dd65ed1..e69094061 100644 --- a/apps/workers/suppliers/rentals_united/metadata.rb +++ b/apps/workers/suppliers/rentals_united/metadata.rb @@ -13,9 +13,6 @@ class Metadata attr_reader :property_sync, :calendar_sync, :host # Prevent from publishing property results containing error codes below. - # - # If property sync is skiped because of one of these errors, calendar - # update won't be started too. IGNORABLE_ERROR_CODES = [ :empty_seasons, :attempt_to_build_archived_property, From 44cb5854737f015184ef7b7215bc26953671a369 Mon Sep 17 00:00:00 2001 From: Sharipov Ruslan Date: Wed, 5 Oct 2016 11:46:28 +0600 Subject: [PATCH 116/118] RU: update route in middlewares/authentication --- apps/api/middlewares/authentication.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api/middlewares/authentication.rb b/apps/api/middlewares/authentication.rb index 5d8921869..91060acf9 100644 --- a/apps/api/middlewares/authentication.rb +++ b/apps/api/middlewares/authentication.rb @@ -54,7 +54,7 @@ class Secrets "/waytostay" => ENV["ROOMORAMA_SECRET_WAYTOSTAY"], "/ciirus" => ENV["ROOMORAMA_SECRET_CIIRUS"], "/saw" => ENV["ROOMORAMA_SECRET_SAW"], - "/rentals_united" => ENV["ROOMORAMA_SECRET_RENTALS_UNITED"] + "/rentalsunited" => ENV["ROOMORAMA_SECRET_RENTALS_UNITED"] } attr_reader :mapping From 36940fb8ca56f3b2515dde6b34b437be8365c60b Mon Sep 17 00:00:00 2001 From: Keang Date: Wed, 5 Oct 2016 15:11:32 +0800 Subject: [PATCH 117/118] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49aff8e73..0e1f87a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ of Concierge. Please check the Wiki entry on the release process to understand how this file is formatted and how the process works. ## Unreleased +### Added +- Rentals Untied sync, quote, book and cancel + ### Changed - Abstract host fee calculation from suppliers to entity level - Return 404 for attempts to quote a property not in records From ba211454103a42e0622cac1d896ae9cdced85a28 Mon Sep 17 00:00:00 2001 From: Keang Date: Wed, 5 Oct 2016 15:19:02 +0800 Subject: [PATCH 118/118] Bump version 0.12.0 RentalsUntied --- CHANGELOG.md | 2 +- lib/concierge/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1f87a03..93f6e9d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ This file summarises the most important changes that went live on each release of Concierge. Please check the Wiki entry on the release process to understand how this file is formatted and how the process works. -## Unreleased +## [0.12.0] - 2016-10-05 ### Added - Rentals Untied sync, quote, book and cancel diff --git a/lib/concierge/version.rb b/lib/concierge/version.rb index a44489ec6..f199cb0d4 100644 --- a/lib/concierge/version.rb +++ b/lib/concierge/version.rb @@ -1,3 +1,3 @@ module Concierge - VERSION = "0.11.6" + VERSION = "0.12.0" end