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,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webrick"
4
+
5
+ module Pact
6
+ module V2
7
+ module Provider
8
+ class PactBrokerProxyRunner
9
+ attr_reader :logger
10
+
11
+
12
+ def initialize(pact_broker_host:, port: 9002, host: "127.0.0.1", pact_broker_user: nil, pact_broker_password: nil, pact_broker_token: nil, logger: nil)
13
+ @host = host
14
+ @port = port
15
+ @pact_broker_host = pact_broker_host
16
+ @pact_broker_user = pact_broker_user
17
+ @pact_broker_password = pact_broker_password
18
+ @pact_broker_token = pact_broker_token
19
+ @logger = logger || Logger.new($stdout)
20
+
21
+ @thread = nil
22
+ end
23
+
24
+ def proxy_url
25
+ "http://#{@host}:#{@port}"
26
+ end
27
+
28
+ def start
29
+ raise "server already running, stop server before starting new one" if @thread
30
+ # Rack 2/3 compatibility
31
+ begin
32
+ require 'rack/handler/webrick'
33
+ handler = ::Rack::Handler::WEBrick
34
+ rescue LoadError
35
+ require 'rackup/handler/webrick'
36
+ handler = Class.new(Rackup::Handler::WEBrick)
37
+ end
38
+ @server = WEBrick::HTTPServer.new({BindAddress: @host, Port: @port}, WEBrick::Config::HTTP)
39
+ @server.mount("/", handler, PactBrokerProxy.new(
40
+ nil,
41
+ backend: @pact_broker_host,
42
+ streaming: false,
43
+ username: @pact_broker_user || nil,
44
+ password: @pact_broker_password || nil,
45
+ token: @pact_broker_token || nil,
46
+ logger: @logger
47
+ ))
48
+
49
+ @thread = Thread.new do
50
+ @logger.debug "starting pact broker proxy server"
51
+ @server.start
52
+ end
53
+ end
54
+
55
+ def stop
56
+ @logger.info("stopping pact broker proxy server")
57
+
58
+ @server&.shutdown
59
+ @thread&.join
60
+
61
+ @logger.info("pact broker proxy server stopped")
62
+ end
63
+
64
+ def run
65
+ start
66
+
67
+ yield
68
+ rescue => e
69
+ logger.fatal("FATAL ERROR: #{e.message} #{e.backtrace.join("\n")}")
70
+ raise
71
+ ensure
72
+ stop
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Pact
6
+ module V2
7
+ module Provider
8
+ module PactConfig
9
+ class Async < Base
10
+ def initialize(provider_name:, opts: {})
11
+ super
12
+ handlers = opts[:message_handlers] || {}
13
+ handlers.each do |name, block|
14
+ new_message_handler(name, &block)
15
+ end
16
+ end
17
+
18
+ def new_message_handler(name, opts: {}, &block)
19
+ provider_setup_server.add_message_handler(name, &block)
20
+ end
21
+
22
+ def new_verifier(config = nil)
23
+ AsyncMessageVerifier.new(self, config)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pact
4
+ module V2
5
+ module Provider
6
+ module PactConfig
7
+ class Base
8
+ attr_reader :provider_name, :provider_version, :log_level, :provider_setup_server, :provider_setup_port, :pact_proxy_port,
9
+ :consumer_branch, :consumer_version, :consumer_name, :broker_url, :broker_username, :broker_password, :verify_only, :pact_dir,
10
+ :pact_uri, :provider_version_branch, :provider_version_tags, :consumer_version_selectors, :enable_pending, :include_wip_pacts_since,
11
+ :fail_if_no_pacts_found, :provider_build_uri, :broker_token, :consumer_version_tags, :publish_verification_results
12
+
13
+
14
+ def initialize(provider_name:, opts: {})
15
+ @provider_name = provider_name
16
+ @log_level = opts[:log_level] || :info
17
+ @pact_dir = opts[:pact_dir] || nil
18
+ @provider_setup_port = opts[:provider_setup_port] || 9001
19
+ @pact_proxy_port = opts[:provider_setup_port] || 9002
20
+ @pact_uri = ENV.fetch("PACT_URL", nil) || opts.fetch(:pact_uri, nil)
21
+ @publish_verification_results = ENV.fetch("PACT_PUBLISH_VERIFICATION_RESULTS", nil) == "true" || opts.fetch(:publish_verification_results, false)
22
+ @provider_version = ENV.fetch("PACT_PROVIDER_VERSION", nil) || opts.fetch(:provider_version, nil)
23
+ @provider_build_uri = ENV.fetch("PACT_PROVIDER_BUILD_URL", nil) || opts.fetch(:provider_build_uri, nil)
24
+ @provider_version_branch = ENV.fetch("PACT_PROVIDER_BRANCH", nil) || opts.fetch(:provider_version_branch, nil)
25
+ @provider_version_tags = ENV.fetch("PACT_PROVIDER_VERSION_TAGS", nil) || opts.fetch(:provider_version_tags, [])
26
+ @consumer_version_tags = ENV.fetch("PACT_CONSUMER_VERSION_TAGS", nil) || opts.fetch(:consumer_version_tags, [])
27
+ @consumer_version_selectors = ENV.fetch("PACT_CONSUMER_VERSION_SELECTORS", nil) || opts.fetch(:consumer_version_selectors, nil)
28
+ @enable_pending = ENV.fetch("PACT_VERIFIER_ENABLE_PENDING", nil) == "true" || opts.fetch(:enable_pending, false)
29
+ @include_wip_pacts_since = ENV.fetch("PACT_INCLUDE_WIP_PACTS_SINCE", nil) || opts.fetch(:include_wip_pacts_since, nil)
30
+ @fail_if_no_pacts_found = ENV.fetch("PACT_FAIL_IF_NO_PACTS_FOUND", nil) == "true" || opts.fetch(:fail_if_no_pacts_found, true)
31
+ @consumer_branch = ENV.fetch("PACT_CONSUMER_BRANCH", nil) || opts.fetch(:consumer_branch, nil)
32
+ @consumer_version = ENV.fetch("PACT_CONSUMER_VERSION", nil) || opts.fetch(:consumer_version, nil)
33
+ @consumer_name = opts[:consumer_name]
34
+ @broker_url = ENV.fetch("PACT_BROKER_BASE_URL", nil) || opts.fetch(:broker_url, nil)
35
+ @broker_username = ENV.fetch("PACT_BROKER_USERNAME", nil) || opts.fetch(:broker_username, nil)
36
+ @broker_password = ENV.fetch("PACT_BROKER_PASSWORD", nil) || opts.fetch(:broker_password, nil)
37
+ @broker_token = ENV.fetch("PACT_BROKER_TOKEN", nil) || opts.fetch(:broker_token, nil)
38
+ @verify_only = [ENV.fetch("PACT_CONSUMER_FULL_NAME", nil)].compact || opts.fetch(:verify_only, [])
39
+
40
+ @provider_setup_server = opts[:provider_setup_server] || ProviderServerRunner.new(port: @provider_setup_port)
41
+ if @broker_url.present?
42
+ @pact_proxy_server = PactBrokerProxyRunner.new(
43
+ port: @pact_proxy_port,
44
+ pact_broker_host: @broker_url,
45
+ pact_broker_user: @broker_username,
46
+ pact_broker_password: @broker_password,
47
+ pact_broker_token: @broker_token
48
+ )
49
+ end
50
+ end
51
+
52
+
53
+ def start_servers
54
+ @provider_setup_server.start
55
+ @pact_proxy_server&.start
56
+ end
57
+
58
+ def stop_servers
59
+ @provider_setup_server.stop
60
+ @pact_proxy_server&.stop
61
+ end
62
+
63
+ def provider_setup_url
64
+ @provider_setup_server.state_setup_url
65
+ end
66
+
67
+ def message_setup_url # rubocop:disable Rails/Delegate
68
+ @provider_setup_server.message_setup_url
69
+ end
70
+
71
+ def pact_broker_proxy_url
72
+ @pact_proxy_server&.proxy_url
73
+ end
74
+
75
+ def new_provider_state(name, opts: {}, &block)
76
+ config = ProviderStateConfiguration.new(name, opts: opts)
77
+ config.instance_eval(&block)
78
+ config.validate!
79
+
80
+ use_hooks = !opts[:skip_hooks]
81
+
82
+ @provider_setup_server.add_setup_state(name, use_hooks, &config.setup_proc) if config.setup_proc
83
+ @provider_setup_server.add_teardown_state(name, use_hooks, &config.teardown_proc) if config.teardown_proc
84
+ end
85
+
86
+ def before_setup(&block)
87
+ @provider_setup_server.set_before_setup_hook(&block)
88
+ end
89
+
90
+ def after_teardown(&block)
91
+ @provider_setup_server.set_after_teardown_hook(&block)
92
+ end
93
+
94
+ def new_verifier
95
+ raise Pact::V2::ImplementationRequired, "#new_verifier should be implemented"
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Pact
6
+ module V2
7
+ module Provider
8
+ module PactConfig
9
+ class Grpc < Base
10
+ attr_reader :grpc_port, :grpc_services, :grpc_server
11
+
12
+ def initialize(provider_name:, opts: {})
13
+ super
14
+
15
+ @grpc_port = opts[:grpc_port] || 0
16
+ @grpc_services = opts[:grpc_services] || []
17
+ end
18
+
19
+ def new_verifier(config = nil)
20
+ GrpcVerifier.new(self, config)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Pact
6
+ module V2
7
+ module Provider
8
+ module PactConfig
9
+ class Http < Base
10
+ attr_reader :http_port
11
+ attr_reader :app
12
+
13
+ def initialize(provider_name:, opts: {})
14
+ super
15
+
16
+ @http_port = opts[:http_port] || 0
17
+ @app = opts[:app] || nil
18
+ end
19
+
20
+ def new_verifier(config = nil)
21
+ HttpVerifier.new(self, config)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ # # frozen_string_literal: true
2
+
3
+ module Pact
4
+ module V2
5
+ module Provider
6
+ module PactConfig
7
+ # Mixed config allows composing one of each: async, grpc, http
8
+ class Mixed < Base
9
+ attr_reader :async_config, :grpc_config, :http_config
10
+
11
+ def initialize(provider_name:, opts: {})
12
+ super
13
+ @provider_setup_server = ProviderServerRunner.new(port: @provider_setup_port)
14
+ if @broker_url.present?
15
+ @pact_proxy_server = PactBrokerProxyRunner.new(
16
+ port: @pact_proxy_port,
17
+ pact_broker_host: @broker_url,
18
+ pact_broker_user: @broker_username,
19
+ pact_broker_password: @broker_password,
20
+ pact_broker_token: @broker_token
21
+ )
22
+ end
23
+ @http_config = opts[:http] ? Http.new(provider_name: provider_name, opts: opts[:http].merge(provider_setup_server: provider_setup_server, pact_proxy_server: @pact_proxy_server)) : nil
24
+ @grpc_config = opts[:grpc] ? Grpc.new(provider_name: provider_name, opts: opts[:grpc].merge(provider_setup_server: provider_setup_server, pact_proxy_server: @pact_proxy_server)) : nil
25
+ @async_config = opts[:async] ? Async.new(provider_name: provider_name, opts: opts[:async].merge(provider_setup_server: provider_setup_server, pact_proxy_server: @pact_proxy_server)) : nil
26
+ end
27
+
28
+ def configs
29
+ [@async_config, @grpc_config, @http_config].compact
30
+ end
31
+
32
+ def start_servers
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require_relative "pact_config/grpc"
4
+
5
+ module Pact
6
+ module V2
7
+ module Provider
8
+ module PactConfig
9
+ def self.new(transport_type, provider_name:, opts: {})
10
+ case transport_type
11
+ when :http
12
+ Http.new(provider_name: provider_name, opts: opts)
13
+ when :grpc
14
+ Grpc.new(provider_name: provider_name, opts: opts)
15
+ when :async
16
+ Async.new(provider_name: provider_name, opts: opts)
17
+ when :mixed
18
+ Mixed.new(provider_name: provider_name, opts: opts)
19
+ else
20
+ raise ArgumentError, "unknown transport_type: #{transport_type}"
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webrick"
4
+
5
+ module Pact
6
+ module V2
7
+ module Provider
8
+ class ProviderServerRunner
9
+ attr_reader :logger
10
+
11
+ SETUP_PROVIDER_STATE_PATH = "/setup-provider"
12
+ VERIFY_MESSAGE_PATH = "/verify-message"
13
+
14
+ def initialize(port: 9001, host: "127.0.0.1", logger: nil)
15
+ @host = host
16
+ @port = port
17
+ @provider_setup_states = {}
18
+ @provider_teardown_states = {}
19
+ @logger = logger || Logger.new($stdout)
20
+
21
+ @state_servlet = ProviderStateServlet.new(logger: @logger)
22
+ @message_servlet = MessageProviderServlet.new(logger: @logger)
23
+ @thread = nil
24
+ end
25
+
26
+ def state_setup_url
27
+ "http://#{@host}:#{@port}#{SETUP_PROVIDER_STATE_PATH}"
28
+ end
29
+
30
+ def message_setup_url
31
+ "http://#{@host}:#{@port}#{VERIFY_MESSAGE_PATH}"
32
+ end
33
+
34
+ def start
35
+ raise "server already running, stop server before starting new one" if @thread
36
+
37
+ @server = WEBrick::HTTPServer.new({BindAddress: @host, Port: @port}, WEBrick::Config::HTTP)
38
+ @server.mount(SETUP_PROVIDER_STATE_PATH, @state_servlet)
39
+ @server.mount(VERIFY_MESSAGE_PATH, @message_servlet)
40
+
41
+ @thread = Thread.new do
42
+ @logger.debug "starting provider setup server"
43
+ @server.start
44
+ end
45
+ end
46
+
47
+ def stop
48
+ @logger.info("stopping provider setup server")
49
+
50
+ @server&.shutdown
51
+ @thread&.join
52
+
53
+ @logger.info("provider setup server stopped")
54
+ end
55
+
56
+ def run
57
+ start
58
+
59
+ yield
60
+ rescue => e
61
+ logger.fatal("FATAL ERROR: #{e.message} #{e.backtrace.join("\n")}")
62
+ raise
63
+ ensure
64
+ stop
65
+ end
66
+
67
+ def add_message_handler(state_name, &block)
68
+ @message_servlet.add_message_handler(state_name, &block)
69
+ end
70
+
71
+ def add_setup_state(state_name, use_before_setup_hook = true, &block)
72
+ @state_servlet.add_setup_state(state_name, use_before_setup_hook, &block)
73
+ end
74
+
75
+ def add_teardown_state(state_name, use_after_teardown_hook = true, &block)
76
+ @state_servlet.add_teardown_state(state_name, use_after_teardown_hook, &block)
77
+ end
78
+
79
+ def set_before_setup_hook(&block)
80
+ @state_servlet.before_setup(&block)
81
+ end
82
+
83
+ def set_after_teardown_hook(&block)
84
+ @state_servlet.after_teardown(&block)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pact
4
+ module V2
5
+ module Provider
6
+ class ProviderStateConfiguration
7
+ attr_reader :name, :opts, :setup_proc, :teardown_proc
8
+
9
+ class ProviderStateConfigurationError < ::Pact::V2::Error; end
10
+
11
+ def initialize(name, opts: {})
12
+ @name = name
13
+ @opts = opts
14
+ @setup_proc = nil
15
+ @teardown_proc = nil
16
+ end
17
+
18
+ def set_up(&block)
19
+ @setup_proc = block
20
+ end
21
+
22
+ def tear_down(&block)
23
+ @teardown_proc = block
24
+ end
25
+
26
+ def validate!
27
+ raise ProviderStateConfigurationError.new("no hooks configured for state #{@name}: \"provider_state\" declaration only needed if setup/teardown hooks are used for that state. Please add hooks or remove \"provider_state\" declaration") unless @setup_proc || @teardown_proc
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webrick"
4
+
5
+ module Pact
6
+ module V2
7
+ module Provider
8
+ class ProviderStateServlet < WEBrick::HTTPServlet::ProcHandler
9
+ attr_reader :logger
10
+
11
+ def initialize(logger: Logger.new($stdout))
12
+ super(build_proc)
13
+
14
+ @logger = logger
15
+
16
+ @provider_setup_states = {}
17
+ @provider_teardown_states = {}
18
+
19
+ @before_setup_hook_proc = nil
20
+ @after_teardown_hook_proc = nil
21
+
22
+ @global_setup_hook = ::Pact::V2.configuration.before_provider_state_proc
23
+ @global_teardown_hook = ::Pact::V2.configuration.after_provider_state_proc
24
+ end
25
+
26
+ def add_setup_state(name, use_before_setup_hook, &block)
27
+ raise "provider state #{name} already configured" if @provider_setup_states[name].present?
28
+
29
+ @provider_setup_states[name] = {proc: block, use_hooks: use_before_setup_hook}
30
+ end
31
+
32
+ def add_teardown_state(name, use_after_teardown_hook, &block)
33
+ raise "provider state #{name} already configured" if @provider_teardown_states[name].present?
34
+
35
+ @provider_teardown_states[name] = {proc: block, use_hooks: use_after_teardown_hook}
36
+ end
37
+
38
+ def before_setup(&block)
39
+ @before_setup_hook_proc = block
40
+ end
41
+
42
+ def after_teardown(&block)
43
+ @after_teardown_hook_proc = block
44
+ end
45
+
46
+ private
47
+
48
+ def call_setup(state_name, state_data)
49
+ logger.debug "call_setup #{state_name} with #{state_data}"
50
+ @global_setup_hook&.call
51
+ @before_setup_hook_proc&.call(state_name, state_data) if @provider_setup_states.dig(state_name, :use_hooks)
52
+ @provider_setup_states.dig(state_name, :proc)&.call(state_data)
53
+ end
54
+
55
+ def call_teardown(state_name, state_data)
56
+ logger.debug "call_teardown #{state_name} with #{state_data}"
57
+ @provider_teardown_states.dig(state_name, :proc)&.call(state_data)
58
+ @after_teardown_hook_proc&.call(state_name, state_data) if @provider_setup_states.dig(state_name, :use_hooks)
59
+ @global_teardown_hook&.call
60
+ end
61
+
62
+ def build_proc
63
+ proc do |request, response|
64
+ # {"action" => "setup", "params" => {"order_uuid" => "mxfcpcsfUOHO"},"state" => "order exists and can be saved"}
65
+ # {"action"=> "teardown", "params" => {"order_uuid" => "mxfcpcsfUOHO"}, "state" => "order exists and can be saved"}
66
+ data = JSON.parse(request.body)
67
+
68
+ action = data["action"]
69
+ state_name = data["state"]
70
+ state_data = data["params"]
71
+
72
+ logger.warn("unknown callback state action: #{action}") if action.blank?
73
+
74
+ call_setup(state_name, state_data) if action == "setup"
75
+ call_teardown(state_name, state_data) if action == "teardown"
76
+
77
+ response.status = 200
78
+ rescue JSON::ParserError => ex
79
+ logger.error("cannot parse request: #{ex.message}")
80
+ response.status = 500
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pact
4
+ module V2
5
+ module Provider
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/railtie"
4
+
5
+ module Pact
6
+ module V2
7
+ class Railtie < Rails::Railtie
8
+ rake_tasks do
9
+ load "pact/v2/tasks/pact.rake"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pact_message_helpers"
4
+ require "json"
5
+
6
+ module PactV2ConsumerDsl
7
+ include Pact::V2::Matchers
8
+
9
+ module ClassMethods
10
+ def has_http_pact_between(consumer, provider, opts: {})
11
+ _has_pact_between(:http, consumer, provider, opts: opts)
12
+ end
13
+
14
+ def has_grpc_pact_between(consumer, provider, opts: {})
15
+ _has_pact_between(:grpc, consumer, provider, opts: opts)
16
+ end
17
+
18
+ def has_message_pact_between(consumer, provider, opts: {})
19
+ _has_pact_between(:message, consumer, provider, opts: opts)
20
+ end
21
+
22
+ def _has_pact_between(transport_type, consumer, provider, opts: {})
23
+ raise "has_#{transport_type}_pact_between is designed to be used with RSpec 3+" unless defined?(::RSpec)
24
+ raise "has_#{transport_type}_pact_between has to be declared at the top level of a suite" unless top_level?
25
+ raise "has_*_pact_between cannot be declared more than once per suite" if defined?(@_pact_config)
26
+
27
+ # rubocop:disable RSpec/BeforeAfterAll
28
+ before(:context) do
29
+ @_pact_config = Pact::V2::Consumer::PactConfig.new(transport_type, consumer_name: consumer, provider_name: provider, opts: opts)
30
+ end
31
+ # rubocop:enable RSpec/BeforeAfterAll
32
+ end
33
+ end
34
+
35
+ def new_interaction(description = nil)
36
+ pact_config.new_interaction(description)
37
+ end
38
+
39
+ def reset_pact # rubocop:disable Rails/Delegate
40
+ pact_config.reset_pact
41
+ end
42
+
43
+ def pact_config
44
+ instance_variable_get(:@_pact_config)
45
+ end
46
+
47
+ def execute_http_pact
48
+ raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
49
+ mock_server = Pact::V2::Consumer::MockServer.create_for_http!(
50
+ pact: pact_config.pact_handle, host: pact_config.mock_host, port: pact_config.mock_port
51
+ )
52
+
53
+ yield(mock_server)
54
+
55
+ if mock_server.matched?
56
+ mock_server.write_pacts!(pact_config.pact_dir)
57
+ else
58
+ msg = mismatches_error_msg(mock_server)
59
+ raise Pact::V2::Consumer::HttpInteractionBuilder::InteractionMismatchesError.new(msg)
60
+ end
61
+ ensure
62
+ @used = true
63
+ mock_server&.cleanup
64
+ reset_pact
65
+ end
66
+
67
+
68
+ def mismatches_error_msg(mock_server)
69
+ rspec_example_desc = RSpec.current_example&.description
70
+ mismatches = JSON.pretty_generate(JSON.parse(mock_server.mismatches))
71
+ mismatches_with_colored_keys = mismatches.gsub(/"([^"]+)":/) { |match| "\e[34m#{match}\e[0m" } # Blue keys / white values
72
+
73
+ "#{rspec_example_desc} has mismatches: #{mismatches_with_colored_keys}"
74
+ end
75
+ end
76
+
77
+ RSpec.configure do |config|
78
+ config.include PactV2ConsumerDsl, pact_entity: :consumer
79
+ config.extend PactV2ConsumerDsl::ClassMethods, pact_entity: :consumer
80
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "waterdrop/pact_waterdrop_client"
4
+
5
+ module PactMessageHelpers
6
+ module ProviderHelpers
7
+ def with_pact_producer
8
+ client = PactWaterdropClient.new
9
+ yield(client)
10
+ client.to_pact
11
+ end
12
+
13
+ def produce_outbox_item(item)
14
+ raise "Please require sbmt/kafka_producer to use helper" unless defined?(::Sbmt::KafkaProducer)
15
+
16
+ with_pact_producer do |client|
17
+ Sbmt::KafkaProducer::OutboxProducer.new(
18
+ client: client, topic: item.transports.first.topic
19
+ ).call(item, item.payload)
20
+ end
21
+ end
22
+ end
23
+
24
+ module ConsumerHelpers
25
+ def outbox_headers
26
+ raise "Please require sbmt/outbox to use helper" unless defined?(::Sbmt::Outbox)
27
+
28
+ {
29
+ Sbmt::Outbox::OutboxItem::OUTBOX_HEADER_NAME => match_regex(/(.+?_)*outbox_item/, "order_outbox_item"),
30
+ Sbmt::Outbox::OutboxItem::IDEMPOTENCY_HEADER_NAME => match_uuid,
31
+ Sbmt::Outbox::OutboxItem::SEQUENCE_HEADER_NAME => match_regex(/\d+/, "68"),
32
+ Sbmt::Outbox::OutboxItem::EVENT_TIME_HEADER_NAME => match_iso8601,
33
+ Sbmt::Outbox::OutboxItem::DISPATCH_TIME_HEADER_NAME => match_iso8601
34
+ }
35
+ end
36
+ end
37
+ end
38
+
39
+ RSpec.configure do |config|
40
+ config.extend PactMessageHelpers::ProviderHelpers, pact_entity: :provider
41
+ config.include PactMessageHelpers::ConsumerHelpers, pact_entity: :consumer
42
+ end