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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1321 -0
- data/LICENSE.txt +23 -0
- data/bin/pact +4 -0
- data/lib/pact/cli/generate_pact_docs.rb +4 -0
- data/lib/pact/cli/run_pact_verification.rb +99 -0
- data/lib/pact/cli/spec_criteria.rb +26 -0
- data/lib/pact/cli.rb +45 -0
- data/lib/pact/consumer/configuration/configuration_extensions.rb +90 -0
- data/lib/pact/consumer/configuration/dsl.rb +11 -0
- data/lib/pact/consumer/configuration/mock_service.rb +112 -0
- data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
- data/lib/pact/consumer/configuration/service_provider.rb +40 -0
- data/lib/pact/consumer/configuration.rb +10 -0
- data/lib/pact/consumer/consumer_contract_builder.rb +82 -0
- data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
- data/lib/pact/consumer/interaction_builder.rb +45 -0
- data/lib/pact/consumer/rspec.rb +35 -0
- data/lib/pact/consumer/spec_hooks.rb +40 -0
- data/lib/pact/consumer/world.rb +37 -0
- data/lib/pact/consumer.rb +7 -0
- data/lib/pact/doc/README.md +13 -0
- data/lib/pact/doc/doc_file.rb +40 -0
- data/lib/pact/doc/generate.rb +11 -0
- data/lib/pact/doc/generator.rb +82 -0
- data/lib/pact/doc/interaction_view_model.rb +124 -0
- data/lib/pact/doc/markdown/consumer_contract_renderer.rb +68 -0
- data/lib/pact/doc/markdown/generator.rb +26 -0
- data/lib/pact/doc/markdown/index_renderer.rb +43 -0
- data/lib/pact/doc/markdown/interaction.erb +14 -0
- data/lib/pact/doc/markdown/interaction_renderer.rb +43 -0
- data/lib/pact/doc/sort_interactions.rb +16 -0
- data/lib/pact/hal/authorization_header_redactor.rb +32 -0
- data/lib/pact/hal/entity.rb +110 -0
- data/lib/pact/hal/http_client.rb +128 -0
- data/lib/pact/hal/link.rb +112 -0
- data/lib/pact/hal/non_json_entity.rb +28 -0
- data/lib/pact/hash_refinements.rb +17 -0
- data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +112 -0
- data/lib/pact/pact_broker/fetch_pacts.rb +103 -0
- data/lib/pact/pact_broker/notices.rb +34 -0
- data/lib/pact/pact_broker/pact_selection_description.rb +66 -0
- data/lib/pact/pact_broker.rb +25 -0
- data/lib/pact/project_root.rb +7 -0
- data/lib/pact/provider/configuration/configuration_extension.rb +69 -0
- data/lib/pact/provider/configuration/dsl.rb +18 -0
- data/lib/pact/provider/configuration/message_provider_dsl.rb +63 -0
- data/lib/pact/provider/configuration/pact_verification.rb +48 -0
- data/lib/pact/provider/configuration/pact_verification_from_broker.rb +126 -0
- data/lib/pact/provider/configuration/service_provider_config.rb +32 -0
- data/lib/pact/provider/configuration/service_provider_dsl.rb +107 -0
- data/lib/pact/provider/configuration.rb +7 -0
- data/lib/pact/provider/context.rb +0 -0
- data/lib/pact/provider/help/console_text.rb +76 -0
- data/lib/pact/provider/help/content.rb +38 -0
- data/lib/pact/provider/help/pact_diff.rb +43 -0
- data/lib/pact/provider/help/prompt_text.rb +49 -0
- data/lib/pact/provider/help/write.rb +56 -0
- data/lib/pact/provider/matchers/messages.rb +66 -0
- data/lib/pact/provider/pact_helper_locator.rb +24 -0
- data/lib/pact/provider/pact_source.rb +40 -0
- data/lib/pact/provider/pact_spec_runner.rb +188 -0
- data/lib/pact/provider/pact_uri.rb +55 -0
- data/lib/pact/provider/pact_verification.rb +17 -0
- data/lib/pact/provider/print_missing_provider_states.rb +35 -0
- data/lib/pact/provider/request.rb +77 -0
- data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
- data/lib/pact/provider/rspec/calculate_exit_code.rb +18 -0
- data/lib/pact/provider/rspec/custom_options_file +0 -0
- data/lib/pact/provider/rspec/formatter_rspec_2.rb +76 -0
- data/lib/pact/provider/rspec/formatter_rspec_3.rb +195 -0
- data/lib/pact/provider/rspec/json_formatter.rb +100 -0
- data/lib/pact/provider/rspec/matchers.rb +80 -0
- data/lib/pact/provider/rspec/pact_broker_formatter.rb +76 -0
- data/lib/pact/provider/rspec.rb +234 -0
- data/lib/pact/provider/state/provider_state.rb +180 -0
- data/lib/pact/provider/state/provider_state_configured_modules.rb +15 -0
- data/lib/pact/provider/state/provider_state_manager.rb +42 -0
- data/lib/pact/provider/state/provider_state_proxy.rb +39 -0
- data/lib/pact/provider/state/set_up.rb +13 -0
- data/lib/pact/provider/state/tear_down.rb +13 -0
- data/lib/pact/provider/test_methods.rb +77 -0
- data/lib/pact/provider/verification_report.rb +36 -0
- data/lib/pact/provider/verification_results/create.rb +88 -0
- data/lib/pact/provider/verification_results/publish.rb +143 -0
- data/lib/pact/provider/verification_results/publish_all.rb +50 -0
- data/lib/pact/provider/verification_results/verification_result.rb +40 -0
- data/lib/pact/provider/world.rb +50 -0
- data/lib/pact/provider.rb +3 -0
- data/lib/pact/retry.rb +37 -0
- data/lib/pact/tasks/task_helper.rb +62 -0
- data/lib/pact/tasks/verification_task.rb +95 -0
- data/lib/pact/tasks.rb +2 -0
- data/lib/pact/templates/help.erb +22 -0
- data/lib/pact/templates/provider_state.erb +14 -0
- data/lib/pact/utils/metrics.rb +100 -0
- data/lib/pact/utils/string.rb +35 -0
- data/lib/pact/v2/configuration.rb +23 -0
- data/lib/pact/v2/consumer/grpc_interaction_builder.rb +187 -0
- data/lib/pact/v2/consumer/http_interaction_builder.rb +163 -0
- data/lib/pact/v2/consumer/interaction_contents.rb +54 -0
- data/lib/pact/v2/consumer/message_interaction_builder.rb +280 -0
- data/lib/pact/v2/consumer/mock_server.rb +99 -0
- data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
- data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
- data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
- data/lib/pact/v2/consumer/pact_config.rb +24 -0
- data/lib/pact/v2/consumer.rb +8 -0
- data/lib/pact/v2/matchers/base.rb +67 -0
- data/lib/pact/v2/matchers/v1/equality.rb +19 -0
- data/lib/pact/v2/matchers/v2/regex.rb +19 -0
- data/lib/pact/v2/matchers/v2/type.rb +17 -0
- data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
- data/lib/pact/v2/matchers/v3/date.rb +18 -0
- data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
- data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
- data/lib/pact/v2/matchers/v3/each.rb +42 -0
- data/lib/pact/v2/matchers/v3/include.rb +17 -0
- data/lib/pact/v2/matchers/v3/integer.rb +17 -0
- data/lib/pact/v2/matchers/v3/number.rb +17 -0
- data/lib/pact/v2/matchers/v3/time.rb +18 -0
- data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
- data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
- data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
- data/lib/pact/v2/matchers/v4/not_empty.rb +17 -0
- data/lib/pact/v2/matchers.rb +94 -0
- data/lib/pact/v2/native/blocking_verifier.rb +17 -0
- data/lib/pact/v2/native/logger.rb +25 -0
- data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
- data/lib/pact/v2/provider/base_verifier.rb +242 -0
- data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
- data/lib/pact/v2/provider/gruf_server.rb +75 -0
- data/lib/pact/v2/provider/http_server.rb +79 -0
- data/lib/pact/v2/provider/http_verifier.rb +43 -0
- data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
- data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
- data/lib/pact/v2/provider/pact_broker_proxy.rb +71 -0
- data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
- data/lib/pact/v2/provider/pact_config/async.rb +29 -0
- data/lib/pact/v2/provider/pact_config/base.rb +101 -0
- data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/provider/pact_config/http.rb +27 -0
- data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
- data/lib/pact/v2/provider/pact_config.rb +26 -0
- data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
- data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
- data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
- data/lib/pact/v2/provider.rb +8 -0
- data/lib/pact/v2/railtie.rb +13 -0
- data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +80 -0
- data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
- data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
- data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
- data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
- data/lib/pact/v2/rspec.rb +17 -0
- data/lib/pact/v2/tasks/pact.rake +13 -0
- data/lib/pact/v2/version.rb +8 -0
- data/lib/pact/v2.rb +71 -0
- data/lib/pact/version.rb +4 -0
- data/lib/pact.rb +13 -0
- data/lib/tasks/pact.rake +34 -0
- data/pact.gemspec +106 -0
- 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
|