pact_broker 2.27.6 → 2.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/.travis.yml +1 -2
  4. data/CHANGELOG.md +38 -0
  5. data/MATRIX.md +4 -0
  6. data/README.md +3 -2
  7. data/Rakefile +1 -2
  8. data/config/database.yml +5 -0
  9. data/lib/pact_broker/api/decorators/matrix_decorator.rb +7 -1
  10. data/lib/pact_broker/api/decorators/reason_decorator.rb +50 -0
  11. data/lib/pact_broker/api/resources/base_resource.rb +1 -1
  12. data/lib/pact_broker/api/resources/error_handler.rb +32 -9
  13. data/lib/pact_broker/app.rb +16 -1
  14. data/lib/pact_broker/domain/webhook_request.rb +1 -1
  15. data/lib/pact_broker/matrix/deployment_status_summary.rb +123 -44
  16. data/lib/pact_broker/matrix/head_row.rb +20 -0
  17. data/lib/pact_broker/matrix/integration.rb +49 -7
  18. data/lib/pact_broker/matrix/query_results.rb +3 -2
  19. data/lib/pact_broker/matrix/query_results_with_deployment_status_summary.rb +2 -2
  20. data/lib/pact_broker/matrix/reason.rb +74 -0
  21. data/lib/pact_broker/matrix/repository.rb +97 -61
  22. data/lib/pact_broker/matrix/resolved_selector.rb +126 -0
  23. data/lib/pact_broker/matrix/row.rb +8 -1
  24. data/lib/pact_broker/matrix/service.rb +2 -16
  25. data/lib/pact_broker/pacts/repository.rb +15 -5
  26. data/lib/pact_broker/repositories/helpers.rb +3 -2
  27. data/lib/pact_broker/ui/views/index/_navbar.haml +14 -0
  28. data/lib/pact_broker/ui/views/index/show-with-tags.haml +1 -12
  29. data/lib/pact_broker/ui/views/index/show.haml +1 -12
  30. data/lib/pact_broker/ui/views/layouts/main.haml +3 -0
  31. data/lib/pact_broker/version.rb +1 -1
  32. data/lib/pact_broker/webhooks/job.rb +11 -5
  33. data/lib/pact_broker/webhooks/service.rb +10 -2
  34. data/lib/pact_broker/webhooks/webhook.rb +4 -0
  35. data/lib/rack/pact_broker/database_transaction.rb +22 -0
  36. data/pact_broker.gemspec +25 -1
  37. data/script/restart.sh +18 -0
  38. data/script/watch.sh +7 -0
  39. data/spec/features/publish_verification_spec.rb +1 -1
  40. data/spec/integration/app_spec.rb +1 -1
  41. data/spec/integration/webhooks/certificate_spec.rb +10 -2
  42. data/spec/lib/pact_broker/api/decorators/matrix_decorator_spec.rb +2 -1
  43. data/spec/lib/pact_broker/api/decorators/reason_decorator_spec.rb +59 -0
  44. data/spec/lib/pact_broker/api/resources/error_handler_spec.rb +84 -21
  45. data/spec/lib/pact_broker/app_spec.rb +22 -0
  46. data/spec/lib/pact_broker/domain/webhook_request_spec.rb +1 -1
  47. data/spec/lib/pact_broker/matrix/deployment_status_summary_spec.rb +116 -28
  48. data/spec/lib/pact_broker/matrix/head_row_spec.rb +23 -0
  49. data/spec/lib/pact_broker/matrix/integration_spec.rb +242 -0
  50. data/spec/lib/pact_broker/matrix/repository_dependency_spec.rb +58 -0
  51. data/spec/lib/pact_broker/matrix/repository_spec.rb +40 -7
  52. data/spec/lib/pact_broker/matrix/service_spec.rb +0 -50
  53. data/spec/lib/pact_broker/pacts/repository_spec.rb +20 -4
  54. data/spec/lib/pact_broker/webhooks/job_spec.rb +9 -9
  55. data/spec/lib/pact_broker/webhooks/service_spec.rb +3 -3
  56. data/spec/lib/pact_broker/webhooks/webhook_spec.rb +39 -0
  57. data/spec/lib/rack/pact_broker/database_transaction_spec.rb +40 -0
  58. data/spec/service_consumers/pact_helper.rb +2 -0
  59. data/spec/spec_helper.rb +2 -3
  60. data/spec/support/jobs.rb +12 -0
  61. data/spec/support/migration_helpers.rb +1 -1
  62. data/spec/support/simplecov.rb +10 -0
  63. data/tasks/audit.rake +2 -0
  64. data/tasks/pact.rake +5 -1
  65. data/tasks/rspec.rake +14 -0
  66. metadata +50 -5
  67. data/db/pact_broker_database.sqlite3 +0 -0
  68. data/spec/lib/pact_broker/matrix/repository_find_integrations_spec.rb +0 -51
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e90caf2f7b3f95a72409e71d887025c4af961ca
4
- data.tar.gz: c7e7ffdac02f644178308f80420500dc5729e77a
3
+ metadata.gz: d618dcd0c7582bad7822bac6bf8b32db26a25cea
4
+ data.tar.gz: 2a0d1f85dbdb9d2291308e483d60422431fe4013
5
5
  SHA512:
