pact_broker 2.13.0 → 2.13.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7427bd8d5ba4ffcaead942be481675c3a275774
4
- data.tar.gz: 86b168d56a8937c4b4461fee9a21f6d4af626c33
3
+ metadata.gz: 6493a06ed84ec11bd1c966faddbe3ddb2b948dc1
4
+ data.tar.gz: f2edee66e10d7ab2fbc8183ff3a685eca1fb20b6
5
5
  SHA512:
6
- metadata.gz: ddd1b99c2162ab7d736f5680c779b1f733c34841959f8803e6289eabdfb9a8d2840188b9af7ab643cf57f86d29a5130ef490a09954db36c001f564c2d6edc10c
7
- data.tar.gz: fec0eaa9e4d5f661e3962010cbe87a22db04de9fdd17bcf46adb8653286bd6f0a3e54c31b5e3be651a21acc40f15013c4ff549efd7b6ed0cc608456c7c1fe4f9
6
+ metadata.gz: 0ddb05561ad94aeb5a3faef53dd0a96b7ca5550f312810f47e1640303298d3f6ef065e29b30d1b41a5928e46d6cba568bacf7c311c745d4f3af0d66d19cb3342
7
+ data.tar.gz: 905912e0bb8021d08d1d6f8f9ca9cc6d45cc3eafa0fd51dbbfb33ab8297627d3ad437472b5b3b2cf5de6ef5fdce736a14066591f8221a239c2ac56ad8e41483a
@@ -1,3 +1,12 @@
1
+ <a name="v2.13.1"></a>
2
+ ### v2.13.1 (2018-01-12)
3
+
4
+
5
+ #### Features
6
+
7
+ * remove runtime dependency on webrick ([1ae7a6e](/../../commit/1ae7a6e))
8
+
9
+
1
10
  <a name="v2.13.0"></a>
2
11
  ### v2.13.0 (2018-01-11)
3
12
 
@@ -0,0 +1,5 @@
1
+ # Pact::Doc
2
+
3
+ The code in pact/doc has been copied from the pact gem to break the runtime dependency between the pact_broker and the pact gem, which requires the pact_mock-service, which requires webrick.
4
+
5
+ This was required as there is a vulnerability in webrick <= 1.3.1 that we don't want to include in the pact_broker gem, but webrick > 1.3.1 requires ruby 2.3.0, but we need ruby 2.2 for Travelling Ruby, which we use to package the Ruby codebase so it can be shared with implementations in other langauges.
@@ -0,0 +1,39 @@
1
+ module Pact
2
+ module Doc
3
+ class DocFile
4
+
5
+ def initialize consumer_contract, dir, consumer_contract_renderer, file_extension
6
+ @dir = dir
7
+ @consumer_contract = consumer_contract
8
+ @consumer_contract_renderer = consumer_contract_renderer
9
+ @file_extension = file_extension
10
+ end
11
+
12
+ def write
13
+ File.open(path, "w") { |io| io << doc_file_contents }
14
+ end
15
+
16
+ def title
17
+ consumer_contract.provider.name
18
+ end
19
+
20
+ def name
21
+ "#{consumer_contract.consumer.name} - #{consumer_contract.provider.name}#{file_extension}"
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :dir, :consumer_contract, :consumer_contract_renderer, :file_extension
27
+
28
+
29
+ def path
30
+ File.join(dir, name)
31
+ end
32
+
33
+ def doc_file_contents
34
+ consumer_contract_renderer.call(consumer_contract)
35
+ end
36
+
37
+ end
38
+ end
39
+ 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,24 @@
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
+ def initialize pact_dir, doc_dir
10
+ super(pact_dir, doc_dir,
11
+ consumer_contract_renderer: ConsumerContractRenderer,
12
+ doc_type: 'markdown',
13
+ file_extension: '.md',
14
+ index_renderer: IndexRenderer,
15
+ index_name: 'README')
16
+ end
17
+
18
+ def self.call pact_dir, doc_dir
19
+ new(pact_dir, doc_dir).call
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ module Pact
2
+ module Doc
3
+ module Markdown
4
+ class IndexRenderer
5
+ attr_reader :consumer_name
6
+ attr_reader :docs # Hash of pact title => file_name
7
+
8
+ def initialize consumer_name, docs
9
+ @consumer_name = consumer_name
10
+ @docs = docs
11
+ end
12
+
13
+ def self.call consumer_name, docs
14
+ new(consumer_name, docs).call
15
+ end
16
+
17
+ def call
18
+ title + "\n\n" + table_of_contents + "\n"
19
+ end
20
+
21
+ private
22
+
23
+ def table_of_contents
24
+ docs.collect do | title, file_name |
25
+ item title, file_name
26
+ end.join("\n")
27
+ end
28
+
29
+ def title
30
+ "### Pacts for #{consumer_name}"
31
+ end
32
+
33
+ def item title, file_name
34
+ "* [#{title}](#{file_name})"
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ 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,42 @@
1
+ require 'erb'
2
+ require 'pact/doc/interaction_view_model'
3
+
4
+ module Pact
5
+ module Doc
6
+ module Markdown
7
+ class InteractionRenderer
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
+ # The template file is written with only ASCII range characters, so we
28
+ # can read as UTF-8. But rendered strings must have same encoding as
29
+ # script encoding because it will joined to strings which are produced by
30
+ # string literal.
31
+ def template_string(template_file)
32
+ File.read(template_contents(template_file), external_encoding: Encoding::UTF_8).force_encoding(__ENCODING__)
33
+ end
34
+
35
+ def template_contents(template_file)
36
+ File.dirname(__FILE__) + template_file
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ module Pact
2
+ module Doc
3
+ class SortInteractions
4
+ def self.call interactions
5
+ interactions.sort{|a, b| sortable_id(a) <=> sortable_id(b)}
6
+ end
7
+
8
+ private
9
+
10
+ def self.sortable_id interaction
11
+ "#{interaction.description.downcase} #{interaction.response.status} #{(interaction.provider_state || '').downcase}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '2.13.0'
2
+ VERSION = '2.13.1'
3
3
  end
