pact_broker 2.42.0 → 2.43.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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