Skip to content

Commit

Permalink
feat: currently deployed selector (#396)
Browse files Browse the repository at this point in the history
* feat(pacts for verification): support specifying consumer versions by currentlyDeployed and environment

* feat(pacts for verification): update inclusion reason to support selectors for currently deployed versions

* feat(pacts for verification): update inclusion reason to support selectors for currently deployed versions with a consumer specified
  • Loading branch information
bethesque authored Mar 1, 2021
1 parent bd0ca9d commit 8666d70
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class VerifiablePactsJSONQuerySchema
optional(:latest).filled(included_in?: [true, false])
optional(:fallbackTag).filled(:str?)
optional(:consumer).filled(:str?, :not_blank?)
optional(:currentlyDeployed).filled(included_in?: [true])
optional(:environment).filled(:str?)

# rule(fallbackTagMustBeForLatest: [:fallbackTag, :latest]) do | fallback_tag, latest |
# fallback_tag.filled?.then(latest.eql?(true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class VerifiablePactsQueryDecorator < BaseDecorator
}
property :fallback_tag
property :consumer
property :environment
property :currently_deployed
end

property :include_pending_status, default: false,
Expand Down
18 changes: 18 additions & 0 deletions lib/pact_broker/pacts/pact_publication_dataset_module.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def for_provider_and_consumer_version_selector provider, selector
# Do this last so that the provider/consumer criteria get included in the "latest" query before the join, rather than after
query = query.latest_for_consumer_branch(selector.branch) if selector.latest_for_branch?
query = query.latest_for_consumer_tag(selector.tag) if selector.latest_for_tag?
query = query.for_currently_deployed_versions(selector.environment) if selector.currently_deployed?
query = query.overall_latest if selector.overall_latest?
query
end
Expand Down Expand Up @@ -149,6 +150,23 @@ def latest_for_consumer_tag(tag_name)
.remove_overridden_revisions_from_complete_query
end

def for_currently_deployed_versions(environment_name)
deployed_versions_join = {
Sequel[:pact_publications][:consumer_version_id] => Sequel[:deployed_versions][:version_id],
Sequel[:deployed_versions][:currently_deployed] => true
}
environments_join = {
Sequel[:deployed_versions][:environment_id] => Sequel[:environments][:id],
Sequel[:environments][:name] => environment_name
}.compact

query = self
if no_columns_selected?
query = query.select_all_qualified.select_append(Sequel[:environments][:name].as(:environment_name))
end
query.join(:deployed_versions, deployed_versions_join).join(:environments, environments_join)
end

