pact_broker 2.122.0 โ†’ 2.123.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f515fab91233eb259d233ef8ff18b35014c55dfebb10a5cec183283bbe760d54
4
- data.tar.gz: 2f35f5f3f748d3df875df0ce5b8270f2b1e4b2cf8682804fda41def6d31b88a1
3
+ metadata.gz: e464517845e4046dd28826ab16b367d5080b69cb535f9a9d5901feda4f262bd8
4
+ data.tar.gz: 9959dfb3c78a5e3940a94306133b1e92244a2531cd275f2fb7353c98faa153a8
5
5
  SHA512:
6
- metadata.gz: 6eaefdf8cd5b2b9856bb076579bf034889b7fdab56cfe159c6e1ed56e74d72dca7929ca983290238e32f280e8d7e9b9bc53b9512b4d1562bde122de035e2f50b
7
- data.tar.gz: fe3c3008fa385b8585d7b2faa08e8196194e92fdfc496a813f0208373905abacdcded76e0eb4b8dc115b588b20be7e5cd32580ec78c4cbddfc7db15222936b73
6
+ metadata.gz: cfdf7a02904a49f475cc615a0a097eaa6b2a3d438df70e3523eae4fe29d7eb7052bc211a07bf70446d6ae76941309bc98846c155285d9ce1efae85718d39e94a
7
+ data.tar.gz: b8dd289895ecf228815e1532d607e15ce7ffd5d41b4ff43f141b2944239aec2f415152189fc73b9aa1725cb4d1dd1ab7a231563bdb0cf576ed4afa53d9067f65
data/CHANGELOG.md CHANGED
@@ -1,3 +1,46 @@
1
+ ## [2.123.0] _2026-11-11_
2
+
3
+ ### ๐Ÿš€ Features
4
+
5
+ - Add fallbackToMainBranch consumer version selector option
6
+
7
+ ### ๐Ÿ› Bug Fixes
8
+
9
+ - Stop the consumer version verification summary querying per verification
10
+ - Apply fallbackTag per consumer
11
+ - Honour fallbackBranch in consumer version selectors
12
+
13
+ ### ๐Ÿšœ Refactor
14
+
15
+ - Name the fallback resolver for tags and branches
16
+
17
+ ### โšก๏ธ Performance
18
+
19
+ - Speed up publishing large contracts (PACT-7601) ([#1067](https://github.com/pact-foundation/pact_broker/issues/1067))
20
+ > **Migration note:** when contracts are published with /contracts/publish,
21
+ > the configured sha_generator is now called with a second argument,
22
+ - Exclude consumers in the fallback tag query
23
+ - Exclude consumers in the fallback branch query
24
+
25
+ ### ๐Ÿ“š Documentation
26
+
27
+ - Say fallbackTag applies per consumer
28
+ - Say fallbackBranch applies per consumer
29
+
30
+ ### ๐Ÿงช Testing
31
+
32
+ - Share one SQL query counter across feature specs
33
+ - Pin fallbackTag semantics in a table
34
+ - Cover fallbackTag dedup, branches and notices
35
+ - Pin fallbackBranch semantics in a table
36
+ - Cover the fallbackBranch request and notices end to end
37
+ - Pin fallbackToMainBranch semantics in the fallback branch table
38
+ - Cover the fallbackToMainBranch request and notices end to end
39
+
40
+ ### ๐Ÿ› ๏ธ Miscellaneous Tasks
41
+
42
+ - Drop a TODO the selector contract already resolves
43
+
1
44
  ## [2.122.0] _2026-10-01_
2
45
 
3
46
  ### ๐Ÿš€ Features
@@ -6,7 +6,7 @@ module PactBroker
6
6
  class ConsumerVersionSelectorContract < BaseContract
7
7
  option :parent # the parent hash in which the ConsumerVersionSelector is embedded
8
8
 
9
- BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch, :matchingBranch, :mainBranch]
9
+ BRANCH_KEYS = [:latest, :tag, :fallbackTag, :branch, :fallbackBranch, :fallbackToMainBranch, :matchingBranch, :mainBranch]
10
10
  ENVIRONMENT_KEYS = [:environment, :deployed, :released, :deployedOrReleased]
11
11
  ALL_KEYS = BRANCH_KEYS + ENVIRONMENT_KEYS + [:consumer]
12
12
 
@@ -18,6 +18,7 @@ module PactBroker
18
18
  optional(:latest).filled(included_in?: [true, false])
19
19
  optional(:fallbackTag).filled(:str?)
20
20
  optional(:fallbackBranch).filled(:str?)
21
+ optional(:fallbackToMainBranch).filled(included_in?: [true])
21
22
  optional(:consumer).filled(:str?)
22
23
  optional(:deployed).filled(included_in?: [true])
23
24
  optional(:released).filled(included_in?: [true])
@@ -108,6 +109,21 @@ module PactBroker
108
109
  end
109
110
  end
110
111
 
112
+ # fallbackToMainBranch
113
+ rule(:fallbackToMainBranch, :fallbackBranch, :branch, :latest) do
114
+ if values[:fallbackToMainBranch] && !values[:branch]
115
+ base.failure(validation_message("pacts_for_verification_selector_fallback_to_main_branch_requires_branch"))
116
+ end
117
+
118
+ if values[:fallbackToMainBranch] && values[:fallbackBranch]
119
+ base.failure(validation_message("pacts_for_verification_selector_fallback_to_main_branch_and_fallback_branch_disallowed"))
120
+ end
121
+
122
+ if values[:fallbackToMainBranch] && values[:latest] == false
123
+ base.failure(validation_message("pacts_for_verification_selector_fallback_to_main_branch_and_latest_false_disallowed"))
124
+ end
125
+ end
126
+
111
127
  # branch/latest
112
128
  rule(:branch, :latest) do
113
129
  if values[:branch] && values[:latest] == false
@@ -5,7 +5,11 @@ module PactBroker
5
5
  extend self
6
6
 
7
7
  def fragment_before_invalid_utf_8_char(string)
8
- string.force_encoding("UTF-8").each_char.with_index do | char, index |
8
+ # Iterating over each char allocates a String per char, which is very slow for large strings,
9
+ # so only do it to find the location of the invalid char once we know there is one.
10
+ return nil if string.force_encoding("UTF-8").valid_encoding?
11
+
12
+ string.each_char.with_index do | char, index |
9
13
  if !char.valid_encoding?
10
14
  fragment = index < 100 ? string[0...index] : string[index-100...index]
11
15
  return index + 1, fragment
@@ -34,6 +34,7 @@ module PactBroker
34
34
  }
