pact 1.28.0 → 1.53.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 (53) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +408 -0
  3. data/lib/pact/cli/run_pact_verification.rb +26 -7
  4. data/lib/pact/cli/spec_criteria.rb +3 -0
  5. data/lib/pact/cli.rb +7 -1
  6. data/lib/pact/consumer/configuration/dsl.rb +0 -1
  7. data/lib/pact/consumer/configuration/mock_service.rb +9 -3
  8. data/lib/pact/consumer/consumer_contract_builder.rb +6 -2
  9. data/lib/pact/consumer/interaction_builder.rb +6 -0
  10. data/lib/pact/consumer/spec_hooks.rb +4 -4
  11. data/lib/pact/doc/sort_interactions.rb +2 -3
  12. data/lib/pact/hal/authorization_header_redactor.rb +32 -0
  13. data/lib/pact/hal/entity.rb +27 -5
  14. data/lib/pact/hal/http_client.rb +25 -7
  15. data/lib/pact/hal/link.rb +67 -20
  16. data/lib/pact/hal/non_json_entity.rb +28 -0
  17. data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +93 -0
  18. data/lib/pact/pact_broker/fetch_pacts.rb +20 -18
  19. data/lib/pact/pact_broker/notices.rb +34 -0
  20. data/lib/pact/pact_broker/pact_selection_description.rb +24 -0
  21. data/lib/pact/pact_broker.rb +25 -0
  22. data/lib/pact/provider/configuration/dsl.rb +6 -1
  23. data/lib/pact/provider/configuration/message_provider_dsl.rb +59 -0
  24. data/lib/pact/provider/configuration/pact_verification_from_broker.rb +29 -4
  25. data/lib/pact/provider/configuration/service_provider_dsl.rb +1 -1
  26. data/lib/pact/provider/help/content.rb +4 -4
  27. data/lib/pact/provider/help/pact_diff.rb +10 -34
  28. data/lib/pact/provider/help/write.rb +6 -7
  29. data/lib/pact/provider/pact_helper_locator.rb +13 -11
  30. data/lib/pact/provider/pact_source.rb +10 -1
  31. data/lib/pact/provider/pact_spec_runner.rb +26 -13
  32. data/lib/pact/provider/pact_uri.rb +8 -6
  33. data/lib/pact/provider/request.rb +1 -1
  34. data/lib/pact/provider/rspec/formatter_rspec_3.rb +39 -8
  35. data/lib/pact/provider/rspec/json_formatter.rb +100 -0
  36. data/lib/pact/provider/rspec/pact_broker_formatter.rb +7 -17
  37. data/lib/pact/provider/rspec.rb +24 -11
  38. data/lib/pact/provider/state/provider_state.rb +6 -7
  39. data/lib/pact/provider/state/provider_state_manager.rb +10 -10
  40. data/lib/pact/provider/state/set_up.rb +1 -1
  41. data/lib/pact/provider/state/tear_down.rb +1 -1
  42. data/lib/pact/provider/test_methods.rb +22 -7
  43. data/lib/pact/provider/verification_results/create.rb +6 -0
  44. data/lib/pact/provider/verification_results/publish.rb +5 -8
  45. data/lib/pact/provider/verification_results/publish_all.rb +14 -1
  46. data/lib/pact/provider/verification_results/verification_result.rb +2 -5
  47. data/lib/pact/provider/world.rb +1 -1
  48. data/lib/pact/retry.rb +2 -0
  49. data/lib/pact/tasks/task_helper.rb +10 -4
  50. data/lib/pact/tasks/verification_task.rb +3 -1
  51. data/lib/pact/version.rb +1 -1
  52. data/pact.gemspec +19 -15
  53. metadata +75 -80
