pact_broker 2.42.0 → 2.43.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.
@@ -82,12 +82,8 @@ module PactBroker
82
82
  def find_integrations_for_specified_selectors(resolved_specified_selectors)
83
83
  specified_pacticipant_names = resolved_specified_selectors.collect(&:pacticipant_name)
84
84
  QuickRow
85
- .select(:consumer_name, :consumer_id, :provider_name, :provider_id)
86
- .matching_selectors(resolved_specified_selectors)
87
- .distinct
88
- .all
85
+ .distinct_integrations(resolved_specified_selectors)
89
86
  .collect(&:to_hash)
90
- .uniq
91
87
  .collect do | hash |
92
88
  required = is_a_row_for_this_integration_required?(specified_pacticipant_names, hash[:consumer_name])
93
89
  Integration.from_hash(hash.merge(required: required))
@@ -122,8 +118,8 @@ module PactBroker
122
118
  end
123
119
 
124
120
  def query_matrix selectors, options
125
- query = options[:latestby] ? QuickRow.eager_all_the_things : Row
126
- query = query.select_all.matching_selectors(selectors)
121
+ query = options[:latestby] ? QuickRow.select_all_columns.eager_all_the_things : Row.select_all
122
+ query = query.matching_selectors(selectors)
127
123
  query = query.limit(options[:limit]) if options[:limit]
128
124
  query
129
125
  .order_by_names_ascending_most_recent_first
@@ -216,26 +212,7 @@ module PactBroker
216
212
  end
217
213
 
218
214
  def selector_for_version(pacticipant, version, original_selector, selector_type, latestby)
219
- pact_publication_ids, verification_ids = nil, nil
220
-
221
- # Querying for the "pre-filtered" pact publications and verifications directly, rather than just using the consumer
222
- # and provider versions allows us to remove the "overwritten" pact revisions and verifications from the
223
- # matrix result set, making the final matrix query more efficient and stopping the query limit from
224
- # removing relevant data (eg. https://github.com/pact-foundation/pact_broker-client/issues/53)
225
- if latestby
226
- pact_publication_ids = PactBroker::Pacts::LatestPactPublicationsByConsumerVersion
227
- .select(:id)
228
- .where(consumer_version_id: version.id)
229
- .collect{ |pact_publication| pact_publication[:id] }
230
-
231
- verification_ids = PactBroker::Verifications::LatestVerificationIdForPactVersionAndProviderVersion
232
- .select(:verification_id)
233
- .distinct
234
- .where(provider_version_id: version.id)
235
- .collect{ |pact_publication| pact_publication[:verification_id] }
236
- end
237
-
238
- ResolvedSelector.for_pacticipant_and_version(pacticipant, version, pact_publication_ids, verification_ids, original_selector, selector_type)
215
+ ResolvedSelector.for_pacticipant_and_version(pacticipant, version, original_selector, selector_type)
239
216
  end
240
217
 
241
218
  def selector_without_version(pacticipant, selector_type)
@@ -23,13 +23,11 @@ module PactBroker
23
23
  )
24
24
  end
25
25
 
