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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +62 -0
  4. data/Appraisals +23 -0
  5. data/CHANGELOG.md +96 -0
  6. data/Dockerfile +14 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +21 -0
  9. data/README.md +212 -0
  10. data/Rakefile +12 -0
  11. data/dip.yml +86 -0
  12. data/docker-compose.yml +40 -0
  13. data/docs/sbmt-pact-arch.png +0 -0
  14. data/lefthook-local.dip_example.yml +4 -0
  15. data/lefthook.yml +6 -0
  16. data/lib/sbmt/pact/configuration.rb +23 -0
  17. data/lib/sbmt/pact/consumer/grpc_interaction_builder.rb +193 -0
  18. data/lib/sbmt/pact/consumer/http_interaction_builder.rb +149 -0
  19. data/lib/sbmt/pact/consumer/interaction_contents.rb +47 -0
  20. data/lib/sbmt/pact/consumer/message_interaction_builder.rb +285 -0
  21. data/lib/sbmt/pact/consumer/mock_server.rb +92 -0
  22. data/lib/sbmt/pact/consumer/pact_config/base.rb +24 -0
  23. data/lib/sbmt/pact/consumer/pact_config/grpc.rb +26 -0
  24. data/lib/sbmt/pact/consumer/pact_config/http.rb +26 -0
  25. data/lib/sbmt/pact/consumer/pact_config/message.rb +17 -0
  26. data/lib/sbmt/pact/consumer/pact_config.rb +24 -0
  27. data/lib/sbmt/pact/consumer.rb +8 -0
  28. data/lib/sbmt/pact/matchers/base.rb +67 -0
  29. data/lib/sbmt/pact/matchers/v1/equality.rb +19 -0
  30. data/lib/sbmt/pact/matchers/v2/regex.rb +19 -0
  31. data/lib/sbmt/pact/matchers/v2/type.rb +17 -0
  32. data/lib/sbmt/pact/matchers/v3/boolean.rb +17 -0
  33. data/lib/sbmt/pact/matchers/v3/date.rb +18 -0
  34. data/lib/sbmt/pact/matchers/v3/date_time.rb +18 -0
  35. data/lib/sbmt/pact/matchers/v3/decimal.rb +17 -0
  36. data/lib/sbmt/pact/matchers/v3/each.rb +42 -0
  37. data/lib/sbmt/pact/matchers/v3/include.rb +17 -0
  38. data/lib/sbmt/pact/matchers/v3/integer.rb +17 -0
  39. data/lib/sbmt/pact/matchers/v3/number.rb +17 -0
  40. data/lib/sbmt/pact/matchers/v3/time.rb +18 -0
  41. data/lib/sbmt/pact/matchers/v4/each_key.rb +26 -0
  42. data/lib/sbmt/pact/matchers/v4/each_key_value.rb +32 -0
  43. data/lib/sbmt/pact/matchers/v4/each_value.rb +33 -0
  44. data/lib/sbmt/pact/matchers/v4/not_empty.rb +17 -0
  45. data/lib/sbmt/pact/matchers.rb +94 -0
  46. data/lib/sbmt/pact/native/blocking_verifier.rb +17 -0
  47. data/lib/sbmt/pact/native/logger.rb +25 -0
  48. data/lib/sbmt/pact/provider/async_message_verifier.rb +32 -0
  49. data/lib/sbmt/pact/provider/base_verifier.rb +158 -0
  50. data/lib/sbmt/pact/provider/grpc_verifier.rb +42 -0
  51. data/lib/sbmt/pact/provider/gruf_server.rb +75 -0
  52. data/lib/sbmt/pact/provider/http_server.rb +66 -0
  53. data/lib/sbmt/pact/provider/http_verifier.rb +46 -0
  54. data/lib/sbmt/pact/provider/message_provider_servlet.rb +80 -0
  55. data/lib/sbmt/pact/provider/pact_broker_proxy.rb +85 -0
  56. data/lib/sbmt/pact/provider/pact_broker_proxy_runner.rb +71 -0
  57. data/lib/sbmt/pact/provider/pact_config/async.rb +25 -0
  58. data/lib/sbmt/pact/provider/pact_config/base.rb +92 -0
  59. data/lib/sbmt/pact/provider/pact_config/grpc.rb +30 -0
  60. data/lib/sbmt/pact/provider/pact_config/http.rb +25 -0
  61. data/lib/sbmt/pact/provider/pact_config.rb +24 -0
  62. data/lib/sbmt/pact/provider/provider_server_runner.rb +89 -0
  63. data/lib/sbmt/pact/provider/provider_state_configuration.rb +32 -0
  64. data/lib/sbmt/pact/provider/provider_state_servlet.rb +84 -0
  65. data/lib/sbmt/pact/provider.rb +8 -0
  66. data/lib/sbmt/pact/railtie.rb +13 -0
  67. data/lib/sbmt/pact/rspec/support/pact_consumer_helpers.rb +46 -0
  68. data/lib/sbmt/pact/rspec/support/pact_message_helpers.rb +42 -0
  69. data/lib/sbmt/pact/rspec/support/pact_provider_helpers.rb +87 -0
  70. data/lib/sbmt/pact/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
  71. data/lib/sbmt/pact/rspec/support/webmock/webmock_helpers.rb +30 -0
  72. data/lib/sbmt/pact/rspec.rb +17 -0
  73. data/lib/sbmt/pact/tasks/pact.rake +13 -0
  74. data/lib/sbmt/pact/version.rb +7 -0
  75. data/lib/sbmt/pact.rb +48 -0
  76. data/sbmt-pact.gemspec +59 -0
  77. 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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ VERSION = "0.12.0"
6
+ end
7
+ 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