@@ -0,0 +1,59 @@
1
+ require 'pact/provider/configuration/service_provider_dsl'
2
+
3
+ module Pact
4
+ module Provider
5
+ module Configuration
6
+ class MessageProviderDSL < ServiceProviderDSL
7
+ class RackToMessageAdapter
8
+ def initialize(message_builder)
9
+ @message_builder = message_builder
10
+ end
11
+
12
+ def call(env)
13
+ request_body_json = JSON.parse(env['rack.input'].read)
14
+ contents = @message_builder.call(request_body_json['description'])
15
+ [200, {"Content-Type" => "application/json"}, [{ contents: contents }.to_json]]
16
+ end
17
+ end
18
+
19
+ def initialize name
20
+ super
21
+ @mapper_block = lambda { |args| }
22
+ end
23
+
24
+ dsl do
25
+ def app &block
26
+ self.app_block = block
27
+ end
28
+
29
+ def app_version application_version
30
+ self.application_version = application_version
31
+ end
32
+
33
+ def app_version_tags tags
34
+ self.tags = tags
35
+ end
36
+
37
+ def publish_verification_results publish_verification_results
38
+ self.publish_verification_results = publish_verification_results
39
+ Pact::RSpec.with_rspec_2 do
40
+ Pact.configuration.error_stream.puts "WARN: Publishing of verification results is currently not supported with rspec 2. If you would like this functionality, please feel free to submit a PR!"
41
+ end
42
+ end
43
+
44
+ def honours_pact_with consumer_name, options = {}, &block
45
+ create_pact_verification consumer_name, options, &block
46
+ end
47
+
48
+ def honours_pacts_from_pact_broker &block
49
+ create_pact_verification_from_broker &block
50
+ end
51
+
52
+ def builder &block
53
+ self.app_block = lambda { RackToMessageAdapter.new(block) }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,6 +1,7 @@
1
1
  require 'pact/shared/dsl'
2
2
  require 'pact/provider/world'
3
- require 'pact/pact_broker/fetch_pacts'
3
+ require 'pact/pact_broker/fetch_pact_uris_for_verification'
4
+ require 'pact/errors'
4
5
 
5
6
  module Pact
6
7
  module Provider
@@ -13,11 +14,13 @@ module Pact
13
14
  # in parent scope, it will clash with these ones,
14
15
  # so put an underscore in front of the name to be safer.
15
16
 
16
- attr_accessor :_provider_name, :_pact_broker_base_url, :_consumer_version_tags, :_basic_auth_options, :_verbose
17
+ attr_accessor :_provider_name, :_pact_broker_base_url, :_consumer_version_tags, :_provider_version_tags, :_basic_auth_options, :_enable_pending, :_verbose
17
18
 
18
- def initialize(provider_name)
19
+ def initialize(provider_name, provider_version_tags)
19
20
  @_provider_name = provider_name
21
+ @_provider_version_tags = provider_version_tags
20
22
  @_consumer_version_tags = []
23
+ @_enable_pending = false
21
24
  @_verbose = false
22
25
  end
23
26
 
@@ -31,6 +34,10 @@ module Pact
31
34
  self._consumer_version_tags = *consumer_version_tags
32
35
  end
33
36
 
37
+ def enable_pending enable_pending
38
+ self._enable_pending = enable_pending
39
+ end
40
+
34
41
  def verbose verbose
35
42
  self._verbose = verbose
36
43
  end
@@ -44,10 +51,28 @@ module Pact
44
51
  private
45
52
 
46
53
  def create_pact_verification
47
- fetch_pacts = Pact::PactBroker::FetchPacts.new(_provider_name, _consumer_version_tags, _pact_broker_base_url, _basic_auth_options.merge(verbose: _verbose))
54
+ fetch_pacts = Pact::PactBroker::FetchPactURIsForVerification.new(
55
+ _provider_name,
56
+ consumer_version_selectors,
57
+ _provider_version_tags,
58
+ _pact_broker_base_url,
59
+ _basic_auth_options.merge(verbose: _verbose),
60
+ { include_pending_status: _enable_pending }
61
+ )
62
+
48
63
  Pact.provider_world.add_pact_uri_source fetch_pacts