26
- def self.for_pacticipant_and_version(pacticipant, version, pact_publication_ids = [], verification_ids = [], original_selector, type)
26
+ def self.for_pacticipant_and_version(pacticipant, version, original_selector, type)
27
27
  ResolvedSelector.new(
28
28
  pacticipant_id: pacticipant.id,
29
29
  pacticipant_name: pacticipant.name,
30
30
  pacticipant_version_id: version.id,
31
- pact_publication_ids: pact_publication_ids,
32
- verification_ids: verification_ids,
33
31
  pacticipant_version_number: version.number,
34
32
  latest: original_selector[:latest],
35
33
  tag: original_selector[:tag],
@@ -73,6 +71,14 @@ module PactBroker
73
71
  self[:tag]
74
72
  end
75
73
 
74
+ def most_specific_criterion
75
+ if pacticipant_version_id
76
+ { pacticipant_version_id: pacticipant_version_id }
77
+ else
78
+ { pacticipant_id: pacticipant_id }
79
+ end
80
+ end
81
+
76
82
  def latest_tagged?
77
83
  latest? && tag
78
84
  end
@@ -73,16 +73,23 @@ module PactBroker
73
73
  self
74
74
  end
75
75
 
76
- def create_pact_with_hierarchy consumer_name = "Consumer", consumer_version = "1.2.3", provider_name = "Provider", json_content = default_json_content
76
+ def create_pact_with_hierarchy consumer_name = "Consumer", consumer_version_number = "1.2.3", provider_name = "Provider", json_content = default_json_content
77
77
  use_consumer(consumer_name)
78
78
  create_consumer(consumer_name) if !consumer
79
79
  use_provider(provider_name)
80
80
  create_provider provider_name if !provider
81
- create_consumer_version consumer_version
81
+ use_consumer_version(consumer_version_number)
82
+ create_consumer_version(consumer_version_number) if !consumer_version
82
83
  create_pact json_content: json_content
83
84
  self
84
85
  end
85
86
 
87
+ def create_pact_with_verification consumer_name = "Consumer", consumer_version = "1.0.#{model_counter}", provider_name = "Provider", provider_version = "1.0.#{model_counter}"
88
+ create_pact_with_hierarchy(consumer_name, consumer_version, provider_name)
89
+ create_verification(number: model_counter, provider_version: provider_version)
90
+ self
91
+ end
92
+
86
93
  def create_version_with_hierarchy pacticipant_name, pacticipant_version
87
94
  pacticipant = pacticipant_service.create(:name => pacticipant_name)
88
95
  version = PactBroker::Domain::Version.create(:number => pacticipant_version, :pacticipant => pacticipant)
@@ -325,6 +332,16 @@ module PactBroker
325
332
  self
326
333
  end
327
334
 
335
+ def create_everything_for_an_integration
336
+ create_pact_with_verification("Foo", "1", "Bar", "2")
337
+ .create_label("label")
338
+ .create_consumer_version_tag("master")
339
+ .create_provider_version_tag("master")
340
+ .create_webhook
341
+ .create_triggered_webhook
342
+ .create_webhook_execution
343
+ end
344
+
328
345
  def model_counter
329
346
  @@model_counter ||= 0
330
347
  @@model_counter += 1
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '2.42.0'
2
+ VERSION = '2.43.0'
3
3
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module PactBroker
5
+ module RequestTarget
6
+ extend self
7
+
8
+ WEB_ASSET_EXTENSIONS = %w[.js .woff .woff2 .css .png .html .map .ttf .ico].freeze
9
+ API_CONTENT_TYPES = %w[application/hal+json application/json text/csv application/yaml].freeze
10
+
11
+ def request_for_ui?(env)
12
+ !(request_for_api?(env))
13
+ end
14
+
15
+ def request_for_api?(env)
16
+ explicit_request_for_api(env) || no_accept_header(env) || (accept_all(env) && !is_web_extension(env))
17
+ end
18
+
19
+ private
20
+
21
+ def body_is_json(env)
22
+ env['CONTENT_TYPE']&.include?("json")
23
+ end
24
+
25
+ def explicit_request_for_api(env)
26
+ accepts_api_content_type(env) || body_is_api_content_type(env)
27
+ end
28
+
29
+ def accepts_api_content_type(env)
30
+ is_api_content_type((env['HTTP_ACCEPT']&.downcase) || "")
31
+ end
32
+
33
+ def body_is_api_content_type(env)
34
+ is_api_content_type((env['CONTENT_TYPE']&.downcase) || "")
35
+ end
36
+
37
+ def is_api_content_type(header)
38
+ API_CONTENT_TYPES.any?{ |content_type| header.include?(content_type) }
39
+ end
40
+
41
+ # default curl Accept header
42
+ # Also used by browsers to request various web assets like woff files
43
+ def accept_all(env)
44
+ env['HTTP_ACCEPT'] == "*/*"
45
+ end
46
+
47
+ # No browser ever makes a request without an accept header, so it must be an API
48
+ # request if there is no Accept header
49
+ def no_accept_header(env)
50
+ env['HTTP_ACCEPT'] == nil || env['HTTP_ACCEPT'] == ""
51
+ end
52
+
53
+ def is_web_extension(env)
54
+ env['PATH_INFO'].end_with?(*WEB_ASSET_EXTENSIONS)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -4,62 +4,25 @@
4
4
  # to actually handle the request, as it would short circuit the cascade
5
5
  # logic.
6
6
 
7
+ require 'rack/pact_broker/request_target'
8
+
7
9
  module Rack
8
10
  module PactBroker
9
11
  class UIRequestFilter
10
- WEB_ASSET_EXTENSIONS = %w[.js .woff .woff2 .css .png .html .map .ttf .ico].freeze
11
- API_CONTENT_TYPES = %w[application/hal+json application/json text/csv application/yaml].freeze
12
+ include RequestTarget
12
13
 
13
14
  def initialize app
14
15
  @app = app
15
16
  end
16
17
 
17
18
  def call env
18
- if request_for_api(env) || no_accept_header(env) || (accept_all(env) && !is_web_extension(env))
19
+ if request_for_ui?(env)
20
+ @app.call(env)
21
+ else
19
22
  # send the request on to the next app in the Rack::Cascade
20
23
  [404, {},[]]
21
- else
22
- @app.call(env)
23
24
  end
24
25
  end
25
-
26
- private
27
-
28
- def body_is_json(env)
29
- env['CONTENT_TYPE'] && env['CONTENT_TYPE'].include?("json")
30
- end
31
-
32
- def request_for_api(env)
33
- accepts_api_content_type(env) || body_is_api_content_type(env)
34
- end
35
-
36
- def accepts_api_content_type(env)
37
- is_api_content_type((env['HTTP_ACCEPT'] && env['HTTP_ACCEPT'].downcase) || "")
38
- end
39
-
40
- def body_is_api_content_type(env)
41
- is_api_content_type((env['CONTENT_TYPE'] && env['CONTENT_TYPE'].downcase) || "")
42
- end
43
-
44
- def is_api_content_type(header)
45
- API_CONTENT_TYPES.any?{ |content_type| header.include?(content_type) }
46
- end
47
-
48
- # default curl Accept header
49
- # Also used by browsers to request various web assets like woff files
50
- def accept_all(env)
51
- env['HTTP_ACCEPT'] == "*/*"
52
- end
53
-
54
- # No browser ever makes a request without an accept header, so it must be an API
55
- # request if there is no Accept header
56
- def no_accept_header(env)
57
- env['HTTP_ACCEPT'] == nil || env['HTTP_ACCEPT'] == ""
58
- end
59
-
60
- def is_web_extension(env)
61
- env['PATH_INFO'].end_with?(*WEB_ASSET_EXTENSIONS)
62
- end
63
26
  end
64
27
  end
65
28
  end
@@ -51,14 +51,14 @@ Gem::Specification.new do |gem|
51
51
  gem.add_runtime_dependency 'sequel', '~> 5.6'
52
52
  gem.add_runtime_dependency 'webmachine', '1.5.0'
53
53
  gem.add_runtime_dependency 'semver2', '~> 3.4.2'
54
- gem.add_runtime_dependency 'rack', '>= 2.0.6', '~>2.0'
54
+ gem.add_runtime_dependency 'rack', '>= 2.0.8', '~>2.0'
55
55
  gem.add_runtime_dependency 'redcarpet', '>=3.3.2', '~>3.3'
56
56
  gem.add_runtime_dependency 'pact-support'
57
57
  gem.add_runtime_dependency 'padrino-core', '>= 0.14.3', '~> 0.14'
58
- gem.add_runtime_dependency 'sinatra', '>= 2.0.2'
58
+ gem.add_runtime_dependency 'sinatra', '>= 2.0.8.1', '< 3.0'
59
59
  gem.add_runtime_dependency 'haml', '~>5.0'
60
60
  gem.add_runtime_dependency 'sucker_punch', '~>2.0'
61
- gem.add_runtime_dependency 'rack-protection', '~>2.0'
61
+ gem.add_runtime_dependency 'rack-protection', '>= 2.0.8.1', '< 3.0'
62
62
  gem.add_runtime_dependency 'dry-types', '~> 0.10.3' # https://travis-ci.org/pact-foundation/pact_broker/jobs/249448621
63
63
  gem.add_runtime_dependency 'dry-logic', '0.4.2' # Later version cases ArgumentError: wrong number of arguments
64
64
  gem.add_runtime_dependency 'table_print', '~> 1.5'
@@ -219,6 +219,25 @@ module PactBroker
219
219
  end
220
220
  end
221
221
 
222
+ context "when a timeout exception is raised connecting to the shields.io server" do
223
+ before do
224
+ allow(Net::HTTP).to receive(:start).and_raise(Net::OpenTimeout)
225
+ end
226
+
227
+ it "logs a warning rather than an error as this will happen reasonably often" do
228
+ expect(logger).to receive(:warn).with(/Timeout retrieving badge from.*shield.*Net::OpenTimeout/)
229
+ subject
230
+ end
231
+
232
+ it "returns a static image" do
233
+ expect(subject).to include ">pact</"
234
+ end
235
+
236
+ it "does not cache the response" do
237
+ expect(Service::CACHE.size).to eq 0
238
+ end
239
+ end
240
+
222
241
  context "when an exception is raised connecting to the shields.io server" do
223
242
  before do
224
243
  allow(Net::HTTP).to receive(:start).and_raise("an error")
@@ -249,6 +249,16 @@ module PactBroker
249
249
  end
250
250
  end
251
251
  end
252
+
253
+ describe "delete_all" do
254
+ before do
255
+ td.create_everything_for_an_integration
256
+ end
257
+
258
+ it "doesn't blow up" do
259
+ Service.delete_all
260
+ end
261
+ end
252
262
  end
253
263
  end
254
264
  end
@@ -190,8 +190,6 @@ module PactBroker
190
190
  pacticipant_name: "Bar",
191
191
  pacticipant_version_id: 10,
192
192
  pacticipant_version_number: "14131c5da3abf323ccf410b1b619edac76231243",
193
- pact_publication_ids: [],
194
- verification_ids: [],
195
193
  latest: nil,
196
194
  tag: nil,
197
195
  type: :inferred
@@ -4,6 +4,7 @@ module PactBroker
4
4
  module Matrix
5
5
  describe Service do
6
6
  let(:td) { TestDataBuilder.new }
7
+
7
8
  describe "find" do
8
9
  subject { Service.find(selectors, options) }
9
10
 
@@ -236,6 +237,45 @@ module PactBroker
236
237
  expect(subject.deployment_status_summary.deployable?).to be true
237
238
  end
238
239
  end
240
+
241
+ describe "when two applications have pacts with each other (nureva use case)" do
242
+ # ServiceA v 1 has been verified by ServiceB v 100
243
+ # but ServiceB v 100 has only been verified by ServiceA v 99.
244
+ # It's missing a verification from ServiceA v1.
245
+ before do
246
+ td.create_pact_with_verification("ServiceB", "100", "ServiceA", "99")
247
+ .create_pact_with_verification("ServiceA", "1", "ServiceB", "100")
248
+ end
249
+
250
+ context "when both application versions are specified explictly" do
251
+ let(:selectors) do
252
+ [
253
+ { pacticipant_name: "ServiceA", pacticipant_version_number: "1" },
254
+ { pacticipant_name: "ServiceB", pacticipant_version_number: "100" }
255
+ ]
256
+ end
257
+
258
+ let(:options) { { latestby: "cvpv" } }
259
+
260
+ it "does not allow the two apps to be deployed together" do
261
+ expect(subject.deployment_status_summary.deployable?).to_not be true
262
+ end
263
+ end
264
+
265
+ context "when only one application is specified" do
266
+ let(:selectors) do
267
+ [
268
+ { pacticipant_name: "ServiceB", pacticipant_version_number: "100" }
269
+ ]
270
+ end
271
+
272
+ let(:options) { { latestby: "cvp", latest: true } }
273
+
274
+ it "does not allow the two apps to be deployed together" do
275
+ expect(subject.deployment_status_summary.deployable?).to_not be true
276
+ end
277
+ end
278
+ end
239
279
  end
240
280
  end
241
281
  end
@@ -1,4 +1,5 @@
1
1
  require 'pact_broker/matrix/quick_row'
2
+ require 'pact_broker/matrix/resolved_selector'
2
3
 
3
4
  module PactBroker
4
5
  module Matrix
@@ -18,9 +19,9 @@ module PactBroker
18
19
  .create_pact
19
20
  end
20
21
 
21
- it "behaves like a Row, except smarter" do
22
+ it "behaves like a Row, except quicker" do
22
23
  a_id = QuickRow.db[:pacticipants].where(name: "A").select(:id).single_record[:id]
23
- rows = QuickRow.consumer_id(a_id).eager(:consumer).eager(:verification).all
24
+ rows = QuickRow.default_scope.where(consumer_id: a_id).eager(:consumer).eager(:verification).all
24
25
  expect(rows.first.consumer).to be rows.last.consumer
25
26
  expect(rows.first.verification).to_not be nil
26
27
  expect(rows.first.consumer_name).to_not be nil
@@ -633,7 +633,7 @@ module PactBroker
633
633
  end
634
634
  end
635
635
 
636
- context "when the latest version is specified for a provider without a tag but the latest known version for a provider does not have a verification" do
636
+ context "when the latest version is specified for a provider ignoring tags but the latest known version for a provider does not have a verification" do
637
637
  before do
638
638
  td.create_pact_with_hierarchy("A", "1.2.3", "B")
639
639
  .create_verification(provider_version: "1.0.0")
@@ -647,7 +647,7 @@ module PactBroker
647
647
  ]
