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,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "pact_message_helpers"
|
4
|
+
require_relative "webmock/webmock_helpers"
|
5
|
+
|
6
|
+
module SbmtPactProducerDsl
|
7
|
+
module ClassMethods
|
8
|
+
PACT_PROVIDER_NOT_DECLARED_MESSAGE = "http_pact_provider or grpc_pact_provider should be declared first"
|
9
|
+
|
10
|
+
def http_pact_provider(provider, opts: {})
|
11
|
+
_pact_provider(:http, provider, opts: opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def grpc_pact_provider(provider, opts: {})
|
15
|
+
_pact_provider(:grpc, provider, opts: opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
def message_pact_provider(provider, opts: {})
|
19
|
+
_pact_provider(:async, provider, opts: opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
def _pact_provider(transport_type, provider, opts: {})
|
23
|
+
raise "#{transport_type}_pact_provider is designed to be used with RSpec" unless defined?(::RSpec)
|
24
|
+
raise "#{transport_type}_pact_provider has to be declared at the top level of a suite" unless top_level?
|
25
|
+
raise "*_pact_provider is designed to be run once per provider so cannot be declared more than once" if defined?(@_pact_config)
|
26
|
+
|
27
|
+
pact_config_instance = Sbmt::Pact::Provider::PactConfig.new(transport_type, provider_name: provider, opts: opts)
|
28
|
+
instance_variable_set(:@_pact_config, pact_config_instance)
|
29
|
+
|
30
|
+
# rubocop:disable RSpec/BeforeAfterAll
|
31
|
+
before(:context) do
|
32
|
+
# rspec allows only context ivars in specs and ignores the rest
|
33
|
+
# so we use block-as-a-closure feature to save pact_config ivar reference and make it available for descendants
|
34
|
+
@_pact_config = pact_config_instance
|
35
|
+
end
|
36
|
+
# rubocop:enable RSpec/BeforeAfterAll
|
37
|
+
|
38
|
+
it "verifies interactions with provider #{provider}" do
|
39
|
+
pact_config.new_verifier.verify!
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def before_state_setup(&block)
|
44
|
+
raise PACT_PROVIDER_NOT_DECLARED_MESSAGE unless pact_config
|
45
|
+
pact_config.before_setup(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def after_state_teardown(&block)
|
49
|
+
raise PACT_PROVIDER_NOT_DECLARED_MESSAGE unless pact_config
|
50
|
+
pact_config.after_teardown(&block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def provider_state(name, opts: {}, &block)
|
54
|
+
raise PACT_PROVIDER_NOT_DECLARED_MESSAGE unless pact_config
|
55
|
+
pact_config.new_provider_state(name, opts: opts, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def handle_message(name, opts: {}, &block)
|
59
|
+
raise "message_pact_provider should be declared first" unless pact_config
|
60
|
+
raise "message_pact_provider should be declared first" unless pact_config.is_a?(Sbmt::Pact::Provider::PactConfig::Async)
|
61
|
+
pact_config.new_message_handler(name, opts: opts, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def pact_config
|
65
|
+
instance_variable_get(:@_pact_config)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def pact_config
|
70
|
+
instance_variable_get(:@_pact_config)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
RSpec.configure do |config|
|
75
|
+
config.include SbmtPactProducerDsl, pact_entity: :provider
|
76
|
+
config.extend SbmtPactProducerDsl::ClassMethods, pact_entity: :provider
|
77
|
+
|
78
|
+
config.around pact_entity: :provider do |example|
|
79
|
+
WebmockHelpers.turned_off do
|
80
|
+
if defined?(::VCR)
|
81
|
+
VCR.turned_off { example.run }
|
82
|
+
else
|
83
|
+
example.run
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PactWaterdropClient
|
4
|
+
attr_reader :message
|
5
|
+
|
6
|
+
Report = Struct.new(:partition, :offset, :topic_name, keyword_init: true)
|
7
|
+
|
8
|
+
def produce_async(message)
|
9
|
+
@message = message
|
10
|
+
end
|
11
|
+
|
12
|
+
def produce_sync(message)
|
13
|
+
@message = message
|
14
|
+
Report.new(partition: 0, offset: 0, topic_name: message[:topic])
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_pact(content_type: nil)
|
18
|
+
payload = message[:payload]
|
19
|
+
metadata = {
|
20
|
+
key: message[:key],
|
21
|
+
topic: message[:topic],
|
22
|
+
content_type: content_type
|
23
|
+
}.merge(message[:headers] || {})
|
24
|
+
|
25
|
+
[payload, metadata]
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WebmockHelpers
|
4
|
+
def self.turned_off
|
5
|
+
yield unless defined?(::WebMock)
|
6
|
+
|
7
|
+
allow_net_connect = WebMock::Config.instance.allow_net_connect
|
8
|
+
allow_localhost = WebMock::Config.instance.allow_localhost
|
9
|
+
allow_hosts = WebMock::Config.instance.allow
|
10
|
+
net_http_connect_on_start = WebMock::Config.instance.net_http_connect_on_start
|
11
|
+
|
12
|
+
return yield if allow_net_connect
|
13
|
+
|
14
|
+
WebMock.allow_net_connect!
|
15
|
+
|
16
|
+
result = yield
|
17
|
+
|
18
|
+
# disable_net_connect! resets previous config settings
|
19
|
+
# so we need to specify them explicitly
|
20
|
+
WebMock.disable_net_connect!(
|
21
|
+
{
|
22
|
+
allow_localhost: allow_localhost,
|
23
|
+
allow: allow_hosts,
|
24
|
+
net_http_connect_on_start: net_http_connect_on_start
|
25
|
+
}
|
26
|
+
)
|
27
|
+
|
28
|
+
result
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec"
|
4
|
+
require_relative "rspec/support/pact_consumer_helpers"
|
5
|
+
require_relative "rspec/support/pact_provider_helpers"
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.define_derived_metadata(file_path: %r{spec/pact/}) { |metadata| metadata[:pact] = true }
|
9
|
+
|
10
|
+
# it's not an error: consumer tests contain `providers` subdirectory (because we're testing against different providers)
|
11
|
+
config.define_derived_metadata(file_path: %r{spec/pact/providers/}) { |metadata| metadata[:pact_entity] = :consumer }
|
12
|
+
# for provider tests it's the same thing: we're running tests which test consumers
|
13
|
+
config.define_derived_metadata(file_path: %r{spec/pact/consumers/}) { |metadata| metadata[:pact_entity] = :provider }
|
14
|
+
|
15
|
+
# exclude pact specs from generic rspec pipeline
|
16
|
+
config.filter_run_excluding :pact
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rspec/core/rake_task"
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:pact).tap do |task|
|
6
|
+
task.pattern = "spec/pact/consumers/**/*_spec.rb"
|
7
|
+
task.rspec_opts = "--require rails_helper --tag pact"
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :pact do
|
11
|
+
desc "Verifies the pact files"
|
12
|
+
task verify: :pact
|
13
|
+
end
|
data/lib/sbmt/pact.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zeitwerk"
|
4
|
+
require "pact/ffi"
|
5
|
+
|
6
|
+
require "sbmt/pact/railtie" if defined?(Rails::Railtie)
|
7
|
+
|
8
|
+
module Sbmt
|
9
|
+
module Pact
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
class ImplementationRequired < Error; end
|
13
|
+
|
14
|
+
class FfiError < Error
|
15
|
+
def initialize(msg, reason, status)
|
16
|
+
super(msg)
|
17
|
+
|
18
|
+
@msg = msg
|
19
|
+
@reason = reason
|
20
|
+
@status = status
|
21
|
+
end
|
22
|
+
|
23
|
+
def message
|
24
|
+
"FFI error: reason: #{@reason}, status: #{@status}, message: #{@msg}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.configure
|
29
|
+
yield configuration if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.configuration
|
33
|
+
@configuration ||= Sbmt::Pact::Configuration.new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
loader = Zeitwerk::Loader.new
|
39
|
+
loader.push_dir(File.join(__dir__, ".."))
|
40
|
+
|
41
|
+
loader.tag = "sbmt-pact"
|
42
|
+
|
43
|
+
loader.ignore("#{__dir__}/pact/version.rb")
|
44
|
+
loader.ignore("#{__dir__}/pact/rspec.rb")
|
45
|
+
loader.ignore("#{__dir__}/pact/rspec")
|
46
|
+
|
47
|
+
loader.setup
|
48
|
+
loader.eager_load
|
data/sbmt-pact.gemspec
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/sbmt/pact/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "sbmt-pact"
|
7
|
+
spec.license = "MIT"
|
8
|
+
spec.version = Sbmt::Pact::VERSION
|
9
|
+
spec.authors = ["Kuper Ruby Platform Team"]
|
10
|
+
|
11
|
+
spec.summary = "Ruby gem for simplified Pact testing between microservices, supporting the latest Pact specifications and multiple transport protocols"
|
12
|
+
spec.description = "It is a powerful Ruby gem designed to streamline Pact testing in microservice architectures. It supports the latest Pact specifications and offers capabilities beyond the current pact-ruby gem, including support for non-HTTP transports like gRPC and async messaging systems like Kafka."
|
13
|
+
spec.homepage = "https://github.com/Kuper-Tech/sbmt-pact"
|
14
|
+
spec.required_ruby_version = ">= 2.7.0"
|
15
|
+
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "false" # rubocop:disable Gemspec/RequireMFA
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
spec.add_dependency "zeitwerk", "~> 2.3"
|
35
|
+
spec.add_dependency "pact-ffi", "~> 0.4.22"
|
36
|
+
spec.add_dependency "rack", "~> 2.0"
|
37
|
+
spec.add_dependency "webrick"
|
38
|
+
spec.add_dependency "rack-proxy"
|
39
|
+
|
40
|
+
spec.add_development_dependency "appraisal", ">= 2.4"
|
41
|
+
spec.add_development_dependency "bundler", ">= 2.3"
|
42
|
+
spec.add_development_dependency "combustion", ">= 1.3"
|
43
|
+
spec.add_development_dependency "gruf", ">= 2.18"
|
44
|
+
spec.add_development_dependency "rake", ">= 13.0"
|
45
|
+
spec.add_development_dependency "sbmt-kafka_consumer", ">= 2.0.1"
|
46
|
+
spec.add_development_dependency "sbmt-kafka_producer", ">= 1.0"
|
47
|
+
spec.add_development_dependency "rspec"
|
48
|
+
spec.add_development_dependency "rspec-rails"
|
49
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
50
|
+
spec.add_development_dependency "rubocop"
|
51
|
+
spec.add_development_dependency "rubocop-rails"
|
52
|
+
spec.add_development_dependency "rubocop-rspec"
|
53
|
+
spec.add_development_dependency "rubocop-performance"
|
54
|
+
spec.add_development_dependency "standard", ">= 1.35.1"
|
55
|
+
spec.add_development_dependency "vcr", ">= 6.0"
|
56
|
+
spec.add_development_dependency "faraday", "> 1.0"
|
57
|
+
spec.add_development_dependency "webmock", ">= 3.0"
|
58
|
+
spec.add_development_dependency "gruf-rspec", ">= 0.6.0"
|
59
|
+
end
|