pact 0.1.28 → 1.3.3

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 (285) hide show
  1. data/.gitignore +2 -1
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG.md +374 -0
  5. data/Gemfile +0 -1
  6. data/Gemfile.lock +58 -55
  7. data/README.md +214 -139
  8. data/Rakefile +4 -28
  9. data/bin/pact +2 -2
  10. data/config.ru +3 -0
  11. data/documentation/README.md +13 -0
  12. data/documentation/configuration.md +166 -0
  13. data/documentation/diff_formatter_embedded.png +0 -0
  14. data/documentation/diff_formatter_list.png +0 -0
  15. data/documentation/diff_formatter_unix.png +0 -0
  16. data/example/animal-service/Gemfile +14 -0
  17. data/example/animal-service/Gemfile.lock +69 -0
  18. data/example/animal-service/Rakefile +5 -0
  19. data/example/animal-service/config.ru +3 -0
  20. data/example/animal-service/db/animal_db.sqlite3 +0 -0
  21. data/example/animal-service/lib/animal_service/animal_repository.rb +12 -0
  22. data/example/animal-service/lib/animal_service/api.rb +28 -0
  23. data/example/animal-service/lib/animal_service/db.rb +5 -0
  24. data/example/animal-service/spec/service_consumers/pact_helper.rb +11 -0
  25. data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +26 -0
  26. data/example/zoo-app/Gemfile +12 -0
  27. data/example/zoo-app/Gemfile.lock +63 -0
  28. data/example/zoo-app/Rakefile +5 -0
  29. data/example/zoo-app/doc/pacts/markdown/README.md +3 -0
  30. data/example/zoo-app/doc/pacts/markdown/Zoo App - Animal Service.md +75 -0
  31. data/example/zoo-app/lib/zoo_app/animal_service_client.rb +40 -0
  32. data/example/zoo-app/lib/zoo_app/models/alligator.rb +15 -0
  33. data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +67 -0
  34. data/example/zoo-app/spec/service_providers/animal_service_client_spec.rb +71 -0
  35. data/example/zoo-app/spec/service_providers/pact_helper.rb +15 -0
  36. data/example/zoo-app/spec/spec_helper.rb +6 -0
  37. data/lib/pact/cli.rb +138 -0
  38. data/lib/pact/configuration.rb +169 -28
  39. data/lib/pact/consumer/app_manager.rb +10 -29
  40. data/lib/pact/consumer/configuration/configuration_extensions.rb +15 -0
  41. data/lib/pact/consumer/configuration/dsl.rb +12 -0
  42. data/lib/pact/consumer/configuration/mock_service.rb +91 -0
  43. data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
  44. data/lib/pact/consumer/configuration/service_provider.rb +40 -0
  45. data/lib/pact/consumer/configuration.rb +11 -0
  46. data/lib/pact/consumer/consumer_contract_builder.rb +44 -47
  47. data/lib/pact/consumer/interaction_builder.rb +41 -0
  48. data/lib/pact/consumer/interactions_filter.rb +48 -0
  49. data/lib/pact/consumer/mock_service/app.rb +82 -0
  50. data/lib/pact/consumer/mock_service/interaction_delete.rb +33 -0
  51. data/lib/pact/consumer/mock_service/interaction_list.rb +76 -0
  52. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +73 -0
  53. data/lib/pact/consumer/mock_service/interaction_post.rb +31 -0
  54. data/lib/pact/consumer/mock_service/interaction_replay.rb +139 -0
  55. data/lib/pact/consumer/mock_service/log_get.rb +28 -0
  56. data/lib/pact/consumer/mock_service/missing_interactions_get.rb +30 -0
  57. data/lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb +31 -0
  58. data/lib/pact/consumer/mock_service/pact_post.rb +33 -0
  59. data/lib/pact/consumer/mock_service/rack_request_helper.rb +51 -0
  60. data/lib/pact/consumer/mock_service/verification_get.rb +68 -0
  61. data/lib/pact/consumer/mock_service.rb +1 -339
  62. data/lib/pact/consumer/mock_service_client.rb +65 -0
  63. data/lib/pact/consumer/mock_service_interaction_expectation.rb +37 -0
  64. data/lib/pact/consumer/request.rb +27 -0
  65. data/lib/pact/consumer/rspec.rb +9 -19
  66. data/lib/pact/consumer/server.rb +90 -0
  67. data/lib/pact/consumer/spec_hooks.rb +38 -0
  68. data/lib/pact/consumer/world.rb +37 -0
  69. data/lib/pact/consumer.rb +10 -7
  70. data/lib/pact/consumer_contract/consumer_contract.rb +115 -0
  71. data/lib/pact/consumer_contract/consumer_contract_writer.rb +84 -0
  72. data/lib/pact/consumer_contract/file_name.rb +19 -0
  73. data/lib/pact/consumer_contract/headers.rb +51 -0
  74. data/lib/pact/consumer_contract/interaction.rb +67 -0
  75. data/lib/pact/consumer_contract/pact_file.rb +24 -0
  76. data/lib/pact/consumer_contract/request.rb +73 -0
  77. data/lib/pact/consumer_contract/service_consumer.rb +28 -0
  78. data/lib/pact/consumer_contract/service_provider.rb +28 -0
  79. data/lib/pact/consumer_contract.rb +1 -110
  80. data/lib/pact/doc/doc_file.rb +40 -0
  81. data/lib/pact/doc/generate.rb +11 -0
  82. data/lib/pact/doc/generator.rb +82 -0
  83. data/lib/pact/doc/interaction_view_model.rb +114 -0
  84. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +56 -0
  85. data/lib/pact/doc/markdown/generator.rb +26 -0
  86. data/lib/pact/doc/markdown/index_renderer.rb +41 -0
  87. data/lib/pact/doc/markdown/interaction.erb +14 -0
  88. data/lib/pact/doc/markdown/interaction_renderer.rb +38 -0
  89. data/lib/pact/doc/sort_interactions.rb +17 -0
  90. data/lib/pact/matchers/actual_type.rb +16 -0
  91. data/lib/pact/matchers/base_difference.rb +37 -0
  92. data/lib/pact/matchers/differ.rb +153 -0
  93. data/lib/pact/matchers/difference.rb +13 -0
  94. data/lib/pact/matchers/difference_indicator.rb +26 -0
  95. data/lib/pact/matchers/embedded_diff_formatter.rb +62 -0
  96. data/lib/pact/matchers/expected_type.rb +35 -0
  97. data/lib/pact/matchers/index_not_found.rb +15 -0
  98. data/lib/pact/matchers/list_diff_formatter.rb +101 -0
  99. data/lib/pact/matchers/matchers.rb +91 -37
  100. data/lib/pact/matchers/no_diff_indicator.rb +18 -0
  101. data/lib/pact/matchers/regexp_difference.rb +13 -0
  102. data/lib/pact/matchers/type_difference.rb +16 -0
  103. data/lib/pact/matchers/unexpected_index.rb +11 -0
  104. data/lib/pact/matchers/unexpected_key.rb +11 -0
  105. data/lib/pact/matchers/unix_diff_formatter.rb +114 -0
  106. data/lib/pact/project_root.rb +7 -0
  107. data/lib/pact/provider/configuration/configuration_extension.rb +58 -0
  108. data/lib/pact/provider/configuration/dsl.rb +13 -0
  109. data/lib/pact/provider/configuration/pact_verification.rb +46 -0
  110. data/lib/pact/provider/configuration/service_provider_config.rb +16 -0
  111. data/lib/pact/provider/configuration/service_provider_dsl.rb +59 -0
  112. data/lib/pact/provider/configuration.rb +7 -0
  113. data/lib/pact/provider/context.rb +0 -0
  114. data/lib/pact/provider/matchers/messages.rb +45 -0
  115. data/lib/pact/provider/pact_helper_locator.rb +22 -0
  116. data/lib/pact/provider/pact_spec_runner.rb +123 -0
  117. data/lib/pact/provider/pact_verification.rb +17 -0
  118. data/lib/pact/provider/print_missing_provider_states.rb +30 -0
  119. data/lib/pact/provider/request.rb +70 -0
  120. data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
  121. data/lib/pact/provider/rspec/custom_options_file +0 -0
  122. data/lib/pact/provider/rspec/formatter_rspec_2.rb +65 -0
  123. data/lib/pact/provider/rspec/formatter_rspec_3.rb +96 -0
  124. data/lib/pact/provider/rspec/matchers.rb +97 -0
  125. data/lib/pact/provider/rspec.rb +181 -0
  126. data/lib/pact/provider/state/provider_state.rb +181 -0
  127. data/lib/pact/provider/state/provider_state_configured_modules.rb +13 -0
  128. data/lib/pact/provider/state/provider_state_manager.rb +42 -0
  129. data/lib/pact/provider/state/provider_state_proxy.rb +39 -0
  130. data/lib/pact/provider/test_methods.rb +47 -0
  131. data/lib/pact/provider/verification_report.rb +36 -0
  132. data/lib/pact/provider/world.rb +23 -0
  133. data/lib/pact/provider.rb +3 -0
  134. data/lib/pact/reification.rb +6 -4
  135. data/lib/pact/rspec.rb +53 -0
  136. data/lib/pact/shared/active_support_support.rb +51 -0
  137. data/lib/pact/shared/dsl.rb +76 -0
  138. data/lib/pact/shared/jruby_support.rb +18 -0
  139. data/lib/pact/shared/json_differ.rb +15 -0
  140. data/lib/pact/shared/key_not_found.rb +15 -0
  141. data/lib/pact/shared/null_expectation.rb +31 -0
  142. data/lib/pact/shared/request.rb +80 -0
  143. data/lib/pact/shared/text_differ.rb +14 -0
  144. data/lib/pact/something_like.rb +49 -0
  145. data/lib/pact/symbolize_keys.rb +12 -0
  146. data/lib/pact/tasks/task_helper.rb +36 -0
  147. data/lib/pact/tasks/verification_task.rb +87 -0
  148. data/lib/pact/tasks.rb +2 -0
  149. data/lib/pact/templates/provider_state.erb +14 -0
  150. data/lib/pact/term.rb +47 -2
  151. data/lib/pact/version.rb +1 -1
  152. data/lib/pact.rb +1 -1
  153. data/lib/tasks/pact.rake +25 -2
  154. data/pact.gemspec +13 -11
  155. data/{scratchpad.txt → scratchpad.rb} +18 -2
  156. data/spec/features/consumption_spec.rb +24 -56
  157. data/spec/features/production_spec.rb +19 -24
  158. data/spec/features/{producer_states → provider_states}/zebras.rb +3 -3
  159. data/spec/integration/consumer_spec.rb +212 -0
  160. data/spec/integration/pact/consumer_configuration_spec.rb +66 -0
  161. data/spec/integration/pact/provider_configuration_spec.rb +25 -0
  162. data/spec/lib/pact/cli_spec.rb +47 -0
  163. data/spec/lib/pact/configuration_spec.rb +269 -7
  164. data/spec/lib/pact/consumer/app_manager_spec.rb +3 -3
  165. data/spec/lib/pact/consumer/configuration_spec.rb +57 -0
  166. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +52 -70
  167. data/spec/lib/pact/consumer/interaction_builder_spec.rb +91 -0
  168. data/spec/lib/pact/consumer/interactions_spec.rb +64 -0
  169. data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
  170. data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +78 -0
  171. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +70 -0
  172. data/spec/lib/pact/consumer/mock_service/interaction_replay_spec.rb +12 -0
  173. data/spec/lib/pact/consumer/mock_service/rack_request_helper_spec.rb +88 -0
  174. data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +142 -0
  175. data/spec/lib/pact/consumer/mock_service_client_spec.rb +88 -0
  176. data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +54 -0
  177. data/spec/lib/pact/consumer/request_spec.rb +24 -0
  178. data/spec/lib/pact/consumer/service_consumer_spec.rb +1 -1
  179. data/spec/lib/pact/consumer_contract/active_support_support_spec.rb +58 -0
  180. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +180 -0
  181. data/spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb +111 -0
  182. data/spec/lib/pact/consumer_contract/headers_spec.rb +107 -0
  183. data/spec/lib/pact/consumer_contract/interaction_spec.rb +107 -0
  184. data/spec/lib/pact/{request_spec.rb → consumer_contract/request_spec.rb} +80 -67
  185. data/spec/lib/pact/doc/generator_spec.rb +84 -0
  186. data/spec/lib/pact/doc/interaction_view_model_spec.rb +132 -0
  187. data/spec/lib/pact/doc/markdown/consumer_contract_renderer_spec.rb +29 -0
  188. data/spec/lib/pact/doc/markdown/index_renderer_spec.rb +29 -0
  189. data/spec/lib/pact/matchers/differ_spec.rb +214 -0
  190. data/spec/lib/pact/matchers/difference_spec.rb +22 -0
  191. data/spec/lib/pact/matchers/embedded_diff_formatter_spec.rb +90 -0
  192. data/spec/lib/pact/matchers/index_not_found_spec.rb +21 -0
  193. data/spec/lib/pact/matchers/list_diff_formatter_spec.rb +114 -0
  194. data/spec/lib/pact/matchers/matchers_spec.rb +400 -254
  195. data/spec/lib/pact/matchers/regexp_difference_spec.rb +20 -0
  196. data/spec/lib/pact/matchers/type_difference_spec.rb +34 -0
  197. data/spec/lib/pact/matchers/unexpected_index_spec.rb +20 -0
  198. data/spec/lib/pact/matchers/unexpected_key_spec.rb +20 -0
  199. data/spec/lib/pact/matchers/unix_diff_formatter_spec.rb +216 -0
  200. data/spec/lib/pact/provider/configuration/configuration_extension_spec.rb +30 -0
  201. data/spec/lib/pact/provider/configuration/pact_verification_spec.rb +43 -0
  202. data/spec/lib/pact/provider/configuration/service_provider_config_spec.rb +21 -0
  203. data/spec/lib/pact/provider/configuration/service_provider_dsl_spec.rb +108 -0
  204. data/spec/lib/pact/provider/configuration_spec.rb +50 -0
  205. data/spec/lib/pact/provider/matchers/messages_spec.rb +116 -0
  206. data/spec/lib/pact/provider/pact_helper_locator_spec.rb +54 -0
  207. data/spec/lib/pact/provider/print_missing_provider_states_spec.rb +19 -0
  208. data/spec/lib/pact/provider/request_spec.rb +78 -0
  209. data/spec/lib/pact/provider/rspec/formatter_rspec_2_spec.rb +68 -0
  210. data/spec/lib/pact/provider/rspec/formatter_rspec_3_spec.rb +72 -0
  211. data/spec/lib/pact/provider/rspec_spec.rb +55 -0
  212. data/spec/lib/pact/provider/state/provider_state_manager_spec.rb +89 -0
  213. data/spec/lib/pact/provider/state/provider_state_proxy_spec.rb +80 -0
  214. data/spec/lib/pact/provider/state/provider_state_spec.rb +213 -0
  215. data/spec/lib/pact/provider/world_spec.rb +41 -0
  216. data/spec/lib/pact/reification_spec.rb +24 -0
  217. data/spec/lib/pact/shared/dsl_spec.rb +86 -0
  218. data/spec/lib/pact/shared/json_differ_spec.rb +36 -0
  219. data/spec/lib/pact/shared/key_not_found_spec.rb +20 -0
  220. data/spec/lib/pact/shared/request_spec.rb +111 -0
  221. data/spec/lib/pact/shared/text_differ_spec.rb +54 -0
  222. data/spec/lib/pact/something_like_spec.rb +21 -0
  223. data/spec/lib/pact/tasks/task_helper_spec.rb +74 -0
  224. data/spec/lib/pact/tasks/verification_task_spec.rb +75 -0
  225. data/spec/lib/pact/term_spec.rb +57 -4
  226. data/spec/pact_specification/compliance-1.0.0.rb +47 -0
  227. data/spec/spec_helper.rb +17 -0
  228. data/spec/standalone/consumer_fail_test.rb +55 -0
  229. data/spec/standalone/consumer_pass_test.rb +51 -0
  230. data/spec/support/a_consumer-a_producer.json +2 -4
  231. data/spec/support/a_consumer-a_provider.json +32 -0
  232. data/spec/support/active_support_if_configured.rb +6 -0
  233. data/spec/support/app_for_config_ru.rb +4 -0
  234. data/spec/support/consumer_contract_template.json +24 -0
  235. data/spec/support/dsl_spec_support.rb +7 -0
  236. data/spec/support/factories.rb +82 -0
  237. data/spec/support/generated_index.md +4 -0
  238. data/spec/support/generated_markdown.md +55 -0
  239. data/spec/support/interaction_view_model.json +63 -0
  240. data/spec/support/interaction_view_model_with_terms.json +50 -0
  241. data/spec/support/markdown_pact.json +48 -0
  242. data/spec/support/missing_provider_states_output.txt +25 -0
  243. data/spec/support/options.json +21 -0
  244. data/spec/support/options_app.rb +15 -0
  245. data/spec/support/pact_helper.rb +57 -0
  246. data/spec/support/shared_examples_for_request.rb +94 -0
  247. data/spec/support/spec_support.rb +20 -0
  248. data/spec/support/stubbing.json +22 -0
  249. data/spec/support/stubbing_using_allow.rb +29 -0
  250. data/spec/support/term.json +48 -0
  251. data/spec/support/test_app_fail.json +41 -2
  252. data/spec/support/test_app_pass.json +19 -2
  253. data/spec/support/test_app_with_right_content_type_differ.json +23 -0
  254. data/tasks/pact-test.rake +98 -8
  255. data/tasks/spec.rake +8 -0
  256. metadata +393 -95
  257. data/lib/pact/app.rb +0 -32
  258. data/lib/pact/consumer/configuration_dsl.rb +0 -71
  259. data/lib/pact/consumer/dsl.rb +0 -98
  260. data/lib/pact/consumer/interaction.rb +0 -70
  261. data/lib/pact/consumer/run_condor.rb +0 -4
  262. data/lib/pact/consumer/run_mock_contract_service.rb +0 -13
  263. data/lib/pact/consumer/service_consumer.rb +0 -22
  264. data/lib/pact/consumer/service_producer.rb +0 -23
  265. data/lib/pact/json_warning.rb +0 -23
  266. data/lib/pact/producer/configuration_dsl.rb +0 -62
  267. data/lib/pact/producer/matchers.rb +0 -22
  268. data/lib/pact/producer/pact_spec_runner.rb +0 -57
  269. data/lib/pact/producer/producer_state.rb +0 -81
  270. data/lib/pact/producer/rspec.rb +0 -127
  271. data/lib/pact/producer/test_methods.rb +0 -89
  272. data/lib/pact/producer.rb +0 -1
  273. data/lib/pact/rake_task.rb +0 -64
  274. data/lib/pact/request.rb +0 -109
  275. data/lib/pact/verification_task.rb +0 -57
  276. data/spec/integration/pact/configuration_spec.rb +0 -65
  277. data/spec/lib/pact/consumer/dsl_spec.rb +0 -52
  278. data/spec/lib/pact/consumer/interaction_spec.rb +0 -108
  279. data/spec/lib/pact/consumer/mock_service_spec.rb +0 -147
  280. data/spec/lib/pact/consumer_contract_spec.rb +0 -125
  281. data/spec/lib/pact/producer/configuration_dsl_spec.rb +0 -101
  282. data/spec/lib/pact/producer/producer_state_spec.rb +0 -103
  283. data/spec/lib/pact/producer/rspec_spec.rb +0 -48
  284. data/spec/lib/pact/verification_task_spec.rb +0 -64
  285. data/spec/support/pact_rake_support.rb +0 -41
