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