6
- metadata.gz: 334d5d897be6dd5886cdf761d4d6cbad10ab46f23acca55e05736b91e9ba651c40347b99e051a04451380135572c868bf829afd099362e30a066c72fc8ca17d3
7
- data.tar.gz: e92686f872dc74ab1ded642e4a76c63eb39520bd77549d543416c8e3dce7e84ba7703beacf52f798ab306d7f5ce95cca7f965f9e7999df2f9aedf67372db6776
6
+ metadata.gz: f7ee32711c4be853ccfb0d0eefd62c87ec9aad7407c2344a0d12c9537aeaa88483e0fce93c33e1ed51389c98f41dae1125f11ce47869e5b73a11e866473c03aa
7
+ data.tar.gz: 3b8217e5d02bff7be409a8dbfa51a4caf8ca1f6dfb2132b470926100efa7aa5c2dd9c85525ae4bd54c8997091c2c2495ebce785342dc4341c41b7562b093edf8
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ .DS_Store
1
2
  *.gem
2
3
  *.rbc
3
4
  .bundle
@@ -6,6 +7,7 @@ coverage
6
7
  InstalledFiles
7
8
  lib/bundler/man
8
9
  log
10
+ *.log
9
11
  pkg
10
12
  rdoc
11
13
  spec/reports
@@ -25,11 +27,12 @@ _yardoc
25
27
  *.swp
26
28
  .idea
27
29
 
28
- *.sqlite
30
+ *.sqlite3
29
31
  Gemfile.lock
30
32
 
31
33
  bethtest/
32
34
  *bethtest*
35
+ *bethtemp*
33
36
  *dogfood*
34
37
  bin/
35
38
  db/test/change_migration_strategy/pact_broker_database.sqlite3
data/.travis.yml CHANGED
@@ -26,5 +26,4 @@ before_script:
26
26
  script:
27
27
  - bundle exec rake
28
28
  - if [ "$DATABASE_ADAPTER" == "postgres" ]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT || true; fi
29
- # - if [ ! -z "$(ruby --version | grep '2\.4\.')" ]; then ./script/db-spec.sh; fi
30
- - bundle exec bundle-audit update && bundle exec bundle-audit
29
+ # - if [ ! -z "$(ruby --version | grep '2\.4\.')" ]; then ./script/db-spec.sh; fi
data/CHANGELOG.md CHANGED
@@ -1,3 +1,41 @@
1
+ <a name="v2.29.0"></a>
2
+ ### v2.29.0 (2019-03-15)
3
+
4
+
5
+ #### Features
6
+
7
+ * **matrix**
8
+ * improve reasons in response when pacticipant cannot be deployed ([e96544f6](/../../commit/e96544f6))
9
+ * allow provider to be deployed to an environment without the consumer having to be deployed there already ([475a4010](/../../commit/475a4010))
10
+
11
+ * change webhook execution failure log to info ([7238bc49](/../../commit/7238bc49))
12
+ * remove db/pact_broker_database.sqlite3 so we don't have conflicts in fork ([f68fce8b](/../../commit/f68fce8b))
13
+ * change error logs to info logs ([700f6992](/../../commit/700f6992))
14
+ * lock dry-logic to version that works ([28769040](/../../commit/28769040))
15
+ * allow a custom UI to be configured ([23695b08](/../../commit/23695b08))
16
+
17
+
18
+ #### Bug Fixes
19
+
20
+ * gracefully handle race conditions when publishing a new revision of a pact ([012c54f0](/../../commit/012c54f0))
21
+
22
+
23
+ <a name="v2.28.0"></a>
24
+ ### v2.28.0 (2019-01-15)
25
+
26
+
27
+ #### Features
28
+
29
+ * enable file list in gemspec to work without git ([6dd27e26](/../../commit/6dd27e26))
30
+ * allow the database connection to be configured for async jobs ([6a745d4b](/../../commit/6a745d4b))
31
+ * add error reference to API error response and ensure potentially sensitive details from the exception message are not exposed ([e7bb4a01](/../../commit/e7bb4a01))
32
+
33
+
34
+ #### Bug Fixes
35
+
36
+ * show consumer/provider/global webhooks in webhook column on index page ([35f8bfc1](/../../commit/35f8bfc1))
37
+
38
+
1
39
  <a name="v2.27.6"></a>