648
648
  end
649
649
 
650
- it "returns no data - this may be confusing. Might need to re-think this logic." do
650
+ it "returns no data" do
651
651
  expect(subject.size).to eq 0
652
652
  end
653
653
  end
@@ -981,6 +981,8 @@ module PactBroker
981
981
  end
982
982
 
983
983
  describe "find pact_broker-client issue 33" do
984
+ # foo1.0.0 bar10.0.0 n1
985
+ # foo1.0.0 baz9.0.0 n1
984
986
  before do
985
987
  td
986
988
  .create_consumer("foo")
@@ -1003,7 +1005,7 @@ module PactBroker
1003
1005
  subject { shorten_rows(results) }
1004
1006
 
1005
1007
  it "only returns a row for the foo pact version that has been verified by the current production version of bar" do
1006
- expect(subject).to eq ["foo1.0.0 bar10.0.0 n1"]
1008
+ expect(subject).to eq ["foo1.0.0 bar10.0.0 n1", "foo1.0.0 baz? n?"]
1007
1009
  end
1008
1010
 
1009
1011
  it "returns 2 integrations" do
@@ -1,67 +1,60 @@
1
- require 'rack/pact_broker/ui_request_filter'
2
- require 'rack/test'
1
+ require 'rack/pact_broker/request_target'
3
2
 