49
64
  end
50
65
 
66
+ def consumer_version_selectors
67
+ # TODO support "all"
68
+ _consumer_version_tags.collect do | tag |
69
+ {
70
+ tag: tag,
71
+ latest: true
72
+ }
73
+ end
74
+ end
75
+
51
76
  def validate
52
77
  raise Pact::Error.new("Please provide a pact_broker_base_url from which to retrieve the pacts") unless _pact_broker_base_url
53
78
  end
@@ -65,7 +65,7 @@ module Pact
65
65
  end
66
66
 
67
67
  def create_pact_verification_from_broker(&block)
68
- PactVerificationFromBroker.build(name, &block)
68
+ PactVerificationFromBroker.build(name, tags, &block)
69
69
  end
70
70
 
71
71
  def finalize
@@ -5,8 +5,8 @@ module Pact
5
5
  module Help
6
6
  class Content
7
7
 
8
- def initialize pact_jsons
9
- @pact_jsons = pact_jsons
8
+ def initialize pact_sources
9
+ @pact_sources = pact_sources
10
10
  end
11
11
 
12
12
  def text
@@ -15,7 +15,7 @@ module Pact
15
15
 
16
16
  private
17
17
 
18
- attr_reader :pact_jsons
18
+ attr_reader :pact_sources
19
19
 
20
20
  def help_text
21
21
  temp_dir = Pact.configuration.tmp_dir
@@ -28,7 +28,7 @@ module Pact
28
28
  end
29
29
 
30
30
  def pact_diffs
31
- pact_jsons.collect do | pact_json |
31
+ pact_sources.collect do | pact_json |
32
32
  PactDiff.call(pact_json)
33
33
  end.compact.join("\n")
34
34
  end
@@ -1,26 +1,24 @@
1
+ require 'pact/hal/entity'
2
+
1
3
  module Pact
2
4
  module Provider
3
5
  module Help
4
-
5
6
  class PactDiff
6
-
7
7
  class PrintPactDiffError < StandardError; end
8
8
 
9
- attr_reader :pact_json, :output
9
+ attr_reader :pact_source, :output
10
10
 
11
- def initialize pact_json
12
- @pact_json = pact_json
11
+ def initialize pact_source
12
+ @pact_source = pact_source
13
13
  end
14
14
 
15
- def self.call pact_json
16
- new(pact_json).call
15
+ def self.call pact_source
16
+ new(pact_source).call
17
17
  end
18
18
 
19
19
  def call
20
20
  begin
21
- if diff_rel && diff_url
22
- header + "\n" + get_diff
23
- end
21
+ header + "\n" + get_diff
24
22
  rescue PrintPactDiffError => e
25
23
  return e.message
26
24
  end
@@ -32,35 +30,13 @@ module Pact
32
30
  "The following changes have been made since the previous distinct version of this pact, and may be responsible for verification failure:\n"
33
31
  end
34
32
 
35
- def pact_hash
36
- @pact_hash ||= json_load(pact_json)
37
- end
38
-
39
- def links
40
- pact_hash['_links'] || pact_hash['links']
41
- end
42
-
43
- def diff_rel
44
- return nil unless links
45
- key = links.keys.find { | key | key =~ /diff/ && key =~ /distinct/ && key =~ /previous/}
46
- key ? links[key] : nil
47
- end
48
-
49
- def diff_url
50
- diff_rel['href']
51
- end
52
-
53
33
  def get_diff
54
34
  begin
55
- open(diff_url) { | file | file.read }
35
+ pact_source.hal_entity._link!("pb:diff-previous-distinct").get!(nil, "Accept" => "text/plain").body
56
36
  rescue StandardError => e
57
- raise PrintPactDiffError.new("Tried to retrieve diff with previous pact from #{diff_url}, but received response code #{e}.")
37
+ raise PrintPactDiffError.new("Tried to retrieve diff with previous pact, but received error #{e.class} #{e.message}.")
58
38
  end
