Skip to content

Commit

Permalink
feat(pacts for verification): allow consumer to be specified with fal…
Browse files Browse the repository at this point in the history
…lback tags, and overall latest to be specified with or without a consumer
  • Loading branch information
bethesque committed Sep 5, 2020
1 parent a46aa3a commit 2d52d17
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 38 deletions.
@@ -1,5 +1,6 @@
require 'dry-validation'
require 'pact_broker/hash_refinements'
require 'pact_broker/string_refinements'
require 'pact_broker/api/contracts/dry_validation_workarounds'
require 'pact_broker/api/contracts/dry_validation_predicates'

Expand All @@ -9,6 +10,7 @@ module Contracts
class VerifiablePactsJSONQuerySchema
extend DryValidationWorkarounds
using PactBroker::HashRefinements
using PactBroker::StringRefinements

SCHEMA = Dry::Validation.Schema do
configure do
Expand All @@ -24,7 +26,7 @@ class VerifiablePactsJSONQuerySchema
# end
# end

required(:tag).filled(:str?)
optional(:tag).filled(:str?)
optional(:latest).filled(included_in?: [true, false])
optional(:fallbackTag).filled(:str?)
optional(:consumer).filled(:str?, :not_blank?)
Expand Down Expand Up @@ -54,8 +56,9 @@ def self.add_cross_field_validation_errors(params, results)
errors << "fallbackTag can only be set if latest is true (at index #{index})"
end

if selector[:consumer] && selector[:latest]
errors << "specifying a consumer with latest == true is not yet supported (at index #{index})"

if not_provided?(selector[:tag]) && selector[:latest] != true
errors << "latest must be true, or a tag must be provided (at index #{index})"
end
end
if errors.any?
Expand All @@ -64,6 +67,10 @@ def self.add_cross_field_validation_errors(params, results)
end
end
end

def self.not_provided?(value)
value.nil? || value.blank?
end
end
end
end
Expand Down
Expand Up @@ -30,6 +30,7 @@ class VerifiablePactsQuerySchema
def self.call(params)
select_first_message(flatten_indexed_messages(SCHEMA.call(params&.symbolize_keys).messages(full: true)))
end

end
end
end
Expand Down
56 changes: 25 additions & 31 deletions lib/pact_broker/pacts/repository.rb
Expand Up @@ -405,42 +405,37 @@ def find_pacts_for_which_the_latest_version_is_required(provider_name, consumer_
SelectedPact.new(pact_publication.to_domain, Selectors.create_for_overall_latest)
end
else
[]
selectors_for_overall_latest = consumer_version_selectors.select(&:overall_latest?)
selectors_for_overall_latest.flat_map do | selector |
query = scope_for(LatestPactPublications).provider(provider_name)
query = query.consumer(selector.consumer) if selector.consumer
query.collect do | latest_pact_publication |
pact_publication = PactPublication.find(id: latest_pact_publication.id)
resolved_selector = selector.consumer ? Selector.latest_for_consumer(selector.consumer) : Selector.overall_latest
SelectedPact.new(pact_publication.to_domain, Selectors.new(resolved_selector))
end
end
end
end

def find_pacts_for_which_the_latest_version_for_the_tag_is_required(provider_name, consumer_version_selectors)
# The tags for which only the latest version is specified
tag_names = consumer_version_selectors.tag_names_of_selectors_for_latest_pacts
selectors = consumer_version_selectors.select(&:latest_for_tag?)

# TODO make this an efficient query!
# These are not yet de-duped. Should make the behaviour consistent between this and find_pacts_for_which_all_versions_for_the_tag_are_required ?
if tag_names.any?
scope_for(LatestTaggedPactPublications)
.provider(provider_name)
.where(tag_name: tag_names)
.all
.group_by(&:pact_version_id)
.values
.collect do | pact_publications |
create_selected_pact(pact_publications)
end
else
[]
selectors.flat_map do | selector |
query = scope_for(LatestTaggedPactPublications).where(tag_name: selector.tag).provider(provider_name)
query = query.consumer(selector.consumer) if selector.consumer
query.all.collect do | latest_tagged_pact_publication |
pact_publication = PactPublication.find(id: latest_tagged_pact_publication.id)
resolved_pact = selector.consumer ? Selector.latest_for_tag_and_consumer(selector.tag, selector.consumer) : Selector.latest_for_tag(selector.tag)
SelectedPact.new(
pact_publication.to_domain,
Selectors.new(resolved_pact)
)
end
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 = scope_for(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)
Expand All @@ -454,10 +449,9 @@ def create_fallback_selected_pact(pact_publications, consumer_version_selectors)

def find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(provider_name, selectors)
selectors.collect do | selector |
scope_for(LatestTaggedPactPublications)
.provider(provider_name)
.where(tag_name: selector.fallback_tag)
.all
query = scope_for(LatestTaggedPactPublications).provider(provider_name).where(tag_name: selector.fallback_tag)
query = query.consumer(selector.consumer) if selector.consumer
query.all
.collect do | latest_tagged_pact_publication |
pact_publication = unscoped(PactPublication).find(id: latest_tagged_pact_publication.id)
SelectedPact.new(
Expand Down
8 changes: 8 additions & 0 deletions lib/pact_broker/pacts/selector.rb
Expand Up @@ -53,6 +53,14 @@ def self.all_for_tag_and_consumer(tag, consumer)
Selector.new(tag: tag, consumer: consumer)
end

def self.latest_for_tag_and_consumer(tag, consumer)
Selector.new(latest: true, tag: tag, consumer: consumer)
end

def self.latest_for_consumer(consumer)
Selector.new(latest: true, consumer: consumer)
end

def self.from_hash hash
Selector.new(hash)
end
Expand Down
Expand Up @@ -71,6 +71,27 @@ module Contracts
end
end

context "when the latest version for a particular consumer is requested" do
let(:consumer_version_selectors) do
[{
consumer: "Foo",
latest: true
}]
end

it { is_expected.to be_empty }
end

context "when the latest version for all is requested" do
let(:consumer_version_selectors) do
[{
latest: true
}]
end

it { is_expected.to be_empty }
end

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

Expand All @@ -89,7 +110,7 @@ module Contracts
end

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

Expand Down Expand Up @@ -150,9 +171,7 @@ module Contracts
}]
end

