pact-v2 2.0.0.pre.preview1

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 (164) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1321 -0
  3. data/LICENSE.txt +23 -0
  4. data/bin/pact +4 -0
  5. data/lib/pact/cli/generate_pact_docs.rb +4 -0
  6. data/lib/pact/cli/run_pact_verification.rb +99 -0
  7. data/lib/pact/cli/spec_criteria.rb +26 -0
  8. data/lib/pact/cli.rb +45 -0
  9. data/lib/pact/consumer/configuration/configuration_extensions.rb +90 -0
  10. data/lib/pact/consumer/configuration/dsl.rb +11 -0
  11. data/lib/pact/consumer/configuration/mock_service.rb +112 -0
  12. data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
  13. data/lib/pact/consumer/configuration/service_provider.rb +40 -0
  14. data/lib/pact/consumer/configuration.rb +10 -0
  15. data/lib/pact/consumer/consumer_contract_builder.rb +82 -0
  16. data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
  17. data/lib/pact/consumer/interaction_builder.rb +45 -0
  18. data/lib/pact/consumer/rspec.rb +35 -0
  19. data/lib/pact/consumer/spec_hooks.rb +40 -0
  20. data/lib/pact/consumer/world.rb +37 -0
  21. data/lib/pact/consumer.rb +7 -0
  22. data/lib/pact/doc/README.md +13 -0
  23. data/lib/pact/doc/doc_file.rb +40 -0
  24. data/lib/pact/doc/generate.rb +11 -0
  25. data/lib/pact/doc/generator.rb +82 -0
  26. data/lib/pact/doc/interaction_view_model.rb +124 -0
  27. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +68 -0
  28. data/lib/pact/doc/markdown/generator.rb +26 -0
  29. data/lib/pact/doc/markdown/index_renderer.rb +43 -0
  30. data/lib/pact/doc/markdown/interaction.erb +14 -0
  31. data/lib/pact/doc/markdown/interaction_renderer.rb +43 -0
  32. data/lib/pact/doc/sort_interactions.rb +16 -0
  33. data/lib/pact/hal/authorization_header_redactor.rb +32 -0
  34. data/lib/pact/hal/entity.rb +110 -0
  35. data/lib/pact/hal/http_client.rb +128 -0
  36. data/lib/pact/hal/link.rb +112 -0
  37. data/lib/pact/hal/non_json_entity.rb +28 -0
  38. data/lib/pact/hash_refinements.rb +17 -0
  39. data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +112 -0
  40. data/lib/pact/pact_broker/fetch_pacts.rb +103 -0
  41. data/lib/pact/pact_broker/notices.rb +34 -0
  42. data/lib/pact/pact_broker/pact_selection_description.rb +66 -0
  43. data/lib/pact/pact_broker.rb +25 -0
  44. data/lib/pact/project_root.rb +7 -0
  45. data/lib/pact/provider/configuration/configuration_extension.rb +69 -0
  46. data/lib/pact/provider/configuration/dsl.rb +18 -0
  47. data/lib/pact/provider/configuration/message_provider_dsl.rb +63 -0
  48. data/lib/pact/provider/configuration/pact_verification.rb +48 -0
  49. data/lib/pact/provider/configuration/pact_verification_from_broker.rb +126 -0
  50. data/lib/pact/provider/configuration/service_provider_config.rb +32 -0
  51. data/lib/pact/provider/configuration/service_provider_dsl.rb +107 -0
  52. data/lib/pact/provider/configuration.rb +7 -0
  53. data/lib/pact/provider/context.rb +0 -0
  54. data/lib/pact/provider/help/console_text.rb +76 -0
  55. data/lib/pact/provider/help/content.rb +38 -0
  56. data/lib/pact/provider/help/pact_diff.rb +43 -0
  57. data/lib/pact/provider/help/prompt_text.rb +49 -0
  58. data/lib/pact/provider/help/write.rb +56 -0
  59. data/lib/pact/provider/matchers/messages.rb +66 -0
  60. data/lib/pact/provider/pact_helper_locator.rb +24 -0
  61. data/lib/pact/provider/pact_source.rb +40 -0
  62. data/lib/pact/provider/pact_spec_runner.rb +188 -0
  63. data/lib/pact/provider/pact_uri.rb +55 -0
  64. data/lib/pact/provider/pact_verification.rb +17 -0
  65. data/lib/pact/provider/print_missing_provider_states.rb +35 -0
  66. data/lib/pact/provider/request.rb +77 -0
  67. data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
  68. data/lib/pact/provider/rspec/calculate_exit_code.rb +18 -0
  69. data/lib/pact/provider/rspec/custom_options_file +0 -0
  70. data/lib/pact/provider/rspec/formatter_rspec_2.rb +76 -0
  71. data/lib/pact/provider/rspec/formatter_rspec_3.rb +195 -0
  72. data/lib/pact/provider/rspec/json_formatter.rb +100 -0
  73. data/lib/pact/provider/rspec/matchers.rb +80 -0
  74. data/lib/pact/provider/rspec/pact_broker_formatter.rb +76 -0
  75. data/lib/pact/provider/rspec.rb +234 -0
  76. data/lib/pact/provider/state/provider_state.rb +180 -0
  77. data/lib/pact/provider/state/provider_state_configured_modules.rb +15 -0
  78. data/lib/pact/provider/state/provider_state_manager.rb +42 -0
  79. data/lib/pact/provider/state/provider_state_proxy.rb +39 -0
  80. data/lib/pact/provider/state/set_up.rb +13 -0
  81. data/lib/pact/provider/state/tear_down.rb +13 -0
  82. data/lib/pact/provider/test_methods.rb +77 -0
  83. data/lib/pact/provider/verification_report.rb +36 -0
  84. data/lib/pact/provider/verification_results/create.rb +88 -0
  85. data/lib/pact/provider/verification_results/publish.rb +143 -0
  86. data/lib/pact/provider/verification_results/publish_all.rb +50 -0
  87. data/lib/pact/provider/verification_results/verification_result.rb +40 -0
  88. data/lib/pact/provider/world.rb +50 -0
  89. data/lib/pact/provider.rb +3 -0
  90. data/lib/pact/retry.rb +37 -0
  91. data/lib/pact/tasks/task_helper.rb +62 -0
  92. data/lib/pact/tasks/verification_task.rb +95 -0
  93. data/lib/pact/tasks.rb +2 -0
  94. data/lib/pact/templates/help.erb +22 -0
  95. data/lib/pact/templates/provider_state.erb +14 -0
  96. data/lib/pact/utils/metrics.rb +100 -0
  97. data/lib/pact/utils/string.rb +35 -0
  98. data/lib/pact/v2/configuration.rb +23 -0
  99. data/lib/pact/v2/consumer/grpc_interaction_builder.rb +187 -0
  100. data/lib/pact/v2/consumer/http_interaction_builder.rb +163 -0
  101. data/lib/pact/v2/consumer/interaction_contents.rb +54 -0
  102. data/lib/pact/v2/consumer/message_interaction_builder.rb +280 -0
  103. data/lib/pact/v2/consumer/mock_server.rb +99 -0
  104. data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
  105. data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
  106. data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
  107. data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
  108. data/lib/pact/v2/consumer/pact_config.rb +24 -0
  109. data/lib/pact/v2/consumer.rb +8 -0
  110. data/lib/pact/v2/matchers/base.rb +67 -0
  111. data/lib/pact/v2/matchers/v1/equality.rb +19 -0
  112. data/lib/pact/v2/matchers/v2/regex.rb +19 -0
  113. data/lib/pact/v2/matchers/v2/type.rb +17 -0
  114. data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
  115. data/lib/pact/v2/matchers/v3/date.rb +18 -0
  116. data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
  117. data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
  118. data/lib/pact/v2/matchers/v3/each.rb +42 -0
  119. data/lib/pact/v2/matchers/v3/include.rb +17 -0
  120. data/lib/pact/v2/matchers/v3/integer.rb +17 -0
  121. data/lib/pact/v2/matchers/v3/number.rb +17 -0
  122. data/lib/pact/v2/matchers/v3/time.rb +18 -0
  123. data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
  124. data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
  125. data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
  126. data/lib/pact/v2/matchers/v4/not_empty.rb +17 -0
  127. data/lib/pact/v2/matchers.rb +94 -0
  128. data/lib/pact/v2/native/blocking_verifier.rb +17 -0
  129. data/lib/pact/v2/native/logger.rb +25 -0
  130. data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
  131. data/lib/pact/v2/provider/base_verifier.rb +242 -0
  132. data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
  133. data/lib/pact/v2/provider/gruf_server.rb +75 -0
  134. data/lib/pact/v2/provider/http_server.rb +79 -0
  135. data/lib/pact/v2/provider/http_verifier.rb +43 -0
  136. data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
  137. data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
  138. data/lib/pact/v2/provider/pact_broker_proxy.rb +71 -0
  139. data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
  140. data/lib/pact/v2/provider/pact_config/async.rb +29 -0
  141. data/lib/pact/v2/provider/pact_config/base.rb +101 -0
  142. data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
  143. data/lib/pact/v2/provider/pact_config/http.rb +27 -0
  144. data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
  145. data/lib/pact/v2/provider/pact_config.rb +26 -0
  146. data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
  147. data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
  148. data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
  149. data/lib/pact/v2/provider.rb +8 -0
  150. data/lib/pact/v2/railtie.rb +13 -0
  151. data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +80 -0
  152. data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
  153. data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
  154. data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
  155. data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
  156. data/lib/pact/v2/rspec.rb +17 -0
  157. data/lib/pact/v2/tasks/pact.rake +13 -0
  158. data/lib/pact/v2/version.rb +8 -0
  159. data/lib/pact/v2.rb +71 -0
  160. data/lib/pact/version.rb +4 -0
  161. data/lib/pact.rb +13 -0
  162. data/lib/tasks/pact.rake +34 -0
  163. data/pact.gemspec +106 -0
  164. metadata +529 -0