35
35
  property :fallback_tag
36
36
  property :fallback_branch
37
+ property :fallback_to_main_branch
37
38
  property :consumer
38
39
  property :environment_name, as: :environment
39
40
  property :currently_deployed, as: :deployed
@@ -11,10 +11,11 @@ module PactBroker
11
11
  property :provider_name
12
12
  property :specification
13
13
  property :content_type
14
- property :decoded_content, setter: -> (fragment:, represented:, user_options:, **) {
14
+ property :decoded_content, setter: -> (fragment:, represented:, user_options:, doc:, **) {
15
15
  represented.decoded_content = fragment
16
- # Set the pact version sha when we set the content
17
- represented.pact_version_sha = user_options.fetch(:sha_generator).call(fragment)
16
+ # Set the pact version sha when we set the content, reusing the content that has already been
17
+ # parsed in the resource, as parsing very large pacts is expensive
18
+ represented.pact_version_sha = user_options.fetch(:sha_generator).call(fragment, parsed_content: doc["decodedParsedContent"])
18
19
  }
19
20
  property :on_conflict, default: "overwrite"
20
21
 
@@ -24,7 +24,7 @@ module PactBroker
24
24
  end
25
25
 
26
26
  link :self do | options |
27
- pact = options[:pact] || represented.latest_pact_publication
27
+ pact = pact_for(options)
28
28
  {
29
29
  title: "Verification result",
30
30
  name: "Verification result #{represented.number} for #{pact.name}",
@@ -33,7 +33,7 @@ module PactBroker
33
33
  end
34
34
 
35
35
  link "pb:pact-version" do | options |
36
- pact = options[:pact] || represented.latest_pact_publication
36
+ pact = pact_for(options)
37
37
  {
38
38
  title: "Pact",
39
39
  name: pact.name,
@@ -47,6 +47,16 @@ module PactBroker
47
47
  href: verification_triggered_webhooks_url(represented, options.fetch(:base_url))
48
48
  }
49
49
  end
50
+
51
+ private
52
+
53
+ # A decorator rendering many verifications passes :pacts_by_pact_version_sha
54
+ # so each one avoids its own lookup.
55
+ def pact_for(options)
56
+ options[:pact] ||
57
+ options[:pacts_by_pact_version_sha]&.[](represented.pact_version_sha) ||
58
+ represented.latest_pact_publication
59
+ end
50
60
  end
51
61
  end
52
62
  end
@@ -25,6 +25,12 @@ module PactBroker
25
25
  }
26
26
  end
27
27
 
28
+ # representable passes through the kwargs from to_json as normal args
29
+ def to_hash(options)
30
+ user_options = options.fetch(:user_options, {}).merge(pacts_by_pact_version_sha: represented.pacts_by_pact_version_sha)
31
+ super(options.merge(user_options: user_options))
32
+ end
33
+
28
34
  def provider_summary
29
35
  OpenStruct.new(
30
36
  successful: represented.select(&:success).collect(&:provider_name),
@@ -56,16 +56,20 @@ module PactBroker
56
56
  @parsed_contracts ||= decorator_class(:publish_contracts_decorator).new(PactBroker::Contracts::ContractsToPublish.new).from_hash(params, { user_options: { sha_generator: PactBroker.configuration.sha_generator } } )
57
57
  end
58
58
 
59
+ # Memoized because decoding and parsing very large contracts is expensive,
60
+ # and this is called multiple times per request (validation, authorization and publication)
59
61
  def params
60
- p = super(default: {}, symbolize_names: false)
61
- if p["contracts"].is_a?(Array)
62
- p["contracts"].each do | contract |
63
- if contract.is_a?(Hash)
64
- decode_and_parse_content(contract)
62
+ @params_with_decoded_contracts ||= begin
63
+ p = super(default: {}, symbolize_names: false)
64
+ if p["contracts"].is_a?(Array)
65
+ p["contracts"].each do | contract |
66
+ if contract.is_a?(Hash)
67
+ decode_and_parse_content(contract)
68
+ end
65
69
  end
66
70
  end
71
+ p
67
72
  end
68
- p
69
73
  end
70
74
 
71
75
  def schema
@@ -32,7 +32,15 @@ Example: This data structure represents the way a user might specify "I want to
32
32
 
33
33
  `consumerVersionSelectors.matchingBranch`: if the key is specified, can only be set to `true`. When true, returns the latest pact for any branch with the same name as the specified `providerVersionBranch`.
34
34
 
35
- `consumerVersionSelectors.fallbackBranch`: the name of the branch to fallback to if the specified `branch` does not exist. Use of this property is discouraged as it may allow a pact to pass on a feature branch while breaking backwards compatibility with the main branch, which is generally not desired. It is better to use two separate consumer version selectors, one with the main branch name, and one with the feature branch name, rather than use this property.
35
+ `consumerVersionSelectors.fallbackBranch`: the name of the branch to fall back to for each consumer that has no pact from the specified `branch`. Use of this property is discouraged as it may allow a pact to pass on a feature branch while breaking backwards compatibility with the main branch, which is generally not desired. It is better to use two separate consumer version selectors, one with the main branch name, and one with the feature branch name, rather than use this property.
36
+
37
+ `consumerVersionSelectors.fallbackToMainBranch`: if the key is specified, can only be set to `true`. Used with `branch`. For each consumer that has no pact for the specified `branch`, returns the latest pact from that consumer's configured `mainBranch` instead. Consumers without a configured `mainBranch` have no fallback. Use this instead of `fallbackBranch` when the consumers have different main branch names, or when a main branch may be renamed, so the provider does not need to know the name. It cannot be used together with `fallbackBranch`. The same caveat as `fallbackBranch` applies.
38
+
39
+ {
40
+ "consumerVersionSelectors": [
41
+ { "branch": "feat-x", "fallbackToMainBranch": true }
42
+ ]
43
+ }
36
44
 
37
45
  `consumerVersionSelectors.deployed`: if the key is specified, can only be set to `true`. Returns the pacts for all versions of the consumer that are currently deployed to any environment. Use of this selector requires that the deployment of the consumer application is recorded in the Pact Broker using the `pact-broker record-deployment` CLI.
38
46
 
@@ -48,7 +56,7 @@ Example: This data structure represents the way a user might specify "I want to
48
56
 
49
57
  `consumerVersionSelectors.tag`: the tag name(s) of the consumer versions to get the pacts for. *This field is still supported but it is recommended to use the `branch` in preference now.*
50
58
 
51
- `consumerVersionSelectors.fallbackTag`: the name of the tag to fallback to if the specified `tag` does not exist. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use two separate selectors - one with the main branch name and one with the feature branch name.*
59
+ `consumerVersionSelectors.fallbackTag`: the name of the tag to fall back to for each consumer that has no pact with the specified `tag`. This is useful when the consumer and provider use matching branch names to coordinate the development of new features. *This field is still supported but it is recommended to use two separate selectors - one with the main branch name and one with the feature branch name.*
52
60
 
53
61
  `providerVersionBranch`: the repository branch name for the provider application version that will be published with the verification results. This is used by the Broker to determine whether or not a particular pact is in pending state or not.
54
62
 
@@ -80,8 +80,8 @@ module PactBroker
80
80
  def symbolize_keys_private(params)
81
81
  case params
82
82
  when Hash
83
- params.inject({}) do |result, (key, value)|
84
- result.merge(key.to_sym => symbolize_keys_private(value))
83
+ params.each_with_object({}) do |(key, value), result|
84
+ result[key.to_sym] = symbolize_keys_private(value)
85
85
  end
86
86
  when Array
87
87
  params.collect { |value| symbolize_keys_private(value) }
@@ -105,6 +105,9 @@ en:
105
105
  pacts_for_verification_selector_branch_and_latest_false_disallowed: cannot specify a branch with latest=false
106
106
  pacts_for_verification_selector_main_branch_and_latest_false_disallowed: cannot specify mainBranch=true with latest=false
107
107
  pacts_for_verification_selector_fallback_branch_and_latest_false_disallowed: fallbackBranch can only be set if latest is true
108
+ pacts_for_verification_selector_fallback_to_main_branch_requires_branch: "a branch must be specified when fallbackToMainBranch=true is specified"
109
+ pacts_for_verification_selector_fallback_to_main_branch_and_fallback_branch_disallowed: cannot specify both fallbackBranch and fallbackToMainBranch=true
110
+ pacts_for_verification_selector_fallback_to_main_branch_and_latest_false_disallowed: fallbackToMainBranch can only be set if latest is true
108
111
  pacts_for_verification_selector_required_params_missing: must specify a value for environment or tag or branch, or specify mainBranch=true, matchingBranch=true, latest=true, deployed=true, released=true or deployedOrReleased=true
109
112
  pacts_for_verification_selector_main_branch_with_other_param_disallowed: cannot specify mainBranch=true with any other criteria apart from consumer
110
113
  pacts_for_verification_selector_matching_branch_with_other_param_disallowed: cannot specify matchingBranch=true with any other criteria apart from consumer
@@ -11,17 +11,20 @@ module PactBroker
11
11
  include PactBroker::Logging
12
12
 
13
13
  # @param [String] json_content
14
- def self.call(json_content, _options = {})
14
+ # @param [Hash] options
15
+ # @option options [Hash] :parsed_content the result of parsing the json_content, if it has already been parsed.
16
+ # Used to avoid parsing very large pacts again. Must be the parsed json_content, or the sha will be wrong.
17
+ def self.call(json_content, options = {})
15
18
  content_for_sha = if PactBroker.configuration.base_equality_only_on_content_that_affects_verification_results
16
- extract_verifiable_content_for_sha(json_content)
19
+ extract_verifiable_content_for_sha(json_content, options[:parsed_content])
17
20
  else
18
21
  json_content
19
22
  end
20
23
  measure_info("Generating SHA1 hexdigest for pact", payload: { length: content_for_sha.length } ){ Digest::SHA1.hexdigest(content_for_sha) }
21
24
  end
22
25
 
23
- def self.extract_verifiable_content_for_sha(json_content)
24
- objects = Content.from_json(json_content)
26
+ def self.extract_verifiable_content_for_sha(json_content, parsed_content = nil)
27
+ objects = parsed_content.is_a?(Hash) ? Content.from_hash(parsed_content) : Content.from_json(json_content)
25
28
  sorted_content = measure_info("Sorting content", payload: { length: json_content.length }){ objects.sort }
26
29
  sorted_content.content_that_affects_verification_results.to_json
27
30
  end
@@ -28,7 +28,9 @@ module PactBroker
28
28
  # @return [VerifiablePact] an array of VerifiablePact objects
29
29
  def find(provider_name, consumer_version_selectors)
30
30
  selected_pacts = find_pacts_by_selector(provider_name, consumer_version_selectors)
31
- selected_pacts = selected_pacts + find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors)
31
+ selected_pacts = selected_pacts +
32
+ find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors) +
33
+ find_pacts_for_fallback_branches(selected_pacts, provider_name, consumer_version_selectors)
32
34
  merge_selected_pacts(selected_pacts)
