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.
- 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
|