pact 1.66.1 → 1.67.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 +4 -4
- data/CHANGELOG.md +30 -0
- data/lib/pact/provider/request.rb +14 -1
- data/lib/pact/v2/configuration.rb +23 -0
- data/lib/pact/v2/consumer/grpc_interaction_builder.rb +194 -0
- data/lib/pact/v2/consumer/http_interaction_builder.rb +162 -0
- data/lib/pact/v2/consumer/interaction_contents.rb +62 -0
- data/lib/pact/v2/consumer/message_interaction_builder.rb +287 -0
- data/lib/pact/v2/consumer/mock_server.rb +100 -0
- data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
- data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
- data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
- data/lib/pact/v2/consumer/pact_config/plugin_async_message.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/plugin_http.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/plugin_sync_message.rb +26 -0
- data/lib/pact/v2/consumer/pact_config.rb +30 -0
- data/lib/pact/v2/consumer/plugin_async_message_interaction_builder.rb +171 -0
- data/lib/pact/v2/consumer/plugin_http_interaction_builder.rb +201 -0
- data/lib/pact/v2/consumer/plugin_sync_message_interaction_builder.rb +180 -0
- data/lib/pact/v2/consumer.rb +8 -0
- data/lib/pact/v2/generators/base.rb +287 -0
- data/lib/pact/v2/generators.rb +49 -0
- data/lib/pact/v2/matchers/base.rb +74 -0
- data/lib/pact/v2/matchers/v1/equality.rb +19 -0
- data/lib/pact/v2/matchers/v2/regex.rb +19 -0
- data/lib/pact/v2/matchers/v2/type.rb +17 -0
- data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
- data/lib/pact/v2/matchers/v3/content_type.rb +32 -0
- data/lib/pact/v2/matchers/v3/date.rb +18 -0
- data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
- data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
- data/lib/pact/v2/matchers/v3/each.rb +42 -0
- data/lib/pact/v2/matchers/v3/include.rb +17 -0
- data/lib/pact/v2/matchers/v3/integer.rb +17 -0
- data/lib/pact/v2/matchers/v3/null.rb +16 -0
- data/lib/pact/v2/matchers/v3/number.rb +17 -0
- data/lib/pact/v2/matchers/v3/semver.rb +23 -0
- data/lib/pact/v2/matchers/v3/time.rb +18 -0
- data/lib/pact/v2/matchers/v3/values.rb +16 -0
- data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
- data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
- data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
- data/lib/pact/v2/matchers/v4/not_empty.rb +24 -0
- data/lib/pact/v2/matchers/v4/status_code.rb +17 -0
- data/lib/pact/v2/matchers.rb +110 -0
- data/lib/pact/v2/native/blocking_verifier.rb +17 -0
- data/lib/pact/v2/native/logger.rb +25 -0
- data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
- data/lib/pact/v2/provider/base_verifier.rb +242 -0
- data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
- data/lib/pact/v2/provider/gruf_server.rb +75 -0
- data/lib/pact/v2/provider/http_server.rb +79 -0
- data/lib/pact/v2/provider/http_verifier.rb +43 -0
- data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
- data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
- data/lib/pact/v2/provider/pact_broker_proxy.rb +66 -0
- data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
- data/lib/pact/v2/provider/pact_config/async.rb +29 -0
- data/lib/pact/v2/provider/pact_config/base.rb +101 -0
- data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/provider/pact_config/http.rb +27 -0
- data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
- data/lib/pact/v2/provider/pact_config.rb +26 -0
- data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
- data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
- data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
- data/lib/pact/v2/provider.rb +8 -0
- data/lib/pact/v2/railtie.rb +13 -0
- data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +93 -0
- data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
- data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
- data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
- data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
- data/lib/pact/v2/rspec.rb +17 -0
- data/lib/pact/v2/tasks/pact.rake +13 -0
- data/lib/pact/v2.rb +71 -0
- data/lib/pact/version.rb +1 -1
- data/lib/pact.rb +1 -0
- data/pact.gemspec +52 -7
- metadata +421 -67
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Provider
|
|
6
|
+
# inspired by Gruf::Cli::Executor
|
|
7
|
+
class GrufServer
|
|
8
|
+
SERVER_STOP_TIMEOUT_SEC = 15
|
|
9
|
+
|
|
10
|
+
def initialize(options = {})
|
|
11
|
+
@options = options
|
|
12
|
+
|
|
13
|
+
setup!
|
|
14
|
+
|
|
15
|
+
@server_pid = nil
|
|
16
|
+
|
|
17
|
+
@services = @options[:services].is_a?(Array) ? @options[:services] : []
|
|
18
|
+
@logger = @options[:logger] || ::Logger.new($stdout)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def start
|
|
22
|
+
raise "server already running, stop server before starting new one" if @thread
|
|
23
|
+
|
|
24
|
+
@logger.info("[gruf] starting standalone server with options: #{@options}")
|
|
25
|
+
|
|
26
|
+
@server = Gruf::Server.new(Gruf.server_options)
|
|
27
|
+
@services.each { |s| @server.add_service(s) } if @services.any?
|
|
28
|
+
@thread = Thread.new do
|
|
29
|
+
@logger.debug "[gruf] starting grpc server"
|
|
30
|
+
@server.start!
|
|
31
|
+
end
|
|
32
|
+
@server.server.wait_till_running(10)
|
|
33
|
+
|
|
34
|
+
@logger.info("[gruf] standalone server started")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def stop
|
|
38
|
+
@logger.info("[gruf] stopping standalone server")
|
|
39
|
+
|
|
40
|
+
@server&.server&.stop
|
|
41
|
+
@thread&.join(SERVER_STOP_TIMEOUT_SEC)
|
|
42
|
+
@thread&.kill
|
|
43
|
+
|
|
44
|
+
@logger.info("[gruf] standalone server stopped")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Run the server
|
|
49
|
+
#
|
|
50
|
+
def run
|
|
51
|
+
start
|
|
52
|
+
|
|
53
|
+
yield
|
|
54
|
+
rescue => e
|
|
55
|
+
@logger.fatal("FATAL ERROR: #{e.message} #{e.backtrace.join("\n")}")
|
|
56
|
+
raise
|
|
57
|
+
ensure
|
|
58
|
+
stop
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def setup!
|
|
64
|
+
Gruf.server_binding_url = @options[:host] if @options[:host]
|
|
65
|
+
if @options[:suppress_default_interceptors]
|
|
66
|
+
Gruf.interceptors.remove(Gruf::Interceptors::ActiveRecord::ConnectionReset)
|
|
67
|
+
Gruf.interceptors.remove(Gruf::Interceptors::Instrumentation::OutputMetadataTimer)
|
|
68
|
+
end
|
|
69
|
+
Gruf.backtrace_on_error = true if @options[:backtrace_on_error]
|
|
70
|
+
Gruf.health_check_enabled = true if @options[:health_check]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Provider
|
|
6
|
+
# inspired by Gruf::Cli::Executor
|
|
7
|
+
class HttpServer
|
|
8
|
+
SERVER_STOP_TIMEOUT_SEC = 15
|
|
9
|
+
|
|
10
|
+
def initialize(options = {})
|
|
11
|
+
@options = options
|
|
12
|
+
|
|
13
|
+
@server_pid = nil
|
|
14
|
+
|
|
15
|
+
@host = @options[:host] || "localhost"
|
|
16
|
+
@logger = @options[:logger] || ::Logger.new($stdout)
|
|
17
|
+
# allow any rack based app to be passed in, otherwise
|
|
18
|
+
# we will load a Rails.application
|
|
19
|
+
# allows for backwards compat with pact-ruby v1
|
|
20
|
+
@app = @options[:app] || nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def start
|
|
24
|
+
raise "server already running, stop server before starting new one" if @thread
|
|
25
|
+
|
|
26
|
+
@logger.info("[webrick] starting server with options: #{@options}")
|
|
27
|
+
|
|
28
|
+
@thread = Thread.new do
|
|
29
|
+
@logger.debug "[webrick] starting http server"
|
|
30
|
+
|
|
31
|
+
# TODO: load from config.ru, if not rails and no app provided?
|
|
32
|
+
# Rack 2/3 compatibility
|
|
33
|
+
begin
|
|
34
|
+
require 'rack/handler/webrick'
|
|
35
|
+
handler = ::Rack::Handler::WEBrick
|
|
36
|
+
rescue LoadError
|
|
37
|
+
require 'rackup/handler/webrick'
|
|
38
|
+
handler = Class.new(Rackup::Handler::WEBrick)
|
|
39
|
+
end
|
|
40
|
+
handler.run(@app || (defined?(Rails) ? Rails.application : nil),
|
|
41
|
+
Host: @options[:host],
|
|
42
|
+
Port: @options[:port],
|
|
43
|
+
Logger: @logger,
|
|
44
|
+
StartCallback: -> { @started = true }) do |server|
|
|
45
|
+
@server = server
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
sleep 0.001 until @started
|
|
49
|
+
|
|
50
|
+
@logger.info("[webrick] server started")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def stop
|
|
54
|
+
@logger.info("[webrick] stopping server")
|
|
55
|
+
|
|
56
|
+
@server&.shutdown
|
|
57
|
+
@thread&.join(SERVER_STOP_TIMEOUT_SEC)
|
|
58
|
+
@thread&.kill
|
|
59
|
+
|
|
60
|
+
@logger.info("[webrick] server stopped")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# Run the server
|
|
65
|
+
#
|
|
66
|
+
def run
|
|
67
|
+
start
|
|
68
|
+
|
|
69
|
+
yield
|
|
70
|
+
rescue => e
|
|
71
|
+
@logger.fatal("FATAL ERROR: #{e.message} #{e.backtrace.join("\n")}")
|
|
72
|
+
raise
|
|
73
|
+
ensure
|
|
74
|
+
stop
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pact/ffi/verifier"
|
|
4
|
+
require "pact/v2/native/logger"
|
|
5
|
+
|
|
6
|
+
module Pact
|
|
7
|
+
module V2
|
|
8
|
+
module Provider
|
|
9
|
+
class HttpVerifier < BaseVerifier
|
|
10
|
+
PROVIDER_TRANSPORT_TYPE = "http"
|
|
11
|
+
|
|
12
|
+
def initialize(pact_config, mixed_config = nil)
|
|
13
|
+
super
|
|
14
|
+
|
|
15
|
+
raise ArgumentError, "pact_config must be an instance of Pact::V2::Provider::PactConfig::Http" unless pact_config.is_a?(::Pact::V2::Provider::PactConfig::Http)
|
|
16
|
+
@http_server = HttpServer.new(host: "127.0.0.1", port: @pact_config.http_port, app: @pact_config.app)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def set_provider_info(pact_handle)
|
|
22
|
+
PactFfi::Verifier.set_provider_info(pact_handle, @pact_config.provider_name, "", "", @pact_config.http_port, "")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def add_provider_transport(pact_handle)
|
|
26
|
+
# The http transport is already added when the `set_provider_info` method is called,
|
|
27
|
+
# so we don't need to explicitly add the transport here
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def start_servers!
|
|
32
|
+
super
|
|
33
|
+
@http_server.start
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def stop_servers
|
|
37
|
+
super
|
|
38
|
+
@http_server.stop
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webrick"
|
|
4
|
+
|
|
5
|
+
module Pact
|
|
6
|
+
module V2
|
|
7
|
+
module Provider
|
|
8
|
+
class MessageProviderServlet < WEBrick::HTTPServlet::ProcHandler
|
|
9
|
+
attr_reader :logger
|
|
10
|
+
|
|
11
|
+
CONTENT_TYPE_JSON = "application/json"
|
|
12
|
+
CONTENT_TYPE_PROTO = "application/protobuf"
|
|
13
|
+
METADATA_HEADER = "pact-message-metadata"
|
|
14
|
+
|
|
15
|
+
def initialize(logger: Logger.new($stdout))
|
|
16
|
+
super(build_proc)
|
|
17
|
+
|
|
18
|
+
@message_handlers = {}
|
|
19
|
+
|
|
20
|
+
@logger = logger
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_message_handler(name, &block)
|
|
24
|
+
raise "message handler for #{name} already configured" if @message_handlers[name].present?
|
|
25
|
+
|
|
26
|
+
@message_handlers[name] = {proc: block}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_proc
|
|
32
|
+
proc do |request, response|
|
|
33
|
+
# {"description":"message: ","providerStates":[{"name":"pet exists","params":{"pet_id":1}}]}
|
|
34
|
+
data = JSON.parse(request.body)
|
|
35
|
+
|
|
36
|
+
description = data["description"]
|
|
37
|
+
provider_states = data["providerStates"]
|
|
38
|
+
|
|
39
|
+
body, metadata = handle(description, provider_states)
|
|
40
|
+
|
|
41
|
+
response.status = 200
|
|
42
|
+
if body.is_a?(String)
|
|
43
|
+
# protobuf-serialized body
|
|
44
|
+
response.body = body
|
|
45
|
+
response.content_type = metadata[:content_type] || CONTENT_TYPE_PROTO
|
|
46
|
+
else
|
|
47
|
+
response.body = body.to_json
|
|
48
|
+
response.content_type = CONTENT_TYPE_JSON
|
|
49
|
+
end
|
|
50
|
+
response[METADATA_HEADER] = Base64.urlsafe_encode64(metadata.to_json)
|
|
51
|
+
rescue JSON::ParserError => ex
|
|
52
|
+
logger.error("cannot parse request: #{ex.message}")
|
|
53
|
+
response.status = 500
|
|
54
|
+
rescue => ex
|
|
55
|
+
logger.error("cannot handle message request: #{ex.message}")
|
|
56
|
+
response.status = 500
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def handle(description, provider_states)
|
|
61
|
+
handler = find_handler_for(description)
|
|
62
|
+
return {}, {} unless handler
|
|
63
|
+
|
|
64
|
+
body, metadata = handler[:proc].call(provider_states&.first || {})
|
|
65
|
+
unless metadata[:content_type]
|
|
66
|
+
# try to find content-type in provider states
|
|
67
|
+
content_type = provider_states&.filter_map { |state| state.dig("params", "contentType") }&.first
|
|
68
|
+
metadata[:content_type] = content_type if content_type
|
|
69
|
+
end
|
|
70
|
+
[body, metadata]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def find_handler_for(description)
|
|
74
|
+
@message_handlers[description]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# # frozen_string_literal: true
|
|
2
|
+
module Pact
|
|
3
|
+
module V2
|
|
4
|
+
module Provider
|
|
5
|
+
# MixedVerifier coordinates verification for all present configs (async, grpc, http)
|
|
6
|
+
class MixedVerifier
|
|
7
|
+
attr_reader :mixed_config, :verifiers
|
|
8
|
+
|
|
9
|
+
def initialize(mixed_config)
|
|
10
|
+
unless mixed_config.is_a?(::Pact::V2::Provider::PactConfig::Mixed)
|
|
11
|
+
raise ArgumentError, "mixed_config must be a PactConfig::Mixed"
|
|
12
|
+
end
|
|
13
|
+
@mixed_config = mixed_config
|
|
14
|
+
@verifiers = []
|
|
15
|
+
@verifiers << AsyncMessageVerifier.new(mixed_config.async_config) if mixed_config.async_config
|
|
16
|
+
@verifiers << GrpcVerifier.new(mixed_config.grpc_config) if mixed_config.grpc_config
|
|
17
|
+
@verifiers << HttpVerifier.new(mixed_config.http_config) if mixed_config.http_config
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack-proxy"
|
|
4
|
+
|
|
5
|
+
module Pact
|
|
6
|
+
module V2
|
|
7
|
+
module Provider
|
|
8
|
+
class PactBrokerProxy < Rack::Proxy
|
|
9
|
+
attr_reader :backend_uri, :path, :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
|
+
@logger = opts[:logger] || Logger.new($stdout)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def perform_request(env)
|
|
22
|
+
request = Rack::Request.new(env)
|
|
23
|
+
env["rack.timeout"] ||= ENV.fetch("PACT_BROKER_REQUEST_TIMEOUT", 5).to_i
|
|
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
|
+
|
|
58
|
+
JSON.generate(parsed_body)
|
|
59
|
+
rescue JSON::ParserError => ex
|
|
60
|
+
logger.error("cannot parse broker response: #{ex.message}")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "webrick"
|
|
4
|
+
|
|
5
|
+
module Pact
|
|
6
|
+
module V2
|
|
7
|
+
module Provider
|
|
8
|
+
class PactBrokerProxyRunner
|
|
9
|
+
attr_reader :logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def initialize(pact_broker_host:, port: 9002, host: "127.0.0.1", pact_broker_user: nil, pact_broker_password: nil, pact_broker_token: nil, logger: nil)
|
|
13
|
+
@host = host
|
|
14
|
+
@port = port
|
|
15
|
+
@pact_broker_host = pact_broker_host
|
|
16
|
+
@pact_broker_user = pact_broker_user
|
|
17
|
+
@pact_broker_password = pact_broker_password
|
|
18
|
+
@pact_broker_token = pact_broker_token
|
|
19
|
+
@logger = logger || Logger.new($stdout)
|
|
20
|
+
|
|
21
|
+
@thread = nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def proxy_url
|
|
25
|
+
"http://#{@host}:#{@port}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def start
|
|
29
|
+
raise "server already running, stop server before starting new one" if @thread
|
|
30
|
+
# Rack 2/3 compatibility
|
|
31
|
+
begin
|
|
32
|
+
require 'rack/handler/webrick'
|
|
33
|
+
handler = ::Rack::Handler::WEBrick
|
|
34
|
+
rescue LoadError
|
|
35
|
+
require 'rackup/handler/webrick'
|
|
36
|
+
handler = Class.new(Rackup::Handler::WEBrick)
|
|
37
|
+
end
|
|
38
|
+
@server = WEBrick::HTTPServer.new({BindAddress: @host, Port: @port}, WEBrick::Config::HTTP)
|
|
39
|
+
@server.mount("/", handler, PactBrokerProxy.new(
|
|
40
|
+
nil,
|
|
41
|
+
backend: @pact_broker_host,
|
|
42
|
+
streaming: false,
|
|
43
|
+
username: @pact_broker_user || nil,
|
|
44
|
+
password: @pact_broker_password || nil,
|
|
45
|
+
token: @pact_broker_token || nil,
|
|
46
|
+
logger: @logger
|
|
47
|
+
))
|
|
48
|
+
|
|
49
|
+
@thread = Thread.new do
|
|
50
|
+
@logger.debug "starting pact broker proxy server"
|
|
51
|
+
@server.start
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stop
|
|
56
|
+
@logger.info("stopping pact broker proxy server")
|
|
57
|
+
|
|
58
|
+
@server&.shutdown
|
|
59
|
+
@thread&.join
|
|
60
|
+
|
|
61
|
+
@logger.info("pact broker proxy server stopped")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def run
|
|
65
|
+
start
|
|
66
|
+
|
|
67
|
+
yield
|
|
68
|
+
rescue => e
|
|
69
|
+
logger.fatal("FATAL ERROR: #{e.message} #{e.backtrace.join("\n")}")
|
|
70
|
+
raise
|
|
71
|
+
ensure
|
|
72
|
+
stop
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Pact
|
|
6
|
+
module V2
|
|
7
|
+
module Provider
|
|
8
|
+
module PactConfig
|
|
9
|
+
class Async < Base
|
|
10
|
+
def initialize(provider_name:, opts: {})
|
|
11
|
+
super
|
|
12
|
+
handlers = opts[:message_handlers] || {}
|
|
13
|
+
handlers.each do |name, block|
|
|
14
|
+
new_message_handler(name, &block)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def new_message_handler(name, opts: {}, &block)
|
|
19
|
+
provider_setup_server.add_message_handler(name, &block)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def new_verifier(config = nil)
|
|
23
|
+
AsyncMessageVerifier.new(self, config)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Provider
|
|
6
|
+
module PactConfig
|
|
7
|
+
class Base
|
|
8
|
+
attr_reader :provider_name, :provider_version, :log_level, :provider_setup_server, :provider_setup_port, :pact_proxy_port,
|
|
9
|
+
:consumer_branch, :consumer_version, :consumer_name, :broker_url, :broker_username, :broker_password, :verify_only, :pact_dir,
|
|
10
|
+
:pact_uri, :provider_version_branch, :provider_version_tags, :consumer_version_selectors, :enable_pending, :include_wip_pacts_since,
|
|
11
|
+
:fail_if_no_pacts_found, :provider_build_uri, :broker_token, :consumer_version_tags, :publish_verification_results
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def initialize(provider_name:, opts: {})
|
|
15
|
+
@provider_name = provider_name
|
|
16
|
+
@log_level = opts[:log_level] || :info
|
|
17
|
+
@pact_dir = opts[:pact_dir] || nil
|
|
18
|
+
@provider_setup_port = opts[:provider_setup_port] || 9001
|
|
19
|
+
@pact_proxy_port = opts[:provider_setup_port] || 9002
|
|
20
|
+
@pact_uri = ENV.fetch("PACT_URL", nil) || opts.fetch(:pact_uri, nil)
|
|
21
|
+
@publish_verification_results = ENV.fetch("PACT_PUBLISH_VERIFICATION_RESULTS", nil) == "true" || opts.fetch(:publish_verification_results, false)
|
|
22
|
+
@provider_version = ENV.fetch("PACT_PROVIDER_VERSION", nil) || opts.fetch(:provider_version, nil)
|
|
23
|
+
@provider_build_uri = ENV.fetch("PACT_PROVIDER_BUILD_URL", nil) || opts.fetch(:provider_build_uri, nil)
|
|
24
|
+
@provider_version_branch = ENV.fetch("PACT_PROVIDER_BRANCH", nil) || opts.fetch(:provider_version_branch, nil)
|
|
25
|
+
@provider_version_tags = ENV.fetch("PACT_PROVIDER_VERSION_TAGS", nil) || opts.fetch(:provider_version_tags, [])
|
|
26
|
+
@consumer_version_tags = ENV.fetch("PACT_CONSUMER_VERSION_TAGS", nil) || opts.fetch(:consumer_version_tags, [])
|
|
27
|
+
@consumer_version_selectors = ENV.fetch("PACT_CONSUMER_VERSION_SELECTORS", nil) || opts.fetch(:consumer_version_selectors, nil)
|
|
28
|
+
@enable_pending = ENV.fetch("PACT_VERIFIER_ENABLE_PENDING", nil) == "true" || opts.fetch(:enable_pending, false)
|
|
29
|
+
@include_wip_pacts_since = ENV.fetch("PACT_INCLUDE_WIP_PACTS_SINCE", nil) || opts.fetch(:include_wip_pacts_since, nil)
|
|
30
|
+
@fail_if_no_pacts_found = ENV.fetch("PACT_FAIL_IF_NO_PACTS_FOUND", nil) == "true" || opts.fetch(:fail_if_no_pacts_found, true)
|
|
31
|
+
@consumer_branch = ENV.fetch("PACT_CONSUMER_BRANCH", nil) || opts.fetch(:consumer_branch, nil)
|
|
32
|
+
@consumer_version = ENV.fetch("PACT_CONSUMER_VERSION", nil) || opts.fetch(:consumer_version, nil)
|
|
33
|
+
@consumer_name = opts[:consumer_name]
|
|
34
|
+
@broker_url = ENV.fetch("PACT_BROKER_BASE_URL", nil) || opts.fetch(:broker_url, nil)
|
|
35
|
+
@broker_username = ENV.fetch("PACT_BROKER_USERNAME", nil) || opts.fetch(:broker_username, nil)
|
|
36
|
+
@broker_password = ENV.fetch("PACT_BROKER_PASSWORD", nil) || opts.fetch(:broker_password, nil)
|
|
37
|
+
@broker_token = ENV.fetch("PACT_BROKER_TOKEN", nil) || opts.fetch(:broker_token, nil)
|
|
38
|
+
@verify_only = [ENV.fetch("PACT_CONSUMER_FULL_NAME", nil)].compact || opts.fetch(:verify_only, [])
|
|
39
|
+
|
|
40
|
+
@provider_setup_server = opts[:provider_setup_server] || ProviderServerRunner.new(port: @provider_setup_port)
|
|
41
|
+
if @broker_url.present?
|
|
42
|
+
@pact_proxy_server = PactBrokerProxyRunner.new(
|
|
43
|
+
port: @pact_proxy_port,
|
|
44
|
+
pact_broker_host: @broker_url,
|
|
45
|
+
pact_broker_user: @broker_username,
|
|
46
|
+
pact_broker_password: @broker_password,
|
|
47
|
+
pact_broker_token: @broker_token
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def start_servers
|
|
54
|
+
@provider_setup_server.start
|
|
55
|
+
@pact_proxy_server&.start
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def stop_servers
|
|
59
|
+
@provider_setup_server.stop
|
|
60
|
+
@pact_proxy_server&.stop
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def provider_setup_url
|
|
64
|
+
@provider_setup_server.state_setup_url
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def message_setup_url # rubocop:disable Rails/Delegate
|
|
68
|
+
@provider_setup_server.message_setup_url
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def pact_broker_proxy_url
|
|
72
|
+
@pact_proxy_server&.proxy_url
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def new_provider_state(name, opts: {}, &block)
|
|
76
|
+
config = ProviderStateConfiguration.new(name, opts: opts)
|
|
77
|
+
config.instance_eval(&block)
|
|
78
|
+
config.validate!
|
|
79
|
+
|
|
80
|
+
use_hooks = !opts[:skip_hooks]
|
|
81
|
+
|
|
82
|
+
@provider_setup_server.add_setup_state(name, use_hooks, &config.setup_proc) if config.setup_proc
|
|
83
|
+
@provider_setup_server.add_teardown_state(name, use_hooks, &config.teardown_proc) if config.teardown_proc
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def before_setup(&block)
|
|
87
|
+
@provider_setup_server.set_before_setup_hook(&block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def after_teardown(&block)
|
|
91
|
+
@provider_setup_server.set_after_teardown_hook(&block)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def new_verifier
|
|
95
|
+
raise Pact::V2::ImplementationRequired, "#new_verifier should be implemented"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Pact
|
|
6
|
+
module V2
|
|
7
|
+
module Provider
|
|
8
|
+
module PactConfig
|
|
9
|
+
class Grpc < Base
|
|
10
|
+
attr_reader :grpc_port, :grpc_services, :grpc_server
|
|
11
|
+
|
|
12
|
+
def initialize(provider_name:, opts: {})
|
|
13
|
+
super
|
|
14
|
+
|
|
15
|
+
@grpc_port = opts[:grpc_port] || 0
|
|
16
|
+
@grpc_services = opts[:grpc_services] || []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def new_verifier(config = nil)
|
|
20
|
+
GrpcVerifier.new(self, config)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Pact
|
|
6
|
+
module V2
|
|
7
|
+
module Provider
|
|
8
|
+
module PactConfig
|
|
9
|
+
class Http < Base
|
|
10
|
+
attr_reader :http_port
|
|
11
|
+
attr_reader :app
|
|
12
|
+
|
|
13
|
+
def initialize(provider_name:, opts: {})
|
|
14
|
+
super
|
|
15
|
+
|
|
16
|
+
@http_port = opts[:http_port] || 0
|
|
17
|
+
@app = opts[:app] || nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def new_verifier(config = nil)
|
|
21
|
+
HttpVerifier.new(self, config)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# # frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Provider
|
|
6
|
+
module PactConfig
|
|
7
|
+
# Mixed config allows composing one of each: async, grpc, http
|
|
8
|
+
class Mixed < Base
|
|
9
|
+
attr_reader :async_config, :grpc_config, :http_config
|
|
10
|
+
|
|
11
|
+
def initialize(provider_name:, opts: {})
|
|
12
|
+
super
|
|
13
|
+
@provider_setup_server = ProviderServerRunner.new(port: @provider_setup_port)
|
|
14
|
+
if @broker_url.present?
|
|
15
|
+
@pact_proxy_server = PactBrokerProxyRunner.new(
|
|
16
|
+
port: @pact_proxy_port,
|
|
17
|
+
pact_broker_host: @broker_url,
|
|
18
|
+
pact_broker_user: @broker_username,
|
|
19
|
+
pact_broker_password: @broker_password,
|
|
20
|
+
pact_broker_token: @broker_token
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
@http_config = opts[:http] ? Http.new(provider_name: provider_name, opts: opts[:http].merge(provider_setup_server: provider_setup_server, pact_proxy_server: @pact_proxy_server)) : nil
|
|
24
|
+
@grpc_config = opts[:grpc] ? Grpc.new(provider_name: provider_name, opts: opts[:grpc].merge(provider_setup_server: provider_setup_server, pact_proxy_server: @pact_proxy_server)) : nil
|
|
25
|
+
@async_config = opts[:async] ? Async.new(provider_name: provider_name, opts: opts[:async].merge(provider_setup_server: provider_setup_server, pact_proxy_server: @pact_proxy_server)) : nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def configs
|
|
29
|
+
[@async_config, @grpc_config, @http_config].compact
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def start_servers
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|