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 module Pact module Provider 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, options = {} new(pact_source, verification_result, options).call end def initialize pact_source, verification_result, options = {} @pact_source = pact_source @verification_result = verification_result http_client_options = pact_source.uri.options.reject{ |k, v| ![:username, :password, :token].include?(k) } @http_client = Pact::Hal::HttpClient.new(http_client_options.merge(verbose: options[:verbose])) @pact_entity = Pact::Hal::Entity.new(pact_source.uri, pact_source.pact_hash, http_client) end def call if can_publish_verification_results? tag_versions_if_configured publish_verification_results true else false end 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 !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 if !verification_result.publishable? Pact.configuration.error_stream.puts "WARN: Cannot publish verification for #{consumer_name} as not all interactions have been verified. Re-run the verification without the filter parameters or environment variables to publish the verification." return false end true 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? 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 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 Pact.configuration.provider.tags.each do | tag | Pact.configuration.output_stream.puts "INFO: Tagging version #{provider_application_version} of #{provider_name} as #{tag.inspect}" tag_entity = tag_link.expand(version: provider_application_version, tag: tag).put unless tag_entity.success? raise PublicationError.new("Error returned from tagging request: status=#{tag_entity.response.code} body=#{tag_entity.response.body}") end end end def publish_verification_results verification_entity = nil begin # The verifications resource didn't have the content_types_provided set correctly, so publishing fails if we don't have */* verification_entity = pact_entity.post(PUBLISH_RELATION, verification_result, { "Accept" => "application/hal+json, */*" }) rescue StandardError => e error_message = "Failed to publish verification results due to: #{e.class} #{e.message} #{e.backtrace.join("\n")}" raise PublicationError.new(error_message) end 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 #{verification_entity.response.code} #{verification_entity.response.body}") end end def consumer_name pact_source.pact_hash['consumer']['name'] end def provider_name pact_source.pact_hash['provider']['name'] end end end end end