sbmt-pact 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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