From 3714d319bc0124612b620938289997b2a3aee4bb Mon Sep 17 00:00:00 2001 From: Beth Skurrie Date: Fri, 10 Nov 2023 08:55:43 +1100 Subject: [PATCH] feat: add support for delete-branch command PACT-1466 --- .../Pact Broker Client - Pact Broker.md | 45 ++++++++++ .../client/branches/delete_branch.rb | 64 +++++++++++++ lib/pact_broker/client/cli/branch_commands.rb | 39 ++++++++ lib/pact_broker/client/cli/broker.rb | 3 +- .../client/branches/delete_branch_spec.rb | 90 +++++++++++++++++++ .../pacts/pact_broker_client-pact_broker.json | 43 +++++++++ spec/service_providers/delete_branch_spec.rb | 68 ++++++++++++++ spec/service_providers/pact_helper.rb | 1 + 8 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 lib/pact_broker/client/branches/delete_branch.rb create mode 100644 lib/pact_broker/client/cli/branch_commands.rb create mode 100644 spec/lib/pact_broker/client/branches/delete_branch_spec.rb create mode 100644 spec/service_providers/delete_branch_spec.rb diff --git a/doc/pacts/markdown/Pact Broker Client - Pact Broker.md b/doc/pacts/markdown/Pact Broker Client - Pact Broker.md index 3580889..39cb014 100644 --- a/doc/pacts/markdown/Pact Broker Client - Pact Broker.md +++ b/doc/pacts/markdown/Pact Broker Client - Pact Broker.md @@ -38,6 +38,8 @@ * [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:latest-version_relation_exists_in_the_index_resource) given the pb:latest-version relation exists in the index resource +* [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:pacticipant-branch_relation_exists_in_the_index_resource) given the pb:pacticipant-branch relation exists in the index resource + * [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:pacticipant-version_and_pb:environments_relations_exist_in_the_index_resource) given the pb:pacticipant-version and pb:environments relations exist in the index resource * [A request for the index resource](#a_request_for_the_index_resource_given_the_pb:publish-contracts_relations_exists_in_the_index_resource) given the pb:publish-contracts relations exists in the index resource @@ -76,6 +78,8 @@ * [A request to create a webhook with every possible event type](#a_request_to_create_a_webhook_with_every_possible_event_type_given_the_'Pricing_Service'_and_'Condor'_already_exist_in_the_pact-broker) given the 'Pricing Service' and 'Condor' already exist in the pact-broker +* [A request to delete a pacticipant branch](#a_request_to_delete_a_pacticipant_branch_given_a_branch_named_main_exists_for_pacticipant_Foo) given a branch named main exists for pacticipant Foo + * [A request to determine if Bar can be deployed with all Foo tagged prod, ignoring the verification for Foo version 3.4.5](#a_request_to_determine_if_Bar_can_be_deployed_with_all_Foo_tagged_prod,_ignoring_the_verification_for_Foo_version_3.4.5_given_provider_Bar_version_4.5.6_has_a_successful_verification_for_Foo_version_1.2.3_tagged_prod_and_a_failed_verification_for_version_3.4.5_tagged_prod) given provider Bar version 4.5.6 has a successful verification for Foo version 1.2.3 tagged prod and a failed verification for version 3.4.5 tagged prod * [A request to get the Pricing Service](#a_request_to_get_the_Pricing_Service_given_the_'Pricing_Service'_already_exists_in_the_pact-broker) given the 'Pricing Service' already exists in the pact-broker @@ -827,6 +831,33 @@ Pact Broker will respond with: } } ``` + +Given **the pb:pacticipant-branch 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" + } +} +``` +Pact Broker will respond with: +```json +{ + "status": 200, + "headers": { + "Content-Type": "application/hal+json;charset=utf-8" + }, + "body": { + "_links": { + "pb:pacticipant-branch": { + "href": "http://localhost:1234/HAL-REL-PLACEHOLDER-PB-PACTICIPANT-BRANCH-{pacticipant}-{branch}" + } + } + } +} +``` Given **the pb:pacticipant-version and pb:environments relations exist in the index resource**, upon receiving **a request for the index resource** from Pact Broker Client, with ```json @@ -1766,6 +1797,20 @@ Pact Broker will respond with: } } ``` + +Given **a branch named main exists for pacticipant Foo**, upon receiving **a request to delete a pacticipant branch** from Pact Broker Client, with +```json +{ + "method": "DELETE", + "path": "/HAL-REL-PLACEHOLDER-PB-PACTICIPANT-BRANCH-Foo-main" +} +``` +Pact Broker will respond with: +```json +{ + "status": 204 +} +``` Given **provider Bar version 4.5.6 has a successful verification for Foo version 1.2.3 tagged prod and a failed verification for version 3.4.5 tagged prod**, upon receiving **a request to determine if Bar can be deployed with all Foo tagged prod, ignoring the verification for Foo version 3.4.5** from Pact Broker Client, with ```json diff --git a/lib/pact_broker/client/branches/delete_branch.rb b/lib/pact_broker/client/branches/delete_branch.rb new file mode 100644 index 0000000..d5830e3 --- /dev/null +++ b/lib/pact_broker/client/branches/delete_branch.rb @@ -0,0 +1,64 @@ +require "pact_broker/client/base_command" + +module PactBroker + module Client + module Branches + class DeleteBranch < PactBroker::Client::BaseCommand + + NOT_SUPPORTED_MESSAGE_PACT_BROKER = "This version of the Pact Broker does not support deleting branches. Please upgrade to version X or later." + NOT_SUPPORTED_MESSAGE_PACTFLOW = "This version of PactFlow does not support deleting branches. Please upgrade to version X or later." + + def initialize(params, options, pact_broker_client_options) + super + @pacticipant_name = params.fetch(:pacticipant) + @branch_name = params.fetch(:branch) + @error_when_not_found = params.fetch(:error_when_not_found) + end + + def do_call + check_if_command_supported + @deleted_resource = branch_link.delete + PactBroker::Client::CommandResult.new(success?, result_message) + end + + private + + attr_reader :pacticipant_name, :branch_name, :error_when_not_found, :deleted_resource + + def branch_link + index_resource._link("pb:pacticipant-branch").expand(pacticipant: pacticipant_name, branch: branch_name) + end + + def check_if_command_supported + unless index_resource.can?("pb:pacticipant-branch") + raise PactBroker::Client::Error.new(is_pactflow? ? NOT_SUPPORTED_MESSAGE_PACTFLOW : NOT_SUPPORTED_MESSAGE_PACT_BROKER) + end + end + + def success? + if deleted_resource.success? + true + elsif deleted_resource.response.status == 404 && !error_when_not_found + true + else + false + end + end + + def result_message + if deleted_resource.success? + green("Successfully deleted branch #{branch_name} of pacticipant #{pacticipant_name}") + elsif deleted_resource.response.status == 404 + if error_when_not_found + red("Could not delete branch #{branch_name} of pacticipant #{pacticipant_name} as it was not found") + else + green("Branch #{branch_name} of pacticipant #{pacticipant_name} not found") + end + else + red(deleted_resource.response.raw_body) + end + end + end + end + end +end diff --git a/lib/pact_broker/client/cli/branch_commands.rb b/lib/pact_broker/client/cli/branch_commands.rb new file mode 100644 index 0000000..e10b4fb --- /dev/null +++ b/lib/pact_broker/client/cli/branch_commands.rb @@ -0,0 +1,39 @@ +module PactBroker + module Client + module CLI + module BranchCommands + def self.included(thor) + thor.class_eval do + method_option :pacticipant, required: true, aliases: "-a", desc: "The name of the pacticipant that the branch belongs to." + method_option :branch, required: true, desc: "The pacticipant branch name." + method_option :error_when_not_found, type: :boolean, default: true, desc: "Raise an error if the branch that is to be deleted is not found." + shared_authentication_options + + desc "delete-branch", "Deletes a pacticipant branch." + def delete_branch + require "pact_broker/client/branches/delete_branch" + + validate_credentials + params = { + pacticipant: options.pacticipant, + branch: options.branch, + error_when_not_found: options.error_when_not_found + } + + result = PactBroker::Client::Branches::DeleteBranch.call(params, {}, pact_broker_client_options) + $stdout.puts result.message + exit(1) unless result.success + end + + no_commands do + def validate_delete_branch_params + raise ::Thor::RequiredArgumentMissingError, "Pacticipant name cannot be blank" if options.pacticipant.strip.size == 0 + raise ::Thor::RequiredArgumentMissingError, "Pacticipant branch name cannot be blank" if options.branch.strip.size == 0 + end + end + end + end + end + end + end +end diff --git a/lib/pact_broker/client/cli/broker.rb b/lib/pact_broker/client/cli/broker.rb index 827c4fc..962a784 100644 --- a/lib/pact_broker/client/cli/broker.rb +++ b/lib/pact_broker/client/cli/broker.rb @@ -8,7 +8,7 @@ require "pact_broker/client/cli/version_commands" require "pact_broker/client/cli/webhook_commands" require "pact_broker/client/cli/matrix_commands" - +require "pact_broker/client/cli/branch_commands" module PactBroker module Client module CLI @@ -19,6 +19,7 @@ class Broker < CustomThor include PactBroker::Client::CLI::MatrixCommands include PactBroker::Client::CLI::PacticipantCommands include PactBroker::Client::CLI::VersionCommands + include PactBroker::Client::CLI::BranchCommands include PactBroker::Client::CLI::WebhookCommands ignored_and_hidden_potential_options_from_environment_variables diff --git a/spec/lib/pact_broker/client/branches/delete_branch_spec.rb b/spec/lib/pact_broker/client/branches/delete_branch_spec.rb new file mode 100644 index 0000000..5fe3953 --- /dev/null +++ b/spec/lib/pact_broker/client/branches/delete_branch_spec.rb @@ -0,0 +1,90 @@ +require "pact_broker/client/branches/delete_branch" + +module PactBroker + module Client + module Branches + describe DeleteBranch 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) + end + + let(:params) do + { + pacticipant: "Foo", + branch: "main", + error_when_not_found: error_when_not_found + } + end + let(:options) do + { + verbose: verbose + } + end + let(:error_when_not_found) { true } + let(:pact_broker_base_url) { "http://example.org" } + let(:pact_broker_client_options) { { pact_broker_base_url: pact_broker_base_url } } + let(:response_headers) { { "Content-Type" => "application/hal+json"} } + let(:verbose) { false } + + before do + stub_request(:get, "http://example.org/").to_return(status: 200, body: index_response_body, headers: response_headers) + stub_request(:delete, "http://example.org/pacticipants/Foo/branches/main").to_return(status: delete_response_status, body: delete_response_body, headers: response_headers) + end + let(:delete_response_status) { 200 } + + let(:index_response_body) do + { + "_links" => { + "pb:pacticipant-branch" => { + "href" => "http://example.org/pacticipants/{pacticipant}/branches/{branch}" + } + } + }.to_json + end + + let(:delete_response_body) do + { "some" => "error message" }.to_json + end + + subject { DeleteBranch.call(params, options, pact_broker_client_options) } + + context "when the branch is deleted" do + it "returns a success result" do + expect(subject.success).to be true + expect(subject.message).to include "Successfully deleted branch main of pacticipant Foo" + end + end + + context "when there is a non-404 error" do + let(:delete_response_status) { 403 } + + it "returns an error result with the response body" do + expect(subject.success).to be false + expect(subject.message).to include "error message" + end + end + + context "when the branch is not found" do + let(:delete_response_status) { 404 } + + context "when error_when_not_found is true" do + it "returns an error" do + expect(subject.success).to be false + expect(subject.message).to include "Could not delete branch main of pacticipant Foo as it was not found" + end + end + + context "when error_when_not_found is false" do + let(:error_when_not_found) { false } + + it "return a success" do + expect(subject.success).to be true + expect(subject.message).to include "Branch main of pacticipant Foo not found" + end + end + end + end + end + end +end diff --git a/spec/pacts/pact_broker_client-pact_broker.json b/spec/pacts/pact_broker_client-pact_broker.json index f569dca..768bc4f 100644 --- a/spec/pacts/pact_broker_client-pact_broker.json +++ b/spec/pacts/pact_broker_client-pact_broker.json @@ -6,6 +6,49 @@ "name": "Pact Broker" }, "interactions": [ + { + "description": "a request for the index resource", + "providerState": "the pb:pacticipant-branch 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": { + "pb:pacticipant-branch": { + "href": "http://localhost:1234/HAL-REL-PLACEHOLDER-PB-PACTICIPANT-BRANCH-{pacticipant}-{branch}" + } + } + }, + "matchingRules": { + "$.body._links.pb:pacticipant-branch.href": { + "match": "regex", + "regex": "http:\\/\\/.*{pacticipant}.*{branch}" + } + } + } + }, + { + "description": "a request to delete a pacticipant branch", + "providerState": "a branch named main exists for pacticipant Foo", + "request": { + "method": "DELETE", + "path": "/HAL-REL-PLACEHOLDER-PB-PACTICIPANT-BRANCH-Foo-main" + }, + "response": { + "status": 204, + "headers": { + } + } + }, { "description": "a request to list the latest pacts", "providerState": "a pact between Condor and the Pricing Service exists", diff --git a/spec/service_providers/delete_branch_spec.rb b/spec/service_providers/delete_branch_spec.rb new file mode 100644 index 0000000..d085d00 --- /dev/null +++ b/spec/service_providers/delete_branch_spec.rb @@ -0,0 +1,68 @@ +require "service_providers/pact_helper" +require "pact_broker/client/branches/delete_branch" + +RSpec.describe "delete a branch", pact: true do + include_context "pact broker" + include PactBrokerPactHelperMethods + + let(:params) do + { + pacticipant: "Foo", + branch: "main", + error_when_not_found: true + } + end + + let(:options) do + { + verbose: verbose + } + end + + let(:pact_broker_base_url) { pact_broker.mock_service_base_url } + let(:pact_broker_client_options) { { pact_broker_base_url: pact_broker_base_url } } + let(:response_headers) { { "Content-Type" => "application/hal+json"} } + let(:verbose) { false } + + subject { PactBroker::Client::Branches::DeleteBranch.call(params, options, pact_broker_client_options) } + + def mock_index + pact_broker + .given("the pb:pacticipant-branch relation exists in the index resource") + .upon_receiving("a request for the index resource") + .with( + method: "GET", + path: '/', + headers: get_request_headers). + will_respond_with( + status: 200, + headers: pact_broker_response_headers, + body: { + _links: { + :'pb:pacticipant-branch' => { + href: placeholder_url_term("pb:pacticipant-branch", ["pacticipant", "branch"], pact_broker) + } + } + } + ) + end + + def mock_branch_delete_request + pact_broker + .given("a branch named main exists for pacticipant Foo") + .upon_receiving("a request to delete a pacticipant branch") + .with( + method: "DELETE", + path: placeholder_path("pb:pacticipant-branch", ["Foo", "main"]) + ) + .will_respond_with( + status: 204 + ) + end + + it "returns a success result" do + mock_index + mock_branch_delete_request + expect(subject.success).to be true + end +end diff --git a/spec/service_providers/pact_helper.rb b/spec/service_providers/pact_helper.rb index eb3d710..587e136 100644 --- a/spec/service_providers/pact_helper.rb +++ b/spec/service_providers/pact_helper.rb @@ -25,6 +25,7 @@ module PactBrokerPactHelperMethods + # Use this for the path in the Pact request expectation. # @param [String] relation eg "pb:pacticipant" # @param [Array] params eg ["Foo"] def placeholder_path(relation, params = [])