33
35
  end
34
36
 
@@ -178,22 +180,32 @@ module PactBroker
178
180
  end
179
181
  end
180
182
 
183
+ # The fallback is applied per consumer, so a consumer without a pact for the tag
184
+ # still gets its fallback pact when another consumer does have a pact for the tag.
181
185
  def find_pacts_for_fallback_tags(selected_pacts, provider_name, consumer_version_selectors)
182
- # TODO at the moment, the validation doesn't stop fallback being used with 'all' selectors
183
- selectors_with_fallback_tags = consumer_version_selectors.select(&:fallback_tag?)
184
- selectors_missing_a_pact = selectors_with_fallback_tags.reject do | selector |
185
- selected_pacts.any? do | selected_pact |
186
- selected_pact.latest_for_tag?(selector.tag)
186
+ consumer_version_selectors.select(&:fallback_tag?).flat_map do | selector |
187
+ consumer_ids_with_a_pact_for_the_tag = consumer_ids_of(selected_pacts.select { | selected_pact | selected_pact.latest_for_tag?(selector.tag) })
188
+ find_fallback_pacts(provider_name, selector, consumer_ids_with_a_pact_for_the_tag) do | query |
189
+ query.for_latest_consumer_versions_with_tag(selector.fallback_tag)
187
190
  end
