Skip to content

Commit

Permalink
feat: allow WIP pacts to be verified without causing the process to r…
Browse files Browse the repository at this point in the history
…eturn an non zero exit code
  • Loading branch information
bethesque committed Jul 23, 2018
1 parent 6519819 commit 9e6de46
Show file tree
Hide file tree
Showing 22 changed files with 335 additions and 54 deletions.
2 changes: 1 addition & 1 deletion lib/pact/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class CLI < Thor
desc 'verify', "Verify a pact"
method_option :pact_helper, aliases: "-h", desc: "Pact helper file", :required => true
method_option :pact_uri, aliases: "-p", desc: "Pact URI"
method_option :wip, type: :boolean, default: false, desc: "If WIP, process will always exit with exit code 0", hide: true
method_option :pact_broker_username, aliases: "-u", desc: "Pact broker user name"
method_option :pact_broker_password, aliases: "-w", desc: "Pact broker password"
method_option :backtrace, aliases: "-b", desc: "Show full backtrace", :default => false, :type => :boolean
Expand All @@ -33,6 +34,5 @@ def docs
require 'pact/doc/generator'
Pact::Doc::Generate.call(options[:pact_dir], options[:doc_dir], [Pact::Doc::Markdown::Generator])
end

end
end
3 changes: 2 additions & 1 deletion lib/pact/cli/run_pact_verification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def pact_spec_options
full_backtrace: options[:backtrace],
criteria: SpecCriteria.call(options),
format: options[:format],
out: options[:out]
out: options[:out],
wip: options[:wip]
}
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/pact/hal/entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,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)
Expand Down
3 changes: 2 additions & 1 deletion lib/pact/pact_broker/fetch_pacts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class FetchPacts
LATEST_PROVIDER_TAG_RELATION = 'pb:latest-provider-pacts-with-tag'.freeze
LATEST_PROVIDER_RELATION = 'pb:latest-provider-pacts'.freeze
PACTS = 'pacts'.freeze
PB_PACTS = 'pb:pacts'.freeze
HREF = 'href'.freeze

def initialize(provider, tags, broker_base_url, http_client_options)
Expand Down Expand Up @@ -79,7 +80,7 @@ def get_latest_pacts_for_provider
end

def get_pact_urls(link_by_provider)
link_by_provider.fetch(PACTS).collect do |pact|
link_by_provider.fetch(PB_PACTS, PACTS).collect do |pact|
Pact::Provider::PactURI.new(pact[HREF], http_client_options)
end
end
Expand Down
62 changes: 62 additions & 0 deletions lib/pact/pact_broker/fetch_wip_pacts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require 'pact/hal/entity'
require 'pact/hal/http_client'
require 'pact/provider/pact_uri'

module Pact
module PactBroker
class FetchWipPacts
attr_reader :provider, :tags, :broker_base_url, :http_client_options, :http_client, :index_entity

WIP_PROVIDER_RELATION = 'pb:wip-provider-pacts'.freeze
PACTS = 'pacts'.freeze
PB_PACTS = 'pb:pacts'.freeze
HREF = 'href'.freeze

def initialize(provider, broker_base_url, http_client_options)
@provider = provider
@http_client_options = http_client_options
@broker_base_url = broker_base_url
@http_client = Pact::Hal::HttpClient.new(http_client_options)
end

def self.call(provider, broker_base_url, http_client_options)
new(provider, broker_base_url, http_client_options).call
end

def call
log_message
if get_index.success?
get_wip_pacts_for_provider
else
raise Pact::Error.new("Error retrieving #{broker_base_url} status=#{index_entity.response.code} #{index_entity.response.raw_body}")
end
end

private

def get_index
@index_entity = Pact::Hal::Link.new({ "href" => broker_base_url }, http_client).get
end

def get_wip_pacts_for_provider
link = index_entity._link(WIP_PROVIDER_RELATION)
if link
get_pact_urls(link.expand(provider: provider).get)
else
[]
end
end

def get_pact_urls(link_by_provider)
link_by_provider.fetch(PB_PACTS, PACTS).collect do |pact|
Pact::Provider::PactURI.new(pact[HREF], http_client_options)
end
end

def log_message
message = "INFO: Fetching WIP pacts for #{provider} from #{broker_base_url}"
Pact.configuration.output_stream.puts message
end
end
end
end
8 changes: 6 additions & 2 deletions lib/pact/provider/pact_spec_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def run
ensure
::RSpec.reset
Pact.clear_provider_world
Pact.clear_consumer_world
end
end

