diff --git a/lib/pact_broker/domain/version.rb b/lib/pact_broker/domain/version.rb index 25c3ea338..a685a6459 100644 --- a/lib/pact_broker/domain/version.rb +++ b/lib/pact_broker/domain/version.rb @@ -141,10 +141,12 @@ def where_branch_name(branch_name) where(id: PactBroker::Versions::BranchVersion.select(:version_id)) else matching_branch_ids = PactBroker::Versions::Branch.select(:id).where(name: branch_name) - matching_branch_version_ids = PactBroker::Versions::BranchVersion - .select(:version_id) + branch_version_ids = PactBroker::Versions::BranchVersion + .select(:version_id, :branch_name) .where(branch_id: matching_branch_ids) - where(id: matching_branch_version_ids) + select_append(:branch_name) + .join(branch_version_ids, { Sequel[first_source_alias][:id] => Sequel[:bv][:version_id] }, { table_alias: :bv}) + end end @@ -152,18 +154,22 @@ def where_branch_head_name(branch_name) if branch_name == true where(id: PactBroker::Versions::BranchHead.select(:version_id)) else - where(id: PactBroker::Versions::BranchHead.select(:version_id).where(branch_name: branch_name)) + branch_heads = PactBroker::Versions::BranchHead.select(:version_id, :branch_name).where(branch_name: branch_name) + select_append(:branch_name) + .join(branch_heads, { Sequel[first_source_alias][:id] => Sequel[:bh][:version_id] }, { table_alias: :bh }) end end def for_main_branches - matching_branch_version_ids = PactBroker::Versions::BranchVersion - .select(:version_id) + branch_version_ids = PactBroker::Versions::BranchVersion + .select(:version_id, :branch_name) .join(:pacticipants, { Sequel[:branch_versions][:pacticipant_id] => Sequel[:pacticipants][:id] }) .join(:branches, { Sequel[:branches][:id] => Sequel[:branch_versions][:branch_id], Sequel[:branches][:name] => Sequel[:pacticipants][:main_branch] }) - where(id: matching_branch_version_ids) + select_append(Sequel[:bv][:branch_name]) + .join(branch_version_ids, { Sequel[first_source_alias][:id] => Sequel[:bv][:version_id] }, table_alias: :bv) + end def latest_for_main_branches @@ -171,11 +177,13 @@ def latest_for_main_branches Sequel[:branch_heads][:pacticipant_id] => Sequel[:pacticipants][:id], Sequel[:branch_heads][:branch_name] => Sequel[:pacticipants][:main_branch] } - matching_branch_version_ids = PactBroker::Versions::BranchHead - .select(:version_id) + branch_head_version_ids = PactBroker::Versions::BranchHead + .select(:version_id, :branch_name) .join(:pacticipants, pacticipants_join) - where(id: matching_branch_version_ids) + select_append(Sequel[:bh][:branch_name]) + .join(branch_head_version_ids, { Sequel[first_source_alias][:id] => Sequel[:bh][:version_id] }, table_alias: :bh) + end def where_number(number) diff --git a/lib/pact_broker/locale/en.yml b/lib/pact_broker/locale/en.yml index 4f141f6e8..f0828122c 100644 --- a/lib/pact_broker/locale/en.yml +++ b/lib/pact_broker/locale/en.yml @@ -87,6 +87,7 @@ en: environment_name_must_be_unique: Another environment with name '%{name}' already exists. cannot_specify_tag_and_environment: Cannot specify both a 'to' tag and an environment. cannot_specify_latest_and_environment: Cannot specify both latest=true and an environment. + cannot_specify_more_than_one_destination_identifier: Cannot specify more than one of tag, environment and mainBranch. environment_with_name_not_found: "Environment with name '%{name}' does not exist" cannot_modify_version_branch: "The branch for a pacticipant version cannot be changed once set (currently '%{old_branch}', proposed value '%{new_branch}'). Your pact publication/verification publication configurations may be in conflict with each other if you are seeing this error. If the current branch value is incorrect, you must delete the pacticipant version resource at %{version_url} and publish the pacts/verification results again with a consistent branch name." invalid_content_for_content_type: "The content could not be parsed as %{content_type}" diff --git a/lib/pact_broker/matrix/deployment_status_summary.rb b/lib/pact_broker/matrix/deployment_status_summary.rb index 4b3836bda..b3d9be16c 100644 --- a/lib/pact_broker/matrix/deployment_status_summary.rb +++ b/lib/pact_broker/matrix/deployment_status_summary.rb @@ -62,7 +62,7 @@ def warning_messages def bad_practice_warnings warnings = [] - if no_to_tag_or_environment_specified? + if resolved_selectors.count(&:specified?) == 1 && no_to_tag_or_branch_or_environment_specified? warnings << NoEnvironmentSpecified.new end @@ -91,8 +91,8 @@ def more_than_one_selector_specified? .any? end - def no_to_tag_or_environment_specified? - !(query_results.options[:tag] || query_results.options[:environment_name]) + def no_to_tag_or_branch_or_environment_specified? + !(query_results.options[:tag] || query_results.options[:environment_name] || query_results.options[:main_branch]) end def considered_specified_selectors_that_do_not_exist @@ -170,16 +170,6 @@ def missing_reasons end.flatten end - def selectors_without_a_version_for(integration) - selectors_with_non_existing_versions.select do | selector | - integration.involves_pacticipant_with_name?(selector.pacticipant_name) - end - end - - def selectors_with_non_existing_versions - @selectors_with_non_existing_versions ||= resolved_selectors.select(&:latest_tagged_version_that_does_not_exist?) - end - def missing_specified_version_reasons(selectors) selectors.collect(&:version_does_not_exist_description) end diff --git a/lib/pact_broker/matrix/parse_query.rb b/lib/pact_broker/matrix/parse_query.rb index d609b5cc2..e2a3a726b 100644 --- a/lib/pact_broker/matrix/parse_query.rb +++ b/lib/pact_broker/matrix/parse_query.rb @@ -48,6 +48,10 @@ def self.call query options[:environment_name] = params["environment"] end + if params.key?("mainBranch") && params["mainBranch"] != "" + options[:main_branch] = params["mainBranch"] == "true" + end + if params["ignore"].is_a?(Array) options[:ignore_selectors] = params["ignore"].collect{ |i| parse_selector(i) } else @@ -65,6 +69,7 @@ def self.parse_selector(i) p.branch = i["branch"] if i["branch"] && i["branch"] != "" p.tag = i["tag"] if i["tag"] && i["tag"] != "" p.environment_name = i["environment"] if i["environment"] && i["environment"] != "" + p.main_branch = true if i["mainBranch"] && i["mainBranch"] == "true" p end # rubocop: enable Metrics/CyclomaticComplexity diff --git a/lib/pact_broker/matrix/repository.rb b/lib/pact_broker/matrix/repository.rb index b35a4dc29..a58bee761 100644 --- a/lib/pact_broker/matrix/repository.rb +++ b/lib/pact_broker/matrix/repository.rb @@ -262,10 +262,9 @@ def build_resolved_selectors(pacticipant, versions, unresolved_selector, selecto end # The user has specified --to TAG or --to-environment ENVIRONMENT in the CLI - # (or nothing, which to defaults to "with the latest version of the other integrated applications") - # The branch isn't implemented in the CLI yet (March 2022), but the API supports it. + # (or nothing, which to defaults to latest=true - "with the latest version of the other integrated applications") def infer_selectors_for_integrations?(options) - options[:latest] || options[:tag] || options[:branch] || options[:environment_name] + options[:latest] || options[:tag] || options[:branch] || options[:environment_name] || options[:main_branch] end # When only one selector is specified, (eg. checking to see if Foo version 2 can be deployed to prod), @@ -282,6 +281,7 @@ def build_inferred_selectors(inferred_pacticipant_names, resolved_ignore_selecto selector = UnresolvedSelector.new(pacticipant_name: pacticipant_name) selector.tag = options[:tag] if options[:tag] selector.branch = options[:branch] if options[:branch] + selector.main_branch = options[:main_branch] if options[:main_branch] selector.latest = options[:latest] if options[:latest] selector.environment_name = options[:environment_name] if options[:environment_name] selector diff --git a/lib/pact_broker/matrix/resolved_selector.rb b/lib/pact_broker/matrix/resolved_selector.rb index df435d9a1..537707afb 100644 --- a/lib/pact_broker/matrix/resolved_selector.rb +++ b/lib/pact_broker/matrix/resolved_selector.rb @@ -45,7 +45,8 @@ def self.for_pacticipant_and_version(pacticipant, version, original_selector, ty pacticipant_version_number: version.number, latest: original_selector[:latest], tag: original_selector[:tag], - branch: original_selector[:branch], + branch: original_selector[:branch] || (original_selector[:main_branch] ? version&.values[:branch_name] : nil), + main_branch: original_selector[:main_branch], environment_name: original_selector[:environment_name], type: type, ignore: ignore, @@ -64,6 +65,7 @@ def self.for_pacticipant_and_non_existing_version(pacticipant, original_selector latest: original_selector[:latest], tag: original_selector[:tag], branch: original_selector[:branch], + main_branch: original_selector[:main_branch], environment_name: original_selector[:environment_name], type: type, ignore: ignore, @@ -104,6 +106,11 @@ def branch self[:branch] end + # @return [Boolean] + def main_branch? + self[:main_branch] + end + def environment_name self[:environment_name] end @@ -117,7 +124,7 @@ def most_specific_criterion end def only_pacticipant_name_specified? - !!pacticipant_name && !branch && !tag && !latest? && !pacticipant_version_number + !!pacticipant_name && !branch && !tag && !latest? && !pacticipant_version_number && !main_branch? end def latest_tagged? @@ -128,6 +135,10 @@ def latest_from_branch? latest? && branch end + def latest_from_main_branch? + latest? && main_branch? + end + def pacticipant_or_version_does_not_exist? pacticipant_does_not_exist? || version_does_not_exist? end @@ -140,10 +151,6 @@ def version_does_not_exist? !version_exists? end - def latest_tagged_version_that_does_not_exist? - version_does_not_exist? && latest_tagged? - end - def specified_version_that_does_not_exist? specified? && version_does_not_exist? end @@ -175,16 +182,25 @@ def consider? !ignore? end - # rubocop: disable Metrics/CyclomaticComplexity + # rubocop: disable Metrics/CyclomaticComplexity, Metrics/MethodLength def description if latest_tagged? && pacticipant_version_number "the latest version of #{pacticipant_name} with tag #{tag} (#{pacticipant_version_number})" elsif latest_tagged? "the latest version of #{pacticipant_name} with tag #{tag} (no such version exists)" + elsif main_branch? && pacticipant_version_number.nil? + "a version of #{pacticipant_name} from the main branch (no such version exists)" + elsif latest_from_main_branch? && pacticipant_version_number.nil? + "the latest version of #{pacticipant_name} from the main branch (no such verison exists)" elsif latest_from_branch? && pacticipant_version_number "the latest version of #{pacticipant_name} from branch #{branch} (#{pacticipant_version_number})" elsif latest_from_branch? "the latest version of #{pacticipant_name} from branch #{branch} (no such version exists)" + elsif branch && pacticipant_version_number + prefix = one_of_many? ? "one of the versions " : "the version " + prefix + "of #{pacticipant_name} from branch #{branch} (#{pacticipant_version_number})" + elsif branch + "a version of #{pacticipant_name} from branch #{branch} (no such version exists)" elsif latest? && pacticipant_version_number "the latest version of #{pacticipant_name} (#{pacticipant_version_number})" elsif latest? @@ -208,7 +224,7 @@ def description "any version of #{pacticipant_name}" end end - # rubocop: enable Metrics/CyclomaticComplexity + # rubocop: enable Metrics/CyclomaticComplexity, Metrics/MethodLength def version_does_not_exist_description if version_does_not_exist? @@ -216,6 +232,8 @@ def version_does_not_exist_description "No version with tag #{tag} exists for #{pacticipant_name}" elsif branch "No version of #{pacticipant_name} from branch #{branch} exists" + elsif main_branch? + "No version of #{pacticipant_name} from the main branch exists" elsif environment_name "No version of #{pacticipant_name} is currently recorded as deployed or released in environment #{environment_name}" elsif pacticipant_version_number diff --git a/lib/pact_broker/matrix/service.rb b/lib/pact_broker/matrix/service.rb index 83150f114..67e5f602c 100644 --- a/lib/pact_broker/matrix/service.rb +++ b/lib/pact_broker/matrix/service.rb @@ -87,8 +87,10 @@ def validate_selectors selectors, options = {} error_messages << "Please provide 1 or more version selectors." end - if options[:tag]&.not_blank? && options[:environment_name]&.not_blank? - error_messages << message("errors.validation.cannot_specify_tag_and_environment") + destination_identifiers = [options[:tag], options[:environment_name], options[:main_branch]&.to_s].compact + + if destination_identifiers.size > 1 + error_messages << message("errors.validation.cannot_specify_more_than_one_destination_identifier") end if options[:latest] && options[:environment_name]&.not_blank? diff --git a/lib/pact_broker/matrix/unresolved_selector.rb b/lib/pact_broker/matrix/unresolved_selector.rb index 1a9ee5427..9d260a560 100644 --- a/lib/pact_broker/matrix/unresolved_selector.rb +++ b/lib/pact_broker/matrix/unresolved_selector.rb @@ -11,7 +11,7 @@ def initialize(params = {}) # TODO rename branch to branch_name def self.from_hash(hash) - new(hash.symbolize_keys.snakecase_keys.slice(:pacticipant_name, :pacticipant_version_number, :latest, :tag, :branch, :environment_name)) + new(hash.symbolize_keys.snakecase_keys.slice(:pacticipant_name, :pacticipant_version_number, :latest, :tag, :branch, :environment_name, :main_branch)) end def pacticipant_name @@ -42,6 +42,11 @@ def branch self[:branch] end + # @return [Boolean] + def main_branch + self[:main_branch] + end + def environment_name self[:environment_name] end @@ -58,6 +63,11 @@ def branch= branch self[:branch] = branch end + # @param [Boolean] main_branch + def main_branch= main_branch + self[:main_branch] = main_branch + end + def environment_name= environment_name self[:environment_name] = environment_name end @@ -79,9 +89,11 @@ def max_age self[:max_age] end + # rubocop: disable Metrics/CyclomaticComplexity def all_for_pacticipant? - !!pacticipant_name && !pacticipant_version_number && !tag && !branch && !latest && !environment_name && !max_age + !!pacticipant_name && !pacticipant_version_number && !tag && !branch && !latest && !environment_name && !max_age && !main_branch end + # rubocop: enable Metrics/CyclomaticComplexity def latest_for_pacticipant_and_tag? !!(pacticipant_name && tag && latest) diff --git a/lib/pact_broker/test/http_test_data_builder.rb b/lib/pact_broker/test/http_test_data_builder.rb index 0cb78c634..10b96e7ad 100644 --- a/lib/pact_broker/test/http_test_data_builder.rb +++ b/lib/pact_broker/test/http_test_data_builder.rb @@ -323,6 +323,20 @@ def can_i_deploy(pacticipant:, version:, to: nil, to_environment: nil) self end + def can_i_merge(pacticipant:, version:) + can_i_merge_response = client.get("matrix", { q: [pacticipant: pacticipant, version: version], latestby: "cvp", mainBranch: true, latest: true }.compact ).tap { |response| check_for_error(response) } + can = !!(can_i_merge_response.body["summary"] || {})["deployable"] + puts "can-i-merge #{pacticipant} version #{version}: #{can ? 'yes' : 'no'}" + summary = can_i_merge_response.body["summary"] + verification_result_urls = (can_i_merge_response.body["matrix"] || []).collect do | row | + row.dig("verificationResult", "_links", "self", "href") + end.compact + summary.merge!("verification_result_urls" => verification_result_urls) + puts summary.to_yaml + separate + self + end + def delete_integration(consumer:, provider:) puts "Deleting all data for the integration between #{consumer} and #{provider}" client.delete("integrations/provider/#{encode(provider)}/consumer/#{encode(consumer)}").tap { |response| check_for_error(response) } diff --git a/lib/pact_broker/ui/views/matrix/show.haml b/lib/pact_broker/ui/views/matrix/show.haml index 9668751fe..4590be983 100644 --- a/lib/pact_broker/ui/views/matrix/show.haml +++ b/lib/pact_broker/ui/views/matrix/show.haml @@ -70,12 +70,14 @@ %input{name: 'q[]latest', value: 'true', hidden: true, class: 'latest-flag'} - - if options.latest || options.tag + %input{name: 'latest', value: options.latest.to_s, hidden: true } + %input{name: 'mainBranch', value: options.mainBranch.to_s, hidden: true} + + - if options.tag .selector %label{for: 'to'} = options.latest ? 'To (tag)' : 'With all (tagged)' %input{name: 'tag', id: 'to', value: options.tag } - %input{name: 'latest', value: options.latest.to_s, hidden: true} - if options.environment_name .selector @@ -83,6 +85,7 @@ To environment %input{name: 'environment', id: 'environment', value: options.environment_name } + %div.top-of-group.form-check %input{type: 'radio', name: "latestby", class: 'form-check-input', value: '', id: 'all_rows', checked: options.all_rows_checked} %label{for: 'all_rows', class: "form-check-label"} diff --git a/script/data/can-i-merge.rb b/script/data/can-i-merge.rb new file mode 100755 index 000000000..5e42d6a21 --- /dev/null +++ b/script/data/can-i-merge.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +begin + + $LOAD_PATH << "#{Dir.pwd}/lib" + require "pact_broker/test/http_test_data_builder" + base_url = ENV["PACT_BROKER_BASE_URL"] || "http://localhost:9292" + + td = PactBroker::Test::HttpTestDataBuilder.new(base_url) + td.delete_pacticipant("Foo") + .delete_pacticipant("Bar") + .create_pacticipant("Foo", main_branch: "main") + .create_pacticipant("Bar", main_branch: "main") + .publish_pact(consumer: "Foo", consumer_version: "1", provider: "Bar", content_id: "111", branch: "feat/x") + .get_pacts_for_verification(provider_version_branch: "main") + .verify_pact(provider_version_branch: "main", provider_version: "1", success: false) + .verify_pact(provider_version_branch: "main", provider_version: "2", success: false) + .can_i_merge(pacticipant: "Foo", version: "1") + + +rescue StandardError => e + puts "#{e.class} #{e.message}" + puts e.backtrace + exit 1 +end diff --git a/spec/fixtures/approvals/matrix_integration_spec.approved.json b/spec/fixtures/approvals/matrix_integration_spec.approved.json index 9d6eebf2e..ca281bb11 100644 --- a/spec/fixtures/approvals/matrix_integration_spec.approved.json +++ b/spec/fixtures/approvals/matrix_integration_spec.approved.json @@ -109,7 +109,6 @@ "PactBroker::Matrix::Service find when two applications have pacts with each other (nureva use case) when both application versions are specified explictly does not allow the two apps to be deployed together": { "deployable": null, "reasons": [ - "PactBroker::Matrix::NoEnvironmentSpecified", "PactBroker::Matrix::PactNotEverVerifiedByProvider" ] }, @@ -151,7 +150,6 @@ "PactBroker::Matrix::Service find specifying a provider which has multiple prod versions of one consumer (explicit) and a single version of another (inferred) without inferred selectors is deployable": { "deployable": true, "reasons": [ - "PactBroker::Matrix::NoEnvironmentSpecified", "PactBroker::Matrix::SelectorWithoutPacticipantVersionNumberSpecified", "PactBroker::Matrix::Successful" ] @@ -159,7 +157,6 @@ "PactBroker::Matrix::Service find when there is a consumer with two providers, and only one of them has a verification, and the consumer and the verified provider are explicitly specified allows the consumer to be deployed": { "deployable": true, "reasons": [ - "PactBroker::Matrix::NoEnvironmentSpecified", "PactBroker::Matrix::SelectorWithoutPacticipantVersionNumberSpecified", "PactBroker::Matrix::Successful" ] diff --git a/spec/lib/pact_broker/domain/version_spec.rb b/spec/lib/pact_broker/domain/version_spec.rb index 8c306d7a5..9ce625f72 100644 --- a/spec/lib/pact_broker/domain/version_spec.rb +++ b/spec/lib/pact_broker/domain/version_spec.rb @@ -158,19 +158,23 @@ def version_numbers end end - context "when selecting all versions with a branch" do + context "when selecting all versions that are the latest from a branch" do before do td.create_consumer("Foo") + .create_consumer_version("0", branch: "main") .create_consumer_version("1", branch: "main") + .create_consumer_version("1", branch: "feat/x") .create_consumer_version("2") .create_consumer("Bar") .create_consumer_version("3", branch: "main") + .create_consumer_version("4", branch: "main") end let(:selector) { PactBroker::DB::Clean::Selector.new(branch: true, latest: true) } - it "selects the consumer versions that are the latest for their branches" do - expect(version_numbers).to eq %w{1 3} + it "selects the consumer versions that are the latest for their branches, but does not specify which branch they belong to, as it might be multiple, and we don't want a version row for each branch" do + expect(version_numbers).to eq %w{1 4} + expect(subject.collect{ |v| v.values[:branch_name] }).to eq [nil, nil] end end @@ -188,8 +192,10 @@ def version_numbers let(:selector) { PactBroker::DB::Clean::Selector.new(main_branch: true, latest: true) } - it "selects the consumer versions that are the latest for their branches" do + it "selects the versions that are the latest for their branches" do expect(version_numbers).to eq %w{2 5} + expect(subject.find{ |v| v.pacticipant.name == "Bar" }.values[:branch_name]).to eq "develop" + expect(subject.find{ |v| v.pacticipant.name == "Foo" }.values[:branch_name]).to eq "main" end end @@ -207,8 +213,10 @@ def version_numbers let(:selector) { PactBroker::DB::Clean::Selector.new(main_branch: true) } - it "selects the consumer versions that are the latest for their branches" do + it "selects the versions for the main branches" do expect(version_numbers).to eq %w{1 2 4 5} + expect(subject.select{ |v| v.pacticipant.name == "Bar" }.collect{ |v| v.values[:branch_name] }.uniq).to eq ["develop"] + expect(subject.select{ |v| v.pacticipant.name == "Foo" }.collect{ |v| v.values[:branch_name] }.uniq).to eq ["main"] end end @@ -333,6 +341,7 @@ def version_numbers it "returns the versions with the matching branch" do expect(version_numbers).to eq %w{1 3 10} + expect(subject.first.values[:branch_name]).to eq "main" end end @@ -350,6 +359,7 @@ def version_numbers it "returns the latest versions for each matching branch" do expect(version_numbers).to eq %w{3 10} + expect(subject.first.values[:branch_name]).to eq "main" end end end diff --git a/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb b/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb index 6c499683f..9a94139bc 100644 --- a/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb +++ b/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb @@ -248,6 +248,7 @@ module Matrix latest: nil, tag: nil, branch: nil, + main_branch: nil, environment_name: nil, type: :inferred, ignore: false, @@ -289,10 +290,52 @@ module Matrix its(:reasons) { is_expected.to include SelectorWithoutPacticipantVersionNumberSpecified.new } end - context "when there is no to tag or environment specified" do + context "when there is no to tag or environment specified and there was only one specified selector" do + let(:resolved_selectors) do + [ + ResolvedSelector.new( + pacticipant_id: foo.id, + pacticipant_name: foo.name, + pacticipant_version_number: foo_version.number, + pacticipant_version_id: foo_version.id, + type: :specified + ), + ResolvedSelector.new( + pacticipant_id: bar.id, + pacticipant_name: bar.name, + pacticipant_version_number: bar_version.number, + pacticipant_version_id: bar_version.id, + type: :inferred + ) + ] + end + let(:options) { { } } its(:reasons) { is_expected.to include NoEnvironmentSpecified.new } + + context "when there were multiple selectors specified" do + let(:resolved_selectors) do + [ + ResolvedSelector.new( + pacticipant_id: foo.id, + pacticipant_name: foo.name, + pacticipant_version_number: foo_version.number, + pacticipant_version_id: foo_version.id, + type: :specified + ), + ResolvedSelector.new( + pacticipant_id: bar.id, + pacticipant_name: bar.name, + pacticipant_version_number: bar_version.number, + pacticipant_version_id: bar_version.id, + type: :specified + ) + ] + end + + its(:reasons) { is_expected.to_not include NoEnvironmentSpecified.new } + end end end end diff --git a/spec/lib/pact_broker/matrix/repository_main_branch_spec.rb b/spec/lib/pact_broker/matrix/repository_main_branch_spec.rb new file mode 100644 index 000000000..cd8e373d0 --- /dev/null +++ b/spec/lib/pact_broker/matrix/repository_main_branch_spec.rb @@ -0,0 +1,116 @@ +require "pact_broker/matrix/repository" +require "pact_broker/matrix/unresolved_selector" + +module PactBroker + module Matrix + describe Repository do + def build_selectors(hash) + hash.collect do | key, value | + UnresolvedSelector.new(pacticipant_name: key, pacticipant_version_number: value) + end + end + + def shorten_row row + "#{row.consumer_name}#{row.consumer_version_number} #{row.provider_name}#{row.provider_version_number || '?'}" + end + + def shorten_rows rows + rows.collect{ |r| shorten_row(r) } + end + + describe "find" do + describe "deploying a consumer with the main branches of the provider" do + before do + td.create_pacticipant("Bar", main_branch: "develop") + .publish_pact(consumer_name: "Foo", consumer_version_number: "1", provider_name: "Bar") + .create_verification(provider_version: "1", branch: "develop") + .create_verification(provider_version: "2", number: 2, branch: "develop") + .create_verification(provider_version: "3", number: 3, branch: "not-develop") + .create_pacticipant("Baz", main_branch: "main") + .publish_pact(consumer_name: "Foo", consumer_version_number: "1", provider_name: "Baz") + .create_verification(provider_version: "10", branch: "main") + .create_verification(provider_version: "11", number: 2, branch: "main") + .create_verification(provider_version: "12", number: 3, branch: "not-main") + end + + subject { shorten_rows(rows) } + + let(:rows) { Repository.new.find(selectors, options) } + + let(:selectors) { build_selectors({ "Foo" => "1" })} + let(:options) { { main_branch: true, latest: true } } + + it "returns the rows between the consumer and the provider's main branches" do + expect(subject.sort).to eq ["Foo1 Bar2", "Foo1 Baz11"] + end + + it "sets the resolved branch name" do + expect(rows.resolved_selectors.find{ |s| s.pacticipant_name == "Bar" }.branch).to eq "develop" + expect(rows.resolved_selectors.find{ |s| s.pacticipant_name == "Baz" }.branch).to eq "main" + end + + context "deploying a consumer with all versions of the provider's main branches (this doesn't even make sense)" do + let(:options) { { main_branch: true } } + + it "returns the rows between the consumer and the latest version of each provider's main branch" do + expect(subject.sort).to eq ["Foo1 Bar1", "Foo1 Bar2", "Foo1 Baz10", "Foo1 Baz11"] + end + + it "sets the resolved branch names" do + expect(rows.resolved_selectors.select{ |s| s.pacticipant_name == "Bar" }.collect(&:branch).uniq).to eq ["develop"] + expect(rows.resolved_selectors.select{ |s| s.pacticipant_name == "Baz" }.collect(&:branch).uniq).to eq ["main"] + end + end + end + + describe "deploying a provider with the main branches of the consumers" do + before do + td.create_pacticipant("Foo", main_branch: "develop") + .publish_pact(consumer_name: "Foo", consumer_version_number: "1", provider_name: "Bar", branch: "develop") + .create_verification(provider_version: "1") + .publish_pact(consumer_name: "Foo", consumer_version_number: "2", provider_name: "Bar", branch: "develop") + .create_verification(provider_version: "1") + .publish_pact(consumer_name: "Foo", consumer_version_number: "3", provider_name: "Bar", branch: "not-develop") + .create_verification(provider_version: "1") + .create_pacticipant("Beep", main_branch: "main") + .publish_pact(consumer_name: "Beep", consumer_version_number: "1", provider_name: "Bar", branch: "main") + .create_verification(provider_version: "1") + .publish_pact(consumer_name: "Beep", consumer_version_number: "2", provider_name: "Bar", branch: "main") + .create_verification(provider_version: "1") + .publish_pact(consumer_name: "Beep", consumer_version_number: "3", provider_name: "Bar", branch: "not-main") + .create_verification(provider_version: "1") + end + + subject { shorten_rows(rows) } + + let(:rows) { Repository.new.find(selectors, options) } + + let(:selectors) { build_selectors({ "Bar" => "1" })} + let(:options) { { main_branch: true, latest: true } } + + it "returns the rows between the provider and the latest version of each consumer's main branche" do + expect(subject.sort).to eq ["Beep2 Bar1", "Foo2 Bar1"] + end + + it "sets the resolved branch name" do + expect(rows.resolved_selectors.find{ |s| s.pacticipant_name == "Beep" }.branch).to eq "main" + expect(rows.resolved_selectors.find{ |s| s.pacticipant_name == "Foo" }.branch).to eq "develop" + end + + context "deploying a provider with all versions of the consumer's main branches (this doesn't even make sense)" do + let(:options) { { main_branch: true } } + + it "returns the rows between the provider and the consumer's main branches" do + expect(subject.sort).to eq ["Beep1 Bar1", "Beep2 Bar1", "Foo1 Bar1", "Foo2 Bar1"] + end + + it "sets the resolved branch names" do + expect(rows.resolved_selectors.select{ |s| s.pacticipant_name == "Beep" }.collect(&:branch).uniq).to eq ["main"] + expect(rows.resolved_selectors.select{ |s| s.pacticipant_name == "Foo" }.collect(&:branch).uniq).to eq ["develop"] + end + end + end + end + end + end +end diff --git a/spec/lib/pact_broker/matrix/resolved_selector_spec.rb b/spec/lib/pact_broker/matrix/resolved_selector_spec.rb index af67fb871..517927711 100644 --- a/spec/lib/pact_broker/matrix/resolved_selector_spec.rb +++ b/spec/lib/pact_broker/matrix/resolved_selector_spec.rb @@ -4,52 +4,192 @@ module PactBroker module Matrix describe ResolvedSelector do describe "#version_does_not_exist_description" do - let(:subject) do - PactBroker::Matrix::ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant, original_selector, :specified, false) - end + context "for an existing version" do + let(:subject) do + PactBroker::Matrix::ResolvedSelector.for_pacticipant_and_version(pacticipant, version, original_selector, :specified, false, one_of_many) + end - let(:original_selector) do - { - pacticipant_name: pacticipant_name, - tag: tag, - branch: branch, - environment_name: environment_name, - pacticipant_version_number: pacticipant_version_number, - } - end + let(:original_selector) do + { + pacticipant_name: pacticipant_name, + tag: tag, + branch: branch, + environment_name: environment_name, + pacticipant_version_number: pacticipant_version_number, + main_branch: main_branch, + latest: latest + } + end - let(:pacticipant) { double("pacticipant", name: pacticipant_name, id: 1)} + let(:pacticipant) { double("pacticipant", name: pacticipant_name, id: 1)} + let(:version) { double("version", number: "123", id: 2, values: version_values)} - let(:pacticipant_name) { "Foo" } - let(:tag) { nil } - let(:branch) { nil } - let(:environment_name) { nil } - let(:pacticipant_version_number) { nil } + let(:pacticipant_name) { "Foo" } + let(:tag) { nil } + let(:branch) { nil } + let(:environment_name) { nil } + let(:main_branch) { nil } + let(:pacticipant_version_number) { nil } + let(:latest) { nil } + let(:one_of_many) { false } + let(:version_values) { {} } - its(:version_does_not_exist_description) { is_expected.to eq "No pacts or verifications have been published for Foo" } + its(:description) { is_expected.to eq "version 123 of Foo" } - context "when it was specified by tag" do - let(:tag) { "dev" } + context "when it was specified by tag" do + let(:tag) { "dev" } - its(:version_does_not_exist_description) { is_expected.to eq "No version with tag dev exists for Foo" } - end + its(:description) { is_expected.to eq "a version of Foo with tag dev (123)" } + end - context "when it was specified by branch" do - let(:branch) { "main" } + context "when it was specified by tag and latest" do + let(:tag) { "dev" } + let(:latest) { true } - its(:version_does_not_exist_description) { is_expected.to eq "No version of Foo from branch main exists" } - end + its(:description) { is_expected.to eq "the latest version of Foo with tag dev (123)" } + end + + context "when it was specified by branch" do + let(:branch) { "main" } + + its(:description) { is_expected.to eq "the version of Foo from branch main (123)" } + + context "when one of many" do + let(:one_of_many) { true } + + its(:description) { is_expected.to eq "one of the versions of Foo from branch main (123)" } + end + end + + context "when it was specified by branch latest" do + let(:branch) { "main" } + let(:latest) { true } + + its(:description) { is_expected.to eq "the latest version of Foo from branch main (123)" } + end - context "when it was specified by environment" do - let(:environment_name) { "test" } + context "when it was specified by environment" do + let(:environment_name) { "test" } - its(:version_does_not_exist_description) { is_expected.to eq "No version of Foo is currently recorded as deployed or released in environment test" } + its(:description) { is_expected.to eq "the version of Foo currently deployed or released to test (123)" } + end + + context "when it was specified by version number" do + let(:pacticipant_version_number) { "123" } + + its(:description) { is_expected.to eq "version 123 of Foo" } + end + + context "when specified by main_branch" do + let(:main_branch) { true } + # the branch_name will be present in the values of the version returned by this selector + let(:version_values) { { branch_name: "develop" } } + + its(:description) { is_expected.to eq "the version of Foo from branch develop (123)" } + + context "when one of many" do + let(:one_of_many) { true } + + its(:description) { is_expected.to eq "one of the versions of Foo from branch develop (123)" } + end + end + + context "when specified by main_branch and latest" do + let(:main_branch) { true } + let(:latest) { true } + let(:version_values) { { branch_name: "develop" } } + + its(:description) { is_expected.to eq "the latest version of Foo from branch develop (123)" } + end end - context "when it was specified by verison number" do - let(:pacticipant_version_number) { "1" } + context "for non existing version" do + let(:subject) do + PactBroker::Matrix::ResolvedSelector.for_pacticipant_and_non_existing_version(pacticipant, original_selector, :specified, false) + end + + let(:original_selector) do + { + pacticipant_name: pacticipant_name, + tag: tag, + branch: branch, + environment_name: environment_name, + pacticipant_version_number: pacticipant_version_number, + main_branch: main_branch, + latest: latest + } + end + + let(:pacticipant) { double("pacticipant", name: pacticipant_name, id: 1)} + + let(:pacticipant_name) { "Foo" } + let(:tag) { nil } + let(:branch) { nil } + let(:environment_name) { nil } + let(:main_branch) { nil } + let(:pacticipant_version_number) { nil } + let(:latest) { nil } + + its(:version_does_not_exist_description) { is_expected.to eq "No pacts or verifications have been published for Foo" } + + context "when it was specified by tag" do + let(:tag) { "dev" } + + its(:description) { is_expected.to eq "a version of Foo with tag dev (no such version exists)" } + its(:version_does_not_exist_description) { is_expected.to eq "No version with tag dev exists for Foo" } + end + + context "when it was specified by tag and latest" do + let(:tag) { "dev" } + let(:latest) { true } + + its(:description) { is_expected.to eq "the latest version of Foo with tag dev (no such version exists)" } + its(:version_does_not_exist_description) { is_expected.to eq "No version with tag dev exists for Foo" } + end + + context "when it was specified by branch" do + let(:branch) { "main" } + + its(:description) { is_expected.to eq "a version of Foo from branch main (no such version exists)" } + its(:version_does_not_exist_description) { is_expected.to eq "No version of Foo from branch main exists" } + end + + context "when it was specified by branch latest" do + let(:branch) { "main" } + let(:latest) { true } + + its(:description) { is_expected.to eq "the latest version of Foo from branch main (no such version exists)" } + its(:version_does_not_exist_description) { is_expected.to eq "No version of Foo from branch main exists" } + end + + context "when it was specified by environment" do + let(:environment_name) { "test" } + + its(:description) { is_expected.to eq "a version of Foo currently deployed or released to test (no version is currently recorded as deployed/released in this environment)" } + its(:version_does_not_exist_description) { is_expected.to eq "No version of Foo is currently recorded as deployed or released in environment test" } + end + + context "when it was specified by version number" do + let(:pacticipant_version_number) { "1" } + + its(:description) { is_expected.to eq "version 1 of Foo (no such version exists)" } + its(:version_does_not_exist_description) { is_expected.to eq "No pacts or verifications have been published for version 1 of Foo" } + end + + context "when specified by main_branch" do + let(:main_branch) { true } + + its(:description) { is_expected.to eq "a version of Foo from the main branch (no such version exists)" } + its(:version_does_not_exist_description) { is_expected.to eq "No version of Foo from the main branch exists" } + end + + context "when specified by main_branch and latest" do + let(:main_branch) { true } + let(:latest) { true } - its(:version_does_not_exist_description) { is_expected.to eq "No pacts or verifications have been published for version 1 of Foo" } + its(:description) { is_expected.to eq "a version of Foo from the main branch (no such version exists)" } + its(:version_does_not_exist_description) { is_expected.to eq "No version of Foo from the main branch exists" } + end end end end diff --git a/spec/lib/pact_broker/matrix/service_spec.rb b/spec/lib/pact_broker/matrix/service_spec.rb index 260c42b71..277470bea 100644 --- a/spec/lib/pact_broker/matrix/service_spec.rb +++ b/spec/lib/pact_broker/matrix/service_spec.rb @@ -98,7 +98,7 @@ module Matrix end it "returns an error message" do - expect(subject.last).to include "Cannot specify both" + expect(subject.last).to include "Cannot specify more than" end end @@ -117,6 +117,36 @@ module Matrix end end + context "when both main_branch=true and an environment are specified" do + let(:selectors) { [] } + + let(:options) do + { + main_branch: true, + environment_name: "prod" + } + end + + it "returns an error message" do + expect(subject.last).to include "Cannot specify more than" + end + end + + context "when both main_branch=true and a tag are specified" do + let(:selectors) { [] } + + let(:options) do + { + main_branch: true, + tag: "prod" + } + end + + it "returns an error message" do + expect(subject.last).to include "Cannot specify more than" + end + end + context "when the environment does not exist" do let(:selectors) { [] } let(:environment) { nil }