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