Skip to content

Commit

Permalink
feat(pacts for verification): allow a fallback tag to be specified
Browse files Browse the repository at this point in the history
  • Loading branch information
bethesque committed Feb 6, 2020
1 parent 04c41dd commit 113180c
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,19 @@ class VerifiablePactsJSONQuerySchema
optional(:providerVersionTags).maybe(:array?)
optional(:consumerVersionSelectors).each do
schema do
# configure do
# def self.messages
# super.merge(en: { errors: { fallbackTagMustBeForLatest: 'can only be set if latest=true' }})
# end
# end

required(:tag).filled(:str?)
optional(:latest).filled(included_in?: [true, false])
optional(:fallbackTag).filled(:str?)

# rule(fallbackTagMustBeForLatest: [:fallbackTag, :latest]) do | fallback_tag, latest |
# fallback_tag.filled?.then(latest.eql?(true))
# end
end
end
optional(:includePendingStatus).filled(included_in?: [true, false])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class VerifiablePactsQuerySchema
schema do
required(:tag).filled(:str?)
optional(:latest).filled(included_in?: ["true", "false"])
optional(:fallback_tag).filled(:str?)
end
end
optional(:include_pending_status).filled(included_in?: ["true", "false"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class VerifiablePactsQueryDecorator < BaseDecorator
setter: ->(fragment:, represented:, **) {
represented.latest = (fragment == 'true' || fragment == true)
}
property :fallback_tag
end

property :include_pending_status, default: false,
Expand Down
69 changes: 60 additions & 9 deletions lib/pact_broker/pacts/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -351,16 +351,36 @@ def find_for_verification(provider_name, consumer_version_selectors)
selected_pacts = find_pacts_for_which_the_latest_version_is_required(provider_name, consumer_version_selectors) +
find_pacts_for_which_the_latest_version_for_the_tag_is_required(provider_name, consumer_version_selectors) +
find_pacts_for_which_all_versions_for_the_tag_are_required(provider_name, consumer_version_selectors)

selected_pacts = selected_pacts + find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors)

selected_pacts
.group_by(&:pact_version_sha)
.values
.collect do | selected_pacts_for_pact_version_id |
SelectedPact.merge(selected_pacts_for_pact_version_id)
end

end

private

def find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors)
# TODO at the moment, the validation doesn't stop fallback being used with 'all' selectors
selectors_with_fallback_tags = consumer_version_selectors.select(&:fallback_tag?)
selectors_missing_a_pact = selectors_with_fallback_tags.reject do | selector |
selected_pacts.any? do | selected_pact |
selected_pact.latest_for_tag?(selector.tag)
end
end

if selectors_missing_a_pact.any?
find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(provider_name, selectors_missing_a_pact)
else
[]
end
end