4
3
  module Rack
5
4
  module PactBroker
6
- describe UIRequestFilter do
7
- include Rack::Test::Methods
5
+ describe RequestTarget do
6
+ let(:rack_env) do
7
+ {
8
+ 'CONTENT_TYPE' => content_type,
9
+ 'HTTP_ACCEPT' => accept,
10
+ 'PATH_INFO' => path
11
+ }
12
+ end
13
+ let(:content_type) { nil }
14
+ let(:accept) { nil }
15
+ let(:path) { '' }
8
16
 
9
- describe "#call" do
10
- let(:target_app) { double('target_app', call: [200, {}, []]) }
11
- let(:app) { UIRequestFilter.new(target_app) }
17
+ describe "#request_for_ui?" do
12
18
  let(:path) { "/" }
13
- let(:accept) { "text/html" }
14
19
 
15
- subject { get path, nil, { "HTTP_ACCEPT" => accept } }
20
+ subject { RequestTarget.request_for_ui?(rack_env) }
16
21
 
17
22
  context "when the Accept header includes text/html" do
18
- it "forwards the request to the target app" do
19
- expect(target_app).to receive(:call)
20
- subject
21
- end
23
+ let(:accept) { "text/html" }
24
+
25
+ it { is_expected.to be true }
22
26
  end
