pact 1.66.2 → 1.67.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/pact/v2/configuration.rb +23 -0
- data/lib/pact/v2/consumer/grpc_interaction_builder.rb +194 -0
- data/lib/pact/v2/consumer/http_interaction_builder.rb +162 -0
- data/lib/pact/v2/consumer/interaction_contents.rb +62 -0
- data/lib/pact/v2/consumer/message_interaction_builder.rb +287 -0
- data/lib/pact/v2/consumer/mock_server.rb +100 -0
- data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
- data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
- data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
- data/lib/pact/v2/consumer/pact_config/plugin_async_message.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/plugin_http.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/plugin_sync_message.rb +26 -0
- data/lib/pact/v2/consumer/pact_config.rb +30 -0
- data/lib/pact/v2/consumer/plugin_async_message_interaction_builder.rb +171 -0
- data/lib/pact/v2/consumer/plugin_http_interaction_builder.rb +201 -0
- data/lib/pact/v2/consumer/plugin_sync_message_interaction_builder.rb +180 -0
- data/lib/pact/v2/consumer.rb +8 -0
- data/lib/pact/v2/generators/base.rb +287 -0
- data/lib/pact/v2/generators.rb +49 -0
- data/lib/pact/v2/matchers/base.rb +74 -0
- data/lib/pact/v2/matchers/v1/equality.rb +19 -0
- data/lib/pact/v2/matchers/v2/regex.rb +19 -0
- data/lib/pact/v2/matchers/v2/type.rb +17 -0
- data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
- data/lib/pact/v2/matchers/v3/content_type.rb +32 -0
- data/lib/pact/v2/matchers/v3/date.rb +18 -0
- data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
- data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
- data/lib/pact/v2/matchers/v3/each.rb +42 -0
- data/lib/pact/v2/matchers/v3/include.rb +17 -0
- data/lib/pact/v2/matchers/v3/integer.rb +17 -0
- data/lib/pact/v2/matchers/v3/null.rb +16 -0
- data/lib/pact/v2/matchers/v3/number.rb +17 -0
- data/lib/pact/v2/matchers/v3/semver.rb +23 -0
- data/lib/pact/v2/matchers/v3/time.rb +18 -0
- data/lib/pact/v2/matchers/v3/values.rb +16 -0
- data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
- data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
- data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
- data/lib/pact/v2/matchers/v4/not_empty.rb +24 -0
- data/lib/pact/v2/matchers/v4/status_code.rb +17 -0
- data/lib/pact/v2/matchers.rb +110 -0
- data/lib/pact/v2/native/blocking_verifier.rb +17 -0
- data/lib/pact/v2/native/logger.rb +25 -0
- data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
- data/lib/pact/v2/provider/base_verifier.rb +242 -0
- data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
- data/lib/pact/v2/provider/gruf_server.rb +75 -0
- data/lib/pact/v2/provider/http_server.rb +79 -0
- data/lib/pact/v2/provider/http_verifier.rb +43 -0
- data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
- data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
- data/lib/pact/v2/provider/pact_broker_proxy.rb +66 -0
- data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
- data/lib/pact/v2/provider/pact_config/async.rb +29 -0
- data/lib/pact/v2/provider/pact_config/base.rb +101 -0
- data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/provider/pact_config/http.rb +27 -0
- data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
- data/lib/pact/v2/provider/pact_config.rb +26 -0
- data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
- data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
- data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
- data/lib/pact/v2/provider.rb +8 -0
- data/lib/pact/v2/railtie.rb +13 -0
- data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +93 -0
- data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
- data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
- data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
- data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
- data/lib/pact/v2/rspec.rb +17 -0
- data/lib/pact/v2/tasks/pact.rake +13 -0
- data/lib/pact/v2.rb +71 -0
- data/lib/pact/version.rb +1 -1
- data/lib/pact.rb +1 -0
- data/pact.gemspec +52 -7
- metadata +419 -65
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
module V3
|
|
7
|
+
class Semver < Pact::V2::Matchers::Base
|
|
8
|
+
def initialize(template = nil)
|
|
9
|
+
@template = template
|
|
10
|
+
super(spec_version: Pact::V2::Matchers::PACT_SPEC_V3, kind: "semver", template: template)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def as_plugin
|
|
14
|
+
if @template.nil? || @template.blank?
|
|
15
|
+
raise MatcherInitializationError, "#{self.class}: template must be provided when calling as_plugin"
|
|
16
|
+
end
|
|
17
|
+
"matching(semver, '#{@template}')"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
module V3
|
|
7
|
+
class Time < Pact::V2::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: Pact::V2::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,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
module V3
|
|
7
|
+
class Values < Pact::V2::Matchers::Base
|
|
8
|
+
def initialize
|
|
9
|
+
super(spec_version: Pact::V2::Matchers::PACT_SPEC_V3, kind: "values", template: nil)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
module V4
|
|
7
|
+
class EachKey < Pact::V2::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 Pact::V2::Matchers::Base" unless key_matchers.all?(Pact::V2::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: Pact::V2::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 Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
module V4
|
|
7
|
+
class EachKeyValue < Pact::V2::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 Pact::V2::Matchers::Base" unless key_matchers.all?(Pact::V2::Matchers::Base)
|
|
12
|
+
|
|
13
|
+
super(
|
|
14
|
+
spec_version: Pact::V2::Matchers::PACT_SPEC_V4,
|
|
15
|
+
kind: [
|
|
16
|
+
EachKey.new(key_matchers, {}),
|
|
17
|
+
EachValue.new([Pact::V2::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 Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
module V4
|
|
7
|
+
class EachValue < Pact::V2::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 Pact::V2::Matchers::Base" unless value_matchers.all?(Pact::V2::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: Pact::V2::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,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
module V4
|
|
7
|
+
class NotEmpty < Pact::V2::Matchers::Base
|
|
8
|
+
def initialize(template = nil)
|
|
9
|
+
@template = template
|
|
10
|
+
super(spec_version: Pact::V2::Matchers::PACT_SPEC_V4, kind: 'notEmpty', template: @template)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def as_plugin
|
|
14
|
+
if @template.nil? || @template.blank?
|
|
15
|
+
raise MatcherInitializationError, "#{self.class}: template must be provided when calling as_plugin"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
"notEmpty('#{@template}')"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
module V4
|
|
7
|
+
class StatusCode < Pact::V2::Matchers::Base
|
|
8
|
+
def initialize(template = nil)
|
|
9
|
+
super(spec_version: Pact::V2::Matchers::PACT_SPEC_V4, kind: 'statusCode', opts: {
|
|
10
|
+
'status' => template
|
|
11
|
+
})
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module V2
|
|
5
|
+
module Matchers
|
|
6
|
+
PACT_SPEC_V1 = 1
|
|
7
|
+
PACT_SPEC_V2 = 2
|
|
8
|
+
PACT_SPEC_V3 = 3
|
|
9
|
+
PACT_SPEC_V4 = 4
|
|
10
|
+
|
|
11
|
+
ANY_STRING_REGEX = /.*/
|
|
12
|
+
UUID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i
|
|
13
|
+
|
|
14
|
+
# simplified
|
|
15
|
+
ISO8601_REGEX = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)*(.\d{2}:\d{2})*/i
|
|
16
|
+
|
|
17
|
+
def match_exactly(arg)
|
|
18
|
+
V1::Equality.new(arg)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def match_type_of(arg)
|
|
22
|
+
V2::Type.new(arg)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def match_include(arg)
|
|
26
|
+
V3::Include.new(arg)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def match_any_string(sample = "any")
|
|
30
|
+
V2::Regex.new(ANY_STRING_REGEX, sample)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def match_any_integer(sample = 10)
|
|
34
|
+
V3::Integer.new(sample)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def match_any_decimal(sample = 10.0)
|
|
38
|
+
V3::Decimal.new(sample)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def match_any_number(sample = 10.0)
|
|
42
|
+
V3::Number.new(sample)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def match_any_boolean(sample = true)
|
|
46
|
+
V3::Boolean.new(sample)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def match_uuid(sample = "e1d01e04-3a2b-4eed-a4fb-54f5cd257338")
|
|
50
|
+
V2::Regex.new(UUID_REGEX, sample)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def match_regex(regex, sample)
|
|
54
|
+
V2::Regex.new(regex, sample)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def match_datetime(format, sample)
|
|
58
|
+
V3::DateTime.new(format, sample)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def match_iso8601(sample = "2024-08-12T12:25:00.243118+03:00")
|
|
62
|
+
V2::Regex.new(ISO8601_REGEX, sample)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def match_date(format, sample)
|
|
66
|
+
V3::Date.new(format, sample)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def match_time(format, sample)
|
|
70
|
+
V3::Time.new(format, sample)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def match_each(template, min = nil)
|
|
74
|
+
V3::Each.new(template, min)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def match_each_regex(regex, sample)
|
|
78
|
+
match_each_value(sample, match_regex(regex, sample))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def match_each_key(template, key_matchers)
|
|
82
|
+
V4::EachKey.new(key_matchers.is_a?(Array) ? key_matchers : [key_matchers], template)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def match_each_value(template, value_matchers = V2::Type.new(""))
|
|
86
|
+
V4::EachValue.new(value_matchers.is_a?(Array) ? value_matchers : [value_matchers], template)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def match_each_kv(template, key_matchers)
|
|
90
|
+
V4::EachKeyValue.new(key_matchers.is_a?(Array) ? key_matchers : [key_matchers], template)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def match_semver(template = nil)
|
|
94
|
+
V3::Semver.new(template)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def match_content_type(content_type, template = nil)
|
|
98
|
+
V3::ContentType.new(content_type, template: template)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def match_not_empty(template = nil)
|
|
102
|
+
V4::NotEmpty.new(template)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def match_status_code(template)
|
|
106
|
+
V4::StatusCode.new(template)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ffi"
|
|
4
|
+
require "pact/ffi/verifier"
|
|
5
|
+
|
|
6
|
+
module Pact
|
|
7
|
+
module V2
|
|
8
|
+
module Native
|
|
9
|
+
module BlockingVerifier
|
|
10
|
+
extend FFI::Library
|
|
11
|
+
ffi_lib DetectOS.get_bin_path
|
|
12
|
+
|
|
13
|
+
attach_function :execute, :pactffi_verifier_execute, %i[pointer], :int32, blocking: true
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pact/ffi/logger"
|
|
4
|
+
|
|
5
|
+
module Pact
|
|
6
|
+
module V2
|
|
7
|
+
module Native
|
|
8
|
+
module Logger
|
|
9
|
+
LOG_LEVELS = {
|
|
10
|
+
off: PactFfi::FfiLogLevelFilter["LOG_LEVEL_OFF"],
|
|
11
|
+
error: PactFfi::FfiLogLevelFilter["LOG_LEVEL_ERROR"],
|
|
12
|
+
warn: PactFfi::FfiLogLevelFilter["LOG_LEVEL_WARN"],
|
|
13
|
+
info: PactFfi::FfiLogLevelFilter["LOG_LEVEL_INFO"],
|
|
14
|
+
debug: PactFfi::FfiLogLevelFilter["LOG_LEVEL_DEBUG"],
|
|
15
|
+
trace: PactFfi::FfiLogLevelFilter["LOG_LEVEL_TRACE"]
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def self.log_to_stdout(log_level)
|
|
19
|
+
raise "invalid log level for PactFfi::FfiLogLevelFilter" unless LOG_LEVELS.key?(log_level)
|
|
20
|
+
PactFfi::Logger.log_to_stdout(LOG_LEVELS[log_level])
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pact/ffi/verifier"
|
|
4
|
+
require "pact/v2/native/logger"
|
|
5
|
+
|
|
6
|
+
module Pact
|
|
7
|
+
module V2
|
|
8
|
+
module Provider
|
|
9
|
+
class AsyncMessageVerifier < BaseVerifier
|
|
10
|
+
PROVIDER_TRANSPORT_TYPE = "message"
|
|
11
|
+
|
|
12
|
+
def initialize(pact_config, mixed_config = nil)
|
|
13
|
+
super
|
|
14
|
+
|
|
15
|
+
raise ArgumentError, "pact_config must be an instance of Pact::V2::Provider::PactConfig::Message" unless pact_config.is_a?(::Pact::V2::Provider::PactConfig::Async)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def add_provider_transport(pact_handle)
|
|
21
|
+
setup_uri = URI(@pact_config.message_setup_url)
|
|
22
|
+
PactFfi::Verifier.add_provider_transport(pact_handle, PROVIDER_TRANSPORT_TYPE, setup_uri.port, setup_uri.path, "")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pact/ffi/verifier"
|
|
4
|
+
require "pact/v2/native/logger"
|
|
5
|
+
require "pact/v2/native/blocking_verifier"
|
|
6
|
+
|
|
7
|
+
module Pact
|
|
8
|
+
module V2
|
|
9
|
+
module Provider
|
|
10
|
+
class BaseVerifier
|
|
11
|
+
PROVIDER_TRANSPORT_TYPE = nil
|
|
12
|
+
attr_reader :logger
|
|
13
|
+
|
|
14
|
+
class VerificationError < Pact::V2::FfiError; end
|
|
15
|
+
|
|
16
|
+
class VerifierError < Pact::V2::Error; end
|
|
17
|
+
|
|
18
|
+
DEFAULT_CONSUMER_SELECTORS = {}
|
|
19
|
+
|
|
20
|
+
# https://docs.rs/pact_ffi/0.4.17/pact_ffi/verifier/fn.pactffi_verify.html#errors
|
|
21
|
+
VERIFICATION_ERRORS = {
|
|
22
|
+
1 => {reason: :verification_failed, status: 1, description: "The verification process failed, see output for errors"},
|
|
23
|
+
2 => {reason: :null_pointer, status: 2, description: "A null pointer was received"},
|
|
24
|
+
3 => {reason: :internal_error, status: 3, description: "The method panicked"},
|
|
25
|
+
4 => {reason: :invalid_arguments, status: 4, description: "Invalid arguments were provided to the verification process"}
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
# env below are set up by pipeline-builder
|
|
29
|
+
# see paas/cicd/images/pact/pipeline-builder/-/blob/master/internal/commands/consumers-pipeline/ruby.go
|
|
30
|
+
def initialize(pact_config, mixed_config = nil)
|
|
31
|
+
raise ArgumentError, "pact_config must be a subclass of Pact::V2::Provider::PactConfig::Base" unless pact_config.is_a?(::Pact::V2::Provider::PactConfig::Base)
|
|
32
|
+
@pact_config = pact_config
|
|
33
|
+
@mixed_config = mixed_config
|
|
34
|
+
@logger = Logger.new($stdout)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def verify!
|
|
38
|
+
raise VerifierError.new("interaction is designed to be used one-time only") if defined?(@used)
|
|
39
|
+
|
|
40
|
+
# if consumer_selectors.blank?
|
|
41
|
+
# logger.info("[verifier] does not need to verify consumer #{@pact_config.consumer_name}")
|
|
42
|
+
# return
|
|
43
|
+
# end
|
|
44
|
+
|
|
45
|
+
exception = nil
|
|
46
|
+
pact_handle = init_pact
|
|
47
|
+
|
|
48
|
+
start_servers!
|
|
49
|
+
|
|
50
|
+
logger.info("[verifier] starting provider verification")
|
|
51
|
+
|
|
52
|
+
result = Pact::V2::Native::BlockingVerifier.execute(pact_handle)
|
|
53
|
+
if VERIFICATION_ERRORS[result].present?
|
|
54
|
+
error = VERIFICATION_ERRORS[result]
|
|
55
|
+
exception = VerificationError.new("There was an error while trying to verify provider \"#{@pact_config.provider_name}\"", error[:reason], error[:status])
|
|
56
|
+
end
|
|
57
|
+
ensure
|
|
58
|
+
@used = true
|
|
59
|
+
PactFfi::Verifier.shutdown(pact_handle) if pact_handle
|
|
60
|
+
stop_servers
|
|
61
|
+
@grpc_server.stop if @grpc_server
|
|
62
|
+
raise exception if exception
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def create_c_pointer_array_from_string_array(string_array)
|
|
68
|
+
pointers = string_array.map { |str| FFI::MemoryPointer.from_string(str) }
|
|
69
|
+
array_pointer = FFI::MemoryPointer.new(:pointer, pointers.size)
|
|
70
|
+
pointers.each_with_index do |ptr, index|
|
|
71
|
+
array_pointer[index].put_pointer(0, ptr)
|
|
72
|
+
end
|
|
73
|
+
array_pointer
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def bool_to_int(value)
|
|
77
|
+
value ? 1 : 0
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def init_pact
|
|
81
|
+
handle = PactFfi::Verifier.new_for_application("pact-ruby-v2", PactFfi.version)
|
|
82
|
+
set_provider_info(handle)
|
|
83
|
+
|
|
84
|
+
if defined?(@mixed_config.grpc_config) && @mixed_config.grpc_config
|
|
85
|
+
@grpc_server = GrufServer.new(host: "127.0.0.1:#{@mixed_config.grpc_config.grpc_port}", services: @mixed_config.grpc_config.grpc_services)
|
|
86
|
+
@grpc_server.start
|
|
87
|
+
PactFfi::Verifier.add_provider_transport(handle, "grpc", @mixed_config.grpc_config.grpc_port, "", "")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if defined?(@mixed_config.async_config) && @mixed_config.async_config
|
|
91
|
+
setup_uri = URI(@mixed_config.async_config.message_setup_url)
|
|
92
|
+
PactFfi::Verifier.add_provider_transport(handle, "message", setup_uri.port, setup_uri.path, "")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# todo: add http transport?
|
|
96
|
+
|
|
97
|
+
PactFfi::Verifier.set_provider_state(handle, @pact_config.provider_setup_url, 1, 1)
|
|
98
|
+
PactFfi::Verifier.set_verification_options(handle, 0, 10000)
|
|
99
|
+
# pactffi_verifier_set_publish_options(
|
|
100
|
+
# handle: *mut VerifierHandle,
|
|
101
|
+
# provider_version: *const c_char,
|
|
102
|
+
# build_url: *const c_char,
|
|
103
|
+
# provider_tags: *const *const c_char,
|
|
104
|
+
# provider_tags_len: c_ushort,
|
|
105
|
+
# provider_branch: *const c_char,
|
|
106
|
+
# )
|
|
107
|
+
c_provider_version_tags = create_c_pointer_array_from_string_array(@pact_config.provider_version_tags)
|
|
108
|
+
c_provider_version_tags_size = @pact_config.provider_version_tags.size
|
|
109
|
+
c_consumer_version_tags = create_c_pointer_array_from_string_array(@pact_config.consumer_version_tags)
|
|
110
|
+
c_consumer_version_tags_size = @pact_config.consumer_version_tags.size
|
|
111
|
+
|
|
112
|
+
if @pact_config.provider_build_uri.present?
|
|
113
|
+
begin
|
|
114
|
+
URI.parse(@pact_config.provider_build_uri)
|
|
115
|
+
rescue URI::InvalidURIError
|
|
116
|
+
raise VerifierError.new("provider_build_uri is not a valid URI")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
if @pact_config.publish_verification_results == true
|
|
121
|
+
if @pact_config.provider_version
|
|
122
|
+
PactFfi::Verifier.set_publish_options(handle, @pact_config.provider_version, @pact_config.provider_build_uri, c_provider_version_tags, c_provider_version_tags_size, @pact_config.provider_version_branch)
|
|
123
|
+
else
|
|
124
|
+
logger.warn("[verifier] - unable to publish verification results as provider version is not set")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
configure_verification_source(handle, c_provider_version_tags, c_provider_version_tags_size, c_consumer_version_tags, c_consumer_version_tags_size)
|
|
129
|
+
|
|
130
|
+
PactFfi::Verifier.set_no_pacts_is_error(handle, bool_to_int(@pact_config.fail_if_no_pacts_found))
|
|
131
|
+
|
|
132
|
+
add_provider_transport(handle)
|
|
133
|
+
|
|
134
|
+
# the core doesnt pick up these env vars, so we need to set them here
|
|
135
|
+
# https://github.com/pact-foundation/pact-reference/issues/451#issuecomment-2338130587
|
|
136
|
+
# PACT_DESCRIPTION
|
|
137
|
+
# Only validate interactions whose descriptions match this filter (regex format)
|
|
138
|
+
# PACT_PROVIDER_STATE
|
|
139
|
+
# Only validate interactions whose provider states match this filter (regex format)
|
|
140
|
+
# PACT_PROVIDER_NO_STATE
|
|
141
|
+
# Only validate interactions that have no defined provider state (true or false)
|
|
142
|
+
PactFfi::Verifier.set_filter_info(
|
|
143
|
+
handle,
|
|
144
|
+
ENV["PACT_DESCRIPTION"] || nil,
|
|
145
|
+
ENV["PACT_PROVIDER_STATE"] || nil,
|
|
146
|
+
bool_to_int(ENV["PACT_PROVIDER_NO_STATE"] || false)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
Pact::V2::Native::Logger.log_to_stdout(@pact_config.log_level)
|
|
150
|
+
|
|
151
|
+
logger.info("[verifier] verification initialized for provider #{@pact_config.provider_name}, version #{@pact_config.provider_version}, transport #{self.class::PROVIDER_TRANSPORT_TYPE}")
|
|
152
|
+
|
|
153
|
+
handle
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def set_provider_info(pact_handle)
|
|
157
|
+
# pub extern "C" fn pactffi_verifier_set_provider_info(
|
|
158
|
+
# handle: *mut VerifierHandle,
|
|
159
|
+
# name: *const c_char,
|
|
160
|
+
# scheme: *const c_char,
|
|
161
|
+
# host: *const c_char,
|
|
162
|
+
# port: c_ushort,
|
|
163
|
+
# path: *const c_char,
|
|
164
|
+
# ) {
|
|
165
|
+
PactFfi::Verifier.set_provider_info(pact_handle, @pact_config.provider_name, "", "", 0, "")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def add_provider_transport(pact_handle)
|
|
169
|
+
raise Pact::V2::ImplementationRequired, "Implement #add_provider_transport in a subclass"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def start_servers!
|
|
173
|
+
logger.info("[verifier] starting services")
|
|
174
|
+
|
|
175
|
+
@servers_started = true
|
|
176
|
+
@pact_config.start_servers
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def stop_servers
|
|
180
|
+
return unless @servers_started
|
|
181
|
+
|
|
182
|
+
logger.info("[verifier] stopping services")
|
|
183
|
+
|
|
184
|
+
@pact_config.stop_servers
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def configure_verification_source(handle, c_provider_version_tags, c_provider_version_tags_size, c_consumer_version_tags, c_consumer_version_tags_size)
|
|
188
|
+
logger.info("[verifier] configuring verification source")
|
|
189
|
+
if @pact_config.pact_broker_proxy_url.blank? && @pact_config.pact_uri.blank?
|
|
190
|
+
# todo support non rail apps
|
|
191
|
+
path = @pact_config.pact_dir || (defined?(Rails) ? Rails.root.join("pacts").to_s : "pacts")
|
|
192
|
+
logger.info("[verifier] pact broker url or pact uri is not set, using directory #{path} as a verification source")
|
|
193
|
+
return PactFfi::Verifier.add_directory_source(handle, path)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
if @pact_config.pact_uri.present?
|
|
197
|
+
if @pact_config.pact_uri.start_with?("http")
|
|
198
|
+
logger.info("[verifier] using pact uri #{@pact_config.pact_uri} as a verification source")
|
|
199
|
+
PactFfi::Verifier.url_source(handle, @pact_config.pact_uri, @pact_config.broker_username, @pact_config.broker_password, @pact_config.broker_token)
|
|
200
|
+
else
|
|
201
|
+
logger.info("[verifier] using pact file #{@pact_config.pact_uri} as a verification source")
|
|
202
|
+
PactFfi::Verifier.add_file_source(handle, @pact_config.pact_uri)
|
|
203
|
+
end
|
|
204
|
+
else
|
|
205
|
+
logger.info("[verifier] using pact broker url #{@pact_config.broker_url} with consumer selectors: #{JSON.dump(consumer_selectors)} as a verification source")
|
|
206
|
+
consumer_selectors = [] if consumer_selectors.nil?
|
|
207
|
+
filters = consumer_selectors.map do |selector|
|
|
208
|
+
FFI::MemoryPointer.from_string(JSON.dump(selector).to_s)
|
|
209
|
+
end
|
|
210
|
+
filters_ptr = FFI::MemoryPointer.new(:pointer, filters.size + 1)
|
|
211
|
+
filters_ptr.write_array_of_pointer(filters)
|
|
212
|
+
PactFfi::Verifier.broker_source_with_selectors(handle, @pact_config.pact_broker_proxy_url, @pact_config.broker_username, @pact_config.broker_password, @pact_config.broker_token, bool_to_int(@pact_config.enable_pending), @pact_config.include_wip_pacts_since, c_provider_version_tags, c_provider_version_tags_size, @pact_config.provider_version_branch, filters_ptr, consumer_selectors.size, c_consumer_version_tags, c_consumer_version_tags_size)
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def consumer_selectors
|
|
217
|
+
(!@pact_config.consumer_version_selectors.empty? && @pact_config.consumer_version_selectors) || @consumer_selectors if @pact_config.consumer_version_selectors
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def build_consumer_selectors(verify_only, consumer_name, consumer_branch)
|
|
221
|
+
# if verify_only and consumer_name are defined - select only needed consumer
|
|
222
|
+
if verify_only.present?
|
|
223
|
+
# select proper consumer branch if defined
|
|
224
|
+
if consumer_name.present?
|
|
225
|
+
return [] unless verify_only.include?(consumer_name)
|
|
226
|
+
return [{"branch" => consumer_branch, "consumer" => consumer_name}] if consumer_branch.present?
|
|
227
|
+
return [DEFAULT_CONSUMER_SELECTORS.merge("consumer" => consumer_name)]
|
|
228
|
+
end
|
|
229
|
+
# or default selectors
|
|
230
|
+
return verify_only.map { |name| DEFAULT_CONSUMER_SELECTORS.merge("consumer" => name) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# select provided consumer_name
|
|
234
|
+
return [{"branch" => consumer_branch, "consumer" => consumer_name}] if consumer_name.present? && consumer_branch.present?
|
|
235
|
+
return [DEFAULT_CONSUMER_SELECTORS.merge("consumer" => consumer_name)] if consumer_name.present?
|
|
236
|
+
|
|
237
|
+
[DEFAULT_CONSUMER_SELECTORS]
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pact/ffi/verifier"
|
|
4
|
+
require "pact/v2/native/logger"
|
|
5
|
+
|
|
6
|
+
module Pact
|
|
7
|
+
module V2
|
|
8
|
+
module Provider
|
|
9
|
+
class GrpcVerifier < BaseVerifier
|
|
10
|
+
PROVIDER_TRANSPORT_TYPE = "grpc"
|
|
11
|
+
|
|
12
|
+
def initialize(pact_config, mixed_config = nil)
|
|
13
|
+
super
|
|
14
|
+
|
|
15
|
+
raise ArgumentError, "pact_config must be an instance of Pact::V2::Provider::PactConfig::Grpc" unless pact_config.is_a?(::Pact::V2::Provider::PactConfig::Grpc)
|
|
16
|
+
@grpc_server = GrufServer.new(host: "127.0.0.1:#{@pact_config.grpc_port}", services: @pact_config.grpc_services)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def add_provider_transport(pact_handle)
|
|
22
|
+
PactFfi::Verifier.add_provider_transport(pact_handle, PROVIDER_TRANSPORT_TYPE, @pact_config.grpc_port, "", "")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def start_servers!
|
|
27
|
+
super
|
|
28
|
+
@grpc_server.start
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stop_servers
|
|
32
|
+
super
|
|
33
|
+
@grpc_server.stop
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|