@@ -0,0 +1,40 @@
1
+ require 'pact/shared/dsl'
2
+ require 'pact/consumer/configuration/mock_service'
3
+
4
+ module Pact
5
+ module Consumer
6
+ module Configuration
7
+ class ServiceProvider
8
+
9
+ extend Pact::DSL
10
+
11
+ attr_accessor :service, :consumer_name, :name
12
+
13
+ def initialize name, consumer_name
14
+ @name = name
15
+ @service = nil
16
+ @consumer_name = consumer_name
17
+ end
18
+
19
+ dsl do
20
+ def mock_service name, &block
21
+ self.service = MockService.build(name, consumer_name, self.name, &block)
22
+ end
23
+ end
24
+
25
+ def finalize
26
+ validate
27
+ end
28
+
29
+ private
30
+
31
+ def validate
32
+ raise "Please configure a service for #{name}" unless service
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,11 @@
1
+ require 'pact/configuration'
2
+ require 'pact/consumer/consumer_contract_builders'
3
+ require 'pact/consumer/consumer_contract_builder'
4
+ require 'pact/consumer/configuration/service_consumer'
5
+ require 'pact/consumer/configuration/service_provider'
6
+ require 'pact/consumer/configuration/dsl'
7
+ require 'pact/consumer/configuration/configuration_extensions'
8
+
9
+
10
+ Pact.send(:extend, Pact::Consumer::DSL)
11
+ Pact::Configuration.send(:include, Pact::Consumer::Configuration::ConfigurationExtensions)
@@ -1,78 +1,75 @@
1
1
  require 'uri'