it "has an error" do
expect(subject[:consumerVersionSelectors].first).to include "not yet supported"
end
it { is_expected.to be_empty }
end
end
end
Expand Down
Expand Up @@ -56,6 +56,20 @@ def find_by_consumer_name_and_consumer_version_number(consumer_name, consumer_ve
it "sets the latest flag on the selector" do
expect(find_by_consumer_version_number("1").selectors.first.latest).to be true
end

context "when a consumer is specified" do
before do
td.create_pact_with_consumer_version_tag("Foo2", "3", "master", "Bar")
end

let(:selector) { Selector.new(tag: tag, fallback_tag: fallback_tag, latest: true, consumer: "Foo") }

it "only returns the pacts for the consumer" do
expect(subject.size).to eq 1
expect(subject.first.consumer.name).to eq "Foo"
expect(subject.first.selectors.first).to eq selector
end
end
end

context "when a pact does not exist for either tag or fallback_tag" do
Expand Down
Expand Up @@ -40,6 +40,68 @@ def find_by_consumer_name_and_consumer_version_number(consumer_name, consumer_ve
end
end

context "when the selector is latest: true" do
let(:pact_selector_1) { Selector.overall_latest }
let(:consumer_version_selectors) do
Selectors.new(pact_selector_1)
end

before do
td.create_pact_with_hierarchy("Foo1", "1", "Bar")
.create_pact_with_hierarchy("Foo1", "2", "Bar")
.create_pact_with_hierarchy("Foo2", "3", "Bar")
.create_pact_with_hierarchy("Foo2", "4", "Bar2")
end

it "returns the latest pact for each consumer" do
expect(subject.size).to eq 2
expect(find_by_consumer_name_and_consumer_version_number("Foo1", "2").selectors).to eq [Selector.overall_latest]
expect(find_by_consumer_name_and_consumer_version_number("Foo2", "3").selectors).to eq [Selector.overall_latest]
end
end

context "when the selector is latest: true for a particular consumer" do
let(:pact_selector_1) { Selector.latest_for_consumer("Foo1") }

let(:consumer_version_selectors) do
Selectors.new(pact_selector_1)
end

before do
td.create_pact_with_hierarchy("Foo1", "1", "Bar")
.create_pact_with_hierarchy("Foo1", "2", "Bar")
.create_pact_with_hierarchy("Foo2", "2", "Bar")
.create_pact_with_hierarchy("Foo2", "2", "Bar2")
end

it "returns the latest pact for each consumer" do
expect(subject.size).to eq 1
expect(find_by_consumer_name_and_consumer_version_number("Foo1", "2").selectors).to eq [pact_selector_1]
end
end

context "when the selector is latest: true, with a tag, for a particular consumer" do
let(:pact_selector_1) { Selector.latest_for_tag_and_consumer("prod", "Foo1") }

let(:consumer_version_selectors) do
Selectors.new(pact_selector_1)
end

before do
td.create_pact_with_hierarchy("Foo1", "1", "Bar")
.create_consumer_version_tag("prod")
.create_pact_with_hierarchy("Foo1", "2", "Bar")
.create_pact_with_hierarchy("Foo2", "2", "Bar")
.create_consumer_version_tag("prod")
.create_pact_with_hierarchy("Foo2", "2", "Bar2")
end

it "returns the latest pact for each consumer" do
expect(subject.size).to eq 1
expect(find_by_consumer_name_and_consumer_version_number("Foo1", "1").selectors).to eq [pact_selector_1]
end
end

context "when the latest consumer tag names are specified" do
before do
td.create_pact_with_hierarchy("Foo", "foo-latest-prod-version", "Bar")
Expand Down

0 comments on commit 2d52d17

Please sign in to comment.