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,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pact/ffi/mock_server"
4
+
5
+ module Sbmt
6
+ module Pact
7
+ module Consumer
8
+ class MockServer
9
+ attr_reader :host, :port, :transport, :handle
10
+
11
+ TRANSPORT_HTTP = "http"
12
+ TRANSPORT_GRPC = "grpc"
13
+
14
+ TRANSPORTS = [TRANSPORT_HTTP, TRANSPORT_GRPC].freeze
15
+
16
+ class MockServerCreateError < Sbmt::Pact::FfiError; end
17
+
18
+ class WritePactsError < Sbmt::Pact::FfiError; end
19
+
20
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/mock_server/fn.pactffi_create_mock_server_for_transport.html
21
+ CREATE_TRANSPORT_ERRORS = {
22
+ -1 => {reason: :invalid_handle, status: -1, description: "An invalid handle was received. Handles should be created with pactffi_new_pact"},
23
+ -2 => {reason: :invalid_transport_json, status: -2, description: "Transport_config is not valid JSON"},
24
+ -3 => {reason: :mock_server_not_started, status: -3, description: "The mock server could not be started"},
25
+ -4 => {reason: :internal_error, status: -4, description: "The method panicked"},
26
+ -5 => {reason: :invalid_host, status: -5, description: "The address is not valid"}
27
+ }.freeze
28
+
29
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/mock_server/fn.pactffi_write_pact_file.html
30
+ WRITE_PACT_FILE_ERRORS = {
31
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
32
+ 2 => {reason: :file_not_accessible, status: 2, description: "The pact file was not able to be written"},
33
+ 3 => {reason: :mock_server_not_found, status: 3, description: "A mock server with the provided port was not found"}
34
+ }.freeze
35
+
36
+ def self.create_for_grpc!(pact:, host: "127.0.0.1", port: 0)
37
+ new(pact: pact, transport: TRANSPORT_GRPC, host: host, port: port)
38
+ end
39
+
40
+ def self.create_for_http!(pact:, host: "127.0.0.1", port: 0)
41
+ new(pact: pact, transport: TRANSPORT_HTTP, host: host, port: port)
42
+ end
43
+
44
+ def initialize(pact:, transport:, host:, port:)
45
+ raise "Transport #{transport} is not supported yet, available transports are: #{TRANSPORTS.join(",")}" unless TRANSPORTS.include?(transport)
46
+
47
+ @pact = pact
48
+ @transport = transport
49
+ @host = host
50
+ @port = port
51
+
52
+ @handle = init_transport!
53
+
54
+ # TODO: handle auto-GC of native memory
55
+ # ObjectSpace.define_finalizer(self, proc do
56
+ # cleanup
57
+ # end)
58
+ end
59
+
60
+ def write_pacts!(dir)
61
+ result = PactFfi::MockServer.write_pact_file(@handle, dir, false)
62
+ return result if WRITE_PACT_FILE_ERRORS[result].blank?
63
+
64
+ error = WRITE_PACT_FILE_ERRORS[result]
65
+ raise WritePactsError.new("There was an error while trying to write pact file to #{dir}", error[:reason], error[:status])
66
+ end
67
+
68
+ def matched?
69
+ PactFfi::MockServer.matched(@handle)
70
+ end
71
+
72
+ def mismatches
73
+ PactFfi::MockServer.mismatches(@handle)
74
+ end
75
+
76
+ def cleanup
77
+ PactFfi::MockServer.cleanup(@handle)
78
+ end
79
+
80
+ private
81
+
82
+ def init_transport!
83
+ handle = PactFfi::MockServer.create_for_transport(@pact, @host, @port, @transport, nil)
84
+ return handle if CREATE_TRANSPORT_ERRORS[handle].blank?
85
+
86
+ error = CREATE_TRANSPORT_ERRORS[handle]
87
+ raise MockServerCreateError.new("There was an error while trying to create mock server for transport:#{@transport}", error[:reason], error[:status])
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Consumer
6
+ module PactConfig
7
+ class Base
8
+ attr_reader :consumer_name, :provider_name, :pact_dir, :log_level
9
+
10
+ def initialize(consumer_name:, provider_name:, opts: {})
11
+ @consumer_name = consumer_name
12
+ @provider_name = provider_name
13
+ @pact_dir = opts[:pact_dir] || Rails.root.join("pacts").to_s
14
+ @log_level = opts[:log_level] || :info
15
+ end
16
+
17
+ def new_interaction(description = nil)
18
+ raise Sbmt::Pact::ImplementationRequired, "#new_interaction should be implemented"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Pact
7
+ module Consumer
8
+ module PactConfig
9
+ class Grpc < Base
10
+ attr_reader :mock_host, :mock_port
11
+
12
+ def initialize(consumer_name:, provider_name:, opts: {})
13
+ super
14
+
15
+ @mock_host = opts[:mock_host] || "127.0.0.1"
16
+ @mock_port = opts[:mock_port] || 3009
17
+ end
18
+
19
+ def new_interaction(description = nil)
20
+ GrpcInteractionBuilder.new(self, description: description)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Pact
7
+ module Consumer
8
+ module PactConfig
9
+ class Http < Base
10
+ attr_reader :mock_host, :mock_port
11
+
12
+ def initialize(consumer_name:, provider_name:, opts: {})
13
+ super
14
+
15
+ @mock_host = opts[:mock_host] || "127.0.0.1"
16
+ @mock_port = opts[:mock_port] || 3000
17
+ end
18
+
19
+ def new_interaction(description = nil)
20
+ HttpInteractionBuilder.new(self, description: description)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Sbmt
6
+ module Pact
7
+ module Consumer
8
+ module PactConfig
9
+ class Message < Base
10
+ def new_interaction(description = nil)
11
+ MessageInteractionBuilder.new(self, description: description)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ 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 Consumer
8
+ module PactConfig
9
+ def self.new(transport_type, consumer_name:, provider_name:, opts: {})
10
+ case transport_type
11
+ when :http
12
+ Http.new(consumer_name: consumer_name, provider_name: provider_name, opts: opts)
13
+ when :grpc
14
+ Grpc.new(consumer_name: consumer_name, provider_name: provider_name, opts: opts)
15
+ when :message
16
+ Message.new(consumer_name: consumer_name, 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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Consumer
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ # see https://github.com/pact-foundation/pact-reference/blob/master/rust/pact_ffi/IntegrationJson.md
7
+ class Base
8
+ attr_reader :spec_version, :kind, :template, :opts
9
+
10
+ class MatcherInitializationError < Sbmt::Pact::Error; end
11
+
12
+ def initialize(spec_version:, kind:, template:, opts: {})
13
+ @spec_version = spec_version
14
+ @kind = kind
15
+ @template = template
16
+ @opts = opts
17
+ end
18
+
19
+ def as_basic
20
+ {
21
+ "pact:matcher:type" => serialize!(@kind.deep_dup, :basic),
22
+ "value" => serialize!(@template.deep_dup, :basic)
23
+ }.merge(serialize!(@opts.deep_dup, :basic))
24
+ end
25
+
26
+ def as_plugin
27
+ params = @opts.values.map { |v| format_primitive(v) }.join(",")
28
+ value = format_primitive(@template)
29
+
30
+ return "matching(#{@kind}, #{params}, #{value})" if params.present?
31
+
32
+ "matching(#{@kind}, #{value})"
33
+ end
34
+
35
+ private
36
+
37
+ def serialize!(data, format)
38
+ # serialize complex types recursively
39
+ case data
40
+ when TrueClass, FalseClass, Numeric, String
41
+ data
42
+ when Array
43
+ data.map { |v| serialize!(v, format) }
44
+ when Hash
45
+ data.transform_values { |v| serialize!(v, format) }
46
+ when Sbmt::Pact::Matchers::Base
47
+ return data.as_basic if format == :basic
48
+ data.as_plugin if format == :plugin
49
+ else
50
+ data
51
+ end
52
+ end
53
+
54
+ def format_primitive(arg)
55
+ case arg
56
+ when TrueClass, FalseClass, Numeric
57
+ arg.to_s
58
+ when String
59
+ "'#{arg}'"
60
+ else
61
+ raise "#{arg.class} is not a primitive"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V1
7
+ class Equality < Sbmt::Pact::Matchers::Base
8
+ def initialize(template)
9
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V1, kind: "equality", template: template)
10
+ end
11
+
12
+ def as_plugin
13
+ "matching(equalTo, #{format_primitive(@template)})"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V2
7
+ class Regex < Sbmt::Pact::Matchers::Base
8
+ def initialize(regex, template)
9
+ raise MatcherInitializationError, "#{self.class}: #{regex} should be an instance of Regexp" unless regex.is_a?(Regexp)
10
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of String or Array" unless template.is_a?(String) || template.is_a?(Array)
11
+ raise MatcherInitializationError, "#{self.class}: #{template} array values should be strings" if template.is_a?(Array) && !template.all?(String)
12
+
13
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V2, kind: "regex", template: template, opts: {regex: regex.to_s})
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V2
7
+ class Type < Sbmt::Pact::Matchers::Base
8
+ def initialize(template)
9
+ raise MatcherInitializationError, "#{self.class}: template is not a primitive" unless template.is_a?(TrueClass) || template.is_a?(FalseClass) || template.is_a?(Numeric) || template.is_a?(String)
10
+
11
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V2, kind: "type", template: template)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class Boolean < Sbmt::Pact::Matchers::Base
8
+ def initialize(template)
9
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of Boolean" unless template.is_a?(TrueClass) || template.is_a?(FalseClass)
10
+
11
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3, kind: "boolean", template: template)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class Date < Sbmt::Pact::Matchers::Base
8
+ def initialize(format, template)
9
+ raise MatcherInitializationError, "#{self.class}: #{format} should be an instance of String" unless template.is_a?(String)
10
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of String" unless template.is_a?(String)
11
+
12
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3, kind: "date", template: template, opts: {format: format})
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class DateTime < Sbmt::Pact::Matchers::Base
8
+ def initialize(format, template)
9
+ raise MatcherInitializationError, "#{self.class}: #{format} should be an instance of String" unless template.is_a?(String)
10
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of String" unless template.is_a?(String)
11
+
12
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3, kind: "datetime", template: template, opts: {format: format})
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class Decimal < Sbmt::Pact::Matchers::Base
8
+ def initialize(template)
9
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of Float" unless template.is_a?(Float)
10
+
11
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3, kind: "decimal", template: template)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class Each < Sbmt::Pact::Matchers::Base
8
+ def initialize(template, min)
9
+ raise MatcherInitializationError, "#{self.class}: #{min} should be greater than 0" if min.present? && min < 1
10
+
11
+ min_array_size = min.presence || 1
12
+ val = template.is_a?(Array) ? template : [template] * min_array_size
13
+
14
+ raise MatcherInitializationError, "#{self.class}: #{min} is invalid: template size is #{val.size}" if min_array_size != val.size
15
+
16
+ super(
17
+ spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3,
18
+ kind: "type",
19
+ template: val,
20
+ opts: {min: min_array_size})
21
+ end
22
+
23
+ def as_plugin
24
+ if @template.first.is_a?(Hash)
25
+ return {
26
+ "pact:match" => "eachValue(matching($'SAMPLE'))",
27
+ "SAMPLE" => serialize!(@template.first.deep_dup, :plugin)
28
+ }
29
+ end
30
+
31
+ params = @opts.except(:min).values.map { |v| format_primitive(v) }.join(",")
32
+ value = format_primitive(@template.first)
33
+
34
+ return "eachValue(matching(#{@kind}, #{params}, #{value}))" if params.present?
35
+
36
+ "eachValue(matching(#{@kind}, #{value}))"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class Include < Sbmt::Pact::Matchers::Base
8
+ def initialize(template)
9
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of String" unless template.is_a?(String)
10
+
11
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3, kind: "include", template: template)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class Integer < Sbmt::Pact::Matchers::Base
8
+ def initialize(template)
9
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of Integer" unless template.is_a?(::Integer)
10
+
11
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3, kind: "integer", template: template)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class Number < Sbmt::Pact::Matchers::Base
8
+ def initialize(template)
9
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of Numeric" unless template.is_a?(Numeric)
10
+
11
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3, kind: "number", template: template)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V3
7
+ class Time < Sbmt::Pact::Matchers::Base
8
+ def initialize(format, template)
9
+ raise MatcherInitializationError, "#{self.class}: #{format} should be an instance of String" unless template.is_a?(String)
10
+ raise MatcherInitializationError, "#{self.class}: #{template} should be an instance of String" unless template.is_a?(String)
11
+
12
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V3, kind: "time", template: template, opts: {format: format})
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V4
7
+ class EachKey < Sbmt::Pact::Matchers::Base
8
+ def initialize(key_matchers, template)
9
+ raise MatcherInitializationError, "#{self.class}: #{template} should be a Hash" unless template.is_a?(Hash)
10
+ raise MatcherInitializationError, "#{self.class}: #{key_matchers} should be an Array" unless key_matchers.is_a?(Array)
11
+ raise MatcherInitializationError, "#{self.class}: #{key_matchers} should be instances of Sbmt::Pact::Matchers::Base" unless key_matchers.all?(Sbmt::Pact::Matchers::Base)
12
+ raise MatcherInitializationError, "#{self.class}: #{key_matchers} size should be greater than 0" unless key_matchers.size > 0
13
+
14
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V4, kind: "each-key", template: template, opts: {rules: key_matchers})
15
+ end
16
+
17
+ def as_plugin
18
+ @opts[:rules].map do |matcher|
19
+ "eachKey(#{matcher.as_plugin})"
20
+ end.join(", ")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V4
7
+ class EachKeyValue < Sbmt::Pact::Matchers::Base
8
+ def initialize(key_matchers, template)
9
+ raise MatcherInitializationError, "#{self.class}: #{template} should be a Hash" unless template.is_a?(Hash)
10
+ raise MatcherInitializationError, "#{self.class}: #{key_matchers} should be an Array" unless key_matchers.is_a?(Array)
11
+ raise MatcherInitializationError, "#{self.class}: #{key_matchers} should be instances of Sbmt::Pact::Matchers::Base" unless key_matchers.all?(Sbmt::Pact::Matchers::Base)
12
+
13
+ super(
14
+ spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V4,
15
+ kind: [
16
+ EachKey.new(key_matchers, {}),
17
+ EachValue.new([Sbmt::Pact::Matchers::V2::Type.new("")], {})
18
+ ],
19
+ template: template
20
+ )
21
+
22
+ @key_matchers = key_matchers
23
+ end
24
+
25
+ def as_plugin
26
+ raise MatcherInitializationError, "#{self.class}: each-key-value is not supported in plugin syntax. Use each / each_key / each_value matchers instead"
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V4
7
+ class EachValue < Sbmt::Pact::Matchers::Base
8
+ def initialize(value_matchers, template)
9
+ # raise MatcherInitializationError, "#{self.class}: #{template} should be a Hash" unless template.is_a?(Hash)
10
+ raise MatcherInitializationError, "#{self.class}: #{value_matchers} should be an Array" unless value_matchers.is_a?(Array)
11
+ raise MatcherInitializationError, "#{self.class}: #{value_matchers} should be instances of Sbmt::Pact::Matchers::Base" unless value_matchers.all?(Sbmt::Pact::Matchers::Base)
12
+ raise MatcherInitializationError, "#{self.class}: #{value_matchers} size should be greater than 0" unless value_matchers.size > 0
13
+
14
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V4, kind: "each-value", template: template, opts: {rules: value_matchers})
15
+ end
16
+
17
+ def as_plugin
18
+ if @template.is_a?(Hash)
19
+ return {
20
+ "pact:match" => "eachValue(matching($'SAMPLE'))",
21
+ "SAMPLE" => serialize!(@template.deep_dup, :plugin)
22
+ }
23
+ end
24
+
25
+ @opts[:rules].map do |matcher|
26
+ "eachValue(#{matcher.as_plugin})"
27
+ end.join(", ")
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbmt
4
+ module Pact
5
+ module Matchers
6
+ module V4
7
+ class NotEmpty < Sbmt::Pact::Matchers::Base
8
+ def initialize(template)
9
+ raise MatcherInitializationError, "#{self.class}: #{template} should not be empty" if template.blank?
10
+
11
+ super(spec_version: Sbmt::Pact::Matchers::PACT_SPEC_V4, kind: "time", template: template)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end