@@ -31,15 +31,15 @@ Gem::Specification.new do |gem|
31
31
  gem.add_runtime_dependency 'semver2', '~> 3.4.2'
32
32
  gem.add_runtime_dependency 'rack', '~>2.0'
33
33
  gem.add_runtime_dependency 'redcarpet', '>=3.3.2', '~>3.3'
34
- gem.add_runtime_dependency 'pact', '~>1.14'
34
+ gem.add_runtime_dependency 'pact-support'
35
35
  gem.add_runtime_dependency 'padrino-core', '~>0.14.1'
36
36
  gem.add_runtime_dependency 'haml', '~>4.0'
37
37
  gem.add_runtime_dependency 'sucker_punch', '~>2.0'
38
38
  gem.add_runtime_dependency 'rack-protection', '~>2.0'
39
39
  gem.add_runtime_dependency 'dry-types', '~> 0.10.3' # https://travis-ci.org/pact-foundation/pact_broker/jobs/249448621
40
40
  gem.add_runtime_dependency 'table_print', '~> 1.5'
41
- gem.add_runtime_dependency 'webrick', '~>1.3.0' # Webrick requires Ruby version >= 2.5.0dev. TODO remove dependency on Pact gem.
42
41
 
42
+ gem.add_development_dependency 'pact', '~>1.14'
43
43
  gem.add_development_dependency 'bundler-audit', '~>0.4'
44
44
  gem.add_development_dependency 'sqlite3', '~>1.3'
45
45
  gem.add_development_dependency 'pry-byebug'