2
2
  require 'json/add/regexp'
3
- require 'pact/json_warning'
4
3
  require 'pact/logging'
4
+ require 'pact/consumer/mock_service_client'
5
+ require 'pact/consumer/interaction_builder'
5
6
 
6
7
  module Pact
7
8
  module Consumer
9
+
8
10
  class ConsumerContractBuilder
9
11
 
10
- include Pact::JsonWarning
11
12
  include Pact::Logging
12
13
 
13
- attr_reader :uri
14
- attr_reader :consumer_contract
15
- attr_reader :pactfile_write_mode
14
+ attr_reader :consumer_contract, :mock_service_base_url
16
15
 
17
16
  def initialize(attributes)
18
- @interactions = {}
19
- @producer_state = nil
20
- @pactfile_write_mode = attributes[:pactfile_write_mode]
21
- @consumer_contract = Pact::ConsumerContract.new(
22
- :consumer => ServiceConsumer.new(name: attributes[:consumer_name]),
23
- :producer => ServiceProducer.new(name: attributes[:producer_name])
24
- )
25
- @uri = URI("http://localhost:#{attributes[:port]}")
26
- @http = Net::HTTP.new(uri.host, uri.port)
27
- if pactfile_write_mode == :update && File.exist?(consumer_contract.pactfile_path)
28
- load_existing_interactions
29
- end
17
+ @interaction_builder = nil
18
+ @consumer_contract_details = {
19
+ consumer: {name: attributes[:consumer_name]},
20
+ provider: {name: attributes[:provider_name]},
21
+ pactfile_write_mode: attributes[:pactfile_write_mode].to_s
22
+ }
23
+ @mock_service_client = MockServiceClient.new(attributes[:port])
24
+ @mock_service_base_url = "http://localhost:#{attributes[:port]}"
25
+ end
26
+
27
+ def given(provider_state)
28
+ interaction_builder.given(provider_state)
30
29
  end