Expand Down Expand Up @@ -90,7 +91,7 @@ def run_specs
::RSpec::Core::CommandLine.new(NoConfigurationOptions.new)
.run(::RSpec.configuration.output_stream, ::RSpec.configuration.error_stream)
end
exit_code
options[:wip] ? 0 : exit_code
end

def rspec_runner_options
Expand Down Expand Up @@ -118,7 +119,8 @@ def pact_jsons
def initialize_specs
pact_sources.each do | pact_source |
options = {
criteria: @options[:criteria]
criteria: @options[:criteria],
wip: @options[:wip]
}
honour_pactfile pact_source.uri, ordered_pact_json(pact_source.pact_json), options
end
Expand All @@ -144,6 +146,8 @@ def configure_output
end

::RSpec.configuration.full_backtrace = @options[:full_backtrace]

::RSpec.configuration.failure_color = :yellow if @options[:wip]
end

def ordered_pact_json(pact_json)
Expand Down
8 changes: 5 additions & 3 deletions lib/pact/provider/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ module ClassMethods
include ::RSpec::Core::DSL

def honour_pactfile pact_uri, pact_json, options
Pact.configuration.output_stream.puts "INFO: Reading pact at #{pact_uri}"
pact_description = options[:wip] ? "WIP pact" : "pact"
Pact.configuration.output_stream.puts "INFO: Reading #{pact_description} at #{pact_uri}"
Pact.configuration.output_stream.puts "INFO: Filtering interactions by: #{options[:criteria]}" if options[:criteria] && options[:criteria].any?
consumer_contract = Pact::ConsumerContract.from_json(pact_json)
::RSpec.describe "Verifying a pact between #{consumer_contract.consumer.name} and #{consumer_contract.provider.name}", pactfile_uri: pact_uri do
::RSpec.describe "Verifying a #{pact_description} between #{consumer_contract.consumer.name} and #{consumer_contract.provider.name}", pactfile_uri: pact_uri do
honour_consumer_contract consumer_contract, options.merge(pact_json: pact_json, pact_uri: pact_uri)
end
end
Expand Down Expand Up @@ -72,7 +73,8 @@ def describe_interaction interaction, options
pact: :verify,
pact_interaction: interaction,
pact_interaction_example_description: interaction_description_for_rerun_command(interaction),
pact_uri: options[:pact_uri]
pact_uri: options[:pact_uri],
pact_wip: options[:wip]
}

describe description_for(interaction), metadata do
Expand Down
20 changes: 18 additions & 2 deletions lib/pact/provider/rspec/formatter_rspec_3.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,21 @@ def failed_interactions_count(summary)
summary.failed_examples.collect{ |e| e.metadata[:pact_interaction_example_description] }.uniq.size
end

def wip?(summary)
summary.failed_examples.any?{ |e| e.metadata[:pact_wip] }
end

def failure_title summary
if wip?(summary)
"#{failed_interactions_count(summary)} pending"
else
::RSpec::Core::Formatters::Helpers.pluralize(failed_interactions_count(summary), "failure")
end
end

def totals_line summary
line = ::RSpec::Core::Formatters::Helpers.pluralize(interactions_count(summary), "interaction")
line << ", " << ::RSpec::Core::Formatters::Helpers.pluralize(failed_interactions_count(summary), "failure")
line << ", " << failure_title(summary)
line
end

Expand All @@ -57,7 +69,11 @@ def color_for_summary summary
end

def print_rerun_commands summary
output.puts("\nFailed interactions:\n\n")
if wip?(summary)
output.puts("\nPending interactions: (Failures listed here are expected and do not affect your suite's status)\n\n")
else
output.puts("\nFailed interactions:\n\n")
end
interaction_rerun_commands(summary).each do | message |
output.puts(message)
end
Expand Down
7 changes: 4 additions & 3 deletions lib/pact/tasks/task_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ module TaskHelper

extend self

def execute_pact_verify pact_uri = nil, pact_helper = nil, rspec_opts = nil
execute_cmd verify_command(pact_helper || Pact::Provider::PactHelperLocater.pact_helper_path, pact_uri, rspec_opts)
def execute_pact_verify pact_uri = nil, pact_helper = nil, rspec_opts = nil, verification_opts = {}
execute_cmd verify_command(pact_helper || Pact::Provider::PactHelperLocater.pact_helper_path, pact_uri, rspec_opts, verification_opts)
end