@@ -0,0 +1,80 @@
1
+ require 'pact/doc/generator'
2
+ require 'fileutils'
3
+
4
+ module Pact
5
+ module Doc
6
+ describe Generator do
7
+
8
+ let(:doc_dir) { './tmp/doc' }
9
+ let(:pact_dir) { './tmp/pacts' }
10
+ let(:file_name) { "Some Consumer - Some Provider#{file_extension}" }
11
+ let(:consumer_contract_renderer) { double("ConsumerContractRenderer", :call => doc_content) }
12
+ let(:doc_content) { "doc_content" }
13
+ let(:index_content) { "index_content" }
14
+ let(:expected_doc_path) { "#{doc_dir}/#{doc_type}/#{file_name}" }
15
+ let(:expected_index_path) { "#{doc_dir}/#{doc_type}/#{index_name}#{file_extension}" }
16
+ let(:doc_type) { 'markdown' }
17
+ let(:file_extension) { ".md" }
18
+ let(:actual_file_contents) { File.read(expected_doc_path) }
19
+ let(:actual_index_contents) { File.read(expected_index_path)}
20
+ let(:index_renderer) { double("IndexRenderer", :call => index_content)}
21
+ let(:index_name) { 'README' }
22
+ let(:after_hook) { double("hook", :call => nil)}
23
+
24
+ before do
25
+ FileUtils.rm_rf doc_dir
26
+ FileUtils.rm_rf pact_dir
27
+ FileUtils.mkdir_p doc_dir
28
+ FileUtils.mkdir_p pact_dir
29
+ FileUtils.cp './spec/support/markdown_pact.json', pact_dir
30
+ end
31
+
32
+ let(:options) { { consumer_contract_renderer: consumer_contract_renderer, doc_type: doc_type, file_extension: file_extension, index_renderer: index_renderer, index_name: index_name } }
33
+
34
+ subject { Generator.new(pact_dir, doc_dir, options) }
35
+
36
+ context "when there are existing files" do
37
+ let(:existing_doc_file_path) { File.join(doc_dir, doc_type, "leftover") }
38
+
39
+ before do
40
+ FileUtils.mkdir_p File.dirname(existing_doc_file_path)
41
+ FileUtils.touch existing_doc_file_path
42
+ end
43
+
44
+ it "clears the existing files" do
45
+ expect(File.exist?(existing_doc_file_path)).to be true
46
+ subject.call
47
+ expect(File.exist?(existing_doc_file_path)).to be false
48
+ end
49
+ end
50
+
51
+ it "creates an index" do
52
+ expect(index_renderer).to receive(:call).with("Some Consumer", {"Some Provider"=>"Some Consumer - Some Provider.md"})
53
+ subject.call
54
+ expect(actual_index_contents).to eq(index_content)
55
+ end
56
+
57
+ it "creates documentation" do
58
+ subject.call
59
+ expect(actual_file_contents).to eq(doc_content)
60
+ end
61
+
62
+ context "with an after hook specified" do
63
+
64
+ subject { Generator.new(pact_dir, doc_dir, options.merge(:after => after_hook)) }
65
+
66
+ it "executes the hook" do
67
+ expect(after_hook).to receive(:call).with(pact_dir, "#{doc_dir}/#{doc_type}", instance_of(Array))
68
+ subject.call
69
+ end
70
+
71
+ it "passes in the consumer_contracts" do
72
+ expect(after_hook).to receive(:call) do | _, _, consumer_contracts |
73
+ expect(consumer_contracts.first).to be_instance_of(Pact::ConsumerContract)
74
+ end
75
+ subject.call
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,186 @@
1
+ require 'pact/doc/interaction_view_model'
2
+
3
+ module Pact
4
+ module Doc
5
+ describe InteractionViewModel do
6
+ let(:consumer_contract) { Pact::ConsumerContract.from_uri './spec/support/interaction_view_model.json' }
7
+ let(:interaction_with_request_with_body_and_headers) { consumer_contract.find_interaction description: "a request with a body and headers" }
8
+ let(:interaction_with_request_without_body_and_headers) { consumer_contract.find_interaction description: "a request with an empty body and empty headers" }
9
+ let(:interaction_with_response_with_body_and_headers) { consumer_contract.find_interaction description: "a response with a body and headers" }
10
+ let(:interaction_with_response_without_body_and_headers) { consumer_contract.find_interaction description: "a response with an empty body and empty headers" }
11
+ let(:interaction) { consumer_contract.interactions.first }
12
+
13
+ subject { InteractionViewModel.new interaction, consumer_contract}
14
+
15
+ describe "id" do
16
+ context "with HTML characters in the description" do
17
+ before do
18
+ interaction.description = "an alligator with > 100 legs exists"
19
+ interaction.provider_state = "a thing exists"
20
+ end
21
+
22
+ it "escapes the HTML characters" do
23
+ expect(subject.id).to eq "an_alligator_with_&gt;_100_legs_exists_given_a_thing_exists"
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "consumer_name" do
29
+ context "with markdown characters in the name" do
30
+ it "escapes the markdown characters" do
31
+ expect(subject.consumer_name).to eq "a\\*consumer"
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "provider_name" do
37
+ context "with markdown characters in the name" do
38
+ it "escapes the markdown characters" do
39
+ expect(subject.provider_name).to eq "a\\_provider"
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "request" do
45
+ let(:interaction) { interaction_with_request_with_body_and_headers }
46
+
47
+ it "includes the method" do
48
+ expect(subject.request).to include('"method"')
49
+ expect(subject.request).to include('"get"')
50
+ end
51
+
52
+ it "includes the body" do
53
+ expect(subject.request).to include('"body"')
54
+ expect(subject.request).to include('"a body"')
55
+ end
56
+
57
+ it "includes the headers" do
58
+ expect(subject.request).to include('"headers"')
59
+ expect(subject.request).to include('"a header"')
60
+ end
61
+
62
+ it "includes the query" do
63
+ expect(subject.request).to include('"query"')
64
+ expect(subject.request).to include('"some=thing"')
65
+ end
66
+
67
+ it "includes the path" do
68
+ expect(subject.request).to include('"path"')
69
+ expect(subject.request).to include('"/path"')
70
+ end
71
+
72
+ it "renders the keys in a meaningful order" do
73
+ expect(subject.request).to match /"method".*"path".*"query".*"headers".*"body"/m
74
+ end
75
+
76
+ context "when the body hash is empty" do
77
+ let(:interaction) { interaction_with_request_without_body_and_headers }
78
+
79
+ it "includes the body" do
80
+ expect(subject.request).to include("body")
81
+ end
82
+ end
83
+ context "when the headers hash is empty" do
84
+ let(:interaction) { interaction_with_request_without_body_and_headers }
85
+
86
+ it "does not include the headers" do
87
+ expect(subject.request).to_not include("headers")
88
+ end
89
+ end
90
+
91
+ context "when a Pact::Term is present" do
92
+ let(:consumer_contract) { Pact::ConsumerContract.from_uri './spec/support/interaction_view_model_with_terms.json'}
93
+ let(:interaction) { consumer_contract.interactions.first }
94
+
95
+ it "uses the generated value" do
96
+ expect(subject.request).to_not include("Term")
97
+ expect(subject.request).to include("sunny")
98
+ end
99
+ end
100
+ end
101
+
102
+ describe "response" do
103
+ let(:interaction) { interaction_with_response_with_body_and_headers }
104
+
105
+ it "includes the status" do
106
+ expect(subject.response).to include('"status"')
107
+ end
108
+
109
+ it "includes the body" do
110
+ expect(subject.response).to include('"body"')
111
+ expect(subject.response).to include('"a body"')
112
+ end
113
+ it "includes the headers" do
114
+ expect(subject.response).to include('"headers"')
115
+ expect(subject.response).to include('"a header"')
116
+ end
117
+
118
+ it "renders the keys in a meaningful order" do
119
+ expect(subject.response).to match /"status".*"headers".*"body"/m
120
+ end
121
+
122
+ context "when the body hash is empty" do
123
+ let(:interaction) { interaction_with_response_without_body_and_headers }
124
+
125
+ it "does not include the body" do
126
+ expect(subject.response).to_not include("body")
127
+ end
128
+ end
129
+
130
+ context "when the headers hash is empty" do
131
+ let(:interaction) { interaction_with_response_without_body_and_headers }
132
+
133
+ it "does not include the headers" do
134
+ expect(subject.response).to_not include("headers")
135
+ end
136
+ end
137
+
138
+ context "when a Pact::Term is present" do
139
+ let(:consumer_contract) { Pact::ConsumerContract.from_uri './spec/support/interaction_view_model_with_terms.json'}
140
+ let(:interaction) { consumer_contract.interactions.first }
141
+
142
+ it "uses the generated value" do
143
+ expect(subject.response).to_not include("Term")
144
+ expect(subject.response).to include("rainy")
145
+ end
146
+ end
147
+ end
148
+
149
+ describe "description" do
150
+ context "with a nil description" do
151
+ let(:interaction) do
152
+ interaction_with_request_with_body_and_headers.description = nil
153
+ interaction_with_request_with_body_and_headers
154
+ end
155
+
156
+ it "does not blow up" do
157
+ expect(subject.description(true)).to eq ''
158
+ expect(subject.description(false)).to eq ''
159
+ end
160
+ end
161
+
162
+ context "with markdown characters in the name" do
163
+ let(:interaction) do
164
+ interaction_with_request_with_body_and_headers.description = 'a *description'
165
+ interaction_with_request_with_body_and_headers
166
+ end
167
+ it "escapes the markdown characters" do
168
+ expect(subject.description).to eq "a \\*description"
169
+ end
170
+ end
171
+ end
172
+
173
+ describe "provider_state" do
174
+ context "with markdown characters in the name" do
175
+ let(:interaction) do
176
+ interaction_with_request_with_body_and_headers.provider_state = 'a *provider state'
177
+ interaction_with_request_with_body_and_headers
178
+ end
179
+ it "escapes the markdown characters" do
180
+ expect(subject.provider_state).to eq "a \\*provider state"
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'pact/doc/markdown/consumer_contract_renderer'
3
+ require 'pact/support'
4
+
5
+ module Pact
6
+ module Doc
7
+ module Markdown
8
+ describe ConsumerContractRenderer do
9
+
10
+ subject { ConsumerContractRenderer.new(consumer_contract) }
11
+ let(:consumer_contract) { Pact::ConsumerContract.from_uri './spec/support/markdown_pact.json' }
12
+
13
+ let(:expected_output) { File.read("./spec/support/generated_markdown.md", external_encoding: Encoding::UTF_8) }
14
+
15
+ describe "#call" do
16
+
17
+ context "with markdown characters in the pacticipant names" do
18
+ let(:consumer_contract) { Pact::ConsumerContract.from_uri './spec/support/markdown_pact_with_markdown_chars_in_names.json' }
19
+
20
+ it "escapes the markdown characters" do
21
+ expect(subject.call).to include '### A pact between Some\*Consumer\*App and Some\_Provider\_App'
22
+ expect(subject.call).to include '#### Requests from Some\*Consumer\*App to Some\_Provider\_App'
23
+ end
24
+ end
25
+
26
+ context "with ruby's default external encoding is not UTF-8" do
27
+ around do |example|
28
+ back = nil
29
+ WarningSilencer.enable { back, Encoding.default_external = Encoding.default_external, Encoding::ASCII_8BIT }
30
+ example.run
31
+ WarningSilencer.enable { Encoding.default_external = back }
32
+ end
33
+
34
+ it "renders the interactions" do
35
+ expect(subject.call).to eq(expected_output)
36
+ end
37
+ end
38
+
39
+ it "renders the interactions" do
40
+ expect(subject.call).to eq(expected_output)
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'pact/doc/markdown/index_renderer'
3
+
4
+ module Pact
5
+ module Doc
6
+ module Markdown
7
+ describe IndexRenderer do
8
+
9
+ let(:consumer_name) { "Some Consumer" }
10
+ let(:docs) { {"Some Provider" => "Some Provider.md", "Some other provider" => "Some other provider.md"} }
11
+ let(:subject) { IndexRenderer.new(consumer_name, docs) }
12
+ let(:expected_content) { File.read('./spec/support/generated_index.md')}
13
+
14
+ describe "#call" do
15
+ it "renders the index" do
16
+ expect(subject.call).to eq expected_content
17
+ end
18
+ end
19
+
20
+ describe ".call" do
21
+ it "renders the index" do
22
+ expect(IndexRenderer.call(consumer_name, docs) ).to eq expected_content
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ ### Pacts for Some Consumer
2
+
3
+ * [Some Provider](Some Provider.md)
4
+ * [Some other provider](Some other provider.md)
@@ -0,0 +1,55 @@
1
+ ### A pact between Some Consumer and Some Provider
2
+
3
+ #### Requests from Some Consumer to Some Provider
4
+
5
+ * [A request for alligators in Brüssel](#a_request_for_alligators_in_Brüssel_given_alligators_exist) given alligators exist
6
+
7
+ * [A request for polar bears](#a_request_for_polar_bears)
8
+
9
+ #### Interactions
10
+
11
+ <a name="a_request_for_alligators_in_Brüssel_given_alligators_exist"></a>
12
+ Given **alligators exist**, upon receiving **a request for alligators in Brüssel** from Some Consumer, with
13
+ ```json
14
+ {
15
+ "method": "get",
16
+ "path": "/alligators"
17
+ }
18
+ ```
19
+ Some Provider will respond with:
20
+ ```json
21
+ {
22
+ "status": 200,
23
+ "headers": {
24
+ "Content-Type": "application/json"
25
+ },
26
+ "body": {
27
+ "alligators": [
28
+ {
29
+ "name": "Bob",
30
+ "phoneNumber": "12345678"
31
+ }
32
+ ]
33
+ }
34
+ }
35
+ ```
36
+ <a name="a_request_for_polar_bears"></a>
37
+ Upon receiving **a request for polar bears** from Some Consumer, with
38
+ ```json
39
+ {
40
+ "method": "get",
41
+ "path": "/polar-bears"
42
+ }
43
+ ```
44
+ Some Provider will respond with:
45
+ ```json
46
+ {
47
+ "status": 404,
48
+ "headers": {
49
+ "Content-Type": "application/json"
50
+ },
51
+ "body": {
52
+ "message": "Sorry, due to climate change, the polar bears are currently unavailable."
53
+ }
54
+ }
55
+ ```
@@ -0,0 +1,63 @@
1
+ {
2
+ "provider": {
3
+ "name": "a_provider"
4
+ },
5
+ "consumer": {
6
+ "name": "a*consumer"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "a request with a body and headers",
11
+ "request": {
12
+ "method": "get",
13
+ "path": "/path",
14
+ "query": "some=thing",
15
+ "headers": {
16
+ "key": "a header"
17
+ },
18
+ "body": {
19
+ "key": "a body"
20
+ }
21
+ },
22
+ "response": {}
23
+ },
24
+ {
25
+ "description": "a request with an empty body and empty headers",
26
+ "request": {
27
+ "method": "get",
28
+ "path": "/",
29
+ "headers": {},
30
+ "body": {}
31
+ },
32
+ "response": {}
33
+ },
34
+ {
35
+ "description": "a response with a body and headers",
36
+ "request": {
37
+ "method": "get",
38
+ "path": "/"
39
+ },
40
+ "response": {
41
+ "headers": {
42
+ "key": "a header"
43
+ },
44
+ "body": {
45
+ "key": "a body"
46
+ },
47
+ "status": 200
48
+ }
49
+ },
50
+ {
51
+ "description": "a response with an empty body and empty headers",
52
+ "request": {
53
+ "method": "get",
54
+ "path": "/"
55
+ },
56
+ "response": {
57
+ "status": 200,
58
+ "headers": {},
59
+ "body": {}
60
+ }
61
+ }
62
+ ]
63
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "provider": {
3
+ "name": "a provider"
4
+ },
5
+ "consumer": {
6
+ "name": "a consumer"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "an interaction with terms",
11
+ "request": {
12
+ "method": "post",
13
+ "path": "/path",
14
+ "query": "some=thing",
15
+ "headers": {
16
+ "key": "a header"
17
+ },
18
+ "body": {
19
+ "term": {
20
+ "json_class": "Pact::Term",
21
+ "data": {
22
+ "generate": "sunny",
23
+ "matcher": {
24
+ "json_class": "Regexp",
25
+ "o": 0,
26
+ "s": "sun"
27
+ }
28
+ }
29
+ }
30
+ }
31
+ },
32
+ "response": {
33
+ "status": 200,
34
+ "body": {
35
+ "term": {
36
+ "json_class": "Pact::Term",
37
+ "data": {
38
+ "generate": "rainy",
39
+ "matcher": {
40
+ "json_class": "Regexp",
41
+ "o": 0,
42
+ "s": "rain"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ]
50
+ }
@@ -0,0 +1,48 @@
1
+ {
2
+ "provider": {
3
+ "name": "Some Provider"
4
+ },
5
+ "consumer": {
6
+ "name": "Some Consumer"
7
+ },
8
+ "interactions": [
9
+ {
10
+ "description": "a request for alligators in Brüssel",
11
+ "provider_state": "alligators exist",
12
+ "request": {
13
+ "method": "get",
14
+ "path": "/alligators"
15
+ },
16
+ "response": {
17
+ "headers" : {"Content-Type": "application/json"},
18
+ "status" : 200,
19
+ "body" : {
20
+ "alligators": [{
21
+ "name": "Bob",
22
+ "phoneNumber" : {
23
+ "json_class": "Pact::Term",
24
+ "data": {
25
+ "generate": "12345678",
26
+ "matcher": {"json_class":"Regexp","o":0,"s":"\\d+"}
27
+ }
28
+ }
29
+ }]
30
+ }
31
+ }
32
+ },{
33
+ "description": "a request for polar bears",
34
+ "provider_state": null,
35
+ "request": {
36
+ "method": "get",
37
+ "path": "/polar-bears"
38
+ },
39
+ "response": {
40
+ "headers" : {"Content-Type": "application/json"},
41
+ "status" : 404,
42
+ "body" : {
43
+ "message": "Sorry, due to climate change, the polar bears are currently unavailable."
44
+ }
45
+ }
46
+ }
47
+ ]
48
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "provider": {
3
+ "name": "Some_Provider_App"
4
+ },
5
+ "consumer": {
6
+ "name": "Some*Consumer*App"
7
+ },
8
+ "interactions": [
9
+
10
+ ]
11
+ }
@@ -0,0 +1,10 @@
1
+ module WarningSilencer
2
+ extend self
3
+
4
+ def enable
5
+ old, $VERBOSE = $VERBOSE, nil
6
+ yield
7
+ ensure
8
+ $VERBOSE = old
9
+ end
10
+ end
@@ -20,8 +20,9 @@ module PactBroker
20
20
  .collect{|it| @connection.foreign_key_list(it)
21
21
  .collect{|fk| {from: it, to: fk[:table]} } }
22
22
  .flatten
23
- .uniq - [:schema_migrations]
24
- check(@connection.tables, dependencies, ordered_table_names)
23
+ .uniq
24
+ table_names = @connection.tables - [:schema_migrations, :schema_info]
25
+ check(table_names, dependencies, ordered_table_names)
25
26
  ordered_table_names
26
27
  end
27
28
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pact_broker
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.13.0
4
+ version: 2.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bethany Skurrie
@@ -165,19 +165,19 @@ dependencies:
165
165
  - !ruby/object:Gem::Version
166
166
  version: '3.3'
167
167
  - !ruby/object:Gem::Dependency
168
- name: pact
168
+ name: pact-support
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
- - - "~>"
171
+ - - ">="
172
172
  - !ruby/object:Gem::Version
173
- version: '1.14'
173
+ version: '0'
174
174
  type: :runtime
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
- - - "~>"
178
+ - - ">="
179
179
  - !ruby/object:Gem::Version
180
- version: '1.14'
180
+ version: '0'
181
181
  - !ruby/object:Gem::Dependency
182
182
  name: padrino-core
183
183
  requirement: !ruby/object:Gem::Requirement
@@ -263,19 +263,19 @@ dependencies:
263
263
  - !ruby/object:Gem::Version
264
264
  version: '1.5'
265
265
  - !ruby/object:Gem::Dependency
266
- name: webrick
266
+ name: pact
267
267
  requirement: !ruby/object:Gem::Requirement
268
268
  requirements:
269
269
  - - "~>"
270
270
  - !ruby/object:Gem::Version
271
- version: 1.3.0
272
- type: :runtime
271
+ version: '1.14'
272
+ type: :development
273
273
  prerelease: false
274
274
  version_requirements: !ruby/object:Gem::Requirement
275
275
  requirements:
276
276
  - - "~>"
277
277
  - !ruby/object:Gem::Version
278
- version: 1.3.0
278
+ version: '1.14'
279
279
  - !ruby/object:Gem::Dependency
280
280
  name: bundler-audit
281
281
  requirement: !ruby/object:Gem::Requirement
@@ -602,6 +602,17 @@ files:
602
602
  - example/example_data.sql
603
603
  - example/pact_broker_database.sqlite3
604
604
  - lib/db.rb
605
+ - lib/pact/doc/README.md
606
+ - lib/pact/doc/doc_file.rb
607
+ - lib/pact/doc/generate.rb
608
+ - lib/pact/doc/generator.rb
609
+ - lib/pact/doc/interaction_view_model.rb
610
+ - lib/pact/doc/markdown/consumer_contract_renderer.rb
611
+ - lib/pact/doc/markdown/generator.rb
612
+ - lib/pact/doc/markdown/index_renderer.rb
613
+ - lib/pact/doc/markdown/interaction.erb
614
+ - lib/pact/doc/markdown/interaction_renderer.rb
615
+ - lib/pact/doc/sort_interactions.rb
605
616
  - lib/pact_broker.rb
606
617
  - lib/pact_broker/api.rb
607
618
  - lib/pact_broker/api/contracts/pacticipant_name_contract.rb
@@ -919,6 +930,10 @@ files:
919
930
  - spec/integration/endpoints/group_spec.rb
920
931
  - spec/integration/ui/index_spec.rb
921
932
  - spec/integration/webhooks/certificate_spec.rb
933
+ - spec/lib/pact/doc/generator_spec.rb
934
+ - spec/lib/pact/doc/interaction_view_model_spec.rb
935
+ - spec/lib/pact/doc/markdown/consumer_contract_renderer_spec.rb
936
+ - spec/lib/pact/doc/markdown/index_renderer_spec.rb
922
937
  - spec/lib/pact_broker/api/contracts/put_pact_params_contract_spec.rb
923
938
  - spec/lib/pact_broker/api/contracts/verification_contract_spec.rb
924
939
  - spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb
@@ -1040,12 +1055,19 @@ files:
1040
1055
  - spec/spec_helper.rb
1041
1056
  - spec/support/database_cleaner.rb
1042
1057
  - spec/support/fixture_helpers.rb
1058
+ - spec/support/generated_index.md
1059
+ - spec/support/generated_markdown.md
1060
+ - spec/support/interaction_view_model.json
1061
+ - spec/support/interaction_view_model_with_terms.json
1062
+ - spec/support/markdown_pact.json
1063
+ - spec/support/markdown_pact_with_markdown_chars_in_names.json
1043
1064
  - spec/support/migration_helpers.rb
1044
1065
  - spec/support/rspec_match_hash.rb
1045
1066
  - spec/support/rspec_matchers.rb
1046
1067
  - spec/support/shared_examples_for_responses.rb
1047
1068
  - spec/support/ssl_webhook_server.rb
1048
1069
  - spec/support/test_data_builder.rb
1070
+ - spec/support/warning_silencer.rb
1049
1071
  - tasks/database.rb
1050
1072
  - tasks/database/table_dependency_calculator.rb
1051
1073
  - tasks/db.rake
@@ -1170,6 +1192,10 @@ test_files:
1170
1192
  - spec/integration/endpoints/group_spec.rb
1171
1193
  - spec/integration/ui/index_spec.rb
1172
1194
  - spec/integration/webhooks/certificate_spec.rb
1195
+ - spec/lib/pact/doc/generator_spec.rb
1196
+ - spec/lib/pact/doc/interaction_view_model_spec.rb
1197
+ - spec/lib/pact/doc/markdown/consumer_contract_renderer_spec.rb
1198
+ - spec/lib/pact/doc/markdown/index_renderer_spec.rb
1173
1199
  - spec/lib/pact_broker/api/contracts/put_pact_params_contract_spec.rb
1174
1200
  - spec/lib/pact_broker/api/contracts/verification_contract_spec.rb
1175
1201
  - spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb
@@ -1291,9 +1317,16 @@ test_files:
1291
1317
  - spec/spec_helper.rb
1292
1318
  - spec/support/database_cleaner.rb
1293
1319
  - spec/support/fixture_helpers.rb
1320
+ - spec/support/generated_index.md
1321
+ - spec/support/generated_markdown.md
1322
+ - spec/support/interaction_view_model.json
1323
+ - spec/support/interaction_view_model_with_terms.json
1324
+ - spec/support/markdown_pact.json
1325
+ - spec/support/markdown_pact_with_markdown_chars_in_names.json
1294
1326
  - spec/support/migration_helpers.rb
1295
1327
  - spec/support/rspec_match_hash.rb
1296
1328
  - spec/support/rspec_matchers.rb
1297
1329
  - spec/support/shared_examples_for_responses.rb
1298
1330
  - spec/support/ssl_webhook_server.rb
1299
1331
  - spec/support/test_data_builder.rb
1332
+ - spec/support/warning_silencer.rb