59
39
  end
60
-
61
- def json_load json
62
- JSON.load(json, nil, { max_nesting: 50 })
63
- end
64
40
  end
65
41
  end
66
42
  end
@@ -9,12 +9,12 @@ module Pact
9
9
 
10
10
  HELP_FILE_NAME = 'help.md'
11
11
 
12
- def self.call pact_jsons, reports_dir = Pact.configuration.reports_dir
13
- new(pact_jsons, reports_dir).call
12
+ def self.call pact_sources, reports_dir = Pact.configuration.reports_dir
13
+ new(pact_sources, reports_dir).call
14
14
  end
15
15
 
16
- def initialize pact_jsons, reports_dir
17
- @pact_jsons = pact_jsons
16
+ def initialize pact_sources, reports_dir
17
+ @pact_sources = pact_sources
18
18
  @reports_dir = File.expand_path(reports_dir)
19
19
  end
20
20
 
@@ -25,7 +25,7 @@ module Pact
25
25
 
26
26
  private
27
27
 
28
- attr_reader :reports_dir, :pact_jsons
28
+ attr_reader :reports_dir, :pact_sources
29
29
 
30
30
  def clean_reports_dir
31
31
  raise "Cleaning report dir #{reports_dir} would delete project!" if reports_dir_contains_pwd
@@ -46,9 +46,8 @@ module Pact
46
46
  end
47
47
 
48
48
  def help_text
49
- Content.new(pact_jsons).text
49
+ Content.new(pact_sources).text
50
50
  end
51
-
52
51
  end
53
52
  end
54
53
  end
@@ -1,22 +1,24 @@
1
1
  module Pact
2
2
  module Provider
3
3
  module PactHelperLocater
4
- PACT_HELPER_FILE_PATTERNS = [
4
+ PACT_HELPER_FILE_PATTERNS = [
5
5
  "spec/**/*service*consumer*/pact_helper.rb",
6
6
  "spec/**/*consumer*/pact_helper.rb",
7
7
  "spec/**/pact_helper.rb",
8
- "**/pact_helper.rb"]
8
+ "test/**/*service*consumer*/pact_helper.rb",
9
+ "test/**/*consumer*/pact_helper.rb",
10
+ "test/**/pact_helper.rb",
11
+ "**/pact_helper.rb"
12
+ ]
9
13
 
10
- NO_PACT_HELPER_FOUND_MSG = "Please create a pact_helper.rb file that can be found using one of the following patterns: #{PACT_HELPER_FILE_PATTERNS.join(", ")}"
11
-
12
- def self.pact_helper_path
13
- pact_helper_search_results = []
14
- PACT_HELPER_FILE_PATTERNS.find { | pattern | (pact_helper_search_results.concat(Dir.glob(pattern))).any? }
15
- raise NO_PACT_HELPER_FOUND_MSG if pact_helper_search_results.empty?
16
- File.join(Dir.pwd, pact_helper_search_results[0])
17
- end
14
+ NO_PACT_HELPER_FOUND_MSG = "Please create a pact_helper.rb file that can be found using one of the following patterns: #{PACT_HELPER_FILE_PATTERNS.join(", ")}"
18
15
 
16
+ def self.pact_helper_path
17
+ pact_helper_search_results = []
18
+ PACT_HELPER_FILE_PATTERNS.find { | pattern | (pact_helper_search_results.concat(Dir.glob(pattern))).any? }
19
+ raise NO_PACT_HELPER_FOUND_MSG if pact_helper_search_results.empty?
20
+ File.join(Dir.pwd, pact_helper_search_results[0])
21
+ end
19
22
  end
20
23
  end
21
24
  end
22
-
@@ -1,10 +1,12 @@
1
1
  require 'pact/consumer_contract/pact_file'
2
+ require 'pact/hal/http_client'
3
+ require 'pact/hal/entity'
2
4
 
3
5
  module Pact
