Skip to content

Commit

Permalink
feat: support consumer version selector for all branches (#667)
Browse files Browse the repository at this point in the history
* feat: support consumer version selector for all branches

* test: add test for latest_for_all_consumer_branches
  • Loading branch information
bethesque authored Feb 29, 2024
1 parent 62017ed commit 34334ca
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class ConsumerVersionSelectorContract < BaseContract
json do
optional(:mainBranch).filled(included_in?: [true])
optional(:tag).filled(:str?)
optional(:branch).filled(:str?)
optional(:branch).filled { str? | eql?(true) }
optional(:matchingBranch).filled(included_in?: [true])
optional(:latest).filled(included_in?: [true, false])
optional(:fallbackTag).filled(:str?)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def content_types_provided
[["application/hal+json", :to_json]]
end

# TODO drop support for GET in next major version.
# GET was only used by the very first Ruby Pact clients that supported the 'pacts for verification'
# feature, until it became clear that the parameters for the request were going to get nested and complex,
# at which point the POST was added.
def allowed_methods
["GET", "POST", "OPTIONS"]
end
Expand All @@ -32,6 +36,7 @@ def process_post
end
end

# For this endoint, the POST is a "read" action (used for Pactflow)
def read_methods
super + %w{POST}
end
Expand Down
29 changes: 27 additions & 2 deletions lib/pact_broker/pacts/pact_publication_dataset_module.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ def for_consumer_name_and_maybe_version_number(consumer_name, consumer_version_n
end
end

# TODO use the branch heads here
# Returns the latest pact for each branch, returning a pact for every branch, even if
# the most recent version of that branch does not have a pact.
# This is different from for_all_branch_heads, which will find the branch head versions,
# and return the pacts associated with those versions.
# This method should not be used for 'pacts for verification', because it will return
# a pact for branches where that integration should no longer exist.
# @return [Dataset<PactBroker::Pacts::PactPublication>]
def latest_by_consumer_branch
branch_versions_join = {
Sequel[:pact_publications][:consumer_version_id] => Sequel[:branch_versions][:version_id]
Expand Down Expand Up @@ -112,10 +118,12 @@ def overall_latest_for_consumer_id_and_provider_id(consumer_id, provider_id)
.limit(1)
end

# Return the pacts (if they exist) for the branch heads.
# Return the pacts (if they exist) for the branch heads of the given branch names
# This uses the new logic of finding the branch head and returning any associated pacts,
# rather than the old logic of returning the pact for the latest version
# on the branch that had a pact.
# @param [String] branch_name
# @return [Sequel::Dataset<PactBroker::Pacts::PactPublication>]
def for_branch_heads(branch_name)
branch_head_join = {
Sequel[:pact_publications][:consumer_version_id] => Sequel[:branch_heads][:version_id],
Expand All @@ -133,6 +141,23 @@ def for_branch_heads(branch_name)
.remove_overridden_revisions_from_complete_query
end

# Return the pacts (if they exist) for all the branch heads.
# @return [Sequel::Dataset<PactBroker::Pacts::PactPublication>]
def latest_for_all_consumer_branches
branch_head_join = {
Sequel[:pact_publications][:consumer_version_id] => Sequel[:branch_heads][:version_id],
}

base_query = self
if no_columns_selected?
base_query = base_query.select_all_qualified.select_append(Sequel[:branch_heads][:branch_name].as(:branch_name))
end

base_query
.join(:branch_heads, branch_head_join)
.remove_overridden_revisions_from_complete_query
end

# The pact that belongs to the branch head.
# May return nil if the branch head does not have a pact published for it.
def latest_for_consumer_branch(branch_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def for_provider_and_consumer_version_selector provider, selector

# Do the "latest" logic last so that the provider/consumer criteria get included in the "latest" query before the join, rather than after
query = query.latest_for_main_branches if selector.latest_for_main_branch?
query = query.latest_for_all_consumer_branches if selector.latest_for_each_branch?
query = query.latest_for_consumer_branch(selector.branch) if selector.latest_for_branch?
query = query.for_latest_consumer_versions_with_tag(selector.tag) if selector.latest_for_tag?
query = query.overall_latest if selector.overall_latest?
Expand Down
10 changes: 8 additions & 2 deletions lib/pact_broker/pacts/selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def resolve_for_environment(consumer_version, environment, target = nil)
def type
if latest_for_main_branch?
:latest_for_main_branch
elsif latest_for_each_branch?
:latest_for_each_branch
elsif latest_for_branch?
:latest_for_branch
elsif matching_branch?
Expand Down Expand Up @@ -265,12 +267,16 @@ def latest_for_tag? potential_tag = nil
# Not sure if the fallback_tag logic is needed
def latest_for_branch? potential_branch = nil
if potential_branch
!!(latest && branch == potential_branch)
latest == true && branch == potential_branch
else
!!(latest && !!branch)
latest == true && branch.is_a?(String)
end
end

def latest_for_each_branch?
latest == true && branch == true
end

def all_for_tag_and_consumer?
!!(tag && !latest? && consumer)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,36 @@ module Contracts

it { is_expected.to_not have_key(:consumerVersionSelectors) }
end

context "when branch is true, and latest is true" do
let(:params) do
{
consumerVersionSelectors: [ { branch: true, latest: true }]
}
end

it { is_expected.to_not have_key(:consumerVersionSelectors) }
end

context "when branch is true, and latest is false" do
let(:params) do
{
consumerVersionSelectors: [ { branch: true, latest: false }]
}
end

its([:consumerVersionSelectors, 0]) { is_expected.to match("cannot specify") }
end

context "when branch is false" do
let(:params) do
{
consumerVersionSelectors: [ { branch: false }]
}
end

its([:consumerVersionSelectors, 0]) { is_expected.to match("branch must be a string or branch must be equal to true") }
end
end
end
end
Expand Down
65 changes: 65 additions & 0 deletions spec/lib/pact_broker/pacts/pact_publication_dataset_module_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,71 @@ module Pacts
end
end

describe "latest_for_all_consumer_branches" do
before do
td.create_consumer("Foo")
.create_provider("Bar")
.create_consumer_version("1", branch: "main")
.create_pact
.create_consumer_version("2", branch: "main")
.create_pact
.revise_pact
.create_consumer_version("3", branch: "feat-x")
.create_pact
.create_consumer("Foo2")
.create_provider("Bar2")
.create_consumer_version("10", branch: "main")
.create_pact
.create_consumer_version("11", branch: "main")
.create_pact
end

subject { PactPublication.latest_for_all_consumer_branches }

it "returns the pacts for all the branch heads" do
all = subject.all_allowing_lazy_load.sort_by{ |pact_publication| pact_publication.consumer_version.order }
expect(all.size).to eq 3
expect(all.first.consumer.name).to eq "Foo"
expect(all.first.provider.name).to eq "Bar"
expect(all.first.consumer_version.number).to eq "2"
expect(all.first.revision_number).to eq 2

expect(all.last.consumer.name).to eq "Foo2"
expect(all.last.provider.name).to eq "Bar2"
expect(all.last.consumer_version.number).to eq "11"
end

it "does not return extra columns" do
expect(subject.first.values.keys.sort).to eq (PactPublication.columns + [:branch_name]).sort
end

context "when there is no pact for the branch head" do
before do
td.create_consumer_version("12", branch: "main")
end

it "does not return a pact" do
all = subject.all_allowing_lazy_load
expect(all.size).to eq 2
end
end

context "when columns are already selected" do
subject { PactPublication.select(Sequel[:pact_publications][:id]).latest_for_consumer_branch("main") }

it "does not override them" do
expect(subject.all.first.values.keys).to eq [:id]
end
end

context "when chained" do
it "works" do
all = PactPublication.for_provider(td.find_pacticipant("Bar")).latest_for_consumer_branch("main").all_allowing_lazy_load
expect(all.collect(&:provider_name).uniq).to eq ["Bar"]
end
end
end

describe "latest_by_consumer_tag" do
before do
td.create_consumer("Foo")
Expand Down

0 comments on commit 34334ca

Please sign in to comment.