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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1321 -0
- data/LICENSE.txt +23 -0
- data/bin/pact +4 -0
- data/lib/pact/cli/generate_pact_docs.rb +4 -0
- data/lib/pact/cli/run_pact_verification.rb +99 -0
- data/lib/pact/cli/spec_criteria.rb +26 -0
- data/lib/pact/cli.rb +45 -0
- data/lib/pact/consumer/configuration/configuration_extensions.rb +90 -0
- data/lib/pact/consumer/configuration/dsl.rb +11 -0
- data/lib/pact/consumer/configuration/mock_service.rb +112 -0
- data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
- data/lib/pact/consumer/configuration/service_provider.rb +40 -0
- data/lib/pact/consumer/configuration.rb +10 -0
- data/lib/pact/consumer/consumer_contract_builder.rb +82 -0
- data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
- data/lib/pact/consumer/interaction_builder.rb +45 -0
- data/lib/pact/consumer/rspec.rb +35 -0
- data/lib/pact/consumer/spec_hooks.rb +40 -0
- data/lib/pact/consumer/world.rb +37 -0
- data/lib/pact/consumer.rb +7 -0
- data/lib/pact/doc/README.md +13 -0
- data/lib/pact/doc/doc_file.rb +40 -0
- data/lib/pact/doc/generate.rb +11 -0
- data/lib/pact/doc/generator.rb +82 -0
- data/lib/pact/doc/interaction_view_model.rb +124 -0
- data/lib/pact/doc/markdown/consumer_contract_renderer.rb +68 -0
- data/lib/pact/doc/markdown/generator.rb +26 -0
- data/lib/pact/doc/markdown/index_renderer.rb +43 -0
- data/lib/pact/doc/markdown/interaction.erb +14 -0
- data/lib/pact/doc/markdown/interaction_renderer.rb +43 -0
- data/lib/pact/doc/sort_interactions.rb +16 -0
- data/lib/pact/hal/authorization_header_redactor.rb +32 -0
- data/lib/pact/hal/entity.rb +110 -0
- data/lib/pact/hal/http_client.rb +128 -0
- data/lib/pact/hal/link.rb +112 -0
- data/lib/pact/hal/non_json_entity.rb +28 -0
- data/lib/pact/hash_refinements.rb +17 -0
- data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +112 -0
- data/lib/pact/pact_broker/fetch_pacts.rb +103 -0
- data/lib/pact/pact_broker/notices.rb +34 -0
- data/lib/pact/pact_broker/pact_selection_description.rb +66 -0
- data/lib/pact/pact_broker.rb +25 -0
- data/lib/pact/project_root.rb +7 -0
- data/lib/pact/provider/configuration/configuration_extension.rb +69 -0
- data/lib/pact/provider/configuration/dsl.rb +18 -0
- data/lib/pact/provider/configuration/message_provider_dsl.rb +63 -0
- data/lib/pact/provider/configuration/pact_verification.rb +48 -0
- data/lib/pact/provider/configuration/pact_verification_from_broker.rb +126 -0
- data/lib/pact/provider/configuration/service_provider_config.rb +32 -0
- data/lib/pact/provider/configuration/service_provider_dsl.rb +107 -0
- data/lib/pact/provider/configuration.rb +7 -0
- data/lib/pact/provider/context.rb +0 -0
- data/lib/pact/provider/help/console_text.rb +76 -0
- data/lib/pact/provider/help/content.rb +38 -0
- data/lib/pact/provider/help/pact_diff.rb +43 -0
- data/lib/pact/provider/help/prompt_text.rb +49 -0
- data/lib/pact/provider/help/write.rb +56 -0
- data/lib/pact/provider/matchers/messages.rb +66 -0
- data/lib/pact/provider/pact_helper_locator.rb +24 -0
- data/lib/pact/provider/pact_source.rb +40 -0
- data/lib/pact/provider/pact_spec_runner.rb +188 -0
- data/lib/pact/provider/pact_uri.rb +55 -0
- data/lib/pact/provider/pact_verification.rb +17 -0
- data/lib/pact/provider/print_missing_provider_states.rb +35 -0
- data/lib/pact/provider/request.rb +77 -0
- data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
- data/lib/pact/provider/rspec/calculate_exit_code.rb +18 -0
- data/lib/pact/provider/rspec/custom_options_file +0 -0
- data/lib/pact/provider/rspec/formatter_rspec_2.rb +76 -0
- data/lib/pact/provider/rspec/formatter_rspec_3.rb +195 -0
- data/lib/pact/provider/rspec/json_formatter.rb +100 -0
- data/lib/pact/provider/rspec/matchers.rb +80 -0
- data/lib/pact/provider/rspec/pact_broker_formatter.rb +76 -0
- data/lib/pact/provider/rspec.rb +234 -0
- data/lib/pact/provider/state/provider_state.rb +180 -0
- data/lib/pact/provider/state/provider_state_configured_modules.rb +15 -0
- data/lib/pact/provider/state/provider_state_manager.rb +42 -0
- data/lib/pact/provider/state/provider_state_proxy.rb +39 -0
- data/lib/pact/provider/state/set_up.rb +13 -0
- data/lib/pact/provider/state/tear_down.rb +13 -0
- data/lib/pact/provider/test_methods.rb +77 -0
- data/lib/pact/provider/verification_report.rb +36 -0
- data/lib/pact/provider/verification_results/create.rb +88 -0
- data/lib/pact/provider/verification_results/publish.rb +143 -0
- data/lib/pact/provider/verification_results/publish_all.rb +50 -0
- data/lib/pact/provider/verification_results/verification_result.rb +40 -0
- data/lib/pact/provider/world.rb +50 -0
- data/lib/pact/provider.rb +3 -0
- data/lib/pact/retry.rb +37 -0
- data/lib/pact/tasks/task_helper.rb +62 -0
- data/lib/pact/tasks/verification_task.rb +95 -0
- data/lib/pact/tasks.rb +2 -0
- data/lib/pact/templates/help.erb +22 -0
- data/lib/pact/templates/provider_state.erb +14 -0
- data/lib/pact/utils/metrics.rb +100 -0
- data/lib/pact/utils/string.rb +35 -0
- data/lib/pact/v2/configuration.rb +23 -0
- data/lib/pact/v2/consumer/grpc_interaction_builder.rb +187 -0
- data/lib/pact/v2/consumer/http_interaction_builder.rb +163 -0
- data/lib/pact/v2/consumer/interaction_contents.rb +54 -0
- data/lib/pact/v2/consumer/message_interaction_builder.rb +280 -0
- data/lib/pact/v2/consumer/mock_server.rb +99 -0
- data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
- data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
- data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
- data/lib/pact/v2/consumer/pact_config.rb +24 -0
- data/lib/pact/v2/consumer.rb +8 -0
- data/lib/pact/v2/matchers/base.rb +67 -0
- data/lib/pact/v2/matchers/v1/equality.rb +19 -0
- data/lib/pact/v2/matchers/v2/regex.rb +19 -0
- data/lib/pact/v2/matchers/v2/type.rb +17 -0
- data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
- data/lib/pact/v2/matchers/v3/date.rb +18 -0
- data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
- data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
- data/lib/pact/v2/matchers/v3/each.rb +42 -0
- data/lib/pact/v2/matchers/v3/include.rb +17 -0
- data/lib/pact/v2/matchers/v3/integer.rb +17 -0
- data/lib/pact/v2/matchers/v3/number.rb +17 -0
- data/lib/pact/v2/matchers/v3/time.rb +18 -0
- data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
- data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
- data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
- data/lib/pact/v2/matchers/v4/not_empty.rb +17 -0
- data/lib/pact/v2/matchers.rb +94 -0
- data/lib/pact/v2/native/blocking_verifier.rb +17 -0
- data/lib/pact/v2/native/logger.rb +25 -0
- data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
- data/lib/pact/v2/provider/base_verifier.rb +242 -0
- data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
- data/lib/pact/v2/provider/gruf_server.rb +75 -0
- data/lib/pact/v2/provider/http_server.rb +79 -0
- data/lib/pact/v2/provider/http_verifier.rb +43 -0
- data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
- data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
- data/lib/pact/v2/provider/pact_broker_proxy.rb +71 -0
- data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
- data/lib/pact/v2/provider/pact_config/async.rb +29 -0
- data/lib/pact/v2/provider/pact_config/base.rb +101 -0
- data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/provider/pact_config/http.rb +27 -0
- data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
- data/lib/pact/v2/provider/pact_config.rb +26 -0
- data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
- data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
- data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
- data/lib/pact/v2/provider.rb +8 -0
- data/lib/pact/v2/railtie.rb +13 -0
- data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +80 -0
- data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
- data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
- data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
- data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
- data/lib/pact/v2/rspec.rb +17 -0
- data/lib/pact/v2/tasks/pact.rake +13 -0
- data/lib/pact/v2/version.rb +8 -0
- data/lib/pact/v2.rb +71 -0
- data/lib/pact/version.rb +4 -0
- data/lib/pact.rb +13 -0
- data/lib/tasks/pact.rake +34 -0
- data/pact.gemspec +106 -0
- 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
|