188
191
  end
192
+ end
189
193
 
190
- if selectors_missing_a_pact.any?
191
- find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(provider_name, selectors_missing_a_pact)
192
- else
193
- []
194
+ # The fallback is applied per consumer, so a consumer without a pact for the branch
195
+ # still gets its fallback pact when another consumer does have a pact for the branch.
196
+ def find_pacts_for_fallback_branches(selected_pacts, provider_name, consumer_version_selectors)
197
+ consumer_version_selectors.select { | selector | selector.fallback_branch? || selector.fallback_to_main_branch? }.flat_map do | selector |
198
+ consumer_ids_with_a_pact_for_the_branch = consumer_ids_of(selected_pacts.select { | selected_pact | selected_pact.latest_for_branch?(selector.branch) })
199
+ find_fallback_pacts(provider_name, selector, consumer_ids_with_a_pact_for_the_branch) do | query |
200
+ selector.fallback_to_main_branch? ? query.latest_for_main_branches : query.latest_for_consumer_branch(selector.fallback_branch)
201
+ end
194
202
  end
195
203
  end
196
204
 
205
+ def consumer_ids_of(selected_pacts)
206
+ selected_pacts.collect { | selected_pact | selected_pact.pact.consumer.id }.uniq
207
+ end
208
+
197
209
  def find_pacts_by_selector(provider_name, consumer_version_selectors)
