Skip to content

Commit

Permalink
feat(publish test results): enable test results to be published to th…
Browse files Browse the repository at this point in the history
…e pact broker in the verification results
  • Loading branch information
bethesque committed Nov 12, 2017
1 parent 9c93c5c commit e0dad27
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 38 deletions.
9 changes: 5 additions & 4 deletions lib/pact/provider/pact_spec_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require 'pact/provider/pact_source'
require 'pact/provider/help/write'
require 'pact/provider/verification_results/publish_all'
require 'pact/provider/rspec/pact_broker_formatter'

require_relative 'rspec'

Expand Down Expand Up @@ -61,6 +62,8 @@ def configure_rspec
config.output_stream = Pact.configuration.output_stream
end

::RSpec.configuration.add_formatter Pact::Provider::RSpec::PactBrokerFormatter, StringIO.new

if options[:format]
::RSpec.configuration.add_formatter options[:format]
# Don't want to mess up the JSON parsing with messages to stdout, so send it to stderr
Expand All @@ -82,14 +85,12 @@ def configure_rspec
Pact.configuration.provider.app
end

# For the Pact::Provider::RSpec::PactBrokerFormatter
Pact.provider_world.pact_sources = pact_sources
jsons = pact_jsons
sources = pact_sources

config.after(:suite) do | suite |
Pact::Provider::Help::Write.call(jsons)
Pact::RSpec.with_rspec_3 do
Pact::Provider::VerificationResults::PublishAll.call(sources, ::RSpec.configuration.reporter.failed_examples)
end
end

end
Expand Down
9 changes: 7 additions & 2 deletions lib/pact/provider/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def honour_pactfile pact_uri, pact_json, options
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
honour_consumer_contract consumer_contract, options.merge(pact_json: pact_json)
honour_consumer_contract consumer_contract, options.merge(pact_json: pact_json, pact_uri: pact_uri)
end
end

Expand Down Expand Up @@ -61,11 +61,16 @@ def describe_interaction_with_provider_state interaction, options

def describe_interaction interaction, options

# pact_uri, pact_provider_state and pact_description are used by
# Pact::Provider::RSpec::PactBrokerFormatter
metadata = {
pact: :verify,
pact_interaction: interaction,
pact_interaction_example_description: interaction_description_for_rerun_command(interaction),
pact_json: options[:pact_json]
pact_json: options[:pact_json],
pact_uri: options[:pact_uri],
pact_provider_state: interaction.provider_state,
pact_description: interaction.description
}

describe description_for(interaction), metadata do
Expand Down
66 changes: 66 additions & 0 deletions lib/pact/provider/rspec/pact_broker_formatter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require 'rspec/core/formatters'
require 'pact/provider/verification_results/publish_all'

module Pact
module Provider
module RSpec
class PactBrokerFormatter < ::RSpec::Core::Formatters::BaseFormatter
Pact::RSpec.with_rspec_3 do
::RSpec::Core::Formatters.register self, :message, :dump_summary, :stop, :seed, :close
end

attr_reader :output_hash

def initialize(output)
super
@output_hash = {
:version => ::RSpec::Core::Version::STRING
}
end

def message(notification)
(@output_hash[:messages] ||= []) << notification.message
end

def dump_summary(summary)
end

def stop(notification)
@output_hash[:examples] = notification.examples.map do |example|
format_example(example).tap do |hash|
e = example.exception
if e
hash[:exception] = {
class: e.class.name,
message: e.message
}
end
end
end
end

def seed(notification)
return unless notification.seed_used?
@output_hash[:seed] = notification.seed
end

def close(_notification)
Pact::Provider::VerificationResults::PublishAll.call(Pact.provider_world.pact_sources, output_hash)
end

private

def format_example(example)
{
exampleDescription: example.description,
fullDescription: example.full_description,
status: example.execution_result.status.to_s,
interactionProviderState: example.metadata[:pact_provider_state],
interactionDescription: example.metadata[:pact_description],
pact_uri: example.metadata[:pact_uri]
}
end
end
end
end
end
46 changes: 31 additions & 15 deletions lib/pact/provider/verification_results/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,54 @@ module Provider
module VerificationResults
class Create

def self.call pact_json, failed_examples
new(pact_json, failed_examples).call
def self.call pact_source, test_results_hash
new(pact_source, test_results_hash).call
end

def initialize pact_json, failed_examples
@pact_json = pact_json
@failed_examples = failed_examples
def initialize pact_source, test_results_hash
@pact_source = pact_source
@test_results_hash = test_results_hash
end

def call
VerificationResult.new(!any_failures?, Pact.configuration.provider.application_version)
VerificationResult.new(!any_failures?, Pact.configuration.provider.application_version, test_results_hash_for_pact_uri)
end

private

def pact_hash
@pact_hash ||= json_load(pact_json)
def pact_uri
@pact_uri ||= pact_source.uri
end

def json_load json
JSON.load(json, nil, { max_nesting: 50 })
def any_failures?
count_failures_for_pact_uri > 0
end

def count_failures_for_pact_json
failed_examples.collect{ |e| e.metadata[:pact_json] == pact_json }.uniq.size
def examples_for_pact_uri
@examples_for_pact_uri ||= test_results_hash[:examples]
.select{ |e| e[:pact_uri] == pact_uri }
.collect{ |e| clean_example(e) }
end