@@ -0,0 +1,35 @@
1
+ require 'pact/consumer'
2
+ require 'pact/consumer/spec_hooks'
3
+ require 'pact/rspec'
4
+ require 'pact/helpers'
5
+
6
+ module Pact
7
+ module Consumer
8
+ module RSpec
9
+ include Pact::Consumer::ConsumerContractBuilders
10
+ include Pact::Helpers
11
+ end
12
+ end
13
+ end
14
+
15
+ hooks = Pact::Consumer::SpecHooks.new
16
+
17
+ RSpec.configure do |config|
18
+ config.include Pact::Consumer::RSpec, :pact => true
19
+
20
+ config.before :all, :pact => true do
21
+ hooks.before_all
22
+ end
23
+
24
+ config.before :each, :pact => true do | example |
25
+ hooks.before_each Pact::RSpec.full_description(example)
26
+ end
27
+
28
+ config.after :each, :pact => true do | example |
29
+ hooks.after_each Pact::RSpec.full_description(example)
30
+ end
31
+
32
+ config.after :suite do
33
+ hooks.after_suite
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ require 'pact/doc/generate'
2
+ require 'pact/consumer/world'
3
+ require 'pact/mock_service/app_manager'
4
+ require 'pact/mock_service/client'
5
+
6
+ module Pact
7
+ module Consumer
8
+ class SpecHooks
9
+
10
+ def before_all
11
+ Pact::MockService::AppManager.instance.spawn_all
12
+ FileUtils.mkdir_p Pact.configuration.pact_dir
13
+ end
14
+
15
+ def before_each example_description
16
+ Pact.consumer_world.register_pact_example_ran
17
+ Pact.configuration.logger.info "Clearing all expectations"
18
+ Pact::MockService::AppManager.instance.urls_of_mock_services.each do | url |
19
+ Pact::MockService::Client.clear_interactions url, example_description
20
+ end
21
+ end
22
+
23
+ def after_each example_description
24
+ Pact.configuration.logger.info "Verifying interactions for #{example_description}"
25
+ Pact.configuration.provider_verifications.each do | provider_verification |
26
+ provider_verification.call example_description
27
+ end
28
+ end
29
+
30
+ def after_suite
31
+ if Pact.consumer_world.any_pact_examples_ran?
32
+ Pact.consumer_world.consumer_contract_builders.each(&:write_pact)
33
+ Pact::Doc::Generate.call
34
+ Pact::MockService::AppManager.instance.kill_all
35
+ Pact::MockService::AppManager.instance.clear_all
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ module Pact
2
+
3
+ def self.consumer_world
4
+ @consumer_world ||= Pact::Consumer::World.new
5
+ end
6
+
7
+ # internal api, for testing only
8
+ def self.clear_consumer_world
9
+ @consumer_world = nil
10
+ end
11
+
12
+ module Consumer
13
+ class World
14
+
15
+ def initialize
16
+ @any_pact_examples_ran = false
17
+ end
18
+
19
+ def consumer_contract_builders
20
+ @consumer_contract_builders ||= []
21
+ end
22
+
23
+ def add_consumer_contract_builder consumer_contract_builder
24
+ consumer_contract_builders << consumer_contract_builder
25
+ end
26
+
27
+ def register_pact_example_ran
28
+ @any_pact_examples_ran = true
29
+ end
30
+
31
+ def any_pact_examples_ran?
32
+ @any_pact_examples_ran
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ require 'pact/consumer_contract'
2
+ require 'pact/consumer/configuration'
3
+ require 'pact/consumer/consumer_contract_builder'
4
+ require 'pact/consumer/consumer_contract_builders'
5
+ require 'pact/consumer/interaction_builder'
6
+ require 'pact/term'
7
+ require 'pact/something_like'
@@ -0,0 +1,13 @@
1
+ # How to roll your own Doc Generator
2
+
3
+ 1. Create a ConsumerContractRenderer that responds to `call` and accepts a `ConsumerContract` (this is the name for the domain model of a "pact"). This should return a String. For an example, see the [Markdown::ConsumerContractRenderer][consumer_contract_renderer].
4
+ 2. Create an IndexRenderer. This allows you to create an index file for your docs. It should respond to `call` and accept the String name of the consumer, and a hash of Hash of `pact title => file_name`, and return a String. For an example, see the [Markdown::IndexRenderer][index_renderer].
5
+ 3. Create a Generator. This is responsible for the overall file generating and writing process. Copy the [Markdown::Generator][generator] and configure it with your own ConsumerContractRenderer, IndexRenderer and file details.
6
+
7
+ If you would like to generate HTML documentation, see how the [HTMLPactRenderer][html_pact_renderer] in the Pact Broker does it.
8
+
9
+ [consumer_contract_renderer]: https://github.com/pact-foundation/pact-ruby/blob/master/lib/pact/doc/markdown/consumer_contract_renderer.rb
10
+ [index_renderer]: https://github.com/pact-foundation/pact-ruby/blob/master/lib/pact/doc/markdown/index_renderer.rb
11
+ [generator]: https://github.com/pact-foundation/pact-ruby/blob/master/lib/pact/doc/markdown/generator.rb
12
+ [html_pact_renderer]: https://github.com/pact-foundation/pact_broker/blob/master/lib/pact_broker/api/renderers/html_pact_renderer.rb
13
+
@@ -0,0 +1,40 @@
1
+ module Pact
2
+ module Doc
3
+
4
+ class DocFile
5
+
6
+ def initialize consumer_contract, dir, consumer_contract_renderer, file_extension
7
+ @dir = dir
8
+ @consumer_contract = consumer_contract
9
+ @consumer_contract_renderer = consumer_contract_renderer
10
+ @file_extension = file_extension
11
+ end
12
+
13
+ def write
14
+ File.open(path, "w") { |io| io << doc_file_contents }
15
+ end
16
+
17
+ def title
18
+ consumer_contract.provider.name
19
+ end
20
+
21
+ def name
22
+ "#{consumer_contract.consumer.name} - #{consumer_contract.provider.name}#{file_extension}"
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :dir, :consumer_contract, :consumer_contract_renderer, :file_extension
28
+
29
+
30
+ def path
31
+ File.join(dir, name)
32
+ end
33
+
34
+ def doc_file_contents
35
+ consumer_contract_renderer.call(consumer_contract)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ module Pact
2
+ module Doc
3
+ class Generate
4
+
5
+ def self.call pact_dir = Pact.configuration.pact_dir, doc_dir = Pact.configuration.doc_dir, doc_generators = Pact.configuration.doc_generators
6
+ doc_generators.each{| doc_generator| doc_generator.call pact_dir, doc_dir }
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -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,124 @@
1
+ require 'pact/shared/active_support_support'
2
+ require 'pact/reification'
3
+ require 'cgi'
4
+
5
+ module Pact
6
+ module Doc
7
+ class InteractionViewModel
8
+
9
+ include Pact::ActiveSupportSupport
10
+
11
+ def initialize interaction, consumer_contract
12
+ @interaction = interaction
13
+ @consumer_contract = consumer_contract
14
+ end
15
+
16
+ def id
17
+ @id ||= begin
18
+ full_desc = if has_provider_state?
19
+ "#{description} given #{interaction.provider_state}"
20
+ else
21
+ description
22
+ end
23
+ CGI.escapeHTML(full_desc.gsub(/\s+/,'_'))
24
+ end
25
+ end
26
+
27
+ def request_method
28
+ interaction.request.method.upcase
29
+ end
30
+
31
+ def request_path
32
+ interaction.request.path
33
+ end
34
+
35
+ def response_status
36
+ interaction.response.status
37
+ end
38
+
39
+ def consumer_name
40
+ markdown_escape @consumer_contract.consumer.name
41
+ end
42
+
43
+ def provider_name
44
+ markdown_escape @consumer_contract.provider.name
45
+ end
46
+
47
+ def has_provider_state?
48
+ @interaction.provider_state && !@interaction.provider_state.empty?
49
+ end
50
+
51
+ def provider_state start_of_sentence = false
52
+ markdown_escape apply_capitals(@interaction.provider_state.strip, start_of_sentence)
53
+ end
54
+
55
+ def description start_of_sentence = false
56
+ return '' unless @interaction.description
57
+ markdown_escape apply_capitals(@interaction.description.strip, start_of_sentence)
58
+ end
59
+
60
+ def request
61
+ fix_json_formatting JSON.pretty_generate(clean_request)
62
+ end
63
+
64
+ def response
65
+ fix_json_formatting JSON.pretty_generate(clean_response)
66
+ end
67
+
68
+ private
69
+
70
+ attr_reader :interaction, :consumer_contract
71
+
72
+ def clean_request
73
+ reified_request = Reification.from_term(interaction.request)
74
+ ordered_clean_hash(reified_request).tap do | hash |
75
+ hash[:body] = reified_request[:body] if reified_request[:body]
76
+ end
77
+ end
78
+
79
+ def clean_response
80
+ ordered_clean_hash Reification.from_term(interaction.response)
81
+ end
82
+
83
+ # Remove empty body and headers hashes from response, and empty headers from request,
84
+ # as an empty hash means "allow anything" - it's more intuitive and cleaner to just
85
+ # remove the empty hashes from display.
86
+ def ordered_clean_hash source
87
+ ordered_keys.each_with_object({}) do |key, target|
88
+ if source.key? key
89
+ target[key] = source[key] unless value_is_an_empty_hash(source[key])
90
+ end
91
+ end
92
+ end
93
+
94
+ def value_is_an_empty_hash value
95
+ value.is_a?(Hash) && value.empty?
96
+ end
97
+
98
+ def ordered_keys
99
+ [:method, :path, :query, :status, :headers, :body]
100
+ end
101
+
102
+ def remove_key_if_empty key, hash
103
+ hash.delete(key) if hash[key].is_a?(Hash) && hash[key].empty?
104
+ end
105
+
106
+ def apply_capitals string, start_of_sentence = false
107
+ start_of_sentence ? capitalize_first_letter(string) : lowercase_first_letter(string)
108
+ end
109
+
110
+ def capitalize_first_letter string
111
+ string[0].upcase + string[1..-1]
112
+ end
113
+
114
+ def lowercase_first_letter string
115
+ string[0].downcase + string[1..-1]
116
+ end
117
+
118
+ def markdown_escape string
119
+ return nil unless string
120
+ string.gsub('*','\*').gsub('_','\_')
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,68 @@
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_name} and #{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_name} to #{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
+ def consumer_name
54
+ markdown_escape consumer_contract.consumer.name
55
+ end
56
+
57
+ def provider_name
58
+ markdown_escape consumer_contract.provider.name
59
+ end
60
+
61
+ def markdown_escape string
62
+ string.gsub('*','\*').gsub('_','\_')
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ 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,43 @@
1
+ require 'erb'
2
+
3
+ module Pact
4
+ module Doc
5
+ module Markdown
6
+ class IndexRenderer
7
+
8
+ attr_reader :consumer_name
9
+ attr_reader :docs # Hash of pact title => file_name
10
+
11
+ def initialize consumer_name, docs
12
+ @consumer_name = consumer_name
13
+ @docs = docs
14
+ end
15
+
16
+ def self.call consumer_name, docs
17
+ new(consumer_name, docs).call
18
+ end
19
+
20
+ def call
21
+ title + "\n\n" + table_of_contents + "\n"
22
+ end
23
+
24
+ private
25
+
26
+ def table_of_contents
27
+ docs.collect do | title, file_name |
28
+ item title, file_name
29
+ end.join("\n")
30
+ end
31
+
32
+ def title
33
+ "### Pacts for #{consumer_name}"
34
+ end
35
+
36
+ def item title, file_name
37
+ "* [#{title}](#{ERB::Util.url_encode(file_name)})"
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ 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,43 @@
1
+ require 'erb'
2
+ require 'pact/doc/interaction_view_model'
3
+
4
+ module Pact
5
+ module Doc
6
+ module Markdown
7
+ class InteractionRenderer
8
+
9
+ attr_reader :interaction
10
+
11
+ def initialize interaction, pact
12
+ @interaction = InteractionViewModel.new(interaction, pact)
13
+ end
14
+
15
+ def render_summary
16
+ suffix = interaction.has_provider_state? ? " given #{interaction.provider_state}" : ""
17
+ "* [#{interaction.description(true)}](##{interaction.id})#{suffix}\n\n"
18
+ end
19
+
20
+ def render_full_interaction
21
+ render('/interaction.erb')
22
+ end
23
+
24
+ def render template_file
25
+ ERB.new(template_string(template_file)).result(binding)
26
+ end
27
+
28
+ # The template file is written with only ASCII range characters, so we
29
+ # can read as UTF-8. But rendered strings must have same encoding as
30
+ # script encoding because it will joined to strings which are produced by
31
+ # string literal.
32
+ def template_string(template_file)
33
+ File.read(template_contents(template_file), external_encoding: Encoding::UTF_8).force_encoding(__ENCODING__)
34
+ end
35
+
36
+ def template_contents(template_file)
37
+ File.dirname(__FILE__) + template_file
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ module Pact
2
+ module Doc
3
+ class SortInteractions
4
+
5
+ def self.call interactions
6
+ interactions.sort_by { |interaction| sortable_id(interaction) }
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
+ end
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ require 'delegate'
2
+
3
+ module Pact
4
+ module Hal
5
+ class AuthorizationHeaderRedactor < SimpleDelegator
6
+ def puts(*args)
7
+ __getobj__().puts(*redact_args(args))
8
+ end
9
+
10
+ def print(*args)
11
+ __getobj__().puts(*redact_args(args))
12
+ end
13
+
14
+ def <<(*args)
15
+ __getobj__().send(:<<, *redact_args(args))
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :redactions
21
+
22
+ def redact_args(args)
23
+ args.collect{ | s| redact(s) }
24
+ end
25
+
26
+ def redact(string)
27
+ return string unless string.is_a?(String)
28
+ string.gsub(/Authorization: .*\\r\\n/, "Authorization: [redacted]\\r\\n")
29
+ end
30
+ end
31
+ end
32
+ end