Skip to content

Commit 113180c

Browse files
committed
feat(pacts for verification): allow a fallback tag to be specified
1 parent 04c41dd commit 113180c

11 files changed

+215
-16
lines changed

lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb

+11
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,19 @@ class VerifiablePactsJSONQuerySchema
1717
optional(:providerVersionTags).maybe(:array?)
1818
optional(:consumerVersionSelectors).each do
1919
schema do
20+
# configure do
21+
# def self.messages
22+
# super.merge(en: { errors: { fallbackTagMustBeForLatest: 'can only be set if latest=true' }})
23+
# end
24+
# end
25+
2026
required(:tag).filled(:str?)
2127
optional(:latest).filled(included_in?: [true, false])
28+
optional(:fallbackTag).filled(:str?)
29+
30+
# rule(fallbackTagMustBeForLatest: [:fallbackTag, :latest]) do | fallback_tag, latest |
31+
# fallback_tag.filled?.then(latest.eql?(true))
32+
# end
2233
end
2334
end
2435
optional(:includePendingStatus).filled(included_in?: [true, false])

lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class VerifiablePactsQuerySchema
1818
schema do
1919
required(:tag).filled(:str?)
2020
optional(:latest).filled(included_in?: ["true", "false"])
21+
optional(:fallback_tag).filled(:str?)
2122
end
2223
end
2324
optional(:include_pending_status).filled(included_in?: ["true", "false"])

lib/pact_broker/api/decorators/verifiable_pacts_query_decorator.rb

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class VerifiablePactsQueryDecorator < BaseDecorator
1919
setter: ->(fragment:, represented:, **) {
2020
represented.latest = (fragment == 'true' || fragment == true)
2121
}
22+
property :fallback_tag
2223
end
2324

2425
property :include_pending_status, default: false,

lib/pact_broker/pacts/repository.rb

+60-9
Original file line numberDiff line numberDiff line change
@@ -351,16 +351,36 @@ def find_for_verification(provider_name, consumer_version_selectors)
351351
selected_pacts = find_pacts_for_which_the_latest_version_is_required(provider_name, consumer_version_selectors) +
352352
find_pacts_for_which_the_latest_version_for_the_tag_is_required(provider_name, consumer_version_selectors) +
353353
find_pacts_for_which_all_versions_for_the_tag_are_required(provider_name, consumer_version_selectors)
354+
355+
selected_pacts = selected_pacts + find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors)
356+
354357
selected_pacts
355358
.group_by(&:pact_version_sha)
356359
.values
357360
.collect do | selected_pacts_for_pact_version_id |
358361
SelectedPact.merge(selected_pacts_for_pact_version_id)
359362
end
363+
360364
end
361365

362366
private
363367

