pact 1.66.2 → 1.67.1

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/lib/pact/v2/configuration.rb +23 -0
  4. data/lib/pact/v2/consumer/grpc_interaction_builder.rb +194 -0
  5. data/lib/pact/v2/consumer/http_interaction_builder.rb +162 -0
  6. data/lib/pact/v2/consumer/interaction_contents.rb +62 -0
  7. data/lib/pact/v2/consumer/message_interaction_builder.rb +287 -0
  8. data/lib/pact/v2/consumer/mock_server.rb +100 -0
  9. data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
  10. data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
  11. data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
  12. data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
  13. data/lib/pact/v2/consumer/pact_config/plugin_async_message.rb +26 -0
  14. data/lib/pact/v2/consumer/pact_config/plugin_http.rb +26 -0
  15. data/lib/pact/v2/consumer/pact_config/plugin_sync_message.rb +26 -0
  16. data/lib/pact/v2/consumer/pact_config.rb +30 -0
  17. data/lib/pact/v2/consumer/plugin_async_message_interaction_builder.rb +171 -0
  18. data/lib/pact/v2/consumer/plugin_http_interaction_builder.rb +201 -0
  19. data/lib/pact/v2/consumer/plugin_sync_message_interaction_builder.rb +180 -0
  20. data/lib/pact/v2/consumer.rb +8 -0
  21. data/lib/pact/v2/generators/base.rb +287 -0
  22. data/lib/pact/v2/generators.rb +49 -0
  23. data/lib/pact/v2/matchers/base.rb +74 -0
  24. data/lib/pact/v2/matchers/v1/equality.rb +19 -0
  25. data/lib/pact/v2/matchers/v2/regex.rb +19 -0
  26. data/lib/pact/v2/matchers/v2/type.rb +17 -0
  27. data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
  28. data/lib/pact/v2/matchers/v3/content_type.rb +32 -0
  29. data/lib/pact/v2/matchers/v3/date.rb +18 -0
  30. data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
  31. data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
  32. data/lib/pact/v2/matchers/v3/each.rb +42 -0
  33. data/lib/pact/v2/matchers/v3/include.rb +17 -0
  34. data/lib/pact/v2/matchers/v3/integer.rb +17 -0
  35. data/lib/pact/v2/matchers/v3/null.rb +16 -0
  36. data/lib/pact/v2/matchers/v3/number.rb +17 -0
  37. data/lib/pact/v2/matchers/v3/semver.rb +23 -0
  38. data/lib/pact/v2/matchers/v3/time.rb +18 -0
  39. data/lib/pact/v2/matchers/v3/values.rb +16 -0
  40. data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
  41. data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
  42. data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
  43. data/lib/pact/v2/matchers/v4/not_empty.rb +24 -0
  44. data/lib/pact/v2/matchers/v4/status_code.rb +17 -0
  45. data/lib/pact/v2/matchers.rb +110 -0
  46. data/lib/pact/v2/native/blocking_verifier.rb +17 -0
  47. data/lib/pact/v2/native/logger.rb +25 -0
  48. data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
  49. data/lib/pact/v2/provider/base_verifier.rb +242 -0
  50. data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
  51. data/lib/pact/v2/provider/gruf_server.rb +75 -0
  52. data/lib/pact/v2/provider/http_server.rb +79 -0
  53. data/lib/pact/v2/provider/http_verifier.rb +43 -0
  54. data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
  55. data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
  56. data/lib/pact/v2/provider/pact_broker_proxy.rb +66 -0
  57. data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
  58. data/lib/pact/v2/provider/pact_config/async.rb +29 -0
  59. data/lib/pact/v2/provider/pact_config/base.rb +101 -0
  60. data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
  61. data/lib/pact/v2/provider/pact_config/http.rb +27 -0
  62. data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
  63. data/lib/pact/v2/provider/pact_config.rb +26 -0
  64. data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
  65. data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
  66. data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
  67. data/lib/pact/v2/provider.rb +8 -0
  68. data/lib/pact/v2/railtie.rb +13 -0
  69. data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +93 -0
  70. data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
  71. data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
  72. data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
  73. data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
  74. data/lib/pact/v2/rspec.rb +17 -0
  75. data/lib/pact/v2/tasks/pact.rake +13 -0
  76. data/lib/pact/v2.rb +71 -0
  77. data/lib/pact/version.rb +1 -1
  78. data/lib/pact.rb +6 -0
  79. data/pact.gemspec +54 -7
  80. metadata +405 -65
