pact-v2 2.0.0.pre.preview1

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 (164) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1321 -0
  3. data/LICENSE.txt +23 -0
  4. data/bin/pact +4 -0
  5. data/lib/pact/cli/generate_pact_docs.rb +4 -0
  6. data/lib/pact/cli/run_pact_verification.rb +99 -0
  7. data/lib/pact/cli/spec_criteria.rb +26 -0
  8. data/lib/pact/cli.rb +45 -0
  9. data/lib/pact/consumer/configuration/configuration_extensions.rb +90 -0
  10. data/lib/pact/consumer/configuration/dsl.rb +11 -0
  11. data/lib/pact/consumer/configuration/mock_service.rb +112 -0
  12. data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
  13. data/lib/pact/consumer/configuration/service_provider.rb +40 -0
  14. data/lib/pact/consumer/configuration.rb +10 -0
  15. data/lib/pact/consumer/consumer_contract_builder.rb +82 -0
  16. data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
  17. data/lib/pact/consumer/interaction_builder.rb +45 -0
  18. data/lib/pact/consumer/rspec.rb +35 -0
  19. data/lib/pact/consumer/spec_hooks.rb +40 -0
  20. data/lib/pact/consumer/world.rb +37 -0
  21. data/lib/pact/consumer.rb +7 -0
  22. data/lib/pact/doc/README.md +13 -0
  23. data/lib/pact/doc/doc_file.rb +40 -0
  24. data/lib/pact/doc/generate.rb +11 -0
  25. data/lib/pact/doc/generator.rb +82 -0
  26. data/lib/pact/doc/interaction_view_model.rb +124 -0
  27. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +68 -0
  28. data/lib/pact/doc/markdown/generator.rb +26 -0
  29. data/lib/pact/doc/markdown/index_renderer.rb +43 -0
  30. data/lib/pact/doc/markdown/interaction.erb +14 -0
  31. data/lib/pact/doc/markdown/interaction_renderer.rb +43 -0
  32. data/lib/pact/doc/sort_interactions.rb +16 -0
  33. data/lib/pact/hal/authorization_header_redactor.rb +32 -0
  34. data/lib/pact/hal/entity.rb +110 -0
  35. data/lib/pact/hal/http_client.rb +128 -0
  36. data/lib/pact/hal/link.rb +112 -0
  37. data/lib/pact/hal/non_json_entity.rb +28 -0
  38. data/lib/pact/hash_refinements.rb +17 -0
  39. data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +112 -0
  40. data/lib/pact/pact_broker/fetch_pacts.rb +103 -0
  41. data/lib/pact/pact_broker/notices.rb +34 -0
  42. data/lib/pact/pact_broker/pact_selection_description.rb +66 -0
  43. data/lib/pact/pact_broker.rb +25 -0
  44. data/lib/pact/project_root.rb +7 -0
  45. data/lib/pact/provider/configuration/configuration_extension.rb +69 -0
  46. data/lib/pact/provider/configuration/dsl.rb +18 -0
  47. data/lib/pact/provider/configuration/message_provider_dsl.rb +63 -0
  48. data/lib/pact/provider/configuration/pact_verification.rb +48 -0
  49. data/lib/pact/provider/configuration/pact_verification_from_broker.rb +126 -0
  50. data/lib/pact/provider/configuration/service_provider_config.rb +32 -0
  51. data/lib/pact/provider/configuration/service_provider_dsl.rb +107 -0
  52. data/lib/pact/provider/configuration.rb +7 -0
  53. data/lib/pact/provider/context.rb +0 -0
  54. data/lib/pact/provider/help/console_text.rb +76 -0
  55. data/lib/pact/provider/help/content.rb +38 -0
  56. data/lib/pact/provider/help/pact_diff.rb +43 -0
  57. data/lib/pact/provider/help/prompt_text.rb +49 -0
  58. data/lib/pact/provider/help/write.rb +56 -0
  59. data/lib/pact/provider/matchers/messages.rb +66 -0
  60. data/lib/pact/provider/pact_helper_locator.rb +24 -0
  61. data/lib/pact/provider/pact_source.rb +40 -0
  62. data/lib/pact/provider/pact_spec_runner.rb +188 -0
  63. data/lib/pact/provider/pact_uri.rb +55 -0
  64. data/lib/pact/provider/pact_verification.rb +17 -0
  65. data/lib/pact/provider/print_missing_provider_states.rb +35 -0
  66. data/lib/pact/provider/request.rb +77 -0
  67. data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
  68. data/lib/pact/provider/rspec/calculate_exit_code.rb +18 -0
  69. data/lib/pact/provider/rspec/custom_options_file +0 -0
  70. data/lib/pact/provider/rspec/formatter_rspec_2.rb +76 -0
  71. data/lib/pact/provider/rspec/formatter_rspec_3.rb +195 -0
  72. data/lib/pact/provider/rspec/json_formatter.rb +100 -0
  73. data/lib/pact/provider/rspec/matchers.rb +80 -0
  74. data/lib/pact/provider/rspec/pact_broker_formatter.rb +76 -0
  75. data/lib/pact/provider/rspec.rb +234 -0
  76. data/lib/pact/provider/state/provider_state.rb +180 -0
  77. data/lib/pact/provider/state/provider_state_configured_modules.rb +15 -0
  78. data/lib/pact/provider/state/provider_state_manager.rb +42 -0
  79. data/lib/pact/provider/state/provider_state_proxy.rb +39 -0
  80. data/lib/pact/provider/state/set_up.rb +13 -0
  81. data/lib/pact/provider/state/tear_down.rb +13 -0
  82. data/lib/pact/provider/test_methods.rb +77 -0
  83. data/lib/pact/provider/verification_report.rb +36 -0
  84. data/lib/pact/provider/verification_results/create.rb +88 -0
  85. data/lib/pact/provider/verification_results/publish.rb +143 -0
  86. data/lib/pact/provider/verification_results/publish_all.rb +50 -0
  87. data/lib/pact/provider/verification_results/verification_result.rb +40 -0
  88. data/lib/pact/provider/world.rb +50 -0
  89. data/lib/pact/provider.rb +3 -0
  90. data/lib/pact/retry.rb +37 -0
  91. data/lib/pact/tasks/task_helper.rb +62 -0
  92. data/lib/pact/tasks/verification_task.rb +95 -0
  93. data/lib/pact/tasks.rb +2 -0
  94. data/lib/pact/templates/help.erb +22 -0
  95. data/lib/pact/templates/provider_state.erb +14 -0
  96. data/lib/pact/utils/metrics.rb +100 -0
  97. data/lib/pact/utils/string.rb +35 -0
  98. data/lib/pact/v2/configuration.rb +23 -0
  99. data/lib/pact/v2/consumer/grpc_interaction_builder.rb +187 -0
  100. data/lib/pact/v2/consumer/http_interaction_builder.rb +163 -0
  101. data/lib/pact/v2/consumer/interaction_contents.rb +54 -0
  102. data/lib/pact/v2/consumer/message_interaction_builder.rb +280 -0
  103. data/lib/pact/v2/consumer/mock_server.rb +99 -0
  104. data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
  105. data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
  106. data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
  107. data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
  108. data/lib/pact/v2/consumer/pact_config.rb +24 -0
  109. data/lib/pact/v2/consumer.rb +8 -0
  110. data/lib/pact/v2/matchers/base.rb +67 -0
  111. data/lib/pact/v2/matchers/v1/equality.rb +19 -0
  112. data/lib/pact/v2/matchers/v2/regex.rb +19 -0
  113. data/lib/pact/v2/matchers/v2/type.rb +17 -0
  114. data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
  115. data/lib/pact/v2/matchers/v3/date.rb +18 -0
  116. data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
  117. data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
  118. data/lib/pact/v2/matchers/v3/each.rb +42 -0
  119. data/lib/pact/v2/matchers/v3/include.rb +17 -0
  120. data/lib/pact/v2/matchers/v3/integer.rb +17 -0
  121. data/lib/pact/v2/matchers/v3/number.rb +17 -0
  122. data/lib/pact/v2/matchers/v3/time.rb +18 -0
  123. data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
  124. data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
  125. data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
  126. data/lib/pact/v2/matchers/v4/not_empty.rb +17 -0
  127. data/lib/pact/v2/matchers.rb +94 -0
  128. data/lib/pact/v2/native/blocking_verifier.rb +17 -0
  129. data/lib/pact/v2/native/logger.rb +25 -0
  130. data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
  131. data/lib/pact/v2/provider/base_verifier.rb +242 -0
  132. data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
  133. data/lib/pact/v2/provider/gruf_server.rb +75 -0
  134. data/lib/pact/v2/provider/http_server.rb +79 -0
  135. data/lib/pact/v2/provider/http_verifier.rb +43 -0
  136. data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
  137. data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
  138. data/lib/pact/v2/provider/pact_broker_proxy.rb +71 -0
  139. data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
  140. data/lib/pact/v2/provider/pact_config/async.rb +29 -0
  141. data/lib/pact/v2/provider/pact_config/base.rb +101 -0
  142. data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
  143. data/lib/pact/v2/provider/pact_config/http.rb +27 -0
  144. data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
  145. data/lib/pact/v2/provider/pact_config.rb +26 -0
  146. data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
  147. data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
  148. data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
  149. data/lib/pact/v2/provider.rb +8 -0
  150. data/lib/pact/v2/railtie.rb +13 -0
  151. data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +80 -0
  152. data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
  153. data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
  154. data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
  155. data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
  156. data/lib/pact/v2/rspec.rb +17 -0
  157. data/lib/pact/v2/tasks/pact.rake +13 -0
  158. data/lib/pact/v2/version.rb +8 -0
  159. data/lib/pact/v2.rb +71 -0
  160. data/lib/pact/version.rb +4 -0
  161. data/lib/pact.rb +13 -0
  162. data/lib/tasks/pact.rake +34 -0
  163. data/pact.gemspec +106 -0
  164. metadata +529 -0
