diff --git a/lib/pact/hal/entity.rb b/lib/pact/hal/entity.rb new file mode 100644 index 00000000..56c30d78 --- /dev/null +++ b/lib/pact/hal/entity.rb @@ -0,0 +1,76 @@ +require 'uri' +require 'delegate' +require 'pact/hal/link' + +module Pact + module Hal + class Entity + def initialize(data, http_client, response = nil) + @data = data + @links = @data.fetch("_links", {}) + @client = http_client + @response = response + end + + def get(key, *args) + _link(key).get(*args) + end + + def post(key, *args) + _link(key).post(*args) + end + + def put(key, *args) + _link(key).put(*args) + end + + def can?(key) + @links.key? key.to_s + end + + def follow(key, http_method, *args) + Link.new(@links[key].merge(method: http_method), @client).run(*args) + end + + def _link(key) + if @links[key] + Link.new(@links[key], @client) + else + nil + end + end + + def success? + true + end + + def response + @response + end + + def fetch(key) + @links[key] + end + + def method_missing(method_name, *args, &block) + if @data.key?(method_name.to_s) + @data[method_name.to_s] + elsif @links.key?(method_name) + Link.new(@links[method_name], @client).run(*args) + else + super + end + end + + def respond_to_missing?(method_name, include_private = false) + @data.key?(method_name) || @links.key?(method_name) + end + end + + class ErrorEntity < Entity + def success? + false + end + end + end +end diff --git a/lib/pact/hal/http_client.rb b/lib/pact/hal/http_client.rb new file mode 100644 index 00000000..30dbee85 --- /dev/null +++ b/lib/pact/hal/http_client.rb @@ -0,0 +1,64 @@ +require 'pact/retry' + +module Pact + module Hal + class HttpClient + attr_accessor :username, :password + + def initialize options + @username = options[:username] + @password = options[:password] + end + + def get href, params = {} + uri = URI(href) + perform_request(create_request(uri, 'Get'), uri) + end + + def put href, body = nil + uri = URI(href) + perform_request(create_request(uri, 'Put', body), uri) + end + + def post href, body = nil + uri = URI(href) + perform_request(create_request(uri, 'Post', body), uri) + end + + def create_request uri, http_method, body = nil + path = uri.path.size == 0 ? "/" : uri.path + request = Net::HTTP.const_get(http_method).new(path) + request['Content-Type'] = "application/json" + request['Accept'] = "application/hal+json" + request.body = body if body + request.basic_auth username, password if username + request + end + + def perform_request request, uri + options = {:use_ssl => uri.scheme == 'https'} + response = Retry.until_true do + Net::HTTP.start(uri.host, uri.port, :ENV, options) do |http| + http.request request + end + end + Response.new(response) + end + + class Response < SimpleDelegator + def body + bod = __getobj__().body + if bod && bod != '' + JSON.parse(bod) + else + nil + end + end + + def success? + __getobj__().code.start_with?("2") + end + end + end + end +end diff --git a/lib/pact/hal/link.rb b/lib/pact/hal/link.rb new file mode 100644 index 00000000..21f512c5 --- /dev/null +++ b/lib/pact/hal/link.rb @@ -0,0 +1,65 @@ +require 'uri' +require 'delegate' + +module Pact + module Hal + class Link + attr_reader :request_method, :href + + def initialize(attrs, http_client) + @attrs = attrs + @request_method = attrs.fetch(:method, :get).to_sym + @href = attrs.fetch('href') + @http_client = http_client + end + + def run(payload = nil) + response = case request_method + when :get + get(payload) + when :put + put(payload) + when :post + post(payload) + end + end + + def get(payload = {}) + wrap_response(@http_client.get(href, payload)) + end + + def put(payload = nil) + wrap_response(@http_client.put(href, payload ? JSON.dump(payload) : nil)) + end + + def post(payload = nil) + wrap_response(@http_client.post(href, payload ? JSON.dump(payload) : nil)) + end + + def expand(params) + expanded_url = expand_url(params, href) + new_attrs = @attrs.merge('href' => expanded_url) + Link.new(new_attrs, @http_client) + end + + private + + def wrap_response(http_response) + require 'pact/hal/entity' # avoid circular reference + if http_response.success? + Entity.new(http_response.body, @http_client, http_response) + else + ErrorEntity.new(http_response.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 + '}', value) + end + new_url + end + end + end +end diff --git a/lib/pact/pact_broker/fetch_pacts.rb b/lib/pact/pact_broker/fetch_pacts.rb new file mode 100644 index 00000000..2c7c8aaa --- /dev/null +++ b/lib/pact/pact_broker/fetch_pacts.rb @@ -0,0 +1,53 @@ +require 'pact/hal/entity' +require 'pact/hal/http_client' + +module Pact + module PactBroker + class FetchPacts + attr_reader :provider, :tags, :broker_base_url, :basic_auth_options, :http_client, :pact_entity + + LATEST_PROVIDER_TAG_RELATION = 'pb:latest-provider-pacts-with-tag'.freeze + LATEST_PROVIDER_RELATION = 'pb:latest-provider-pacts'.freeze + PACTS = 'pacts'.freeze + HREF = 'href'.freeze + + def initialize(provider, tags, broker_base_url, basic_auth_options) + @provider = provider + @tags = tags + + @http_client = Pact::Hal::HttpClient.new(basic_auth_options) + @response = @http_client.get(broker_base_url) + @pact_entity = Pact::Hal::Entity.new(@response.body, http_client) + end + + def self.call(provider, tags = nil, broker_base_url, basic_auth_options) + new(provider, tags, broker_base_url, basic_auth_options).call + end + + def call + pact_urls = [] + if tags + link = pact_entity._link(LATEST_PROVIDER_TAG_RELATION) + tags.each do |tag| + link_by_tag = link.expand(provider: provider, tag: tag).get + get_pact_urls(link_by_tag, pact_urls) + end + else + link = pact_entity._link(LATEST_PROVIDER_RELATION) + link_by_provider = link.expand(provider: provider).get + get_pact_urls(link_by_provider, pact_urls) + end + pact_urls + end + + private + + def get_pact_urls(link_by_provider, pact_urls) + pacts = link_by_provider.fetch(PACTS) + pacts.each do |pact| + pact_urls.push(pact[HREF]) + end + end + end + end +end \ No newline at end of file diff --git a/lib/pact/provider/verification_results/publish.rb b/lib/pact/provider/verification_results/publish.rb index e8502c04..935a49d0 100644 --- a/lib/pact/provider/verification_results/publish.rb +++ b/lib/pact/provider/verification_results/publish.rb @@ -1,6 +1,8 @@ require 'json' require 'pact/errors' require 'pact/retry' +require 'pact/hal/entity' +require 'pact/hal/http_client' # TODO move this to the pact broker client @@ -10,6 +12,11 @@ module VerificationResults class PublicationError < Pact::Error; end class Publish + + PUBLISH_RELATION = 'pb:publish-verification-results'.freeze + PROVIDER_RELATION = 'pb:provider'.freeze + VERSION_TAG_RELATION = 'pb:version-tag'.freeze + def self.call pact_source, verification_result new(pact_source, verification_result).call end @@ -17,6 +24,15 @@ def self.call pact_source, verification_result def initialize pact_source, verification_result @pact_source = pact_source @verification_result = verification_result + + http_client_options = {} + if pact_source.uri.basic_auth? + http_client_options[:username] = pact_source.uri.username + http_client_options[:password] = pact_source.uri.password + end + + @http_client = Pact::Hal::HttpClient.new(http_client_options) + @pact_entity = Pact::Hal::Entity.new(pact_source.pact_hash, http_client) end def call @@ -27,11 +43,12 @@ def call end private + attr_reader :pact_source, :verification_result, :pact_entity, :http_client def can_publish_verification_results? return false unless Pact.configuration.provider.publish_verification_results? - if publication_url.nil? + if !pact_entity.can?(PUBLISH_RELATION) Pact.configuration.error_stream.puts "WARN: Cannot publish verification for #{consumer_name} as there is no link named pb:publish-verification-results in the pact JSON. If you are using a pact broker, please upgrade to version 2.0.0 or later." return false end @@ -43,94 +60,54 @@ def can_publish_verification_results? true end - def publication_url - @publication_url ||= pact_source.pact_hash.fetch('_links', {}).fetch('pb:publish-verification-results', {})['href'] - end - - def tag_url tag - # This is so dodgey, need to use proper HAL - if publication_url - u = URI(publication_url) - if match = publication_url.match(%r{/provider/([^/]+)}) - provider_name = match[1] - base_url = "#{u.scheme}://#{u.host}:#{u.host == u.default_port ? '' : u.port}" - provider_application_version = Pact.configuration.provider.application_version - "#{base_url}/pacticipants/#{provider_name}/versions/#{provider_application_version}/tags/#{tag}" - end - end + def hacky_tag_url provider_entity + hacky_tag_url = provider_entity._link('self').href + "/versions/{version}/tags/{tag}" + Pact::Hal::Link.new('href' => hacky_tag_url) end def tag_versions_if_configured if Pact.configuration.provider.tags.any? - tag_versions if tag_url('') + if pact_entity.can?(PROVIDER_RELATION) + tag_versions + else + Pact.configuration.error_stream.puts "WARN: Could not tag provider version as the pb:provider link cannot be found" + end end end def tag_versions - Pact.configuration.provider.tags.each do | tag | - uri = URI(tag_url(tag)) - request = build_request('Put', uri, nil, "Tagging provider version at") - response = nil - begin - options = {:use_ssl => uri.scheme == 'https'} - Retry.until_true do - response = Net::HTTP.start(uri.host, uri.port, options) do |http| - http.request request - end - end - rescue StandardError => e - error_message = "Failed to tag provider version due to: #{e.class} #{e.message}" - raise PublicationError.new(error_message) - end + provider_entity = pact_entity.get(PROVIDER_RELATION) + tag_link = provider_entity._link(VERSION_TAG_RELATION) || hacky_tag_url(provider_entity) + provider_application_version = Pact.configuration.provider.application_version - unless response.code.start_with?("2") - raise PublicationError.new("Error returned from tagging request #{response.code} #{response.body}") + Pact.configuration.provider.tags.each do | tag | + tag_entity = tag_link.expand(version: provider_application_version, tag: tag).put + unless tag_entity.success? + raise PublicationError.new("Error returned from tagging request #{tag_entity.response.code} #{tag_entity.response.body}") end end end def publish_verification_results - uri = URI(publication_url) - request = build_request('Post', uri, verification_result.to_json, "Publishing verification result #{verification_result} to") - response = nil + verification_entity = nil begin - options = {:use_ssl => uri.scheme == 'https'} - Retry.until_true do - response = Net::HTTP.start(uri.host, uri.port, options) do |http| - http.request request - end - end + verification_entity = pact_entity.post(PUBLISH_RELATION, verification_result) rescue StandardError => e error_message = "Failed to publish verification results due to: #{e.class} #{e.message}" raise PublicationError.new(error_message) end - if response.code.start_with?("2") - new_resource_url = JSON.parse(response.body)['_links']['self']['href'] + if verification_entity.success? + new_resource_url = verification_entity._link('self').href Pact.configuration.output_stream.puts "INFO: Verification results published to #{new_resource_url}" else - raise PublicationError.new("Error returned from verification results publication #{response.code} #{response.body}") + raise PublicationError.new("Error returned from verification results publication #{verification_entity.response.code} #{verification_entity.response.body}") end end - def build_request meth, uri, body, action - request = Net::HTTP.const_get(meth).new(uri.path) - request['Content-Type'] = "application/json" - request.body = body if body - debug_uri = uri - if pact_source.uri.basic_auth? - request.basic_auth pact_source.uri.username, pact_source.uri.password - debug_uri = URI(uri.to_s).tap { |x| x.userinfo="#{pact_source.uri.username}:*****"} - end - Pact.configuration.output_stream.puts "INFO: #{action} #{debug_uri}" - request - end - def consumer_name pact_source.pact_hash['consumer']['name'] end - - attr_reader :pact_source, :verification_result end end end diff --git a/lib/pact/provider/verification_results/verification_result.rb b/lib/pact/provider/verification_results/verification_result.rb index dcd69cea..617befca 100644 --- a/lib/pact/provider/verification_results/verification_result.rb +++ b/lib/pact/provider/verification_results/verification_result.rb @@ -20,12 +20,12 @@ def provider_application_version_set? !!provider_application_version end - def to_json + def to_json(options = {}) { success: success, providerApplicationVersion: provider_application_version, #testResults: test_results_hash # not yet - }.to_json + }.to_json(options) end def to_s diff --git a/spec/lib/pact/hal/entity_spec.rb b/spec/lib/pact/hal/entity_spec.rb new file mode 100644 index 00000000..b8e6f775 --- /dev/null +++ b/spec/lib/pact/hal/entity_spec.rb @@ -0,0 +1,93 @@ +require 'pact/hal/entity' +require 'pact/hal/http_client' + +module Pact + module Hal + describe Entity do + let(:http_client) do + instance_double('Pact::Hal::HttpClient', post: provider_response) + end + + let(:provider_response) do + double('response', body: provider_hash, success?: true) + end + + let(:provider_hash) do + { + 'name' => 'Provider' + } + end + let(:pact_hash) do + { + 'name' => 'a name', + + '_links' => { + 'pb:provider' => { + 'href' => 'http://provider' + }, + 'pb:version-tag' => { + 'href' => 'http://provider/version/{version}/tag/{tag}' + } + } + } + end + + subject(:entity) { Entity.new(pact_hash, http_client) } + + it 'delegates to the properties in the data' do + expect(subject.name).to eq 'a name' + end + + describe 'post' do + let(:post_provider) { entity.post('pb:provider', {'some' => 'data'} ) } + + it 'executes an http request' do + expect(http_client).to receive(:post).with('http://provider', '{"some":"data"}') + post_provider + end + + it 'returns the entity for the relation' do + expect(post_provider).to be_a(Entity) + end + + context 'with template params' do + let(:post_provider) { entity._link('pb:version-tag').expand(version: '1', tag: 'prod').post({'some' => 'data'} ) } + + it 'posts to the expanded URL' do + expect(http_client).to receive(:post).with('http://provider/version/1/tag/prod', '{"some":"data"}') + post_provider + end + end + end + + describe 'can?' do + context 'when the relation exists' do + it 'returns true' do + expect(subject.can?('pb:provider')).to be true + end + end + + context 'when the relation does not exist' do + it 'returns false' do + expect(subject.can?('pb:consumer')).to be false + end + end + end + + describe 'fetch' do + context 'when the key exist' do + it 'returns fetched value' do + expect(subject.fetch('pb:provider')).to be do + {href: 'http://provider'} + end + 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 + end + end + end +end diff --git a/spec/lib/pact/hal/http_client_spec.rb b/spec/lib/pact/hal/http_client_spec.rb new file mode 100644 index 00000000..674dbc58 --- /dev/null +++ b/spec/lib/pact/hal/http_client_spec.rb @@ -0,0 +1,75 @@ +require 'pact/hal/http_client' + +module Pact + module Hal + describe HttpClient do + + before do + allow(Retry).to receive(:until_true) { |&block| block.call } + end + + subject { HttpClient.new(username: 'foo', password: 'bar' ) } + + describe "get" do + let!(:request) do + stub_request(:get, "http://example.org/"). + with( headers: { + 'Accept'=>'application/hal+json', + 'Authorization'=>'Basic Zm9vOmJhcg==', + 'Content-Type'=>'application/json' + }). + to_return(status: 200, body: response_body, headers: {'Content-Type' => 'application/json'}) + end + + let(:response_body) { {some: 'json'}.to_json } + let(:do_get) { subject.get('http://example.org') } + + it "performs a get request" do + do_get + expect(request).to have_been_made + end + + it "retries on failure" do + expect(Retry).to receive(:until_true) + do_get + end + + it "returns a response" do + expect(do_get.body).to eq({"some" => "json"}) + end + end + + describe "post" do + let!(:request) do + stub_request(:post, "http://example.org/"). + with( headers: { + 'Accept'=>'application/hal+json', + 'Authorization'=>'Basic Zm9vOmJhcg==', + 'Content-Type'=>'application/json' + }, + body: request_body). + to_return(status: 200, body: response_body, headers: {'Content-Type' => 'application/json'}) + end + + let(:request_body) { {some: 'data'}.to_json } + let(:response_body) { {some: 'json'}.to_json } + + let(:do_post) { subject.post('http://example.org/', request_body) } + + it "performs a post request" do + do_post + expect(request).to have_been_made + end + + it "calls Retry.until_true" do + expect(Retry).to receive(:until_true) + do_post + end + + it "returns a response" do + expect(do_post.body).to eq({"some" => "json"}) + end + end + end + end +end diff --git a/spec/lib/pact/hal/link_spec.rb b/spec/lib/pact/hal/link_spec.rb new file mode 100644 index 00000000..5b783284 --- /dev/null +++ b/spec/lib/pact/hal/link_spec.rb @@ -0,0 +1,88 @@ +require 'pact/hal/link' +require 'pact/hal/entity' +require 'pact/hal/http_client' + +module Pact + module Hal + describe Link do + let(:http_client) do + instance_double('Pact::Hal::HttpClient', post: response) + end + + let(:response) do + instance_double('Pact::Hal::HttpClient::Response', success?: success, body: response_body) + end + + let(:success) { true } + + let(:entity) do + instance_double('Pact::Hal::Entity') + end + + let(:attrs) do + { + 'href' => 'http://foo/{bar}', + 'title' => 'title', + method: :post + } + end + + let(:response_body) do + { + 'some' => 'body' + } + end + + subject { Link.new(attrs, http_client) } + + describe "#run" do + before do + allow(Pact::Hal::Entity).to receive(:new).and_return(entity) + end + + let(:do_run) { subject.run('foo' => 'bar') } + + it "executes the configured http request" do + expect(http_client).to receive(:post) + do_run + end + + it "creates an Entity" do + expect(Pact::Hal::Entity).to receive(:new).with(response_body, http_client, response) + do_run + end + + it "returns an Entity" do + expect(do_run).to eq entity + end + + context "when an error response is returned" do + before do + allow(Pact::Hal::ErrorEntity).to receive(:new).and_return(entity) + end + + let(:success) { false } + + it "creates an ErrorEntity" do + expect(Pact::Hal::ErrorEntity).to receive(:new).with(response_body, http_client, response) + do_run + end + end + end + + describe "#get" do + + end + + describe "#put" do + + end + + describe "#expand" do + it "returns a duplicate Link with the expanded href" do + expect(subject.expand(bar: 'wiffle').href).to eq "http://foo/wiffle" + end + end + end + end +end diff --git a/spec/lib/pact/pact_broker/fetch_pacts_spec.rb b/spec/lib/pact/pact_broker/fetch_pacts_spec.rb new file mode 100644 index 00000000..3853b37d --- /dev/null +++ b/spec/lib/pact/pact_broker/fetch_pacts_spec.rb @@ -0,0 +1,219 @@ +require 'pact/pact_broker/fetch_pacts' + +module Pact + module PactBroker + describe('call') do + let(:provider) {'provider-name'} + let(:broker_base_url) {'https://pact.broker.com.au/'} + let(:basic_auth_options) {{username: 'foo', password: 'bar'}} + let!(:broker_request) do + stub_request(:get, broker_base_url) + .with(headers: { + Accept: 'application/hal+json', + Authorization: 'Basic Zm9vOmJhcg==', + 'Content-Type' => 'application/json' + }) + .to_return(status: 200, body: broker_response, + headers: {'Content-Type' => 'application/json'}) + end + + let(:broker_response) do + { + '_links' => { + 'self' => { + 'href' => broker_base_url + }, + 'pb:latest-provider-pacts' => { + 'href' => 'https://pact.broker.com.au/pacts/provider/{provider}/latest', + 'title' => 'Latest pacts by provider' + }, + 'pb:latest-provider-pacts-with-tag' => { + 'href' => 'https://pact.broker.com.au/pacts/provider/{provider}/latest/{tag}', + 'title' => 'Latest pacts by provider with the specified tag' + } + } + }.to_json + end + + context 'when tags are provided' do + let(:tags) {%w[tag-1 tag-2]} + + context 'when pacts are available' do + let(:pact_entities_for_tag_1) do + { + '_links' => { + 'pacts' => [ + { + 'href' => 'pact-brker-url-for-consumer-1-tag-1', + 'title' => 'Pact between consumer-1-name (v1.0.625) and provider-name', + 'name' => 'consumer-1-name' + }, + { + 'href' => 'pact-brker-url-for-consumer-2-tag-1', + 'title' => 'Pact between consumer-2-name (v1.0.1138) and provider-name', + 'name' => 'consumer-2-name' + }] + } + }.to_json + end + + let(:pact_entities_for_tag_2) do + { + '_links' => { + 'pacts' => [ + { + 'href' => 'pact-brker-url-for-consumer-1-tag-2', + 'title' => 'Pact between consumer-1-name (v1.0.625) and provider-name', + 'name' => 'consumer-1-name' + }, + { + 'href' => 'pact-brker-url-for-consumer-2-tag-2', + 'title' => 'Pact between consumer-2-name (v1.0.1138) and provider-name', + 'name' => 'consumer-2-name' + }] + } + }.to_json + end + + let!(:provider_tag_request) do + stub_request(:get, 'https://pact.broker.com.au/pacts/provider/provider-name/latest/tag-1') + .with(headers: { + 'Accept' => 'application/hal+json', + 'Authorization' => 'Basic Zm9vOmJhcg==', + 'Content-Type' => 'application/json' + }) + .to_return(status: 200, body: pact_entities_for_tag_1, headers: {'Content-Type' => 'application/json'}) + + stub_request(:get, 'https://pact.broker.com.au/pacts/provider/provider-name/latest/tag-2') + .with(headers: { + 'Accept' => 'application/hal+json', + 'Authorization' => 'Basic Zm9vOmJhcg==', + 'Content-Type' => 'application/json' + }) + .to_return(status: 200, body: pact_entities_for_tag_2, headers: {'Content-Type' => 'application/json'}) + end + + subject do + FetchPacts.call(provider, tags, broker_base_url, basic_auth_options) + end + + it 'should make a get request to broker base url' do + subject + expect(WebMock).to have_requested(:get, broker_base_url) + end + + it 'should make a get request to provider tag-1 url' do + subject + expect(WebMock).to have_requested(:get, 'https://pact.broker.com.au/pacts/provider/provider-name/latest/tag-1') + end + + it 'should make a get request to provider tag-2 url' do + subject + expect(WebMock).to have_requested(:get, 'https://pact.broker.com.au/pacts/provider/provider-name/latest/tag-2') + end + + it "should return arrays of pact urls based on provider name and tag's latest version" do + @result = subject + expect(@result).to be_a Array + expect(@result).to eq(%w(pact-brker-url-for-consumer-1-tag-1 pact-brker-url-for-consumer-2-tag-1 pact-brker-url-for-consumer-1-tag-2 pact-brker-url-for-consumer-2-tag-2)) + end + end + context 'when pacts are not available' do + let(:pact_entities_for_tag_1) do + { + '_links' => { + 'pacts' => [] + } + }.to_json + end + + let(:pact_entities_for_tag_2) do + { + '_links' => { + 'pacts' => [] + } + }.to_json + end + + let!(:provider_tag_request) do + stub_request(:get, 'https://pact.broker.com.au/pacts/provider/provider-name/latest/tag-1') + .with(headers: { + 'Accept' => 'application/hal+json', + 'Authorization' => 'Basic Zm9vOmJhcg==', + 'Content-Type' => 'application/json' + }) + .to_return(status: 200, body: pact_entities_for_tag_1, headers: {'Content-Type' => 'application/json'}) + + stub_request(:get, 'https://pact.broker.com.au/pacts/provider/provider-name/latest/tag-2') + .with(headers: { + 'Accept' => 'application/hal+json', + 'Authorization' => 'Basic Zm9vOmJhcg==', + 'Content-Type' => 'application/json' + }) + .to_return(status: 200, body: pact_entities_for_tag_2, headers: {'Content-Type' => 'application/json'}) + end + + subject do + FetchPacts.call(provider, tags, broker_base_url, basic_auth_options) + end + + it 'should return empty array' do + @result = subject + expect(@result).to be_a Array + expect(@result).to eq([]) + end + end + end + + context 'when tag is not provided' do + let(:pact_entities_for_provider_name) do + { + '_links' => { + 'pacts' => [ + { + 'href' => 'pact-brker-url-for-consumer-1', + 'title' => 'Pact between consumer-1-name (v1.0.625) and provider-name', + 'name' => 'consumer-1-name' + }, + { + 'href' => 'pact-brker-url-for-consumer-2', + 'title' => 'Pact between consumer-2-name (v1.0.1138) and provider-name', + 'name' => 'consumer-2-name' + }] + } + }.to_json + end + + let!(:provider_request) do + stub_request(:get, 'https://pact.broker.com.au/pacts/provider/provider-name/latest') + .with(headers: { + 'Accept' => 'application/hal+json', + 'Authorization' => 'Basic Zm9vOmJhcg==', + 'Content-Type' => 'application/json' + }) + .to_return(status: 200, body: pact_entities_for_provider_name, headers: {'Content-Type' => 'application/json'}) + end + + subject do + FetchPacts.call(provider, nil, broker_base_url, basic_auth_options) + end + + it 'should make a get request to broker base url' do + subject + expect(WebMock).to have_requested(:get, broker_base_url) + end + + it 'should make a get request to provider url' do + subject + expect(WebMock).to have_requested(:get, 'https://pact.broker.com.au/pacts/provider/provider-name/latest') + end + + it 'should return array of pact urls by provider' do + @result = subject + expect(@result).to be_a Array + expect(@result).to eq(%w(pact-brker-url-for-consumer-1 pact-brker-url-for-consumer-2)) + end + end + end + end +end \ No newline at end of file diff --git a/spec/lib/pact/provider/verification_results/publish_spec.rb b/spec/lib/pact/provider/verification_results/publish_spec.rb index ce5d26a5..b0e7f0f7 100644 --- a/spec/lib/pact/provider/verification_results/publish_spec.rb +++ b/spec/lib/pact/provider/verification_results/publish_spec.rb @@ -10,6 +10,7 @@ module VerificationResults let(:tag_version_url) { 'http://tag-me/{tag}' } let(:pact_source) { instance_double("Pact::Provider::PactSource", pact_hash: pact_hash, uri: pact_url)} let(:pact_url) { instance_double("Pact::Provider::PactURI", basic_auth?: basic_auth, username: 'username', password: 'password')} + let(:provider_url) { 'http://provider' } let(:basic_auth) { false } let(:pact_hash) do { @@ -19,6 +20,9 @@ module VerificationResults '_links' => { 'pb:publish-verification-results'=> { 'href' => publish_verification_url + }, + 'pb:provider' => { + 'href' => provider_url } } } @@ -32,6 +36,27 @@ module VerificationResults } }.to_json end + let(:provider_body) do + { + '_links' => { + 'self' => { + 'href' => provider_url + }, + 'pb:version-tag' => { + 'href' => 'http://provider/version/{version}/tag/{tag}' + } + } + }.to_json + end + let(:tag_body) do + { + '_links' => { + 'self' => { + 'href' => 'http://tag-url' + } + } + }.to_json + end let(:app_version_set) { false } let(:verification_json) { '{"foo": "bar"}' } let(:publish_verification_results) { false } @@ -51,9 +76,11 @@ module VerificationResults before do allow($stdout).to receive(:puts) + allow($stderr).to receive(:puts) allow(Pact.configuration).to receive(:provider).and_return(provider_configuration) stub_request(:post, stubbed_publish_verification_url).to_return(status: 200, body: created_verification_body) - stub_request(:put, 'http://broker/pacticipants/Bar/versions/1.2.3/tags/foo') + stub_request(:put, 'http://provider/version/1.2.3/tag/foo').to_return(status: 200, headers: { 'Content-Type' => 'application/hal+json'}, body: tag_body) + stub_request(:get, provider_url).to_return(status: 200, headers: { 'Content-Type' => 'application/hal+json'}, body: provider_body) allow(Retry).to receive(:until_true) { |&block| block.call } end @@ -77,11 +104,6 @@ module VerificationResults expect(WebMock).to have_requested(:post, publish_verification_url).with(body: verification_json, headers: {'Content-Type' => 'application/json'}) end - it "should call Retry.until_true once" do - subject - expect(Retry).to have_received(:until_true).once() - end - context "when the verification result is not publishable" do let(:publishable) { false } @@ -96,12 +118,7 @@ module VerificationResults it "tags the provider version" do subject - expect(WebMock).to have_requested(:put, 'http://broker/pacticipants/Bar/versions/1.2.3/tags/foo').with(headers: {'Content-Type' => 'application/json'}) - end - - it "should call Retry.until_true twice" do - subject - expect(Retry).to have_received(:until_true).twice() + expect(WebMock).to have_requested(:put, 'http://provider/version/1.2.3/tag/foo').with(headers: {'Content-Type' => 'application/json'}) end context "when there is no pb:publish-verification-results link" do @@ -138,7 +155,7 @@ module VerificationResults context "when an HTTP error is returned" do it "raises a PublicationError" do - stub_request(:post, stubbed_publish_verification_url).to_return(status: 500, body: 'some error') + stub_request(:post, stubbed_publish_verification_url).to_return(status: 500, body: '{}') expect{ subject }.to raise_error(PublicationError, /Error returned/) end end