pact 1.67.5 → 2.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/pact/configuration.rb +21 -0
- data/lib/pact/consumer/grpc_interaction_builder.rb +192 -0
- data/lib/pact/consumer/http_interaction_builder.rb +160 -0
- data/lib/pact/consumer/interaction_contents.rb +62 -0
- data/lib/pact/consumer/message_interaction_builder.rb +286 -0
- data/lib/pact/consumer/mock_server.rb +106 -0
- data/lib/pact/consumer/pact_config/base.rb +22 -0
- data/lib/pact/consumer/pact_config/grpc.rb +24 -0
- data/lib/pact/consumer/pact_config/http.rb +53 -0
- data/lib/pact/consumer/pact_config/message.rb +15 -0
- data/lib/pact/consumer/pact_config/plugin_async_message.rb +24 -0
- data/lib/pact/consumer/pact_config/plugin_http.rb +24 -0
- data/lib/pact/consumer/pact_config/plugin_sync_message.rb +24 -0
- data/lib/pact/consumer/pact_config.rb +28 -0
- data/lib/pact/consumer/plugin_async_message_interaction_builder.rb +169 -0
- data/lib/pact/consumer/plugin_http_interaction_builder.rb +199 -0
- data/lib/pact/consumer/plugin_sync_message_interaction_builder.rb +178 -0
- data/lib/pact/consumer.rb +6 -7
- data/lib/pact/generators/base.rb +285 -0
- data/lib/pact/generators.rb +47 -0
- data/lib/pact/matchers/base.rb +72 -0
- data/lib/pact/matchers/v1/equality.rb +17 -0
- data/lib/pact/matchers/v2/regex.rb +26 -0
- data/lib/pact/matchers/v2/type.rb +18 -0
- data/lib/pact/matchers/v3/boolean.rb +18 -0
- data/lib/pact/matchers/v3/content_type.rb +30 -0
- data/lib/pact/matchers/v3/date.rb +22 -0
- data/lib/pact/matchers/v3/date_time.rb +22 -0
- data/lib/pact/matchers/v3/decimal.rb +18 -0
- data/lib/pact/matchers/v3/each.rb +46 -0
- data/lib/pact/matchers/v3/include.rb +18 -0
- data/lib/pact/matchers/v3/integer.rb +18 -0
- data/lib/pact/matchers/v3/null.rb +13 -0
- data/lib/pact/matchers/v3/number.rb +18 -0
- data/lib/pact/matchers/v3/semver.rb +22 -0
- data/lib/pact/matchers/v3/time.rb +22 -0
- data/lib/pact/matchers/v3/values.rb +13 -0
- data/lib/pact/matchers/v4/each_key.rb +34 -0
- data/lib/pact/matchers/v4/each_key_value.rb +38 -0
- data/lib/pact/matchers/v4/each_value.rb +40 -0
- data/lib/pact/matchers/v4/not_empty.rb +22 -0
- data/lib/pact/matchers/v4/status_code.rb +15 -0
- data/lib/pact/matchers.rb +108 -0
- data/lib/pact/native/blocking_verifier.rb +15 -0
- data/lib/pact/native/logger.rb +24 -0
- data/lib/pact/provider/async_message_verifier.rb +29 -0
- data/lib/pact/provider/base_verifier.rb +259 -0
- data/lib/pact/provider/grpc_verifier.rb +40 -0
- data/lib/pact/provider/gruf_server.rb +73 -0
- data/lib/pact/provider/http_server.rb +77 -0
- data/lib/pact/provider/http_verifier.rb +45 -0
- data/lib/pact/provider/message_provider_servlet.rb +77 -0
- data/lib/pact/provider/mixed_verifier.rb +21 -0
- data/lib/pact/provider/pact_broker_proxy.rb +62 -0
- data/lib/pact/provider/pact_broker_proxy_runner.rb +78 -0
- data/lib/pact/provider/pact_config/async.rb +28 -0
- data/lib/pact/provider/pact_config/base.rb +101 -0
- data/lib/pact/provider/pact_config/grpc.rb +24 -0
- data/lib/pact/provider/pact_config/http.rb +25 -0
- data/lib/pact/provider/pact_config/mixed.rb +38 -0
- data/lib/pact/provider/pact_config.rb +24 -0
- data/lib/pact/provider/provider_server_runner.rb +90 -0
- data/lib/pact/provider/provider_state_configuration.rb +32 -0
- data/lib/pact/provider/provider_state_servlet.rb +84 -0
- data/lib/pact/provider.rb +6 -3
- data/lib/pact/railtie.rb +11 -0
- data/lib/pact/{v2/rspec → rspec}/support/pact_consumer_helpers.rb +8 -8
- data/lib/pact/{v2/rspec → rspec}/support/pact_provider_helpers.rb +25 -24
- data/lib/pact/{v2/rspec → rspec}/support/webmock/webmock_helpers.rb +1 -1
- data/lib/pact/{v2/rspec.rb → rspec.rb} +6 -5
- data/lib/pact/support/core_ext.rb +93 -0
- data/lib/pact/tasks/pact.rake +13 -0
- data/lib/pact/version.rb +1 -1
- data/lib/pact.rb +52 -11
- data/pact.gemspec +47 -64
- metadata +146 -404
- data/lib/pact/cli/generate_pact_docs.rb +0 -4
- data/lib/pact/cli/run_pact_verification.rb +0 -99
- data/lib/pact/cli/spec_criteria.rb +0 -26
- data/lib/pact/cli.rb +0 -45
- data/lib/pact/consumer/configuration/configuration_extensions.rb +0 -90
- data/lib/pact/consumer/configuration/dsl.rb +0 -11
- data/lib/pact/consumer/configuration/mock_service.rb +0 -112
- data/lib/pact/consumer/configuration/service_consumer.rb +0 -51
- data/lib/pact/consumer/configuration/service_provider.rb +0 -40
- data/lib/pact/consumer/configuration.rb +0 -10
- data/lib/pact/consumer/consumer_contract_builder.rb +0 -82
- data/lib/pact/consumer/consumer_contract_builders.rb +0 -10
- data/lib/pact/consumer/interaction_builder.rb +0 -45
- data/lib/pact/consumer/rspec.rb +0 -35
- data/lib/pact/consumer/spec_hooks.rb +0 -40
- data/lib/pact/consumer/world.rb +0 -37
- data/lib/pact/doc/README.md +0 -13
- data/lib/pact/doc/doc_file.rb +0 -40
- data/lib/pact/doc/generate.rb +0 -11
- data/lib/pact/doc/generator.rb +0 -82
- data/lib/pact/doc/interaction_view_model.rb +0 -124
- data/lib/pact/doc/markdown/consumer_contract_renderer.rb +0 -68
- data/lib/pact/doc/markdown/generator.rb +0 -26
- data/lib/pact/doc/markdown/index_renderer.rb +0 -43
- data/lib/pact/doc/markdown/interaction.erb +0 -14
- data/lib/pact/doc/markdown/interaction_renderer.rb +0 -43
- data/lib/pact/doc/sort_interactions.rb +0 -16
- data/lib/pact/hal/authorization_header_redactor.rb +0 -32
- data/lib/pact/hal/entity.rb +0 -110
- data/lib/pact/hal/http_client.rb +0 -146
- data/lib/pact/hal/link.rb +0 -112
- data/lib/pact/hal/non_json_entity.rb +0 -28
- data/lib/pact/hash_refinements.rb +0 -17
- data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +0 -112
- data/lib/pact/pact_broker/fetch_pacts.rb +0 -103
- data/lib/pact/pact_broker/notices.rb +0 -34
- data/lib/pact/pact_broker/pact_selection_description.rb +0 -66
- data/lib/pact/pact_broker.rb +0 -25
- data/lib/pact/project_root.rb +0 -7
- data/lib/pact/provider/configuration/configuration_extension.rb +0 -69
- data/lib/pact/provider/configuration/dsl.rb +0 -18
- data/lib/pact/provider/configuration/message_provider_dsl.rb +0 -63
- data/lib/pact/provider/configuration/pact_verification.rb +0 -48
- data/lib/pact/provider/configuration/pact_verification_from_broker.rb +0 -126
- data/lib/pact/provider/configuration/service_provider_config.rb +0 -32
- data/lib/pact/provider/configuration/service_provider_dsl.rb +0 -107
- data/lib/pact/provider/configuration.rb +0 -7
- data/lib/pact/provider/context.rb +0 -0
- data/lib/pact/provider/help/console_text.rb +0 -76
- data/lib/pact/provider/help/content.rb +0 -38
- data/lib/pact/provider/help/pact_diff.rb +0 -43
- data/lib/pact/provider/help/prompt_text.rb +0 -49
- data/lib/pact/provider/help/write.rb +0 -56
- data/lib/pact/provider/matchers/messages.rb +0 -66
- data/lib/pact/provider/pact_helper_locator.rb +0 -24
- data/lib/pact/provider/pact_source.rb +0 -40
- data/lib/pact/provider/pact_spec_runner.rb +0 -188
- data/lib/pact/provider/pact_uri.rb +0 -55
- data/lib/pact/provider/pact_verification.rb +0 -17
- data/lib/pact/provider/print_missing_provider_states.rb +0 -35
- data/lib/pact/provider/request.rb +0 -90
- data/lib/pact/provider/rspec/backtrace_formatter.rb +0 -43
- data/lib/pact/provider/rspec/calculate_exit_code.rb +0 -18
- data/lib/pact/provider/rspec/custom_options_file +0 -0
- data/lib/pact/provider/rspec/formatter_rspec_2.rb +0 -76
- data/lib/pact/provider/rspec/formatter_rspec_3.rb +0 -195
- data/lib/pact/provider/rspec/json_formatter.rb +0 -100
- data/lib/pact/provider/rspec/matchers.rb +0 -80
- data/lib/pact/provider/rspec/pact_broker_formatter.rb +0 -76
- data/lib/pact/provider/rspec.rb +0 -234
- data/lib/pact/provider/state/provider_state.rb +0 -180
- data/lib/pact/provider/state/provider_state_configured_modules.rb +0 -15
- data/lib/pact/provider/state/provider_state_manager.rb +0 -42
- data/lib/pact/provider/state/provider_state_proxy.rb +0 -39
- data/lib/pact/provider/state/set_up.rb +0 -13
- data/lib/pact/provider/state/tear_down.rb +0 -13
- data/lib/pact/provider/test_methods.rb +0 -77
- data/lib/pact/provider/verification_report.rb +0 -36
- data/lib/pact/provider/verification_results/create.rb +0 -88
- data/lib/pact/provider/verification_results/publish.rb +0 -143
- data/lib/pact/provider/verification_results/publish_all.rb +0 -50
- data/lib/pact/provider/verification_results/verification_result.rb +0 -40
- data/lib/pact/provider/world.rb +0 -50
- data/lib/pact/retry.rb +0 -37
- data/lib/pact/tasks/task_helper.rb +0 -62
- data/lib/pact/tasks/verification_task.rb +0 -95
- data/lib/pact/tasks.rb +0 -2
- data/lib/pact/templates/help.erb +0 -22
- data/lib/pact/templates/provider_state.erb +0 -14
- data/lib/pact/utils/metrics.rb +0 -100
- data/lib/pact/utils/string.rb +0 -35
- data/lib/pact/v2/configuration.rb +0 -23
- data/lib/pact/v2/consumer/grpc_interaction_builder.rb +0 -194
- data/lib/pact/v2/consumer/http_interaction_builder.rb +0 -162
- data/lib/pact/v2/consumer/interaction_contents.rb +0 -62
- data/lib/pact/v2/consumer/message_interaction_builder.rb +0 -288
- data/lib/pact/v2/consumer/mock_server.rb +0 -108
- data/lib/pact/v2/consumer/pact_config/base.rb +0 -24
- data/lib/pact/v2/consumer/pact_config/grpc.rb +0 -26
- data/lib/pact/v2/consumer/pact_config/http.rb +0 -55
- data/lib/pact/v2/consumer/pact_config/message.rb +0 -17
- data/lib/pact/v2/consumer/pact_config/plugin_async_message.rb +0 -26
- data/lib/pact/v2/consumer/pact_config/plugin_http.rb +0 -26
- data/lib/pact/v2/consumer/pact_config/plugin_sync_message.rb +0 -26
- data/lib/pact/v2/consumer/pact_config.rb +0 -30
- data/lib/pact/v2/consumer/plugin_async_message_interaction_builder.rb +0 -171
- data/lib/pact/v2/consumer/plugin_http_interaction_builder.rb +0 -201
- data/lib/pact/v2/consumer/plugin_sync_message_interaction_builder.rb +0 -180
- data/lib/pact/v2/consumer.rb +0 -8
- data/lib/pact/v2/generators/base.rb +0 -287
- data/lib/pact/v2/generators.rb +0 -49
- data/lib/pact/v2/matchers/base.rb +0 -74
- data/lib/pact/v2/matchers/v1/equality.rb +0 -19
- data/lib/pact/v2/matchers/v2/regex.rb +0 -19
- data/lib/pact/v2/matchers/v2/type.rb +0 -17
- data/lib/pact/v2/matchers/v3/boolean.rb +0 -17
- data/lib/pact/v2/matchers/v3/content_type.rb +0 -32
- data/lib/pact/v2/matchers/v3/date.rb +0 -18
- data/lib/pact/v2/matchers/v3/date_time.rb +0 -18
- data/lib/pact/v2/matchers/v3/decimal.rb +0 -17
- data/lib/pact/v2/matchers/v3/each.rb +0 -42
- data/lib/pact/v2/matchers/v3/include.rb +0 -17
- data/lib/pact/v2/matchers/v3/integer.rb +0 -17
- data/lib/pact/v2/matchers/v3/null.rb +0 -16
- data/lib/pact/v2/matchers/v3/number.rb +0 -17
- data/lib/pact/v2/matchers/v3/semver.rb +0 -23
- data/lib/pact/v2/matchers/v3/time.rb +0 -18
- data/lib/pact/v2/matchers/v3/values.rb +0 -16
- data/lib/pact/v2/matchers/v4/each_key.rb +0 -26
- data/lib/pact/v2/matchers/v4/each_key_value.rb +0 -32
- data/lib/pact/v2/matchers/v4/each_value.rb +0 -33
- data/lib/pact/v2/matchers/v4/not_empty.rb +0 -24
- data/lib/pact/v2/matchers/v4/status_code.rb +0 -17
- data/lib/pact/v2/matchers.rb +0 -110
- data/lib/pact/v2/native/blocking_verifier.rb +0 -17
- data/lib/pact/v2/native/logger.rb +0 -25
- data/lib/pact/v2/provider/async_message_verifier.rb +0 -28
- data/lib/pact/v2/provider/base_verifier.rb +0 -242
- data/lib/pact/v2/provider/grpc_verifier.rb +0 -38
- data/lib/pact/v2/provider/gruf_server.rb +0 -75
- data/lib/pact/v2/provider/http_server.rb +0 -79
- data/lib/pact/v2/provider/http_verifier.rb +0 -43
- data/lib/pact/v2/provider/message_provider_servlet.rb +0 -79
- data/lib/pact/v2/provider/mixed_verifier.rb +0 -22
- data/lib/pact/v2/provider/pact_broker_proxy.rb +0 -66
- data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +0 -80
- data/lib/pact/v2/provider/pact_config/async.rb +0 -29
- data/lib/pact/v2/provider/pact_config/base.rb +0 -103
- data/lib/pact/v2/provider/pact_config/grpc.rb +0 -26
- data/lib/pact/v2/provider/pact_config/http.rb +0 -27
- data/lib/pact/v2/provider/pact_config/mixed.rb +0 -40
- data/lib/pact/v2/provider/pact_config.rb +0 -26
- data/lib/pact/v2/provider/provider_server_runner.rb +0 -92
- data/lib/pact/v2/provider/provider_state_configuration.rb +0 -32
- data/lib/pact/v2/provider/provider_state_servlet.rb +0 -86
- data/lib/pact/v2/provider.rb +0 -8
- data/lib/pact/v2/railtie.rb +0 -13
- data/lib/pact/v2/tasks/pact.rake +0 -13
- data/lib/pact/v2.rb +0 -71
- data/lib/tasks/pact.rake +0 -34
- /data/lib/pact/{v2/rspec → rspec}/support/pact_message_helpers.rb +0 -0
- /data/lib/pact/{v2/rspec → rspec}/support/waterdrop/pact_waterdrop_client.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ae64ff5080cc286c9a913c8ad1e06d99c35961f687eabe3e99c5bcaac6078aeb
|
|
4
|
+
data.tar.gz: 8fdbd4c74eb3e5c2cf57300ce269cb7e48f52fb83e5886f8e7aee08e97274d9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b01cb166524d3fdc62d7a34ecacd922281d63885cb4cc48cc4ba3d4d5007511d5b2263b6697060cc443fda3efcdec45d2d54d182e6b9f1e9a9d9f3ac593cb36
|
|
7
|
+
data.tar.gz: acbf67bc8d3b23c85fbbcda46cdbf29297fd2eb859430536d58537ff055fb64ac4d6f9c28d6fee78324ebd33b9e534c6dd2e26dd454376dea9b8ea8aa28eb7e6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
<a name="v2.0.1"></a>
|
|
2
|
+
### v2.0.1 (2026-05-29)
|
|
3
|
+
|
|
4
|
+
#### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **example**
|
|
7
|
+
* remove combustion dependency from examples ([281b284](/../../commit/281b284))
|
|
8
|
+
|
|
9
|
+
* align core_ext polyfills more closely with active_support implementations ([f08b8ce](/../../commit/f08b8ce))
|
|
10
|
+
* correct Object#blank? and Object#present? polyfill implementations ([53580b9](/../../commit/53580b9))
|
|
11
|
+
* add deep_dup polyfill alongside blank?/present? ([b116e14](/../../commit/b116e14))
|
|
12
|
+
* remove hard requirements on active_support and webmock ([e77cd34](/../../commit/e77cd34))
|
|
13
|
+
* support scalar request and response bodies in InteractionContents ([5440ed1](/../../commit/5440ed1))
|
|
14
|
+
|
|
15
|
+
<a name="v2.0.0"></a>
|
|
16
|
+
### v2.0.0 (2026-03-18)
|
|
17
|
+
|
|
18
|
+
#### Features
|
|
19
|
+
|
|
20
|
+
* **v2**
|
|
21
|
+
* move pact/v2 to pact namespace (#397) ([1cc97ec](/../../commit/1cc97ec))
|
|
22
|
+
|
|
1
23
|
<a name="v1.67.5"></a>
|
|
2
24
|
### v1.67.5 (2026-02-13)
|
|
3
25
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_reader :before_provider_state_proc, :after_provider_state_proc
|
|
6
|
+
|
|
7
|
+
class GlobalProviderConfigurationError < ::Pact::Error; end
|
|
8
|
+
|
|
9
|
+
def before_provider_state_setup(&block)
|
|
10
|
+
raise GlobalProviderConfigurationError, 'no block given' unless block
|
|
11
|
+
|
|
12
|
+
@before_provider_state_proc = block
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def after_provider_state_teardown(&block)
|
|
16
|
+
raise GlobalProviderConfigurationError, 'no block given' unless block
|
|
17
|
+
|
|
18
|
+
@after_provider_state_proc = block
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
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 Consumer
|
|
9
|
+
class GrpcInteractionBuilder
|
|
10
|
+
CONTENT_TYPE = "application/protobuf"
|
|
11
|
+
GRPC_CONTENT_TYPE = "application/grpc"
|
|
12
|
+
PROTOBUF_PLUGIN_NAME = "protobuf"
|
|
13
|
+
PROTOBUF_PLUGIN_VERSION = "0.6.5"
|
|
14
|
+
|
|
15
|
+
class PluginInitError < Pact::FfiError; end
|
|
16
|
+
|
|
17
|
+
# https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_using_plugin.html
|
|
18
|
+
INIT_PLUGIN_ERRORS = {
|
|
19
|
+
1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
|
|
20
|
+
2 => {reason: :plugin_load_failed, status: 2, description: "Failed to load the plugin"},
|
|
21
|
+
3 => {reason: :invalid_handle, status: 3, description: "Pact Handle is not valid"}
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
|
|
25
|
+
CREATE_INTERACTION_ERRORS = {
|
|
26
|
+
1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
|
|
27
|
+
2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
|
|
28
|
+
3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
|
|
29
|
+
4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
|
|
30
|
+
5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
|
|
31
|
+
6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
34
|
+
class CreateInteractionError < Pact::FfiError; end
|
|
35
|
+
|
|
36
|
+
class InteractionMismatchesError < Pact::Error; end
|
|
37
|
+
|
|
38
|
+
class InteractionBuilderError < Pact::Error; end
|
|
39
|
+
|
|
40
|
+
def initialize(pact_config, description: nil)
|
|
41
|
+
@pact_config = pact_config
|
|
42
|
+
@description = description || ""
|
|
43
|
+
@proto_path = nil
|
|
44
|
+
@proto_include_dirs = []
|
|
45
|
+
@service_name = nil
|
|
46
|
+
@method_name = nil
|
|
47
|
+
@request = nil
|
|
48
|
+
@response = nil
|
|
49
|
+
@response_meta = nil
|
|
50
|
+
@provider_state_meta = nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def with_service(proto_path, method, include_dirs = [])
|
|
54
|
+
raise InteractionBuilderError.new("invalid grpc method: cannot be blank") if method.blank?
|
|
55
|
+
|
|
56
|
+
service_name, method_name = method.split("/") || []
|
|
57
|
+
raise InteractionBuilderError.new("invalid grpc method: #{method}, should be like service/SomeMethod") if service_name.blank? || method_name.blank?
|
|
58
|
+
|
|
59
|
+
absolute_path = File.expand_path(proto_path)
|
|
60
|
+
raise InteractionBuilderError.new("proto file #{proto_path} does not exist") unless File.exist?(absolute_path)
|
|
61
|
+
|
|
62
|
+
@proto_path = absolute_path
|
|
63
|
+
@service_name = service_name
|
|
64
|
+
@method_name = method_name
|
|
65
|
+
@proto_include_dirs = include_dirs.map { |dir| File.expand_path(dir) }
|
|
66
|
+
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def with_pact_protobuf_plugin_version(version)
|
|
71
|
+
raise InteractionBuilderError.new("version is required") if version.blank?
|
|
72
|
+
|
|
73
|
+
@proto_plugin_version = version
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def given(provider_state, metadata = {})
|
|
78
|
+
@provider_state_meta = {provider_state => metadata}
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def upon_receiving(description)
|
|
83
|
+
@description = description
|
|
84
|
+
self
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def with_request(req_hash)
|
|
88
|
+
@request = InteractionContents.plugin(req_hash)
|
|
89
|
+
self
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def will_respond_with(resp_hash)
|
|
93
|
+
@response = InteractionContents.plugin(resp_hash)
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def will_respond_with_meta(meta_hash)
|
|
98
|
+
@response_meta = InteractionContents.plugin(meta_hash)
|
|
99
|
+
self
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def interaction_json
|
|
103
|
+
result = {
|
|
104
|
+
"pact:proto": @proto_path,
|
|
105
|
+
"pact:proto-service": "#{@service_name}/#{@method_name}",
|
|
106
|
+
"pact:content-type": CONTENT_TYPE,
|
|
107
|
+
request: @request
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
result["pact:protobuf-config"] = {additionalIncludes: @proto_include_dirs} if @proto_include_dirs.present?
|
|
111
|
+
|
|
112
|
+
result[:response] = @response if @response.is_a?(Hash)
|
|
113
|
+
result[:responseMetadata] = @response_meta if @response_meta.is_a?(Hash)
|
|
114
|
+
|
|
115
|
+
JSON.dump(result)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def validate!
|
|
119
|
+
raise InteractionBuilderError.new("uninitialized service params, use #with_service to configure") if @proto_path.blank? || @service_name.blank? || @method_name.blank?
|
|
120
|
+
raise InteractionBuilderError.new("invalid request format, should be a hash") unless @request.is_a?(Hash)
|
|
121
|
+
raise InteractionBuilderError.new("invalid response format, should be a hash") unless @response.is_a?(Hash) || @response_meta.is_a?(Hash)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def execute(&block)
|
|
125
|
+
raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
|
|
126
|
+
|
|
127
|
+
validate!
|
|
128
|
+
|
|
129
|
+
pact_handle = init_pact
|
|
130
|
+
init_plugin!(pact_handle)
|
|
131
|
+
|
|
132
|
+
message_pact = PactFfi::SyncMessageConsumer.new_interaction(pact_handle, @description)
|
|
133
|
+
@provider_state_meta&.each_pair do |provider_state, meta|
|
|
134
|
+
if meta.present?
|
|
135
|
+
meta.each_pair { |k, v| PactFfi.given_with_param(message_pact, provider_state, k.to_s, v.to_s) }
|
|
136
|
+
else
|
|
137
|
+
PactFfi.given(message_pact, provider_state)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
result = PactFfi::PluginConsumer.interaction_contents(message_pact, 0, GRPC_CONTENT_TYPE, interaction_json)
|
|
142
|
+
if CREATE_INTERACTION_ERRORS[result].present?
|
|
143
|
+
error = CREATE_INTERACTION_ERRORS[result]
|
|
144
|
+
raise CreateInteractionError.new("There was an error while trying to add interaction \"#{@description}\"", error[:reason], error[:status])
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
mock_server = MockServer.create_for_grpc!(pact: pact_handle, host: @pact_config.mock_host, port: @pact_config.mock_port)
|
|
148
|
+
|
|
149
|
+
yield(message_pact, mock_server)
|
|
150
|
+
|
|
151
|
+
ensure
|
|
152
|
+
if mock_server.matched?
|
|
153
|
+
mock_server.write_pacts!(@pact_config.pact_dir)
|
|
154
|
+
else
|
|
155
|
+
msg = mismatches_error_msg(mock_server)
|
|
156
|
+
raise InteractionMismatchesError.new(msg)
|
|
157
|
+
end
|
|
158
|
+
@used = true
|
|
159
|
+
mock_server&.cleanup
|
|
160
|
+
PactFfi::PluginConsumer.cleanup_plugins(pact_handle)
|
|
161
|
+
PactFfi.free_pact_handle(pact_handle)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
def mismatches_error_msg(mock_server)
|
|
167
|
+
rspec_example_desc = RSpec.current_example&.description
|
|
168
|
+
return "interaction for #{@service_name}/#{@method_name} has mismatches: #{mock_server.mismatches}" if rspec_example_desc.blank?
|
|
169
|
+
|
|
170
|
+
"#{rspec_example_desc} has mismatches: #{mock_server.mismatches}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def init_pact
|
|
174
|
+
handle = PactFfi.new_pact(@pact_config.consumer_name, @pact_config.provider_name)
|
|
175
|
+
PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_V4"])
|
|
176
|
+
PactFfi.with_pact_metadata(handle, "pact-ruby", "pact-ffi", PactFfi.version)
|
|
177
|
+
|
|
178
|
+
Pact::Native::Logger.log_to_stdout(@pact_config.log_level)
|
|
179
|
+
|
|
180
|
+
handle
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def init_plugin!(pact_handle)
|
|
184
|
+
result = PactFfi::PluginConsumer.using_plugin(pact_handle, PROTOBUF_PLUGIN_NAME, @proto_plugin_version || PROTOBUF_PLUGIN_VERSION)
|
|
185
|
+
return result if INIT_PLUGIN_ERRORS[result].blank?
|
|
186
|
+
|
|
187
|
+
error = INIT_PLUGIN_ERRORS[result]
|
|
188
|
+
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])
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
require "json"
|
|
7
|
+
|
|
8
|
+
module Pact
|
|
9
|
+
module Consumer
|
|
10
|
+
class HttpInteractionBuilder
|
|
11
|
+
|
|
12
|
+
# https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
|
|
13
|
+
CREATE_INTERACTION_ERRORS = {
|
|
14
|
+
1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
|
|
15
|
+
2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
|
|
16
|
+
3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
|
|
17
|
+
4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
|
|
18
|
+
5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
|
|
19
|
+
6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
class CreateInteractionError < Pact::FfiError; end
|
|
23
|
+
|
|
24
|
+
class InteractionMismatchesError < Pact::Error; end
|
|
25
|
+
|
|
26
|
+
class InteractionBuilderError < Pact::Error; end
|
|
27
|
+
|
|
28
|
+
class << self
|
|
29
|
+
def create_finalizer(pact_handle)
|
|
30
|
+
proc { PactFfi.free_pact_handle(pact_handle) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize(pact_config, description: nil)
|
|
35
|
+
@pact_config = pact_config
|
|
36
|
+
@description = description || ""
|
|
37
|
+
|
|
38
|
+
@pact_handle = pact_config.pact_handle ||= init_pact
|
|
39
|
+
@pact_interaction = PactFfi.new_interaction(pact_handle, @description)
|
|
40
|
+
|
|
41
|
+
ObjectSpace.define_finalizer(self, self.class.create_finalizer(pact_interaction))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def given(provider_state, metadata = {})
|
|
45
|
+
if metadata.present?
|
|
46
|
+
PactFfi.given_with_params(pact_interaction, provider_state, JSON.dump(metadata))
|
|
47
|
+
else
|
|
48
|
+
PactFfi.given(pact_interaction, provider_state)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def upon_receiving(description)
|
|
55
|
+
@description = description
|
|
56
|
+
PactFfi.upon_receiving(pact_interaction, @description)
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def with_request(method: nil, path: nil, query: {}, headers: {}, body: nil)
|
|
61
|
+
interaction_part = PactFfi::FfiInteractionPart["INTERACTION_PART_REQUEST"]
|
|
62
|
+
PactFfi.with_request(pact_interaction, method.to_s, format_value(path))
|
|
63
|
+
|
|
64
|
+
# Processing as an array of hashes, allows us to consider duplicate keys
|
|
65
|
+
# which should be passed to the core, at a non 0 index
|
|
66
|
+
if query.is_a?(Array)
|
|
67
|
+
key_index = Hash.new(0)
|
|
68
|
+
query.each do |query_item|
|
|
69
|
+
InteractionContents.basic(query_item).each_pair do |key, value_item|
|
|
70
|
+
PactFfi.with_query_parameter_v2(pact_interaction, key.to_s, key_index[key], format_value(value_item))
|
|
71
|
+
key_index[key] += 1
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
else
|
|
75
|
+
InteractionContents.basic(query).each_pair do |key, value_item|
|
|
76
|
+
PactFfi.with_query_parameter_v2(pact_interaction, key.to_s, 0, format_value(value_item))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
InteractionContents.basic(headers).each_pair do |key, value_item|
|
|
81
|
+
PactFfi.with_header_v2(pact_interaction, interaction_part, key.to_s, 0, format_value(value_item))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if body
|
|
85
|
+
PactFfi.with_body(pact_interaction, interaction_part, "application/json", format_value(InteractionContents.basic(body).value))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def will_respond_with(status: nil, headers: {}, body: nil)
|
|
92
|
+
interaction_part = PactFfi::FfiInteractionPart["INTERACTION_PART_RESPONSE"]
|
|
93
|
+
PactFfi.response_status(pact_interaction, status)
|
|
94
|
+
|
|
95
|
+
InteractionContents.basic(headers).each_pair do |key, value_item|
|
|
96
|
+
PactFfi.with_header_v2(pact_interaction, interaction_part, key.to_s, 0, format_value(value_item))
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
if body
|
|
100
|
+
PactFfi.with_body(pact_interaction, interaction_part, "application/json", format_value(InteractionContents.basic(body).value))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def execute(&block)
|
|
107
|
+
raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
|
|
108
|
+
|
|
109
|
+
mock_server = MockServer.create_for_http!(
|
|
110
|
+
pact: pact_handle, host: pact_config.mock_host, port: pact_config.mock_port
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
yield(mock_server)
|
|
114
|
+
|
|
115
|
+
ensure
|
|
116
|
+
if mock_server.matched?
|
|
117
|
+
mock_server.write_pacts!(pact_config.pact_dir)
|
|
118
|
+
else
|
|
119
|
+
msg = mismatches_error_msg(mock_server)
|
|
120
|
+
raise InteractionMismatchesError.new(msg)
|
|
121
|
+
end
|
|
122
|
+
@used = true
|
|
123
|
+
mock_server&.cleanup
|
|
124
|
+
# Reset the pact handle to allow for a new interaction to be built
|
|
125
|
+
# without previous interactions being included
|
|
126
|
+
@pact_config.reset_pact
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
attr_reader :pact_handle, :pact_interaction, :pact_config
|
|
132
|
+
|
|
133
|
+
def mismatches_error_msg(mock_server)
|
|
134
|
+
rspec_example_desc = RSpec.current_example&.description
|
|
135
|
+
mismatches = JSON.pretty_generate(JSON.parse(mock_server.mismatches))
|
|
136
|
+
mismatches_with_colored_keys = mismatches.gsub(/"([^"]+)":/) { |match| "\e[34m#{match}\e[0m" } # Blue keys / white values
|
|
137
|
+
|
|
138
|
+
"#{rspec_example_desc} has mismatches: #{mismatches_with_colored_keys}"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def init_pact
|
|
142
|
+
handle = PactFfi.new_pact(pact_config.consumer_name, pact_config.provider_name)
|
|
143
|
+
PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_#{pact_config.pact_specification}"])
|
|
144
|
+
PactFfi.with_pact_metadata(handle, "pact-ruby", "pact-ffi", PactFfi.version)
|
|
145
|
+
|
|
146
|
+
Pact::Native::Logger.log_to_stdout(pact_config.log_level)
|
|
147
|
+
|
|
148
|
+
handle
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def format_value(obj)
|
|
152
|
+
return obj if obj.is_a?(String)
|
|
153
|
+
|
|
154
|
+
return JSON.dump({value: obj}) if obj.is_a?(Array)
|
|
155
|
+
|
|
156
|
+
JSON.dump(obj)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pact
|
|
4
|
+
module Consumer
|
|
5
|
+
class InteractionContents < Hash
|
|
6
|
+
BASIC_FORMAT = :basic
|
|
7
|
+
PLUGIN_FORMAT = :plugin
|
|
8
|
+
|
|
9
|
+
attr_reader :format
|
|
10
|
+
|
|
11
|
+
def self.basic(contents_hash)
|
|
12
|
+
new(contents_hash, BASIC_FORMAT)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.plugin(contents_hash)
|
|
16
|
+
new(contents_hash, PLUGIN_FORMAT)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(contents_hash, format)
|
|
20
|
+
serialized = init_hash(contents_hash, format)
|
|
21
|
+
# A scalar body (plain string, integer, etc.) serializes to a non-Hash
|
|
22
|
+
# value that cannot be merged pair by pair; expose it via #value instead.
|
|
23
|
+
if serialized.is_a?(Hash)
|
|
24
|
+
serialized.each_pair { |k, v| self[k] = v }
|
|
25
|
+
else
|
|
26
|
+
@value = serialized
|
|
27
|
+
end
|
|
28
|
+
@format = format
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def value
|
|
32
|
+
defined?(@value) ? @value : self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def serialize(hash, format)
|
|
38
|
+
# serialize recursively
|
|
39
|
+
if hash.is_a?(Pact::Matchers::Base) || hash.is_a?(Pact::Generators::Base)
|
|
40
|
+
return hash.as_basic if format == :basic
|
|
41
|
+
return hash.as_plugin if format == :plugin
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
return hash.map { |value| serialize(value, format) } if hash.is_a?(Array)
|
|
45
|
+
|
|
46
|
+
# A value that is not a collection or a matcher/generator has nothing to
|
|
47
|
+
# recurse into, so return it unchanged (string, integer, boolean, nil, ...).
|
|
48
|
+
return hash unless hash.is_a?(Hash)
|
|
49
|
+
|
|
50
|
+
hash.each_pair do |key, value|
|
|
51
|
+
hash[key] = serialize(value, format)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
hash
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def init_hash(hash, format)
|
|
58
|
+
serialize(hash.deep_dup, format)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|