@@ -0,0 +1,43 @@
1
+ require 'pact/hal/entity'
2
+
3
+ module Pact
4
+ module Provider
5
+ module Help
6
+ class PactDiff
7
+ class PrintPactDiffError < StandardError; end
8
+
9
+ attr_reader :pact_source, :output
10
+
11
+ def initialize pact_source
12
+ @pact_source = pact_source
13
+ end
14
+
15
+ def self.call pact_source
16
+ new(pact_source).call
17
+ end
18
+
19
+ def call
20
+ begin
21
+ header + "\n" + get_diff
22
+ rescue PrintPactDiffError => e
23
+ return e.message
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def header
30
+ "The following changes have been made since the previous distinct version of this pact, and may be responsible for verification failure:\n"
31
+ end
32
+
33
+ def get_diff
34
+ begin
35
+ pact_source.hal_entity._link!("pb:diff-previous-distinct").get!(nil, "Accept" => "text/plain").body
36
+ rescue StandardError => e
37
+ raise PrintPactDiffError.new("Tried to retrieve diff with previous pact, but received error #{e.class} #{e.message}.")
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,49 @@
1
+ require 'pact/consumer/configuration'
2
+ require 'rainbow'
3
+ require 'pathname'
4
+
5
+ module Pact
6
+ module Provider
7
+ module Help
8
+ class PromptText
9
+
10
+ def self.call reports_dir = Pact.configuration.reports_dir, options = {color: Pact.configuration.color_enabled}
11
+ new(reports_dir, options).call
12
+ end
13
+
14
+ def initialize reports_dir, options
15
+ @reports_dir = File.expand_path(reports_dir)
16
+ @options = options
17
+ end
18
+
19
+ def call
20
+ options[:color] ? prompt_text_colored : prompt_text_plain
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :reports_dir, :options
26
+
27
+ def prompt_text_plain
28
+ "For assistance debugging failures, run `bundle exec rake pact:verify:help#{rake_args}`\n"
29
+ end
30
+
31
+ def prompt_text_colored
32
+ Rainbow(prompt_text_plain).yellow
33
+ end
34
+
35
+ def rake_args
36
+ if reports_dir == Pact.configuration.default_reports_dir
37
+ ''
38
+ else
39
+ "[#{relative_reports_dir}]"
40
+ end
41
+ end
42
+
43
+ def relative_reports_dir
44
+ Pathname.new(reports_dir).relative_path_from(Pathname.new(Dir.pwd))
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,56 @@
1
+ require 'pact/provider/help/content'
2
+ require 'fileutils'
3
+ require 'pact/consumer/configuration'
4
+
5
+ module Pact
6
+ module Provider
7
+ module Help
8
+ class Write
9
+
10
+ HELP_FILE_NAME = 'help.md'
11
+
12
+ def self.call pact_sources, reports_dir = Pact.configuration.reports_dir
13
+ new(pact_sources, reports_dir).call
14
+ end
15
+
16
+ def initialize pact_sources, reports_dir
17
+ @pact_sources = pact_sources
18
+ @reports_dir = File.expand_path(reports_dir)
19
+ end
20
+
21
+ def call
22
+ clean_reports_dir
23
+ write
24
+ rescue StandardError => e
25
+ Pact.configuration.error_stream.puts("ERROR: Error generating help output - #{e.class} #{e.message} \n" + e.backtrace.join("\n"))
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :reports_dir, :pact_sources
31
+
32
+ def clean_reports_dir
33
+ raise "Cleaning report dir #{reports_dir} would delete project!" if reports_dir_contains_pwd
34
+ FileUtils.rm_rf reports_dir
35
+ FileUtils.mkdir_p reports_dir
36
+ end
37
+
38
+ def reports_dir_contains_pwd
39
+ Dir.pwd.start_with?(reports_dir)
40
+ end
41
+
42
+ def write
43
+ File.open(help_path, "w") { |file| file << help_text }
44
+ end
45
+
46
+ def help_path
47
+ File.join(reports_dir, 'help.md')
48
+ end
49
+
50
+ def help_text
51
+ Content.new(pact_sources).text
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,66 @@
1
+ require 'rainbow'
2
+ require 'pact/term'
3
+
4
+ module Pact
5
+ module Matchers
6
+ module Messages
7
+
8
+ def match_term_failure_message diff, actual, diff_formatter, color_enabled
9
+ actual_string = String === actual ? actual : actual.to_json
10
+ maybe_coloured_string = color_enabled ? Rainbow(actual_string).white : actual_string
11
+ message = "Actual: #{maybe_coloured_string}\n\n"
12
+ formatted_diff = diff_formatter.call(diff)
13
+ message + colorize_if_enabled(formatted_diff, color_enabled)
14
+ end
15
+
16
+ def match_header_failure_message header_name, expected, actual
17
+ "Expected header \"#{header_name}\" to #{expected_desc(expected)}, but was #{actual_desc(actual)}"
18
+ end
19
+
20
+ def expected_desc_for_it expected
21
+ case expected
22
+ when NilClass then "is nil"
23
+ when Regexp
24
+ "matches #{expected.inspect}"
25
+ when Pact::Term
26
+ "matches #{expected.matcher.inspect}"
27
+ when Pact::SomethingLike
28
+ "is an instance of #{Pact::Reification.from_term(expected).class}"
29
+ else
30
+ "equals #{expected.inspect}"
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def colorize_if_enabled formatted_diff, color_enabled
37
+ if color_enabled
38
+ # RSpec wraps each line in the failure message with failure_color, turning it red.
39
+ # To ensure the lines in the diff that should be white, stay white, put an
40
+ # ANSI reset at the start of each line.
41
+ formatted_diff.split("\n").collect{ |line|"\e[0m#{line}" }.join("\n")
42
+ else
43
+ formatted_diff
44
+ end
45
+ end
46
+
47
+ def expected_desc expected
48
+ case expected
49
+ when NilClass then "be nil"
50
+ when Regexp
51
+ "match #{expected.inspect}"
52
+ when Pact::Term
53
+ "match #{expected.matcher.inspect}"
54
+ when Pact::SomethingLike
55
+ "be an instance of #{Pact::Reification.from_term(expected).class}"
56
+ else
57
+ "equal #{expected.inspect}"
58
+ end
59
+ end
60
+
61
+ def actual_desc actual
62
+ actual.nil? ? 'nil' : '"' + actual + '"'
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,24 @@
1
+ module Pact
2
+ module Provider
3
+ module PactHelperLocater
4
+ PACT_HELPER_FILE_PATTERNS = [
5
+ "spec/**/*service*consumer*/pact_helper.rb",
6
+ "spec/**/*consumer*/pact_helper.rb",
7
+ "spec/**/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
+ ]
13
+
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(", ")}"
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
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ require 'pact/consumer_contract/pact_file'
2
+ require 'pact/hal/http_client'
3
+ require 'pact/hal/entity'
4
+ require 'pact/consumer_contract'
5
+
6
+ module Pact
7
+ module Provider
8
+ class PactSource
9
+
10
+ attr_reader :uri # PactURI class
11
+
12
+ def initialize uri
13
+ @uri = uri
14
+ end
15
+
16
+ def pact_json
17
+ @pact_json ||= Pact::PactFile.read(uri.uri, uri.options)
18
+ end
19
+
20
+ def pact_hash
21
+ @pact_hash ||= JSON.load(pact_json, nil, { max_nesting: 50 })
22
+ end
23
+
24
+ def pending?
25
+ uri.metadata[:pending]
26
+ end
27
+
28
+ def consumer_contract
29
+ @consumer_contract ||= Pact::ConsumerContract.from_json(pact_json)
30
+ end
31
+
32
+ def hal_entity
33
+ http_client_keys = [:username, :password, :token]
34
+ http_client_options = uri.options.reject{ |k, _| !http_client_keys.include?(k) }
35
+ http_client = Pact::Hal::HttpClient.new(http_client_options)
36
+ Pact::Hal::Entity.new(uri, pact_hash, http_client)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,188 @@
1
+ require 'open-uri'
2
+ require 'rspec'
3
+ require 'rspec/core'
4
+ require 'rspec/core/formatters/documentation_formatter'
5
+ require 'pact/provider/pact_helper_locator'
6
+ require 'pact/project_root'
7
+ require 'pact/rspec'
8
+ require 'pact/provider/pact_source'
9
+ require 'pact/provider/help/write'
10
+ require 'pact/provider/verification_results/publish_all'
11
+ require 'pact/provider/rspec/pact_broker_formatter'
12
+ require 'pact/provider/rspec/json_formatter'
13
+ require 'pact/provider/rspec'
14
+ require 'pact/provider/rspec/calculate_exit_code'
15
+ require 'pact/utils/metrics'
16
+
17
+ module Pact
18
+ module Provider
19
+ class PactSpecRunner
20
+
21
+ include Pact::Provider::RSpec::ClassMethods
22
+
23
+ attr_reader :pact_urls
24
+ attr_reader :options
25
+
26
+ def initialize pact_urls, options = {}
27
+ @pact_urls = pact_urls
28
+ @options = options
29
+ @results = nil
30
+ end
31
+
32
+ def run
33
+ begin
34
+ configure_rspec
35
+ initialize_specs
36
+ run_specs
37
+ ensure
38
+ ::RSpec.reset
39
+ Pact.clear_provider_world
40
+ Pact.clear_consumer_world
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def configure_rspec
47
+ monkey_patch_backtrace_formatter
48
+
49
+ config = ::RSpec.configuration
50
+
51
+ config.color = true
52
+ config.pattern = "pattern which doesn't match any files"
53
+ config.backtrace_inclusion_patterns = [Regexp.new(Dir.getwd), /pact.*main/]
54
+
55
+ config.extend Pact::Provider::RSpec::ClassMethods
56
+ config.include Pact::Provider::RSpec::InstanceMethods
57
+ config.include Pact::Provider::TestMethods
58
+
59
+ if options[:silent]
60
+ config.output_stream = StringIO.new
61
+ config.error_stream = StringIO.new
62
+ else
63
+ config.error_stream = Pact.configuration.error_stream
64
+ config.output_stream = Pact.configuration.output_stream
65
+ end
66
+
67
+ configure_output
68
+
69
+ config.before(:suite) do
70
+ # Preload app before suite so the classes loaded in memory are consistent for
71
+ # before :each and after :each hooks.
72
+ # Otherwise the app and all its dependencies are loaded between the first before :each
73
+ # and the first after :each, leading to inconsistent behaviour
74
+ # (eg. with database_cleaner transactions)
75
+ Pact.configuration.provider.app
76
+ end
77
+
78
+ # For the Pact::Provider::RSpec::PactBrokerFormatter
79
+ Pact.provider_world.verbose = options[:verbose]
80
+ Pact.provider_world.pact_sources = pact_sources
81
+ jsons = pact_jsons
82
+ executing_with_ruby = executing_with_ruby?
83
+
84
+ config.after(:suite) do | suite |
85
+ Pact.provider_world.failed_examples = suite.reporter.failed_examples
86
+ Pact::Provider::Help::Write.call(Pact.provider_world.pact_sources) if executing_with_ruby
87
+ end
88
+ end
89
+
90
+ def run_specs
91
+ exit_code = if Pact::RSpec.runner_defined?
92
+ ::RSpec::Core::Runner.run(rspec_runner_options)
93
+ else
94
+ ::RSpec::Core::CommandLine.new(NoConfigurationOptions.new)
95
+ .run(::RSpec.configuration.output_stream, ::RSpec.configuration.error_stream)
96
+ end
97
+
98
+ if options[:ignore_failures]
99
+ 0
100
+ else
101
+ Pact::Provider::RSpec::CalculateExitCode.call(pact_sources, Pact.provider_world.failed_examples)
102
+ end
103
+ end
104
+
105
+ def rspec_runner_options
106
+ ["--options", Pact.project_root.join("lib/pact/provider/rspec/custom_options_file").to_s]
107
+ end
108
+
109
+ def monkey_patch_backtrace_formatter
110
+ Pact::RSpec.with_rspec_3 do
111
+ require 'pact/provider/rspec/backtrace_formatter'
112
+ end
113
+ end
114
+
115
+ def pact_sources
116
+ @pact_sources ||= begin
117
+ pact_urls.collect do | pact_url |
118
+ Pact::Provider::PactSource.new(pact_url)
119
+ end
120
+ end
121
+ end
122
+
123
+ def pact_jsons
124
+ pact_sources.collect(&:pact_json)
125
+ end
126
+
127
+ def initialize_specs
128
+ pact_sources.each do | pact_source |
129
+ spec_options = {
130
+ criteria: options[:criteria],
131
+ ignore_failures: options[:ignore_failures],
132
+ request_customizer: options[:request_customizer]
133
+ }
134
+ Pact::Utils::Metrics.report_metric("Pacts verified", "ProviderTest", "Completed")
135
+
136
+ honour_pactfile pact_source, ordered_pact_json(pact_source.pact_json), spec_options
137
+ end
138
+ end
139
+
140
+ def configure_output
141
+ Pact::RSpec.with_rspec_3 do
142
+ ::RSpec.configuration.add_formatter Pact::Provider::RSpec::PactBrokerFormatter, StringIO.new
143
+ end
144
+
145
+ output = options[:out] || Pact.configuration.output_stream
146
+ if options[:format]
147
+ formatter = options[:format] == 'json' ? Pact::Provider::RSpec::JsonFormatter : options[:format]
148
+ # Send formatted output to $stdout for parsing, unless a file is specified
149
+ output = options[:out] || $stdout
150
+ ::RSpec.configuration.add_formatter formatter, output
151
+ # Don't want to mess up the JSON parsing with INFO and DEBUG messages to stdout, so send it to stderr
152
+ Pact.configuration.output_stream = Pact.configuration.error_stream if !options[:out]
153
+ else
154
+ # Sometimes the formatter set in the cli.rb get set with an output of StringIO.. don't know why
155
+ formatter_class = Pact::RSpec.formatter_class
156
+ pact_formatter = ::RSpec.configuration.formatters.find {|f| f.class == formatter_class && f.output == ::RSpec.configuration.output_stream}
157
+ ::RSpec.configuration.add_formatter(formatter_class, output) unless pact_formatter
158
+ end
159
+
160
+ ::RSpec.configuration.full_backtrace = @options[:full_backtrace]
161
+ end
162
+
163
+ def ordered_pact_json(pact_json)
164
+ return pact_json if Pact.configuration.interactions_replay_order == :recorded
165
+
166
+ consumer_contract = JSON.parse(pact_json)
167
+ consumer_contract["interactions"] = consumer_contract["interactions"].shuffle
168
+ consumer_contract.to_json
169
+ end
170
+
171
+ def class_exists? name
172
+ Kernel.const_get name
173
+ rescue NameError
174
+ false
175
+ end
176
+
177
+ def executing_with_ruby?
178
+ ENV['PACT_EXECUTING_LANGUAGE'] == 'ruby'
179
+ end
180
+
181
+ class NoConfigurationOptions
182
+ def method_missing(method, *args, &block)
183
+ # Do nothing!
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,55 @@
1
+ module Pact
2
+ module Provider
3
+ class PactURI
4
+ attr_reader :uri, :options, :metadata
5
+
6
+ def initialize(uri, options = nil, metadata = nil)
7
+ @uri = uri
8
+ @options = options || {}
9
+ @metadata = metadata || {} # make sure it's not nil if nil is passed in
10
+ end
11
+
12
+ def == other
13
+ other.is_a?(PactURI) &&
14
+ uri == other.uri &&
15
+ options == other.options &&
16
+ metadata == other.metadata
17
+ end
18
+
19
+ def basic_auth?
20
+ !!username && !!password
21
+ end
22
+
23
+ def username
24
+ options[:username]
25
+ end
26
+
27
+ def password
28
+ options[:password]
29
+ end
30
+
31
+ def to_s
32
+ if basic_auth? && http_or_https_uri?
33
+ begin
34
+ URI(@uri).tap { |x| x.userinfo="#{username}:*****"}.to_s
35
+ rescue URI::InvalidComponentError
36
+ URI(@uri).tap { |x| x.userinfo="*****:*****"}.to_s
37
+ end
38
+ elsif personal_access_token? && http_or_https_uri?
39
+ URI(@uri).tap { |x| x.userinfo="*****"}.to_s
40
+ else
41
+ uri
42
+ end
43
+ end
44
+
45
+ private def personal_access_token?
46
+ !!username && !password
47
+ end
48
+
49
+ private def http_or_https_uri?
50
+ uri.start_with?('http://', 'https://')
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ module Pact::Provider
2
+ class PactVerification
3
+ attr_reader :consumer_name, :uri, :ref
4
+ def initialize consumer_name, uri, ref
5
+ @consumer_name = consumer_name
6
+ @uri = uri
7
+ @ref = ref
8
+ end
9
+
10
+ def == other
11
+ other.is_a?(PactVerification) &&
12
+ consumer_name == other.consumer_name &&
13
+ uri == other.uri &&
14
+ ref == other.ref
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ require 'rainbow'
2
+
3
+ module Pact
4
+ module Provider
5
+ class PrintMissingProviderStates
6
+
7
+ # Hash of consumer names to array of names of missing provider states
8
+ def self.call missing_provider_states, output
9
+ if missing_provider_states.any?
10
+ output.puts colorize(text(missing_provider_states))
11
+ end
12
+ end
13
+
14
+ def self.colorize string
15
+ lines = string.split("\n")
16
+ first_line = Rainbow(lines[0]).cyan.underline
17
+ other_lines = Rainbow(lines[1..-1].join("\n")).cyan
18
+ first_line + "\n" + other_lines
19
+ end
20
+
21
+ def self.text missing_provider_states
22
+ create_provider_states_for(missing_provider_states)
23
+ end
24
+
25
+ def self.create_provider_states_for consumers
26
+ ERB.new(template_string).result(binding)
27
+ end
28
+
29
+ def self.template_string
30
+ File.read(File.expand_path( '../../templates/provider_state.erb', __FILE__))
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,77 @@
1
+ require 'json'
2
+ require 'pact/reification'
3
+ require 'pact/shared/null_expectation'
4
+ require 'pact/generators'
5
+
6
+ module Pact
7
+ module Provider
8
+ module Request
9
+ class Replayable
10
+
11
+ # See https://github.com/rack/rack/blob/e7d741c6282ca4cf4e01506f5681e6e6b14c0b32/SPEC#L87-89
12
+ NO_HTTP_PREFIX = ["CONTENT-TYPE", "CONTENT-LENGTH"]
13
+
14
+ def initialize expected_request, state_params = nil
15
+ @expected_request = expected_request
16
+ @state_params = state_params
17
+ end
18
+
19
+ def method
20
+ expected_request.method
21
+ end
22
+
23
+ def path
24
+ Pact::Generators.apply_generators(expected_request, "path", expected_request.full_path, @state_params)
25
+ end
26
+
27
+ def body
28
+ case expected_request.body
29
+ when String then expected_request.body
30
+ when NullExpectation then ''
31
+ else
32
+ Pact::Generators.apply_generators(expected_request, "body", reified_body, @state_params)
33
+ end
34
+ end
35
+
36
+ def headers
37
+ request_headers = {}
38
+ # https://github.com/pact-foundation/pact-ruby/pull/327
39
+ request_headers.merge!('HOST' => 'localhost') if defined?(Sinatra)
40
+ return request_headers if expected_request.headers.is_a?(Pact::NullExpectation)
41
+
42
+ expected_request.headers.each do |key, value|
43
+ request_headers[key] = Pact::Reification.from_term(value)
44
+ end
45
+
46
+ request_headers = Pact::Generators.apply_generators(expected_request, "header", request_headers, @state_params)
47
+ request_headers.map{ |key,value| [rack_request_header_for(key), value]}.to_h
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :expected_request
53
+
54
+ def reified_body
55
+ rb = Pact::Reification.from_term(expected_request.body)
56
+ if rb.is_a?(String)
57
+ rb
58
+ else
59
+ JSON.dump(rb)
60
+ end
61
+ end
62
+
63
+ def rack_request_header_for header
64
+ with_http_prefix(header.to_s.upcase).tr('-', '_')
65
+ end
66
+
67
+ def rack_request_value_for value
68
+ Array(value).join("\n")
69
+ end
70
+
71
+ def with_http_prefix header
72
+ NO_HTTP_PREFIX.include?(header) ? header : "HTTP_#{header}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end