23
27
 
24
28
  context "when the request is for a web asset with an Accept header of */*" do
25
29
  let(:path) { "/blah/foo.woff" }
26
30
  let(:accept) { "*/*" }
27
31
 
28
- it "forwards the request to the target app" do
29
- expect(target_app).to receive(:call)
30
- subject
31
- end
32
+ it { is_expected.to be true }
32
33
  end
33
34
 
34
35
  context "when the request is for a content type served by the API (HAL browser request)" do
35
36
  let(:accept) { "application/hal+json, application/json, */*; q=0.01" }
36
37
 
37
- it "returns a 404" do
38
- expect(subject.status).to eq 404
39
- end
38
+ it { is_expected.to be false }
40
39
  end
41
40
 
42
41
  context "when the request is not for a web asset and the Accept headers is */* (default Accept header from curl request)" do
43
42
  let(:accept) { "*/*" }
44
43
 
45
- it "returns a 404" do
46
- expect(subject.status).to eq 404
47
- end
44
+ it { is_expected.to be false }
48
45
  end
49
46
 
50
47
  context "when the request is not for a web asset and no Accept header is specified" do
51
48
  let(:accept) { nil }
52
49
 
53
- it "returns a 404" do
54
- expect(subject.status).to eq 404
55
- end
50
+ it { is_expected.to be false }
56
51
  end
57
52
 
58
53
  context "when the request ends in a web asset extension but has Accept application/hal+json" do
59
54
  let(:accept) { "application/hal+json" }
60
55
  let(:path) { "/blah/foo.woff" }
61
56
 
62
- it "returns a 404" do
63
- expect(subject.status).to eq 404
64
- end
57
+ it { is_expected.to be false }
65
58
  end
66
59
  end
67
60
  end