198
210
  provider = pacticipant_repository.find_by_name(provider_name)
199
211
 
@@ -248,17 +260,25 @@ module PactBroker
248
260
  selectors
249
261
  end
250
262
 
251
- def find_pacts_for_which_the_latest_version_for_the_fallback_tag_is_required(provider_name, selectors)
252
- selectors.collect do | selector |
253
- query = scope_for(PactPublication).eager_for_domain_with_content.for_provider_name(provider_name).for_latest_consumer_versions_with_tag(selector.fallback_tag)
254
- query = query.for_consumer_name(selector.consumer) if selector.consumer
255
- query.all.collect do | pact_publication |
256
- SelectedPact.new(
257
- pact_publication.to_domain,
258
- Selectors.new(selector.resolve_for_fallback(pact_publication.consumer_version))
259
- )
260
- end
261
- end.flatten
263
+ # Yields the provider's pacts so the caller can narrow them to the latest for the fallback.
264
+ def find_fallback_pacts(provider_name, selector, consumer_ids_to_exclude)
265
+ query = yield scope_for(PactPublication).eager_for_domain_with_content.for_provider_name(provider_name)
266
+ query = query.for_consumer_name(selector.consumer) if selector.consumer
267
+ query = query.exclude(Sequel[:pact_publications][:consumer_id] => consumer_ids_to_exclude) if consumer_ids_to_exclude.any?
268
+ query.all.collect do | pact_publication |
269
+ SelectedPact.new(
270
+ pact_publication.to_domain,
271
+ Selectors.new(resolve_selector_for_fallback(selector, pact_publication))
272
+ )
273
+ end
274
+ end
275
+
276
+ def resolve_selector_for_fallback(selector, pact_publication)
277
+ if selector.fallback_to_main_branch?
278
+ selector.resolve_for_fallback_to_main_branch(pact_publication.consumer_version, pact_publication.consumer.main_branch)
279
+ else
280
+ selector.resolve_for_fallback(pact_publication.consumer_version)
281
+ end
262
282
  end
