pact_broker 2.27.6 → 2.29.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.
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