def successfully_verified_by_provider_branch(provider_id, provider_version_branch)
verifications_join = {
pact_version_id: :pact_version_id,
Expand Down
15 changes: 11 additions & 4 deletions lib/pact_broker/pacts/pacts_for_verification_repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class PactsForVerificationRepository
include PactBroker::Repositories::Helpers

def find(provider_name, consumer_version_selectors)
selected_pacts = find_pacts_for_which_the_latest_version_of_something_is_required(provider_name, consumer_version_selectors) +
selected_pacts = find_pacts_by_selector(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)
merge_selected_pacts(selected_pacts)
Expand Down Expand Up @@ -101,23 +101,29 @@ def find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version
end
end

def find_pacts_for_which_the_latest_version_of_something_is_required(provider_name, consumer_version_selectors)
def find_pacts_by_selector(provider_name, consumer_version_selectors)
provider = pacticipant_repository.find_by_name(provider_name)

selectors = if consumer_version_selectors.empty?
Selectors.create_for_overall_latest
else
consumer_version_selectors.select(&:latest_for_tag?) +
consumer_version_selectors.select(&:latest_for_branch?) +
consumer_version_selectors.select(&:overall_latest?)
consumer_version_selectors.select(&:overall_latest?) +
consumer_version_selectors.select(&:currently_deployed?)
end

selectors.flat_map do | selector |
query = scope_for(PactPublication).for_provider_and_consumer_version_selector(provider, selector)
query.all.collect do | pact_publication |
resolved_selector = if selector.currently_deployed?
selector.resolve_for_environment(pact_publication.consumer_version, pact_publication.values.fetch(:environment_name))
else
selector.resolve(pact_publication.consumer_version)
end
SelectedPact.new(
pact_publication.to_domain,
Selectors.new(selector.resolve(pact_publication.consumer_version))
Selectors.new(resolved_selector)
)
end
end
Expand All @@ -141,6 +147,7 @@ def find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(pro

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
# Need to move support for this into PactPublication.for_provider_and_consumer_version_selector
selectors = consumer_version_selectors.select(&:all_for_tag?)

selectors.flat_map do | selector |
Expand Down
62 changes: 61 additions & 1 deletion lib/pact_broker/pacts/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ def resolve_for_fallback(consumer_version)
ResolvedSelector.new(self.to_h, consumer_version)
end

def resolve_for_environment(consumer_version, environment)
ResolvedSelector.new(self.to_h.merge(environment: environment), consumer_version)
end

# Only currently used to identify the currently_deployed from the others in
# verifiable_pact_messages, so don't need the "for_consumer" sub category
def type
if latest_for_branch?
:latest_for_branch
elsif currently_deployed?
:currently_deployed
elsif latest_for_tag?
:latest_for_tag
elsif all_for_tag?
:all_for_tag
elsif overall_latest?
:overall_latest
else
:undefined
end
end

def tag= tag
self[:tag] = tag
end
Expand Down Expand Up @@ -57,6 +79,26 @@ def consumer
self[:consumer]
end

def currently_deployed= currently_deployed
self[:currently_deployed] = currently_deployed
end

def currently_deployed
self[:currently_deployed]
end

def currently_deployed?
currently_deployed
end

def environment= environment
self[:environment] = environment
end

def environment
self[:environment]
end

def self.overall_latest
Selector.new(latest: true)
end
Expand Down Expand Up @@ -97,6 +139,18 @@ def self.latest_for_consumer(consumer)
Selector.new(latest: true, consumer: consumer)
end

def self.for_currently_deployed(environment = nil)
Selector.new( { currently_deployed: true, environment: environment }.compact )
end

def self.for_currently_deployed_and_consumer(consumer)
Selector.new(currently_deployed: true, consumer: consumer)
end

def self.for_currently_deployed_and_environment_and_consumer(environment, consumer)
Selector.new(currently_deployed: true, environment: environment, consumer: consumer)
end

def self.from_hash hash
Selector.new(hash)
end
Expand All @@ -118,7 +172,7 @@ def branch
end

def overall_latest?
!!(latest? && !tag && !branch)
!!(latest? && !tag && !branch && !currently_deployed && !environment)
end

# Not sure if the fallback_tag logic is needed
Expand Down Expand Up @@ -164,6 +218,12 @@ def <=> other
else
latest_for_branch? ? -1 : 1
end
elsif currently_deployed? || other.currently_deployed?
if currently_deployed? == other.currently_deployed?
environment <=> other.environment
else
currently_deployed? ? -1 : 1
end
elsif latest_for_tag? || other.latest_for_tag?
if latest_for_tag? == other.latest_for_tag?
tag <=> other.tag
Expand Down
28 changes: 23 additions & 5 deletions lib/pact_broker/pacts/verifiable_pact_messages.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,12 @@ def pact_version_short_description
attr_reader :verifiable_pact, :pact_version_url

def join(list, last_joiner = " and ")
quoted_list = list.collect { | tag | "'#{tag}'" }
comma_joined = quoted_list[0..-3] || []
and_joined = quoted_list[-2..-1] || quoted_list
join_unquoted(list.collect { | word | "'#{word}'" }, last_joiner = " and ")
end

def join_unquoted(list, last_joiner = " and ")
comma_joined = list[0..-3] || []
and_joined = list[-2..-1] || list
if comma_joined.any?
"#{comma_joined.join(', ')}, #{and_joined.join(last_joiner)}"
else
Expand Down Expand Up @@ -153,11 +156,24 @@ def branches
end

def selector_descriptions
selectors.sort.collect do | selector |
selector_description(selector)
selectors.sort.group_by(&:type).values.flat_map do | selectors |
selectors_descriptions(selectors)
end.join(", ")
end

def selectors_descriptions(selectors)
if selectors.first.currently_deployed?
selectors.group_by(&:consumer).flat_map do | consumer_name, selectors |
display_name = consumer_name ? "the version(s) of #{consumer_name}" : "the consumer version(s)"
"pacts for #{display_name} currently deployed to #{join_unquoted(selectors.collect(&:environment))}"
end
else
selectors.collect do | selector |
selector_description(selector)
end
end
end

def selector_description selector
if selector.overall_latest?
consumer_label = selector.consumer ? selector.consumer : 'a consumer'
Expand All @@ -180,6 +196,8 @@ def selector_description selector
"pacts for all #{selector.consumer} versions tagged '#{selector.tag}'"
elsif selector.all_for_tag?
"pacts for all consumer versions tagged '#{selector.tag}'"
elsif selector.currently_deployed?
"pacts for consumer version(s) currently deployed to #{selector.environment}"
else
selector.to_json
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
require 'pact_broker/pacts/repository'

module PactBroker
module Pacts
describe Repository do
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 currently_deployed is true" do
before do
td.create_environment("test")
.create_pact_with_hierarchy("Foo", "1", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: false)
.create_pact_with_hierarchy("Foo", "2", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: true)
.create_pact_with_hierarchy("Waffle", "3", "Bar")
.create_pact_with_hierarchy("Waffle", "4", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: true)
end

let(:consumer_version_selectors) do
PactBroker::Pacts::Selectors.new(
PactBroker::Pacts::Selector.for_currently_deployed
)
end

it "returns the pacts for the currently deployed versions" do
expect(subject.size).to eq 2
expect(subject.first.selectors).to eq [PactBroker::Pacts::Selector.for_currently_deployed.resolve_for_environment(td.find_version("Foo", "2"), "test")]
expect(subject.last.selectors).to eq [PactBroker::Pacts::Selector.for_currently_deployed.resolve_for_environment(td.find_version("Waffle", "4"), "test")]
end
end

context "when currently_deployed is true and an environment is specified" do
before do
td.create_environment("test")
.create_pact_with_hierarchy("Foo", "1", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: false)
.create_pact_with_hierarchy("Foo", "2", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: true)
.create_pact_with_hierarchy("Waffle", "3", "Bar")
.create_pact_with_hierarchy("Waffle", "4", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: true)
.create_environment("prod")
.create_pact_with_hierarchy("Foo", "5", "Bar")
.comment("not included, wrong environment")
.create_deployed_version_for_consumer_version(currently_deployed: true)
end

let(:consumer_version_selectors) do
PactBroker::Pacts::Selectors.new(
PactBroker::Pacts::Selector.for_currently_deployed("test")
)
end

it "returns the pacts for the currently deployed versions" do
expect(subject.size).to eq 2
expect(subject.first.selectors).to eq [PactBroker::Pacts::Selector.for_currently_deployed("test").resolve(td.find_version("Foo", "2"))]
expect(subject.last.selectors).to eq [PactBroker::Pacts::Selector.for_currently_deployed("test").resolve(td.find_version("Waffle", "4"))]
end
end

context "when currently_deployed is true and an environment is and consumer specified" do
before do
td.create_environment("test")
.create_pact_with_hierarchy("Foo", "1", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: false)
.create_pact_with_hierarchy("Foo", "2", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: true)
.create_pact_with_hierarchy("Waffle", "3", "Bar")
.create_pact_with_hierarchy("Waffle", "4", "Bar")
.create_deployed_version_for_consumer_version(currently_deployed: true)
.create_environment("prod")
.create_pact_with_hierarchy("Foo", "5", "Bar")
.comment("not included, wrong environment")
.create_deployed_version_for_consumer_version(currently_deployed: true)
end

let(:consumer_version_selectors) do
PactBroker::Pacts::Selectors.new(
PactBroker::Pacts::Selector.for_currently_deployed_and_environment_and_consumer("test", "Foo")
)
end

it "returns the pacts for the currently deployed versions" do
expect(subject.size).to eq 1
expect(subject.first.selectors).to eq [PactBroker::Pacts::Selector.for_currently_deployed_and_environment_and_consumer("test", "Foo").resolve(td.find_version("Foo", "2"))]
end
end

context "when the same version is deployed to multiple environments" do
before do
td.create_environment("test")
.create_environment("prod")
.create_pact_with_hierarchy("Foo", "1", "Bar")
.create_deployed_version_for_consumer_version(environment_name: "test")
.create_deployed_version_for_consumer_version(environment_name: "prod")
end

let(:consumer_version_selectors) do
PactBroker::Pacts::Selectors.new(
PactBroker::Pacts::Selector.for_currently_deployed
)
end

it "returns one pact_publication with multiple selectors" do
expect(subject.size).to eq 1
expect(subject.first.selectors.size).to eq 2
expect(subject.first.selectors.first.environment).to eq "test"
expect(subject.first.selectors.last.environment).to eq "prod"
end
end
end
end
end
end
6 changes: 4 additions & 2 deletions spec/lib/pact_broker/pacts/selector_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ module Pacts
let(:all_dev_for_consumer_1) { Selector.all_for_tag_and_consumer('dev', 'Bar') }
let(:all_prod) { Selector.all_for_tag('prod') }
let(:all_dev) { Selector.all_for_tag('dev') }
let(:currently_deployed_to_prod) { Selector.for_currently_deployed('prod') }
let(:currently_deployed_to_test) { Selector.for_currently_deployed('test') }

let(:unsorted_selectors) do
[all_prod, all_dev, all_dev_for_consumer_1, latest_for_branch_main, latest_for_tag_prod, overall_latest_1, overall_latest_1, latest_for_tag_dev, all_prod_for_consumer_2, all_prod_for_consumer_1]
[all_prod, all_dev, currently_deployed_to_prod, all_dev_for_consumer_1, latest_for_branch_main, latest_for_tag_prod, currently_deployed_to_test, overall_latest_1, overall_latest_1, latest_for_tag_dev, all_prod_for_consumer_2, all_prod_for_consumer_1]
end

let(:expected_sorted_selectors) do
[overall_latest_1, overall_latest_1, latest_for_branch_main, latest_for_tag_dev, latest_for_tag_prod, all_dev_for_consumer_1, all_prod_for_consumer_2, all_prod_for_consumer_1, all_dev, all_prod]
[overall_latest_1, overall_latest_1, latest_for_branch_main, currently_deployed_to_prod, currently_deployed_to_test, latest_for_tag_dev, latest_for_tag_prod, all_dev_for_consumer_1, all_prod_for_consumer_2, all_prod_for_consumer_1, all_dev, all_prod]
end

it "sorts the selectors" do
Expand Down
Loading

0 comments on commit 8666d70

Please sign in to comment.