pact-v2 2.0.0.pre.preview2 → 2.0.0.pre.preview3
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 +13 -0
- data/lib/pact/provider/request.rb +14 -1
- data/lib/pact/v2/consumer/grpc_interaction_builder.rb +10 -3
- data/lib/pact/v2/consumer/http_interaction_builder.rb +1 -2
- data/lib/pact/v2/consumer/interaction_contents.rb +8 -0
- data/lib/pact/v2/consumer/message_interaction_builder.rb +9 -2
- data/lib/pact/v2/consumer/mock_server.rb +4 -3
- 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 +6 -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/generators/base.rb +287 -0
- data/lib/pact/v2/generators.rb +49 -0
- data/lib/pact/v2/matchers/base.rb +13 -6
- data/lib/pact/v2/matchers/v3/content_type.rb +32 -0
- data/lib/pact/v2/matchers/v3/null.rb +16 -0
- data/lib/pact/v2/matchers/v3/semver.rb +23 -0
- data/lib/pact/v2/matchers/v3/values.rb +16 -0
- data/lib/pact/v2/matchers/v4/not_empty.rb +10 -3
- data/lib/pact/v2/matchers/v4/status_code.rb +17 -0
- data/lib/pact/v2/matchers.rb +16 -0
- data/lib/pact/v2/provider/pact_broker_proxy.rb +0 -5
- data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +14 -1
- data/lib/pact/v2/version.rb +2 -2
- data/lib/pact/version.rb +1 -1
- data/lib/pact.rb +7 -13
- data/pact.gemspec +57 -67
- metadata +203 -204
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pact/ffi/sync_message_consumer"
|
4
|
+
require "pact/ffi/plugin_consumer"
|
5
|
+
require "pact/ffi/logger"
|
6
|
+
|
7
|
+
module Pact
|
8
|
+
module V2
|
9
|
+
module Consumer
|
10
|
+
class PluginSyncMessageInteractionBuilder
|
11
|
+
|
12
|
+
class PluginInitError < Pact::V2::FfiError; end
|
13
|
+
|
14
|
+
# https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_using_plugin.html
|
15
|
+
INIT_PLUGIN_ERRORS = {
|
16
|
+
1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
|
17
|
+
2 => {reason: :plugin_load_failed, status: 2, description: "Failed to load the plugin"},
|
18
|
+
3 => {reason: :invalid_handle, status: 3, description: "Pact Handle is not valid"}
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
# https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
|
22
|
+
CREATE_INTERACTION_ERRORS = {
|
23
|
+
1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
|
24
|
+
2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
|
25
|
+
3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
|
26
|
+
4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
|
27
|
+
5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
|
28
|
+
6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
class CreateInteractionError < Pact::V2::FfiError; end
|
32
|
+
|
33
|
+
class InteractionMismatchesError < Pact::V2::Error; end
|
34
|
+
|
35
|
+
class InteractionBuilderError < Pact::V2::Error; end
|
36
|
+
|
37
|
+
def initialize(pact_config, description: nil)
|
38
|
+
@pact_config = pact_config
|
39
|
+
@description = description || ""
|
40
|
+
@request = nil
|
41
|
+
@response = nil
|
42
|
+
@response_meta = nil
|
43
|
+
@provider_state_meta = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def with_plugin(plugin_name, plugin_version)
|
47
|
+
raise InteractionBuilderError.new("plugin_name is required") if plugin_name.blank?
|
48
|
+
raise InteractionBuilderError.new("plugin_version is required") if plugin_version.blank?
|
49
|
+
|
50
|
+
@plugin_name = plugin_name
|
51
|
+
@plugin_version = plugin_version
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def given(provider_state, metadata = {})
|
56
|
+
@provider_state_meta = {provider_state => metadata}
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def upon_receiving(description)
|
61
|
+
@description = description
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_request(req_hash)
|
66
|
+
@request = InteractionContents.plugin(req_hash)
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def with_content_type(content_type)
|
71
|
+
@content_type = content_type
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def will_respond_with(resp_hash)
|
76
|
+
@response = InteractionContents.plugin(resp_hash)
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def will_respond_with_meta(meta_hash)
|
81
|
+
@response_meta = InteractionContents.plugin(meta_hash)
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def with_plugin_metadata(meta_hash)
|
86
|
+
@plugin_metadata = meta_hash
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def with_transport(transport)
|
91
|
+
@transport = transport
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def interaction_json
|
96
|
+
result = {
|
97
|
+
request: @request
|
98
|
+
}
|
99
|
+
result.merge!(@plugin_metadata) if @plugin_metadata.is_a?(Hash)
|
100
|
+
|
101
|
+
result[:response] = @response if @response.is_a?(Hash)
|
102
|
+
result[:responseMetadata] = @response_meta if @response_meta.is_a?(Hash)
|
103
|
+
|
104
|
+
JSON.dump(result)
|
105
|
+
end
|
106
|
+
|
107
|
+
def validate!
|
108
|
+
raise InteractionBuilderError.new("invalid request format, should be a hash") unless @request.is_a?(Hash)
|
109
|
+
raise InteractionBuilderError.new("invalid response format, should be a hash") unless @response.is_a?(Hash) || @response_meta.is_a?(Hash)
|
110
|
+
end
|
111
|
+
|
112
|
+
def execute(&block)
|
113
|
+
raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
|
114
|
+
|
115
|
+
validate!
|
116
|
+
|
117
|
+
pact_handle = init_pact
|
118
|
+
init_plugin!(pact_handle)
|
119
|
+
|
120
|
+
message_pact = PactFfi::SyncMessageConsumer.new_interaction(pact_handle, @description)
|
121
|
+
@provider_state_meta&.each_pair do |provider_state, meta|
|
122
|
+
if meta.present?
|
123
|
+
meta.each_pair { |k, v| PactFfi.given_with_param(message_pact, provider_state, k.to_s, v.to_s) }
|
124
|
+
else
|
125
|
+
PactFfi.given(message_pact, provider_state)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
result = PactFfi::PluginConsumer.interaction_contents(message_pact, 0, @content_type, interaction_json)
|
129
|
+
if CREATE_INTERACTION_ERRORS[result].present?
|
130
|
+
error = CREATE_INTERACTION_ERRORS[result]
|
131
|
+
raise CreateInteractionError.new("There was an error while trying to add interaction \"#{@description}\"", error[:reason], error[:status])
|
132
|
+
end
|
133
|
+
|
134
|
+
mock_server = MockServer.create_for_transport!(pact: pact_handle, transport: @transport, host: @pact_config.mock_host, port: @pact_config.mock_port)
|
135
|
+
|
136
|
+
yield(message_pact, mock_server)
|
137
|
+
|
138
|
+
ensure
|
139
|
+
if mock_server.matched?
|
140
|
+
mock_server.write_pacts!(@pact_config.pact_dir)
|
141
|
+
else
|
142
|
+
msg = mismatches_error_msg(mock_server)
|
143
|
+
raise InteractionMismatchesError.new(msg)
|
144
|
+
end
|
145
|
+
@used = true
|
146
|
+
mock_server&.cleanup
|
147
|
+
PactFfi::PluginConsumer.cleanup_plugins(pact_handle)
|
148
|
+
PactFfi.free_pact_handle(pact_handle)
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def mismatches_error_msg(mock_server)
|
154
|
+
rspec_example_desc = RSpec.current_example&.description
|
155
|
+
return "interaction for has mismatches: #{mock_server.mismatches}" if rspec_example_desc.blank?
|
156
|
+
|
157
|
+
"#{rspec_example_desc} has mismatches: #{mock_server.mismatches}"
|
158
|
+
end
|
159
|
+
|
160
|
+
def init_pact
|
161
|
+
handle = PactFfi.new_pact(@pact_config.consumer_name, @pact_config.provider_name)
|
162
|
+
PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_V4"])
|
163
|
+
PactFfi.with_pact_metadata(handle, "pact-ruby-v2", "pact-ffi", PactFfi.version)
|
164
|
+
|
165
|
+
Pact::V2::Native::Logger.log_to_stdout(@pact_config.log_level)
|
166
|
+
|
167
|
+
handle
|
168
|
+
end
|
169
|
+
|
170
|
+
def init_plugin!(pact_handle)
|
171
|
+
result = PactFfi::PluginConsumer.using_plugin(pact_handle, @plugin_name, @plugin_version)
|
172
|
+
return result if INIT_PLUGIN_ERRORS[result].blank?
|
173
|
+
|
174
|
+
error = INIT_PLUGIN_ERRORS[result]
|
175
|
+
raise PluginInitError.new("There was an error while trying to initialize plugin #{@plugin_name}/#{@plugin_version}", error[:reason], error[:status])
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
module V2
|
5
|
+
module Generators
|
6
|
+
module Base
|
7
|
+
def as_basic
|
8
|
+
raise NotImplementedError, "Subclasses must implement the as_basic method"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class RandomIntGenerator
|
13
|
+
include Base
|
14
|
+
|
15
|
+
def initialize(min:, max:)
|
16
|
+
@min = min
|
17
|
+
@max = max
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_basic
|
21
|
+
{
|
22
|
+
"pact:matcher:type" => "integer",
|
23
|
+
"pact:generator:type" => "RandomInt",
|
24
|
+
"min" => @min,
|
25
|
+
"max" => @max,
|
26
|
+
"value" => rand(@min..@max)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class RandomDecimalGenerator
|
32
|
+
include Base
|
33
|
+
|
34
|
+
def initialize(digits:)
|
35
|
+
@digits = digits
|
36
|
+
end
|
37
|
+
|
38
|
+
def as_basic
|
39
|
+
{
|
40
|
+
'pact:matcher:type' => 'decimal',
|
41
|
+
"pact:generator:type" => "RandomDecimal",
|
42
|
+
"digits" => @digits,
|
43
|
+
"value" => rand.round(@digits)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class RandomHexadecimalGenerator
|
49
|
+
include Base
|
50
|
+
|
51
|
+
def initialize(digits:)
|
52
|
+
@digits = digits
|
53
|
+
end
|
54
|
+
|
55
|
+
def as_basic
|
56
|
+
{
|
57
|
+
"pact:matcher:type" => "decimal",
|
58
|
+
"pact:generator:type" => "RandomHexadecimal",
|
59
|
+
"digits" => @digits,
|
60
|
+
"value" => SecureRandom.hex((@digits / 2.0).ceil)[0...@digits]
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class RandomStringGenerator
|
66
|
+
include Base
|
67
|
+
|
68
|
+
def initialize(size:, example: nil)
|
69
|
+
@size = size
|
70
|
+
@example = example
|
71
|
+
end
|
72
|
+
|
73
|
+
def as_basic
|
74
|
+
{
|
75
|
+
"pact:matcher:type" => "type",
|
76
|
+
"pact:generator:type" => "RandomString",
|
77
|
+
"size" => @size,
|
78
|
+
"value" => @example || SecureRandom.alphanumeric(@size)
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class UuidGenerator
|
84
|
+
include Base
|
85
|
+
|
86
|
+
|
87
|
+
def initialize(example: nil)
|
88
|
+
@example = example
|
89
|
+
@regexStr = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';
|
90
|
+
if @example
|
91
|
+
regex = Regexp.new("^#{@regexStr}$")
|
92
|
+
unless @example.match?(regex)
|
93
|
+
raise ArgumentError, "regex: Example value '#{@example}' does not match the UUID regular expression '#{@regexStr}'"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def as_basic
|
99
|
+
{
|
100
|
+
"pact:matcher:type" => "regex",
|
101
|
+
"pact:generator:type" => "Uuid",
|
102
|
+
"regex" => @regexStr,
|
103
|
+
"value" => @example || SecureRandom.uuid
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
class DateGenerator
|
109
|
+
include Base
|
110
|
+
|
111
|
+
def initialize(format: nil, example: nil)
|
112
|
+
@format = format || default_format
|
113
|
+
@example = example || Time.now.strftime(convert_from_java_simple_date_format(@format))
|
114
|
+
end
|
115
|
+
|
116
|
+
def as_basic
|
117
|
+
h = { "pact:generator:type" => type }
|
118
|
+
h["pact:matcher:type"] = matcher_type
|
119
|
+
h["format"] = @format if @format
|
120
|
+
h["value"] = @example
|
121
|
+
h
|
122
|
+
end
|
123
|
+
|
124
|
+
def type
|
125
|
+
'Date'
|
126
|
+
end
|
127
|
+
|
128
|
+
def matcher_type
|
129
|
+
'date'
|
130
|
+
end
|
131
|
+
|
132
|
+
def default_format
|
133
|
+
'yyyy-MM-dd'
|
134
|
+
end
|
135
|
+
|
136
|
+
# Converts Java SimpleDateFormat to Ruby strftime format
|
137
|
+
def convert_from_java_simple_date_format(format)
|
138
|
+
f = format.dup
|
139
|
+
# Year
|
140
|
+
f.gsub!(/(?<!%)y{4,}/, '%Y')
|
141
|
+
f.gsub!(/(?<!%)y{1,3}/, '%y')
|
142
|
+
# Month
|
143
|
+
f.gsub!(/(?<!%)M{4,}/, '%B')
|
144
|
+
f.gsub!(/(?<!%)M{3}/, '%b')
|
145
|
+
f.gsub!(/(?<!%)M{1,2}/, '%m')
|
146
|
+
# Week
|
147
|
+
f.gsub!(/(?<!%)w{1,}/, '%W')
|
148
|
+
# Day
|
149
|
+
f.gsub!(/(?<!%)D{1,}/, '%j')
|
150
|
+
f.gsub!(/(?<!%)d{1,}/, '%d')
|
151
|
+
f.gsub!(/(?<!%)E{4,}/, '%A')
|
152
|
+
f.gsub!(/(?<!%)E{1,3}/, '%a')
|
153
|
+
f.gsub!(/(?<!%)u{1,}/, '%u')
|
154
|
+
# Time
|
155
|
+
f.gsub!(/(?<!%)a{1,}/, '%p')
|
156
|
+
f.gsub!(/(?<!%)k{1,}/, '%H')
|
157
|
+
f.gsub!(/(?<!%)n{1,}/, '%M')
|
158
|
+
f.gsub!(/(?<!%)s{1,}/, '%S')
|
159
|
+
f.gsub!(/(?<!%)S{1,}/, '%L')
|
160
|
+
# Timezone
|
161
|
+
f.gsub!(/(?<!%)z{1,}/, '%z')
|
162
|
+
f.gsub!(/(?<!%)Z{1,}/, '%z')
|
163
|
+
f.gsub!(/(?<!%)X{1,}/, '%Z')
|
164
|
+
# Java 'H' (hour in day, 0-23) to Ruby '%H'
|
165
|
+
f.gsub!(/(?<!%)H{2}/, '%H')
|
166
|
+
f.gsub!(/(?<!%)H{1}/, '%k')
|
167
|
+
# Java 'm' (minute in hour) to Ruby '%M'
|
168
|
+
f.gsub!(/(?<!%)m{2}/, '%M')
|
169
|
+
f.gsub!(/(?<!%)m{1}/, '%-M')
|
170
|
+
# Java 'h' (hour in am/pm, 1-12) to Ruby '%I'
|
171
|
+
f.gsub!(/(?<!%)h{2}/, '%I')
|
172
|
+
f.gsub!(/(?<!%)h{1}/, '%-I')
|
173
|
+
# Java 's' (second in minute) to Ruby '%S'
|
174
|
+
f.gsub!(/(?<!%)s{1,}/, '%S')
|
175
|
+
# Java 'a' (am/pm marker) to Ruby '%p'
|
176
|
+
f.gsub!(/(?<!%)a/, '%p')
|
177
|
+
# Java 'K' (hour in am/pm, 0-11) to Ruby '%l'
|
178
|
+
f.gsub!(/(?<!%)K{2}/, '%l')
|
179
|
+
f.gsub!(/(?<!%)K{1}/, '%-l')
|
180
|
+
# Java 'S' (fractional seconds, milliseconds) to Ruby '%L'
|
181
|
+
f.gsub!(/(?<!%)S{1,}/, '%L')
|
182
|
+
# Java 'z' (general time zone) to Ruby '%Z'
|
183
|
+
f.gsub!(/(?<!%)z{1,}/, '%Z')
|
184
|
+
# Java 'Z' (RFC 822 time zone) to Ruby '%z'
|
185
|
+
f.gsub!(/(?<!%)Z{1,}/, '%z')
|
186
|
+
# Java 'X' (ISO 8601 time zone) to Ruby '%z'
|
187
|
+
f.gsub!(/(?<!%)X{1,}/, '%z')
|
188
|
+
# Java 'G' (era designator) - no direct Ruby equivalent, remove or leave as is
|
189
|
+
f.gsub!(/(?<!%)G+/, '%G')
|
190
|
+
# Java 'Q' (quarter) - no direct Ruby equivalent, remove or leave as is
|
191
|
+
f.gsub!(/(?<!%)Q+/, '')
|
192
|
+
# Java 'F' (day of week in month) - no direct Ruby equivalent, remove or leave as is
|
193
|
+
f.gsub!(/(?<!%)F+/, '')
|
194
|
+
# Java 'c' (stand-alone day of week) - no direct Ruby equivalent, remove or leave as is
|
195
|
+
f.gsub!(/(?<!%)c+/, '')
|
196
|
+
# Java 'L' (stand-alone month) - treat as month
|
197
|
+
f.gsub!(/(?<!%)L{4,}/, '%B')
|
198
|
+
f.gsub!(/(?<!%)L{3}/, '%b')
|
199
|
+
f.gsub!(/(?<!%)L{1,2}/, '%m')
|
200
|
+
f
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Time provides the time generator which will give the current time in the defined format
|
205
|
+
class TimeGenerator < DateGenerator
|
206
|
+
def type
|
207
|
+
'Time'
|
208
|
+
end
|
209
|
+
|
210
|
+
def matcher_type
|
211
|
+
'time'
|
212
|
+
end
|
213
|
+
|
214
|
+
def default_format
|
215
|
+
'HH:mm'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
class DateTimeGenerator < DateGenerator
|
220
|
+
def type
|
221
|
+
'DateTime'
|
222
|
+
end
|
223
|
+
|
224
|
+
def matcher_type
|
225
|
+
'datetime'
|
226
|
+
end
|
227
|
+
|
228
|
+
def default_format
|
229
|
+
'yyyy-MM-dd HH:mm'
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
class RandomBooleanGenerator
|
234
|
+
include Base
|
235
|
+
|
236
|
+
def initialize(example: nil)
|
237
|
+
@example = example
|
238
|
+
end
|
239
|
+
|
240
|
+
def as_basic
|
241
|
+
{
|
242
|
+
"pact:matcher:type" => "boolean",
|
243
|
+
"pact:generator:type" => "RandomBoolean",
|
244
|
+
"value" => @example.nil? ? [true, false].sample : @example
|
245
|
+
}
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
class ProviderStateGenerator
|
250
|
+
include Base
|
251
|
+
|
252
|
+
def initialize(expression:, example:)
|
253
|
+
@expression = expression
|
254
|
+
@value = example
|
255
|
+
end
|
256
|
+
|
257
|
+
def as_basic
|
258
|
+
{
|
259
|
+
'pact:matcher:type': 'type',
|
260
|
+
"pact:generator:type" => "ProviderState",
|
261
|
+
"expression" => @expression,
|
262
|
+
"value" => @value
|
263
|
+
}
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class MockServerURLGenerator
|
268
|
+
include Base
|
269
|
+
|
270
|
+
def initialize(regex:, example:)
|
271
|
+
@regex = regex
|
272
|
+
@example = example
|
273
|
+
end
|
274
|
+
|
275
|
+
def as_basic
|
276
|
+
{
|
277
|
+
"pact:generator:type" => "MockServerURL",
|
278
|
+
"pact:matcher:type" => "regex",
|
279
|
+
"regex" => @regex,
|
280
|
+
"example" => @example,
|
281
|
+
"value" => @example
|
282
|
+
}
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
module V2
|
5
|
+
module Generators
|
6
|
+
|
7
|
+
def generate_random_int(min:, max:)
|
8
|
+
Pact::V2::Generators::RandomIntGenerator.new(min: min, max: max)
|
9
|
+
end
|
10
|
+
def generate_random_decimal(digits:)
|
11
|
+
Pact::V2::Generators::RandomDecimalGenerator.new(digits: digits)
|
12
|
+
end
|
13
|
+
def generate_random_hexadecimal(digits:)
|
14
|
+
Pact::V2::Generators::RandomHexadecimalGenerator.new(digits: digits)
|
15
|
+
end
|
16
|
+
def generate_random_string(size:)
|
17
|
+
Pact::V2::Generators::RandomStringGenerator.new(size: size)
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_uuid(example: nil)
|
21
|
+
Pact::V2::Generators::UuidGenerator.new(example: example)
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate_date(format: nil, example: nil)
|
25
|
+
Pact::V2::Generators::DateGenerator.new(format: format, example: example)
|
26
|
+
end
|
27
|
+
|
28
|
+
def generate_time(format: nil)
|
29
|
+
Pact::V2::Generators::TimeGenerator.new(format: format)
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_datetime(format: nil)
|
33
|
+
Pact::V2::Generators::DateTimeGenerator.new(format: format)
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate_random_boolean
|
37
|
+
Pact::V2::Generators::RandomBooleanGenerator.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def generate_from_provider_state(expression:, example:)
|
41
|
+
Pact::V2::Generators::ProviderStateGenerator.new(expression: expression, example: example).as_basic
|
42
|
+
end
|
43
|
+
|
44
|
+
def generate_mock_server_url(regex: nil, example: nil)
|
45
|
+
Pact::V2::Generators::MockServerURLGenerator.new(regex: regex, example: example)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -9,7 +9,7 @@ module Pact
|
|
9
9
|
|
10
10
|
class MatcherInitializationError < Pact::V2::Error; end
|
11
11
|
|
12
|
-
def initialize(spec_version:, kind:, template
|
12
|
+
def initialize(spec_version:, kind:, template: nil, opts: {})
|
13
13
|
@spec_version = spec_version
|
14
14
|
@kind = kind
|
15
15
|
@template = template
|
@@ -17,15 +17,22 @@ module Pact
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def as_basic
|
20
|
-
{
|
21
|
-
"pact:matcher:type" => serialize!(@kind.deep_dup, :basic)
|
22
|
-
|
23
|
-
|
20
|
+
result = {
|
21
|
+
"pact:matcher:type" => serialize!(@kind.deep_dup, :basic)
|
22
|
+
}
|
23
|
+
result["status"] = serialize!(@opts[:status].deep_dup, :basic) if @opts[:status]
|
24
|
+
result["value"] = serialize!(@template.deep_dup, :basic) unless @template.nil?
|
25
|
+
result.merge!(serialize!(@opts.deep_dup, :basic))
|
26
|
+
result
|
24
27
|
end
|
25
28
|
|
26
29
|
def as_plugin
|
27
30
|
params = @opts.values.map { |v| format_primitive(v) }.join(",")
|
28
|
-
value = format_primitive(@template)
|
31
|
+
value = format_primitive(@template) unless @template.nil?
|
32
|
+
|
33
|
+
if @template.nil?
|
34
|
+
return "matching(#{@kind}#{params.present? ? ", #{params}" : ""})"
|
35
|
+
end
|
29
36
|
|
30
37
|
return "matching(#{@kind}, #{params}, #{value})" if params.present?
|
31
38
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
module V2
|
5
|
+
module Matchers
|
6
|
+
module V3
|
7
|
+
class ContentType < Pact::V2::Matchers::Base
|
8
|
+
def initialize(content_type, template: nil)
|
9
|
+
@content_type = content_type
|
10
|
+
@template = template
|
11
|
+
@opts = {}
|
12
|
+
@opts[:plugin_template] = template unless template.nil?
|
13
|
+
unless content_type.is_a?(String) && !content_type.empty?
|
14
|
+
raise MatcherInitializationError, "#{self.class}: content_type must be a non-empty String"
|
15
|
+
end
|
16
|
+
|
17
|
+
super(
|
18
|
+
spec_version: Pact::V2::Matchers::PACT_SPEC_V3,
|
19
|
+
kind: "contentType",
|
20
|
+
template: content_type,
|
21
|
+
opts: @opts
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def as_plugin
|
26
|
+
"matching(contentType, '#{@content_type}', '#{@opts[:plugin_template]}')"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
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 Null < Pact::V2::Matchers::Base
|
8
|
+
def initialize
|
9
|
+
super(spec_version: Pact::V2::Matchers::PACT_SPEC_V3, kind: "null", template: nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -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,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
|
@@ -5,10 +5,17 @@ module Pact
|
|
5
5
|
module Matchers
|
6
6
|
module V4
|
7
7
|
class NotEmpty < Pact::V2::Matchers::Base
|
8
|
-
def initialize(template)
|
9
|
-
|
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
|
10
17
|
|
11
|
-
|
18
|
+
"notEmpty('#{@template}')"
|
12
19
|
end
|
13
20
|
end
|
14
21
|
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
|