4
6
  module Provider
5
7
  class PactSource
6
8
 
7
- attr_reader :uri
9
+ attr_reader :uri # PactURI class
8
10
 
9
11
  def initialize uri
10
12
  @uri = uri
@@ -17,6 +19,13 @@ module Pact
17
19
  def pact_hash
18
20
  @pact_hash ||= JSON.load(pact_json, nil, { max_nesting: 50 })
19
21
  end
22
+
23
+ def hal_entity
24
+ http_client_keys = [:username, :password, :token]
25
+ http_client_options = uri.options.reject{ |k, _| !http_client_keys.include?(k) }
26
+ http_client = Pact::Hal::HttpClient.new(http_client_options)
27
+ Pact::Hal::Entity.new(uri, pact_hash, http_client)
28
+ end
20
29
  end
21
30
  end
22
31
  end
@@ -9,9 +9,8 @@ require 'pact/provider/pact_source'
9
9
  require 'pact/provider/help/write'
10
10
  require 'pact/provider/verification_results/publish_all'
11
11
  require 'pact/provider/rspec/pact_broker_formatter'
12
-
13
- require_relative 'rspec'
14
-
12
+ require 'pact/provider/rspec/json_formatter'
13
+ require 'pact/provider/rspec'
15
14
 
16
15
  module Pact
17
16
  module Provider
@@ -36,6 +35,7 @@ module Pact
36
35
  ensure
37
36
  ::RSpec.reset
38
37
  Pact.clear_provider_world
38
+ Pact.clear_consumer_world
39
39
  end
40
40
  end
41
41
 
@@ -79,7 +79,7 @@ module Pact
79
79
  executing_with_ruby = executing_with_ruby?
80
80
 
81
81
  config.after(:suite) do | suite |
82
- Pact::Provider::Help::Write.call(jsons) if executing_with_ruby
82
+ Pact::Provider::Help::Write.call(Pact.provider_world.pact_sources) if executing_with_ruby
83
83
  end
84
84
  end
85
85
 
@@ -90,7 +90,7 @@ module Pact
90
90
  ::RSpec::Core::CommandLine.new(NoConfigurationOptions.new)
91
91
  .run(::RSpec.configuration.output_stream, ::RSpec.configuration.error_stream)
92
92
  end
93
- exit_code
93
+ pending_mode? ? 0 : exit_code
94
94
  end
95
95
 
96
96
  def rspec_runner_options
@@ -117,10 +117,12 @@ module Pact
117
117
 
118
118
  def initialize_specs
119
119
  pact_sources.each do | pact_source |