@@ -0,0 +1,287 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pact/ffi/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 MessageInteractionBuilder
11
+ META_CONTENT_TYPE_HEADER = "contentType"
12
+
13
+ JSON_CONTENT_TYPE = "application/json"
14
+ PROTO_CONTENT_TYPE = "application/protobuf"
15
+
16
+ PROTOBUF_PLUGIN_NAME = "protobuf"
17
+ PROTOBUF_PLUGIN_VERSION = "0.6.5"
18
+
19
+ # https://docs.rs/pact_ffi/latest/pact_ffi/mock_server/handles/fn.pactffi_write_message_pact_file.html
20
+ WRITE_PACT_FILE_ERRORS = {
21
+ 1 => {reason: :file_not_accessible, status: 1, description: "The pact file was not able to be written"},
22
+ 2 => {reason: :internal_error, status: 2, description: "The message pact for the given handle was not found"}
23
+ }.freeze
24
+
25
+ class PluginInitError < Pact::V2::FfiError; end
26
+
27
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_using_plugin.html
28
+ INIT_PLUGIN_ERRORS = {
29
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
30
+ 2 => {reason: :plugin_load_failed, status: 2, description: "Failed to load the plugin"},
31
+ 3 => {reason: :invalid_handle, status: 3, description: "Pact Handle is not valid"}
32
+ }.freeze
33
+
34
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
35
+ CREATE_INTERACTION_ERRORS = {
36
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
37
+ 2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
38
+ 3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
39
+ 4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
40
+ 5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
41
+ 6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
42
+ }.freeze
43
+
44
+ class CreateInteractionError < Pact::V2::FfiError; end
45
+
46
+ class InteractionMismatchesError < Pact::V2::Error; end
47
+
48
+ class InteractionBuilderError < Pact::V2::Error; end
49
+
50
+ def initialize(pact_config, description: nil)
51
+ @pact_config = pact_config
52
+ @description = description
53
+
54
+ @json_contents = nil
55
+ @proto_contents = nil
56
+ @proto_path = nil
57
+ @proto_message_class = nil
58
+ @proto_include_dirs = []
59
+ @meta = {}
60
+ @headers = {}
61
+ @provider_state_meta = nil
62
+ end
63
+
64
+ def given(provider_state, metadata = {})
65
+ @provider_state_meta = {provider_state => metadata}
66
+ self
67
+ end
68
+
69
+ def upon_receiving(description)
70
+ @description = description
71
+ self
72
+ end
73
+
74
+ def with_json_contents(contents_hash)
75
+ @json_contents = InteractionContents.basic(contents_hash)
76
+ self
77
+ end
78
+
79
+ def with_proto_class(proto_path, message_class_name, include_dirs = [])
80
+ absolute_path = File.expand_path(proto_path)
81
+ raise InteractionBuilderError.new("proto file #{proto_path} does not exist") unless File.exist?(absolute_path)
82
+
83
+ @proto_path = absolute_path
84
+ @proto_message_class = message_class_name
85
+ @proto_include_dirs = include_dirs.map { |dir| File.expand_path(dir) }
86
+ self
87
+ end
88
+
89
+ def with_pact_protobuf_plugin_version(version)
90
+ raise InteractionBuilderError.new("version is required") if version.blank?
91
+
92
+ @proto_plugin_version = version
93
+ self
94
+ end
95
+
96
+ def with_proto_contents(contents_hash)
97
+ @proto_contents = InteractionContents.plugin(contents_hash)
98
+ self
99
+ end
100
+
101
+ def with_metadata(meta_hash)
102
+ @meta = InteractionContents.basic(meta_hash)
103
+ self
104
+ end
105
+
106
+ def with_headers(headers_hash)
107
+ @headers = InteractionContents.basic(headers_hash)
108
+ self
109
+ end
110
+
111
+ def with_header(key, value)
112
+ @headers[key] = value
113
+ self
114
+ end
115
+
116
+ def validate!
117
+ if proto_interaction?
118
+ raise InteractionBuilderError.new("proto_path / proto_message are not defined, please set ones with #with_proto_message") if @proto_contents.blank? || @proto_message_class.blank?
119
+ raise InteractionBuilderError.new("invalid request format, should be a hash") unless @proto_contents.is_a?(Hash)
120
+ else
121
+ raise InteractionBuilderError.new("invalid request format, should be a hash") unless @json_contents.is_a?(Hash)
122
+ end
123
+ raise InteractionBuilderError.new("description is required for message interactions, please set one with #upon_receiving") if @description.blank?
124
+ end
125
+
126
+ def execute(&block)
127
+ raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
128
+
129
+ validate!
130
+ pact_handle = init_pact
131
+ init_plugin!(pact_handle) if proto_interaction?
132
+
133
+ message_pact = PactFfi::MessageConsumer.new_message_interaction(pact_handle, @description)
134
+
135
+ configure_interaction!(message_pact)
136
+
137
+ # strip out matchers and get raw payload/metadata
138
+ payload, metadata = fetch_reified_message(pact_handle)
139
+ configure_provider_state(message_pact, metadata)
140
+
141
+ yield(payload, metadata)
142
+
143
+ write_pacts!(pact_handle, @pact_config.pact_dir)
144
+ ensure
145
+ @used = true
146
+ PactFfi::MessageConsumer.free_handle(message_pact)
147
+ PactFfi.free_pact_handle(pact_handle)
148
+ end
149
+
150
+ def build_interaction_json
151
+ return JSON.dump(@json_contents) unless proto_interaction?
152
+
153
+ contents = {
154
+ "pact:proto": @proto_path,
155
+ "pact:message-type": @proto_message_class,
156
+ "pact:content-type": PROTO_CONTENT_TYPE
157
+ }.merge(@proto_contents)
158
+
159
+ contents["pact:protobuf-config"] = {additionalIncludes: @proto_include_dirs} if @proto_include_dirs.present?
160
+
161
+ JSON.dump(contents)
162
+ end
163
+
164
+ private
165
+
166
+ def write_pacts!(handle, dir)
167
+ result = PactFfi.write_message_pact_file(handle, @pact_config.pact_dir, false)
168
+ return result if WRITE_PACT_FILE_ERRORS[result].blank?
169
+
170
+ error = WRITE_PACT_FILE_ERRORS[result]
171
+ raise WritePactsError.new("There was an error while trying to write pact file to #{dir}", error[:reason], error[:status])
172
+ end
173
+
174
+ def init_pact
175
+ handle = PactFfi::MessageConsumer.new_message_pact(@pact_config.consumer_name, @pact_config.provider_name)
176
+ PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_V4"])
177
+ PactFfi.with_pact_metadata(handle, "pact-ruby-v2", "pact-ffi", PactFfi.version)
178
+
179
+ Pact::V2::Native::Logger.log_to_stdout(@pact_config.log_level)
180
+
181
+ handle
182
+ end
183
+
184
+ def fetch_reified_message(pact_handle)
185
+ iterator = PactFfi::MessageConsumer.pact_handle_get_message_iter(pact_handle)
186
+ raise InteractionBuilderError.new("cannot get message iterator: internal error") if iterator.blank?
187
+
188
+ message_handle = PactFfi.pact_message_iter_next(iterator)
189
+ raise InteractionBuilderError.new("cannot get message from iterator: no messages") if message_handle.blank?
190
+
191
+ contents = fetch_reified_message_body(message_handle)
192
+ meta = fetch_reified_message_headers(message_handle)
193
+
194
+ [contents, meta.compact]
195
+ ensure
196
+ PactFfi.pact_message_iter_delete(iterator) if iterator.present?
197
+ end
198
+
199
+ def fetch_reified_message_headers(message_handle)
200
+ meta = {"headers" => {}}
201
+
202
+ meta[META_CONTENT_TYPE_HEADER] = PactFfi.message_find_metadata(message_handle, META_CONTENT_TYPE_HEADER)
203
+
204
+ @meta.each_key do |key|
205
+ meta[key.to_s] = PactFfi.message_find_metadata(message_handle, key.to_s)
206
+ end
207
+
208
+ @headers.each_key do |key|
209
+ meta["headers"][key.to_s] = PactFfi.message_find_metadata(message_handle, key.to_s)
210
+ end
211
+
212
+ meta
213
+ end
214
+
215
+ def configure_provider_state(message_pact, reified_metadata)
216
+ content_type = reified_metadata[META_CONTENT_TYPE_HEADER]
217
+ @provider_state_meta&.each_pair do |provider_state, meta|
218
+ if meta.present?
219
+ meta.each_pair { |k, v| PactFfi.given_with_param(message_pact, provider_state, k.to_s, v.to_s) }
220
+ PactFfi.given_with_param(message_pact, provider_state, META_CONTENT_TYPE_HEADER, content_type.to_s) if content_type
221
+ elsif content_type.present?
222
+ PactFfi.given_with_param(message_pact, provider_state, META_CONTENT_TYPE_HEADER, content_type.to_s)
223
+ else
224
+ PactFfi.given(message_pact, provider_state)
225
+ end
226
+ end
227
+ end
228
+
229
+ def fetch_reified_message_body(message_handle)
230
+ if proto_interaction?
231
+ len = PactFfi::MessageConsumer.get_contents_length(message_handle)
232
+ ptr = PactFfi::MessageConsumer.get_contents_bin(message_handle)
233
+ return nil if ptr.blank? || len == 0
234
+
235
+ return String.new(ptr.read_string_length(len))
236
+ end
237
+
238
+ contents = PactFfi::MessageConsumer.get_contents(message_handle)
239
+ return nil if contents.blank?
240
+
241
+ JSON.parse(contents)
242
+ end
243
+
244
+ def configure_interaction!(message_pact)
245
+ interaction_json = build_interaction_json
246
+
247
+ if proto_interaction?
248
+ result = PactFfi::PluginConsumer.interaction_contents(message_pact, 0, PROTO_CONTENT_TYPE, interaction_json)
249
+ if CREATE_INTERACTION_ERRORS[result].present?
250
+ error = CREATE_INTERACTION_ERRORS[result]
251
+ raise CreateInteractionError.new("There was an error while trying to add interaction \"#{@description}\"", error[:reason], error[:status])
252
+ end
253
+ else
254
+ result = PactFfi.with_body(message_pact, 0, JSON_CONTENT_TYPE, interaction_json)
255
+ unless result
256
+ raise InteractionMismatchesError.new("There was an error while trying to add message interaction contents \"#{@description}\"")
257
+ end
258
+ end
259
+
260
+ # meta should be configured last to avoid resetting after body is set
261
+ InteractionContents.basic(@meta.merge(@headers)).each_pair do |key, value|
262
+ PactFfi::MessageConsumer.with_metadata_v2(message_pact, key.to_s, JSON.dump(value))
263
+ end
264
+ end
265
+
266
+ def init_plugin!(pact_handle)
267
+ result = PactFfi::PluginConsumer.using_plugin(pact_handle, PROTOBUF_PLUGIN_NAME, @proto_plugin_version || PROTOBUF_PLUGIN_VERSION)
268
+ return result if INIT_PLUGIN_ERRORS[result].blank?
269
+
270
+ error = INIT_PLUGIN_ERRORS[result]
271
+ raise PluginInitError.new("There was an error while trying to initialize plugin #{PROTOBUF_PLUGIN_NAME}/#{@proto_plugin_version || PROTOBUF_PLUGIN_VERSION}", error[:reason], error[:status])
272
+ end
273
+
274
+ def serialize_metadata(metadata_hash)
275
+ metadata = metadata_hash.deep_dup
276
+ serialize_as!(metadata, :basic)
277
+
278
+ metadata
279
+ end
280
+
281
+ def proto_interaction?
282
+ @proto_contents.present?
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pact/ffi/mock_server"
4
+
5
+ module Pact
6
+ module V2
7
+ module Consumer
8
+ class MockServer
9
+ attr_reader :host, :port, :transport, :handle, :url
10
+
11
+ TRANSPORT_HTTP = "http"
12
+ TRANSPORT_GRPC = "grpc"
13
+
14
+ class MockServerCreateError < Pact::V2::FfiError; end
15
+
16
+ class WritePactsError < Pact::V2::FfiError; end
17
+
18
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/mock_server/fn.pactffi_create_mock_server_for_transport.html
19
+ CREATE_TRANSPORT_ERRORS = {
20
+ -1 => {reason: :invalid_handle, status: -1, description: "An invalid handle was received. Handles should be created with pactffi_new_pact"},
21
+ -2 => {reason: :invalid_transport_json, status: -2, description: "Transport_config is not valid JSON"},
22
+ -3 => {reason: :mock_server_not_started, status: -3, description: "The mock server could not be started"},
23
+ -4 => {reason: :internal_error, status: -4, description: "The method panicked"},
24
+ -5 => {reason: :invalid_host, status: -5, description: "The address is not valid"}
25
+ }.freeze
26
+
27
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/mock_server/fn.pactffi_write_pact_file.html
28
+ WRITE_PACT_FILE_ERRORS = {
29
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
30
+ 2 => {reason: :file_not_accessible, status: 2, description: "The pact file was not able to be written"},
31
+ 3 => {reason: :mock_server_not_found, status: 3, description: "A mock server with the provided port was not found"}
32
+ }.freeze
33
+
34
+ def self.create_for_grpc!(pact:, host: "127.0.0.1", port: 0)
35
+ new(pact: pact, transport: TRANSPORT_GRPC, host: host, port: port)
36
+ end
37
+
38
+ def self.create_for_http!(pact:, host: "127.0.0.1", port: 0)
39
+ new(pact: pact, transport: TRANSPORT_HTTP, host: host, port: port)
40
+ end
41
+
42
+ def self.create_for_transport!(pact:, transport:, host: "127.0.0.1", port: 0)
43
+ new(pact: pact, transport: transport, host: host, port: port)
44
+ end
45
+
46
+ def initialize(pact:, transport:, host:, port:)
47
+
48
+ @pact = pact
49
+ @transport = transport
50
+ @host = host
51
+ @port = port
52
+
53
+ @handle = init_transport!
54
+ # the returned handle is the port number
55
+ # we set it here, so we can consume a port number of 0
56
+ # and allow pact to assign a random available port
57
+ @port = @handle
58
+ # construct the url for the mock server
59
+ # as a convenience for the user
60
+ @url = "#{transport}://#{host}:#{@handle}"
61
+ # TODO: handle auto-GC of native memory
62
+ # ObjectSpace.define_finalizer(self, proc do
63
+ # cleanup
64
+ # end)
65
+ end
66
+
67
+ def write_pacts!(dir)
68
+ result = PactFfi::MockServer.write_pact_file(@handle, dir, false)
69
+ return result if WRITE_PACT_FILE_ERRORS[result].blank?
70
+
71
+ error = WRITE_PACT_FILE_ERRORS[result]
72
+ raise WritePactsError.new("There was an error while trying to write pact file to #{dir}", error[:reason], error[:status])
73
+ end
74
+
75
+ def matched?
76
+ PactFfi::MockServer.matched(@handle)
77
+ end
78
+
79
+ def mismatches
80
+ PactFfi::MockServer.mismatches(@handle)
81
+ end
82
+
83
+ def cleanup
84
+ PactFfi::MockServer.cleanup(@handle)
85
+ end
86
+
87
+ private
88
+
89
+ def init_transport!
90
+ handle = PactFfi::MockServer.create_for_transport(@pact, @host, @port, @transport, nil)
91
+ # the returned handle is the port number
92
+ return handle if CREATE_TRANSPORT_ERRORS[handle].blank?
93
+
94
+ error = CREATE_TRANSPORT_ERRORS[handle]
95
+ raise MockServerCreateError.new("There was an error while trying to create mock server for transport:#{@transport}", error[:reason], error[:status])
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pact
4
+ module V2
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] || (defined?(Rails) ? Rails.root.join("../pacts").to_s : "pacts")
14
+ @log_level = opts[:log_level] || :info
15
+ end
16
+
17
+ def new_interaction(description = nil)
18
+ raise Pact::V2::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 Pact
6
+ module V2
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,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Pact
6
+ module V2
7
+ module Consumer
8
+ module PactConfig
9
+ class Http < Base
10
+ attr_reader :mock_host, :mock_port, :pact_handle
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] || 0
17
+ @log_level = opts[:log_level] || :info
18
+ @pact_specification = get_pact_specification(opts)
19
+ @pact_handle = init_pact
20
+ end
21
+
22
+ def new_interaction(description = nil)
23
+ HttpInteractionBuilder.new(self, description: description)
24
+ end
25
+
26
+ def reset_pact
27
+ @pact_handle = init_pact
28
+ end
29
+
30
+ def get_pact_specification(opts)
31
+ pact_spec_version = opts[:pact_specification] || "V4"
32
+ unless pact_spec_version.match?(/^v?[1-4](\.\d+){0,2}$/i)
33
+ raise ArgumentError, "Invalid pact specification version format \n Valid versions are 1, 1.1, 2, 3, 4. Default is V4 \n V prefix is optional, and case insensitive"
34
+ end
35
+ pact_spec_version = pact_spec_version.upcase
36
+ pact_spec_version = "V#{pact_spec_version}" unless pact_spec_version.start_with?("V")
37
+ pact_spec_version = pact_spec_version.sub(/(\.0+)+$/, "")
38
+ pact_spec_version = pact_spec_version.tr(".", "_")
39
+ PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_#{pact_spec_version.upcase}"]
40
+ end
41
+
42
+ def init_pact
43
+ handle = PactFfi.new_pact(consumer_name, provider_name)
44
+ PactFfi.with_specification(handle, @pact_specification)
45
+ PactFfi.with_pact_metadata(handle, "pact-ruby-v2", "pact-ffi", PactFfi.version)
46
+
47
+ Pact::V2::Native::Logger.log_to_stdout(@log_level)
48
+
49
+ handle
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Pact
6
+ module V2
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,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Pact
6
+ module V2
7
+ module Consumer
8
+ module PactConfig
9
+ class PluginAsyncMessage < 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] || 0
17
+ end
18
+
19
+ def new_interaction(description = nil)
20
+ PluginAsyncMessageInteractionBuilder.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 Pact
6
+ module V2
7
+ module Consumer
8
+ module PactConfig
9
+ class PluginHttp < 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] || 0
17
+ end
18
+
19
+ def new_interaction(description = nil)
20
+ PluginHttpInteractionBuilder.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 Pact
6
+ module V2
7
+ module Consumer
8
+ module PactConfig
9
+ class PluginSyncMessage < 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] || 0
17
+ end
18
+
19
+ def new_interaction(description = nil)
20
+ PluginSyncMessageInteractionBuilder.new(self, description: description)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pact_config/grpc"
4
+
5
+ module Pact
6
+ module V2
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
+ when :plugin_sync_message
18
+ PluginSyncMessage.new(consumer_name: consumer_name, provider_name: provider_name, opts: opts)
19
+ when :plugin_async_message
20
+ PluginAsyncMessage.new(consumer_name: consumer_name, provider_name: provider_name, opts: opts)
21
+ when :plugin_http
22
+ PluginHttp.new(consumer_name: consumer_name, provider_name: provider_name, opts: opts)
23
+ else
24
+ raise ArgumentError, "unknown transport_type: #{transport_type}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end