def find_pacts_for_which_the_latest_version_is_required(provider_name, consumer_version_selectors)
if consumer_version_selectors.empty?
LatestPactPublications
Expand All @@ -384,26 +404,57 @@ def find_pacts_for_which_the_latest_version_for_the_tag_is_required(provider_nam
if tag_names.any?
LatestTaggedPactPublications
.provider(provider_name)
.order_ignore_case(:consumer_name)
.where(tag_name: tag_names)
.all
.group_by(&:pact_version_id)
.values
.collect do | pact_publications |
selector_tag_names = pact_publications.collect(&:tag_name)
selectors = Selectors.create_for_latest_of_each_tag(selector_tag_names)
last_pact_publication = pact_publications.sort_by(&:consumer_version_order).last
pact_publication = PactPublication.find(id: last_pact_publication.id)
SelectedPact.new(
pact_publication.to_domain,
selectors
)
create_selected_pact(pact_publications)
end
else
[]
end
end

def create_selected_pact(pact_publications)
selector_tag_names = pact_publications.collect(&:tag_name)
selectors = Selectors.create_for_latest_of_each_tag(selector_tag_names)
last_pact_publication = pact_publications.sort_by(&:consumer_version_order).last
pact_publication = PactPublication.find(id: last_pact_publication.id)
SelectedPact.new(
pact_publication.to_domain,
selectors
)
end

def create_fallback_selected_pact(pact_publications, consumer_version_selectors)
selector_tag_names = pact_publications.collect(&:tag_name)
selectors = Selectors.create_for_latest_fallback_of_each_tag(selector_tag_names)
last_pact_publication = pact_publications.sort_by(&:consumer_version_order).last
pact_publication = PactPublication.find(id: last_pact_publication.id)
SelectedPact.new(
pact_publication.to_domain,
selectors
)
end

def find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(provider_name, selectors)
selectors.collect do | selector |
LatestTaggedPactPublications
.provider(provider_name)
.where(tag_name: selector.fallback_tag)
.all
.collect do | latest_tagged_pact_publication |
pact_publication = PactPublication.find(id: latest_tagged_pact_publication.id)
SelectedPact.new(
pact_publication.to_domain,
Selectors.new(selector)
)
end
end.flatten
end


def find_pacts_for_which_all_versions_for_the_tag_are_required(provider_name, consumer_version_selectors)
# The tags for which all versions are specified
tag_names = consumer_version_selectors.tag_names_of_selectors_for_all_pacts
Expand Down
4 changes: 2 additions & 2 deletions lib/pact_broker/pacts/selected_pact.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def overall_latest?
selectors.overall_latest?
end

def latest_for_tag?
selectors.latest_for_tag?
def latest_for_tag? potential_tag = nil
selectors.latest_for_tag?(potential_tag)
end

def consumer_version_order
Expand Down
23 changes: 20 additions & 3 deletions lib/pact_broker/pacts/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ def latest
self[:latest]
end

def fallback_tag= fallback_tag
self[:fallback_tag] = fallback_tag
end

def fallback_tag
self[:fallback_tag]
end

def self.overall_latest
Selector.new(latest: true)
end
Expand All @@ -33,6 +41,10 @@ def self.from_hash hash
Selector.new(hash)
end

def fallback_tag?
!!fallback_tag
end

def tag
self[:tag]
end
Expand All @@ -41,8 +53,13 @@ def overall_latest?
!!(latest? && !tag)
end

def latest_for_tag?
!!(latest && tag)
# Not sure if the fallback_tag logic is needed
def latest_for_tag? potential_tag = nil
if potential_tag
!!(latest && tag == potential_tag)
else
!!(latest && !!tag)
end
end

def all_for_tag?
Expand Down Expand Up @@ -70,7 +87,7 @@ def <=> other
private

def latest?
self[:latest]
!!self[:latest]
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/pact_broker/pacts/selectors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ def overall_latest?
any?(&:overall_latest?)
end

def latest_for_tag?
any?(&:latest_for_tag?)
def latest_for_tag? potential_tag = nil
any? { | selector | selector.latest_for_tag?(potential_tag) }
end

def tag_names_of_selectors_for_all_pacts
Expand Down
6 changes: 6 additions & 0 deletions lib/pact_broker/test/test_data_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ def create_pact_with_hierarchy consumer_name = "Consumer", consumer_version_numb
self
end

def create_pact_with_consumer_version_tag consumer_name, consumer_version_number, consumer_version_tag_name, provider_name
create_pact_with_hierarchy(consumer_name, consumer_version_number, provider_name)
create_consumer_version_tag(consumer_version_tag_name)
self
end

def create_pact_with_verification consumer_name = "Consumer", consumer_version = "1.0.#{model_counter}", provider_name = "Provider", provider_version = "1.0.#{model_counter}"
create_pact_with_hierarchy(consumer_name, consumer_version, provider_name)
create_verification(number: model_counter, provider_version: provider_version)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,35 @@ module Contracts
end
end

context "when the fallback tag is specified" do
context "when latest is specified" do
let(:consumer_version_selectors) do
[{
tag: "feat-x",
fallbackTag: "master",
latest: true
}]
end

it "has no errors" do
expect(subject).to eq({})
end
end

context "when latest is not specified", pending: true do
let(:consumer_version_selectors) do
[{
tag: "feat-x",
fallbackTag: "master"
}]
end

it "has an error" do
expect(subject[:consumerVersionSelectors].first).to include "not allowed"
end
end
end

context "when providerVersionTags is not an array" do
let(:provider_version_tags) { true }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ module Decorators
end
end

context "with a fallback" do
let(:consumer_version_selectors) do
[{ "tag" => "feat-x", "fallbackTag" => "dev", "latest" => true }]
end

it "sets the fallback" do
expect(subject.consumer_version_selectors.first.fallback_tag).to eq "dev"
end
end

it "parses the latest as a boolean" do
expect(subject.consumer_version_selectors.first).to eq PactBroker::Pacts::Selector.new(tag: "dev", latest: true)
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require 'pact_broker/pacts/repository'

module PactBroker
module Pacts
describe Repository do
let(:td) { TestDataBuilder.new }

describe "#find_for_verification" do
def find_by_consumer_version_number(consumer_version_number)
subject.find{ |pact| pact.consumer_version_number == consumer_version_number }
end

def find_by_consumer_name_and_consumer_version_number(consumer_name, consumer_version_number)
subject.find{ |pact| pact.consumer_name == consumer_name && pact.consumer_version_number == consumer_version_number }
end

subject { Repository.new.find_for_verification("Bar", consumer_version_selectors) }

context "when there is a fallback tag specified" do
before do
td.create_pact_with_consumer_version_tag("Foo", "1", "master", "Bar")
.create_pact_with_consumer_version_tag("Foo", "2", "feat-x", "Bar")
end

let(:tag) { "feat-x" }
let(:fallback_tag) { "master" }
let(:selector) { Selector.new(tag: tag, fallback_tag: fallback_tag, latest: true) }
let(:consumer_version_selectors) { Selectors.new(selector) }

context "when a pact exists for the main tag" do
it "returns the pact with the main tag" do
expect(find_by_consumer_version_number("2")).to_not be nil
expect(find_by_consumer_version_number("2").selectors.first).to eq Selector.latest_for_tag(tag)
end

it "does not set the fallback_tag on the selector" do
expect(find_by_consumer_version_number("2").selectors.first.fallback_tag).to be nil
end
end

context "when a pact does not exist for the main tag and pact exists for the fallback tag" do
let(:tag) { "no-existy" }

it "returns the pact with the fallback tag" do
expect(find_by_consumer_version_number("1")).to_not be nil
end

it "sets the fallback_tag on the selector" do
expect(find_by_consumer_version_number("1").selectors.first.fallback_tag).to eq fallback_tag
end

it "sets the tag on the selector" do
expect(find_by_consumer_version_number("1").selectors.first.tag).to eq tag
end

it "sets the latest flag on the selector" do
expect(find_by_consumer_version_number("1").selectors.first.latest).to be true
end
end

context "when a pact does not exist for either tag or fallback_tag" do
let(:tag) { "no-existy" }
let(:fallback_tag) { "also-no-existy" }

it "returns an empty list" do
expect(subject).to be_empty
end
end
end
end
end
end
end

0 comments on commit 113180c

Please sign in to comment.