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