From 2593028c2e6454d007e5314836b5e09d0c16367b Mon Sep 17 00:00:00 2001 From: choonkeat Date: Mon, 11 Jul 2016 12:13:37 +0800 Subject: [PATCH 01/22] config changes to add Audit --- config/credentials/production.yml | 5 +++++ config/credentials/staging.yml | 5 +++++ config/credentials/test.yml | 5 +++++ config/suppliers.yml | 9 ++++++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/config/credentials/production.yml b/config/credentials/production.yml index d55e5eede..efd47dd48 100644 --- a/config/credentials/production.yml +++ b/config/credentials/production.yml @@ -29,6 +29,11 @@ waytostay: client_id: <%= ENV["WAYTOSTAY_CLIENT_ID"] %> client_secret: <%= ENV["WAYTOSTAY_CLIENT_SECRET"] %> +audit: + secret_key: test_secret + host: http://localhost:9292 + fetch_properties_endpoint: /spec/fixtures/audit/properties.json + ciirus: url: <%= ENV["CIIRUS_URL"] %> username: <%= ENV["CIIRUS_USERNAME"] %> diff --git a/config/credentials/staging.yml b/config/credentials/staging.yml index d55e5eede..efd47dd48 100644 --- a/config/credentials/staging.yml +++ b/config/credentials/staging.yml @@ -29,6 +29,11 @@ waytostay: client_id: <%= ENV["WAYTOSTAY_CLIENT_ID"] %> client_secret: <%= ENV["WAYTOSTAY_CLIENT_SECRET"] %> +audit: + secret_key: test_secret + host: http://localhost:9292 + fetch_properties_endpoint: /spec/fixtures/audit/properties.json + ciirus: url: <%= ENV["CIIRUS_URL"] %> username: <%= ENV["CIIRUS_USERNAME"] %> diff --git a/config/credentials/test.yml b/config/credentials/test.yml index 204b64d67..d70642e1c 100644 --- a/config/credentials/test.yml +++ b/config/credentials/test.yml @@ -35,6 +35,11 @@ waytostay: client_id: test_id client_secret: test_secret +audit: + secret_key: test_secret + host: http://localhost:9292 + fetch_properties_endpoint: /spec/fixtures/audit/properties.json + ciirus: url: "http://www.example.org" username: "roomorama-user" diff --git a/config/suppliers.yml b/config/suppliers.yml index 86415f554..2609b84d2 100644 --- a/config/suppliers.yml +++ b/config/suppliers.yml @@ -21,9 +21,16 @@ WayToStay: availabilities: absence: "WayToStay calendar is synchronised with property metadata, due to the diff-like API provided." +Audit: + workers: + metadata: + every: "1h" + availabilities: + every: "1h" + Ciirus: workers: metadata: every: "1d" availabilities: - every: "5h" \ No newline at end of file + every: "5h" From d7822d573501df06cb3139b6928d06177ba3f688 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Sun, 10 Jul 2016 00:42:17 +0800 Subject: [PATCH 02/22] Importer#fetch_properties works --- lib/concierge/suppliers/audit/importer.rb | 45 ++++++++++++++++ spec/fixtures/audit/properties.json | 44 ++++++++++++++++ .../suppliers/audit/importer_spec.rb | 51 +++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 lib/concierge/suppliers/audit/importer.rb create mode 100644 spec/fixtures/audit/properties.json create mode 100644 spec/lib/concierge/suppliers/audit/importer_spec.rb diff --git a/lib/concierge/suppliers/audit/importer.rb b/lib/concierge/suppliers/audit/importer.rb new file mode 100644 index 000000000..fc274ee4c --- /dev/null +++ b/lib/concierge/suppliers/audit/importer.rb @@ -0,0 +1,45 @@ +module Audit + # +Audit::Importer+ + # + # This class wraps supplier API and provides data for building properties. + # + # Usage + # + # importer = Audit::Importer.new(credentials) + # importer.fetch_properties + # + # => # 'XX-12345-67', ...}, ...] + class Importer + attr_reader :credentials + + def initialize(credentials) + @credentials = credentials + end + + # retrieves the list of properties + def fetch_properties + client = Concierge::HTTPClient.new("http://localhost:9292") + result = client.get("/fetch_properties") + if result.success? + json = JSON.parse(result.value.body) + Result.new(json['result']) + else + result + end + end + + def json_to_property(json) + Roomorama::Property.load(Concierge::SafeAccessHash.new json).tap do |property_result| + if property_result.success? + property = property_result.value + property.update_calendar(json['availability_dates']) + property.units.each do |unit| + if unitjson = json['units'].find {|h| h['identifier'] == unit.identifier } + unit.update_calendar(unitjson['availability_dates']) + end + end + end + end + end + end +end diff --git a/spec/fixtures/audit/properties.json b/spec/fixtures/audit/properties.json new file mode 100644 index 000000000..8b883ef18 --- /dev/null +++ b/spec/fixtures/audit/properties.json @@ -0,0 +1,44 @@ +{ + "result": [ + { + "identifier": "audit-property-1", + "title": "Audit Property #1", + "max_guests": 2, + "nightly_rate": 100, + "images": [ + { + "identifier": "audit-image1", + "url": "https://www.example.org/img1", + "caption": "Barbecue Pit" + } + ], + "units": [ + { + "identifier": "audit-unit1", + "title": "Audit Unit 1", + "number_of_units": 2, + "images": [ + { + "identifier": "audit-unit1img1", + "url": "https://www.example.org/unit1img1" + }, + { + "identifier": "audit-unit1img2", + "url": "https://www.example.org/unit1img2" + } + ] + }, + { + "identifier": "audit-unit2", + "title": "Audit Unit 2", + "images": [ + { + "identifier": "audit-unit2img1", + "url": "https://www.example.org/unit2img1" + } + ] + } + ] + } + ] +} diff --git a/spec/lib/concierge/suppliers/audit/importer_spec.rb b/spec/lib/concierge/suppliers/audit/importer_spec.rb new file mode 100644 index 000000000..e8fe99be9 --- /dev/null +++ b/spec/lib/concierge/suppliers/audit/importer_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +RSpec.describe Audit::Importer do + include Support::Fixtures + include Support::HTTPStubbing + + let(:credentials) { Concierge::Credentials.for('audit') } + let(:importer) { described_class.new(credentials) } + + describe '#fetch_properties' do + + subject { importer.fetch_properties } + + context 'success' do + before do + allow_any_instance_of(Faraday::Connection).to receive(:get) do + Faraday::Response.new(method: :get, status: 200, body: IO.binread('spec/fixtures/audit/properties.json')) + end + end + + it 'should return Result of array of Hash' do + is_expected.to be_success + expect(subject.value).to be_kind_of(Array) + expect(subject.value.collect(&:class).uniq).to eq([Hash]) + end + end + + context 'error' do + before do + allow_any_instance_of(Faraday::Connection).to receive(:get) do + raise Faraday::Error.new("oops123") + end + end + + it 'should return Result with errors' do + is_expected.not_to be_success + expect(subject.error.code).to eq :network_failure + end + end + end + + describe '#json_to_property' do + let(:json) { JSON.parse(IO.binread('spec/fixtures/audit/properties.json'))['result'].sample } + + it 'should return Result of Roomorama::Property' do + result = importer.json_to_property(json) + expect(result).to be_kind_of(Result) + expect(result.value).to be_kind_of(Roomorama::Property) + end + end +end From d0f46aad8f6f838ff17c00cb023c7d1183dacdae Mon Sep 17 00:00:00 2001 From: choonkeat Date: Mon, 11 Jul 2016 13:08:10 +0800 Subject: [PATCH 03/22] Workers::Suppliers::Audit works --- apps/workers/suppliers/audit.rb | 59 +++++++++++++++++++++ spec/fixtures/audit/properties.json | 15 ++++++ spec/workers/suppliers/audit_spec.rb | 78 ++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 apps/workers/suppliers/audit.rb create mode 100644 spec/workers/suppliers/audit_spec.rb diff --git a/apps/workers/suppliers/audit.rb b/apps/workers/suppliers/audit.rb new file mode 100644 index 000000000..7a76c2235 --- /dev/null +++ b/apps/workers/suppliers/audit.rb @@ -0,0 +1,59 @@ +module Workers::Suppliers + + class Audit + SUPPLIER_NAME = "Audit" + attr_reader :synchronisation, :host + + def initialize(host) + @host = host + @synchronisation = Workers::Synchronisation.new(host) + end + + def perform + result = importer.fetch_properties + if result.success? + result.value.each do |json| + synchronisation.start(json['identifier']) do + importer.json_to_property(json) + end + end + synchronisation.finish! + else + message = "Failed to perform the `#fetch_properties` operation" + announce_error(message, result) + end + end + + private + + def importer + @properties ||= ::Audit::Importer.new(credentials) + end + + def credentials + @credentials ||= Concierge::Credentials.for(SUPPLIER_NAME) + end + + def announce_error(message, result) + message = { + label: 'Synchronisation Failure', + message: message, + backtrace: caller + } + context = Concierge::Context::Message.new(message) + Concierge.context.augment(context) + + Concierge::Announcer.trigger(Concierge::Errors::EXTERNAL_ERROR, { + operation: 'sync', + supplier: SUPPLIER_NAME, + code: result.error.code, + context: Concierge.context.to_h, + happened_at: Time.now + }) + end + end +end + +Concierge::Announcer.on("sync.#{Workers::Suppliers::Audit::SUPPLIER_NAME}") do |host| + Workers::Suppliers::Audit.new(host).perform +end diff --git a/spec/fixtures/audit/properties.json b/spec/fixtures/audit/properties.json index 8b883ef18..28ded21b4 100644 --- a/spec/fixtures/audit/properties.json +++ b/spec/fixtures/audit/properties.json @@ -5,6 +5,11 @@ "title": "Audit Property #1", "max_guests": 2, "nightly_rate": 100, + "availability_dates": { + "2016-07-11": true, + "2016-07-12": true, + "2016-07-13": true + }, "images": [ { "identifier": "audit-image1", @@ -17,6 +22,11 @@ "identifier": "audit-unit1", "title": "Audit Unit 1", "number_of_units": 2, + "availability_dates": { + "2016-07-11": true, + "2016-07-12": false, + "2016-07-13": true + }, "images": [ { "identifier": "audit-unit1img1", @@ -31,6 +41,11 @@ { "identifier": "audit-unit2", "title": "Audit Unit 2", + "availability_dates": { + "2016-07-11": false, + "2016-07-12": true, + "2016-07-13": true + }, "images": [ { "identifier": "audit-unit2img1", diff --git a/spec/workers/suppliers/audit_spec.rb b/spec/workers/suppliers/audit_spec.rb new file mode 100644 index 000000000..6d2a98ce0 --- /dev/null +++ b/spec/workers/suppliers/audit_spec.rb @@ -0,0 +1,78 @@ +require "spec_helper" + +RSpec.describe Workers::Suppliers::Audit do + include Support::Factories + include Support::Fixtures + + let(:host) { create_host } + let(:worker) { described_class.new(host) } + let(:fetch_properties_json) { JSON.parse(IO.binread('spec/fixtures/audit/properties.json')) } + let(:credentials) { Hash.new } + + def synchronisation_counters + [:created, :updated, :deleted].inject({}) do |sum,k| + sum.merge(k => @counters.send(k)) + end + end + + before do + # do NOT make API calls during tests + allow_any_instance_of(Workers::Synchronisation).to receive(:run_operation).and_return(nil) + + # keep track of counters + @counters = Workers::Synchronisation::PropertyCounters.new(0, 0, 0) + allow_any_instance_of(Workers::Synchronisation).to receive(:save_sync_process) do |instance, *args| + @counters = instance.counters + end + end + + describe "perform" do + + before do + allow_any_instance_of(Audit::Importer).to receive(:fetch_properties) do + Result.new(fetch_properties_json['result']) + end + end + + subject { proc { worker.perform } } + + context "fetched new property" do + it { is_expected.to change { synchronisation_counters }.to eq(created: 1, updated: 0, deleted: 0) } + end + + context "fetched existing property" do + before do + fetch_properties_json['result'].each do |json| + result = Audit::Importer.new(credentials).json_to_property(json) + roomorama_property = result.value + # See Workers::Router#dispatch + # enqueues a diff operation if there is a property with the same identifier for the same host + data = roomorama_property.to_h.merge!(title: "Different title") + create_property(host_id: host.id, identifier: roomorama_property.identifier, data: data) + end + end + + it { is_expected.to change { synchronisation_counters }.to eq(created: 0, updated: 1, deleted: 0) } + end + + context "error when importing json_to_property" do + before do + allow_any_instance_of(Audit::Importer).to receive(:json_to_property) do + Result.error(:missing_required_data, { 'foo' => 'bar'} ) + end + end + + it { is_expected.not_to change { synchronisation_counters } } + end + + context "error when fetching" do + before do + allow_any_instance_of(Audit::Importer).to receive(:fetch_properties) do + Result.error(:network_failure) + end + end + + it { is_expected.not_to change { synchronisation_counters } } + end + end +end From 68efc077d3627c8644b3135e32c0f82056cd6550 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Mon, 11 Jul 2016 23:25:19 +0800 Subject: [PATCH 04/22] Use Support::HTTPStubbing, Support::Fixtures, Concierge::Credentials --- config/credentials/test.yml | 2 +- lib/concierge/suppliers/audit/importer.rb | 4 ++-- spec/lib/concierge/suppliers/audit/importer_spec.rb | 11 ++++------- spec/workers/suppliers/audit_spec.rb | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/config/credentials/test.yml b/config/credentials/test.yml index d70642e1c..e4fd4cc67 100644 --- a/config/credentials/test.yml +++ b/config/credentials/test.yml @@ -36,7 +36,7 @@ waytostay: client_secret: test_secret audit: - secret_key: test_secret + secret_key: <%= ENV.fetch('AUDIT_SECRET_KEY', 'test_secret') %> host: http://localhost:9292 fetch_properties_endpoint: /spec/fixtures/audit/properties.json diff --git a/lib/concierge/suppliers/audit/importer.rb b/lib/concierge/suppliers/audit/importer.rb index fc274ee4c..63cd1de40 100644 --- a/lib/concierge/suppliers/audit/importer.rb +++ b/lib/concierge/suppliers/audit/importer.rb @@ -18,8 +18,8 @@ def initialize(credentials) # retrieves the list of properties def fetch_properties - client = Concierge::HTTPClient.new("http://localhost:9292") - result = client.get("/fetch_properties") + client = Concierge::HTTPClient.new(credentials.host) + result = client.get(credentials.fetch_properties_endpoint) if result.success? json = JSON.parse(result.value.body) Result.new(json['result']) diff --git a/spec/lib/concierge/suppliers/audit/importer_spec.rb b/spec/lib/concierge/suppliers/audit/importer_spec.rb index e8fe99be9..27ca7519a 100644 --- a/spec/lib/concierge/suppliers/audit/importer_spec.rb +++ b/spec/lib/concierge/suppliers/audit/importer_spec.rb @@ -6,6 +6,7 @@ let(:credentials) { Concierge::Credentials.for('audit') } let(:importer) { described_class.new(credentials) } + let(:endpoint) { "#{credentials.host}#{credentials.fetch_properties_endpoint}" } describe '#fetch_properties' do @@ -13,9 +14,7 @@ context 'success' do before do - allow_any_instance_of(Faraday::Connection).to receive(:get) do - Faraday::Response.new(method: :get, status: 200, body: IO.binread('spec/fixtures/audit/properties.json')) - end + stub_call(:get, endpoint) { [200, {}, read_fixture('audit/properties.json')] } end it 'should return Result of array of Hash' do @@ -27,9 +26,7 @@ context 'error' do before do - allow_any_instance_of(Faraday::Connection).to receive(:get) do - raise Faraday::Error.new("oops123") - end + stub_call(:get, endpoint) { raise Faraday::Error.new("oops123") } end it 'should return Result with errors' do @@ -40,7 +37,7 @@ end describe '#json_to_property' do - let(:json) { JSON.parse(IO.binread('spec/fixtures/audit/properties.json'))['result'].sample } + let(:json) { JSON.parse(read_fixture('audit/properties.json'))['result'].sample } it 'should return Result of Roomorama::Property' do result = importer.json_to_property(json) diff --git a/spec/workers/suppliers/audit_spec.rb b/spec/workers/suppliers/audit_spec.rb index 6d2a98ce0..076ce7784 100644 --- a/spec/workers/suppliers/audit_spec.rb +++ b/spec/workers/suppliers/audit_spec.rb @@ -6,7 +6,7 @@ let(:host) { create_host } let(:worker) { described_class.new(host) } - let(:fetch_properties_json) { JSON.parse(IO.binread('spec/fixtures/audit/properties.json')) } + let(:fetch_properties_json) { JSON.parse(read_fixture('audit/properties.json')) } let(:credentials) { Hash.new } def synchronisation_counters From cc6c111e4577a58c44b079c6ae722633a58fef18 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Tue, 12 Jul 2016 10:58:01 +0800 Subject: [PATCH 05/22] Setup mock Audit supplier server --- lib/concierge/suppliers/audit/importer.rb | 21 ++++++++++++++++++ spec/fixtures/audit/config.ru | 20 +++++++++++++++++ ...{properties.json => fetch_properties.json} | 8 +++---- spec/fixtures/audit/villa.jpg | Bin 0 -> 284 bytes .../audit/\360\237\230\273\347\214\253.png" | Bin 0 -> 275 bytes .../suppliers/audit/importer_spec.rb | 4 ++-- spec/workers/suppliers/audit_spec.rb | 4 ++-- 7 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 spec/fixtures/audit/config.ru rename spec/fixtures/audit/{properties.json => fetch_properties.json} (83%) create mode 100644 spec/fixtures/audit/villa.jpg create mode 100644 "spec/fixtures/audit/\360\237\230\273\347\214\253.png" diff --git a/lib/concierge/suppliers/audit/importer.rb b/lib/concierge/suppliers/audit/importer.rb index 63cd1de40..1172b7d7f 100644 --- a/lib/concierge/suppliers/audit/importer.rb +++ b/lib/concierge/suppliers/audit/importer.rb @@ -29,6 +29,10 @@ def fetch_properties end def json_to_property(json) + # `Roomorama::Property.load` prefer absolute urls, but our fixture `url` values are relative + # make it happy + fix_relative_urls!(URI.join(credentials.host, credentials.fetch_properties_endpoint), json) + Roomorama::Property.load(Concierge::SafeAccessHash.new json).tap do |property_result| if property_result.success? property = property_result.value @@ -41,5 +45,22 @@ def json_to_property(json) end end end + + private + + def fix_relative_urls!(base_uri, object) + case object + when Hash + object.each do |key, value| + if key == 'url' + object[key] = URI.join(base_uri, URI.escape(value)).to_s + elsif value.kind_of?(Hash) || value.kind_of?(Array) + fix_relative_urls!(base_uri, value) + end + end + when Array + object.each {|item| fix_relative_urls!(base_uri, item) } + end + end end end diff --git a/spec/fixtures/audit/config.ru b/spec/fixtures/audit/config.ru new file mode 100644 index 000000000..66f4c805a --- /dev/null +++ b/spec/fixtures/audit/config.ru @@ -0,0 +1,20 @@ +# +Audit supplier server+ +# +# This is the mock API server for `Audit` supplier +# +# Usage +# +# bash$ rackup spec/fixtures/audit/config.ru +# [2016-07-12 10:44:29] INFO WEBrick 1.3.1 +# [2016-07-12 10:44:29] INFO ruby 2.3.0 (2015-12-25) [x86_64-darwin14] +# [2016-07-12 10:44:29] INFO WEBrick::HTTPServer#start: pid=92594 port=9292 +# + +require 'rack' + +use Rack::Static, :urls => ['/spec'] + +run -> (env) { + path = Dir['spec/fixtures/audit/*'].sample + [200, {'Content-Type' => 'text/html'}, ["Try #{path} instead"]] +} diff --git a/spec/fixtures/audit/properties.json b/spec/fixtures/audit/fetch_properties.json similarity index 83% rename from spec/fixtures/audit/properties.json rename to spec/fixtures/audit/fetch_properties.json index 28ded21b4..f7771f0f0 100644 --- a/spec/fixtures/audit/properties.json +++ b/spec/fixtures/audit/fetch_properties.json @@ -13,7 +13,7 @@ "images": [ { "identifier": "audit-image1", - "url": "https://www.example.org/img1", + "url": "/spec/fixtures/audit/villa.jpg?img1", "caption": "Barbecue Pit" } ], @@ -30,11 +30,11 @@ "images": [ { "identifier": "audit-unit1img1", - "url": "https://www.example.org/unit1img1" + "url": "/spec/fixtures/audit/villa.jpg?unit1img1" }, { "identifier": "audit-unit1img2", - "url": "https://www.example.org/unit1img2" + "url": "/spec/fixtures/audit/😻猫.png?unit1img2" } ] }, @@ -49,7 +49,7 @@ "images": [ { "identifier": "audit-unit2img1", - "url": "https://www.example.org/unit2img1" + "url": "/spec/fixtures/audit/😻猫.png?unit2img1" } ] } diff --git a/spec/fixtures/audit/villa.jpg b/spec/fixtures/audit/villa.jpg new file mode 100644 index 0000000000000000000000000000000000000000..92b97c94e9461a33a55bf70f55c3751cff7da160 GIT binary patch literal 284 zcmb7<+X{j}5Qb;k$YnQ|UC8jl5G_RsI)ETy8DU8FI6Yw(Jw%6_-ehy{%DcX~`xs{a zU*Gi`cK);{@W?9r4FiI(Bj0;x^+ZGdNtlX&|%|bs6;(5M|(<}|b)nT1& zin1)D#j&Zjr+ily<_I#*g)KZOy&?&d;&1d76g8m;D(ERB3iS;}Cgcx&V`bMuYaKMT YzQLYZRvp{6e!`Go`-Ldj!-ea|2a*vi;{X5v literal 0 HcmV?d00001 diff --git "a/spec/fixtures/audit/\360\237\230\273\347\214\253.png" "b/spec/fixtures/audit/\360\237\230\273\347\214\253.png" new file mode 100644 index 0000000000000000000000000000000000000000..70c0d249acaafec8628e80732c4a0b6edc3c95ca GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^j3CU&3?x-=hn)gaEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8Uxs6XIGEX7T_3|LCtf zoPZ*XN#5=*jQ^Q*?*TdNC7!;n><`#^xY#w7m?FOeg}6Lj978y+Cno^eObm>V6!#bd zS*j(j5hW>!C8<`)MX5lF!N|bSOxM6%*U%`$(7?*T+{(mI+rYxgz~I)a?OiAua`RI% Y(<*UmSiPO!45)#@)78&qol`;+0Nh(d)c^nh literal 0 HcmV?d00001 diff --git a/spec/lib/concierge/suppliers/audit/importer_spec.rb b/spec/lib/concierge/suppliers/audit/importer_spec.rb index 27ca7519a..b32c48228 100644 --- a/spec/lib/concierge/suppliers/audit/importer_spec.rb +++ b/spec/lib/concierge/suppliers/audit/importer_spec.rb @@ -14,7 +14,7 @@ context 'success' do before do - stub_call(:get, endpoint) { [200, {}, read_fixture('audit/properties.json')] } + stub_call(:get, endpoint) { [200, {}, read_fixture('audit/fetch_properties.json')] } end it 'should return Result of array of Hash' do @@ -37,7 +37,7 @@ end describe '#json_to_property' do - let(:json) { JSON.parse(read_fixture('audit/properties.json'))['result'].sample } + let(:json) { JSON.parse(read_fixture('audit/fetch_properties.json'))['result'].sample } it 'should return Result of Roomorama::Property' do result = importer.json_to_property(json) diff --git a/spec/workers/suppliers/audit_spec.rb b/spec/workers/suppliers/audit_spec.rb index 076ce7784..01acbc7f0 100644 --- a/spec/workers/suppliers/audit_spec.rb +++ b/spec/workers/suppliers/audit_spec.rb @@ -6,8 +6,8 @@ let(:host) { create_host } let(:worker) { described_class.new(host) } - let(:fetch_properties_json) { JSON.parse(read_fixture('audit/properties.json')) } - let(:credentials) { Hash.new } + let(:fetch_properties_json) { JSON.parse(read_fixture('audit/fetch_properties.json')) } + let(:credentials) { worker.send(:credentials) } def synchronisation_counters [:created, :updated, :deleted].inject({}) do |sum,k| From 3429b6728cd894410aed5ef82f5a7d9256b6ab9f Mon Sep 17 00:00:00 2001 From: choonkeat Date: Wed, 13 Jul 2016 10:11:27 +0800 Subject: [PATCH 06/22] Fill up missing info required by Roomorama API --- spec/fixtures/audit/fetch_properties.json | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/spec/fixtures/audit/fetch_properties.json b/spec/fixtures/audit/fetch_properties.json index f7771f0f0..1947659a7 100644 --- a/spec/fixtures/audit/fetch_properties.json +++ b/spec/fixtures/audit/fetch_properties.json @@ -3,6 +3,21 @@ { "identifier": "audit-property-1", "title": "Audit Property #1", + "type": "hostel", + "postal_code": "069935", + "country_code": "SG", + "city": "Singapore", + "description": "Audit property description", + "number_of_bedrooms": 2, + "minimum_stay": 1, + "weekly_rate": 100, + "monthly_rate": 400, + "cancellation_policy": "standard", + "default_to_available": true, + "lat": 1.282097, + "lng": 103.848025, + "currency": "SGD", + "street_address": "115 Amoy Street", "max_guests": 2, "nightly_rate": 100, "availability_dates": { @@ -21,6 +36,15 @@ { "identifier": "audit-unit1", "title": "Audit Unit 1", + "description": "Audit unit 1 description", + "nightly_rate": 20, + "weekly_rate": 100, + "monthly_rate": 400, + "number_of_bedrooms": 2, + "max_guests": 4, + "host_daily_price": 20, + "host_weekly_price": 100, + "host_monthly_price": 400, "number_of_units": 2, "availability_dates": { "2016-07-11": true, @@ -41,6 +65,16 @@ { "identifier": "audit-unit2", "title": "Audit Unit 2", + "description": "Audit unit 2 description", + "nightly_rate": 20, + "weekly_rate": 100, + "monthly_rate": 400, + "number_of_bedrooms": 2, + "max_guests": 4, + "host_daily_price": 20, + "host_weekly_price": 100, + "host_monthly_price": 400, + "number_of_units": 2, "availability_dates": { "2016-07-11": false, "2016-07-12": true, From 40f0312b3c018210ecd7b40213487c5cf573e3b4 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Wed, 13 Jul 2016 11:28:00 +0800 Subject: [PATCH 07/22] Add optional fields too -- be complete --- spec/fixtures/audit/fetch_properties.json | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/spec/fixtures/audit/fetch_properties.json b/spec/fixtures/audit/fetch_properties.json index 1947659a7..86e824f66 100644 --- a/spec/fixtures/audit/fetch_properties.json +++ b/spec/fixtures/audit/fetch_properties.json @@ -3,21 +3,41 @@ { "identifier": "audit-property-1", "title": "Audit Property #1", - "type": "hostel", + "type": "house", + "subtype": "cabin", "postal_code": "069935", "country_code": "SG", "city": "Singapore", + "neighborhood": "CBD", "description": "Audit property description", "number_of_bedrooms": 2, "minimum_stay": 1, "weekly_rate": 100, "monthly_rate": 400, + "security_deposit_amount": 99, + "security_deposit_currency_code": "MYR", + "security_deposit_type": "credit_card_auth", + "pets_allowed": true, + "smoking_allowed": true, + "services_cleaning": true, + "services_cleaning_rate": 10, + "services_cleaning_required": true, + "services_airport_pickup": true, + "services_car_rental": true, + "services_car_rental_rate": 11.5, + "services_airport_pickup_rate": 12, + "services_concierge": true, + "services_concierge_rate": 13, + "disabled": true, + "instant_booking": true, + "check_in_instructions": "Lorem ipsum instructions", "cancellation_policy": "standard", "default_to_available": true, "lat": 1.282097, "lng": 103.848025, "currency": "SGD", - "street_address": "115 Amoy Street", + "address": "115 Amoy Street", + "apartment_number": "#02-01", "max_guests": 2, "nightly_rate": 100, "availability_dates": { From 30dcf3a0b699f050d8c7793143be060939e56ced Mon Sep 17 00:00:00 2001 From: choonkeat Date: Fri, 15 Jul 2016 12:11:35 +0800 Subject: [PATCH 08/22] Remove Property#update_calendar code; see #125 9600f7b3 --- apps/workers/suppliers/audit.rb | 19 +++++++--- lib/concierge/suppliers/audit/importer.rb | 12 +++--- spec/fixtures/audit/fetch_properties.json | 10 ----- .../suppliers/audit/importer_spec.rb | 14 ++++++- spec/workers/suppliers/audit_spec.rb | 38 +++++++++++++------ 5 files changed, 59 insertions(+), 34 deletions(-) diff --git a/apps/workers/suppliers/audit.rb b/apps/workers/suppliers/audit.rb index 7a76c2235..6fa6052b5 100644 --- a/apps/workers/suppliers/audit.rb +++ b/apps/workers/suppliers/audit.rb @@ -2,22 +2,31 @@ module Workers::Suppliers class Audit SUPPLIER_NAME = "Audit" - attr_reader :synchronisation, :host + attr_reader :property_sync, :calendar_sync, :host def initialize(host) @host = host - @synchronisation = Workers::Synchronisation.new(host) + @property_sync = Workers::PropertySynchronisation.new(host) + @calendar_sync = Workers::CalendarSynchronisation.new(host) end def perform result = importer.fetch_properties if result.success? result.value.each do |json| - synchronisation.start(json['identifier']) do - importer.json_to_property(json) + property_sync.start(json['identifier']) do + importer.json_to_property(json) do |calendar_entries| + calendar_sync.start(json['identifier']) do + calendar = Roomorama::Calendar.new(json['identifier']) + calendar_entries.each {|entry| calendar.add entry } + Result.new(calendar) + end + end end end - synchronisation.finish! + + property_sync.finish! + calendar_sync.finish! else message = "Failed to perform the `#fetch_properties` operation" announce_error(message, result) diff --git a/lib/concierge/suppliers/audit/importer.rb b/lib/concierge/suppliers/audit/importer.rb index 1172b7d7f..3f37af813 100644 --- a/lib/concierge/suppliers/audit/importer.rb +++ b/lib/concierge/suppliers/audit/importer.rb @@ -36,12 +36,14 @@ def json_to_property(json) Roomorama::Property.load(Concierge::SafeAccessHash.new json).tap do |property_result| if property_result.success? property = property_result.value - property.update_calendar(json['availability_dates']) - property.units.each do |unit| - if unitjson = json['units'].find {|h| h['identifier'] == unit.identifier } - unit.update_calendar(unitjson['availability_dates']) - end + calendar_entries = json['availability_dates'].collect do |yyyymmdd, boolean| + Roomorama::Calendar::Entry.new( + date: yyyymmdd, + available: boolean, + nightly_rate: property.nightly_rate, + ) end + yield calendar_entries end end end diff --git a/spec/fixtures/audit/fetch_properties.json b/spec/fixtures/audit/fetch_properties.json index 86e824f66..9658e8ab6 100644 --- a/spec/fixtures/audit/fetch_properties.json +++ b/spec/fixtures/audit/fetch_properties.json @@ -66,11 +66,6 @@ "host_weekly_price": 100, "host_monthly_price": 400, "number_of_units": 2, - "availability_dates": { - "2016-07-11": true, - "2016-07-12": false, - "2016-07-13": true - }, "images": [ { "identifier": "audit-unit1img1", @@ -95,11 +90,6 @@ "host_weekly_price": 100, "host_monthly_price": 400, "number_of_units": 2, - "availability_dates": { - "2016-07-11": false, - "2016-07-12": true, - "2016-07-13": true - }, "images": [ { "identifier": "audit-unit2img1", diff --git a/spec/lib/concierge/suppliers/audit/importer_spec.rb b/spec/lib/concierge/suppliers/audit/importer_spec.rb index b32c48228..047591448 100644 --- a/spec/lib/concierge/suppliers/audit/importer_spec.rb +++ b/spec/lib/concierge/suppliers/audit/importer_spec.rb @@ -39,10 +39,20 @@ describe '#json_to_property' do let(:json) { JSON.parse(read_fixture('audit/fetch_properties.json'))['result'].sample } - it 'should return Result of Roomorama::Property' do - result = importer.json_to_property(json) + it 'should return Result of Roomorama::Property with calendar parsed' do + parsed_calendar_entries = [] + result = importer.json_to_property(json) do |calendar_entries| + calendar_entries.each_with_index do |entry, index| + yyyymmdd, bool = json['availability_dates'].to_a[index] + expect(entry.date).to eq(Date.parse(yyyymmdd)) + expect(entry.available).to eq(bool) + expect(entry.nightly_rate).to eq(json['nightly_rate']) + parsed_calendar_entries << entry + end + end expect(result).to be_kind_of(Result) expect(result.value).to be_kind_of(Roomorama::Property) + expect(parsed_calendar_entries.length).to eq(3) end end end diff --git a/spec/workers/suppliers/audit_spec.rb b/spec/workers/suppliers/audit_spec.rb index 01acbc7f0..7f822de22 100644 --- a/spec/workers/suppliers/audit_spec.rb +++ b/spec/workers/suppliers/audit_spec.rb @@ -9,20 +9,29 @@ let(:fetch_properties_json) { JSON.parse(read_fixture('audit/fetch_properties.json')) } let(:credentials) { worker.send(:credentials) } - def synchronisation_counters - [:created, :updated, :deleted].inject({}) do |sum,k| - sum.merge(k => @counters.send(k)) + def keyvalue(counters) + [:created, :updated, :deleted, :available, :unavailable].inject({}) do |sum,k| + if counters.respond_to?(k) + sum.merge(k => counters.send(k)) + else + sum + end end end before do # do NOT make API calls during tests - allow_any_instance_of(Workers::Synchronisation).to receive(:run_operation).and_return(nil) + allow_any_instance_of(Workers::PropertySynchronisation).to receive(:run_operation).and_return(nil) # keep track of counters - @counters = Workers::Synchronisation::PropertyCounters.new(0, 0, 0) - allow_any_instance_of(Workers::Synchronisation).to receive(:save_sync_process) do |instance, *args| - @counters = instance.counters + @property_counters = Workers::PropertySynchronisation::PropertyCounters.new(0, 0, 0) + allow_any_instance_of(Workers::PropertySynchronisation).to receive(:save_sync_process) do |instance, *args| + @property_counters = instance.counters + end + + @calendar_counters = Workers::CalendarSynchronisation::AvailabilityCounters.new(0, 0) + allow_any_instance_of(Workers::CalendarSynchronisation).to receive(:finish!) do |instance, *args| + @calendar_counters = instance.counters end end @@ -37,13 +46,15 @@ def synchronisation_counters subject { proc { worker.perform } } context "fetched new property" do - it { is_expected.to change { synchronisation_counters }.to eq(created: 1, updated: 0, deleted: 0) } + it { is_expected.to change { keyvalue(@property_counters) }.to eq(created: 1, updated: 0, deleted: 0) } + it { is_expected.to change { keyvalue(@calendar_counters) }.to eq(available: 3, unavailable: 0) } end context "fetched existing property" do before do fetch_properties_json['result'].each do |json| - result = Audit::Importer.new(credentials).json_to_property(json) + result = Audit::Importer.new(credentials).json_to_property(json) do |calendar_entries| + end roomorama_property = result.value # See Workers::Router#dispatch # enqueues a diff operation if there is a property with the same identifier for the same host @@ -52,7 +63,8 @@ def synchronisation_counters end end - it { is_expected.to change { synchronisation_counters }.to eq(created: 0, updated: 1, deleted: 0) } + it { is_expected.to change { keyvalue(@property_counters) }.to eq(created: 0, updated: 1, deleted: 0) } + it { is_expected.to change { keyvalue(@calendar_counters) }.to eq(available: 3, unavailable: 0) } end context "error when importing json_to_property" do @@ -62,7 +74,8 @@ def synchronisation_counters end end - it { is_expected.not_to change { synchronisation_counters } } + it { is_expected.not_to change { keyvalue(@property_counters) } } + it { is_expected.not_to change { keyvalue(@calendar_counters) } } end context "error when fetching" do @@ -72,7 +85,8 @@ def synchronisation_counters end end - it { is_expected.not_to change { synchronisation_counters } } + it { is_expected.not_to change { keyvalue(@property_counters) } } + it { is_expected.not_to change { keyvalue(@calendar_counters) } } end end end From 72c4d3efec02edd133a6e26e4023fd2d5341fec1 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Wed, 20 Jul 2016 11:01:35 +0800 Subject: [PATCH 09/22] Fixup be192ba stub Workers::CalendarSynchronisation#run_operation --- spec/fixtures/audit/fetch_properties.json | 2 +- spec/workers/suppliers/audit_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/fixtures/audit/fetch_properties.json b/spec/fixtures/audit/fetch_properties.json index 9658e8ab6..57c330279 100644 --- a/spec/fixtures/audit/fetch_properties.json +++ b/spec/fixtures/audit/fetch_properties.json @@ -28,7 +28,7 @@ "services_airport_pickup_rate": 12, "services_concierge": true, "services_concierge_rate": 13, - "disabled": true, + "disabled": false, "instant_booking": true, "check_in_instructions": "Lorem ipsum instructions", "cancellation_policy": "standard", diff --git a/spec/workers/suppliers/audit_spec.rb b/spec/workers/suppliers/audit_spec.rb index 7f822de22..fdd14a64c 100644 --- a/spec/workers/suppliers/audit_spec.rb +++ b/spec/workers/suppliers/audit_spec.rb @@ -22,6 +22,7 @@ def keyvalue(counters) before do # do NOT make API calls during tests allow_any_instance_of(Workers::PropertySynchronisation).to receive(:run_operation).and_return(nil) + allow_any_instance_of(Workers::CalendarSynchronisation).to receive(:run_operation).and_return(nil) # keep track of counters @property_counters = Workers::PropertySynchronisation::PropertyCounters.new(0, 0, 0) From a90f4f4a4f50242d8cf9cd268abce8123b249630 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Fri, 15 Jul 2016 12:00:47 +0800 Subject: [PATCH 10/22] Add common integration parts https://github.com/roomorama/concierge/wiki/Supplier-integration-checklist#common-integration-parts --- .env.example | 1 + apps/api/config/initializers/validate_supplier_credentials.rb | 1 + apps/api/middlewares/authentication.rb | 1 + 3 files changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 74ceadec5..609523c43 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ CONCIERGE_DATABASE_URL="postgres://localhost/concierge_development" WAYTOSTAY_URL="https://apis.sandbox.waytostay.com:25443" +ROOMORAMA_SECRET_AUDIT=xxx ROOMORAMA_SECRET_JTB=xxx ROOMORAMA_SECRET_KIGO_LEGACY=xxx ROOMORAMA_SECRET_KIGO=xxx diff --git a/apps/api/config/initializers/validate_supplier_credentials.rb b/apps/api/config/initializers/validate_supplier_credentials.rb index 6dc55a6b7..7eacc60a9 100644 --- a/apps/api/config/initializers/validate_supplier_credentials.rb +++ b/apps/api/config/initializers/validate_supplier_credentials.rb @@ -2,6 +2,7 @@ if enforce_on_envs.include?(Hanami.env) Concierge::Credentials.validate_credentials!({ + audit: %w(secret_key host), atleisure: %w(username password test_mode), jtb: %w(id user password company url), kigo: %w(subscription_key), diff --git a/apps/api/middlewares/authentication.rb b/apps/api/middlewares/authentication.rb index a7912c102..f5017db62 100644 --- a/apps/api/middlewares/authentication.rb +++ b/apps/api/middlewares/authentication.rb @@ -46,6 +46,7 @@ class Authentication # secrets.for(request_path) # => X32842I class Secrets APP_SECRETS = { + "/audit" => ENV["ROOMORAMA_SECRET_AUDIT"], "/jtb" => ENV["ROOMORAMA_SECRET_JTB"], "/kigo/legacy" => ENV["ROOMORAMA_SECRET_KIGO_LEGACY"], "/kigo" => ENV["ROOMORAMA_SECRET_KIGO"], From 536437c75f0da52c5cc5b699b8930fa1bc39ec70 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Fri, 15 Jul 2016 12:02:05 +0800 Subject: [PATCH 11/22] Bare minimum support for audit APIs quote, booking, cancel --- apps/api/config/routes.rb | 3 + apps/api/controllers/audit/booking.rb | 20 +++ apps/api/controllers/audit/cancel.rb | 19 +++ apps/api/controllers/audit/quote.rb | 20 +++ lib/concierge/suppliers/audit/client.rb | 64 ++++++++++ spec/api/controllers/audit/booking_spec.rb | 53 ++++++++ spec/api/controllers/audit/cancel_spec.rb | 41 ++++++ spec/api/controllers/audit/quote_spec.rb | 91 ++++++++++++++ spec/fixtures/audit/booking.success.json | 13 ++ spec/fixtures/audit/cancel.success.json | 3 + spec/fixtures/audit/quotation.success.json | 12 ++ .../concierge/suppliers/audit/client_spec.rb | 119 ++++++++++++++++++ 12 files changed, 458 insertions(+) create mode 100644 apps/api/controllers/audit/booking.rb create mode 100644 apps/api/controllers/audit/cancel.rb create mode 100644 apps/api/controllers/audit/quote.rb create mode 100644 lib/concierge/suppliers/audit/client.rb create mode 100644 spec/api/controllers/audit/booking_spec.rb create mode 100644 spec/api/controllers/audit/cancel_spec.rb create mode 100644 spec/api/controllers/audit/quote_spec.rb create mode 100644 spec/fixtures/audit/booking.success.json create mode 100644 spec/fixtures/audit/cancel.success.json create mode 100644 spec/fixtures/audit/quotation.success.json create mode 100644 spec/lib/concierge/suppliers/audit/client_spec.rb diff --git a/apps/api/config/routes.rb b/apps/api/config/routes.rb index d1a5b1fd1..75fb56477 100644 --- a/apps/api/config/routes.rb +++ b/apps/api/config/routes.rb @@ -4,6 +4,7 @@ post '/kigo/legacy/quote', to: 'kigo/legacy#quote' post '/poplidays/quote', to: 'poplidays#quote' post '/waytostay/quote', to: 'waytostay#quote' +post '/audit/quote', to: 'audit#quote' post '/ciirus/quote', to: 'ciirus#quote' post '/jtb/booking', to: 'j_t_b#booking' @@ -12,8 +13,10 @@ post '/ciirus/booking', to: 'ciirus#booking' post '/kigo/booking', to: 'kigo#booking' post '/kigo/legacy/booking', to: 'kigo/legacy#booking' +post '/audit/booking', to: 'audit#booking' post 'waytostay/cancel', to: 'waytostay#cancel' +post '/audit/cancel', to: 'audit#cancel' post 'ciirus/cancel', to: 'ciirus#cancel' post 'checkout', to: 'static#checkout' diff --git a/apps/api/controllers/audit/booking.rb b/apps/api/controllers/audit/booking.rb new file mode 100644 index 000000000..7ca75e8a8 --- /dev/null +++ b/apps/api/controllers/audit/booking.rb @@ -0,0 +1,20 @@ +require_relative "../booking" + +module API::Controllers::Audit + + # API::Controllers::Audit::Booking + # + # Performs create booking for properties from Audit. + class Booking + include API::Controllers::Booking + + def create_booking(params) + Audit::Client.new.book(params) + end + + def supplier_name + Audit::Client::SUPPLIER_NAME + end + end +end + diff --git a/apps/api/controllers/audit/cancel.rb b/apps/api/controllers/audit/cancel.rb new file mode 100644 index 000000000..bdbca075a --- /dev/null +++ b/apps/api/controllers/audit/cancel.rb @@ -0,0 +1,19 @@ +require_relative "../cancel" + +module API::Controllers::Audit + + # API::Controllers::Audit::Cancel + # + # Cancels reservation from Audit. + class Cancel + include API::Controllers::Cancel + + def cancel_reservation(params) + Audit::Client.new.cancel(params) + end + + def supplier_name + Audit::Client::SUPPLIER_NAME + end + end +end diff --git a/apps/api/controllers/audit/quote.rb b/apps/api/controllers/audit/quote.rb new file mode 100644 index 000000000..2cf8de8db --- /dev/null +++ b/apps/api/controllers/audit/quote.rb @@ -0,0 +1,20 @@ +require_relative "../quote" + +module API::Controllers::Audit + + # API::Controllers::Audit::Quote + # + # Performs booking quotations for properties from Audit. + class Quote + include API::Controllers::Quote + + def quote_price(params) + Audit::Client.new.quote(params) + end + + def supplier_name + Audit::Client::SUPPLIER_NAME + end + end +end + diff --git a/lib/concierge/suppliers/audit/client.rb b/lib/concierge/suppliers/audit/client.rb new file mode 100644 index 000000000..877dfa8a2 --- /dev/null +++ b/lib/concierge/suppliers/audit/client.rb @@ -0,0 +1,64 @@ +module Audit + # +Audit::Client+ + # + # This class is a convenience class for interacting with Audit. + # + # For more information on how to interact with Audit, check the project Wiki. + class Client + + SUPPLIER_NAME = "Audit" + + attr_reader :credentials + + def initialize + @credentials = Concierge::Credentials.for("audit") + end + + # On success, return Result wrapping Quotation object + def quote(params) + client = Concierge::HTTPClient.new(credentials.host) + result = client.get("/spec/fixtures/audit/quotation.#{params[:property_id]}.json") + if result.success? + json = JSON.parse(result.value.body) + Result.new(Quotation.new(json['result'])) + else + result + end + end + + # On success, return Result wrapping Reservation object + def book(params) + client = Concierge::HTTPClient.new(credentials.host) + result = client.get("/spec/fixtures/audit/booking.#{params[:property_id]}.json") + if result.success? + json = JSON.parse(result.value.body) + Result.new(Reservation.new(json['result'])) + else + result + end + end + + # On success, return Result wrapping reservation_id String + def cancel(params) + client = Concierge::HTTPClient.new(credentials.host) + result = client.get("/spec/fixtures/audit/cancel.#{params[:reservation_id]}.json") + if result.success? + json = JSON.parse(result.value.body) + Result.new(json['result']) + else + result + end + end + + def announce_error(operation, result) + Concierge::Announcer.trigger(Concierge::Errors::EXTERNAL_ERROR, { + operation: operation, + supplier: SUPPLIER_NAME, + code: result.error.code, + context: Concierge.context.to_h, + happened_at: Time.now + }) + end + + end +end diff --git a/spec/api/controllers/audit/booking_spec.rb b/spec/api/controllers/audit/booking_spec.rb new file mode 100644 index 000000000..5a8abed35 --- /dev/null +++ b/spec/api/controllers/audit/booking_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' +require_relative "../shared/booking_validations" + +RSpec.describe API::Controllers::Audit::Booking do + include Support::HTTPStubbing + + let(:params) { + { + property_id: "A123", + check_in: "2016-03-22", + check_out: "2016-03-24", + guests: 2, + subtotal: 300, + customer: { + first_name: "Alex", + last_name: "Black", + country: "India", + city: "Mumbai", + address: "first street", + postal_code: "123123", + email: "test@example.com", + phone: "555-55-55", + } + } + } + + it_behaves_like "performing booking parameters validations", controller_generator: -> { described_class.new } + + + describe "#call" do + + let(:response) { parse_response(described_class.new.call(params)) } + + it "returns proper error if external request failed" do + erred_reservation = Result.error(:network_error) + expect_any_instance_of(Audit::Client).to receive(:book).and_return(erred_reservation) + + 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 + + it "returns a booking code when successful" do + reservation = Result.new(Reservation.new(params)) + reservation.value.code = "test_code" + expect_any_instance_of(Audit::Client).to receive(:book).and_return(reservation) + + expect(response.status).to eq 200 + expect(response.body["status"]).to eq "ok" + expect(response.body["code"]).to eq "test_code" + end + end +end diff --git a/spec/api/controllers/audit/cancel_spec.rb b/spec/api/controllers/audit/cancel_spec.rb new file mode 100644 index 000000000..97eef9cbb --- /dev/null +++ b/spec/api/controllers/audit/cancel_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' +require_relative "../shared/cancel" + +RSpec.describe API::Controllers::Audit::Cancel do + let(:params) { { reservation_id: "A123" } } + + it_behaves_like "cancel action" do + let(:success_cases) { + [ + { params: {reservation_id: "A023"}, cancelled_reservation_id: "XYZ" }, + { params: {reservation_id: "A024"}, cancelled_reservation_id: "ASD" }, + ] + } + let(:error_cases) { + [ + { params: {reservation_id: "A123"}, error: {"cancellation" => "Could not cancel with remote supplier"} }, + { params: {reservation_id: "A124"}, error: {"cancellation" => "Already cancelled"} }, + ] + } + + before do + allow_any_instance_of(Audit::Client).to receive(:cancel) do |instance, par| + result = nil + error_cases.each do |kase| + if par.reservation_id == kase[:params][:reservation_id] + result = Result.error(:already_cancelled, kase[:error]) + break + end + end + success_cases.each do |kase| + if par.reservation_id == kase[:params][:reservation_id] + result = Result.new(kase[:cancelled_reservation_id]) + break + end + end + result + end + end + end + +end diff --git a/spec/api/controllers/audit/quote_spec.rb b/spec/api/controllers/audit/quote_spec.rb new file mode 100644 index 000000000..ea779f64d --- /dev/null +++ b/spec/api/controllers/audit/quote_spec.rb @@ -0,0 +1,91 @@ +require "spec_helper" +require "concierge/result" +require_relative "../shared/quote_validations" +require_relative "../shared/external_error_reporting" + +RSpec.describe API::Controllers::Audit::Quote do + include Support::HTTPStubbing + + let(:params) { + { property_id: "success", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } + } + + 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(:params) { + { property_id: "success", unit_id: "123", check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } + } + let(:supplier_name) { "Audit" } + let(:error_code) { "savon_erorr" } + + def provoke_failure! + credentials = Concierge::Credentials.for("audit") + stub_call(:get, URI.join(credentials.host, "/spec/fixtures/audit/quotation.success.json").to_s) { raise Faraday::Error.new("oops123") } + Struct.new(:code).new("network_failure") + end + end + + describe "#call" do + subject { described_class.new.call(params) } + + it "returns a proper error message if client returns quotation with error" do + expect_any_instance_of(Audit::Client).to receive(:quote).and_return(Result.error(:network_error)) + + response = parse_response(subject) + expect(response.status).to eq 503 + expect(response.body["status"]).to eq "error" + expect(response.body["errors"]["quote"]).to eq "Could not quote price with remote supplier" + end + + it "returns unavailable quotation when client returns so" do + unavailable_quotation = Quotation.new({ + property_id: params[:property_id], + check_in: params[:check_in], + check_out: params[:check_out], + guests: params[:guests], + available: false + }) + expect_any_instance_of(Audit::Client).to receive(:quote).and_return(Result.new(unavailable_quotation)) + + response = parse_response(subject) + expect(response.status).to eq 200 + expect(response.body["status"]).to eq "ok" + expect(response.body["available"]).to eq false + expect(response.body["property_id"]).to eq "success" + 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).not_to have_key("currency") + expect(response.body).not_to have_key("total") + end + + it "returns available quotation when call is successful" do + available_quotation = Quotation.new({ + property_id: params[:property_id], + check_in: params[:check_in], + check_out: params[:check_out], + guests: params[:guests], + available: true, + currency: "EUR", + total: 56.78, + }) + expect_any_instance_of(Audit::Client).to receive(:quote).and_return(Result.new(available_quotation)) + + response = parse_response(subject) + expect(response.status).to eq 200 + expect(response.body["status"]).to eq "ok" + expect(response.body["available"]).to eq true + expect(response.body["property_id"]).to eq "success" + 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 "EUR" + expect(response.body["total"]).to eq 56.78 + + end + end + +end diff --git a/spec/fixtures/audit/booking.success.json b/spec/fixtures/audit/booking.success.json new file mode 100644 index 000000000..4d8758bc8 --- /dev/null +++ b/spec/fixtures/audit/booking.success.json @@ -0,0 +1,13 @@ +{ + "result": { + "property_id": "String", + "unit_id": "String", + "check_in": "String", + "check_out": "String", + "guests": 3, + "code": "FOOBAR-123", + "extra": "Hash", + "customer": { + } + } +} diff --git a/spec/fixtures/audit/cancel.success.json b/spec/fixtures/audit/cancel.success.json new file mode 100644 index 000000000..80bf76ad5 --- /dev/null +++ b/spec/fixtures/audit/cancel.success.json @@ -0,0 +1,3 @@ +{ + "result": "success" +} diff --git a/spec/fixtures/audit/quotation.success.json b/spec/fixtures/audit/quotation.success.json new file mode 100644 index 000000000..df8eb8886 --- /dev/null +++ b/spec/fixtures/audit/quotation.success.json @@ -0,0 +1,12 @@ +{ + "result": { + "property_id": "String", + "unit_id": "String", + "check_in": "String", + "check_out": "String", + "guests": 2, + "available": true, + "total": 400.59, + "currency": "String" + } +} diff --git a/spec/lib/concierge/suppliers/audit/client_spec.rb b/spec/lib/concierge/suppliers/audit/client_spec.rb new file mode 100644 index 000000000..20a26d2fa --- /dev/null +++ b/spec/lib/concierge/suppliers/audit/client_spec.rb @@ -0,0 +1,119 @@ +require "spec_helper" + +RSpec.describe Audit::Client do + include Support::HTTPStubbing + include Support::Fixtures + + let(:base_url) { Concierge::Credentials.for('Audit')['host'] } + subject { described_class.new } + + describe "#quote" do + + def quote_params_for(property_id) + { property_id: property_id, check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } + end + + it "returns the wrapped quotation from Audit::Price when successful" do + json = JSON.parse(read_fixture('audit/quotation.success.json')) + stub_call(:get, "#{base_url}/spec/fixtures/audit/quotation.success.json") { + [200, {}, json.to_json] + } + + quote_result = subject.quote(quote_params_for("success")) + expect(quote_result).to be_success + + quote = quote_result.value + expect(quote).to be_a Quotation + expect(quote.total).to eq json['result']['total'] + end + + it "returns a Result with a generic error message on failure" do + stub_call(:get, "#{base_url}/spec/fixtures/audit/quotation.connection_timeout.json") { + raise Faraday::TimeoutError.new + } + + quote_result = subject.quote(quote_params_for("connection_timeout")) + expect(quote_result).to_not be_success + expect(quote_result.error.code).to eq :connection_timeout + + quote = quote_result.value + expect(quote).to be_nil + end + end + + describe "#book" do + def book_params_for(property_id) + { + property_id: property_id, + check_in: "2016-03-22", + check_out: "2016-03-24", + guests: 2, + subtotal: 300, + customer: { + first_name: "Alex", + last_name: "Black", + country: "India", + city: "Mumbai", + address: "first street", + postal_code: "123123", + email: "test@example.com", + phone: "555-55-55", + } + } + end + + it "returns the wrapped reservation from Audit::Booking when successful" do + json = JSON.parse(read_fixture('audit/booking.success.json')) + stub_call(:get, "#{base_url}/spec/fixtures/audit/booking.success.json") { + [200, {}, json.to_json] + } + + reservation_result = subject.book(book_params_for('success')) + expect(reservation_result).to be_success + + reservation = reservation_result.value + expect(reservation).to be_a Reservation + expect(reservation.code).to eq json['result']['code'] + end + + it "returns a Result with a generic error message on failure" do + stub_call(:get, "#{base_url}/spec/fixtures/audit/booking.connection_timeout.json") { + raise Faraday::TimeoutError.new + } + + reservation_result = subject.book(book_params_for('connection_timeout')) + expect(reservation_result).to_not be_success + expect(reservation_result.error.code).to eq :connection_timeout + end + end + + describe "#cancel" do + def cancel_params_for(reservation_id) + { + reservation_id: reservation_id + } + end + + it "returns the wrapped reservation_id when successful" do + json = JSON.parse(read_fixture('audit/cancel.success.json')) + stub_call(:get, "#{base_url}/spec/fixtures/audit/cancel.success.json") { + [200, {}, json.to_json] + } + + reservation_result = subject.cancel(cancel_params_for('success')) + expect(reservation_result).to be_success + + expect(reservation_result.value).to eq json['result'] + end + + it "returns a Result with a generic error message on failure" do + stub_call(:get, "#{base_url}/spec/fixtures/audit/cancel.connection_timeout.json") { + raise Faraday::TimeoutError.new + } + + reservation_result = subject.cancel(cancel_params_for('connection_timeout')) + expect(reservation_result).to_not be_success + expect(reservation_result.error.code).to eq :connection_timeout + end + end +end From 46e06d989270fcd440876ba5a127142219bed26a Mon Sep 17 00:00:00 2001 From: choonkeat Date: Wed, 20 Jul 2016 13:21:14 +0800 Subject: [PATCH 12/22] Add mock audit server to respond to scenarios: - connection_timeout - invalid_json - wrong_json --- lib/concierge/suppliers/audit/client.rb | 6 ++++ spec/fixtures/audit/config.ru | 45 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/lib/concierge/suppliers/audit/client.rb b/lib/concierge/suppliers/audit/client.rb index 877dfa8a2..6dc56b5dc 100644 --- a/lib/concierge/suppliers/audit/client.rb +++ b/lib/concierge/suppliers/audit/client.rb @@ -15,6 +15,8 @@ def initialize end # On success, return Result wrapping Quotation object + # - When property_id is `success`, a successful response is returned + # - When property_id is `connection_timeout`, Faraday::TimeoutError should be raised by HTTP::Client def quote(params) client = Concierge::HTTPClient.new(credentials.host) result = client.get("/spec/fixtures/audit/quotation.#{params[:property_id]}.json") @@ -27,6 +29,8 @@ def quote(params) end # On success, return Result wrapping Reservation object + # - When property_id is `success`, a successful response is returned + # - When property_id is `connection_timeout`, Faraday::TimeoutError should be raised by HTTP::Client def book(params) client = Concierge::HTTPClient.new(credentials.host) result = client.get("/spec/fixtures/audit/booking.#{params[:property_id]}.json") @@ -39,6 +43,8 @@ def book(params) end # On success, return Result wrapping reservation_id String + # - When reservation_id is `success`, a successful response is returned + # - When reservation_id is `connection_timeout`, Faraday::TimeoutError should be raised by HTTP::Client def cancel(params) client = Concierge::HTTPClient.new(credentials.host) result = client.get("/spec/fixtures/audit/cancel.#{params[:reservation_id]}.json") diff --git a/spec/fixtures/audit/config.ru b/spec/fixtures/audit/config.ru index 66f4c805a..abeb6014d 100644 --- a/spec/fixtures/audit/config.ru +++ b/spec/fixtures/audit/config.ru @@ -9,9 +9,54 @@ # [2016-07-12 10:44:29] INFO ruby 2.3.0 (2015-12-25) [x86_64-darwin14] # [2016-07-12 10:44:29] INFO WEBrick::HTTPServer#start: pid=92594 port=9292 # +# +# To get successful response, request for +# - http://localhost:9292/spec/fixtures/audit/quotation.success.json +# - http://localhost:9292/spec/fixtures/audit/booking.success.json +# - http://localhost:9292/spec/fixtures/audit/cancel.success.json +# +# To get a connection timeout (sleeps Concierge::HTTPClient::CONNECTION_TIMEOUT + 1 second), +# replace `success` with `connection_timeout`, e.g. +# - http://localhost:9292/spec/fixtures/audit/quotation.connection_timeout.json +# +# To get an invalid json response, replace `success` with `invalid_json` or `wrong_json`, e.g. +# - http://localhost:9292/spec/fixtures/audit/quotation.wrong_json.json require 'rack' +require_relative '../../../lib/concierge/http_client.rb' +class FileNotFoundHandler + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + if status == 404 + case File.basename(env['REQUEST_PATH']) + + when /connection_timeout/ + # First we wait + sleep Concierge::HTTPClient::CONNECTION_TIMEOUT + 1 + + # Then we return the requested info (Concierge::HTTPClient should have errored out by now) + @app.call(env.merge({ + 'PATH_INFO' => env['PATH_INFO'].gsub('connection_timeout', 'success'), + 'REQUEST_PATH' => env['REQUEST_PATH'].gsub('connection_timeout', 'success'), + })) + + when /wrong_json/ + [200, {}, ["[1, 2, 3]"]] + + when /invalid_json/ + [200, {}, ["{"]] + end + + end || [status, headers, body] + end +end + +use FileNotFoundHandler use Rack::Static, :urls => ['/spec'] run -> (env) { From 291a4c4f0b835b5fd474f4f5b49f2424593416cb Mon Sep 17 00:00:00 2001 From: choonkeat Date: Fri, 22 Jul 2016 14:04:13 +0800 Subject: [PATCH 13/22] See a415f7c renaming to reservation.reference_number --- lib/concierge/suppliers/audit/client.rb | 8 ++++---- spec/api/controllers/audit/booking_spec.rb | 4 ++-- spec/api/controllers/audit/cancel_spec.rb | 16 ++++++++-------- .../lib/concierge/suppliers/audit/client_spec.rb | 8 ++++---- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/concierge/suppliers/audit/client.rb b/lib/concierge/suppliers/audit/client.rb index 6dc56b5dc..b6cea1d67 100644 --- a/lib/concierge/suppliers/audit/client.rb +++ b/lib/concierge/suppliers/audit/client.rb @@ -42,12 +42,12 @@ def book(params) end end - # On success, return Result wrapping reservation_id String - # - When reservation_id is `success`, a successful response is returned - # - When reservation_id is `connection_timeout`, Faraday::TimeoutError should be raised by HTTP::Client + # On success, return Result wrapping reference_number String + # - When reference_number is `success`, a successful response is returned + # - When reference_number is `connection_timeout`, Faraday::TimeoutError should be raised by HTTP::Client def cancel(params) client = Concierge::HTTPClient.new(credentials.host) - result = client.get("/spec/fixtures/audit/cancel.#{params[:reservation_id]}.json") + result = client.get("/spec/fixtures/audit/cancel.#{params[:reference_number]}.json") if result.success? json = JSON.parse(result.value.body) Result.new(json['result']) diff --git a/spec/api/controllers/audit/booking_spec.rb b/spec/api/controllers/audit/booking_spec.rb index 5a8abed35..6546b0df2 100644 --- a/spec/api/controllers/audit/booking_spec.rb +++ b/spec/api/controllers/audit/booking_spec.rb @@ -42,12 +42,12 @@ it "returns a booking code when successful" do reservation = Result.new(Reservation.new(params)) - reservation.value.code = "test_code" + reservation.value.reference_number = "test_code" expect_any_instance_of(Audit::Client).to receive(:book).and_return(reservation) expect(response.status).to eq 200 expect(response.body["status"]).to eq "ok" - expect(response.body["code"]).to eq "test_code" + expect(response.body["reference_number"]).to eq "test_code" end end end diff --git a/spec/api/controllers/audit/cancel_spec.rb b/spec/api/controllers/audit/cancel_spec.rb index 97eef9cbb..f29e3bbac 100644 --- a/spec/api/controllers/audit/cancel_spec.rb +++ b/spec/api/controllers/audit/cancel_spec.rb @@ -2,19 +2,19 @@ require_relative "../shared/cancel" RSpec.describe API::Controllers::Audit::Cancel do - let(:params) { { reservation_id: "A123" } } + let(:params) { { reference_number: "A123" } } it_behaves_like "cancel action" do let(:success_cases) { [ - { params: {reservation_id: "A023"}, cancelled_reservation_id: "XYZ" }, - { params: {reservation_id: "A024"}, cancelled_reservation_id: "ASD" }, + { params: {reference_number: "A023"}, cancelled_reference_number: "XYZ" }, + { params: {reference_number: "A024"}, cancelled_reference_number: "ASD" }, ] } let(:error_cases) { [ - { params: {reservation_id: "A123"}, error: {"cancellation" => "Could not cancel with remote supplier"} }, - { params: {reservation_id: "A124"}, error: {"cancellation" => "Already cancelled"} }, + { params: {reference_number: "A123"}, error: {"cancellation" => "Could not cancel with remote supplier"} }, + { params: {reference_number: "A124"}, error: {"cancellation" => "Already cancelled"} }, ] } @@ -22,14 +22,14 @@ allow_any_instance_of(Audit::Client).to receive(:cancel) do |instance, par| result = nil error_cases.each do |kase| - if par.reservation_id == kase[:params][:reservation_id] + 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.reservation_id == kase[:params][:reservation_id] - result = Result.new(kase[:cancelled_reservation_id]) + if par.reference_number == kase[:params][:reference_number] + result = Result.new(kase[:cancelled_reference_number]) break end end diff --git a/spec/lib/concierge/suppliers/audit/client_spec.rb b/spec/lib/concierge/suppliers/audit/client_spec.rb index 20a26d2fa..a753acb2a 100644 --- a/spec/lib/concierge/suppliers/audit/client_spec.rb +++ b/spec/lib/concierge/suppliers/audit/client_spec.rb @@ -73,7 +73,7 @@ def book_params_for(property_id) reservation = reservation_result.value expect(reservation).to be_a Reservation - expect(reservation.code).to eq json['result']['code'] + expect(reservation.reference_number).to eq json['result']['reference_number'] end it "returns a Result with a generic error message on failure" do @@ -88,13 +88,13 @@ def book_params_for(property_id) end describe "#cancel" do - def cancel_params_for(reservation_id) + def cancel_params_for(reference_number) { - reservation_id: reservation_id + reference_number: reference_number } end - it "returns the wrapped reservation_id when successful" do + it "returns the wrapped reference_number when successful" do json = JSON.parse(read_fixture('audit/cancel.success.json')) stub_call(:get, "#{base_url}/spec/fixtures/audit/cancel.success.json") { [200, {}, json.to_json] From d4ee04d8eee35c47d908221b09df3785a2c12996 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Fri, 22 Jul 2016 14:29:20 +0800 Subject: [PATCH 14/22] Implements shared specs spec/lib/concierge/suppliers/shared --- spec/fixtures/audit/booking.success.json | 1 + .../fixtures/audit/quotation.unavailable.json | 12 +++ .../concierge/suppliers/audit/client_spec.rb | 98 +++++++++++++------ 3 files changed, 81 insertions(+), 30 deletions(-) create mode 100644 spec/fixtures/audit/quotation.unavailable.json diff --git a/spec/fixtures/audit/booking.success.json b/spec/fixtures/audit/booking.success.json index 4d8758bc8..16b011b5e 100644 --- a/spec/fixtures/audit/booking.success.json +++ b/spec/fixtures/audit/booking.success.json @@ -1,5 +1,6 @@ { "result": { + "reference_number": "success-reservation_number123", "property_id": "String", "unit_id": "String", "check_in": "String", diff --git a/spec/fixtures/audit/quotation.unavailable.json b/spec/fixtures/audit/quotation.unavailable.json new file mode 100644 index 000000000..69bff7e22 --- /dev/null +++ b/spec/fixtures/audit/quotation.unavailable.json @@ -0,0 +1,12 @@ +{ + "result": { + "property_id": "String", + "unit_id": "String", + "check_in": "String", + "check_out": "String", + "guests": 2, + "available": false, + "total": 400.59, + "currency": "String" + } +} diff --git a/spec/lib/concierge/suppliers/audit/client_spec.rb b/spec/lib/concierge/suppliers/audit/client_spec.rb index a753acb2a..13c46caed 100644 --- a/spec/lib/concierge/suppliers/audit/client_spec.rb +++ b/spec/lib/concierge/suppliers/audit/client_spec.rb @@ -1,4 +1,7 @@ require "spec_helper" +require_relative "../shared/book" +require_relative "../shared/quote" +require_relative "../shared/cancel" RSpec.describe Audit::Client do include Support::HTTPStubbing @@ -8,30 +11,35 @@ subject { described_class.new } describe "#quote" do + let(:success_json) { JSON.parse(read_fixture('audit/quotation.success.json')) } + let(:unavailable_json) { JSON.parse(read_fixture('audit/quotation.unavailable.json')) } + + before do + stub_call(:get, "#{base_url}/spec/fixtures/audit/quotation.success.json") { + [200, {}, success_json.to_json] + } + stub_call(:get, "#{base_url}/spec/fixtures/audit/quotation.unavailable.json") { + [200, {}, unavailable_json.to_json] + } + stub_call(:get, "#{base_url}/spec/fixtures/audit/quotation.connection_timeout.json") { + raise Faraday::TimeoutError.new + } + end def quote_params_for(property_id) { property_id: property_id, check_in: "2016-03-22", check_out: "2016-03-25", guests: 2 } end it "returns the wrapped quotation from Audit::Price when successful" do - json = JSON.parse(read_fixture('audit/quotation.success.json')) - stub_call(:get, "#{base_url}/spec/fixtures/audit/quotation.success.json") { - [200, {}, json.to_json] - } - quote_result = subject.quote(quote_params_for("success")) expect(quote_result).to be_success quote = quote_result.value expect(quote).to be_a Quotation - expect(quote.total).to eq json['result']['total'] + expect(quote.total).to eq success_json['result']['total'] end it "returns a Result with a generic error message on failure" do - stub_call(:get, "#{base_url}/spec/fixtures/audit/quotation.connection_timeout.json") { - raise Faraday::TimeoutError.new - } - quote_result = subject.quote(quote_params_for("connection_timeout")) expect(quote_result).to_not be_success expect(quote_result.error.code).to eq :connection_timeout @@ -39,9 +47,31 @@ def quote_params_for(property_id) quote = quote_result.value expect(quote).to be_nil end + + it_behaves_like "supplier quote method" do + let(:supplier_client) { described_class.new } + let(:success_params) { quote_params_for('success') } + let(:unavailable_params_list) {[ + quote_params_for('unavailable'), + ]} + let(:error_params_list) {[ + quote_params_for('connection_timeout'), + ]} + end end describe "#book" do + let(:success_json) { JSON.parse(read_fixture('audit/booking.success.json')) } + + before do + stub_call(:get, "#{base_url}/spec/fixtures/audit/booking.success.json") { + [200, {}, success_json.to_json] + } + stub_call(:get, "#{base_url}/spec/fixtures/audit/booking.connection_timeout.json") { + raise Faraday::TimeoutError.new + } + end + def book_params_for(property_id) { property_id: property_id, @@ -63,31 +93,42 @@ def book_params_for(property_id) end it "returns the wrapped reservation from Audit::Booking when successful" do - json = JSON.parse(read_fixture('audit/booking.success.json')) - stub_call(:get, "#{base_url}/spec/fixtures/audit/booking.success.json") { - [200, {}, json.to_json] - } - reservation_result = subject.book(book_params_for('success')) expect(reservation_result).to be_success reservation = reservation_result.value expect(reservation).to be_a Reservation - expect(reservation.reference_number).to eq json['result']['reference_number'] + expect(reservation.reference_number).to eq success_json['result']['reference_number'] end it "returns a Result with a generic error message on failure" do - stub_call(:get, "#{base_url}/spec/fixtures/audit/booking.connection_timeout.json") { - raise Faraday::TimeoutError.new - } - reservation_result = subject.book(book_params_for('connection_timeout')) expect(reservation_result).to_not be_success expect(reservation_result.error.code).to eq :connection_timeout end + + it_behaves_like "supplier book method" do + let(:supplier_client) { described_class.new } + let(:success_params) { book_params_for('success') } + let(:successful_reference_number) { success_json['result']['reference_number'] } + let(:error_params_list) {[ + book_params_for('connection_timeout'), + ]} + end end describe "#cancel" do + let(:success_json) { JSON.parse(read_fixture('audit/cancel.success.json')) } + + before do + stub_call(:get, "#{base_url}/spec/fixtures/audit/cancel.success.json") { + [200, {}, success_json.to_json] + } + stub_call(:get, "#{base_url}/spec/fixtures/audit/cancel.connection_timeout.json") { + raise Faraday::TimeoutError.new + } + end + def cancel_params_for(reference_number) { reference_number: reference_number @@ -95,25 +136,22 @@ def cancel_params_for(reference_number) end it "returns the wrapped reference_number when successful" do - json = JSON.parse(read_fixture('audit/cancel.success.json')) - stub_call(:get, "#{base_url}/spec/fixtures/audit/cancel.success.json") { - [200, {}, json.to_json] - } - reservation_result = subject.cancel(cancel_params_for('success')) expect(reservation_result).to be_success - expect(reservation_result.value).to eq json['result'] + expect(reservation_result.value).to eq success_json['result'] end it "returns a Result with a generic error message on failure" do - stub_call(:get, "#{base_url}/spec/fixtures/audit/cancel.connection_timeout.json") { - raise Faraday::TimeoutError.new - } - reservation_result = subject.cancel(cancel_params_for('connection_timeout')) expect(reservation_result).to_not be_success expect(reservation_result.error.code).to eq :connection_timeout end + + it_behaves_like "supplier cancel method" do + let(:supplier_client) { described_class.new } + let(:success_params) { cancel_params_for('success') } + let(:error_params) { cancel_params_for('connection_timeout') } + end end end From 0d6c233387b8f68aefa6042b79848dec32982e99 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Mon, 25 Jul 2016 11:59:38 +0800 Subject: [PATCH 15/22] Extracted rack middleware logic to lib/concierge/suppliers/audit/server.rb --- lib/concierge/suppliers/audit/server.rb | 40 +++++++++++++++++++++++++ spec/fixtures/audit/config.ru | 35 ++-------------------- 2 files changed, 42 insertions(+), 33 deletions(-) create mode 100644 lib/concierge/suppliers/audit/server.rb diff --git a/lib/concierge/suppliers/audit/server.rb b/lib/concierge/suppliers/audit/server.rb new file mode 100644 index 000000000..804d6399c --- /dev/null +++ b/lib/concierge/suppliers/audit/server.rb @@ -0,0 +1,40 @@ +require_relative '../../http_client' + +module Audit + # +Audit::Server+ + # + # This class is the Audit web app. + # + # For more information on how to interact with Audit, check the project Wiki. + class Server + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + if status == 404 + case File.basename(env['REQUEST_PATH']) + + when /connection_timeout/ + # First we wait + sleep Concierge::HTTPClient::CONNECTION_TIMEOUT + 1 + + # Then we return the requested info (Concierge::HTTPClient should have errored out by now) + @app.call(env.merge({ + 'PATH_INFO' => env['PATH_INFO'].gsub('connection_timeout', 'success'), + 'REQUEST_PATH' => env['REQUEST_PATH'].gsub('connection_timeout', 'success'), + })) + + when /wrong_json/ + [200, {}, ["[1, 2, 3]"]] + + when /invalid_json/ + [200, {}, ["{"]] + end + + end || [status, headers, body] + end + end +end diff --git a/spec/fixtures/audit/config.ru b/spec/fixtures/audit/config.ru index abeb6014d..d78d540ae 100644 --- a/spec/fixtures/audit/config.ru +++ b/spec/fixtures/audit/config.ru @@ -23,40 +23,9 @@ # - http://localhost:9292/spec/fixtures/audit/quotation.wrong_json.json require 'rack' +require_relative '../../../lib/concierge/suppliers/audit/server.rb' -require_relative '../../../lib/concierge/http_client.rb' -class FileNotFoundHandler - def initialize(app) - @app = app - end - - def call(env) - status, headers, body = @app.call(env) - if status == 404 - case File.basename(env['REQUEST_PATH']) - - when /connection_timeout/ - # First we wait - sleep Concierge::HTTPClient::CONNECTION_TIMEOUT + 1 - - # Then we return the requested info (Concierge::HTTPClient should have errored out by now) - @app.call(env.merge({ - 'PATH_INFO' => env['PATH_INFO'].gsub('connection_timeout', 'success'), - 'REQUEST_PATH' => env['REQUEST_PATH'].gsub('connection_timeout', 'success'), - })) - - when /wrong_json/ - [200, {}, ["[1, 2, 3]"]] - - when /invalid_json/ - [200, {}, ["{"]] - end - - end || [status, headers, body] - end -end - -use FileNotFoundHandler +use Audit::Server use Rack::Static, :urls => ['/spec'] run -> (env) { From 544ef8802e71b15c471a3058d9c7677ba4e5c1d8 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Mon, 25 Jul 2016 12:22:24 +0800 Subject: [PATCH 16/22] Add "REPLACEME" replacement logic to Audit::Server --- lib/concierge/suppliers/audit/server.rb | 54 ++++++++++----- spec/fixtures/audit/booking.success.json | 2 +- .../concierge/suppliers/audit/server_spec.rb | 69 +++++++++++++++++++ 3 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 spec/lib/concierge/suppliers/audit/server_spec.rb diff --git a/lib/concierge/suppliers/audit/server.rb b/lib/concierge/suppliers/audit/server.rb index 804d6399c..1baecbfb9 100644 --- a/lib/concierge/suppliers/audit/server.rb +++ b/lib/concierge/suppliers/audit/server.rb @@ -14,27 +14,49 @@ def initialize(app) def call(env) status, headers, body = @app.call(env) - if status == 404 - case File.basename(env['REQUEST_PATH']) + status, headers, body = handle_404(env) if status == 404 - when /connection_timeout/ - # First we wait - sleep Concierge::HTTPClient::CONNECTION_TIMEOUT + 1 + if File.basename(env['PATH_INFO']) =~ /booking/ + [status, headers, [replace_response_body(body)]] + else + [status, headers, body] + end + end + + private - # Then we return the requested info (Concierge::HTTPClient should have errored out by now) - @app.call(env.merge({ - 'PATH_INFO' => env['PATH_INFO'].gsub('connection_timeout', 'success'), - 'REQUEST_PATH' => env['REQUEST_PATH'].gsub('connection_timeout', 'success'), - })) + def handle_404(env) + case File.basename(env['PATH_INFO']) + when /connection_timeout/ + # First we wait + sleep Concierge::HTTPClient::CONNECTION_TIMEOUT + 1 - when /wrong_json/ - [200, {}, ["[1, 2, 3]"]] + # Then we return the requested info (Concierge::HTTPClient should have errored out by now) + @app.call(env.merge({ + 'PATH_INFO' => env['PATH_INFO'].gsub('connection_timeout', 'success'), + 'REQUEST_PATH' => env['REQUEST_PATH'] && env['REQUEST_PATH'].gsub('connection_timeout', 'success'), + })) - when /invalid_json/ - [200, {}, ["{"]] - end + when /wrong_json/ + [200, {}, ["[1, 2, 3]"]] + + when /invalid_json/ + [200, {}, ["{"]] + end + end - end || [status, headers, body] + def replace_response_body(body) + body_string = case body + when Rack::File + IO.read body.path + else + body.join("") + end + + body_string.gsub("REPLACEME", [ + "success", + "connection_timeout" + ].sample) end end end diff --git a/spec/fixtures/audit/booking.success.json b/spec/fixtures/audit/booking.success.json index 16b011b5e..a71f8fe84 100644 --- a/spec/fixtures/audit/booking.success.json +++ b/spec/fixtures/audit/booking.success.json @@ -1,6 +1,6 @@ { "result": { - "reference_number": "success-reservation_number123", + "reference_number": "REPLACEME", "property_id": "String", "unit_id": "String", "check_in": "String", diff --git a/spec/lib/concierge/suppliers/audit/server_spec.rb b/spec/lib/concierge/suppliers/audit/server_spec.rb new file mode 100644 index 000000000..57953e58f --- /dev/null +++ b/spec/lib/concierge/suppliers/audit/server_spec.rb @@ -0,0 +1,69 @@ +require "spec_helper" + +RSpec.describe Audit::Server do + include Support::Fixtures + + let(:app) { ->(env) { [200, env, "app"] } } + let(:middleware) { described_class.new(Rack::Static.new(app, urls: ['/spec'])) } + + it "serves success files as-is" do + file = %w[ + spec/fixtures/audit/cancel.success.json + spec/fixtures/audit/quotation.success.json + ].sample + code, env, response = middleware.call Rack::MockRequest.env_for("http://admin.example.com/#{file}", {}) + expect(code).to eq(200) + expect(response_body_as_string(response)).to eq(IO.read file) + end + + it "serves connection_timeout with correct content but after CONNECTION_TIMEOUT seconds delay" do + # we could do a start/end check.. but that'll make test slower + expect(middleware).to receive(:sleep).with(Concierge::HTTPClient::CONNECTION_TIMEOUT + 1).and_return(nil) + + file = %w[ + spec/fixtures/audit/cancel.success.json + spec/fixtures/audit/quotation.success.json + ].sample + code, env, response = middleware.call Rack::MockRequest.env_for("http://admin.example.com/#{file.gsub("success", "connection_timeout")}", {}) + expect(code).to eq(200) + expect(response_body_as_string(response)).to eq(IO.read file) + end + + it "serves successful booking with random `reference_number`" do + file = "spec/fixtures/audit/booking.success.json" + code, env, response = middleware.call Rack::MockRequest.env_for("http://admin.example.com/#{file}", {}) + expect(code).to eq(200) + file_json = JSON.parse(IO.read file) + resp_json = JSON.parse(response_body_as_string(response)) + expect(resp_json).not_to eq(file_json) + expect(result_without_key resp_json, 'reference_number').to eq(result_without_key file_json, 'reference_number') + end + + it "serves wrong_json with a wrong but valid json string" do + file = Dir["spec/fixtures/audit/*.success.json"].sample.gsub("success", "wrong_json") + code, env, response = middleware.call Rack::MockRequest.env_for("http://admin.example.com/#{file}", {}) + expect(code).to eq(200) + expect(response_body_as_string(response)).to eq("[1, 2, 3]") + end + + it "serves invalid_json with an invalid json string" do + file = Dir["spec/fixtures/audit/*.success.json"].sample.gsub("success", "invalid_json") + code, env, response = middleware.call Rack::MockRequest.env_for("http://admin.example.com/#{file}", {}) + expect(code).to eq(200) + expect(response_body_as_string(response)).to eq("{") + end + + def result_without_key(hash, key) + hash['result'].delete(key) + hash + end + + def response_body_as_string(response) + case response + when Rack::File + IO.read response.path + else + response.join("") + end + end +end From ec75de9b140ebbfc334650fae3b2a40530ab0c1f Mon Sep 17 00:00:00 2001 From: choonkeat Date: Tue, 26 Jul 2016 11:48:09 +0800 Subject: [PATCH 17/22] Respond with "success" when requesting "sample" (roomorama test requests) Return proper 404 response when 404 --- apps/api/config/environment_variables.yml | 1 + lib/concierge/suppliers/audit/server.rb | 21 +++++++++++++------ .../concierge/suppliers/audit/server_spec.rb | 16 ++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/apps/api/config/environment_variables.yml b/apps/api/config/environment_variables.yml index 60227e882..9671ceaa6 100644 --- a/apps/api/config/environment_variables.yml +++ b/apps/api/config/environment_variables.yml @@ -1,3 +1,4 @@ +- ROOMORAMA_SECRET_AUDIT - ROOMORAMA_SECRET_JTB - ROOMORAMA_SECRET_KIGO_LEGACY - ROOMORAMA_SECRET_KIGO diff --git a/lib/concierge/suppliers/audit/server.rb b/lib/concierge/suppliers/audit/server.rb index 1baecbfb9..5f92507c4 100644 --- a/lib/concierge/suppliers/audit/server.rb +++ b/lib/concierge/suppliers/audit/server.rb @@ -14,10 +14,11 @@ def initialize(app) def call(env) status, headers, body = @app.call(env) - status, headers, body = handle_404(env) if status == 404 + status, headers, body = handle_404(env) || [status, headers, body] if status == 404 if File.basename(env['PATH_INFO']) =~ /booking/ - [status, headers, [replace_response_body(body)]] + new_body = replace_response_body(body) + [status, headers.merge('Content-Length' => new_body.length.to_s), [new_body]] else [status, headers, body] end @@ -25,17 +26,25 @@ def call(env) private + def retry_with(env, old_string, new_string) + @app.call(env.merge({ + 'PATH_INFO' => env['PATH_INFO'].gsub(old_string, new_string), + 'REQUEST_PATH' => env['REQUEST_PATH'] && env['REQUEST_PATH'].gsub(old_string, new_string), + })) + end + def handle_404(env) case File.basename(env['PATH_INFO']) + when /sample/ + # sample = success + retry_with(env, 'sample', 'success') + when /connection_timeout/ # First we wait sleep Concierge::HTTPClient::CONNECTION_TIMEOUT + 1 # Then we return the requested info (Concierge::HTTPClient should have errored out by now) - @app.call(env.merge({ - 'PATH_INFO' => env['PATH_INFO'].gsub('connection_timeout', 'success'), - 'REQUEST_PATH' => env['REQUEST_PATH'] && env['REQUEST_PATH'].gsub('connection_timeout', 'success'), - })) + retry_with(env, 'connection_timeout', 'success') when /wrong_json/ [200, {}, ["[1, 2, 3]"]] diff --git a/spec/lib/concierge/suppliers/audit/server_spec.rb b/spec/lib/concierge/suppliers/audit/server_spec.rb index 57953e58f..11e70d3d4 100644 --- a/spec/lib/concierge/suppliers/audit/server_spec.rb +++ b/spec/lib/concierge/suppliers/audit/server_spec.rb @@ -16,6 +16,16 @@ expect(response_body_as_string(response)).to eq(IO.read file) end + it "serves sample requests as success" do + file = %w[ + spec/fixtures/audit/cancel.success.json + spec/fixtures/audit/quotation.success.json + ].sample + code, env, response = middleware.call Rack::MockRequest.env_for("http://admin.example.com/#{file.gsub("success", "sample")}", {}) + expect(code).to eq(200) + expect(response_body_as_string(response)).to eq(IO.read file) + end + it "serves connection_timeout with correct content but after CONNECTION_TIMEOUT seconds delay" do # we could do a start/end check.. but that'll make test slower expect(middleware).to receive(:sleep).with(Concierge::HTTPClient::CONNECTION_TIMEOUT + 1).and_return(nil) @@ -53,6 +63,12 @@ expect(response_body_as_string(response)).to eq("{") end + it "serves unknown as 404" do + file = Dir["spec/fixtures/audit/*.success.json"].sample.gsub("success", "unknown") + code, env, response = middleware.call Rack::MockRequest.env_for("http://admin.example.com/#{file}", {}) + expect(code).to eq(404) + end + def result_without_key(hash, key) hash['result'].delete(key) hash From 19421e6148973a4cc76e1589ec5abbe6a4b2be87 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Tue, 26 Jul 2016 12:08:16 +0800 Subject: [PATCH 18/22] Change fetch_properties API endpoint to be "properties.json" which returns an array of "fixtures/audit/property.json" with "identifier" being set to success, connection_timeout, etc ... --- lib/concierge/suppliers/audit/server.rb | 12 +++ spec/fixtures/audit/property.json | 99 +++++++++++++++++++ .../suppliers/audit/importer_spec.rb | 2 +- 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/audit/property.json diff --git a/lib/concierge/suppliers/audit/server.rb b/lib/concierge/suppliers/audit/server.rb index 5f92507c4..6c064e2bd 100644 --- a/lib/concierge/suppliers/audit/server.rb +++ b/lib/concierge/suppliers/audit/server.rb @@ -1,3 +1,4 @@ +require 'json' require_relative '../../http_client' module Audit @@ -35,6 +36,17 @@ def retry_with(env, old_string, new_string) def handle_404(env) case File.basename(env['PATH_INFO']) + when /properties/ + property_json = JSON.parse(IO.read 'spec/fixtures/audit/property.json') + result = [ + 'success', + 'connection_timeout', + 'wrong_json', + 'invalid_json', + ].collect {|k| property_json.merge('identifier' => k, 'title' => "#{property_json['title']} (#{k})") } + new_body = Hash(result: result).to_json + [200, {}, [new_body]] + when /sample/ # sample = success retry_with(env, 'sample', 'success') diff --git a/spec/fixtures/audit/property.json b/spec/fixtures/audit/property.json new file mode 100644 index 000000000..1a50d029f --- /dev/null +++ b/spec/fixtures/audit/property.json @@ -0,0 +1,99 @@ +{ + "identifier": "audit-property-1", + "title": "Audit Property #1", + "type": "house", + "subtype": "cabin", + "postal_code": "069935", + "country_code": "SG", + "city": "Singapore", + "neighborhood": "CBD", + "description": "Audit property description", + "number_of_bedrooms": 2, + "minimum_stay": 1, + "weekly_rate": 100, + "monthly_rate": 400, + "security_deposit_amount": 99, + "security_deposit_currency_code": "MYR", + "security_deposit_type": "credit_card_auth", + "pets_allowed": true, + "smoking_allowed": true, + "services_cleaning": true, + "services_cleaning_rate": 10, + "services_cleaning_required": true, + "services_airport_pickup": true, + "services_car_rental": true, + "services_car_rental_rate": 11.5, + "services_airport_pickup_rate": 12, + "services_concierge": true, + "services_concierge_rate": 13, + "disabled": false, + "instant_booking": true, + "check_in_instructions": "Lorem ipsum instructions", + "cancellation_policy": "standard", + "default_to_available": true, + "lat": 1.282097, + "lng": 103.848025, + "currency": "SGD", + "address": "115 Amoy Street", + "apartment_number": "#02-01", + "max_guests": 2, + "nightly_rate": 100, + "availability_dates": { + "2016-07-11": true, + "2016-07-12": true, + "2016-07-13": true + }, + "images": [ + { + "identifier": "audit-image1", + "url": "/spec/fixtures/audit/villa.jpg?img1", + "caption": "Barbecue Pit" + } + ], + "units": [ + { + "identifier": "audit-unit1", + "title": "Audit Unit 1", + "description": "Audit unit 1 description", + "nightly_rate": 20, + "weekly_rate": 100, + "monthly_rate": 400, + "number_of_bedrooms": 2, + "max_guests": 4, + "host_daily_price": 20, + "host_weekly_price": 100, + "host_monthly_price": 400, + "number_of_units": 2, + "images": [ + { + "identifier": "audit-unit1img1", + "url": "/spec/fixtures/audit/villa.jpg?unit1img1" + }, + { + "identifier": "audit-unit1img2", + "url": "/spec/fixtures/audit/😻猫.png?unit1img2" + } + ] + }, + { + "identifier": "audit-unit2", + "title": "Audit Unit 2", + "description": "Audit unit 2 description", + "nightly_rate": 20, + "weekly_rate": 100, + "monthly_rate": 400, + "number_of_bedrooms": 2, + "max_guests": 4, + "host_daily_price": 20, + "host_weekly_price": 100, + "host_monthly_price": 400, + "number_of_units": 2, + "images": [ + { + "identifier": "audit-unit2img1", + "url": "/spec/fixtures/audit/😻猫.png?unit2img1" + } + ] + } + ] +} diff --git a/spec/lib/concierge/suppliers/audit/importer_spec.rb b/spec/lib/concierge/suppliers/audit/importer_spec.rb index 047591448..04acfaf88 100644 --- a/spec/lib/concierge/suppliers/audit/importer_spec.rb +++ b/spec/lib/concierge/suppliers/audit/importer_spec.rb @@ -37,7 +37,7 @@ end describe '#json_to_property' do - let(:json) { JSON.parse(read_fixture('audit/fetch_properties.json'))['result'].sample } + let(:json) { JSON.parse(read_fixture('audit/property.json')) } it 'should return Result of Roomorama::Property with calendar parsed' do parsed_calendar_entries = [] From 030320f13ba1cc18c8f714a047f314cdcbe14a74 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Tue, 26 Jul 2016 13:53:52 +0800 Subject: [PATCH 19/22] Fix to make MoneyExchanger#convert happy Otherwise, "base_currency = self.to_currency(base_currency)" becomes nil --- spec/fixtures/audit/quotation.success.json | 2 +- spec/fixtures/audit/quotation.unavailable.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/fixtures/audit/quotation.success.json b/spec/fixtures/audit/quotation.success.json index df8eb8886..a28df28a9 100644 --- a/spec/fixtures/audit/quotation.success.json +++ b/spec/fixtures/audit/quotation.success.json @@ -7,6 +7,6 @@ "guests": 2, "available": true, "total": 400.59, - "currency": "String" + "currency": "USD" } } diff --git a/spec/fixtures/audit/quotation.unavailable.json b/spec/fixtures/audit/quotation.unavailable.json index 69bff7e22..36a6a4415 100644 --- a/spec/fixtures/audit/quotation.unavailable.json +++ b/spec/fixtures/audit/quotation.unavailable.json @@ -7,6 +7,6 @@ "guests": 2, "available": false, "total": 400.59, - "currency": "String" + "currency": "USD" } } From a1e479ef0a03aa0cddf4214f9cac45254902cefe Mon Sep 17 00:00:00 2001 From: choonkeat Date: Tue, 26 Jul 2016 14:29:18 +0800 Subject: [PATCH 20/22] Extract possible scenarios to Audit::Server::SCENARIOS array --- lib/concierge/suppliers/audit/server.rb | 19 +++++++++---------- .../concierge/suppliers/audit/server_spec.rb | 1 + 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/concierge/suppliers/audit/server.rb b/lib/concierge/suppliers/audit/server.rb index 6c064e2bd..be5527e2b 100644 --- a/lib/concierge/suppliers/audit/server.rb +++ b/lib/concierge/suppliers/audit/server.rb @@ -9,6 +9,13 @@ module Audit # For more information on how to interact with Audit, check the project Wiki. class Server + SCENARIOS = [ + 'success', + 'connection_timeout', + 'wrong_json', + 'invalid_json', + ] + def initialize(app) @app = app end @@ -38,12 +45,7 @@ def handle_404(env) case File.basename(env['PATH_INFO']) when /properties/ property_json = JSON.parse(IO.read 'spec/fixtures/audit/property.json') - result = [ - 'success', - 'connection_timeout', - 'wrong_json', - 'invalid_json', - ].collect {|k| property_json.merge('identifier' => k, 'title' => "#{property_json['title']} (#{k})") } + result = SCENARIOS.collect {|k| property_json.merge('identifier' => k, 'title' => "#{property_json['title']} (#{k})") } new_body = Hash(result: result).to_json [200, {}, [new_body]] @@ -74,10 +76,7 @@ def replace_response_body(body) body.join("") end - body_string.gsub("REPLACEME", [ - "success", - "connection_timeout" - ].sample) + body_string.gsub("REPLACEME", SCENARIOS.sample) end end end diff --git a/spec/lib/concierge/suppliers/audit/server_spec.rb b/spec/lib/concierge/suppliers/audit/server_spec.rb index 11e70d3d4..fe1eff74f 100644 --- a/spec/lib/concierge/suppliers/audit/server_spec.rb +++ b/spec/lib/concierge/suppliers/audit/server_spec.rb @@ -46,6 +46,7 @@ file_json = JSON.parse(IO.read file) resp_json = JSON.parse(response_body_as_string(response)) expect(resp_json).not_to eq(file_json) + expect(Audit::Server::SCENARIOS).to include(resp_json['result']['reference_number']) expect(result_without_key resp_json, 'reference_number').to eq(result_without_key file_json, 'reference_number') end From ad39d2a7a4ee81a04b9526571b8d8bd39e2024ad Mon Sep 17 00:00:00 2001 From: choonkeat Date: Tue, 26 Jul 2016 14:36:51 +0800 Subject: [PATCH 21/22] Prettify output --- lib/concierge/suppliers/audit/server.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/concierge/suppliers/audit/server.rb b/lib/concierge/suppliers/audit/server.rb index be5527e2b..b4b47f833 100644 --- a/lib/concierge/suppliers/audit/server.rb +++ b/lib/concierge/suppliers/audit/server.rb @@ -46,7 +46,7 @@ def handle_404(env) when /properties/ property_json = JSON.parse(IO.read 'spec/fixtures/audit/property.json') result = SCENARIOS.collect {|k| property_json.merge('identifier' => k, 'title' => "#{property_json['title']} (#{k})") } - new_body = Hash(result: result).to_json + new_body = JSON.pretty_generate(result: result) [200, {}, [new_body]] when /sample/ From f142eed6b6e9d3f45892ff0a6742175cd56a4220 Mon Sep 17 00:00:00 2001 From: choonkeat Date: Fri, 12 Aug 2016 00:10:55 +0800 Subject: [PATCH 22/22] Update CalendarSynchronisation usage based on bbaa974 Update PropertySynchronisation usage based on e1a33ddb --- spec/workers/suppliers/audit_spec.rb | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/spec/workers/suppliers/audit_spec.rb b/spec/workers/suppliers/audit_spec.rb index fdd14a64c..02ea3ff5f 100644 --- a/spec/workers/suppliers/audit_spec.rb +++ b/spec/workers/suppliers/audit_spec.rb @@ -21,7 +21,7 @@ def keyvalue(counters) before do # do NOT make API calls during tests - allow_any_instance_of(Workers::PropertySynchronisation).to receive(:run_operation).and_return(nil) + allow_any_instance_of(Workers::PropertySynchronisation).to receive(:run_operation).and_return(double(:result, :"success?" => true)) allow_any_instance_of(Workers::CalendarSynchronisation).to receive(:run_operation).and_return(nil) # keep track of counters @@ -48,7 +48,15 @@ def keyvalue(counters) context "fetched new property" do it { is_expected.to change { keyvalue(@property_counters) }.to eq(created: 1, updated: 0, deleted: 0) } - it { is_expected.to change { keyvalue(@calendar_counters) }.to eq(available: 3, unavailable: 0) } + it { is_expected.not_to change { keyvalue(@calendar_counters) } } + + context "property was synchronised by Concierge" do + before do + allow_any_instance_of(Workers::CalendarSynchronisation).to receive(:synchronised?).and_return(true) + end + + it { is_expected.to change { keyvalue(@calendar_counters) }.to eq(available: 3, unavailable: 0) } + end end context "fetched existing property" do