Skip to content

Commit

Permalink
feat: add support for matchingBranch property in consumer version sel…
Browse files Browse the repository at this point in the history
…ectors
  • Loading branch information
bethesque committed Oct 12, 2021
1 parent 7bd667e commit 48068d2
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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?)
Expand Down Expand Up @@ -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] ||= []
Expand All @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 15 additions & 1 deletion lib/pact_broker/pacts/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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? }
Expand All @@ -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?
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Contracts
describe PactsForVerificationJSONQuerySchema do
ALL_PROPERTIES = {
mainBranch: true,
matchingBranch: true,
tag: "tag",
branch: "branch",
latest: true,
Expand All @@ -20,6 +21,7 @@ module Contracts

VALID_KEY_COMBINATIONS = [
[:mainBranch],
[:matchingBranch],
[:tag],
[:tag, :latest],
[:tag, :latest, :fallbackTag],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 48068d2

Please sign in to comment.