sbmt-pact 0.12.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 (77) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +62 -0
  4. data/Appraisals +23 -0
  5. data/CHANGELOG.md +96 -0
  6. data/Dockerfile +14 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +21 -0
  9. data/README.md +212 -0
  10. data/Rakefile +12 -0
  11. data/dip.yml +86 -0
  12. data/docker-compose.yml +40 -0
  13. data/docs/sbmt-pact-arch.png +0 -0
  14. data/lefthook-local.dip_example.yml +4 -0
  15. data/lefthook.yml +6 -0
  16. data/lib/sbmt/pact/configuration.rb +23 -0
  17. data/lib/sbmt/pact/consumer/grpc_interaction_builder.rb +193 -0
  18. data/lib/sbmt/pact/consumer/http_interaction_builder.rb +149 -0
  19. data/lib/sbmt/pact/consumer/interaction_contents.rb +47 -0
  20. data/lib/sbmt/pact/consumer/message_interaction_builder.rb +285 -0
  21. data/lib/sbmt/pact/consumer/mock_server.rb +92 -0
  22. data/lib/sbmt/pact/consumer/pact_config/base.rb +24 -0
  23. data/lib/sbmt/pact/consumer/pact_config/grpc.rb +26 -0
  24. data/lib/sbmt/pact/consumer/pact_config/http.rb +26 -0
  25. data/lib/sbmt/pact/consumer/pact_config/message.rb +17 -0
  26. data/lib/sbmt/pact/consumer/pact_config.rb +24 -0
  27. data/lib/sbmt/pact/consumer.rb +8 -0
  28. data/lib/sbmt/pact/matchers/base.rb +67 -0
  29. data/lib/sbmt/pact/matchers/v1/equality.rb +19 -0
  30. data/lib/sbmt/pact/matchers/v2/regex.rb +19 -0
  31. data/lib/sbmt/pact/matchers/v2/type.rb +17 -0
  32. data/lib/sbmt/pact/matchers/v3/boolean.rb +17 -0
  33. data/lib/sbmt/pact/matchers/v3/date.rb +18 -0
  34. data/lib/sbmt/pact/matchers/v3/date_time.rb +18 -0
  35. data/lib/sbmt/pact/matchers/v3/decimal.rb +17 -0
  36. data/lib/sbmt/pact/matchers/v3/each.rb +42 -0
  37. data/lib/sbmt/pact/matchers/v3/include.rb +17 -0
  38. data/lib/sbmt/pact/matchers/v3/integer.rb +17 -0
  39. data/lib/sbmt/pact/matchers/v3/number.rb +17 -0
  40. data/lib/sbmt/pact/matchers/v3/time.rb +18 -0
  41. data/lib/sbmt/pact/matchers/v4/each_key.rb +26 -0
  42. data/lib/sbmt/pact/matchers/v4/each_key_value.rb +32 -0
  43. data/lib/sbmt/pact/matchers/v4/each_value.rb +33 -0
  44. data/lib/sbmt/pact/matchers/v4/not_empty.rb +17 -0
  45. data/lib/sbmt/pact/matchers.rb +94 -0
  46. data/lib/sbmt/pact/native/blocking_verifier.rb +17 -0
  47. data/lib/sbmt/pact/native/logger.rb +25 -0
  48. data/lib/sbmt/pact/provider/async_message_verifier.rb +32 -0
  49. data/lib/sbmt/pact/provider/base_verifier.rb +158 -0
  50. data/lib/sbmt/pact/provider/grpc_verifier.rb +42 -0
  51. data/lib/sbmt/pact/provider/gruf_server.rb +75 -0
  52. data/lib/sbmt/pact/provider/http_server.rb +66 -0
  53. data/lib/sbmt/pact/provider/http_verifier.rb +46 -0
  54. data/lib/sbmt/pact/provider/message_provider_servlet.rb +80 -0
  55. data/lib/sbmt/pact/provider/pact_broker_proxy.rb +85 -0
  56. data/lib/sbmt/pact/provider/pact_broker_proxy_runner.rb +71 -0
  57. data/lib/sbmt/pact/provider/pact_config/async.rb +25 -0
  58. data/lib/sbmt/pact/provider/pact_config/base.rb +92 -0
  59. data/lib/sbmt/pact/provider/pact_config/grpc.rb +30 -0
  60. data/lib/sbmt/pact/provider/pact_config/http.rb +25 -0
  61. data/lib/sbmt/pact/provider/pact_config.rb +24 -0
  62. data/lib/sbmt/pact/provider/provider_server_runner.rb +89 -0
  63. data/lib/sbmt/pact/provider/provider_state_configuration.rb +32 -0
  64. data/lib/sbmt/pact/provider/provider_state_servlet.rb +84 -0
  65. data/lib/sbmt/pact/provider.rb +8 -0
  66. data/lib/sbmt/pact/railtie.rb +13 -0
  67. data/lib/sbmt/pact/rspec/support/pact_consumer_helpers.rb +46 -0
  68. data/lib/sbmt/pact/rspec/support/pact_message_helpers.rb +42 -0
  69. data/lib/sbmt/pact/rspec/support/pact_provider_helpers.rb +87 -0
  70. data/lib/sbmt/pact/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
  71. data/lib/sbmt/pact/rspec/support/webmock/webmock_helpers.rb +30 -0
  72. data/lib/sbmt/pact/rspec.rb +17 -0
  73. data/lib/sbmt/pact/tasks/pact.rake +13 -0
  74. data/lib/sbmt/pact/version.rb +7 -0
  75. data/lib/sbmt/pact.rb +48 -0
  76. data/sbmt-pact.gemspec +59 -0
  77. metadata +462 -0
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack-proxy"
4
+
5
+ module Sbmt
6
+ module Pact
7
+ module Provider
8
+ class PactBrokerProxy < Rack::Proxy
9
+ attr_reader :backend_uri, :path, :filter_type, :logger
10
+
11
+ # e.g. /pacts/provider/paas-stand-seeker/consumer/paas-stand-placer/pact-version/2967a9343bd8fdd28a286c4b8322380020618892/metadata/c1tdW2VdPXByb2R1Y3Rpb24mc1tdW2N2XT03MzIy
12
+ PACT_FILE_REQUEST_PATH_REGEX = %r{/pacts/provider/.+?/consumer/.+?/pact-version/.+}.freeze
13
+
14
+ def initialize(app = nil, opts = {})
15
+ super
16
+ @backend_uri = URI(opts[:backend])
17
+ @path = nil
18
+ @filter_type = opts[:filter_type]
19
+ @logger = opts[:logger] || Logger.new($stdout)
20
+ end
21
+
22
+ def perform_request(env)
23
+ request = Rack::Request.new(env)
24
+ @path = request.path
25
+
26
+ super
27
+ end
28
+
29
+ def rewrite_env(env)
30
+ env["HTTP_HOST"] = backend_uri.host
31
+ env
32
+ end
33
+
34
+ def rewrite_response(triplet)
35
+ status, headers, body = triplet
36
+
37
+ if status == "200" && PACT_FILE_REQUEST_PATH_REGEX.match?(path)
38
+ patched_body = patch_response(body.first)
39
+
40
+ # we need to recalculate content length
41
+ headers[Rack::CONTENT_LENGTH] = patched_body.bytesize.to_s
42
+
43
+ return [status, headers, [patched_body]]
44
+ end
45
+
46
+ triplet
47
+ end
48
+
49
+ private
50
+
51
+ def patch_response(raw_body)
52
+ parsed_body = JSON.parse(raw_body)
53
+
54
+ return body if parsed_body["consumer"].blank? || parsed_body["provider"].blank?
55
+ return body if parsed_body["interactions"].blank?
56
+
57
+ filter_interactions(parsed_body)
58
+
59
+ JSON.generate(parsed_body)
60
+ rescue JSON::ParserError => ex
61
+ logger.error("cannot parse broker response: #{ex.message}")
62
+ end
63
+
64
+ def filter_interactions(pact_json_hash)
65
+ return pact_json_hash unless filter_type
66
+
67
+ pact_json_hash["interactions"].each do |interaction|
68
+ set_description_prefix(interaction, "grpc:") if interaction["transport"] == "grpc" && filter_type == :grpc
69
+ set_description_prefix(interaction, "http:") if interaction["transport"] == "http" && filter_type == :http
70
+ set_description_prefix(interaction, "http:") if interaction["type"] == "Synchronous/HTTP" && filter_type == :http
71
+ set_description_prefix(interaction, "async:") if interaction["type"] == "Asynchronous/Messages" && filter_type == :async
72
+ set_description_prefix(interaction, "sync:") if interaction["type"] == "Synchronous/Messages" && filter_type == :sync
73
+ end
74
+
75
+ pact_json_hash
76
+ end
77
+
78
+ def set_description_prefix(interaction, prefix)
79
+ orig_description = interaction["description"]
80
+ interaction["description"] = "#{prefix} #{orig_description}" unless orig_description.start_with?(prefix)
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webrick"
4
+
5
+ module Sbmt
6
+ module Pact
7
+ module Provider
8
+ class PactBrokerProxyRunner
9
+ attr_reader :logger
10
+
11
+ FILTER_TYPE_NONE = nil
12
+ FILTER_TYPE_GRPC = :grpc
13
+ FILTER_TYPE_ASYNC = :async
14
+ FILTER_TYPE_SYNC = :sync
15
+ FILTER_TYPE_HTTP = :http
16
+
17
+ def initialize(pact_broker_host:, filter_type: nil, port: 9002, host: "127.0.0.1", pact_broker_user: nil, pact_broker_password: nil, logger: nil)
18
+ @host = host
19
+ @port = port
20
+ @pact_broker_host = pact_broker_host
21
+ @filter_type = filter_type
22
+ @pact_broker_user = pact_broker_user
23
+ @pact_broker_password = pact_broker_password
24
+ @logger = logger || Logger.new($stdout)
25
+
26
+ @thread = nil
27
+ end
28
+
29
+ def proxy_url
30
+ "http://#{@host}:#{@port}"
31
+ end
32
+
33
+ def start
34
+ raise "server already running, stop server before starting new one" if @thread
35
+
36
+ @server = WEBrick::HTTPServer.new({BindAddress: @host, Port: @port}, WEBrick::Config::HTTP)
37
+ @server.mount("/", Rack::Handler::WEBrick, PactBrokerProxy.new(
38
+ nil,
39
+ backend: @pact_broker_host, streaming: false, filter_type: @filter_type,
40
+ username: @pact_broker_user, password: @pact_broker_password, logger: @logger
41
+ ))
42
+
43
+ @thread = Thread.new do
44
+ Rails.logger.debug "starting pact broker proxy server"
45
+ @server.start
46
+ end
47
+ end
48
+
49
+ def stop
50
+ @logger.info("stopping pact broker proxy server")
51
+
52
+ @server&.shutdown
53
+ @thread&.join
54
+
55
+ @logger.info("pact broker proxy server stopped")
56
+ end
57
+
58
+ def run
59
+ start
60
+
61
+ yield
62
+ rescue => e
63
+ logger.fatal("FATAL ERROR: #{e.message} #{e.backtrace.join("\n")}")
64
+ raise
65
+ ensure
66
+ stop
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Pact
7
+ module Provider
8
+ module PactConfig
9
+ class Async < Base
10
+ def new_message_handler(name, opts: {}, &block)
11
+ provider_setup_server.add_message_handler(name, &block)
12
+ end
13
+
14
+ def filter_type
15
+ PACT_BROKER_FILTER_TYPE_ASYNC
16
+ end
17
+
18
+ def new_verifier
19
+ AsyncMessageVerifier.new(self)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
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
10
+
11
+ PACT_BROKER_FILTER_TYPE_NONE = PactBrokerProxyRunner::FILTER_TYPE_NONE
12
+ PACT_BROKER_FILTER_TYPE_GRPC = PactBrokerProxyRunner::FILTER_TYPE_GRPC
13
+ PACT_BROKER_FILTER_TYPE_ASYNC = PactBrokerProxyRunner::FILTER_TYPE_ASYNC
14
+ PACT_BROKER_FILTER_TYPE_SYNC = PactBrokerProxyRunner::FILTER_TYPE_SYNC
15
+ PACT_BROKER_FILTER_TYPE_HTTP = PactBrokerProxyRunner::FILTER_TYPE_HTTP
16
+
17
+ def initialize(provider_name:, opts: {})
18
+ @provider_name = provider_name
19
+ @log_level = opts[:log_level] || :info
20
+ @provider_setup_port = opts[:provider_setup_port] || 9001
21
+ @pact_proxy_port = opts[:provider_setup_port] || 9002
22
+ @provider_version = opts[:provider_version] || ENV.fetch("PACT_PROVIDER_VERSION", "1.0.0")
23
+ @consumer_branch = opts[:consumer_branch] || ENV.fetch("PACT_CONSUMER_BRANCH", nil)
24
+ @consumer_version = opts[:consumer_version] || ENV.fetch("PACT_CONSUMER_VERSION", nil)
25
+ @consumer_name = opts[:consumer_name]
26
+ @broker_url = opts[:broker_url] || ENV.fetch("PACT_BROKER_URL", nil)
27
+ @broker_username = opts[:broker_username] || ENV.fetch("PACT_BROKER_USERNAME", "")
28
+ @broker_password = opts[:broker_password] || ENV.fetch("PACT_BROKER_PASSWORD", "")
29
+ @verify_only = opts[:verify_only] || [ENV.fetch("PACT_CONSUMER_FULL_NAME", nil)].compact
30
+
31
+ @provider_setup_server = ProviderServerRunner.new(port: @provider_setup_port)
32
+ if @broker_url.present?
33
+ @pact_proxy_server = PactBrokerProxyRunner.new(
34
+ port: @pact_proxy_port, pact_broker_host: @broker_url, filter_type: filter_type,
35
+ pact_broker_user: @broker_username, pact_broker_password: @broker_password
36
+ )
37
+ end
38
+ end
39
+
40
+ def filter_type
41
+ PACT_BROKER_FILTER_TYPE_NONE
42
+ end
43
+
44
+ def start_servers
45
+ @provider_setup_server.start
46
+ @pact_proxy_server&.start
47
+ end
48
+
49
+ def stop_servers
50
+ @provider_setup_server.stop
51
+ @pact_proxy_server&.stop
52
+ end
53
+
54
+ def provider_setup_url
55
+ @provider_setup_server.state_setup_url
56
+ end
57
+
58
+ def message_setup_url
59
+ @provider_setup_server.message_setup_url
60
+ end
61
+
62
+ def pact_broker_proxy_url
63
+ @pact_proxy_server&.proxy_url
64
+ end
65
+
66
+ def new_provider_state(name, opts: {}, &block)
67
+ config = ProviderStateConfiguration.new(name, opts: opts)
68
+ config.instance_eval(&block)
69
+ config.validate!
70
+
71
+ use_hooks = !opts[:skip_hooks]
72
+
73
+ @provider_setup_server.add_setup_state(name, use_hooks, &config.setup_proc) if config.setup_proc
74
+ @provider_setup_server.add_teardown_state(name, use_hooks, &config.teardown_proc) if config.teardown_proc
75
+ end
76
+
77
+ def before_setup(&block)
78
+ @provider_setup_server.set_before_setup_hook(&block)
79
+ end
80
+
81
+ def after_teardown(&block)
82
+ @provider_setup_server.set_after_teardown_hook(&block)
83
+ end
84
+
85
+ def new_verifier
86
+ raise Sbmt::Pact::ImplementationRequired, "#new_verifier should be implemented"
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Pact
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] || 3009
16
+ @grpc_services = opts[:grpc_services] || []
17
+ end
18
+
19
+ def filter_type
20
+ PACT_BROKER_FILTER_TYPE_GRPC
21
+ end
22
+
23
+ def new_verifier
24
+ GrpcVerifier.new(self)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Pact
7
+ module Provider
8
+ module PactConfig
9
+ class Http < Base
10
+ attr_reader :http_port
11
+
12
+ def initialize(provider_name:, opts: {})
13
+ super
14
+
15
+ @http_port = opts[:http_port] || 3000
16
+ end
17
+
18
+ def new_verifier
19
+ HttpVerifier.new(self)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pact_config/grpc"
4
+
5
+ module Sbmt
6
+ module Pact
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
+ else
18
+ raise ArgumentError, "unknown transport_type: #{transport_type}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webrick"
4
+
5
+ module Sbmt
6
+ module Pact
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
+ Rails.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 Sbmt
4
+ module Pact
5
+ module Provider
6
+ class ProviderStateConfiguration
7
+ attr_reader :name, :opts, :setup_proc, :teardown_proc
8
+
9
+ class ProviderStateConfigurationError < ::Sbmt::Pact::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,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webrick"
4
+
5
+ module Sbmt
6
+ module Pact
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 = ::Sbmt::Pact.configuration.before_provider_state_proc
23
+ @global_teardown_hook = ::Sbmt::Pact.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
+ @global_setup_hook&.call
50
+ @before_setup_hook_proc&.call(state_name, state_data) if @provider_setup_states.dig(state_name, :use_hooks)
51
+ @provider_setup_states.dig(state_name, :proc)&.call(state_data)
52
+ end
53
+
54
+ def call_teardown(state_name, state_data)
55
+ @provider_teardown_states.dig(state_name, :proc)&.call(state_data)
56
+ @after_teardown_hook_proc&.call(state_name, state_data) if @provider_setup_states.dig(state_name, :use_hooks)
57
+ @global_teardown_hook&.call
58
+ end
59
+
60
+ def build_proc
61
+ proc do |request, response|
62
+ # {"action" => "setup", "params" => {"order_uuid" => "mxfcpcsfUOHO"},"state" => "order exists and can be saved"}
63
+ # {"action"=> "teardown", "params" => {"order_uuid" => "mxfcpcsfUOHO"}, "state" => "order exists and can be saved"}
64
+ data = JSON.parse(request.body)
65
+
66
+ action = data["action"]
67
+ state_name = data["state"]
68
+ state_data = data["params"]
69
+
70
+ logger.warn("unknown callback state action: #{action}") if action.blank?
71
+
72
+ call_setup(state_name, state_data) if action == "setup"
73
+ call_teardown(state_name, state_data) if action == "teardown"
74
+
75
+ response.status = 200
76
+ rescue JSON::ParserError => ex
77
+ logger.error("cannot parse request: #{ex.message}")
78
+ response.status = 500
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
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 Sbmt
6
+ module Pact
7
+ class Railtie < Rails::Railtie
8
+ rake_tasks do
9
+ load "sbmt/pact/tasks/pact.rake"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pact_message_helpers"
4
+
5
+ module SbmtPactConsumerDsl
6
+ include Sbmt::Pact::Matchers
7
+
8
+ module ClassMethods
9
+ def has_http_pact_between(consumer, provider, opts: {})
10
+ _has_pact_between(:http, consumer, provider, opts: opts)
11
+ end
12
+
13
+ def has_grpc_pact_between(consumer, provider, opts: {})
14
+ _has_pact_between(:grpc, consumer, provider, opts: opts)
15
+ end
16
+
17
+ def has_message_pact_between(consumer, provider, opts: {})
18
+ _has_pact_between(:message, consumer, provider, opts: opts)
19
+ end
20
+
21
+ def _has_pact_between(transport_type, consumer, provider, opts: {})
22
+ raise "has_#{transport_type}_pact_between is designed to be used with RSpec 3+" unless defined?(::RSpec)
23
+ raise "has_#{transport_type}_pact_between has to be declared at the top level of a suite" unless top_level?
24
+ raise "has_*_pact_between cannot be declared more than once per suite" if defined?(@_pact_config)
25
+
26
+ # rubocop:disable RSpec/BeforeAfterAll
27
+ before(:context) do
28
+ @_pact_config = Sbmt::Pact::Consumer::PactConfig.new(transport_type, consumer_name: consumer, provider_name: provider, opts: opts)
29
+ end
30
+ # rubocop:enable RSpec/BeforeAfterAll
31
+ end
32
+ end
33
+
34
+ def new_interaction(description = nil)
35
+ pact_config.new_interaction(description)
36
+ end
37
+
38
+ def pact_config
39
+ instance_variable_get(:@_pact_config)
40
+ end
41
+ end
42
+
43
+ RSpec.configure do |config|
44
+ config.include SbmtPactConsumerDsl, pact_entity: :consumer
45
+ config.extend SbmtPactConsumerDsl::ClassMethods, pact_entity: :consumer
46
+ 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