263
283
 
264
284
  def find_provider_tags_for_which_pact_publication_id_is_pending(pact_publication, successfully_verified_head_pact_publication_ids_for_each_provider_tag)
@@ -202,6 +202,7 @@ module PactBroker
202
202
  def find_by_consumer_version consumer_name, consumer_version_number
203
203
  scope_for(PactPublication)
204
204
  .eager_for_domain_with_content
205
+ .eager(pact_version: :latest_verification)
205
206
  .for_consumer_name_and_maybe_version_number(consumer_name, consumer_version_number)
206
207
  .remove_overridden_revisions_from_complete_query
207
208
  .all
@@ -57,6 +57,10 @@ module PactBroker
57
57
  selectors.latest_for_tag?(potential_tag)
58
58
  end
59
59
 
60
+ def latest_for_branch? potential_branch = nil
61
+ selectors.latest_for_branch?(potential_branch)
62
+ end
63
+
60
64
  def consumer_version_order
61
65
  pact.consumer_version.order
62
66
  end
@@ -6,7 +6,7 @@ module PactBroker
6
6
  class Selector < Hash
7
7
  using PactBroker::HashRefinements
8
8
 
9
- PROPERTY_NAMES = [:latest, :tag, :branch, :consumer, :consumer_version, :environment_name, :fallback_tag, :fallback_branch, :main_branch, :matching_branch, :currently_supported, :currently_deployed]
9
+ PROPERTY_NAMES = [:latest, :tag, :branch, :consumer, :consumer_version, :environment_name, :fallback_tag, :fallback_branch, :fallback_to_main_branch, :main_branch, :matching_branch, :currently_supported, :currently_deployed]
10
10
 
11
11
  def initialize(properties = {})
12
12
  properties.without(*PROPERTY_NAMES).tap { |it| warn("WARN: Unsupported property for #{self.class.name}: #{it.keys.join(", ")} at #{caller[0..3]}") if it.any? }
@@ -14,13 +14,19 @@ module PactBroker
14
14
  end
15
15
 
16
16
  def resolve(consumer_version)
17
- ResolvedSelector.new(self.to_h.without(:fallback_tag, :fallback_branch), consumer_version)
17
+ ResolvedSelector.new(self.to_h.without(:fallback_tag, :fallback_branch, :fallback_to_main_branch), consumer_version)
18
18
  end
19
19
 
20
20
  def resolve_for_fallback(consumer_version)
21
21
  ResolvedSelector.new(self.to_h, consumer_version)
22
22
  end
23
23
 
24
+ # Records the consumer's main branch as the fallback branch, so the verification
25
+ # notice names the branch that was actually used.
26
+ def resolve_for_fallback_to_main_branch(consumer_version, main_branch)
27
+ ResolvedSelector.new(self.to_h.without(:fallback_to_main_branch).merge(fallback_branch: main_branch), consumer_version)
28
+ end
29
+
24
30
  def resolve_for_environment(consumer_version, environment, target = nil)