31
30
 
32
- def load_existing_interactions
33
- json = File.read(consumer_contract.pactfile_path)
34
- if json.size > 0
35
- existing_consumer_contract = Pact::ConsumerContract.from_json json
36
- existing_consumer_contract.interactions.each do | interaction |
37
- @interactions["#{interaction.description} given #{interaction.producer_state}"] = interaction
31
+ def upon_receiving(description)
32
+ interaction_builder.upon_receiving(description)
33
+ end
34
+
35
+ def interaction_builder
36
+ @interaction_builder ||=
37
+ begin
38
+ interaction_builder = InteractionBuilder.new
39
+ interaction_builder.on_interaction_fully_defined do | interaction |
40
+ self.handle_interaction_fully_defined(interaction)
38
41
  end
39
- consumer_contract.interactions = @interactions.values
42
+ interaction_builder
40
43
  end
41
44
  end
42
45
 
43
- def given(producer_state)
44
- @producer_state = producer_state
45
- self
46
+ def verify example_description
47
+ mock_service_client.verify example_description
48
+ end
49
+
50
+ def log msg
51
+ mock_service_client.log msg
46
52
  end
47
53
 
48
- def upon_receiving(description)
49
- interaction_builder = InteractionBuilder.new(description, @producer_state)
50
- producer = self
51
- interaction_builder.on_interaction_fully_defined do | interaction |
52
- producer.handle_interaction_fully_defined(interaction)
53
- end
54
- @interactions["#{description} given #{@producer_state}"] ||= interaction_builder.interaction
55
- consumer_contract.interactions = @interactions.values
56
- interaction_builder
54
+ def write_pact
55
+ mock_service_client.write_pact @consumer_contract_details
57
56
  end