def handle_verification_failure
exit_status = yield
abort if exit_status != 0
end

def verify_command pact_helper, pact_uri, rspec_opts
def verify_command pact_helper, pact_uri, rspec_opts, verification_opts
command_parts = []
# Clear SPEC_OPTS, otherwise we can get extra formatters, creating duplicate output eg. CI Reporting.
# Allow deliberate configuration using rspec_opts in VerificationTask.
Expand All @@ -28,6 +28,7 @@ def verify_command pact_helper, pact_uri, rspec_opts
command_parts << "-S pact verify"
command_parts << "--pact-helper" << Shellwords.escape(pact_helper.end_with?(".rb") ? pact_helper : pact_helper + ".rb")
(command_parts << "--pact-uri" << pact_uri) if pact_uri
command_parts << "--wip" if verification_opts[:wip]
command_parts << "--pact-broker-username" << ENV['PACT_BROKER_USERNAME'] if ENV['PACT_BROKER_USERNAME']
command_parts << "--pact-broker-password" << ENV['PACT_BROKER_PASSWORD'] if ENV['PACT_BROKER_PASSWORD']
command_parts << "--backtrace" if ENV['BACKTRACE'] == 'true'
Expand Down
4 changes: 3 additions & 1 deletion lib/pact/tasks/verification_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ class VerificationTask < ::Rake::TaskLib

attr_reader :pact_spec_configs
attr_accessor :rspec_opts
attr_accessor :wip

def initialize(name)
@rspec_opts = nil
@wip = false
@pact_spec_configs = []
@name = name
yield self
Expand Down Expand Up @@ -74,7 +76,7 @@ def rake_task
require 'pact/tasks/task_helper'

exit_statuses = pact_spec_configs.collect do | config |
Pact::TaskHelper.execute_pact_verify config[:uri], config[:pact_helper], rspec_opts
Pact::TaskHelper.execute_pact_verify config[:uri], config[:pact_helper], rspec_opts, { wip: wip }
end

Pact::TaskHelper.handle_verification_failure do
Expand Down
21 changes: 17 additions & 4 deletions spec/lib/pact/hal/entity_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,31 @@ module Hal
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
Expand Down
9 changes: 5 additions & 4 deletions spec/lib/pact/pact_broker/fetch_pacts_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ module PactBroker
describe FetchPacts do

describe "call" do
before do
allow(Pact.configuration).to receive(:output_stream).and_return(double('output stream').as_null_object)
stub_request(:get, "http://broker.org/").to_return(status: 500, body: "foo", headers: {})
end

let(:provider) { "Foo"}
let(:tags) { ["master", "prod"] }
let(:broker_base_url) { "http://broker.org" }
let(:http_client_options) { {} }

before do
stub_request(:get, "http://broker.org/").to_return(status: 500, body: "foo", headers: {})
end

subject { FetchPacts.call(provider, tags, broker_base_url, http_client_options)}

let(:subject_with_rescue) do
Expand Down
54 changes: 54 additions & 0 deletions spec/lib/pact/pact_broker/fetch_wip_pacts_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'pact/pact_broker/fetch_wip_pacts'

module Pact
module PactBroker
describe FetchWipPacts do
describe "call" do
before do
allow(Pact.configuration).to receive(:output_stream).and_return(double('output stream').as_null_object)
end

let(:provider) { "Foo"}
let(:broker_base_url) { "http://broker.org" }
let(:http_client_options) { {} }
subject { FetchWipPacts.call(provider, broker_base_url, http_client_options)}

context "when there is an error retrieving the index resource" do
before do
stub_request(:get, "http://broker.org/").to_return(status: 500, body: "foo", headers: {})
end

let(:subject_with_rescue) do
begin
subject
rescue Pact::Error
# can't be bothered stubbing out everything to make the rest of the code execute nicely
# when all we care about is the message
end
end

it "raises a Pact::Error" do
expect { subject }.to raise_error Pact::Error, /500.*foo/
end
end

context "when the pb:wip-provider-pacts relation does not exist" do
before do
stub_request(:get, "http://broker.org/").to_return(status: 200, body: response_body, headers: response_headers)
end

let(:response_headers) { { "Content-Type" => "application/hal+json" } }
let(:response_body) do
{
_links: {}
}.to_json
end

it "returns an empty list" do
expect(subject).to eq []
end
end
end
end
end
end
Loading

0 comments on commit 9e6de46

Please sign in to comment.