25
31
  ResolvedSelector.new(self.to_h.merge({ environment: environment, target: target }.compact), consumer_version)
26
32
  end
@@ -87,6 +93,10 @@ module PactBroker
87
93
  self[:fallback_branch] = fallback_branch
88
94
  end
89
95
 
96
+ def fallback_to_main_branch= fallback_to_main_branch
97
+ self[:fallback_to_main_branch] = fallback_to_main_branch
98
+ end
99
+
90
100
  def fallback_tag
91
101
  self[:fallback_tag]
92
102
  end
@@ -95,6 +105,10 @@ module PactBroker
95
105
  self[:fallback_branch]
96
106
  end
97
107
 
108
+ def fallback_to_main_branch
109
+ self[:fallback_to_main_branch]
110
+ end
111
+
98
112
  def consumer= consumer
99
113
  self[:consumer] = consumer
100
114
  end
@@ -235,6 +249,10 @@ module PactBroker
235
249
  !!fallback_branch
236
250
  end
237
251
 
252
+ def fallback_to_main_branch?
253
+ !!fallback_to_main_branch
254
+ end
255
+
238
256
  def main_branch
239
257
  self[:main_branch]
240
258
  end
@@ -59,6 +59,10 @@ module PactBroker
59
59
  any? { | selector | selector.latest_for_tag?(potential_tag) }
60
60
  end
61
61
 
62
+ def latest_for_branch? potential_branch = nil
63
+ any? { | selector | selector.latest_for_branch?(potential_branch) }
64
+ end
65
+
62
66
  def tag_names_of_selectors_for_all_pacts
63
67
  select(&:all_for_tag?).collect(&:tag).uniq
64
68
  end
@@ -99,7 +99,7 @@ module PactBroker
99
99
  .remove_verifications_for_overridden_consumer_versions
100
100
  .for_consumer_name_and_consumer_version_number(consumer_name, consumer_version_number)
101
101
  .latest_by_pact_version
102
- .eager(:provider)
102
+ .eager(:provider, :consumer, :provider_version, :pact_version)
103
103
  .all
104
104
  .sort_by { | v | v.provider_name.downcase }
105
105
  end
@@ -13,6 +13,10 @@ module PactBroker
13
13
  successful.count == pacts.count
14
14
  end
15
15
 
16
+ def pacts_by_pact_version_sha
17
+ pacts.to_h { | pact | [pact.pact_version_sha, pact] }
18
+ end
19
+
16
20
  def provider_summary
17
21
  OpenStruct.new(
18
22
  successful: successful,
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = "2.122.0"
2
+ VERSION = "2.123.0"
3
3
  end
data/pact_broker.gemspec CHANGED
@@ -64,7 +64,7 @@ Gem::Specification.new do |gem|
64
64
  gem.add_runtime_dependency "rack-protection", "~> 4.1"
65
65
  gem.add_runtime_dependency "table_print", "~> 1.5"
66
66
  gem.add_runtime_dependency "semantic_logger", "~> 5.0"
67
- gem.add_runtime_dependency "sanitize", "~> 6.0"
67
+ gem.add_runtime_dependency "sanitize", "~> 7.0"
68
68
  gem.add_runtime_dependency "wisper", "~> 2.0"
69
69
  gem.add_runtime_dependency "anyway_config", "~> 2.1"
70
70
  gem.add_runtime_dependency "request_store", "~> 1.5"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pact_broker
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.122.0
4
+ version: 2.123.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bethany Skurrie
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-10-01 00:00:00.000000000 Z
13
+ date: 2026-11-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -228,14 +228,14 @@ dependencies:
228
228
  requirements:
229
229
  - - "~>"
230
230
  - !ruby/object:Gem::Version
231
- version: '6.0'
231
+ version: '7.0'
232
232
  type: :runtime
233
233
  prerelease: false
234
234
  version_requirements: !ruby/object:Gem::Requirement
235
235
  requirements:
236
236
  - - "~>"
237
237
  - !ruby/object:Gem::Version
238
- version: '6.0'
238
+ version: '7.0'
239
239
  - !ruby/object:Gem::Dependency
240
240
  name: wisper
241
241
  requirement: !ruby/object:Gem::Requirement