sbmt-pact 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
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