sbmt-pact 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +62 -0
- data/Appraisals +23 -0
- data/CHANGELOG.md +96 -0
- data/Dockerfile +14 -0
- data/Gemfile +5 -0
- data/LICENSE +21 -0
- data/README.md +212 -0
- data/Rakefile +12 -0
- data/dip.yml +86 -0
- data/docker-compose.yml +40 -0
- data/docs/sbmt-pact-arch.png +0 -0
- data/lefthook-local.dip_example.yml +4 -0
- data/lefthook.yml +6 -0
- data/lib/sbmt/pact/configuration.rb +23 -0
- data/lib/sbmt/pact/consumer/grpc_interaction_builder.rb +193 -0
- data/lib/sbmt/pact/consumer/http_interaction_builder.rb +149 -0
- data/lib/sbmt/pact/consumer/interaction_contents.rb +47 -0
- data/lib/sbmt/pact/consumer/message_interaction_builder.rb +285 -0
- data/lib/sbmt/pact/consumer/mock_server.rb +92 -0
- data/lib/sbmt/pact/consumer/pact_config/base.rb +24 -0
- data/lib/sbmt/pact/consumer/pact_config/grpc.rb +26 -0
- data/lib/sbmt/pact/consumer/pact_config/http.rb +26 -0
- data/lib/sbmt/pact/consumer/pact_config/message.rb +17 -0
- data/lib/sbmt/pact/consumer/pact_config.rb +24 -0
- data/lib/sbmt/pact/consumer.rb +8 -0
- data/lib/sbmt/pact/matchers/base.rb +67 -0
- data/lib/sbmt/pact/matchers/v1/equality.rb +19 -0
- data/lib/sbmt/pact/matchers/v2/regex.rb +19 -0
- data/lib/sbmt/pact/matchers/v2/type.rb +17 -0
- data/lib/sbmt/pact/matchers/v3/boolean.rb +17 -0
- data/lib/sbmt/pact/matchers/v3/date.rb +18 -0
- data/lib/sbmt/pact/matchers/v3/date_time.rb +18 -0
- data/lib/sbmt/pact/matchers/v3/decimal.rb +17 -0
- data/lib/sbmt/pact/matchers/v3/each.rb +42 -0
- data/lib/sbmt/pact/matchers/v3/include.rb +17 -0
- data/lib/sbmt/pact/matchers/v3/integer.rb +17 -0
- data/lib/sbmt/pact/matchers/v3/number.rb +17 -0
- data/lib/sbmt/pact/matchers/v3/time.rb +18 -0
- data/lib/sbmt/pact/matchers/v4/each_key.rb +26 -0
- data/lib/sbmt/pact/matchers/v4/each_key_value.rb +32 -0
- data/lib/sbmt/pact/matchers/v4/each_value.rb +33 -0
- data/lib/sbmt/pact/matchers/v4/not_empty.rb +17 -0
- data/lib/sbmt/pact/matchers.rb +94 -0
- data/lib/sbmt/pact/native/blocking_verifier.rb +17 -0
- data/lib/sbmt/pact/native/logger.rb +25 -0
- data/lib/sbmt/pact/provider/async_message_verifier.rb +32 -0
- data/lib/sbmt/pact/provider/base_verifier.rb +158 -0
- data/lib/sbmt/pact/provider/grpc_verifier.rb +42 -0
- data/lib/sbmt/pact/provider/gruf_server.rb +75 -0
- data/lib/sbmt/pact/provider/http_server.rb +66 -0
- data/lib/sbmt/pact/provider/http_verifier.rb +46 -0
- data/lib/sbmt/pact/provider/message_provider_servlet.rb +80 -0
- data/lib/sbmt/pact/provider/pact_broker_proxy.rb +85 -0
- data/lib/sbmt/pact/provider/pact_broker_proxy_runner.rb +71 -0
- data/lib/sbmt/pact/provider/pact_config/async.rb +25 -0
- data/lib/sbmt/pact/provider/pact_config/base.rb +92 -0
- data/lib/sbmt/pact/provider/pact_config/grpc.rb +30 -0
- data/lib/sbmt/pact/provider/pact_config/http.rb +25 -0
- data/lib/sbmt/pact/provider/pact_config.rb +24 -0
- data/lib/sbmt/pact/provider/provider_server_runner.rb +89 -0
- data/lib/sbmt/pact/provider/provider_state_configuration.rb +32 -0
- data/lib/sbmt/pact/provider/provider_state_servlet.rb +84 -0
- data/lib/sbmt/pact/provider.rb +8 -0
- data/lib/sbmt/pact/railtie.rb +13 -0
- data/lib/sbmt/pact/rspec/support/pact_consumer_helpers.rb +46 -0
- data/lib/sbmt/pact/rspec/support/pact_message_helpers.rb +42 -0
- data/lib/sbmt/pact/rspec/support/pact_provider_helpers.rb +87 -0
- data/lib/sbmt/pact/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
- data/lib/sbmt/pact/rspec/support/webmock/webmock_helpers.rb +30 -0
- data/lib/sbmt/pact/rspec.rb +17 -0
- data/lib/sbmt/pact/tasks/pact.rake +13 -0
- data/lib/sbmt/pact/version.rb +7 -0
- data/lib/sbmt/pact.rb +48 -0
- data/sbmt-pact.gemspec +59 -0
- 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,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
|