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