diff --git a/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema.rb b/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema.rb index f8ca2d0cc..e90b433cf 100644 --- a/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema.rb +++ b/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema.rb @@ -15,7 +15,7 @@ class PactsForVerificationJSONQuerySchema using PactBroker::HashRefinements using PactBroker::StringRefinements - BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch] + BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch, :matchingBranch] ENVIRONMENT_KEYS = [:environment, :deployed, :released, :deployedOrReleased] ALL_KEYS = BRANCH_KEYS + ENVIRONMENT_KEYS @@ -36,6 +36,7 @@ class PactsForVerificationJSONQuerySchema optional(:mainBranch).filled(included_in?: [true]) optional(:tag).filled(:str?) optional(:branch).filled(:str?) + optional(:matchingBranch).filled(included_in?: [true]) optional(:latest).filled(included_in?: [true, false]) optional(:fallbackTag).filled(:str?) optional(:fallbackBranch).filled(:str?) @@ -65,7 +66,7 @@ def self.add_cross_field_validation_errors(params, results) # This is a ducking joke. Need to get rid of dry-validation if params[:consumerVersionSelectors].is_a?(Array) errors = params[:consumerVersionSelectors].each_with_index.flat_map do | selector, index | - validate_consumer_version_selector(selector, index) + validate_consumer_version_selector(selector, index, params) end if errors.any? results[:consumerVersionSelectors] ||= [] @@ -82,7 +83,7 @@ def self.not_provided?(value) # when setting the branch, it doesn't make sense to verify all pacts for a branch, # so latest is not required, but cannot be set to false # rubocop: disable Metrics/CyclomaticComplexity, Metrics/MethodLength - def self.validate_consumer_version_selector(selector, index) + def self.validate_consumer_version_selector(selector, index, params) errors = [] if selector[:fallbackTag] && !selector[:latest] @@ -97,17 +98,22 @@ def self.validate_consumer_version_selector(selector, index) not_provided?(selector[:tag]) && not_provided?(selector[:branch]) && not_provided?(selector[:environment]) && + selector[:matchingBranch] != true && selector[:deployed] != true && selector[:released] != true && selector[:deployedOrReleased] != true && selector[:latest] != true - errors << "must specify a value for environment or tag or branch, or specify mainBranch=true, latest=true, deployed=true, released=true or deployedOrReleased=true (at index #{index})" + errors << "must specify a value for environment or tag or branch, or specify mainBranch=true, matchingBranch=true, latest=true, deployed=true, released=true or deployedOrReleased=true (at index #{index})" end if selector[:mainBranch] && selector.slice(*ALL_KEYS - [:consumer, :mainBranch]).any? errors << "cannot specify mainBranch=true with any other criteria apart from consumer (at index #{index})" end + if selector[:matchingBranch] && selector.slice(*ALL_KEYS - [:consumer, :matchingBranch]).any? + errors << "cannot specify matchingBranch=true with any other criteria apart from consumer (at index #{index})" + end + if selector[:tag] && selector[:branch] errors << "cannot specify both a tag and a branch (at index #{index})" end @@ -136,6 +142,10 @@ def self.validate_consumer_version_selector(selector, index) errors << "cannot specify both released=true and deployedOrReleased=true (at index #{index})" end + if selector[:matchingBranch] && not_provided?(params[:providerVersionBranch]) + errors << "the providerVersionBranch must be specified when the selector matchingBranch=true is used (at index #{index})" + end + non_environment_fields = selector.slice(*BRANCH_KEYS).keys.sort environment_related_fields = selector.slice(*ENVIRONMENT_KEYS).keys.sort diff --git a/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb b/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb index 0c9bb9b4c..5aa7796ea 100644 --- a/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb +++ b/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb @@ -21,6 +21,10 @@ class PactsForVerificationQueryDecorator < BaseDecorator represented.branch = fragment represented.latest = true } + property :matching_branch, setter: -> (fragment:, represented:, **other) { + represented.matching_branch = fragment + represented.latest = true + } property :latest, setter: ->(fragment:, represented:, **) { represented.latest = (fragment == "true" || fragment == true) @@ -53,6 +57,9 @@ def from_hash(hash) result = super(hash&.snakecase_keys) result.consumer_version_selectors = split_out_deployed_or_released_selectors(result.consumer_version_selectors) + if result.provider_version_branch + result.consumer_version_selectors = set_branch_for_matching_branch_selectors(result.consumer_version_selectors, result.provider_version_branch) + end if result.consumer_version_selectors && !result.consumer_version_selectors.is_a?(PactBroker::Pacts::Selectors) result.consumer_version_selectors = PactBroker::Pacts::Selectors.new(result.consumer_version_selectors) @@ -62,6 +69,17 @@ def from_hash(hash) private + def set_branch_for_matching_branch_selectors(consumer_version_selectors, provider_version_branch) + consumer_version_selectors.collect do | consumer_version_selector | + if consumer_version_selector[:matching_branch] + consumer_version_selector[:branch] = provider_version_branch + consumer_version_selector + else + consumer_version_selector + end + end + end + def split_out_deployed_or_released_selectors(consumer_version_selectors) consumer_version_selectors.flat_map do | selector | if selector.currently_deployed && selector.currently_supported diff --git a/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown b/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown index 579abc6dc..0d89753da 100644 --- a/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +++ b/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown @@ -30,6 +30,8 @@ Example: This data structure represents the way a user might specify "I want to `consumerVersionSelectors.branch`: the branch name of the consumer versions to get the pacts for. Use of this selector requires that the consumer has configured a branch name when publishing the pacts. +`consumerVersionSelectors.matchingBranch`: if the key is specified, can only be set to `true`. When true, returns the latest pact for any branch with the same name as the specified `providerVersionBranch`. + `consumerVersionSelectors.fallbackBranch`: the name of the branch to fallback to if the specified `branch` does not exist. Use of this property is discouraged as it may allow a pact to pass on a feature branch while breaking backwards compatibility with the main branch, which is generally not desired. It is better to use two separate consumer version selectors, one with the main branch name, and one with the feature branch name, rather than use this property. `consumerVersionSelectors.deployed`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are currently deployed to any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-deployment` CLI. diff --git a/lib/pact_broker/pacts/selector.rb b/lib/pact_broker/pacts/selector.rb index 722eef0bc..2f88154a7 100644 --- a/lib/pact_broker/pacts/selector.rb +++ b/lib/pact_broker/pacts/selector.rb @@ -6,7 +6,7 @@ module Pacts class Selector < Hash using PactBroker::HashRefinements - PROPERTY_NAMES = [:latest, :tag, :branch, :consumer, :consumer_version, :environment_name, :fallback_tag, :fallback_branch, :main_branch, :currently_supported, :currently_deployed] + PROPERTY_NAMES = [:latest, :tag, :branch, :consumer, :consumer_version, :environment_name, :fallback_tag, :fallback_branch, :main_branch, :matching_branch, :currently_supported, :currently_deployed] def initialize(properties = {}) properties.without(*PROPERTY_NAMES).tap { |it| warn("WARN: Unsupported property for #{self.class.name}: #{it.keys.join(", ")} at #{caller[0..3]}") if it.any? } @@ -31,6 +31,8 @@ def resolve_for_environment(consumer_version, environment, target = nil) def type if latest_for_branch? :latest_for_branch + elsif matching_branch? + :matching_branch elsif currently_deployed? :currently_deployed elsif currently_supported? @@ -53,6 +55,10 @@ def main_branch= main_branch self[:main_branch] = main_branch end + def matching_branch= matching_branch + self[:matching_branch] = matching_branch + end + def tag= tag self[:tag] = tag end @@ -233,6 +239,14 @@ def branch self[:branch] end + def matching_branch + self[:matching_branch] + end + + def matching_branch? + !!matching_branch + end + def overall_latest? !!(latest? && !tag && !branch && !main_branch && !currently_deployed && !currently_supported && !environment_name) end diff --git a/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_combinations_spec.rb b/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_combinations_spec.rb index dd6a99328..47d2d33c9 100644 --- a/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_combinations_spec.rb +++ b/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_combinations_spec.rb @@ -6,6 +6,7 @@ module Contracts describe PactsForVerificationJSONQuerySchema do ALL_PROPERTIES = { mainBranch: true, + matchingBranch: true, tag: "tag", branch: "branch", latest: true, @@ -20,6 +21,7 @@ module Contracts VALID_KEY_COMBINATIONS = [ [:mainBranch], + [:matchingBranch], [:tag], [:tag, :latest], [:tag, :latest, :fallbackTag], diff --git a/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_spec.rb b/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_spec.rb index 1665cbe4b..a0d49e7f9 100644 --- a/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_spec.rb +++ b/spec/lib/pact_broker/api/contracts/pacts_for_verification_json_query_schema_spec.rb @@ -25,7 +25,7 @@ module Contracts }] end - subject { PactsForVerificationJSONQuerySchema.(params) } + subject { PactsForVerificationJSONQuerySchema.(params).tap { |it| puts it } } context "when the params are valid" do it "has no errors" do @@ -342,6 +342,16 @@ module Contracts its([:consumerVersionSelectors, 0]) { is_expected.to eq "environment with name 'prod' does not exist at index 0" } end + + context "when matchingBranch is true, but the providerVersionBranch is not set" do + let(:consumer_version_selectors) do + [{ + matchingBranch: true + }] + end + + its([:consumerVersionSelectors, 0]) { is_expected.to include "the providerVersionBranch must be specified"} + end end end end diff --git a/spec/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator_spec.rb b/spec/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator_spec.rb index 49a9e128c..03822f71e 100644 --- a/spec/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator_spec.rb +++ b/spec/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator_spec.rb @@ -125,6 +125,16 @@ module Decorators expect(subject.consumer_version_selectors.last).to eq PactBroker::Pacts::Selector.for_currently_supported end end + + context "when matchingBranch is true" do + let(:consumer_version_selectors) do + [{ "matchingBranch" => true }] + end + + it "sets the branch and latest and matching branch properties" do + expect(subject.consumer_version_selectors).to contain_exactly(have_attributes(branch: "main", matching_branch: true, latest: true)) + end + end end context "when parsing query string params" do