368+
def find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors)
369+
# TODO at the moment, the validation doesn't stop fallback being used with 'all' selectors
370+
selectors_with_fallback_tags = consumer_version_selectors.select(&:fallback_tag?)
371+
selectors_missing_a_pact = selectors_with_fallback_tags.reject do | selector |
372+
selected_pacts.any? do | selected_pact |
373+
selected_pact.latest_for_tag?(selector.tag)
374+
end
375+
end
376+
377+
if selectors_missing_a_pact.any?
378+
find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(provider_name, selectors_missing_a_pact)
379+
else
380+
[]
381+
end
382+
end
383+
364384
def find_pacts_for_which_the_latest_version_is_required(provider_name, consumer_version_selectors)
365385
if consumer_version_selectors.empty?
366386
LatestPactPublications
@@ -384,26 +404,57 @@ def find_pacts_for_which_the_latest_version_for_the_tag_is_required(provider_nam
384404
if tag_names.any?
385405
LatestTaggedPactPublications
386406
.provider(provider_name)
387-
.order_ignore_case(:consumer_name)
388407
.where(tag_name: tag_names)
389408
.all
390409
.group_by(&:pact_version_id)
391410
.values
392411
.collect do | pact_publications |
393-
selector_tag_names = pact_publications.collect(&:tag_name)
394-
selectors = Selectors.create_for_latest_of_each_tag(selector_tag_names)
395-
last_pact_publication = pact_publications.sort_by(&:consumer_version_order).last
396-
pact_publication = PactPublication.find(id: last_pact_publication.id)
397-
SelectedPact.new(
398-
pact_publication.to_domain,
399-
selectors
400-
)
412+
create_selected_pact(pact_publications)
401413
end
402414
else
403415
[]
404416
end
405417
end
406418

419+
def create_selected_pact(pact_publications)
420+
selector_tag_names = pact_publications.collect(&:tag_name)
421+
selectors = Selectors.create_for_latest_of_each_tag(selector_tag_names)
422+
last_pact_publication = pact_publications.sort_by(&:consumer_version_order).last
423+
pact_publication = PactPublication.find(id: last_pact_publication.id)
424+
SelectedPact.new(
425+
pact_publication.to_domain,
426+
selectors
427+
)
428+
end
429+
430+
def create_fallback_selected_pact(pact_publications, consumer_version_selectors)
431+
selector_tag_names = pact_publications.collect(&:tag_name)
432+
selectors = Selectors.create_for_latest_fallback_of_each_tag(selector_tag_names)
433+
last_pact_publication = pact_publications.sort_by(&:consumer_version_order).last
434+
pact_publication = PactPublication.find(id: last_pact_publication.id)
435+
SelectedPact.new(
436+
pact_publication.to_domain,
437+
selectors
438+
)
439+
end
440+
441+
def find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(provider_name, selectors)
442+
selectors.collect do | selector |
443+
LatestTaggedPactPublications
444+
.provider(provider_name)
445+
.where(tag_name: selector.fallback_tag)
446+
.all
447+
.collect do | latest_tagged_pact_publication |
448+
pact_publication = PactPublication.find(id: latest_tagged_pact_publication.id)
449+
SelectedPact.new(
450+
pact_publication.to_domain,
451+
Selectors.new(selector)
452+
)
453+
end
454+
end.flatten
455+
end
456+
457+
407458
def find_pacts_for_which_all_versions_for_the_tag_are_required(provider_name, consumer_version_selectors)
408459
# The tags for which all versions are specified
409460
tag_names = consumer_version_selectors.tag_names_of_selectors_for_all_pacts

lib/pact_broker/pacts/selected_pact.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ def overall_latest?
2525
selectors.overall_latest?
2626
end
2727

28-
def latest_for_tag?
29-
selectors.latest_for_tag?
28+
def latest_for_tag? potential_tag = nil
29+
selectors.latest_for_tag?(potential_tag)
3030
end
3131

3232
def consumer_version_order

lib/pact_broker/pacts/selector.rb

+20-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ def latest
1717
self[:latest]
1818
end
1919

20+
def fallback_tag= fallback_tag
21+
self[:fallback_tag] = fallback_tag
22+
end
23+
24+
def fallback_tag
25+
self[:fallback_tag]
26+
end
27+
2028
def self.overall_latest
2129
Selector.new(latest: true)
2230
end
@@ -33,6 +41,10 @@ def self.from_hash hash
3341
Selector.new(hash)
3442
end
3543

44+
def fallback_tag?
45+
!!fallback_tag
46+
end
47+
3648
def tag
3749
self[:tag]
3850
end
@@ -41,8 +53,13 @@ def overall_latest?
4153
!!(latest? && !tag)
4254
end
4355

44-
def latest_for_tag?
45-
!!(latest && tag)
56+
# Not sure if the fallback_tag logic is needed
57+
def latest_for_tag? potential_tag = nil
58+
if potential_tag
59+
!!(latest && tag == potential_tag)
60+
else
61+
!!(latest && !!tag)
62+
end
4663
end
4764

4865
def all_for_tag?
@@ -70,7 +87,7 @@ def <=> other
7087
private
7188

7289
def latest?
73-
self[:latest]
90+
!!self[:latest]
7491
end
7592
end
7693
end

lib/pact_broker/pacts/selectors.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ def overall_latest?
2828
any?(&:overall_latest?)
2929
end
3030

31-
def latest_for_tag?
32-
any?(&:latest_for_tag?)
31+
def latest_for_tag? potential_tag = nil
32+
any? { | selector | selector.latest_for_tag?(potential_tag) }
3333
end
3434

3535
def tag_names_of_selectors_for_all_pacts

lib/pact_broker/test/test_data_builder.rb

+6
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ def create_pact_with_hierarchy consumer_name = "Consumer", consumer_version_numb
8484
self
8585
end
8686

87+
def create_pact_with_consumer_version_tag consumer_name, consumer_version_number, consumer_version_tag_name, provider_name
88+
create_pact_with_hierarchy(consumer_name, consumer_version_number, provider_name)
89+
create_consumer_version_tag(consumer_version_tag_name)
90+
self
91+
end
92+
8793
def create_pact_with_verification consumer_name = "Consumer", consumer_version = "1.0.#{model_counter}", provider_name = "Provider", provider_version = "1.0.#{model_counter}"
8894
create_pact_with_hierarchy(consumer_name, consumer_version, provider_name)
8995
create_verification(number: model_counter, provider_version: provider_version)

spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb

+29
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,35 @@ module Contracts
2828
end
2929
end
3030

31+
context "when the fallback tag is specified" do
32+
context "when latest is specified" do
33+
let(:consumer_version_selectors) do
34+
[{
35+
tag: "feat-x",
36+
fallbackTag: "master",
37+
latest: true
38+
}]
39+
end
40+
41+
it "has no errors" do
42+
expect(subject).to eq({})
43+
end
44+
end
45+
46+
context "when latest is not specified", pending: true do
47+
let(:consumer_version_selectors) do
48+
[{
49+
tag: "feat-x",
50+
fallbackTag: "master"
51+
}]
52+
end
53+
54+
it "has an error" do
55+
expect(subject[:consumerVersionSelectors].first).to include "not allowed"
56+
end
57+
end
58+
end
59+
3160
context "when providerVersionTags is not an array" do
3261
let(:provider_version_tags) { true }
3362

spec/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator_spec.rb

+10
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ module Decorators
3535
end
3636
end
3737

38+
context "with a fallback" do
39+
let(:consumer_version_selectors) do
40+
[{ "tag" => "feat-x", "fallbackTag" => "dev", "latest" => true }]
41+
end
42+
43+
it "sets the fallback" do
44+
expect(subject.consumer_version_selectors.first.fallback_tag).to eq "dev"
45+
end
46+
end
47+
3848
it "parses the latest as a boolean" do
3949
expect(subject.consumer_version_selectors.first).to eq PactBroker::Pacts::Selector.new(tag: "dev", latest: true)
4050
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
require 'pact_broker/pacts/repository'
2+
3+
module PactBroker
4+
module Pacts
5+
describe Repository do
6+
let(:td) { TestDataBuilder.new }
7+
8+
describe "#find_for_verification" do
9+
def find_by_consumer_version_number(consumer_version_number)
10+
subject.find{ |pact| pact.consumer_version_number == consumer_version_number }
11+
end
12+
13+
def find_by_consumer_name_and_consumer_version_number(consumer_name, consumer_version_number)
14+
subject.find{ |pact| pact.consumer_name == consumer_name && pact.consumer_version_number == consumer_version_number }
15+
end
16+
17+
subject { Repository.new.find_for_verification("Bar", consumer_version_selectors) }
18+
19+
context "when there is a fallback tag specified" do
20+
before do
21+
td.create_pact_with_consumer_version_tag("Foo", "1", "master", "Bar")
22+
.create_pact_with_consumer_version_tag("Foo", "2", "feat-x", "Bar")
23+
end
24+
25+
let(:tag) { "feat-x" }
26+
let(:fallback_tag) { "master" }
27+
let(:selector) { Selector.new(tag: tag, fallback_tag: fallback_tag, latest: true) }
28+
let(:consumer_version_selectors) { Selectors.new(selector) }
29+
30+
context "when a pact exists for the main tag" do
31+
it "returns the pact with the main tag" do
32+
expect(find_by_consumer_version_number("2")).to_not be nil
33+
expect(find_by_consumer_version_number("2").selectors.first).to eq Selector.latest_for_tag(tag)
34+
end
35+
36+
it "does not set the fallback_tag on the selector" do
37+
expect(find_by_consumer_version_number("2").selectors.first.fallback_tag).to be nil
38+
end
39+
end
40+
41+
context "when a pact does not exist for the main tag and pact exists for the fallback tag" do
42+
let(:tag) { "no-existy" }
43+
44+
it "returns the pact with the fallback tag" do
45+
expect(find_by_consumer_version_number("1")).to_not be nil
46+
end
47+
48+
it "sets the fallback_tag on the selector" do
49+
expect(find_by_consumer_version_number("1").selectors.first.fallback_tag).to eq fallback_tag
50+
end
51+
52+
it "sets the tag on the selector" do
53+
expect(find_by_consumer_version_number("1").selectors.first.tag).to eq tag
54+
end
55+
56+
it "sets the latest flag on the selector" do
57+
expect(find_by_consumer_version_number("1").selectors.first.latest).to be true
58+
end
59+
end
60+
61+
context "when a pact does not exist for either tag or fallback_tag" do
62+
let(:tag) { "no-existy" }
63+
let(:fallback_tag) { "also-no-existy" }
64+
65+
it "returns an empty list" do
66+
expect(subject).to be_empty
67+
end
68+
end
69+
end
70+
end
71+
end
72+
end
73+
end

0 commit comments

Comments
 (0)