Skip to content

Commit

Permalink
feat(pacts for verification): update validation for consumer version …
Browse files Browse the repository at this point in the history
…selectors for the fields environment, currentlyDeployed and branch
  • Loading branch information
bethesque committed Mar 4, 2021
1 parent ea8e2bb commit ec7dc43
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 12 deletions.
65 changes: 55 additions & 10 deletions lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
require 'pact_broker/string_refinements'
require 'pact_broker/api/contracts/dry_validation_workarounds'
require 'pact_broker/api/contracts/dry_validation_predicates'
require 'pact_broker/messages'

module PactBroker
module Api
module Contracts
class VerifiablePactsJSONQuerySchema
extend DryValidationWorkarounds
extend PactBroker::Messages

using PactBroker::HashRefinements
using PactBroker::StringRefinements

Expand All @@ -27,8 +30,10 @@ class VerifiablePactsJSONQuerySchema
# end

optional(:tag).filled(:str?)
optional(:branch).filled(:str?)
optional(:latest).filled(included_in?: [true, false])
optional(:fallbackTag).filled(:str?)
optional(:fallbackBranch).filled(:str?)
optional(:consumer).filled(:str?, :not_blank?)
optional(:currentlyDeployed).filled(included_in?: [true])
optional(:environment).filled(:str?)
Expand All @@ -52,16 +57,8 @@ def self.call(params)
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 do | selector, index |
if selector[:fallbackTag] && !selector[:latest]
errors << "fallbackTag can only be set if latest is true (at index #{index})"
end


if not_provided?(selector[:tag]) && selector[:latest] != true
errors << "latest must be true, or a tag must be provided (at index #{index})"
end
errors = params[:consumerVersionSelectors].each_with_index.flat_map do | selector, index |
validate_consumer_version_selector(selector, index)
end
if errors.any?
results[:consumerVersionSelectors] ||= []
Expand All @@ -73,6 +70,54 @@ def self.add_cross_field_validation_errors(params, results)
def self.not_provided?(value)
value.nil? || value.blank?
end

# when setting a tag, latest=true and latest=false are both valid
# 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
def self.validate_consumer_version_selector(selector, index)
errors = []

if selector[:fallbackTag] && !selector[:latest]
errors << "fallbackTag can only be set if latest is true (at index #{index})"
end

if selector[:fallbackBranch] && selector[:latest] == false
errors << "fallbackBranch can only be set if latest is true (at index #{index})"
end

if not_provided?(selector[:tag]) &&
not_provided?(selector[:branch]) &&
not_provided?(selector[:environment]) &&
selector[:currentlyDeployed] != true &&
selector[:latest] != true
errors << "must specify a value for environment or tag, or specify latest=true, or specify currentlyDeployed=true (at index #{index})"
end

if selector[:tag] && selector[:branch]
errors << "cannot specify both a tag and a branch (at index #{index})"
end

if selector[:fallbackBranch] && !selector[:branch]
errors << "a branch must be specified when a fallbackBranch is specified (at index #{index})"
end

if selector[:fallbackTag] && !selector[:tag]
errors << "a tag must be specified when a fallbackTag is specified (at index #{index})"
end

if selector[:branch] && selector[:latest] == false
errors << "cannot specify a branch with latest=false (at index #{index})"
end

non_environment_fields = selector.slice(:latest, :tag, :fallbackTag, :branch, :fallbackBranch).keys
environment_related_fields = selector.slice(:environment, :currentlyDeployed).keys

if (non_environment_fields.any? && environment_related_fields.any?)
errors << "cannot specify the #{pluralize("field", non_environment_fields.count)} #{non_environment_fields.join("/")} with the #{pluralize("field", environment_related_fields.count)} #{environment_related_fields.join("/")} (at index #{index})"
end

errors
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class VerifiablePactsQueryDecorator < BaseDecorator
represented.latest = (fragment == 'true' || fragment == true)
}
property :fallback_tag
property :fallback_branch
property :consumer
property :environment
property :currently_deployed
Expand Down
12 changes: 12 additions & 0 deletions lib/pact_broker/messages.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ def potential_duplicate_pacticipant_message new_name, potential_duplicate_pactic
create_pacticipant_url: pacticipants_url(base_url))
end

def pluralize(word, count)
if count == 1
word
else
if word.end_with?("y")
word.chomp("y") + "ies"
else
word + "s"
end
end
end

private

def pacticipants_url base_url
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require 'pact_broker/api/contracts/verifiable_pacts_json_query_schema'

module PactBroker
module Api
module Contracts
describe VerifiablePactsJSONQuerySchema do
ALL_PROPERTIES = {
tag: "tag",
branch: "branch",
latest: true,
fallbackTag: "fallbackTag",
fallbackBranch: "fallbackBranch",
environment: "environment",
currentlyDeployed: true,
consumer: "consumer"
}

