diff --git a/lib/pact_broker/client/hal/entity.rb b/lib/pact_broker/client/hal/entity.rb index 5f9c3761..e78ec725 100644 --- a/lib/pact_broker/client/hal/entity.rb +++ b/lib/pact_broker/client/hal/entity.rb @@ -1,12 +1,18 @@ -require 'uri' +require 'erb' require 'delegate' require 'pact_broker/client/hal/link' module PactBroker module Client module Hal + class RelationNotFoundError < ::PactBroker::Client::Error; end + + class ErrorResponseReturned < ::PactBroker::Client::Error; end + class Entity - def initialize(data, http_client, response = nil) + + def initialize(href, data, http_client, response = nil) + @href = href @data = data @links = (@data || {}).fetch("_links", {}) @client = http_client @@ -33,14 +39,20 @@ def follow(key, http_method, *args) Link.new(@links[key].merge(method: http_method), @client).run(*args) end - def _link(key) + def _link(key, fallback_key = nil) if @links[key] Link.new(@links[key], @client) + elsif fallback_key && @links[fallback_key] + Link.new(@links[fallback_key], @client) else nil end end + def _link!(key) + _link(key) or raise RelationNotFoundError.new("Could not find relation '#{key}' in resource at #{@href}") + end + def success? true end @@ -49,8 +61,8 @@ def response @response end - def fetch(key) - @links[key] + def fetch(key, fallback_key = nil) + @links[key] || (fallback_key && @links[fallback_key]) end def method_missing(method_name, *args, &block) @@ -66,12 +78,29 @@ def method_missing(method_name, *args, &block) def respond_to_missing?(method_name, include_private = false) @data.key?(method_name) || @links.key?(method_name) end + + def assert_success! + self + end end class ErrorEntity < Entity + + def initialize(href, data, http_client, response = nil) + @href = href + @data = data + @links = {} + @client = http_client + @response = response + end + def success? false end + + def assert_success! + raise ErrorResponseReturned.new("Error retrieving #{@href} status=#{response ? response.code: nil} #{response ? response.raw_body : ''}") + end end end end diff --git a/lib/pact_broker/client/hal/http_client.rb b/lib/pact_broker/client/hal/http_client.rb index 0b78723b..c2dd4f30 100644 --- a/lib/pact_broker/client/hal/http_client.rb +++ b/lib/pact_broker/client/hal/http_client.rb @@ -4,7 +4,7 @@ module PactBroker module Client module Hal class HttpClient - attr_reader :username, :password, :verbose + attr_accessor :username, :password, :verbose def initialize options @username = options[:username] @@ -56,7 +56,7 @@ def perform_request request, uri class Response < SimpleDelegator def body - bod = __getobj__().body + bod = raw_body if bod && bod != '' JSON.parse(bod) else @@ -77,6 +77,7 @@ def success? end end end + end end end diff --git a/lib/pact_broker/client/hal/link.rb b/lib/pact_broker/client/hal/link.rb index 452a9dd5..29dc2322 100644 --- a/lib/pact_broker/client/hal/link.rb +++ b/lib/pact_broker/client/hal/link.rb @@ -38,15 +38,15 @@ def name end def get(payload = {}, headers = {}) - wrap_response(@http_client.get(href, payload, headers)) + wrap_response(href, @http_client.get(href, payload, headers)) end def put(payload = nil, headers = {}) - wrap_response(@http_client.put(href, payload ? JSON.dump(payload) : nil, headers)) + wrap_response(href, @http_client.put(href, payload ? JSON.dump(payload) : nil, headers)) end def post(payload = nil, headers = {}) - wrap_response(@http_client.post(href, payload ? JSON.dump(payload) : nil, headers)) + wrap_response(href, @http_client.post(href, payload ? JSON.dump(payload) : nil, headers)) end def expand(params) @@ -57,21 +57,19 @@ def expand(params) private - def wrap_response(http_response) - require 'pact_broker/client/hal/entity' # avoid circular reference + def wrap_response(href, http_response) + require 'pact/hal/entity' # avoid circular reference if http_response.success? - Entity.new(http_response.body, @http_client, http_response) + Entity.new(href, http_response.body, @http_client, http_response) else - ErrorEntity.new(http_response.body, @http_client, http_response) + ErrorEntity.new(href, http_response.raw_body, @http_client, http_response) end end def expand_url(params, url) - new_url = url - params.each do | key, value | - new_url = new_url.gsub('{' + key.to_s + '}', URI.escape(value)) + params.inject(url) do | url, (key, value) | + url.gsub('{' + key.to_s + '}', ERB::Util.url_encode(value)) end - new_url end end end diff --git a/spec/lib/pact_broker/client/hal/entity_spec.rb b/spec/lib/pact_broker/client/hal/entity_spec.rb index 49873e81..935c5efb 100644 --- a/spec/lib/pact_broker/client/hal/entity_spec.rb +++ b/spec/lib/pact_broker/client/hal/entity_spec.rb @@ -32,7 +32,7 @@ module Hal } end - subject(:entity) { Entity.new(pact_hash, http_client) } + subject(:entity) { Entity.new("http://pact", pact_hash, http_client) } it "delegates to the properties in the data" do expect(subject.name).to eq "a name" @@ -60,6 +60,32 @@ module Hal end end + describe "assert_success!" do + context "when the response is successful" do + it "returns the entity" do + expect(entity.assert_success!).to be entity + end + end + + context "when the response is not successful and there is no response" do + subject(:entity) { ErrorEntity.new("http://pact", pact_hash, http_client) } + + it "raises an error" do + expect { entity.assert_success! }.to raise_error ErrorResponseReturned, "Error retrieving http://pact status= " + end + end + + context "when the response is not successful and there is a response" do + let(:response) { double('response', code: 200, raw_body: "body") } + + subject(:entity) { ErrorEntity.new("http://pact", pact_hash, http_client, response) } + + it "raises an error" do + expect { entity.assert_success! }.to raise_error ErrorResponseReturned, "Error retrieving http://pact status=200 body" + end + end + end + describe "can?" do context "when the relation exists" do it "returns true" do @@ -74,19 +100,47 @@ module Hal end end + describe "_link!" do + context 'when the key exists' do + it 'returns a Link' do + expect(subject._link!('pb:provider')).to be_a(Link) + expect(subject._link!('pb:provider').href).to eq 'http://provider' + end + end + + context 'when the key does not exist' do + it 'raises an error' do + expect { subject._link!('foo') }.to raise_error RelationNotFoundError, "Could not find relation 'foo' in resource at http://pact" + end + end + end + describe 'fetch' do - context 'when the key exist' do + context 'when the key exists' do it 'returns fetched value' do - expect(subject.fetch('pb:provider')).to be do - {href: 'http://provider'} - end + expect(subject.fetch('pb:provider')).to eq("href" => 'http://provider') end end + context "when the key doesn't not exist" do it 'returns nil' do expect(subject.fetch('i-dont-exist')).to be nil end end + + context "when a fallback key is provided" do + context "when the fallback value exists" do + it "returns the fallback value" do + expect(subject.fetch('i-dont-exist', 'pb:provider')).to eq("href" => 'http://provider') + end + end + + context "when the fallback value does not exist" do + it "returns nil" do + expect(subject.fetch('i-dont-exist', 'i-also-dont-exist')).to be nil + end + end + end end end end diff --git a/spec/lib/pact_broker/client/hal/link_spec.rb b/spec/lib/pact_broker/client/hal/link_spec.rb index e5ed7634..efe14591 100644 --- a/spec/lib/pact_broker/client/hal/link_spec.rb +++ b/spec/lib/pact_broker/client/hal/link_spec.rb @@ -10,7 +10,7 @@ module Hal end let(:response) do - instance_double('PactBroker::Client::Hal::HttpClient::Response', success?: success, body: response_body) + instance_double('PactBroker::Client::Hal::HttpClient::Response', success?: success, body: response_body, raw_body: response_body.to_json) end let(:success) { true } @@ -48,7 +48,7 @@ module Hal end it "creates an Entity" do - expect(PactBroker::Client::Hal::Entity).to receive(:new).with(response_body, http_client, response) + expect(PactBroker::Client::Hal::Entity).to receive(:new).with("http://foo/{bar}", response_body, http_client, response) do_run end @@ -64,7 +64,7 @@ module Hal let(:success) { false } it "creates an ErrorEntity" do - expect(PactBroker::Client::Hal::ErrorEntity).to receive(:new).with(response_body, http_client, response) + expect(PactBroker::Client::Hal::ErrorEntity).to receive(:new).with("http://foo/{bar}", response_body.to_json, http_client, response) do_run end end @@ -102,6 +102,10 @@ module Hal it "returns a duplicate Link with the expanded href with URL escaping" do expect(subject.expand(bar: 'wiffle meep').href).to eq "http://foo/wiffle%20meep" end + + it "returns a duplicate Link with the expanded href with URL escaping for forward slashes" do + expect(subject.expand(bar: 'wiffle/meep').href).to eq "http://foo/wiffle%2Fmeep" + end end end end