pact_broker 2.13.0 → 2.13.1

Sign up to get free protection for your applications and to get access to all the features.
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