VALID_KEY_COMBINATIONS = [
[:tag],
[:tag, :latest],
[:tag, :latest, :fallbackTag],
[:branch],
[:branch, :latest],
[:branch, :latest, :fallbackBranch],
[:branch, :fallbackBranch],
[:environment],
[:environment, :currentlyDeployed],
[:currentlyDeployed],
]

VALID_KEY_COMBINATIONS.each do | valid_key_combination |
selector = ALL_PROPERTIES.slice(*valid_key_combination)

describe "with #{selector}" do
it "is valid" do
params = { consumerVersionSelectors: [selector] }
expect(VerifiablePactsJSONQuerySchema.(params)).to be_empty
end

extra_keys = ALL_PROPERTIES.keys - valid_key_combination - [:consumer]
extra_keys.each do | extra_key |
selector_with_extra_key = selector.merge(extra_key => ALL_PROPERTIES[extra_key])
expect_to_be_valid = !!VALID_KEY_COMBINATIONS.find{ | valid_key_combination | valid_key_combination.sort == selector_with_extra_key.keys.sort }
params = { consumerVersionSelectors: [selector_with_extra_key] }

describe "with #{selector_with_extra_key}" do
if expect_to_be_valid
it "is valid" do
expect(VerifiablePactsJSONQuerySchema.(params)).to be_empty
end
else
it "is not valid" do
expect(VerifiablePactsJSONQuerySchema.(params)).to_not be_empty
end
end
end

selector_with_consumer = selector.merge(consumer: ALL_PROPERTIES[:consumer])

describe "with #{selector_with_consumer}" do
it "is valid" do
params = { consumerVersionSelectors: [selector_with_consumer] }

expect(VerifiablePactsJSONQuerySchema.(params).empty?).to be true
end
end
end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ module Contracts
it { is_expected.to have_key(:consumerVersionSelectors) }
end

context "when the consumer_version_selector is missing a tag" do
context "when the consumer_version_selector is empty" do
let(:consumer_version_selectors) do
[{}]
end

it "flattens the messages" do
expect(subject[:consumerVersionSelectors].first).to eq "latest must be true, or a tag must be provided (at index 0)"
expect(subject[:consumerVersionSelectors].first).to match /must specify a value.*at index 0/
end
end

Expand Down Expand Up @@ -173,6 +173,149 @@ module Contracts

it { is_expected.to be_empty }
end

context "when currentlyDeployed is specified" do
let(:consumer_version_selectors) do
[{
currentlyDeployed: true
}]
end

it { is_expected.to be_empty }
end

context "when the environment is specified" do
let(:consumer_version_selectors) do
[{
environment: "test"
}]
end

it { is_expected.to be_empty }
end

context "when currentlyDeployed with an environment is specified" do
let(:consumer_version_selectors) do
[{
environment: "feat",
currentlyDeployed: true
}]
end

it { is_expected.to be_empty }
end

context "when currentlyDeployed=false with an environment is specified" do
let(:consumer_version_selectors) do
[{
environment: "feat",
currentlyDeployed: false
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "currentlyDeployed must be one of: true at index 0" }
end

context "when the environment is specified and currentlyDeployed is nil" do
let(:consumer_version_selectors) do
[{
environment: "feat",
currentlyDeployed: nil
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "currentlyDeployed can't be blank at index 0" }
end

context "when currentlyDeployed is nil" do
let(:consumer_version_selectors) do
[{
currentlyDeployed: nil
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "currentlyDeployed can't be blank at index 0" }
end

context "when latest=true and an environment is specified" do
let(:consumer_version_selectors) do
[{
environment: "feat",
latest: true
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "cannot specify the field latest with the field environment (at index 0)" }
end

context "when latest=true, tag and an environment and currentlyDeployed are specified" do
let(:consumer_version_selectors) do
[{
environment: "feat",
latest: true,
tag: "foo",
currentlyDeployed: true
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "cannot specify the fields tag/latest with the fields environment/currentlyDeployed (at index 0)" }
end

context "when a tag and a branch are specified" do
let(:consumer_version_selectors) do
[{
branch: "foo",
tag: "foo",
latest: true
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "cannot specify both a tag and a branch (at index 0)" }
end

context "when a fallbackTag is specified without a tag" do
let(:consumer_version_selectors) do
[{
fallbackTag: "foo",
latest: true,
branch: "foo"
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "a tag must be specified when a fallbackTag is specified (at index 0)" }
end

context "when a fallbackBranch is specified without a branch" do
let(:consumer_version_selectors) do
[{
fallbackBranch: "foo",
tag: "foo"
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "a branch must be specified when a fallbackBranch is specified (at index 0)" }
end

context "when a branch is specified with no latest=true" do
let(:consumer_version_selectors) do
[{
branch: "foo"
}]
end

it { is_expected.to be_empty }
end

context "when a branch is specified with latest=false" do
let(:consumer_version_selectors) do
[{
branch: "foo",
latest: false
}]
end

its([:consumerVersionSelectors, 0]) { is_expected.to eq "cannot specify a branch with latest=false (at index 0)" }
end
end
end
end
Expand Down

0 comments on commit ec7dc43

Please sign in to comment.