58
57
 
59
- def handle_interaction_fully_defined interaction
60
- @http.request_post('/interactions', interaction.to_json_with_generated_response)
61
- @producer_state = nil
62
- consumer_contract.update_pactfile
58
+ def wait_for_interactions options = {}
59
+ wait_max_seconds = options.fetch(:wait_max_seconds, 3)
60
+ poll_interval = options.fetch(:poll_interval, 0.1)
61
+ mock_service_client.wait_for_interactions wait_max_seconds, poll_interval
63
62
  end
64
63
 
65
- def verify example_description
66
- http = Net::HTTP.new(uri.host, uri.port)
67
- response = http.request_get("/verify?example_description=#{URI.encode(example_description)}")
68
- raise response.body unless response.is_a? Net::HTTPSuccess
64
+ def handle_interaction_fully_defined interaction
65
+ mock_service_client.add_expected_interaction interaction #TODO: What will happen if duplicate added?
66
+ self.interaction_builder = nil
69
67
  end
70
68
 
71
69
  private
72
70
 
73
- def filenamify name
74
- name.downcase.gsub(/\s/, '_')
75
- end
71
+ attr_reader :mock_service_client
72
+ attr_writer :interaction_builder
76
73
 
77
74
  end
78
75
  end
@@ -0,0 +1,41 @@
1
+ require 'net/http'
2
+ require 'pact/reification'
3
+ require 'pact/consumer_contract/interaction'
4
+
5
+ module Pact
6
+ module Consumer
7
+ class InteractionBuilder
8
+
9
+ attr_reader :interaction
10
+
11
+ def initialize
12
+ @interaction = Interaction.new
13
+ end
14
+
15
+ def upon_receiving description
16
+ @interaction.description = description
17
+ self
18
+ end
19
+
20
+ def given provider_state
21
+ @interaction.provider_state = provider_state.nil? ? nil : provider_state.to_s
22
+ self
23
+ end
24
+
25
+ def with(request_details)
26
+ interaction.request = Pact::Request::Expected.from_hash(request_details)
27
+ self
28
+ end
29
+
30
+ def will_respond_with(response)
31
+ interaction.response = response
32
+ @callback.call interaction
33
+ self
34
+ end
35
+
36
+ def on_interaction_fully_defined &block
37
+ @callback = block
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ #
2
+ # When running in pactfile_write_mode :overwrite, all interactions are cleared from the
3
+ # pact file, and all new interactions should be distinct (unique description and provider state).
4
+ # When running in pactfile_write_mode :update, an interaction with the same description
5
+ # and provider state as an existing one will just overwrite that one interaction.
6
+ #
7
+
8
+ module Pact
9
+ module Consumer
10
+
11
+ #TODO: think of a better word than filter
12
+ class InteractionsFilter
13
+ def initialize interactions = []
14
+ @interactions = interactions
15
+ end
16
+
17
+ def index_of interaction
18
+ @interactions.find_index{ |i| i.matches_criteria?(description: interaction.description, provider_state: interaction.provider_state)}
19
+ end
20
+ end
21
+
22
+ class UpdatableInteractionsFilter < InteractionsFilter
23
+
24
+ def << interaction
25
+ if (ndx = index_of(interaction))
26
+ @interactions[ndx] = interaction
27
+ else
28
+ @interactions << interaction
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ class DistinctInteractionsFilter < InteractionsFilter
35
+
36
+ def << interaction
37
+ if (ndx = index_of(interaction))
38
+ if @interactions[ndx] != interaction
39
+ raise "Interaction with same description (#{interaction.description}) and provider state (#{interaction.provider_state}) already exists"
40
+ end
41
+ else
42
+ @interactions << interaction
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,82 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'logger'
4
+ require 'awesome_print'
5
+ require 'awesome_print/core_ext/logger' #For some reason we get an error indicating that the method 'ap' is private unless we load this specifically
6
+ require 'pact/consumer/request'
7
+ require 'pact/consumer/mock_service/interaction_list'
8
+ require 'pact/consumer/mock_service/interaction_delete'
9
+ require 'pact/consumer/mock_service/interaction_post'
10
+ require 'pact/consumer/mock_service/interaction_replay'
11
+ require 'pact/consumer/mock_service/missing_interactions_get'
12
+ require 'pact/consumer/mock_service/verification_get'
13
+ require 'pact/consumer/mock_service/log_get'
14
+ require 'pact/consumer/mock_service/pact_post'
15
+
16
+ AwesomePrint.defaults = {
17
+ indent: -2,
18
+ plain: true,
19
+ index: false
20
+ }
21
+
22
+ module Pact
23
+ module Consumer
24
+
25
+ class MockService
26
+
27
+ def initialize options = {}
28
+ log_description = configure_logger options
29
+ interaction_list = InteractionList.new
30
+
31
+ @name = options.fetch(:name, "MockService")
32
+ interactions = []
33
+ @handlers = [
34
+ MissingInteractionsGet.new(@name, @logger, interaction_list),
35
+ VerificationGet.new(@name, @logger, interaction_list, log_description),
36
+ InteractionPost.new(@name, @logger, interaction_list),
37
+ InteractionDelete.new(@name, @logger, interaction_list),
38
+ LogGet.new(@name, @logger),
39
+ PactPost.new(@name, @logger, interactions),
40
+ InteractionReplay.new(@name, @logger, interaction_list, interactions)
41
+ ]
42
+ end
43
+
44
+ def configure_logger options
45
+ options = {log_file: $stdout}.merge options
46
+ log_stream = options[:log_file]
47
+ @logger = Logger.new log_stream
48
+ @logger.level = Pact.configuration.logger.level
49
+
50
+ if log_stream.is_a? File
51
+ File.absolute_path(log_stream).gsub(Dir.pwd + "/", '')
52
+ else
53
+ "standard out/err"
54
+ end
55
+ end
56
+
57
+ def to_s
58
+ "#{@name} #{super.to_s}"
59
+ end
60
+
61
+ def call env
62
+ response = []
63
+ begin
64
+ relevant_handler = @handlers.detect { |handler| handler.match? env }
65
+ response = relevant_handler.respond env
66
+ rescue StandardError => e
67
+ @logger.error 'Error ocurred in mock service:'
68
+ @logger.ap e, :error
69
+ @logger.ap e.backtrace
70
+ response = [500, {'Content-Type' => 'application/json'}, [{message: e.message, backtrace: e.backtrace}.to_json]]
71
+ rescue Exception => e
72
+ @logger.error 'Exception ocurred in mock service:'
73
+ @logger.ap e, :error
74
+ @logger.ap e.backtrace
75
+ raise e
76
+ end
77
+ response
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,33 @@
1
+ require 'pact/consumer/mock_service/rack_request_helper'
2
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
3
+
4
+ module Pact
5
+ module Consumer
6
+
7
+ class InteractionDelete < MockServiceAdministrationEndpoint
8
+
9
+ include RackRequestHelper
10
+
11
+ attr_accessor :interaction_list
12
+
13
+ def initialize name, logger, interaction_list
14
+ super name, logger
15
+ @interaction_list = interaction_list
16
+ end
17
+
18
+ def request_path
19
+ '/interactions'
20
+ end
21
+
22
+ def request_method
23
+ 'DELETE'
24
+ end
25
+
26
+ def respond env
27
+ interaction_list.clear
28
+ logger.info "Cleared interactions before example \"#{params_hash(env)['example_description']}\""
29
+ [200, {}, ['Deleted interactions']]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,76 @@
1
+ module Pact
2
+ module Consumer
3
+ class InteractionList
4
+
5
+ attr_reader :interactions
6
+ attr_reader :unexpected_requests
7
+ attr_reader :interaction_mismatches
8
+
9
+ def initialize
10
+ clear
11
+ end
12
+
13
+ # For testing, sigh
14
+ def clear
15
+ @interactions = []
16
+ @matched_interactions = []
17
+ @interaction_mismatches = []
18
+ @unexpected_requests = []
19
+ end
20
+
21
+ def add interactions
22
+ @interactions << interactions
23
+ end
24
+
25
+ def register_matched interaction
26
+ @matched_interactions << interaction
27
+ end
28
+
29
+ def register_unexpected_request request
30
+ @unexpected_requests << request
31
+ end
32
+
33
+ def register_interaction_mismatch interaction_mismatch
34
+ @interaction_mismatches << interaction_mismatch
35
+ end
36
+
37
+ def all_matched?
38
+ interaction_diffs.empty?
39
+ end
40
+
41
+ def missing_interactions
42
+ @interactions - @matched_interactions - @interaction_mismatches.collect(&:candidate_interactions).flatten
43
+ end
44
+
45
+ def missing_interactions_summaries
46
+ missing_interactions.collect(&:request).collect(&:method_and_path)
47
+ end
48
+
49
+ def interaction_mismatches_summaries
50
+ interaction_mismatches.collect(&:short_summary)
51
+ end
52
+
53
+ def unexpected_requests_summaries
54
+ unexpected_requests.collect(&:method_and_path)
55
+ end
56
+
57
+ def interaction_diffs
58
+ {
59
+ :missing_interactions => missing_interactions_summaries,
60
+ :interaction_mismatches => interaction_mismatches_summaries,
61
+ :unexpected_requests => unexpected_requests_summaries
62
+ }.inject({}) do | hash, pair |
63
+ hash[pair.first] = pair.last if pair.last.any?
64
+ hash
65
+ end
66
+ end
67
+
68
+ def find_candidate_interactions actual_request
69
+ interactions.select do | interaction |
70
+ interaction.request.matches_route? actual_request
71
+ end
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,73 @@
1
+ module Pact
2
+ module Consumer
3
+
4
+ # Presents the differences between an actual request, and a list of
5
+ # expected interactions where the methods and paths match the actual request.
6
+ # This is used to display a helpful message to the user when a request
7
+ # comes in that doesn't match any of the expected interactions.
8
+ class InteractionMismatch
9
+
10
+ attr_accessor :candidate_interactions, :actual_request
11
+
12
+ # Assumes the method and path matches...
13
+
14
+ def initialize candidate_interactions, actual_request
15
+ @candidate_interactions = candidate_interactions
16
+ @actual_request = actual_request
17
+ @candiate_diffs = candidate_interactions.collect{ | candidate_interaction| CandidateDiff.new(candidate_interaction, actual_request)}
18
+ end
19
+
20
+ def to_hash
21
+ candiate_diffs.collect(&:to_hash)
22
+ end
23
+
24
+ def to_s
25
+ candiate_diffs.collect(&:to_s).join("\n")
26
+ end
27
+
28
+ def short_summary
29
+ mismatched_attributes = candiate_diffs.collect(&:mismatched_attributes).flatten.uniq.join(", ").reverse.sub(",", "dna ").reverse #OMG what a hack!
30
+ actual_request.method_and_path + " (request #{mismatched_attributes} did not match)"
31
+ end
32
+
33
+ private
34
+
35
+ attr_accessor :candiate_diffs
36
+
37
+ class CandidateDiff
38
+
39
+ attr_accessor :candidate_interaction, :actual_request
40
+
41
+ def initialize candidate_interaction, actual_request
42
+ @candidate_interaction = candidate_interaction
43
+ @actual_request = actual_request
44
+ end
45
+
46
+ def mismatched_attributes
47
+ diff.keys
48
+ end
49
+
50
+ def to_hash
51
+ summary = {:description => candidate_interaction.description}
52
+ summary[:provider_state] = candidate_interaction.provider_state if candidate_interaction.provider_state
53
+ summary.merge(diff)
54
+ end
55
+
56
+ def to_s
57
+ [
58
+ "Diff with interaction: #{candidate_interaction.description_with_provider_state_quoted}",
59
+ diff_formatter.call(diff, {colour: false})
60
+ ].join("\n")
61
+ end
62
+
63
+ def diff_formatter
64
+ Pact.configuration.diff_formatter_for_content_type(candidate_interaction.request.content_type)
65
+ end
66
+
67
+ def diff
68
+ @diff ||= candidate_interaction.request.difference(actual_request)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,31 @@
1
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
2
+
3
+ module Pact
4
+ module Consumer
5
+ class InteractionPost < MockServiceAdministrationEndpoint
6
+
7
+ attr_accessor :interaction_list
8
+
9
+ def initialize name, logger, interaction_list
10
+ super name, logger
11
+ @interaction_list = interaction_list
12
+ end
13
+
14
+ def request_path
15
+ '/interactions'
16
+ end
17
+
18
+ def request_method
19
+ 'POST'
20
+ end
21
+
22
+ def respond env
23
+ interaction = Interaction.from_hash(JSON.load(env['rack.input'].string))
24
+ interaction_list.add interaction
25
+ logger.info "Registered expected interaction #{interaction.request.method_and_path}"
26
+ logger.debug JSON.pretty_generate JSON.parse(interaction.to_json)
27
+ [200, {}, ['Added interaction']]
28
+ end
29
+ end
30
+ end
31
+ end