diff --git a/lib/pact_broker/domain/pact.rb b/lib/pact_broker/domain/pact.rb index 5f50a0b62..bcde7e97a 100644 --- a/lib/pact_broker/domain/pact.rb +++ b/lib/pact_broker/domain/pact.rb @@ -94,7 +94,13 @@ def select_pending_provider_version_tags(provider_version_tags) provider_version_tags - tags_with_successful_verifications_from_that_branch - tags_with_previous_successful_verifications_from_other_branches end - def pending? + # @param [String] branch_name the name of the provider branch that will be verifying the pacts + # @return Boolean whether or not the pact is in pending state (ie. the build should not fail if the verification fails) + def pending_for_provider_branch?(branch_name) + pact_version.pending_for_provider_branch?(branch_name) + end + + def pending_for_any_provider_branch? !pact_version.verified_successfully_by_any_provider_version? end diff --git a/lib/pact_broker/pacts/pact_version.rb b/lib/pact_broker/pacts/pact_version.rb index 51b665e58..25135bcae 100644 --- a/lib/pact_broker/pacts/pact_version.rb +++ b/lib/pact_broker/pacts/pact_version.rb @@ -80,6 +80,14 @@ def join_provider_version_tags_for_tag(tag) } join(:tags, tags_join) end + + def join_branch_versions_for_branch_name_to_verifications(branch_name) + branch_versions_join = { + Sequel[:branch_versions][:version_id] => Sequel[:verifications][:provider_version_id], + Sequel[:branch_versions][:branch_name] => branch_name + } + join(:branch_versions, branch_versions_join) + end end def name @@ -106,6 +114,10 @@ def latest_consumer_version_number latest_consumer_version.number end + def pending_for_provider_branch?(branch_name) + !any_successful_verifications_from_provider_branch?(branch_name) && !any_successful_verifications_from_another_branch_from_before_this_branch_created?(branch_name) + end + def select_provider_tags_with_successful_verifications_from_another_branch_from_before_this_branch_created(tags) tags.select do | tag | first_tag_with_name = PactBroker::Domain::Tag.where(pacticipant_id: provider_id, name: tag).order(:created_at).first @@ -157,6 +169,33 @@ def set_interactions_and_messages_counts! ) end end + + def any_successful_verifications_from_provider_branch?(branch_name) + PactVersion.where(Sequel[:pact_versions][:id] => id) + .join_successful_verifications + .join_branch_versions_for_branch_name_to_verifications(branch_name) + .any? + end + + def any_successful_verifications_from_another_branch_from_before_this_branch_created?(branch_name) + branch = PactBroker::Versions::Branch.where(pacticipant_id: provider_id, name: branch_name).single_record + + verifications_join = { + Sequel[:verifications][:pact_version_id] => Sequel[:pact_versions][:id], + Sequel[:verifications][:success] => true + } + query = PactVersion.where(Sequel[:pact_versions][:id] => id) + .join(:verifications, verifications_join) + .join(:branch_versions, Sequel[:branch_versions][:version_id] => Sequel[:verifications][:provider_version_id]) do + Sequel.lit("branch_versions.branch_name != ?", branch_name) + end + + if branch + query = query.where { Sequel[:verifications][:created_at] < branch.created_at } + end + + query.any? + end end end end diff --git a/lib/pact_broker/pacts/service.rb b/lib/pact_broker/pacts/service.rb index 0d8ebdbb5..d8fd04510 100644 --- a/lib/pact_broker/pacts/service.rb +++ b/lib/pact_broker/pacts/service.rb @@ -122,7 +122,7 @@ def find_for_verification(provider_name, provider_version_branch, provider_versi .find_for_verification(provider_name, consumer_version_selectors) .collect do | selected_pact | # Todo move this into the repository - squash_pacts_for_verification(provider_version_tags, selected_pact, options[:include_pending_status]) + squash_pacts_for_verification(provider_version_tags, provider_version_branch, selected_pact, options[:include_pending_status]) end verifiable_wip_pacts = if options[:include_wip_pacts_since] diff --git a/lib/pact_broker/pacts/squash_pacts_for_verification.rb b/lib/pact_broker/pacts/squash_pacts_for_verification.rb index 8dc9c8f84..1dc03f176 100644 --- a/lib/pact_broker/pacts/squash_pacts_for_verification.rb +++ b/lib/pact_broker/pacts/squash_pacts_for_verification.rb @@ -5,17 +5,19 @@ module PactBroker module Pacts module SquashPactsForVerification - def self.call(provider_version_tags, selected_pact, include_pending_status = false) + def self.call(provider_version_tags, provider_version_branch, selected_pact, include_pending_status = false) domain_pact = selected_pact.pact if include_pending_status pending_provider_tags = [] pending = nil - if provider_version_tags.any? + if provider_version_branch + pending = domain_pact.pending_for_provider_branch?(provider_version_branch) + elsif provider_version_tags.any? pending_provider_tags = domain_pact.select_pending_provider_version_tags(provider_version_tags) pending = pending_provider_tags.any? else - pending = domain_pact.pending? + pending = domain_pact.pending_for_any_provider_branch? end non_pending_provider_tags = provider_version_tags - pending_provider_tags VerifiablePact.new( @@ -23,7 +25,8 @@ def self.call(provider_version_tags, selected_pact, include_pending_status = fal selected_pact.selectors, pending, pending_provider_tags, - non_pending_provider_tags + non_pending_provider_tags, + provider_version_branch ) else VerifiablePact.new( diff --git a/lib/pact_broker/test/test_data_builder.rb b/lib/pact_broker/test/test_data_builder.rb index 813377cf3..19a71fec0 100644 --- a/lib/pact_broker/test/test_data_builder.rb +++ b/lib/pact_broker/test/test_data_builder.rb @@ -399,22 +399,13 @@ def create_verification parameters = {} verification = PactBroker::Domain::Verification.new(parameters) pact_version = PactBroker::Pacts::Repository.new.find_pact_version(@consumer, @provider, pact.pact_version_sha) @provider_version = version_repository.find_by_pacticipant_id_and_number_or_create(provider.id, provider_version_number) - PactBroker::Versions::BranchVersionRepository.new.add_branch(@provider_version, branch) if branch + branch_version = PactBroker::Versions::BranchVersionRepository.new.add_branch(@provider_version, branch) if branch - if tag_names.any? - tag_names.each do | tag_name | - PactBroker::Domain::Tag.new(name: tag_name, version: @provider_version, version_order: @provider_version.order).insert_ignore - set_created_at_if_set(parameters[:created_at], :tags, version_id: @provider_version.id, name: tag_name) - end - end + set_created_at_for_provider_tags(parameters, tag_names) @verification = PactBroker::Verifications::Repository.new.create(verification, provider_version_number, pact_version) - set_created_at_if_set(parameters[:created_at], :verifications, id: @verification.id) - set_created_at_if_set(parameters[:created_at], :versions, id: @provider_version.id) - set_created_at_if_set(parameters[:created_at], :latest_verification_id_for_pact_version_and_provider_version, pact_version_id: pact_version_id, provider_version_id: @provider_version.id) - set_created_at_if_set(parameters[:created_at], :pact_version_provider_tag_successful_verifications, { verification_id: @verification.id }, :execution_date) - + set_created_at_for_verification_resources(parameters, pact_version.id, branch, branch_version) self end @@ -674,6 +665,31 @@ def webhook_pacticipant(name, params) [nil, Domain::WebhookPacticipant.new(label: label)] end end + + def set_created_at_for_provider_tags(parameters, tag_names) + if tag_names.any? + tag_names.each do | tag_name | + PactBroker::Domain::Tag.new(name: tag_name, version: @provider_version, version_order: @provider_version.order).insert_ignore + set_created_at_if_set(parameters[:created_at], :tags, version_id: @provider_version.id, name: tag_name) + end + end + end + + def set_created_at_for_verification_resources(parameters, pact_version_id, branch, branch_version) + if branch + set_created_at_if_set(parameters[:created_at], :branch_versions, id: branch_version.id) + + # if the branch has just been created... + if branch_version.branch.branch_versions.size == 1 + set_created_at_if_set(parameters[:created_at], :branches, id: branch_version.branch.id) + end + end + + set_created_at_if_set(parameters[:created_at], :verifications, id: @verification.id) + set_created_at_if_set(parameters[:created_at], :versions, id: @provider_version.id) + set_created_at_if_set(parameters[:created_at], :latest_verification_id_for_pact_version_and_provider_version, pact_version_id: pact_version_id, provider_version_id: @provider_version.id) + set_created_at_if_set(parameters[:created_at], :pact_version_provider_tag_successful_verifications, { verification_id: @verification.id }, :execution_date) + end end end end diff --git a/lib/pact_broker/versions/branch.rb b/lib/pact_broker/versions/branch.rb index 214412130..2ae36c591 100644 --- a/lib/pact_broker/versions/branch.rb +++ b/lib/pact_broker/versions/branch.rb @@ -8,6 +8,7 @@ class Branch < Sequel::Model(:branches) plugin :insert_ignore, identifying_columns: [:name, :pacticipant_id] associate(:many_to_one, :pacticipant, :class => "PactBroker::Domain::Pacticipant", :key => :pacticipant_id, :primary_key => :id) + associate(:one_to_many, :branch_versions, :class => "PactBroker::Versions::BranchVersion", :key => :branch_id, :primary_key => :id) dataset_module do include PactBroker::Repositories::Helpers diff --git a/spec/features/pending_pacts_with_branches_spec.rb b/spec/features/pending_pacts_with_branches_spec.rb new file mode 100644 index 000000000..320550c6a --- /dev/null +++ b/spec/features/pending_pacts_with_branches_spec.rb @@ -0,0 +1,137 @@ +RSpec.describe "the pending lifecycle of a pact (with tags)" do + let(:pact_content_1) { { some: "interactions" }.to_json } + let(:pact_content_2) { { some: "other interactions" }.to_json } + let(:request_headers) { { "CONTENT_TYPE" => "application/json", "HTTP_ACCEPT" => "application/hal+json"} } + let(:failed_verification_results) do + { + providerApplicationVersion: "2", + success: false + }.to_json + end + let(:successful_verification_results) do + { + providerApplicationVersion: "2", + success: true + }.to_json + end + + def publish_pact + put("/pacts/provider/Bar/consumer/Foo/version/1", pact_content_1, request_headers) + end + + def get_pacts_for_verification(provider_version_branch = "main") + post("/pacts/provider/Bar/for-verification", { includePendingStatus: true, providerVersionBranch: provider_version_branch }.to_json, request_headers) + end + + def pact_url_from(pacts_for_verification_response) + JSON.parse(pacts_for_verification_response.body)["_embedded"]["pacts"][0]["_links"]["self"]["href"] + end + + def get_pact(pact_url) + get pact_url, nil, request_headers + end + + def verification_results_url_from(pact_response) + JSON.parse(pact_response.body)["_links"]["pb:publish-verification-results"]["href"] + end + + def publish_verification_results(verification_results_url, results) + put("/pacticipants/Bar/branches/main/versions/#{JSON.parse(results)["providerApplicationVersion"]}", nil, { "CONTENT_TYPE" => "application/json" }) + post(verification_results_url, results, request_headers) + end + + def pending_status_from(pacts_for_verification_response) + JSON.parse(pacts_for_verification_response.body)["_embedded"]["pacts"][0]["verificationProperties"]["pending"] + end + + context "a pact" do + describe "when it is first published" do + it "is pending" do + publish_pact + pacts_for_verification_response = get_pacts_for_verification + expect(pending_status_from(pacts_for_verification_response)).to be true + end + end + + describe "when it is verified unsuccessfully" do + it "is still pending" do + # CONSUMER BUILD + # publish pact + publish_pact + + # PROVIDER BUILD + # fetch pacts to verify + pacts_for_verification_response = get_pacts_for_verification + pact_url = pact_url_from(pacts_for_verification_response) + pact_response = get_pact(pact_url) + + # verify pact... failure... + + # publish failure verification results + verification_results_url = verification_results_url_from(pact_response) + publish_verification_results(verification_results_url, failed_verification_results) + + # ANOTHER PROVIDER BUILD + # get pacts for verification + pacts_for_verification_response = get_pacts_for_verification + # still pending + expect(pending_status_from(pacts_for_verification_response)).to be true + end + end + + describe "when it is verified successfully" do + it "is no longer pending" do + # CONSUMER BUILD + publish_pact + + # PROVIDER BUILD + pacts_for_verification_response = get_pacts_for_verification + + # fetch pact + pact_url = pact_url_from(pacts_for_verification_response) + pact_response = get_pact(pact_url) + + # verify pact... success! + + # publish failure verification results + verification_results_url = verification_results_url_from(pact_response) + publish_verification_results(verification_results_url, successful_verification_results) + + # ANOTHER PROVIDER BUILD 2 + # get pacts for verification + # publish successful verification results + pacts_for_verification_response = get_pacts_for_verification + # not pending any more + expect(pending_status_from(pacts_for_verification_response)).to be false + end + end + + + describe "when it is verified successfully by one branch, and then another branch of the provider is created" do + it "is not pending for the new branch" do + # CONSUMER BUILD + publish_pact + + # PROVIDER BUILD + pacts_for_verification_response = get_pacts_for_verification + + # fetch pact + pact_url = pact_url_from(pacts_for_verification_response) + pact_response = get_pact(pact_url) + + # verify pact... success! + + # publish failure verification results + verification_results_url = verification_results_url_from(pact_response) + publish_verification_results(verification_results_url, successful_verification_results) + + # ANOTHER PROVIDER BUILD 2 from another branch + # get pacts for verification + # publish successful verification results + pacts_for_verification_response = get_pacts_for_verification("feat/foo") + # not pending + expect(pending_status_from(pacts_for_verification_response)).to be false + end + end + end +end diff --git a/spec/lib/pact_broker/pacts/pact_version_spec.rb b/spec/lib/pact_broker/pacts/pact_version_spec.rb index e94dc925f..b8c74dd12 100644 --- a/spec/lib/pact_broker/pacts/pact_version_spec.rb +++ b/spec/lib/pact_broker/pacts/pact_version_spec.rb @@ -280,7 +280,8 @@ module Pacts context "when there is a successful verification from before the first provider version with the specified tag was created" do before do - td.create_pact_with_hierarchy("Foo", "1", "Bar") + td.set_now(Date.today - 7) + .create_pact_with_hierarchy("Foo", "1", "Bar") .create_verification(provider_version: "20", tag_names: ["dev"], success: true) .add_day .create_verification(provider_version: "21", tag_names: ["feat-new-branch"], number: 2, success: false) @@ -293,7 +294,8 @@ module Pacts context "when there is a successful verification from after the first provider version with the specified tag was created" do before do - td.create_pact_with_hierarchy("Foo", "1", "Bar") + td.set_now(Date.today - 7) + .create_pact_with_hierarchy("Foo", "1", "Bar") .create_verification(provider_version: "21", tag_names: ["feat-new-branch"], number: 2, success: false) .add_day .create_verification(provider_version: "20", tag_names: ["dev"], success: true) @@ -306,7 +308,6 @@ module Pacts end describe "#verified_successfully_by_any_provider_version?" do - let(:pact_version) { PactVersion.last } subject { pact_version.verified_successfully_by_any_provider_version? } @@ -338,6 +339,113 @@ module Pacts it { is_expected.to be false } end end + + describe "any_successful_verifications_from_provider_branch?" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "1", branch: "main", success: true) + .create_verification(provider_version: "2", branch: "main", success: false, number: 2) + .create_verification(provider_version: "3", branch: "feat/a", success: false, number: 3) + end + + subject { PactVersion.last.any_successful_verifications_from_provider_branch?(branch_name) } + + let(:branch_name) { "main" } + + context "when there is a successful verification from the specified branch and a failed one" do + it { is_expected.to be true } + end + + context "when there are not any verifications from the specified branch" do + let(:branch_name) { "feat/b" } + + it { is_expected.to be false } + end + + context "when there are only failed verifications from the specified branch" do + let(:branch_name) { "feat/a" } + + it { is_expected.to be false } + end + end + + describe "any_successful_verifications_from_another_branch_from_before_this_branch_created?" do + let(:pact_version) { PactVersion.last } + + subject { pact_version.any_successful_verifications_from_another_branch_from_before_this_branch_created?(branch_name) } + + context "when the provider version branch specified does not exist yet but there are previous successful verifications from another branch" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "20", branch: "dev", success: true) + .create_verification(provider_version: "21", number: 2) + end + + let(:branch_name) { "feat-new-branch" } + + it { is_expected.to be true } + end + + context "when there is a successful verification from before the specified branch was created" do + before do + td.set_now(Date.today - 7) + .create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "20", branch: "dev", success: true) + .add_day + .create_verification(provider_version: "21", branch: "feat-new-branch", number: 2, success: false) + end + + let(:branch_name) { "feat-new-branch" } + + it { is_expected.to be true } + end + + context "when there is a successful verification from after the branch was created" do + before do + td.set_now(Date.today - 7) + .create_pact_with_hierarchy("Foo", "1", "Bar") + .create_verification(provider_version: "21", branch: "feat-new-branch", number: 2, success: false) + .add_day + .create_verification(provider_version: "20", branch: "dev", success: true) + end + + let(:branch_name) { "feat-new-branch" } + + it { is_expected.to be false } + end + end + + describe "pending_for_provider_branch?" do + before do + td.create_pact_with_hierarchy("Foo", "1", "Bar") + end + + let(:pact_version) { PactVersion.last } + + subject { pact_version.pending_for_provider_branch?("main") } + + context "with no successful verification from the specified branch" do + it { is_expected.to be true } + end + + context "with a successful verification from the specified branch" do + before do + td.create_verification(provider_version: "1", branch: "main") + end + + it { is_expected.to be false } + end + + context "when there is a successful verification from before the specified branch was created" do + before do + td.create_verification(provider_version: "20", branch: "other-branch", success: true) + end + + let(:branch_name) { "feat-new-branch" } + + it { is_expected.to be false } + end + end end end end diff --git a/spec/lib/pact_broker/pacts/squash_pacts_for_verification_spec.rb b/spec/lib/pact_broker/pacts/squash_pacts_for_verification_spec.rb index 1d952ab00..f06105095 100644 --- a/spec/lib/pact_broker/pacts/squash_pacts_for_verification_spec.rb +++ b/spec/lib/pact_broker/pacts/squash_pacts_for_verification_spec.rb @@ -11,11 +11,13 @@ module SquashPactsForVerification let(:pact_version_sha_2) { "2" } let(:domain_pact_1) do double("pact1", - pending?: pending_1, - select_pending_provider_version_tags: pending_provider_version_tags + pending_for_any_provider_branch?: pending_1, + select_pending_provider_version_tags: pending_provider_version_tags, + pending_for_provider_branch?: pending_2, ) end let(:pending_1) { false } + let(:pending_2) { false } let(:pending_provider_version_tags) { [] } let(:pact_1) do @@ -25,10 +27,11 @@ module SquashPactsForVerification selectors: double("selectors") ) end + let(:provider_version_branch) { nil } let(:provider_version_tags) { [] } - subject { SquashPactsForVerification.call(provider_version_tags, selected_pact, true) } + subject { SquashPactsForVerification.call(provider_version_tags, provider_version_branch, selected_pact, true) } context "when there are no provider tags" do context "when the pact version is not pending" do @@ -64,6 +67,26 @@ module SquashPactsForVerification its(:non_pending_provider_tags) { is_expected.to eq %w[dev feat-x] } end end + + context "when there is a provider version branch" do + let(:provider_version_branch) { "dev" } + + context "when a pact is pending for the provider branch" do + let(:pending_2) { true } + + its(:pending) { is_expected.to be true } + its(:pending_provider_tags) { is_expected.to eq [] } + its(:non_pending_provider_tags) { is_expected.to eq %w[] } + its(:provider_branch) { is_expected.to eq "dev" } + end + + context "when a pact is not pending for the provider branch" do + its(:pending) { is_expected.to be false } + its(:pending_provider_tags) { is_expected.to eq [] } + its(:non_pending_provider_tags) { is_expected.to eq %w[] } + its(:provider_branch) { is_expected.to eq "dev" } + end + end end end end