def any_failures?
count_failures_for_pact_json > 0
def count_failures_for_pact_uri
examples_for_pact_uri.count{ |e| e[:status] != 'passed' }
end

def test_results_hash_for_pact_uri
{
examples: examples_for_pact_uri,
summary: {
exampleCount: examples_for_pact_uri.size,
failureCount: count_failures_for_pact_uri
}
}
end

def clean_example(example)
example.reject{ |k, v| k == :pact_uri }
end

attr_reader :pact_json, :failed_examples
attr_reader :pact_source, :test_results_hash
end
end
end
Expand Down
13 changes: 9 additions & 4 deletions lib/pact/provider/verification_results/publish.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,25 @@ def tag_versions

def publish
uri = URI(publication_url)
request = build_request('Post', uri, verification_result.to_json, "Publishing verification result #{verification_result.to_json} to")
request = build_request('Post', uri, verification_result.to_json, "Publishing verification result #{verification_result} to")
response = nil
begin
options = {:use_ssl => uri.scheme == 'https'}
response = Net::HTTP.start(uri.host, uri.port, options) do |http|
http.request request
end
rescue StandardError => e
error_message = "Failed to publish verification result due to: #{e.class} #{e.message}"
error_message = "Failed to publish verification results due to: #{e.class} #{e.message}"
raise PublicationError.new(error_message)
end

unless response.code.start_with?("2")
raise PublicationError.new("Error returned from verification result publication #{response.code} #{response.body}")


if response.code.start_with?("2")
new_resource_url = JSON.parse(response.body)['_links']['self']['href']
$stdout.puts "INFO: Verification results published to #{new_resource_url}"
else
raise PublicationError.new("Error returned from verification results publication #{response.code} #{response.body}")
end
end

Expand Down
12 changes: 6 additions & 6 deletions lib/pact/provider/verification_results/publish_all.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ module Provider
module VerificationResults
class PublishAll

def self.call pact_sources, rspec_summary
new(pact_sources, rspec_summary).call
def self.call pact_sources, test_results_hash
new(pact_sources, test_results_hash).call
end

def initialize pact_sources, rspec_summary
def initialize pact_sources, test_results_hash
@pact_sources = pact_sources
@rspec_summary = rspec_summary
@test_results_hash = test_results_hash
end

# TODO do not publish unless all interactions have been run
Expand All @@ -26,11 +26,11 @@ def call

def verification_results
pact_sources.collect do | pact_source |
[pact_source, Create.call(pact_source.pact_json, rspec_summary)]
[pact_source, Create.call(pact_source, test_results_hash)]
end
end

attr_reader :pact_sources, :rspec_summary
attr_reader :pact_sources, :test_results_hash
end
end
end
Expand Down
14 changes: 11 additions & 3 deletions lib/pact/provider/verification_results/verification_result.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
require 'json'

module Pact
module Provider
module VerificationResults
class VerificationResult

def initialize success, provider_application_version
def initialize success, provider_application_version, test_results_hash
@success = success
@provider_application_version = provider_application_version
@test_results_hash = test_results_hash
end

def provider_application_version_set?
Expand All @@ -15,13 +18,18 @@ def provider_application_version_set?
def to_json
{
success: success,
providerApplicationVersion: provider_application_version
providerApplicationVersion: provider_application_version,
testResults: test_results_hash
}.to_json
end

def to_s
"[success: #{success}, providerApplicationVersion: #{provider_application_version}]"
end

private

attr_reader :success, :provider_application_version
attr_reader :success, :provider_application_version, :test_results_hash
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/pact/provider/world.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ def self.clear_provider_world
module Provider
class World

attr_accessor :pact_sources

def provider_states
@provider_states_proxy ||= Pact::Provider::State::ProviderStateProxy.new
end
Expand All @@ -29,7 +31,6 @@ def pact_verifications
def pact_urls
pact_verifications.collect(&:uri)
end

end
end
end
68 changes: 68 additions & 0 deletions spec/integration/publish_verification_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
require 'pact/provider/verification_results/publish_all'
require 'pact/provider/pact_uri'

describe "publishing verifications" do

before do
allow(Pact.configuration).to receive(:provider).and_return(provider_configuration)
allow($stdout).to receive(:puts)
end

let(:provider_configuration) do
double('provider_configuration',
application_version: '1.2.3',
publish_verification_results?: true,
tags: [])
end

let(:pact_sources) do
[instance_double('Pact::Provider::PactSource', pact_hash: pact_hash, uri: pact_uri)]
end

let(:pact_uri) do
instance_double('Pact::Provider::PactURI', uri: 'pact.json', basic_auth?: false)
end

let(:pact_hash) do
{
'_links' => {
'pb:publish-verification-results' => {
'href' => 'http://publish/'
}
}
}
end

let(:created_verification_body) do
{
'_links' => {
'self' => {
'href' => 'http://created'
}
}
}.to_json
end

let(:test_results_hash) do
{
examples: [
{
exampleDescription: '1',
status: 'passed',
pact_uri: pact_uri
}
]
}
end

subject { Pact::Provider::VerificationResults::PublishAll.call(pact_sources, test_results_hash) }

let!(:request) do
stub_request(:post, 'http://publish').to_return(status: 200, body: created_verification_body)
end

it "publishes the results" do
subject
expect(request).to have_been_made
end
end
Loading

0 comments on commit e0dad27

Please sign in to comment.