pact 1.67.4 → 2.0.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.
Files changed (239) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -0
  3. data/lib/pact/configuration.rb +21 -0
  4. data/lib/pact/consumer/grpc_interaction_builder.rb +192 -0
  5. data/lib/pact/consumer/http_interaction_builder.rb +160 -0
  6. data/lib/pact/consumer/interaction_contents.rb +60 -0
  7. data/lib/pact/consumer/message_interaction_builder.rb +286 -0
  8. data/lib/pact/consumer/mock_server.rb +106 -0
  9. data/lib/pact/consumer/pact_config/base.rb +22 -0
  10. data/lib/pact/consumer/pact_config/grpc.rb +24 -0
  11. data/lib/pact/consumer/pact_config/http.rb +53 -0
  12. data/lib/pact/consumer/pact_config/message.rb +15 -0
  13. data/lib/pact/consumer/pact_config/plugin_async_message.rb +24 -0
  14. data/lib/pact/consumer/pact_config/plugin_http.rb +24 -0
  15. data/lib/pact/consumer/pact_config/plugin_sync_message.rb +24 -0
  16. data/lib/pact/consumer/pact_config.rb +28 -0
  17. data/lib/pact/consumer/plugin_async_message_interaction_builder.rb +169 -0
  18. data/lib/pact/consumer/plugin_http_interaction_builder.rb +199 -0
  19. data/lib/pact/consumer/plugin_sync_message_interaction_builder.rb +178 -0
  20. data/lib/pact/consumer.rb +6 -7
  21. data/lib/pact/generators/base.rb +285 -0
  22. data/lib/pact/generators.rb +47 -0
  23. data/lib/pact/matchers/base.rb +72 -0
  24. data/lib/pact/matchers/v1/equality.rb +17 -0
  25. data/lib/pact/matchers/v2/regex.rb +26 -0
  26. data/lib/pact/matchers/v2/type.rb +18 -0
  27. data/lib/pact/matchers/v3/boolean.rb +18 -0
  28. data/lib/pact/matchers/v3/content_type.rb +30 -0
  29. data/lib/pact/matchers/v3/date.rb +22 -0
  30. data/lib/pact/matchers/v3/date_time.rb +22 -0
  31. data/lib/pact/matchers/v3/decimal.rb +18 -0
  32. data/lib/pact/matchers/v3/each.rb +46 -0
  33. data/lib/pact/matchers/v3/include.rb +18 -0
  34. data/lib/pact/matchers/v3/integer.rb +18 -0
  35. data/lib/pact/matchers/v3/null.rb +13 -0
  36. data/lib/pact/matchers/v3/number.rb +18 -0
  37. data/lib/pact/matchers/v3/semver.rb +22 -0
  38. data/lib/pact/matchers/v3/time.rb +22 -0
  39. data/lib/pact/matchers/v3/values.rb +13 -0
  40. data/lib/pact/matchers/v4/each_key.rb +34 -0
  41. data/lib/pact/matchers/v4/each_key_value.rb +38 -0
  42. data/lib/pact/matchers/v4/each_value.rb +40 -0
  43. data/lib/pact/matchers/v4/not_empty.rb +22 -0
  44. data/lib/pact/matchers/v4/status_code.rb +15 -0
  45. data/lib/pact/matchers.rb +108 -0
  46. data/lib/pact/native/blocking_verifier.rb +15 -0
  47. data/lib/pact/native/logger.rb +24 -0
  48. data/lib/pact/provider/async_message_verifier.rb +29 -0
  49. data/lib/pact/provider/base_verifier.rb +259 -0
  50. data/lib/pact/provider/grpc_verifier.rb +40 -0
  51. data/lib/pact/provider/gruf_server.rb +73 -0
  52. data/lib/pact/provider/http_server.rb +77 -0
  53. data/lib/pact/provider/http_verifier.rb +45 -0
  54. data/lib/pact/provider/message_provider_servlet.rb +77 -0
  55. data/lib/pact/provider/mixed_verifier.rb +21 -0
  56. data/lib/pact/provider/pact_broker_proxy.rb +62 -0
  57. data/lib/pact/provider/pact_broker_proxy_runner.rb +78 -0
  58. data/lib/pact/provider/pact_config/async.rb +28 -0
  59. data/lib/pact/provider/pact_config/base.rb +101 -0
  60. data/lib/pact/provider/pact_config/grpc.rb +24 -0
  61. data/lib/pact/provider/pact_config/http.rb +25 -0
  62. data/lib/pact/provider/pact_config/mixed.rb +38 -0
  63. data/lib/pact/provider/pact_config.rb +24 -0
  64. data/lib/pact/provider/provider_server_runner.rb +90 -0
  65. data/lib/pact/provider/provider_state_configuration.rb +32 -0
  66. data/lib/pact/provider/provider_state_servlet.rb +84 -0
  67. data/lib/pact/provider.rb +6 -3
  68. data/lib/pact/railtie.rb +11 -0
  69. data/lib/pact/{v2/rspec → rspec}/support/pact_consumer_helpers.rb +8 -8
  70. data/lib/pact/{v2/rspec → rspec}/support/pact_provider_helpers.rb +25 -24
  71. data/lib/pact/{v2/rspec.rb → rspec.rb} +6 -5
  72. data/lib/pact/tasks/pact.rake +13 -0
  73. data/lib/pact/version.rb +1 -1
  74. data/lib/pact.rb +46 -11
  75. data/pact.gemspec +42 -65
  76. metadata +145 -404
  77. data/lib/pact/cli/generate_pact_docs.rb +0 -4
  78. data/lib/pact/cli/run_pact_verification.rb +0 -99
  79. data/lib/pact/cli/spec_criteria.rb +0 -26
  80. data/lib/pact/cli.rb +0 -45
  81. data/lib/pact/consumer/configuration/configuration_extensions.rb +0 -90
  82. data/lib/pact/consumer/configuration/dsl.rb +0 -11
  83. data/lib/pact/consumer/configuration/mock_service.rb +0 -112
  84. data/lib/pact/consumer/configuration/service_consumer.rb +0 -51
  85. data/lib/pact/consumer/configuration/service_provider.rb +0 -40
  86. data/lib/pact/consumer/configuration.rb +0 -10
  87. data/lib/pact/consumer/consumer_contract_builder.rb +0 -82
  88. data/lib/pact/consumer/consumer_contract_builders.rb +0 -10
  89. data/lib/pact/consumer/interaction_builder.rb +0 -45
  90. data/lib/pact/consumer/rspec.rb +0 -35
  91. data/lib/pact/consumer/spec_hooks.rb +0 -40
  92. data/lib/pact/consumer/world.rb +0 -37
  93. data/lib/pact/doc/README.md +0 -13
  94. data/lib/pact/doc/doc_file.rb +0 -40
  95. data/lib/pact/doc/generate.rb +0 -11
  96. data/lib/pact/doc/generator.rb +0 -82
  97. data/lib/pact/doc/interaction_view_model.rb +0 -124
  98. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +0 -68
  99. data/lib/pact/doc/markdown/generator.rb +0 -26
  100. data/lib/pact/doc/markdown/index_renderer.rb +0 -43
  101. data/lib/pact/doc/markdown/interaction.erb +0 -14
  102. data/lib/pact/doc/markdown/interaction_renderer.rb +0 -43
  103. data/lib/pact/doc/sort_interactions.rb +0 -16
  104. data/lib/pact/hal/authorization_header_redactor.rb +0 -32
  105. data/lib/pact/hal/entity.rb +0 -110
  106. data/lib/pact/hal/http_client.rb +0 -128
  107. data/lib/pact/hal/link.rb +0 -112
  108. data/lib/pact/hal/non_json_entity.rb +0 -28
  109. data/lib/pact/hash_refinements.rb +0 -17
  110. data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +0 -112
  111. data/lib/pact/pact_broker/fetch_pacts.rb +0 -103
  112. data/lib/pact/pact_broker/notices.rb +0 -34
  113. data/lib/pact/pact_broker/pact_selection_description.rb +0 -66
  114. data/lib/pact/pact_broker.rb +0 -25
  115. data/lib/pact/project_root.rb +0 -7
  116. data/lib/pact/provider/configuration/configuration_extension.rb +0 -69
  117. data/lib/pact/provider/configuration/dsl.rb +0 -18
  118. data/lib/pact/provider/configuration/message_provider_dsl.rb +0 -63
  119. data/lib/pact/provider/configuration/pact_verification.rb +0 -48
  120. data/lib/pact/provider/configuration/pact_verification_from_broker.rb +0 -126
  121. data/lib/pact/provider/configuration/service_provider_config.rb +0 -32
  122. data/lib/pact/provider/configuration/service_provider_dsl.rb +0 -107
  123. data/lib/pact/provider/configuration.rb +0 -7
  124. data/lib/pact/provider/context.rb +0 -0
  125. data/lib/pact/provider/help/console_text.rb +0 -76
  126. data/lib/pact/provider/help/content.rb +0 -38
  127. data/lib/pact/provider/help/pact_diff.rb +0 -43
  128. data/lib/pact/provider/help/prompt_text.rb +0 -49
  129. data/lib/pact/provider/help/write.rb +0 -56
  130. data/lib/pact/provider/matchers/messages.rb +0 -66
  131. data/lib/pact/provider/pact_helper_locator.rb +0 -24
  132. data/lib/pact/provider/pact_source.rb +0 -40
  133. data/lib/pact/provider/pact_spec_runner.rb +0 -188
  134. data/lib/pact/provider/pact_uri.rb +0 -55
  135. data/lib/pact/provider/pact_verification.rb +0 -17
  136. data/lib/pact/provider/print_missing_provider_states.rb +0 -35
  137. data/lib/pact/provider/request.rb +0 -90
  138. data/lib/pact/provider/rspec/backtrace_formatter.rb +0 -43
  139. data/lib/pact/provider/rspec/calculate_exit_code.rb +0 -18
  140. data/lib/pact/provider/rspec/custom_options_file +0 -0
  141. data/lib/pact/provider/rspec/formatter_rspec_2.rb +0 -76
  142. data/lib/pact/provider/rspec/formatter_rspec_3.rb +0 -195
  143. data/lib/pact/provider/rspec/json_formatter.rb +0 -100
  144. data/lib/pact/provider/rspec/matchers.rb +0 -80
  145. data/lib/pact/provider/rspec/pact_broker_formatter.rb +0 -76
  146. data/lib/pact/provider/rspec.rb +0 -234
  147. data/lib/pact/provider/state/provider_state.rb +0 -180
  148. data/lib/pact/provider/state/provider_state_configured_modules.rb +0 -15
  149. data/lib/pact/provider/state/provider_state_manager.rb +0 -42
  150. data/lib/pact/provider/state/provider_state_proxy.rb +0 -39
  151. data/lib/pact/provider/state/set_up.rb +0 -13
  152. data/lib/pact/provider/state/tear_down.rb +0 -13
  153. data/lib/pact/provider/test_methods.rb +0 -77
  154. data/lib/pact/provider/verification_report.rb +0 -36
  155. data/lib/pact/provider/verification_results/create.rb +0 -88
  156. data/lib/pact/provider/verification_results/publish.rb +0 -143
  157. data/lib/pact/provider/verification_results/publish_all.rb +0 -50
  158. data/lib/pact/provider/verification_results/verification_result.rb +0 -40
  159. data/lib/pact/provider/world.rb +0 -50
  160. data/lib/pact/retry.rb +0 -37
  161. data/lib/pact/tasks/task_helper.rb +0 -62
  162. data/lib/pact/tasks/verification_task.rb +0 -95
  163. data/lib/pact/tasks.rb +0 -2
  164. data/lib/pact/templates/help.erb +0 -22
  165. data/lib/pact/templates/provider_state.erb +0 -14
  166. data/lib/pact/utils/metrics.rb +0 -100
  167. data/lib/pact/utils/string.rb +0 -35
  168. data/lib/pact/v2/configuration.rb +0 -23
  169. data/lib/pact/v2/consumer/grpc_interaction_builder.rb +0 -194
  170. data/lib/pact/v2/consumer/http_interaction_builder.rb +0 -162
  171. data/lib/pact/v2/consumer/interaction_contents.rb +0 -62
  172. data/lib/pact/v2/consumer/message_interaction_builder.rb +0 -288
  173. data/lib/pact/v2/consumer/mock_server.rb +0 -108
  174. data/lib/pact/v2/consumer/pact_config/base.rb +0 -24
  175. data/lib/pact/v2/consumer/pact_config/grpc.rb +0 -26
  176. data/lib/pact/v2/consumer/pact_config/http.rb +0 -55
  177. data/lib/pact/v2/consumer/pact_config/message.rb +0 -17
  178. data/lib/pact/v2/consumer/pact_config/plugin_async_message.rb +0 -26
  179. data/lib/pact/v2/consumer/pact_config/plugin_http.rb +0 -26
  180. data/lib/pact/v2/consumer/pact_config/plugin_sync_message.rb +0 -26
  181. data/lib/pact/v2/consumer/pact_config.rb +0 -30
  182. data/lib/pact/v2/consumer/plugin_async_message_interaction_builder.rb +0 -171
  183. data/lib/pact/v2/consumer/plugin_http_interaction_builder.rb +0 -201
  184. data/lib/pact/v2/consumer/plugin_sync_message_interaction_builder.rb +0 -180
  185. data/lib/pact/v2/consumer.rb +0 -8
  186. data/lib/pact/v2/generators/base.rb +0 -287
  187. data/lib/pact/v2/generators.rb +0 -49
  188. data/lib/pact/v2/matchers/base.rb +0 -74
  189. data/lib/pact/v2/matchers/v1/equality.rb +0 -19
  190. data/lib/pact/v2/matchers/v2/regex.rb +0 -19
  191. data/lib/pact/v2/matchers/v2/type.rb +0 -17
  192. data/lib/pact/v2/matchers/v3/boolean.rb +0 -17
  193. data/lib/pact/v2/matchers/v3/content_type.rb +0 -32
  194. data/lib/pact/v2/matchers/v3/date.rb +0 -18
  195. data/lib/pact/v2/matchers/v3/date_time.rb +0 -18
  196. data/lib/pact/v2/matchers/v3/decimal.rb +0 -17
  197. data/lib/pact/v2/matchers/v3/each.rb +0 -42
  198. data/lib/pact/v2/matchers/v3/include.rb +0 -17
  199. data/lib/pact/v2/matchers/v3/integer.rb +0 -17
  200. data/lib/pact/v2/matchers/v3/null.rb +0 -16
  201. data/lib/pact/v2/matchers/v3/number.rb +0 -17
  202. data/lib/pact/v2/matchers/v3/semver.rb +0 -23
  203. data/lib/pact/v2/matchers/v3/time.rb +0 -18
  204. data/lib/pact/v2/matchers/v3/values.rb +0 -16
  205. data/lib/pact/v2/matchers/v4/each_key.rb +0 -26
  206. data/lib/pact/v2/matchers/v4/each_key_value.rb +0 -32
  207. data/lib/pact/v2/matchers/v4/each_value.rb +0 -33
  208. data/lib/pact/v2/matchers/v4/not_empty.rb +0 -24
  209. data/lib/pact/v2/matchers/v4/status_code.rb +0 -17
  210. data/lib/pact/v2/matchers.rb +0 -110
  211. data/lib/pact/v2/native/blocking_verifier.rb +0 -17
  212. data/lib/pact/v2/native/logger.rb +0 -25
  213. data/lib/pact/v2/provider/async_message_verifier.rb +0 -28
  214. data/lib/pact/v2/provider/base_verifier.rb +0 -242
  215. data/lib/pact/v2/provider/grpc_verifier.rb +0 -38
  216. data/lib/pact/v2/provider/gruf_server.rb +0 -75
  217. data/lib/pact/v2/provider/http_server.rb +0 -79
  218. data/lib/pact/v2/provider/http_verifier.rb +0 -43
  219. data/lib/pact/v2/provider/message_provider_servlet.rb +0 -79
  220. data/lib/pact/v2/provider/mixed_verifier.rb +0 -22
  221. data/lib/pact/v2/provider/pact_broker_proxy.rb +0 -66
  222. data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +0 -80
  223. data/lib/pact/v2/provider/pact_config/async.rb +0 -29
  224. data/lib/pact/v2/provider/pact_config/base.rb +0 -103
  225. data/lib/pact/v2/provider/pact_config/grpc.rb +0 -26
  226. data/lib/pact/v2/provider/pact_config/http.rb +0 -27
  227. data/lib/pact/v2/provider/pact_config/mixed.rb +0 -40
  228. data/lib/pact/v2/provider/pact_config.rb +0 -26
  229. data/lib/pact/v2/provider/provider_server_runner.rb +0 -92
  230. data/lib/pact/v2/provider/provider_state_configuration.rb +0 -32
  231. data/lib/pact/v2/provider/provider_state_servlet.rb +0 -86
  232. data/lib/pact/v2/provider.rb +0 -8
  233. data/lib/pact/v2/railtie.rb +0 -13
  234. data/lib/pact/v2/tasks/pact.rake +0 -13
  235. data/lib/pact/v2.rb +0 -71
  236. data/lib/tasks/pact.rake +0 -34
  237. /data/lib/pact/{v2/rspec → rspec}/support/pact_message_helpers.rb +0 -0
  238. /data/lib/pact/{v2/rspec → rspec}/support/waterdrop/pact_waterdrop_client.rb +0 -0
  239. /data/lib/pact/{v2/rspec → rspec}/support/webmock/webmock_helpers.rb +0 -0
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pact/ffi/async_message_pact"
4
+ require "pact/ffi/plugin_consumer"
5
+ require "pact/ffi/logger"
6
+
7
+ module Pact
8
+ module Consumer
9
+ class PluginAsyncMessageInteractionBuilder
10
+
11
+ class PluginInitError < Pact::FfiError; end
12
+
13
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_using_plugin.html
14
+ INIT_PLUGIN_ERRORS = {
15
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
16
+ 2 => {reason: :plugin_load_failed, status: 2, description: "Failed to load the plugin"},
17
+ 3 => {reason: :invalid_handle, status: 3, description: "Pact Handle is not valid"}
18
+ }.freeze
19
+
20
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
21
+ CREATE_INTERACTION_ERRORS = {
22
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
23
+ 2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
24
+ 3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
25
+ 4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
26
+ 5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
27
+ 6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
28
+ }.freeze
29
+
30
+ class CreateInteractionError < Pact::FfiError; end
31
+
32
+ class InteractionMismatchesError < Pact::Error; end
33
+
34
+ class InteractionBuilderError < Pact::Error; end
35
+
36
+ def initialize(pact_config, description: nil)
37
+ @pact_config = pact_config
38
+ @description = description || ""
39
+ @contents = nil
40
+ @provider_state_meta = nil
41
+ end
42
+
43
+ def with_plugin(plugin_name, plugin_version)
44
+ raise InteractionBuilderError.new("plugin_name is required") if plugin_name.blank?
45
+ raise InteractionBuilderError.new("plugin_version is required") if plugin_version.blank?
46
+
47
+ @plugin_name = plugin_name
48
+ @plugin_version = plugin_version
49
+ self
50
+ end
51
+
52
+ def given(provider_state, metadata = {})
53
+ @provider_state_meta = {provider_state => metadata}
54
+ self
55
+ end
56
+
57
+ def upon_receiving(description)
58
+ @description = description
59
+ self
60
+ end
61
+
62
+ def with_contents(contents_hash)
63
+ @contents = InteractionContents.plugin(contents_hash)
64
+ self
65
+ end
66
+
67
+ def with_content_type(content_type)
68
+ @interaction_content_type = content_type || @content_type
69
+ self
70
+ end
71
+
72
+ def with_plugin_metadata(meta_hash)
73
+ @plugin_metadata = meta_hash
74
+ self
75
+ end
76
+
77
+ def with_transport(transport)
78
+ @transport = transport
79
+ self
80
+ end
81
+
82
+ def interaction_json
83
+ result = {
84
+ contents: @contents
85
+ }
86
+ result.merge!(@plugin_metadata) if @plugin_metadata.is_a?(Hash)
87
+ JSON.dump(result)
88
+ end
89
+
90
+ def validate!
91
+ raise InteractionBuilderError.new("invalid contents format, should be a hash") unless @contents.is_a?(Hash)
92
+ end
93
+
94
+ def execute(&block)
95
+ raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
96
+
97
+ validate!
98
+
99
+ pact_handle = init_pact
100
+ init_plugin!(pact_handle)
101
+
102
+ interaction = PactFfi::AsyncMessageConsumer.new(pact_handle, @description)
103
+
104
+ @provider_state_meta&.each_pair do |provider_state, meta|
105
+ if meta.present?
106
+ meta.each_pair do |k, v|
107
+ if v.nil? || (v.respond_to?(:empty?) && v.empty?)
108
+ PactFfi.given(interaction, provider_state)
109
+ else
110
+ PactFfi.given_with_param(interaction, provider_state, k.to_s, v.to_s)
111
+ end
112
+ end
113
+ else
114
+ PactFfi.given(interaction, provider_state)
115
+ end
116
+ end
117
+
118
+ result = PactFfi.with_body(interaction, 0, @interaction_content_type, interaction_json)
119
+ if CREATE_INTERACTION_ERRORS[result].present?
120
+ error = CREATE_INTERACTION_ERRORS[result]
121
+ raise CreateInteractionError.new("There was an error while trying to add interaction \"#{@description}\"", error[:reason], error[:status])
122
+ end
123
+
124
+ mock_server = MockServer.create_for_transport!(pact: pact_handle, transport: @transport, host: @pact_config.mock_host, port: @pact_config.mock_port)
125
+
126
+ yield(pact_handle, mock_server)
127
+
128
+ ensure
129
+ if mock_server.matched?
130
+ mock_server.write_pacts!(@pact_config.pact_dir)
131
+ else
132
+ msg = mismatches_error_msg(mock_server)
133
+ raise InteractionMismatchesError.new(msg)
134
+ end
135
+ @used = true
136
+ mock_server&.cleanup
137
+ PactFfi::PluginConsumer.cleanup_plugins(pact_handle) if pact_handle
138
+ PactFfi.free_pact_handle(pact_handle) if pact_handle
139
+ end
140
+
141
+ private
142
+
143
+ def mismatches_error_msg(mock_server)
144
+ rspec_example_desc = RSpec.current_example&.description
145
+ return "interaction for has mismatches: #{mock_server.mismatches}" if rspec_example_desc.blank?
146
+
147
+ "#{rspec_example_desc} has mismatches: #{mock_server.mismatches}"
148
+ end
149
+
150
+ def init_pact
151
+ handle = PactFfi.new_pact(@pact_config.consumer_name, @pact_config.provider_name)
152
+ PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_V4"])
153
+ PactFfi.with_pact_metadata(handle, "pact-ruby", "pact-ffi", PactFfi.version)
154
+
155
+ Pact::Native::Logger.log_to_stdout(@pact_config.log_level)
156
+
157
+ handle
158
+ end
159
+
160
+ def init_plugin!(pact_handle)
161
+ result = PactFfi::PluginConsumer.using_plugin(pact_handle, @plugin_name, @plugin_version)
162
+ return result if INIT_PLUGIN_ERRORS[result].blank?
163
+
164
+ error = INIT_PLUGIN_ERRORS[result]
165
+ raise PluginInitError.new("There was an error while trying to initialize plugin #{@plugin_name}/#{@plugin_version}", error[:reason], error[:status])
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pact/ffi/http_consumer"
4
+ require "pact/ffi/plugin_consumer"
5
+ require "pact/ffi/logger"
6
+
7
+ module Pact
8
+ module Consumer
9
+ class PluginHttpInteractionBuilder
10
+
11
+ class PluginInitError < Pact::FfiError; end
12
+
13
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_using_plugin.html
14
+ INIT_PLUGIN_ERRORS = {
15
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
16
+ 2 => {reason: :plugin_load_failed, status: 2, description: "Failed to load the plugin"},
17
+ 3 => {reason: :invalid_handle, status: 3, description: "Pact Handle is not valid"}
18
+ }.freeze
19
+
20
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
21
+ CREATE_INTERACTION_ERRORS = {
22
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
23
+ 2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
24
+ 3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
25
+ 4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
26
+ 5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
27
+ 6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
28
+ }.freeze
29
+
30
+ class CreateInteractionError < Pact::FfiError; end
31
+
32
+ class InteractionMismatchesError < Pact::Error; end
33
+
34
+ class InteractionBuilderError < Pact::Error; end
35
+
36
+ def initialize(pact_config, description: nil)
37
+ @pact_config = pact_config
38
+ @description = description || ""
39
+ @contents = nil
40
+ @provider_state_meta = nil
41
+ end
42
+
43
+ def with_plugin(plugin_name, plugin_version)
44
+ raise InteractionBuilderError.new("plugin_name is required") if plugin_name.blank?
45
+ raise InteractionBuilderError.new("plugin_version is required") if plugin_version.blank?
46
+
47
+ @plugin_name = plugin_name
48
+ @plugin_version = plugin_version
49
+ self
50
+ end
51
+
52
+ def given(provider_state, metadata = {})
53
+ @provider_state_meta = {provider_state => metadata}
54
+ self
55
+ end
56
+
57
+ def upon_receiving(description)
58
+ @description = description
59
+ self
60
+ end
61
+
62
+ def with_request(method: nil, path: nil, query: {}, headers: {}, body: nil)
63
+ @request = {
64
+ method: method,
65
+ path: path,
66
+ query: query,
67
+ headers: headers,
68
+ body: body
69
+ }
70
+ self
71
+ end
72
+
73
+ def will_respond_with(status: nil, headers: {}, body: nil)
74
+ @response = {
75
+ status: status,
76
+ headers: headers,
77
+ body: body
78
+ }
79
+ self
80
+ end
81
+
82
+ def with_content_type(content_type)
83
+ @content_type = content_type
84
+ self
85
+ end
86
+
87
+
88
+ def with_plugin_metadata(meta_hash)
89
+ @plugin_metadata = meta_hash
90
+ self
91
+ end
92
+
93
+ def with_transport(transport)
94
+ @transport = transport
95
+ self
96
+ end
97
+
98
+ def interaction_json
99
+ result = {
100
+ request: @request,
101
+ response: @response
102
+ }
103
+ result.merge!(@plugin_metadata) if @plugin_metadata.is_a?(Hash)
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)
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
+ interaction = PactFfi.new_interaction(pact_handle, @description)
121
+ @provider_state_meta&.each_pair do |provider_state, meta|
122
+ if meta.present?
123
+ meta.each_pair do |k, v|
124
+ if v.nil? || (v.respond_to?(:empty?) && v.empty?)
125
+ PactFfi.given(interaction, provider_state)
126
+ else
127
+ PactFfi.given_with_param(interaction, provider_state, k.to_s, v.to_s)
128
+ end
129
+ end
130
+ else
131
+ PactFfi.given(interaction, provider_state)
132
+ end
133
+ end
134
+ PactFfi::HttpConsumer.with_request(interaction, @request[:method], @request[:path])
135
+
136
+ result = PactFfi::PluginConsumer.interaction_contents(interaction, 0, @request[:headers]["content-type"], format_value(@request[:body]))
137
+ if CREATE_INTERACTION_ERRORS[result].present?
138
+ error = CREATE_INTERACTION_ERRORS[result]
139
+ raise CreateInteractionError.new("There was an error while trying to add interaction \"#{@description}\"", error[:reason], error[:status])
140
+ end
141
+ result = PactFfi::PluginConsumer.interaction_contents(interaction, 1, @response[:headers]["content-type"], format_value(@response[:body]))
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
+ mock_server = MockServer.create_for_transport!(pact: pact_handle, transport: @transport || 'http', host: @pact_config.mock_host, port: @pact_config.mock_port)
147
+
148
+ yield(mock_server)
149
+
150
+ ensure
151
+ if mock_server.matched?
152
+ mock_server.write_pacts!(@pact_config.pact_dir)
153
+ else
154
+ msg = mismatches_error_msg(mock_server)
155
+ raise InteractionMismatchesError.new(msg)
156
+ end
157
+ @used = true
158
+ mock_server&.cleanup
159
+ PactFfi::PluginConsumer.cleanup_plugins(pact_handle) if pact_handle
160
+ PactFfi.free_pact_handle(pact_handle) if pact_handle
161
+ end
162
+
163
+ private
164
+
165
+ def mismatches_error_msg(mock_server)
166
+ rspec_example_desc = RSpec.current_example&.description
167
+ return "interaction for has mismatches: #{mock_server.mismatches}" if rspec_example_desc.blank?
168
+
169
+ "#{rspec_example_desc} has mismatches: #{mock_server.mismatches}"
170
+ end
171
+
172
+ def init_pact
173
+ handle = PactFfi.new_pact(@pact_config.consumer_name, @pact_config.provider_name)
174
+ PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_V4"])
175
+ PactFfi.with_pact_metadata(handle, "pact-ruby", "pact-ffi", PactFfi.version)
176
+
177
+ Pact::Native::Logger.log_to_stdout(@pact_config.log_level)
178
+
179
+ handle
180
+ end
181
+
182
+ def init_plugin!(pact_handle)
183
+ result = PactFfi::PluginConsumer.using_plugin(pact_handle, @plugin_name, @plugin_version)
184
+ return result if INIT_PLUGIN_ERRORS[result].blank?
185
+
186
+ error = INIT_PLUGIN_ERRORS[result]
187
+ raise PluginInitError.new("There was an error while trying to initialize plugin #{@plugin_name}/#{@plugin_version}", error[:reason], error[:status])
188
+ end
189
+
190
+ def format_value(obj)
191
+ return obj if obj.is_a?(String)
192
+
193
+ return JSON.dump({value: obj}) if obj.is_a?(Array)
194
+
195
+ JSON.dump(obj)
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,178 @@
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 PluginSyncMessageInteractionBuilder
10
+
11
+ class PluginInitError < Pact::FfiError; end
12
+
13
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_using_plugin.html
14
+ INIT_PLUGIN_ERRORS = {
15
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
16
+ 2 => {reason: :plugin_load_failed, status: 2, description: "Failed to load the plugin"},
17
+ 3 => {reason: :invalid_handle, status: 3, description: "Pact Handle is not valid"}
18
+ }.freeze
19
+
20
+ # https://docs.rs/pact_ffi/0.4.17/pact_ffi/plugins/fn.pactffi_interaction_contents.html
21
+ CREATE_INTERACTION_ERRORS = {
22
+ 1 => {reason: :internal_error, status: 1, description: "A general panic was caught"},
23
+ 2 => {reason: :mock_server_already_running, status: 2, description: "The mock server has already been started"},
24
+ 3 => {reason: :invalid_handle, status: 3, description: "The interaction handle is invalid"},
25
+ 4 => {reason: :invalid_content_type, status: 4, description: "The content type is not valid"},
26
+ 5 => {reason: :invalid_contents, status: 5, description: "The contents JSON is not valid JSON"},
27
+ 6 => {reason: :plugin_error, status: 6, description: "The plugin returned an error"}
28
+ }.freeze
29
+
30
+ class CreateInteractionError < Pact::FfiError; end
31
+
32
+ class InteractionMismatchesError < Pact::Error; end
33
+
34
+ class InteractionBuilderError < Pact::Error; end
35
+
36
+ def initialize(pact_config, description: nil)
37
+ @pact_config = pact_config
38
+ @description = description || ""
39
+ @request = nil
40
+ @response = nil
41
+ @response_meta = nil
42
+ @provider_state_meta = nil
43
+ end
44
+
45
+ def with_plugin(plugin_name, plugin_version)
46
+ raise InteractionBuilderError.new("plugin_name is required") if plugin_name.blank?
47
+ raise InteractionBuilderError.new("plugin_version is required") if plugin_version.blank?
48
+
49
+ @plugin_name = plugin_name
50
+ @plugin_version = plugin_version
51
+ self
52
+ end
53
+
54
+ def given(provider_state, metadata = {})
55
+ @provider_state_meta = {provider_state => metadata}
56
+ self
57
+ end
58
+
59
+ def upon_receiving(description)
60
+ @description = description
61
+ self
62
+ end
63
+
64
+ def with_request(req_hash)
65
+ @request = InteractionContents.plugin(req_hash)
66
+ self
67
+ end
68
+
69
+ def with_content_type(content_type)
70
+ @content_type = content_type
71
+ self
72
+ end
73
+
74
+ def will_respond_with(resp_hash)
75
+ @response = InteractionContents.plugin(resp_hash)
76
+ self
77
+ end
78
+
79
+ def will_respond_with_meta(meta_hash)
80
+ @response_meta = InteractionContents.plugin(meta_hash)
81
+ self
82
+ end
83
+
84
+ def with_plugin_metadata(meta_hash)
85
+ @plugin_metadata = meta_hash
86
+ self
87
+ end
88
+
89
+ def with_transport(transport)
90
+ @transport = transport
91
+ self
92
+ end
93
+
94
+ def interaction_json
95
+ result = {
96
+ request: @request
97
+ }
98
+ result.merge!(@plugin_metadata) if @plugin_metadata.is_a?(Hash)
99
+
100
+ result[:response] = @response if @response.is_a?(Hash)
101
+ result[:responseMetadata] = @response_meta if @response_meta.is_a?(Hash)
102
+
103
+ JSON.dump(result)
104
+ end
105
+
106
+ def validate!
107
+ raise InteractionBuilderError.new("invalid request format, should be a hash") unless @request.is_a?(Hash)
108
+ raise InteractionBuilderError.new("invalid response format, should be a hash") unless @response.is_a?(Hash) || @response_meta.is_a?(Hash)
109
+ end
110
+
111
+ def execute(&block)
112
+ raise InteractionBuilderError.new("interaction is designed to be used one-time only") if defined?(@used)
113
+
114
+ validate!
115
+
116
+ pact_handle = init_pact
117
+ init_plugin!(pact_handle)
118
+
119
+ message_pact = PactFfi::SyncMessageConsumer.new_interaction(pact_handle, @description)
120
+ @provider_state_meta&.each_pair do |provider_state, meta|
121
+ if meta.present?
122
+ meta.each_pair { |k, v| PactFfi.given_with_param(message_pact, provider_state, k.to_s, v.to_s) }
123
+ else
124
+ PactFfi.given(message_pact, provider_state)
125
+ end
126
+ end
127
+ result = PactFfi::PluginConsumer.interaction_contents(message_pact, 0, @content_type, interaction_json)
128
+ if CREATE_INTERACTION_ERRORS[result].present?
129
+ error = CREATE_INTERACTION_ERRORS[result]
130
+ raise CreateInteractionError.new("There was an error while trying to add interaction \"#{@description}\"", error[:reason], error[:status])
131
+ end
132
+
133
+ mock_server = MockServer.create_for_transport!(pact: pact_handle, transport: @transport, host: @pact_config.mock_host, port: @pact_config.mock_port)
134
+
135
+ yield(message_pact, mock_server)
136
+
137
+ ensure
138
+ if mock_server.matched?
139
+ mock_server.write_pacts!(@pact_config.pact_dir)
140
+ else
141
+ msg = mismatches_error_msg(mock_server)
142
+ raise InteractionMismatchesError.new(msg)
143
+ end
144
+ @used = true
145
+ mock_server&.cleanup
146
+ PactFfi::PluginConsumer.cleanup_plugins(pact_handle)
147
+ PactFfi.free_pact_handle(pact_handle)
148
+ end
149
+
150
+ private
151
+
152
+ def mismatches_error_msg(mock_server)
153
+ rspec_example_desc = RSpec.current_example&.description
154
+ return "interaction for has mismatches: #{mock_server.mismatches}" if rspec_example_desc.blank?
155
+
156
+ "#{rspec_example_desc} has mismatches: #{mock_server.mismatches}"
157
+ end
158
+
159
+ def init_pact
160
+ handle = PactFfi.new_pact(@pact_config.consumer_name, @pact_config.provider_name)
161
+ PactFfi.with_specification(handle, PactFfi::FfiSpecificationVersion["SPECIFICATION_VERSION_V4"])
162
+ PactFfi.with_pact_metadata(handle, "pact-ruby", "pact-ffi", PactFfi.version)
163
+
164
+ Pact::Native::Logger.log_to_stdout(@pact_config.log_level)
165
+
166
+ handle
167
+ end
168
+
169
+ def init_plugin!(pact_handle)
170
+ result = PactFfi::PluginConsumer.using_plugin(pact_handle, @plugin_name, @plugin_version)
171
+ return result if INIT_PLUGIN_ERRORS[result].blank?
172
+
173
+ error = INIT_PLUGIN_ERRORS[result]
174
+ raise PluginInitError.new("There was an error while trying to initialize plugin #{@plugin_name}/#{@plugin_version}", error[:reason], error[:status])
175
+ end
176
+ end
177
+ end
178
+ end
data/lib/pact/consumer.rb CHANGED
@@ -1,7 +1,6 @@
1
- require 'pact/consumer_contract'
2
- require 'pact/consumer/configuration'
3
- require 'pact/consumer/consumer_contract_builder'
4
- require 'pact/consumer/consumer_contract_builders'
5
- require 'pact/consumer/interaction_builder'
6
- require 'pact/term'
7
- require 'pact/something_like'
1
+ # frozen_string_literal: true
2
+
3
+ module Pact
4
+ module Consumer
5
+ end
6
+ end