120
- options = {
121
- criteria: @options[:criteria]
120
+ spec_options = {
121
+ criteria: options[:criteria],
122
+ ignore_failures: options[:ignore_failures],
123
+ request_customizer: options[:request_customizer]
122
124
  }
123
- honour_pactfile pact_source.uri, ordered_pact_json(pact_source.pact_json), options
125
+ honour_pactfile pact_source, ordered_pact_json(pact_source.pact_json), spec_options
124
126
  end
125
127
  end
126
128
 
@@ -131,11 +133,12 @@ module Pact
131
133
 
132
134
  output = options[:out] || Pact.configuration.output_stream
133
135
  if options[:format]
134
- ::RSpec.configuration.add_formatter options[:format], output
135
- if !options[:out]
136
- # Don't want to mess up the JSON parsing with messages to stdout, so send it to stderr
137
- Pact.configuration.output_stream = Pact.configuration.error_stream
138
- end
136
+ formatter = options[:format] == 'json' ? Pact::Provider::RSpec::JsonFormatter : options[:format]
137
+ # Send formatted output to $stdout for parsing, unless a file is specified
138
+ output = options[:out] || $stdout
139
+ ::RSpec.configuration.add_formatter formatter, output
140
+ # Don't want to mess up the JSON parsing with INFO and DEBUG messages to stdout, so send it to stderr
141
+ Pact.configuration.output_stream = Pact.configuration.error_stream if !options[:out]
139
142
  else
140
143
  # Sometimes the formatter set in the cli.rb get set with an output of StringIO.. don't know why
141
144
  formatter_class = Pact::RSpec.formatter_class
@@ -144,6 +147,8 @@ module Pact
144
147
  end
145
148
 
146
149
  ::RSpec.configuration.full_backtrace = @options[:full_backtrace]
150
+
151
+ ::RSpec.configuration.failure_color = :yellow if pending_mode?
147
152
  end
148
153
 
149
154
  def ordered_pact_json(pact_json)
@@ -154,12 +159,20 @@ module Pact
154
159
  consumer_contract.to_json
155
160
  end
156
161
 
162
+ def all_pacts_pending?
163
+ pact_urls.all?{ | pact_url| pact_url.metadata[:pending] }
164
+ end
165
+
157
166
  def class_exists? name
158
167
  Kernel.const_get name
159
168
  rescue NameError
160
169
  false
161
170
  end
162
171
 
172
+ def pending_mode?
173
+ (all_pacts_pending? || options[:ignore_failures])
174
+ end
175
+
163
176
  def executing_with_ruby?
164
177
  ENV['PACT_EXECUTING_LANGUAGE'] == 'ruby'
165
178
  end
@@ -1,17 +1,19 @@
1
1
  module Pact
2
2
  module Provider
3
3
  class PactURI
4
- attr_reader :uri, :options
4
+ attr_reader :uri, :options, :metadata
5
5
 
6
- def initialize (uri, options={})
6
+ def initialize (uri, options = nil, metadata = nil)
7
7
  @uri = uri
8
- @options = options
8
+ @options = options || {}
9
+ @metadata = metadata || {} # make sure it's not nil if nil is passed in
9
10
  end
10
11
 
11
12
  def == other
12
13
  other.is_a?(PactURI) &&
13
14
  uri == other.uri &&
14
- options == other.options
15
+ options == other.options &&
16
+ metadata == other.metadata
15
17
  end
16
18
 
17
19
  def basic_auth?
@@ -27,10 +29,10 @@ module Pact
27
29
  end
28
30
 
29
31
  def to_s
30
- if(basic_auth?)
32
+ if basic_auth? && uri.start_with?('http://', 'https://')
31
33
  URI(@uri).tap { |x| x.userinfo="#{username}:*****"}.to_s
32
34
  else
33
- @uri
35
+ uri
34
36
  end
35
37
  end
36
38
  end
@@ -54,7 +54,7 @@ module Pact
54
54
  end
55
55
 
56
56
  def rack_request_header_for header
57
- with_http_prefix(header.to_s.upcase).gsub('-', '_')
57
+ with_http_prefix(header.to_s.upcase).tr('-', '_')
58
58
  end
59
59
 
60
60
  def rack_request_value_for value
@@ -6,13 +6,13 @@ require 'pact/provider/help/prompt_text'
6
6
  module Pact
7
7
  module Provider
8
8
  module RSpec
9
-
10
9
  class Formatter < ::RSpec::Core::Formatters::DocumentationFormatter
11
10
 
12
11
  class NilFormatter < ::RSpec::Core::Formatters::BaseFormatter
13
12
  Pact::RSpec.with_rspec_3 do
14
13
  ::RSpec::Core::Formatters.register self, :start, :example_group_started, :close
15
14
  end
15
+
16
16
  def dump_summary(summary)
17
17
  end
18
18
  end
@@ -42,9 +42,21 @@ module Pact
42
42
  summary.failed_examples.collect{ |e| e.metadata[:pact_interaction_example_description] }.uniq.size
43
43
  end
44
44
 
45
+ def ignore_failures?(summary)
46
+ summary.failed_examples.any?{ |e| e.metadata[:pact_ignore_failures] }
47
+ end
48
+
49
+ def failure_title summary
50
+ if ignore_failures?(summary)
51
+ "#{failed_interactions_count(summary)} pending"
52
+ else
53
+ ::RSpec::Core::Formatters::Helpers.pluralize(failed_interactions_count(summary), "failure")
54
+ end
55
+ end
56
+
45
57
  def totals_line summary
46
58
  line = ::RSpec::Core::Formatters::Helpers.pluralize(interactions_count(summary), "interaction")
47
- line << ", " << ::RSpec::Core::Formatters::Helpers.pluralize(failed_interactions_count(summary), "failure")
59
+ line << ", " << failure_title(summary)
48
60
  line
49
61
  end
50
62
 
@@ -57,7 +69,11 @@ module Pact
57
69
  end
58
70
 
59
71
  def print_rerun_commands summary
60
- output.puts("\nFailed interactions:\n\n")
72
+ if ignore_failures?(summary)
73
+ output.puts("\nPending interactions: (Failures listed here are expected and do not affect your suite's status)\n\n")
74
+ else
75
+ output.puts("\nFailed interactions:\n\n")
76
+ end
61
77
  interaction_rerun_commands(summary).each do | message |
62
78
  output.puts(message)
63
79
  end
@@ -77,17 +93,32 @@ module Pact
77
93
 
78
94
  def interaction_rerun_command_for example
79
95
  example_description = example.metadata[:pact_interaction_example_description]
80
- if ENV['PACT_INTERACTION_RERUN_COMMAND']
96
+
97
+ _id = example.metadata[:pact_interaction]._id
98
+ index = example.metadata[:pact_interaction].index
99
+ provider_state = example.metadata[:pact_interaction].provider_state
100
+ description = example.metadata[:pact_interaction].description
101
+ pactfile_uri = example.metadata[:pactfile_uri]
102
+
103
+ if _id && ENV['PACT_INTERACTION_RERUN_COMMAND_FOR_BROKER']
104
+ cmd = String.new(ENV['PACT_INTERACTION_RERUN_COMMAND_FOR_BROKER'])
105
+ cmd.gsub!("<PACT_URI>", example.metadata[:pactfile_uri].to_s)
106
+ cmd.gsub!("<PACT_BROKER_INTERACTION_ID>", "#{_id}")
107
+ colorizer.wrap("#{cmd} ", ::RSpec.configuration.failure_color) + colorizer.wrap("# #{example_description}", ::RSpec.configuration.detail_color)
108
+ elsif ENV['PACT_INTERACTION_RERUN_COMMAND']
81
109
  cmd = String.new(ENV['PACT_INTERACTION_RERUN_COMMAND'])
82
- provider_state = example.metadata[:pact_interaction].provider_state
83
- description = example.metadata[:pact_interaction].description
84
- pactfile_uri = example.metadata[:pactfile_uri]
85
110
  cmd.gsub!("<PACT_URI>", pactfile_uri.to_s)
86
111
  cmd.gsub!("<PACT_DESCRIPTION>", description)
87
112
  cmd.gsub!("<PACT_PROVIDER_STATE>", "#{provider_state}")
113
+ cmd.gsub!("<PACT_INTERACTION_INDEX>", "#{index}")
88
114
  colorizer.wrap("#{cmd} ", ::RSpec.configuration.failure_color) + colorizer.wrap("# #{example_description}", ::RSpec.configuration.detail_color)
89
115
  else
90
- colorizer.wrap("* #{example_description}", ::RSpec.configuration.failure_color)
116
+ message = if _id
117
+ "* #{example_description} (to re-run just this interaction, set environment variable PACT_BROKER_INTERACTION_ID=\"#{_id}\")"
118
+ else
119
+ "* #{example_description} (to re-run just this interaction, set environment variables PACT_DESCRIPTION=\"#{description}\" PACT_PROVIDER_STATE=\"#{provider_state}\")"
120
+ end
121
+ colorizer.wrap(message, ::RSpec.configuration.failure_color)
91
122
  end
92
123
  end
93
124
 
@@ -0,0 +1,100 @@
1
+ require 'rspec/core/formatters/json_formatter'
2
+
3
+ module Pact
4
+ module Provider
5
+ module RSpec
6
+ class JsonFormatter < ::RSpec::Core::Formatters::JsonFormatter
7
+ ::RSpec::Core::Formatters.register self, :message, :dump_summary, :dump_profile, :stop, :seed, :close
8
+
9
+ def dump_summary(summary)
10
+ super(create_custom_summary(summary))
11
+ output_hash[:summary][:pacts] = pacts(summary)
12
+ end
13
+
14
+ def format_example(example)
15
+ {
16
+ :id => example.id,
17
+ :interaction_index => example.metadata[:pact_interaction].index,
18
+ :description => example.description,
19
+ :full_description => example.full_description,
20
+ :status => calculate_status(example),
21
+ :file_path => example.metadata[:file_path],
22
+ :line_number => example.metadata[:line_number],
23
+ :run_time => example.execution_result.run_time,
24
+ :mismatches => extract_differences(example),
25
+ :pact_url => example.metadata[:pact_uri].uri
26
+ }
27
+ end
28
+
29
+ def stop(notification)
30
+ output_hash[:examples] = notification.examples.map do |example|
31
+ format_example(example).tap do |hash|
32
+ e = example.exception
33
+ if e
34
+ hash[:exception] = {
35
+ class: e.class.name,
36
+ message: e.message,
37
+ }
38
+ # No point providing a backtrace for a mismatch, too much noise
39
+ if !e.is_a?(::RSpec::Expectations::ExpectationNotMetError)
40
+ hash[:exception][:backtrace]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def calculate_status(example)
48
+ if example.execution_result.status == :failed && example.metadata[:pact_ignore_failures]
49
+ 'pending'
50
+ else
51
+ example.execution_result.status.to_s
52
+ end
53
+ end
54
+
55
+ # There will most likely be only one pact associated with this RSpec execution, because
56
+ # the most likely user of this formatter is the Go implementation that parses the JSON
57
+ # and builds Go tests from them.
58
+ # If the JSON formatter is used by someone else and they have multiple pacts, all the notices
59
+ # for the pacts will be mushed together in one collection, so it will be hard to know which notice
60
+ # belongs to which pact.
61
+ def pacts(summary)
62
+ unique_pact_metadatas(summary).collect do | example_metadata |
63
+ pact_uri = example_metadata[:pact_uri]
64
+ notices = (pact_uri.metadata[:notices] && pact_uri.metadata[:notices].before_verification_notices) || []
65
+ {
66
+ notices: notices,
67
+ url: pact_uri.uri,
68
+ consumer_name: example_metadata[:pact_consumer_contract].consumer.name,
69
+ provider_name: example_metadata[:pact_consumer_contract].provider.name,
70
+ short_description: pact_uri.metadata[:short_description]
71
+ }
72
+ end
73
+ end
74
+
75
+ def unique_pact_metadatas(summary)
76
+ summary.examples.collect(&:metadata).group_by{ | metadata | metadata[:pact_uri].uri }.values.collect(&:first)
77
+ end
78
+
79
+ def create_custom_summary(summary)
80
+ ::RSpec::Core::Notifications::SummaryNotification.new(
81
+ summary.duration,
82
+ summary.examples,
83
+ summary.examples.select{ | example | example.execution_result.status == :failed && !example.metadata[:pact_ignore_failures] },
84
+ summary.examples.select{ | example | example.execution_result.status == :failed && example.metadata[:pact_ignore_failures] },
85
+ summary.load_time,
86
+ summary.errors_outside_of_examples_count
87
+ )
88
+ end
89
+
90
+ def extract_differences(example)
91
+ if example.metadata[:pact_diff]
92
+ Pact::Matchers::ExtractDiffMessages.call(example.metadata[:pact_diff]).to_a
93
+ else
94
+ []
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end