2
40
  ### v2.27.6 (2018-11-23)
3
41
 
data/MATRIX.md ADDED
@@ -0,0 +1,4 @@
1
+ # Interpreting the Matrix results
2
+
3
+ * If there is a row with a blank provider version, it's because the pact for that consumer version hasn't been verified by that provider (the result of a left outer join).
4
+ * If there is no row, it's because it has been verified, but not by the provider version you've specified.
data/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Pact Broker
2
2
  [![Gem Version](https://badge.fury.io/rb/pact_broker.svg)](http://badge.fury.io/rb/pact_broker)
3
- [![Build Status](https://travis-ci.org/pact-foundation/pact_broker.svg?branch=master)](https://travis-ci.org/pact-foundation/pact_broker) [![Join the chat at https://gitter.im/pact-foundation/pact_broker](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/pact-foundation/pact_broker?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3
+ [![Build Status](https://travis-ci.org/pact-foundation/pact_broker.svg?branch=master)](https://travis-ci.org/pact-foundation/pact_broker)
4
+ [![Join the chat at https://pact-foundation.slack.com/](https://img.shields.io/badge/chat-on%20slack-blue.svg?logo=slack)](https://slack.pact.io)
5
+ [![security](https://hakiri.io/github/pact-foundation/pact_broker/master.svg)](https://hakiri.io/github/pact-foundation/pact_broker/master)
4
6
  [![Code Climate](https://codeclimate.com/github/pact-foundation/pact_broker/badges/gpa.svg)](https://codeclimate.com/github/pact-foundation/pact_broker)
5
7
  [![Test Coverage](https://codeclimate.com/github/pact-foundation/pact_broker/badges/coverage.svg)](https://codeclimate.com/github/pact-foundation/pact_broker/coverage)
6
- [![Issue Count](https://codeclimate.com/github/pact-foundation/pact_broker/badges/issue_count.svg)](https://codeclimate.com/github/pact-foundation/pact_broker)
7
8
 
8
9
  The Pact Broker is an application for sharing for consumer driven contracts and verification results. It is optimised for use with "pacts" (contracts created by the [Pact][pact-docs] framework), but can be used for any type of contract that can be serialized to JSON.
9
10
 
data/Rakefile CHANGED
@@ -14,9 +14,8 @@ end
14
14
  FileList['lib/tasks/**/*.rake'].each { |task| load "#{Dir.pwd}/#{task}" }
15
15
  FileList['tasks/**/*.rake'].each { |task| load "#{Dir.pwd}/#{task}" }
16
16
 
17
- task :default => ['db:prepare:test', :boot, :spec, 'pact:verify']
17
+ task :default => ['db:prepare:test', :boot, :spec, 'pact:verify', 'bundle:audit']
18
18
 
19
19
  task :boot do
20
20
  require File.join(File.dirname(__FILE__), 'config/boot')
21
21
  end
22
-
data/config/database.yml CHANGED
@@ -16,6 +16,11 @@ test:
16
16
  adapter: postgres
17
17
  host: "127.0.0.1"
18
18
  port: "5433"
19
+ docker_compose_postgres:
20
+ <<: *default
21
+ adapter: postgres
22
+ host: postgres
23
+ port: "5432"
19
24
  mysql:
20
25
  <<: *default
21
26
  adapter: mysql2
@@ -1,5 +1,6 @@
1
1
  require 'ostruct'
2
2
  require 'pact_broker/api/pact_broker_urls'
3
+ require 'pact_broker/api/decorators/reason_decorator'
3
4
 
4
5
  module PactBroker
5
6
  module Api
@@ -33,7 +34,11 @@ module PactBroker
33
34
  end
34
35
 
35
36
  def reason
36
- query_results_with_deployment_status_summary.deployment_status_summary.reasons.join("\n")
37
+ query_results_with_deployment_status_summary
38
+ .deployment_status_summary
39
+ .reasons
40
+ .collect{ | reason | ReasonDecorator.new(reason).to_s }
41
+ .join("\n")
37
42
  end
38
43
 
39
44
  private
@@ -130,3 +135,4 @@ module PactBroker
130
135
  end
131
136
  end
132
137
  end
138
+
@@ -0,0 +1,50 @@
1
+ module PactBroker
2
+ module Api
3
+ module Decorators
4
+ class ReasonDecorator
5
+ def initialize(reason)
6
+ @reason = reason
7
+ end
8
+
9
+ def to_s
10
+ case reason
11
+ when PactBroker::Matrix::PactNotEverVerifiedByProvider
12
+ "There is no verified pact between #{reason.consumer_selector.description} and #{reason.provider_selector.description}"
13
+ when PactBroker::Matrix::PactNotVerifiedByRequiredProviderVersion
14
+ "There is no verified pact between #{reason.consumer_selector.description} and #{reason.provider_selector.description}"
15
+ when PactBroker::Matrix::VerificationFailed
16
+ "The verification between #{reason.consumer_selector.description} and #{reason.provider_selector.description} failed"
17
+ when PactBroker::Matrix::SpecifiedVersionDoesNotExist
18
+ version_does_not_exist_description(reason.selector)
19
+ when PactBroker::Matrix::VerificationFailed
20
+ "The verification for the pact between #{reason.consumer_selector.description} and #{reason.provider_selector.description} failed"
21
+ when PactBroker::Matrix::NoDependenciesMissing
22
+ "There are no missing dependencies"
23
+ when PactBroker::Matrix::Successful
24
+ "All required verification results are published and successful"
25
+ else
26
+ reason
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :reason
33
+
34
+ def version_does_not_exist_description selector
35
+ if selector.version_does_not_exist?
36
+ if selector.tag
37
+ "No version with tag #{selector.tag} exists for #{selector.pacticipant_name}"
38
+ elsif selector.pacticipant_version_number
39
+ "No pacts or verifications have been published for version #{selector.pacticipant_version_number} of #{selector.pacticipant_name}"
40
+ else
41
+ "No pacts or verifications have been published for #{selector.pacticipant_name}"
42
+ end
43
+ else
44
+ ""
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -127,7 +127,7 @@ module PactBroker
127
127
  JSON.parse(request_body, PACT_PARSING_OPTIONS) #Not load! Otherwise it will try to load Ruby classes.
128
128
  false
129
129
  rescue StandardError => e
130
- logger.error "Error parsing JSON #{e} - #{request_body}"
130
+ logger.info "Error parsing JSON #{e} - #{request_body}"
131
131
  set_json_error_message "Error parsing JSON - #{e.message}"
132
132
  response.headers['Content-Type'] = 'application/hal+json;charset=utf-8'
133
133
  true
@@ -1,5 +1,6 @@
1
1
  require 'webmachine/convert_request_to_rack_env'
2
2
  require 'pact_broker/configuration'
3
+ require 'securerandom'
3
4
 
4
5
  module PactBroker
5
6
  module Api
@@ -10,24 +11,46 @@ module PactBroker
10
11
  include PactBroker::Logging
11
12
 
12
13
  def self.call e, request, response
13
- logger.error e
14
+ error_reference = SecureRandom.urlsafe_base64.gsub(/[^a-z]/i, '')[0,10]
15
+ logger.error "#{e.message} - error reference #{error_reference}"
14
16
  logger.error e.backtrace
15
- response_body = { error: { :message => e.message } }
16
- if PactBroker.configuration.show_backtrace_in_error_response?
17
- response_body[:error][:backtrace] = e.backtrace
18
- end
19
- response.body = response_body.to_json
20
- report(e, request) if reportable?(e)
17
+ response.body = response_body_hash(e, error_reference).to_json
18
+ report(e, error_reference, request) if reportable?(e)
21
19
  end
22
20
 
23
21
  def self.reportable? e
24
22
  !e.is_a?(PactBroker::Error)
25
23
  end
26
24
 
27
- def self.report e, request
25
+ def self.display_message(e, error_reference)
26
+ if PactBroker.configuration.show_backtrace_in_error_response?
27
+ e.message
28
+ else
29
+ reportable?(e) ? obfuscated_error_message(error_reference) : e.message
30
+ end
31
+ end
32
+
33
+ def self.obfuscated_error_message error_reference
34
+ "An error has occurred. The details have been logged with the reference #{error_reference}"
35
+ end
36
+
37
+ def self.response_body_hash e, error_reference
38
+ response_body = {
39
+ error: {
40
+ message: display_message(e, error_reference),
41
+ reference: error_reference
42
+ }
43
+ }
44
+ if PactBroker.configuration.show_backtrace_in_error_response?
45
+ response_body[:error][:backtrace] = e.backtrace
46
+ end
47
+ response_body
48
+ end
49
+
50
+ def self.report e, error_reference, request
28
51
  PactBroker.configuration.api_error_reporters.each do | error_notifier |
29
52
  begin
30
- error_notifier.call(e, env: Webmachine::ConvertRequestToRackEnv.call(request))
53
+ error_notifier.call(e, env: Webmachine::ConvertRequestToRackEnv.call(request), error_reference: error_reference)
31
54
  rescue StandardError => e
32
55
  log_error(e, "Error executing api_error_reporter")
33
56
  end
@@ -32,7 +32,6 @@ module PactBroker
32
32
  yield configuration
33
33
  post_configure
34
34
  prepare_database
35
- prepare_app
36
35
  end
37
36
 
38
37
  # Allows middleware to be inserted at the bottom of the shared middlware stack
@@ -54,12 +53,18 @@ module PactBroker
54
53
  @make_it_later_ui_auth.make_it_later(middleware)
55
54
  end
56
55
 
56
+ def use_custom_ui custom_ui
57
+ @custom_ui = custom_ui
58
+ end
59
+
57
60
  def call env
58
61
  running_app.call env
59
62
  end
60
63
 
61
64
  private
62
65
 
66
+ attr_reader :custom_ui
67
+
63
68
  def post_configure
64
69
  configure_logger
65
70
  SuckerPunch.logger = configuration.custom_logger || SemanticLogger['SuckerPunch']
@@ -101,6 +106,7 @@ module PactBroker
101
106
  configure_middleware
102
107
 
103
108
  # need this first so UI login logic is performed before API login logic
109
+ @cascade_apps << build_custom_ui if custom_ui
104
110
  @cascade_apps << build_ui
105
111
 
106
112
  if configuration.enable_diagnostic_endpoints
@@ -139,6 +145,14 @@ module PactBroker
139
145
  builder
140
146
  end
141
147
 
148
+ def build_custom_ui
149
+ logger.info "Mounting Custom UI"
150
+ builder = ::Rack::Builder.new
151
+ builder.use Rack::PactBroker::AcceptsHtmlFilter
152
+ builder.run @custom_ui
153
+ builder
154
+ end
155
+
142
156
  def build_api
143
157
  logger.info "Mounting PactBroker::API"
144
158
  require 'pact_broker/api'
@@ -174,6 +188,7 @@ module PactBroker
174
188
 
175
189
  def running_app
176
190
  @running_app ||= begin
191
+ prepare_app
177
192
  apps = @cascade_apps
178
193
  @app_builder.map "/" do
179
194
  run Rack::Cascade.new(apps)
@@ -196,7 +196,7 @@ module PactBroker
196
196
  end
197
197
 
198
198
  def log_error e
199
- logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message} #{e.backtrace.join("\n")}"
199
+ logger.info "Error executing webhook #{uuid} #{e.class.name} - #{e.message} #{e.backtrace.join("\n")}"
200
200
 
201
201
  if options[:show_response]
202
202
  execution_logger.error "Error executing webhook #{uuid} #{e.class.name} - #{e.message}"
@@ -1,4 +1,5 @@
1
1
  require 'pact_broker/logging'
2
+ require 'pact_broker/matrix/reason'
2
3
 
3
4
  module PactBroker
4
5
  module Matrix
@@ -11,89 +12,167 @@ module PactBroker
11
12
  @rows = rows
12
13
  @resolved_selectors = resolved_selectors
13
14
  @integrations = integrations
15
+ @dummy_selectors = create_dummy_selectors
14
16
  end
15
17
 
16
18
  def counts
17
19
  {
18
20
  success: rows.count{ |row| row.success },
19
21
  failed: rows.count { |row| row.success == false },
20
- unknown: integrations_without_a_row.count + rows.count { |row| row.success.nil? }
22
+ unknown: required_integrations_without_a_row.count + rows.count { |row| row.success.nil? }
21
23
  }
22
24
  end
23
25
 
24
26
  def deployable?
25
- return nil if rows.empty?
27
+ return false if specified_selectors_that_do_not_exist.any?
26
28
  return nil if rows.any?{ |row| row.success.nil? }
27
- return nil if integrations_without_a_row.any?
28
- rows.all?{ |row| row.success }
29
+ return nil if required_integrations_without_a_row.any?
30
+ rows.all?{ |row| row.success } # true if rows is empty
29
31
  end
30
32
 
31
33
  def reasons
32
- @reasons ||= begin
33
- reasons = []
34
- if rows.empty?
35
- reasons << "No results matched the given query"
36
- else
37
- reasons.concat(missing_reasons)
38
- reasons.concat(failure_messages)
39
- reasons.concat(unverified_messages)
40
- reasons.concat(success_messages)
34
+ error_messages.any? ? error_messages : success_messages
35
+ end
36
+
37
+ private
38
+
39
+ attr_reader :dummy_selectors
40
+
41
+ def error_messages
42
+ @error_messages ||= begin
43
+ messages = []
44
+ messages.concat(specified_selectors_do_not_exist_messages)
45
+ if messages.empty?
46
+ messages.concat(missing_reasons)
47
+ messages.concat(failure_messages)
48
+ messages.concat(not_ever_verified_reasons)
41
49
  end
42
- reasons
50
+ messages.uniq
43
51
  end
44
52
  end
45
53
 
46
- def unverified_messages
47
- if rows.any?{ |row| row.success.nil? }
48
- ["Missing one or more verification results"]
49
- else
50
- []
54
+ def specified_selectors_that_do_not_exist
55
+ resolved_selectors.select(&:specified_version_that_does_not_exist?)
56
+ end
57
+
58
+ def specified_selectors_do_not_exist_messages
59
+ specified_selectors_that_do_not_exist.collect do | selector |
60
+ SpecifiedVersionDoesNotExist.new(selector)
51
61
  end
52
62
  end
53
63
 
64
+ def not_ever_verified_reasons
65
+ rows.select{ | row | row.success.nil? }.collect{ |row | pact_not_ever_verified_by_provider(row) }
66
+ end
67
+
54
68
  def failure_messages
55
- if rows.any?{ |row| row.success == false }
56
- ["One or more verifications have failed"]
57
- else
58
- []
69
+ rows.select{ |row| row.success == false }.collect do | row |
70
+ VerificationFailed.new(*selectors_for(row))
59
71
  end
60
72
  end
61
73
 
62
74
  def success_messages
63
- if rows.all?{ |row| row.success } && integrations_without_a_row.empty?
64
- ["All verification results are published and successful"]
75
+ if rows.all?{ |row| row.success } && required_integrations_without_a_row.empty?
76
+ if rows.any?
77
+ [Successful.new]
78
+ else
79
+ [NoDependenciesMissing.new]
80
+ end
65
81
  else
66
82
  []
67
- end
83
+ end.flatten.uniq
68
84
  end
69
85
 
70
- def integrations_without_a_row
71
- @integrations_without_a_row ||= begin
72
- integrations.select do | relationship |
73
- !rows.find do | row |
74
- row.consumer_id == relationship.consumer_id && row.provider_id == relationship.provider_id
75
- end
86
+ # For deployment, the consumer requires the provider,
87
+ # but the provider does not require the consumer
88
+ # This method tells us which providers are missing.
89
+ # Technically, it tells us which integrations do not have a row
90
+ # because the pact that belongs to the consumer version has
91
+ # in fact been verified, but not by the provider version specified
92
+ # in the query (because left outer join)
93
+ #
94
+ # Imagine query for deploying Foo v3 to prod with the following matrix:
95
+ # Foo v2 -> Bar v1 (latest prod) [this line not included because CV doesn't match]
96
+ # Foo v3 -> Bar v2 [this line not included because PV doesn't match]
97
+ #
98
+ # No matrix rows would be returned. This method identifies that we have no row for
99
+ # the Foo -> Bar integration, and therefore cannot deploy Foo.
100
+ # However, if we were to try and deploy the provider, Bar, that would be ok
101
+ # as Bar does not rely on Foo, so this method would not return that integration.
102
+ def required_integrations_without_a_row
103
+ @required_integrations_without_a_row ||= begin
104
+ integrations.select(&:required?).select do | integration |
105
+ !row_exists_for_integration(integration)
76
106
  end
77
107
  end
78
108
  end
79
109
 
110
+ def row_exists_for_integration(integration)
111
+ rows.find { | row | integration == row }
112
+ end
113
+
80
114
  def missing_reasons
81
- integrations_without_a_row.collect do | missing_relationship|
82
- consumer_version_desc = "#{missing_relationship.consumer_name} (#{resolved_version_for(missing_relationship.consumer_id)})"
83
- provider_version_desc = "#{missing_relationship.provider_name} (#{resolved_version_for(missing_relationship.provider_id)})"
84
- "There is no verified pact between #{consumer_version_desc} and #{provider_version_desc}"
85
- end
115
+ required_integrations_without_a_row.collect do | integration |
116
+ pact_not_verified_by_required_provider_version(integration)
117
+ end.flatten
86
118
  end
87
119
 
88
- def resolved_version_for(pacticipant_id)
89
- resolved_selector = resolved_selectors.find{ | s| s[:pacticipant_id] == pacticipant_id }
90
- if resolved_selector
91
- resolved_selector[:pacticipant_version_number]
92
- else
93
- logger.warn "Could not find the resolved version for pacticipant_id #{pacticipant_id} from integrations #{integrations.collect(&:to_s).join(", ")} in resolved selectors #{resolved_selectors.inspect}"
94
- "unresolved version"
120
+ def selectors_without_a_version_for(integration)
121
+ selectors_with_non_existing_versions.select do | selector |
122
+ integration.involves_pacticipant_with_name?(selector.pacticipant_name)
95
123
  end
96
124
  end
125
+
126
+ def selectors_with_non_existing_versions
127
+ @selectors_with_non_existing_versions ||= resolved_selectors.select(&:latest_tagged_version_that_does_not_exist?)
128
+ end
129
+
130
+ def missing_specified_version_reasons(selectors)
131
+ selectors.collect(&:version_does_not_exist_description)
132
+ end
133
+
134
+ def pact_not_verified_by_required_provider_version(integration)
135
+ PactNotVerifiedByRequiredProviderVersion.new(*selectors_for(integration))
136
+ end
137
+
138
+ def pact_not_ever_verified_by_provider(row)
139
+ PactNotEverVerifiedByProvider.new(*selectors_for(row))
140
+ end
141
+
142
+ def selector_for(pacticipant_name)
143
+ resolved_selectors.find{ | s| s.pacticipant_name == pacticipant_name } ||
144
+ dummy_selectors.find{ | s| s.pacticipant_name == pacticipant_name }
145
+ end
146
+
147
+ def selectors_for(row)
148
+ [selector_for(row.consumer_name), selector_for(row.provider_name)]
149
+ end
150
+
151
+ # When the user has not specified a version of the provider (eg no 'latest' and/or 'tag')
152
+ # so the "add inferred selectors" code in the Matrix::Repository has not run,
153
+ # we may end up with rows for which we do not have a selector.
154
+ # To solve this, create dummy selectors from the row and integration data.
155
+ def create_dummy_selectors
156
+ (dummy_selectors_from_rows + dummy_selectors_from_integrations).uniq
157
+ end
158
+
159
+ def dummy_selectors_from_integrations
160
+ integrations.collect do | row |
161
+ dummy_consumer_selector = ResolvedSelector.for_pacticipant(row.consumer, :inferred)
162
+ dummy_provider_selector = ResolvedSelector.for_pacticipant(row.provider, :inferred)
163
+ [dummy_consumer_selector, dummy_provider_selector]
164
+ end.flatten
165
+ end
166
+
167
+ def dummy_selectors_from_rows
168
+ rows.collect do | row |
169
+ dummy_consumer_selector = ResolvedSelector.for_pacticipant_and_version(row.consumer, row.consumer_version, {}, :inferred)
170
+ dummy_provider_selector = row.provider_version ?
171
+ ResolvedSelector.for_pacticipant_and_version(row.provider, row.provider_version, {}, :inferred) :
172
+ ResolvedSelector.for_pacticipant(row.provider, :inferred)
173
+ [dummy_consumer_selector, dummy_provider_selector]
174
+ end.flatten
175
+ end
97
176
  end
98
177
  end
99
178
  end