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,82 @@
1
+ require 'pact/doc/doc_file'
2
+ require 'fileutils'
3
+
4
+ module Pact
5
+ module Doc
6
+
7
+ class Generator
8
+
9
+ def initialize pact_dir, doc_dir, options
10
+ @doc_dir = doc_dir
11
+ @pact_dir = pact_dir
12
+ @consumer_contract_renderer = options[:consumer_contract_renderer]
13
+ @doc_type = options[:doc_type]
14
+ @file_extension = options[:file_extension]
15
+ @index_renderer = options[:index_renderer]
16
+ @index_name = options[:index_name]
17
+ @after = options.fetch(:after, lambda{|pact_dir, target_dir, consumer_contracts| })
18
+ end
19
+
20
+ def call
21
+ ensure_target_dir_exists_and_is_clean
22
+ write_index if consumer_contracts.any?
23
+ write_doc_files
24
+ perform_after_hook
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :doc_dir, :pact_dir, :consumer_contract_renderer, :doc_type, :file_extension, :index_renderer, :after
30
+
31
+ def write_index
32
+ File.open(index_file_path, "w") { |io| io << index_file_contents }
33
+ end
34
+
35
+ def index_file_path
36
+ File.join(target_dir, "#{@index_name}#{file_extension}")
37
+ end
38
+
39
+ def index_file_contents
40
+ index_renderer.call(consumer_contracts.first.consumer.name, index_data)
41
+ end
42
+
43
+ def index_data
44
+ doc_files.each_with_object({}) do | doc_file, data |
45
+ data[doc_file.title] = doc_file.name
46
+ end
47
+ end
48
+
49
+ def write_doc_files
50
+ doc_files.each(&:write)
51
+ end
52
+
53
+ def doc_files
54
+ consumer_contracts.collect do | consumer_contract |
55
+ DocFile.new(consumer_contract, target_dir, consumer_contract_renderer, file_extension)
56
+ end
57
+ end
58
+
59
+ def consumer_contracts
60
+ @consumer_contracts ||= begin
61
+ Dir.glob("#{pact_dir}/**").collect do |file|
62
+ Pact::ConsumerContract.from_uri file
63
+ end
64
+ end
65
+ end
66
+
67
+ def perform_after_hook
68
+ after.call(pact_dir, target_dir, consumer_contracts)
69
+ end
70
+
71
+ def ensure_target_dir_exists_and_is_clean
72
+ FileUtils.rm_rf target_dir
73
+ FileUtils.mkdir_p target_dir
74
+ end
75
+
76
+ def target_dir
77
+ File.join(doc_dir, doc_type)
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,114 @@
1
+ require 'pact/shared/active_support_support'
2
+ require 'pact/reification'
3
+
4
+ module Pact
5
+ module Doc
6
+ class InteractionViewModel
7
+
8
+ include Pact::ActiveSupportSupport
9
+
10
+ def initialize interaction, consumer_contract
11
+ @interaction = interaction
12
+ @consumer_contract = consumer_contract
13
+ end
14
+
15
+ def id
16
+ @id ||= begin
17
+ if has_provider_state?
18
+ "#{description} given #{interaction.provider_state}"
19
+ else
20
+ interaction.description
21
+ end.gsub(/\s+/,'_')
22
+ end
23
+ end
24
+
25
+ def request_method
26
+ interaction.request.method.upcase
27
+ end
28
+
29
+ def request_path
30
+ interaction.request.path
31
+ end
32
+
33
+ def response_status
34
+ interaction.response['status']
35
+ end
36
+
37
+ def consumer_name
38
+ @consumer_contract.consumer.name
39
+ end
40
+
41
+ def provider_name
42
+ @consumer_contract.provider.name
43
+ end
44
+
45
+ def has_provider_state?
46
+ @interaction.provider_state && !@interaction.provider_state.empty?
47
+ end
48
+
49
+ def provider_state start_of_sentence = false
50
+ apply_capitals(@interaction.provider_state.strip, start_of_sentence)
51
+ end
52
+
53
+ def description start_of_sentence = false
54
+ apply_capitals(@interaction.description.strip, start_of_sentence)
55
+ end
56
+
57
+ def request
58
+ fix_json_formatting JSON.pretty_generate(clean_request)
59
+ end
60
+
61
+ def response
62
+ fix_json_formatting JSON.pretty_generate(clean_response)
63
+ end
64
+
65
+ private
66
+
67
+ attr_reader :interaction, :consumer_contract
68
+
69
+ def clean_request
70
+ ordered_clean_hash Reification.from_term(interaction.request)
71
+ end
72
+
73
+ def clean_response
74
+ ordered_clean_hash Reification.from_term(interaction.response)
75
+ end
76
+
77
+ # Remove empty body and headers hashes from response, and empty headers from request,
78
+ # as an empty hash means "allow anything" - it's more intuitive and cleaner to just
79
+ # remove the empty hashes from display.
80
+ def ordered_clean_hash source
81
+ ordered_keys.each_with_object({}) do |key, target|
82
+ if source.key? key
83
+ target[key] = source[key] unless value_is_an_empty_hash_that_is_not_request_body(source[key], key)
84
+ end
85
+ end
86
+ end
87
+
88
+ def value_is_an_empty_hash_that_is_not_request_body value, key
89
+ value.is_a?(Hash) && value.empty? && key != :body
90
+ end
91
+
92
+ def ordered_keys
93
+ [:method, :path, :query, :headers, :body, "status", "headers","body"]
94
+ end
95
+
96
+ def remove_key_if_empty key, hash
97
+ hash.delete(key) if hash[key].is_a?(Hash) && hash[key].empty?
98
+ end
99
+
100
+ def apply_capitals string, start_of_sentence = false
101
+ start_of_sentence ? capitalize_first_letter(string) : lowercase_first_letter(string)
102
+ end
103
+
104
+ def capitalize_first_letter string
105
+ string[0].upcase + string[1..-1]
106
+ end
107
+
108
+ def lowercase_first_letter string
109
+ string[0].downcase + string[1..-1]
110
+ end
111
+
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,56 @@
1
+ require 'pact/doc/markdown/interaction_renderer'
2
+ require 'pact/doc/sort_interactions'
3
+
4
+ module Pact
5
+ module Doc
6
+ module Markdown
7
+ class ConsumerContractRenderer
8
+
9
+ def initialize consumer_contract
10
+ @consumer_contract = consumer_contract
11
+ end
12
+
13
+ def self.call consumer_contract
14
+ new(consumer_contract).call
15
+ end
16
+
17
+ def call
18
+ title + summaries_title + summaries.join + interactions_title + full_interactions.join
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :consumer_contract
24
+
25
+ def title
26
+ "### A pact between #{consumer_contract.consumer.name} and #{consumer_contract.provider.name}\n\n"
27
+ end
28
+
29
+ def interaction_renderers
30
+ @interaction_renderers ||= sorted_interactions.collect{|interaction| InteractionRenderer.new interaction, @consumer_contract}
31
+ end
32
+
33
+ def summaries_title
34
+ "#### Requests from #{consumer_contract.consumer.name} to #{consumer_contract.provider.name}\n\n"
35
+ end
36
+
37
+ def interactions_title
38
+ "#### Interactions\n\n"
39
+ end
40
+
41
+ def summaries
42
+ interaction_renderers.collect(&:render_summary)
43
+ end
44
+
45
+ def full_interactions
46
+ interaction_renderers.collect(&:render_full_interaction)
47
+ end
48
+
49
+ def sorted_interactions
50
+ SortInteractions.call(consumer_contract.interactions)
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,26 @@
1
+ require 'pact/doc/generator'
2
+ require 'pact/doc/markdown/consumer_contract_renderer'
3
+ require 'pact/doc/markdown/index_renderer'
4
+
5
+ module Pact
6
+ module Doc
7
+ module Markdown
8
+ class Generator < Pact::Doc::Generator
9
+
10
+ def initialize pact_dir, doc_dir
11
+ super(pact_dir, doc_dir,
12
+ consumer_contract_renderer: ConsumerContractRenderer,
13
+ doc_type: 'markdown',
14
+ file_extension: '.md',
15
+ index_renderer: IndexRenderer,
16
+ index_name: 'README')
17
+ end
18
+
19
+ def self.call pact_dir, doc_dir
20
+ new(pact_dir, doc_dir).call
21
+ end
22
+
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ module Pact
2
+ module Doc
3
+ module Markdown
4
+ class IndexRenderer
5
+
6
+ attr_reader :consumer_name
7
+ attr_reader :docs # Hash of pact title => file_name
8
+
9
+ def initialize consumer_name, docs
10
+ @consumer_name = consumer_name
11
+ @docs = docs
12
+ end
13
+
14
+ def self.call consumer_name, docs
15
+ new(consumer_name, docs).call
16
+ end
17
+
18
+ def call
19
+ title + "\n\n" + table_of_contents + "\n"
20
+ end
21
+
22
+ private
23
+
24
+ def table_of_contents
25
+ docs.collect do | title, file_name |
26
+ item title, file_name
27
+ end.join("\n")
28
+ end
29
+
30
+ def title
31
+ "### Pacts for #{consumer_name}"
32
+ end
33
+
34
+ def item title, file_name
35
+ "* [#{title}](#{file_name})"
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,14 @@
1
+ <a name="<%= interaction.id %>"></a>
2
+ <%= if interaction.has_provider_state?
3
+ "Given **#{interaction.provider_state}**, upon receiving"
4
+ else
5
+ "Upon receiving"
6
+ end
7
+ %> **<%= interaction.description %>** from <%= interaction.consumer_name %>, with
8
+ ```json
9
+ <%= interaction.request %>
10
+ ```
11
+ <%= interaction.provider_name %> will respond with:
12
+ ```json
13
+ <%= interaction.response %>
14
+ ```
@@ -0,0 +1,38 @@
1
+ require 'pact/doc/interaction_view_model'
2
+
3
+ module Pact
4
+ module Doc
5
+ module Markdown
6
+ class InteractionRenderer
7
+
8
+ attr_reader :interaction
9
+
10
+ def initialize interaction, pact
11
+ @interaction = InteractionViewModel.new(interaction, pact)
12
+ end
13
+
14
+ def render_summary
15
+ suffix = interaction.has_provider_state? ? " given #{interaction.provider_state}" : ""
16
+ "* [#{interaction.description(true)}](##{interaction.id})#{suffix}\n\n"
17
+ end
18
+
19
+ def render_full_interaction
20
+ render('/interaction.erb')
21
+ end
22
+
23
+ def render template_file
24
+ ERB.new(template_string(template_file)).result(binding)
25
+ end
26
+
27
+ def template_string(template_file)
28
+ File.read( template_contents(template_file) )
29
+ end
30
+
31
+ def template_contents(template_file)
32
+ File.dirname(__FILE__) + template_file
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ module Pact
2
+ module Doc
3
+ class SortInteractions
4
+
5
+ def self.call interactions
6
+ interactions.sort{|a, b| sortable_id(a) <=> sortable_id(b)}
7
+ end
8
+
9
+ private
10
+
11
+ def self.sortable_id interaction
12
+ "#{interaction.description.downcase} #{interaction.response['status']} #{(interaction.provider_state || '').downcase}"
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ require 'pact/matchers/expected_type'
2
+
3
+
4
+ module Pact
5
+ class ActualType < Pact::ExpectedType
6
+
7
+ def initialize value
8
+ @value = value
9
+ end
10
+
11
+ def to_s
12
+ type
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ require 'pact/matchers/expected_type'
2
+ require 'pact/matchers/actual_type'
3
+
4
+ module Pact
5
+ module Matchers
6
+ class BaseDifference
7
+
8
+ attr_reader :expected, :actual
9
+
10
+ def initialize expected, actual
11
+ @expected = expected
12
+ @actual = actual
13
+ end
14
+
15
+ def any?
16
+ true
17
+ end
18
+
19
+ def empty?
20
+ false
21
+ end
22
+
23
+ def to_json options = {}
24
+ as_json.to_json(options)
25
+ end
26
+
27
+ def to_s
28
+ as_json.to_s
29
+ end
30
+
31
+ def == other
32
+ other.class == self.class && other.expected == expected && other.actual == actual
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,153 @@
1
+ # Ripped from RSpec::Expectations::Differ in rspec/expectations/differ.rb in rspec-expectations 2.14.3
2
+ # Thank you kindly to the original author.
3
+ # Needed to be able to turn the colour off, but can't set RSpec.configuration.color to false
4
+ # once it has been set to true due to a "if bool" at the start of the color= method
5
+
6
+ require 'diff/lcs'
7
+ require 'diff/lcs/hunk'
8
+ require 'pp'
9
+
10
+ module Pact
11
+ module Matchers
12
+ class Differ
13
+
14
+ def initialize(color = false)
15
+ @color = color
16
+ end
17
+
18
+ # This is snagged from diff/lcs/ldiff.rb (which is a commandline tool)
19
+ def diff_as_string(input_data_new, input_data_old)
20
+ output = matching_encoding("", input_data_old)
21
+ data_old = input_data_old.split(matching_encoding("\n", input_data_old)).map! { |e| e.chomp }
22
+ data_new = input_data_new.split(matching_encoding("\n", input_data_new)).map! { |e| e.chomp }
23
+ diffs = Diff::LCS.diff(data_old, data_new)
24
+ return output if diffs.empty?
25
+ oldhunk = hunk = nil
26
+ file_length_difference = 0
27
+ diffs.each do |piece|
28
+ begin
29
+ hunk = Diff::LCS::Hunk.new(
30
+ data_old, data_new, piece, context_lines, file_length_difference
31
+ )
32
+ file_length_difference = hunk.file_length_difference
33
+ next unless oldhunk
34
+ # Hunks may overlap, which is why we need to be careful when our
35
+ # diff includes lines of context. Otherwise, we might print
36
+ # redundant lines.
37
+ if (context_lines > 0) and hunk.overlaps?(oldhunk)
38
+ if hunk.respond_to?(:merge)
39
+ # diff-lcs 1.2.x
40
+ hunk.merge(oldhunk)
41
+ else
42
+ # diff-lcs 1.1.3
43
+ hunk.unshift(oldhunk)
44
+ end
45
+ else
46
+ output << matching_encoding(oldhunk.diff(format).to_s, output)
47
+ end
48
+ ensure
49
+ oldhunk = hunk
50
+ output << matching_encoding("\n", output)
51
+ end
52
+ end
53
+ #Handle the last remaining hunk
54
+ output << matching_encoding(oldhunk.diff(format).to_s,output)
55
+ output << matching_encoding("\n",output)
56
+ color_diff output
57
+ rescue Encoding::CompatibilityError
58
+ if input_data_new.encoding != input_data_old.encoding
59
+ "Could not produce a diff because the encoding of the actual string (#{input_data_old.encoding}) "+
60
+ "differs from the encoding of the expected string (#{input_data_new.encoding})"
61
+ else
62
+ "Could not produce a diff because of the encoding of the string (#{input_data_old.encoding})"
63
+ end
64
+ end
65
+
66
+ def diff_as_object(actual, expected)
67
+ actual_as_string = object_to_string(actual)
68
+ expected_as_string = object_to_string(expected)
69
+ if diff = diff_as_string(actual_as_string, expected_as_string)
70
+ color_diff diff
71
+ end
72
+ end
73
+
74
+ def red(text)
75
+ return text unless @color
76
+ color(text, 31)
77
+ end
78
+
79
+ def green(text)
80
+ return text unless @color
81
+ color(text, 32)
82
+ end
83
+
84
+ protected
85
+
86
+ def format
87
+ :unified
88
+ end
89
+
90
+ def context_lines
91
+ 3
92
+ end
93
+
94
+ def color(text, color_code)
95
+ "\e[#{color_code}m#{text}\e[0m"
96
+ end
97
+
98
+
99
+ def blue(text)
100
+ color(text, 34)
101
+ end
102
+
103
+ def color_diff(diff)
104
+ return diff unless @color
105
+
106
+ diff.lines.map { |line|
107
+ case line[0].chr
108
+ when "+"
109
+ green line
110
+ when "-"
111
+ red line
112
+ when "@"
113
+ line[1].chr == "@" ? blue(line) : line
114
+ else
115
+ line
116
+ end
117
+ }.join
118
+ end
119
+
120
+ def object_to_string(object)
121
+ case object
122
+ when Hash
123
+ object.keys.sort_by { |k| k.to_s }.map do |key|
124
+ pp_key = PP.singleline_pp(key, "")
125
+ pp_value = PP.singleline_pp(object[key], "")
126
+
127
+ # on 1.9.3 PP seems to minimise to US-ASCII, ensure we're matching source encoding
128
+ #
129
+ # note, PP is used to ensure the ordering of the internal values of key/value e.g.
130
+ # <# a: b: c:> not <# c: a: b:>
131
+ matching_encoding("#{pp_key} => #{pp_value}", key.to_s)
132
+ end.join(",\n")
133
+ when String
134
+ object =~ /\n/ ? object : object.inspect
135
+ else
136
+ PP.pp(object,"")
137
+ end
138
+ end
139
+
140
+ if String.method_defined?(:encoding)
141
+ def matching_encoding(string, source)
142
+ string.encode(source.encoding)
143
+ end
144
+ else
145
+ def matching_encoding(string, source)
146
+ string
147
+ end
148
+ end
149
+ end
150
+
151
+ end
152
+ end
153
+
@@ -0,0 +1,13 @@
1
+ require 'pact/matchers/base_difference'
2
+
3
+ module Pact
4
+ module Matchers
5
+ class Difference < BaseDifference
6
+
7
+ def as_json options = {}
8
+ {:EXPECTED => expected, :ACTUAL => actual}
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require 'pact/shared/active_support_support'
2
+
3
+ module Pact
4
+ class DifferenceIndicator
5
+
6
+ include ActiveSupportSupport
7
+
8
+ def == other
9
+ other.class == self.class
10
+ end
11
+
12
+ def eql? other
13
+ self == other
14
+ end
15
+
16
+ def to_json options = {}
17
+ remove_unicode as_json.to_json(options)
18
+ end
19
+
20
+ def as_json options = {}
21
+ to_s
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,62 @@
1
+ require 'pact/shared/active_support_support'
2
+ require 'term/ansicolor'
3
+
4
+ module Pact
5
+ module Matchers
6
+ class EmbeddedDiffFormatter
7
+
8
+ include Pact::ActiveSupportSupport
9
+ C = ::Term::ANSIColor
10
+
11
+
12
+ EXPECTED = /"EXPECTED([A-Z_]*)":/
13
+
14
+ ACTUAL = /"ACTUAL([A-Z_]*)":/
15
+
16
+ attr_reader :diff, :colour
17
+
18
+ def initialize diff, options = {}
19
+ @diff = diff
20
+ @colour = options.fetch(:colour, false)
21
+ end
22
+
23
+ def self.call diff, options = {colour: Pact.configuration.color_enabled}
24
+ new(diff, options).call
25
+ end
26
+
27
+ def call
28
+ to_s
29
+ end
30
+
31
+ def to_hash
32
+ diff
33
+ end
34
+
35
+ def to_s
36
+ colourise_message_if_configured fix_json_formatting(diff.to_json)
37
+ end
38
+
39
+ def colourise_message_if_configured message
40
+ if colour
41
+ colourise_message message
42
+ else
43
+ message
44
+ end
45
+ end
46
+
47
+ def colourise_message message
48
+ message.split("\n").collect{| line | colourise(line) }.join("\n")
49
+ end
50
+
51
+ def colourise line
52
+ line.gsub(EXPECTED){|match| coloured_key match, :red }.gsub(ACTUAL){ | match | coloured_key match, :green }
53
+ end
54
+
55
+ def coloured_key match, colour
56
+ '"' + C.color(colour, match.downcase.gsub(/^"|":$/,'')) + '":'
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+ end