diff --git a/doc/pacts/markdown/Pact Broker Client - Pactflow.md b/doc/pacts/markdown/Pact Broker Client - Pactflow.md
index 491a95e4..2aa4759b 100644
--- a/doc/pacts/markdown/Pact Broker Client - Pactflow.md
+++ b/doc/pacts/markdown/Pact Broker Client - Pactflow.md
@@ -4,12 +4,16 @@
* [A request for the index resource](#a_request_for_the_index_resource)
+* [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:publish-provider-contract_relation_exists_in_the_index_resource) given the pb:publish-provider-contract relation exists in the index resource
+
* [A request to create a provider contract](#a_request_to_create_a_provider_contract)
* [A request to create a provider contract](#a_request_to_create_a_provider_contract_given_there_is_a_pf:ui_href_in_the_response) given there is a pf:ui href in the response
* [A request to create a webhook for a team](#a_request_to_create_a_webhook_for_a_team_given_a_team_with_UUID_2abbc12a-427d-432a-a521-c870af1739d9_exists) given a team with UUID 2abbc12a-427d-432a-a521-c870af1739d9 exists
+* [A request to publish a provider contract](#a_request_to_publish_a_provider_contract)
+
#### Interactions
@@ -45,6 +49,33 @@ PactFlow will respond with:
}
}
```
+
+Given **the pb:publish-provider-contract relation exists in the index resource**, upon receiving **a request for the index resource** from Pact Broker Client, with
+```json
+{
+ "method": "GET",
+ "path": "/",
+ "headers": {
+ "Accept": "application/hal+json"
+ }
+}
+```
+PactFlow will respond with:
+```json
+{
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/hal+json;charset=utf-8"
+ },
+ "body": {
+ "_links": {
+ "pf:publish-provider-contract": {
+ "href": "http://localhost:1235/HAL-REL-PLACEHOLDER-PF-PUBLISH-PROVIDER-CONTRACT-{provider}"
+ }
+ }
+ }
+}
+```
Upon receiving **a request to create a provider contract** from Pact Broker Client, with
```json
@@ -171,3 +202,66 @@ PactFlow will respond with:
}
}
```
+
+Upon receiving **a request to publish a provider contract** from Pact Broker Client, with
+```json
+{
+ "method": "post",
+ "path": "/HAL-REL-PLACEHOLDER-PF-PUBLISH-PROVIDER-CONTRACT-Bar",
+ "headers": {
+ "Content-Type": "application/json",
+ "Accept": "application/hal+json"
+ },
+ "body": {
+ "pacticipantVersionNumber": "1",
+ "tags": [
+ "dev"
+ ],
+ "branch": "main",
+ "buildUrl": "http://build",
+ "contract": {
+ "content": "LS0tCnNvbWU6IGNvbnRyYWN0Cg==",
+ "contentType": "application/yaml",
+ "specification": "oas",
+ "selfVerificationResults": {
+ "success": true,
+ "content": "c29tZSByZXN1bHRz",
+ "contentType": "text/plain",
+ "format": "text",
+ "verifier": "my custom tool",
+ "verifierVersion": "1.0"
+ }
+ }
+ }
+}
+```
+PactFlow will respond with:
+```json
+{
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/hal+json;charset=utf-8"
+ },
+ "body": {
+ "notices": [
+ {
+ "text": "some notice",
+ "type": "info"
+ }
+ ],
+ "_embedded": {
+ "version": {
+ "number": "1"
+ }
+ },
+ "_links": {
+ "pb:pacticipant-version-tags": [
+ {
+ }
+ ],
+ "pb:branch-version": {
+ }
+ }
+ }
+}
+```
diff --git a/lib/pactflow/client/cli/provider_contract_commands.rb b/lib/pactflow/client/cli/provider_contract_commands.rb
index 66e82569..aa666bb0 100644
--- a/lib/pactflow/client/cli/provider_contract_commands.rb
+++ b/lib/pactflow/client/cli/provider_contract_commands.rb
@@ -26,7 +26,7 @@ def self.included(thor)
method_option :verification_results_format, desc: "The format of the verification output eg. junit, text"
method_option :verifier, desc: "The tool used to verify the provider contract"
method_option :verifier_version, desc: "The version of the tool used to verify the provider contract"
- #method_option :build_url, desc: "The build URL that created the pact"
+ method_option :build_url, desc: "The build URL that created the provider contract"
output_option_json_or_text
shared_authentication_options
@@ -70,6 +70,7 @@ def publish_provider_contract_command_params(provider_contract_path)
provider_version_number: options.provider_app_version.strip,
branch_name: options.branch && options.branch.strip,
tags: (options.tag && options.tag.collect(&:strip)) || [],
+ build_url: options.build_url,
contract: {
content: File.read(provider_contract_path),
content_type: options.content_type,
diff --git a/lib/pactflow/client/provider_contracts/publish.rb b/lib/pactflow/client/provider_contracts/publish.rb
index dfb5acf0..1a793ec0 100644
--- a/lib/pactflow/client/provider_contracts/publish.rb
+++ b/lib/pactflow/client/provider_contracts/publish.rb
@@ -1,13 +1,14 @@
-require "pact_broker/client/base_command"
-require "pact_broker/client/versions/create"
-require 'pact_broker/client/colorize_notices'
require "base64"
+require "pact_broker/client/base_command"
+require "pact_broker/client/colorize_notices"
+require "pactflow/client/provider_contracts/publish_the_old_way"
module Pactflow
module Client
module ProviderContracts
class Publish < PactBroker::Client::BaseCommand
- attr_reader :branch_name, :tags, :provider_name, :provider_version_number, :contract, :verification_results
+ PUBLISH_RELATION = "pf:publish-provider-contract"
+
def initialize(params, options, pact_broker_client_options)
super
@@ -15,62 +16,30 @@ def initialize(params, options, pact_broker_client_options)
@provider_version_number = params[:provider_version_number]
@branch_name = params[:branch_name]
@tags = params[:tags] || []
+ @build_url = params[:build_url]
@contract = params[:contract]
@verification_results = params[:verification_results]
end
private
- def do_call
- create_branch_version_and_tags
- render_response(create_contract)
- end
+ attr_reader :provider_name, :provider_version_number, :branch_name, :tags, :build_url, :contract, :verification_results
- def render_response(res)
- notices = [
- { type: 'success', text: "Successfully published provider contract for #{provider_name} version #{provider_version_number} to PactFlow"},
- ]
- if res.body && res.body['_links'] && res.body['_links']['pf:ui']['href']
- notices.concat([{ text: "View the uploaded contract at #{res.body['_links']['pf:ui']['href']}" }])
+ def do_call
+ if enabled? && index_resource.assert_success!.can?(PUBLISH_RELATION)
+ publish_provider_contracts
+ PactBroker::Client::CommandResult.new(success?, message)
+ else
+ PublishTheOldWay.call(params, options, pact_broker_client_options)
end
- notices.concat(next_steps)
- PactBroker::Client::CommandResult.new(true, PactBroker::Client::ColorizeNotices.call(notices.collect do |n|
- OpenStruct.new(n)
- end).join("\n"))
- end
-
- def next_steps
- [
- { type: 'prompt', text: 'Next steps:' },
- { type: 'prompt',
- text: ' * Check your application is safe to deploy - https://docs.pact.io/can_i_deploy' },
- { text: " pact-broker can-i-deploy --pacticipant #{provider_name} --version #{provider_version_number} --to-environment " },
- { type: 'prompt',
- text: ' * Record deployment or release to specified environment (choose one) - https://docs.pact.io/go/record-deployment' },
- { text: " pact-broker record-deployment --pacticipant #{provider_name} --version #{provider_version_number} --environment " },
- { text: " pact-broker record-release --pacticipant #{provider_name} --version #{provider_version_number} --environment " }
- ]
end
- def create_branch_version_and_tags
- if branch_name || tags.any?
- pacticipant_version_params = {
- pacticipant_name: provider_name,
- version_number: provider_version_number,
- branch_name: branch_name,
- tags: tags
- }
- result = PactBroker::Client::Versions::Create.call(pacticipant_version_params, options, pact_broker_client_options)
- if !result.success
- raise PactBroker::Client::Error.new(result.message)
- end
- end
+ def enabled?
+ ENV.fetch("PACT_BROKER_FEATURES", "").include?("publish_provider_contracts_all_in_one")
end
- def create_contract
- contract_path = "#{pact_broker_base_url}/contracts/provider/{provider}/version/{version}"
- entrypoint = create_entry_point(contract_path, pact_broker_client_options)
- entrypoint.expand(provider: provider_name, version: provider_version_number).put!(contract_params).response
+ def publish_provider_contracts
+ @response_entity = index_resource._link(PUBLISH_RELATION).expand(provider: provider_name).post!(contract_params, headers: { "Accept" => "application/hal+json,application/problem+json" })
end
def contract_params
@@ -82,22 +51,45 @@ def contract_params
verifier: verification_results[:verifier],
verifierVersion: verification_results[:verifier_version]
}.compact
-
- body_params = {
- content: encode_content(contract[:content]),
- contractType: contract[:specification],
- contentType: contract[:content_type],
- }.compact
+
+ contract_params = {
+ content: encode_content(contract[:content]),
+ specification: contract[:specification],
+ contentType: contract[:content_type]
+ }.compact
if verification_results_params.any?
- body_params[:verificationResults] = verification_results_params
+ contract_params[:selfVerificationResults] = verification_results_params
end
- body_params
+
+ {
+ pacticipantVersionNumber: provider_version_number,
+ tags: tags,
+ branch: branch_name,
+ buildUrl: build_url,
+ contract: contract_params
+ }
end
def encode_content oas
Base64.strict_encode64(oas)
end
+
+ def message
+ if options[:output] == "json"
+ @response_entity.response.raw_body
+ else
+ text_message
+ end
+ end
+
+ def success?
+ @response_entity.success?
+ end
+
+ def text_message
+ PactBroker::Client::ColorizeNotices.call(@response_entity.notices.collect{ |n| OpenStruct.new(n) } )
+ end
end
end
end
diff --git a/lib/pactflow/client/provider_contracts/publish_the_old_way.rb b/lib/pactflow/client/provider_contracts/publish_the_old_way.rb
new file mode 100644
index 00000000..44acaf39
--- /dev/null
+++ b/lib/pactflow/client/provider_contracts/publish_the_old_way.rb
@@ -0,0 +1,104 @@
+require "pact_broker/client/base_command"
+require "pact_broker/client/versions/create"
+require 'pact_broker/client/colorize_notices'
+require "base64"
+
+module Pactflow
+ module Client
+ module ProviderContracts
+ class PublishTheOldWay < PactBroker::Client::BaseCommand
+ attr_reader :branch_name, :tags, :provider_name, :provider_version_number, :contract, :verification_results
+
+ def initialize(params, options, pact_broker_client_options)
+ super
+ @provider_name = params[:provider_name]
+ @provider_version_number = params[:provider_version_number]
+ @branch_name = params[:branch_name]
+ @tags = params[:tags] || []
+ @contract = params[:contract]
+ @verification_results = params[:verification_results]
+ end
+
+ private
+
+ def do_call
+ create_branch_version_and_tags
+ render_response(create_contract)
+ end
+
+ def render_response(res)
+ notices = [
+ { type: 'success', text: "Successfully published provider contract for #{provider_name} version #{provider_version_number} to PactFlow"},
+ ]
+ if res.body && res.body['_links'] && res.body['_links']['pf:ui']['href']
+ notices.concat([{ text: "View the uploaded contract at #{res.body['_links']['pf:ui']['href']}" }])
+ end
+ notices.concat(next_steps)
+ PactBroker::Client::CommandResult.new(true, PactBroker::Client::ColorizeNotices.call(notices.collect do |n|
+ OpenStruct.new(n)
+ end).join("\n"))
+ end
+
+ def next_steps
+ [
+ { type: 'prompt', text: 'Next steps:' },
+ { type: 'prompt',
+ text: ' * Check your application is safe to deploy - https://docs.pact.io/can_i_deploy' },
+ { text: " pact-broker can-i-deploy --pacticipant #{provider_name} --version #{provider_version_number} --to-environment " },
+ { type: 'prompt',
+ text: ' * Record deployment or release to specified environment (choose one) - https://docs.pact.io/go/record-deployment' },
+ { text: " pact-broker record-deployment --pacticipant #{provider_name} --version #{provider_version_number} --environment " },
+ { text: " pact-broker record-release --pacticipant #{provider_name} --version #{provider_version_number} --environment " }
+ ]
+ end
+
+ def create_branch_version_and_tags
+ if branch_name || tags.any?
+ pacticipant_version_params = {
+ pacticipant_name: provider_name,
+ version_number: provider_version_number,
+ branch_name: branch_name,
+ tags: tags
+ }
+ result = PactBroker::Client::Versions::Create.call(pacticipant_version_params, options, pact_broker_client_options)
+ if !result.success
+ raise PactBroker::Client::Error.new(result.message)
+ end
+ end
+ end
+
+ def create_contract
+ contract_path = "#{pact_broker_base_url}/contracts/provider/{provider}/version/{version}"
+ entrypoint = create_entry_point(contract_path, pact_broker_client_options)
+ entrypoint.expand(provider: provider_name, version: provider_version_number).put!(contract_params).response
+ end
+
+ def contract_params
+ verification_results_params = {
+ success: verification_results[:success],
+ content: verification_results[:content] ? encode_content(verification_results[:content]) : nil,
+ contentType: verification_results[:content_type],
+ format: verification_results[:format],
+ verifier: verification_results[:verifier],
+ verifierVersion: verification_results[:verifier_version]
+ }.compact
+
+ body_params = {
+ content: encode_content(contract[:content]),
+ contractType: contract[:specification],
+ contentType: contract[:content_type],
+ }.compact
+
+ if verification_results_params.any?
+ body_params[:verificationResults] = verification_results_params
+ end
+ body_params
+ end
+
+ def encode_content oas
+ Base64.strict_encode64(oas)
+ end
+ end
+ end
+ end
+end
diff --git a/script/foo-bar.json b/script/foo-bar.json
new file mode 100644
index 00000000..f15ac596
--- /dev/null
+++ b/script/foo-bar.json
@@ -0,0 +1,81 @@
+{
+ "consumer": {
+ "name": "Foo"
+ },
+ "provider": {
+ "name": "Bar"
+ },
+ "interactions": [
+ {
+ "description": "a request to list the latest pacts",
+ "providerState": "a pact between Condor and the Pricing Service exists",
+ "request": {
+ "method": "get",
+ "path": "/pacts/latest",
+ "headers": {
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/hal+json"
+ },
+ "body": {
+ "_links": {
+ "self": {
+ "href": "http://example.org/pacts/latest"
+ }
+ },
+ "pacts": [
+ {
+ "_links": {
+ "self": [
+ {
+ "href": "http://example.org/pacts/provider/Pricing%20Service/consumer/Condor/latest"
+ },
+ {
+ "href": "http://example.org/pacts/provider/Pricing%20Service/consumer/Condor/version/1.3.0"
+ }
+ ]
+ },
+ "_embedded": {
+ "consumer": {
+ "name": "Condor",
+ "_links": {
+ "self": {
+ "href": "http://example.org/pacticipants/Condor"
+ }
+ },
+ "_embedded": {
+ "version": {
+ "number": "1.3.0"
+ }
+ }
+ },
+ "provider": {
+ "_links": {
+ "self": {
+ "href": "http://example.org/pacticipants/Pricing%20Service"
+ }
+ },
+ "name": "Pricing Service"
+ }
+ }
+ }
+ ]
+ },
+ "matchingRules": {
+ "$.headers.Content-Type": {
+ "match": "regex",
+ "regex": "application\\/hal\\+json"
+ }
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "pactSpecification": {
+ "version": "2.0.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/script/publish-pact.sh b/script/publish-pact.sh
index f60ba5c3..aa6c1c97 100755
--- a/script/publish-pact.sh
+++ b/script/publish-pact.sh
@@ -1,5 +1,5 @@
-export PACT_BROKER_BASE_URL="http://localhost:9292"
-export PACT_BROKER_TOKEN="localhost"
+export PACT_BROKER_BASE_URL=${PACT_BROKER_BASE_URL:-"http://localhost:9292"}
+export PACT_BROKER_TOKEN=${PACT_BROKER_TOKEN:-"localhost"}
#export PACT_BROKER_FEATURES=publish_pacts_using_old_api
# bundle exec bin/pact-broker create-or-update-webhook http://localhost:9393 \
@@ -8,11 +8,11 @@ export PACT_BROKER_TOKEN="localhost"
# --description "foo webhook" \
# --contract-published
-bundle exec bin/pact-broker create-or-update-webhook http://localhost:9393 \
- --uuid d40f38c3-aaa3-47f5-9161-95csfadfsd7 \
- --description "This is quite a long description for a webhook that I hope will be truncated" \
- --request POST \
- --contract-published
+# bundle exec bin/pact-broker create-or-update-webhook http://localhost:9393 \
+# --uuid d40f38c3-aaa3-47f5-9161-95csfadfsd7 \
+# --description "This is quite a long description for a webhook that I hope will be truncated" \
+# --request POST \
+# --contract-published
# bundle exec bin/pact-broker publish spec/pacts/pact_broker_client-pact_broker.json spec/fixtures/foo-bar.json \
# --consumer-app-version 1.2.12 \
@@ -29,10 +29,8 @@ bundle exec bin/pact-broker create-or-update-webhook http://localhost:9393 \
# --contract-published
-bundle exec bin/pact-broker publish spec/pacts/pact_broker_client-pact_broker.json \
+bundle exec bin/pact-broker publish scripts/foo-bar.json \
--consumer-app-version 1.2.26 \
- --broker-base-url http://localhost:9292 \
- --broker-token localhost \
--auto-detect-version-properties \
--build-url http://mybuild \
--branch master --tag foo5 --tag foo6
diff --git a/script/publish-provider-contract.sh b/script/publish-provider-contract.sh
index df4981cc..99c4fff3 100755
--- a/script/publish-provider-contract.sh
+++ b/script/publish-provider-contract.sh
@@ -1,7 +1,7 @@
export PACT_BROKER_BASE_URL=${PACT_BROKER_BASE_URL:-"http://localhost:9292"}
bundle exec bin/pactflow publish-provider-contract \
script/oas.yml \
- --provider Foo \
+ --provider Bar \
--provider-app-version 1013b5650d61214e19f10558f97fb5a3bb082d44 \
--branch main \
--tag dev \
diff --git a/spec/fixtures/approvals/publish_provider_contract.approved.txt b/spec/fixtures/approvals/publish_provider_contract.approved.txt
new file mode 100644
index 00000000..28346172
--- /dev/null
+++ b/spec/fixtures/approvals/publish_provider_contract.approved.txt
@@ -0,0 +1,2 @@
+some notice
+some other notice
diff --git a/spec/integration/publish_provider_contract_spec.rb b/spec/integration/publish_provider_contract_spec.rb
new file mode 100644
index 00000000..8cae54bf
--- /dev/null
+++ b/spec/integration/publish_provider_contract_spec.rb
@@ -0,0 +1,57 @@
+require "pactflow/client/cli/pactflow"
+
+RSpec.describe "publish-provider-contract" do
+ before do
+ allow(ENV).to receive(:fetch).and_call_original
+ allow(ENV).to receive(:fetch).with("PACT_BROKER_FEATURES", "").and_return("publish_provider_contracts_all_in_one")
+ end
+ let(:index_body_hash) do
+ {
+ _links: {
+ "pf:publish-provider-contract" => {
+ href: "http://broker/some-publish/{provider}"
+ }
+ }
+ }
+ end
+
+ let(:post_response_body) do
+ {
+ "notices"=>[{"text"=>"some notice", "type"=>"info"}, {"text"=>"some other notice", "type"=>"info"}]
+ }
+ end
+
+ let!(:index_request) do
+ stub_request(:get, "http://broker").to_return(status: 200, body: index_body_hash.to_json, headers: { "Content-Type" => "application/hal+json" } )
+ end
+
+ let!(:publish_request) do
+ stub_request(:post, "http://broker/some-publish/Bar").to_return(status: 200, body: post_response_body.to_json, headers: { "Content-Type" => "application/hal+json" } )
+ end
+
+ let(:parameters) do
+ %w{
+ publish-provider-contract
+ script/oas.yml
+ --provider Bar
+ --broker-base-url http://broker
+ --provider-app-version 1013b5650d61214e19f10558f97fb5a3bb082d44
+ --branch main
+ --tag dev
+ --specification oas
+ --content-type application/yml
+ --verification-exit-code 0
+ --verification-results script/verification-results.txt
+ --verification-results-content-type text/plain
+ --verification-results-format text
+ --verifier my-custom-tool
+ --verifier-version "1.0"
+ }
+ end
+
+ subject { capture(:stdout) { Pactflow::Client::CLI::Pactflow.start(parameters) } }
+
+ it "prints the notices" do
+ Approvals.verify(subject, :name => "publish_provider_contract", format: :txt)
+ end
+end
diff --git a/spec/lib/pactflow/client/provider_contracts/publish_spec.rb b/spec/lib/pactflow/client/provider_contracts/publish_spec.rb
new file mode 100644
index 00000000..91e841f8
--- /dev/null
+++ b/spec/lib/pactflow/client/provider_contracts/publish_spec.rb
@@ -0,0 +1,179 @@
+require "pactflow/client/provider_contracts/publish"
+
+module Pactflow
+ module Client
+ module ProviderContracts
+ describe Publish do
+ before do
+ allow_any_instance_of(PactBroker::Client::Hal::HttpClient).to receive(:sleep)
+ allow_any_instance_of(PactBroker::Client::Hal::HttpClient).to receive(:default_max_tries).and_return(1)
+ allow(ENV).to receive(:fetch).and_call_original
+ allow(ENV).to receive(:fetch).with("PACT_BROKER_FEATURES", "").and_return("publish_provider_contracts_all_in_one")
+ end
+
+ let(:command_params) do
+ {
+ provider_name: "Bar",
+ provider_version_number: "1",
+ branch_name: "main",
+ tags: ["dev"],
+ build_url: "http://build",
+ contract: {
+ content: { "some" => "contract" }.to_yaml,
+ content_type: "application/yaml",
+ specification: "oas"
+ },
+ verification_results: {
+ success: true,
+ content: "some results",
+ content_type: "text/plain",
+ format: "text",
+ verifier: "my custom tool",
+ verifier_version: "1.0"
+ }
+ }
+ end
+
+ let(:options) do
+ {
+ verbose: false
+ }
+ end
+
+ let(:pact_broker_client_options) do
+ { pact_broker_base_url: "http://pactflow" }
+ end
+
+ let(:index_body_hash) do
+ {
+ _links: {
+ "pf:publish-provider-contract" => {
+ href: "http://pactflow/some-publish/{provider}"
+ }
+ }
+ }
+ end
+
+ let(:post_response_body) do
+ {
+ "notices"=>[{"text"=>"some notice", "type"=>"info"}]
+ }
+ end
+
+ let!(:index_request) do
+ stub_request(:get, "http://pactflow")
+ .to_return(
+ status: index_status,
+ body: index_body_hash.to_json,
+ headers: { "Content-Type" => "application/hal+json" }
+ )
+ end
+ let(:index_status) { 200 }
+
+ let!(:publish_request) do
+ stub_request(:post, "http://pactflow/some-publish/Bar")
+ .to_return(
+ status: publish_status,
+ body: post_response_body.to_json,
+ headers: { "Content-Type" => "application/hal+json" }
+ )
+ end
+ let(:publish_status) { 200 }
+
+ subject { Publish.call(command_params, options, pact_broker_client_options) }
+
+ context "when there is no relation pf:publish-provider-contract" do
+ before do
+ allow(PublishTheOldWay).to receive(:call).with(command_params, options, pact_broker_client_options).and_return(instance_double(PactBroker::Client::CommandResult))
+ end
+
+ let(:index_body_hash) do
+ {
+ _links: {}
+ }
+ end
+
+ it "publishes the provider contracts the old way" do
+ expect(PublishTheOldWay).to receive(:call).with(command_params, options, pact_broker_client_options)
+ subject
+ end
+ end
+
+ context "when the feature is not enabled" do
+ before do
+ allow(ENV).to receive(:fetch).with("PACT_BROKER_FEATURES", "").and_return("")
+ end
+
+ it "publishes the provider contracts the old way" do
+ expect(PublishTheOldWay).to receive(:call).with(command_params, options, pact_broker_client_options)
+ subject
+ end
+ end
+
+ it "returns a result and message" do
+ expect(subject.success).to be true
+ expect(subject.message).to include("some notice")
+ end
+
+ it "colourises the notices" do
+ expect(PactBroker::Client::ColorizeNotices).to receive(:call).with([OpenStruct.new(text: "some notice", type: "info")]).and_return("coloured notices")
+ expect(subject.message).to eq "coloured notices"
+ end
+
+ context "when the output is json" do
+ let(:options) { { output: "json" } }
+
+ it "returns the raw response" do
+ expect(subject.message).to eq post_response_body.to_json
+ end
+ end
+
+ context "when there is an error retrieving the index" do
+ let(:index_status) { 500 }
+ let(:index_body_hash) { { "some" => "error" }}
+
+ it "returns an error result with the response body" do
+ expect(subject.success).to be false
+ expect(subject.message).to match(/some.*error/)
+ end
+ end
+
+ context "when there is an error response from publishing" do
+ let(:publish_status) { 400 }
+ let(:post_response_body) do
+ {
+ "some" => "error"
+ }
+ end
+
+ it "returns an error result with the response body" do
+ expect(subject.success).to be false
+ expect(subject.message).to match(/some.*error/)
+ end
+
+ context "when the output is json" do
+ let(:options) { { output: "json" } }
+
+ it "returns the raw response" do
+ expect(subject.message).to eq post_response_body.to_json
+ end
+ end
+ end
+
+ context "when there is an error response from publishing" do
+ let(:publish_status) { 400 }
+ let(:post_response_body) do
+ {
+ "some" => "error"
+ }
+ end
+
+ it "returns an error result with the response body" do
+ expect(subject.success).to be false
+ expect(subject.message).to match(/some.*error/)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/pacts/pact_broker_client-pactflow.json b/spec/pacts/pact_broker_client-pactflow.json
index bffebb21..b5ef8b41 100644
--- a/spec/pacts/pact_broker_client-pactflow.json
+++ b/spec/pacts/pact_broker_client-pactflow.json
@@ -6,6 +6,103 @@
"name": "PactFlow"
},
"interactions": [
+ {
+ "description": "a request for the index resource",
+ "providerState": "the pb:publish-provider-contract relation exists in the index resource",
+ "request": {
+ "method": "GET",
+ "path": "/",
+ "headers": {
+ "Accept": "application/hal+json"
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/hal+json;charset=utf-8"
+ },
+ "body": {
+ "_links": {
+ "pf:publish-provider-contract": {
+ "href": "http://localhost:1235/HAL-REL-PLACEHOLDER-PF-PUBLISH-PROVIDER-CONTRACT-{provider}"
+ }
+ }
+ },
+ "matchingRules": {
+ "$.body._links.pf:publish-provider-contract.href": {
+ "match": "regex",
+ "regex": "http:\\/\\/.*{provider}"
+ }
+ }
+ }
+ },
+ {
+ "description": "a request to publish a provider contract",
+ "request": {
+ "method": "post",
+ "path": "/HAL-REL-PLACEHOLDER-PF-PUBLISH-PROVIDER-CONTRACT-Bar",
+ "headers": {
+ "Content-Type": "application/json",
+ "Accept": "application/hal+json"
+ },
+ "body": {
+ "pacticipantVersionNumber": "1",
+ "tags": [
+ "dev"
+ ],
+ "branch": "main",
+ "buildUrl": "http://build",
+ "contract": {
+ "content": "LS0tCnNvbWU6IGNvbnRyYWN0Cg==",
+ "contentType": "application/yaml",
+ "specification": "oas",
+ "selfVerificationResults": {
+ "success": true,
+ "content": "c29tZSByZXN1bHRz",
+ "contentType": "text/plain",
+ "format": "text",
+ "verifier": "my custom tool",
+ "verifierVersion": "1.0"
+ }
+ }
+ }
+ },
+ "response": {
+ "status": 200,
+ "headers": {
+ "Content-Type": "application/hal+json;charset=utf-8"
+ },
+ "body": {
+ "notices": [
+ {
+ "text": "some notice",
+ "type": "info"
+ }
+ ],
+ "_embedded": {
+ "version": {
+ "number": "1"
+ }
+ },
+ "_links": {
+ "pb:pacticipant-version-tags": [
+ {
+ }
+ ],
+ "pb:branch-version": {
+ }
+ }
+ },
+ "matchingRules": {
+ "$.body.notices": {
+ "min": 1
+ },
+ "$.body.notices[*].*": {
+ "match": "type"
+ }
+ }
+ }
+ },
{
"description": "a request to create a provider contract",
"request": {
diff --git a/spec/service_providers/pact_helper.rb b/spec/service_providers/pact_helper.rb
index 8b22f83e..eb3d710b 100644
--- a/spec/service_providers/pact_helper.rb
+++ b/spec/service_providers/pact_helper.rb
@@ -25,10 +25,12 @@
module PactBrokerPactHelperMethods
+ # @param [String] relation eg "pb:pacticipant"
+ # @param [Array] params eg ["Foo"]
def placeholder_path(relation, params = [])
path = "/HAL-REL-PLACEHOLDER-#{relation.gsub(':', '-').upcase}"
if params.any?
- joined_params = params.collect{ |param| "{#{param}}"}.join("-")
+ joined_params = params.join("-")
path = "#{path}-#{joined_params}"
end
@@ -36,7 +38,19 @@ def placeholder_path(relation, params = [])
end
def placeholder_url(relation, params = [], mock_service = pact_broker)
- "#{mock_service.mock_service_base_url}#{placeholder_path(relation, params)}"
+ "#{mock_service.mock_service_base_url}#{placeholder_path_for_term(relation, params)}"
+ end
+
+ # @param [String] relation eg "pb:pacticipants"
+ # @param [Array] params eg ["pacticipant"]
+ def placeholder_path_for_term(relation, params = [])
+ path = "/HAL-REL-PLACEHOLDER-#{relation.gsub(':', '-').upcase}"
+ if params.any?
+ joined_params = params.collect{ |param| "{#{param}}"}.join("-")
+ path = "#{path}-#{joined_params}"
+ end
+
+ path
end
def placeholder_url_term(relation, params = [], mock_service = pact_broker)
diff --git a/spec/service_providers/pactflow_publish_provider_contract_spec.rb b/spec/service_providers/pactflow_publish_provider_contract_spec.rb
index b15d875e..b3d417a6 100644
--- a/spec/service_providers/pactflow_publish_provider_contract_spec.rb
+++ b/spec/service_providers/pactflow_publish_provider_contract_spec.rb
@@ -1,11 +1,13 @@
+require "yaml"
require_relative "pact_helper"
require "pactflow/client/provider_contracts/publish"
-require "yaml"
RSpec.describe "publishing a provider contract to PactFlow", pact: true do
before do
- # no point re-testing this
- allow(PactBroker::Client::Versions::Create).to receive(:call).and_return(double("result", success: true))
+ allow_any_instance_of(PactBroker::Client::Hal::HttpClient).to receive(:sleep)
+ allow_any_instance_of(PactBroker::Client::Hal::HttpClient).to receive(:default_max_tries).and_return(1)
+ allow(ENV).to receive(:fetch).and_call_original
+ allow(ENV).to receive(:fetch).with("PACT_BROKER_FEATURES", "").and_return("publish_provider_contracts_all_in_one")
end
include_context "pact broker"
@@ -17,6 +19,7 @@
provider_version_number: "1",
branch_name: "main",
tags: ["dev"],
+ build_url: "http://build",
contract: {
content: { "some" => "contract" }.to_yaml,
content_type: "application/yaml",
@@ -33,14 +36,17 @@
}
end
- let(:body) { { some: "body" }.to_json }
-
let(:request_body) do
{
+ "pacticipantVersionNumber" => "1",
+ "tags" => ["dev"],
+ "branch" => "main",
+ "buildUrl" => "http://build",
+ "contract" => {
"content" => "LS0tCnNvbWU6IGNvbnRyYWN0Cg==",
- "contractType" => "oas",
"contentType" => "application/yaml",
- "verificationResults" => {
+ "specification" => "oas",
+ "selfVerificationResults" => {
"success" => true,
"content" => "c29tZSByZXN1bHRz",
"contentType" => "text/plain",
@@ -48,14 +54,36 @@
"verifier" => "my custom tool",
"verifierVersion" => "1.0"
}
+ }
}
end
- let(:response_status) { 201 }
+ let(:response_status) { 200 }
+
+ # Can't tell from the response if the buildUrl was correct, but it's not that important
+ # Add some assertions to the body to ensure we have called the endpoint correctly,
+ # not because we use the properties in the CLI output.
+ # There is unfortunately no good way to determine from the response whether or not
+ # we have correctly published the self verification results.
let(:success_response) do
{
status: response_status,
- headers: pact_broker_response_headers
+ headers: pact_broker_response_headers,
+ body: {
+ "notices" => Pact.each_like("text" => "some notice", "type" => "info"),
+ "_embedded" => {
+ "version" => {
+ # This tells us we have set the version number correctly
+ "number" => "1"
+ }
+ },
+ "_links" => {
+ # The links tell us we have successfully created the tags, but we don't care about the contents
+ "pb:pacticipant-version-tags" => [{}],
+ # The link tells us we have successfully created the branch version, but we don't care about the contents
+ "pb:branch-version" => {},
+ }
+ }
}
end
@@ -74,54 +102,38 @@
context "creating a provider contract with valid parameters" do
before do
pactflow
- .upon_receiving("a request to create a provider contract")
+ .given("the pb:publish-provider-contract relation exists in the index resource")
+ .upon_receiving("a request for the index resource")
.with(
- method: :put,
- path: "/contracts/provider/Bar/version/1",
- headers: put_request_headers,
- body: request_body)
- .will_respond_with(success_response)
- end
-
- it "returns a CommandResult with success = true" do
- expect(subject).to be_a PactBroker::Client::CommandResult
- expect(subject.success).to be true
- expect(subject.message).to include "Successfully published provider contract for Bar version 1"
- expect(subject.message).not_to include pactflow.mock_service_base_url
- end
- end
-
- context "creating a provider contract with valid parameters with pf:ui return results" do
- let(:success_response_with_pf_ui_url) do
- {
- status: response_status,
- headers: pact_broker_response_headers,
- body: { "_links": {
- "pf:ui": {
- "href": Pact.like("some-url")
+ method: "GET",
+ path: "/",
+ headers: get_request_headers
+ ).will_respond_with(
+ status: 200,
+ headers: pact_broker_response_headers,
+ body: {
+ _links: {
+ :'pf:publish-provider-contract' => {
+ href: placeholder_url_term("pf:publish-provider-contract", ['provider'], pactflow)
+ }
+ }
}
- } }
- }
- end
- before do
+ )
+
pactflow
- .given("there is a pf:ui href in the response")
- .upon_receiving("a request to create a provider contract")
+ .upon_receiving("a request to publish a provider contract")
.with(
- method: :put,
- path: "/contracts/provider/Bar/version/1",
- headers: put_request_headers,
- body: request_body
- )
- .will_respond_with(success_response_with_pf_ui_url)
+ method: :post,
+ path: placeholder_path("pf:publish-provider-contract", ["Bar"]),
+ headers: post_request_headers,
+ body: request_body
+ ).will_respond_with(success_response)
end
- it "returns a CommandResult with success = true and a provider contract ui url" do
+ it "returns a CommandResult with success = true" do
expect(subject).to be_a PactBroker::Client::CommandResult
expect(subject.success).to be true
- expect(subject.message).to include "Successfully published provider contract for Bar version 1"
- expect(subject.message).to include "Next steps:"
- expect(subject.message).to include "some-url"
+ expect(subject.message).to include "some notice"
end
end
-end
\ No newline at end of file
+end
diff --git a/spec/service_providers/pactflow_publish_provider_contract_the_old_way_spec.rb b/spec/service_providers/pactflow_publish_provider_contract_the_old_way_spec.rb
new file mode 100644
index 00000000..0a08c823
--- /dev/null
+++ b/spec/service_providers/pactflow_publish_provider_contract_the_old_way_spec.rb
@@ -0,0 +1,129 @@
+require_relative "pact_helper"
+require "yaml"
+require "pactflow/client/provider_contracts/publish_the_old_way"
+require "pact_broker/client/versions/create"
+
+
+RSpec.describe "publishing a provider contract to PactFlow the old way", pact: true do
+ before do
+ # no point re-testing this
+ allow(PactBroker::Client::Versions::Create).to receive(:call).and_return(double("result", success: true))
+ end
+
+ include_context "pact broker"
+ include PactBrokerPactHelperMethods
+
+ let(:command_params) do
+ {
+ provider_name: "Bar",
+ provider_version_number: "1",
+ branch_name: "main",
+ tags: ["dev"],
+ contract: {
+ content: { "some" => "contract" }.to_yaml,
+ content_type: "application/yaml",
+ specification: "oas"
+ },
+ verification_results: {
+ success: true,
+ content: "some results",
+ content_type: "text/plain",
+ format: "text",
+ verifier: "my custom tool",
+ verifier_version: "1.0"
+ }
+ }
+ end
+
+ let(:body) { { some: "body" }.to_json }
+
+ let(:request_body) do
+ {
+ "content" => "LS0tCnNvbWU6IGNvbnRyYWN0Cg==",
+ "contractType" => "oas",
+ "contentType" => "application/yaml",
+ "verificationResults" => {
+ "success" => true,
+ "content" => "c29tZSByZXN1bHRz",
+ "contentType" => "text/plain",
+ "format" => "text",
+ "verifier" => "my custom tool",
+ "verifierVersion" => "1.0"
+ }
+ }
+ end
+
+ let(:response_status) { 201 }
+ let(:success_response) do
+ {
+ status: response_status,
+ headers: pact_broker_response_headers
+ }
+ end
+
+ let(:options) do
+ {
+ verbose: false
+ }
+ end
+
+ let(:pact_broker_client_options) do
+ { pact_broker_base_url: pactflow.mock_service_base_url }
+ end
+
+ subject { Pactflow::Client::ProviderContracts::PublishTheOldWay.call(command_params, options, pact_broker_client_options) }
+
+ context "creating a provider contract with valid parameters" do
+ before do
+ pactflow
+ .upon_receiving("a request to create a provider contract")
+ .with(
+ method: :put,
+ path: "/contracts/provider/Bar/version/1",
+ headers: put_request_headers,
+ body: request_body)
+ .will_respond_with(success_response)
+ end
+
+ it "returns a CommandResult with success = true" do
+ expect(subject).to be_a PactBroker::Client::CommandResult
+ expect(subject.success).to be true
+ expect(subject.message).to include "Successfully published provider contract for Bar version 1"
+ expect(subject.message).not_to include pactflow.mock_service_base_url
+ end
+ end
+
+ context "creating a provider contract with valid parameters with pf:ui return results" do
+ let(:success_response_with_pf_ui_url) do
+ {
+ status: response_status,
+ headers: pact_broker_response_headers,
+ body: { "_links": {
+ "pf:ui": {
+ "href": Pact.like("some-url")
+ }
+ } }
+ }
+ end
+ before do
+ pactflow
+ .given("there is a pf:ui href in the response")
+ .upon_receiving("a request to create a provider contract")
+ .with(
+ method: :put,
+ path: "/contracts/provider/Bar/version/1",
+ headers: put_request_headers,
+ body: request_body
+ )
+ .will_respond_with(success_response_with_pf_ui_url)
+ end
+
+ it "returns a CommandResult with success = true and a provider contract ui url" do
+ expect(subject).to be_a PactBroker::Client::CommandResult
+ expect(subject.success).to be true
+ expect(subject.message).to include "Successfully published provider contract for Bar version 1"
+ expect(subject.message).to include "Next steps:"
+ expect(subject.message).to include "some-url"
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 9e40586c..113e155f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -33,6 +33,10 @@ def capture(stream)
eval "$#{stream} = StringIO.new"
yield
result = eval("$#{stream}").string
+ rescue SystemExit => e
+ puts "CAUGHT SYSTEM EXIT"
+ puts e
+ puts e.backtrace
ensure
eval("$#{stream} = #{stream.upcase}")
end