pact_broker 2.121.2 → 2.123.0
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 +4 -4
- data/CHANGELOG.md +116 -0
- data/Gemfile +1 -4
- data/lib/pact_broker/api/contracts/consumer_version_selector_contract.rb +17 -1
- data/lib/pact_broker/api/contracts/utf_8_validation.rb +5 -1
- data/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb +1 -0
- data/lib/pact_broker/api/decorators/publish_contract_decorator.rb +4 -3
- data/lib/pact_broker/api/decorators/verification_decorator.rb +12 -2
- data/lib/pact_broker/api/decorators/verification_summary_decorator.rb +6 -0
- data/lib/pact_broker/api/renderers/html_pact_renderer.rb +6 -69
- data/lib/pact_broker/api/renderers/markdown/consumer_contract_renderer.rb +87 -0
- data/lib/pact_broker/api/renderers/markdown/interaction_renderer.rb +52 -0
- data/lib/pact_broker/api/renderers/markdown/interaction_view_model.rb +162 -0
- data/lib/pact_broker/api/renderers/markdown.rb +9 -0
- data/lib/pact_broker/api/resources/publish_contracts.rb +10 -6
- data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +10 -2
- data/lib/pact_broker/hash_refinements.rb +2 -2
- data/lib/pact_broker/locale/en.yml +3 -0
- data/lib/pact_broker/pacts/create_formatted_diff.rb +2 -7
- data/lib/pact_broker/pacts/generate_sha.rb +7 -4
- data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +41 -21
- data/lib/pact_broker/pacts/repository.rb +2 -2
- data/lib/pact_broker/pacts/selected_pact.rb +4 -0
- data/lib/pact_broker/pacts/selector.rb +20 -2
- data/lib/pact_broker/pacts/selectors.rb +4 -0
- data/lib/pact_broker/unified_diff.rb +26 -0
- data/lib/pact_broker/verifications/repository.rb +1 -1
- data/lib/pact_broker/verifications/summary_for_consumer_version.rb +4 -0
- data/lib/pact_broker/version.rb +1 -1
- data/pact_broker.gemspec +2 -2
- metadata +25 -30
- data/lib/pact/doc/doc_file.rb +0 -38
- data/lib/pact/doc/generate.rb +0 -11
- data/lib/pact/doc/generator.rb +0 -82
- data/lib/pact/doc/interaction_view_model.rb +0 -157
- data/lib/pact/doc/markdown/consumer_contract_renderer.rb +0 -74
- data/lib/pact/doc/markdown/generator.rb +0 -24
- data/lib/pact/doc/markdown/index_renderer.rb +0 -40
- data/lib/pact/doc/markdown/interaction_renderer.rb +0 -51
- data/lib/pact/doc/sort_interactions.rb +0 -14
- data/lib/pact/doc.rb +0 -5
- /data/lib/{pact/doc → pact_broker/api/renderers}/markdown/interaction.erb +0 -0
- /data/lib/{pact/doc → pact_broker/api/renderers}/markdown/interaction_async.erb +0 -0
data/lib/pact/doc/generator.rb
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
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_arg, target_dir_arg, consumer_contracts_arg| })
|
|
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
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
require "pact/shared/active_support_support"
|
|
2
|
-
require "pact/reification"
|
|
3
|
-
require "cgi"
|
|
4
|
-
require "pact/doc"
|
|
5
|
-
|
|
6
|
-
module Pact
|
|
7
|
-
module Doc
|
|
8
|
-
class InteractionViewModel
|
|
9
|
-
|
|
10
|
-
include Pact::ActiveSupportSupport
|
|
11
|
-
|
|
12
|
-
MARKDOWN_BOLD_CHARACTERS = "**"
|
|
13
|
-
|
|
14
|
-
def initialize interaction, consumer_contract
|
|
15
|
-
@interaction = interaction
|
|
16
|
-
@consumer_contract = consumer_contract
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def id
|
|
20
|
-
@id ||= begin
|
|
21
|
-
full_desc = if has_provider_state?
|
|
22
|
-
"#{description} given #{interaction.provider_state}"
|
|
23
|
-
else
|
|
24
|
-
description
|
|
25
|
-
end
|
|
26
|
-
CGI.escapeHTML(full_desc.gsub(/\s+/,"_"))
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def request_method
|
|
31
|
-
interaction.request.method.upcase
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def request_path
|
|
35
|
-
interaction.request.path
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def response_status
|
|
39
|
-
interaction.response.status
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def consumer_name
|
|
43
|
-
markdown_escape @consumer_contract.consumer.name
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def provider_name
|
|
47
|
-
markdown_escape @consumer_contract.provider.name
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def has_provider_state?
|
|
51
|
-
@interaction.provider_state && !@interaction.provider_state.empty?
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def provider_state start_of_sentence = false
|
|
55
|
-
markdown_escape apply_capitals(@interaction.provider_state.strip, start_of_sentence)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def formatted_provider_states mark_bold: false
|
|
59
|
-
bold_marker = mark_bold ? MARKDOWN_BOLD_CHARACTERS : ""
|
|
60
|
-
|
|
61
|
-
(@interaction.provider_states || []).map do |ps|
|
|
62
|
-
"#{bold_marker}" \
|
|
63
|
-
"#{markdown_escape(apply_capitals(ps.name.strip, false))}" \
|
|
64
|
-
"#{bold_marker}"
|
|
65
|
-
end.join(" and ")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def description start_of_sentence = false
|
|
69
|
-
return "" unless @interaction.description
|
|
70
|
-
markdown_escape apply_capitals(@interaction.description.strip, start_of_sentence)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def request
|
|
74
|
-
the_clean_request = clean_request
|
|
75
|
-
if the_clean_request == "ASYNC_REQUEST"
|
|
76
|
-
"ASYNC_REQUEST"
|
|
77
|
-
else
|
|
78
|
-
fix_json_formatting JSON.pretty_generate(clean_request)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def response
|
|
83
|
-
fix_json_formatting JSON.pretty_generate(clean_response)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
private
|
|
87
|
-
|
|
88
|
-
attr_reader :interaction, :consumer_contract
|
|
89
|
-
|
|
90
|
-
def clean_request
|
|
91
|
-
reified = Reification.from_term(interaction.request)
|
|
92
|
-
return "ASYNC_REQUEST" if reified[:method] == "FAKE_ASYNC_METHOD"
|
|
93
|
-
|
|
94
|
-
ordered = ordered_clean_hash(reified).tap do |h|
|
|
95
|
-
h[:body] = reified[:body] if reified[:body]
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
return ordered[:body].to_h.slice("contents", "metadata") if ordered[:method] == "FAKE_SYNC_METHOD"
|
|
99
|
-
|
|
100
|
-
ordered
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def clean_response
|
|
104
|
-
raw = Reification.from_term(interaction.response)
|
|
105
|
-
|
|
106
|
-
case raw[:status]
|
|
107
|
-
when "FAKE_ASYNC_METHOD"
|
|
108
|
-
raw[:body] || {}
|
|
109
|
-
when "FAKE_SYNC_METHOD"
|
|
110
|
-
Array(raw.dig(:body, "contents")).map { |item| item.slice("contents", "metadata") }
|
|
111
|
-
else
|
|
112
|
-
ordered_clean_hash(raw)
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Remove empty body and headers hashes from response, and empty headers from request,
|
|
117
|
-
# as an empty hash means "allow anything" - it's more intuitive and cleaner to just
|
|
118
|
-
# remove the empty hashes from display.
|
|
119
|
-
def ordered_clean_hash source
|
|
120
|
-
ordered_keys.each_with_object({}) do |key, target|
|
|
121
|
-
if source.key? key
|
|
122
|
-
target[key] = source[key] unless value_is_an_empty_hash(source[key])
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def value_is_an_empty_hash value
|
|
128
|
-
value.is_a?(Hash) && value.empty?
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def ordered_keys
|
|
132
|
-
[:method, :path, :query, :status, :headers, :body]
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def remove_key_if_empty key, hash
|
|
136
|
-
hash.delete(key) if hash[key].is_a?(Hash) && hash[key].empty?
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def apply_capitals string, start_of_sentence = false
|
|
140
|
-
start_of_sentence ? capitalize_first_letter(string) : lowercase_first_letter(string)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def capitalize_first_letter string
|
|
144
|
-
string[0].upcase + string[1..-1]
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def lowercase_first_letter string
|
|
148
|
-
string[0].downcase + string[1..-1]
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def markdown_escape string
|
|
152
|
-
return nil unless string
|
|
153
|
-
string.gsub(Pact::Doc::MARKDOWN_SPECIAL_CHARS_REGEXP) { |char| "\\#{char}" }
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
end
|
|
157
|
-
end
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
require "pact/doc/markdown/interaction_renderer"
|
|
2
|
-
require "pact/doc/sort_interactions"
|
|
3
|
-
require "rack/utils"
|
|
4
|
-
require "pact/doc"
|
|
5
|
-
|
|
6
|
-
module Pact
|
|
7
|
-
module Doc
|
|
8
|
-
module Markdown
|
|
9
|
-
class ConsumerContractRenderer
|
|
10
|
-
|
|
11
|
-
def initialize consumer_contract
|
|
12
|
-
@consumer_contract = consumer_contract
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def self.call consumer_contract
|
|
16
|
-
new(consumer_contract).call
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def call
|
|
20
|
-
title + summaries_title + summaries + interactions_title + full_interactions
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
attr_reader :consumer_contract
|
|
26
|
-
|
|
27
|
-
def title
|
|
28
|
-
"# A pact between #{consumer_name} and #{provider_name}\n\n"
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def interaction_renderers
|
|
32
|
-
@interaction_renderers ||= sorted_interactions.collect{|interaction| InteractionRenderer.new interaction, @consumer_contract}
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def summaries_title
|
|
36
|
-
"### Requests from #{consumer_name} to #{provider_name}\n\n"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def interactions_title
|
|
40
|
-
"### Interactions\n\n"
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def summaries
|
|
44
|
-
interaction_renderers.collect(&:render_summary).join
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def full_interactions
|
|
48
|
-
interaction_renderers.collect(&:render_full_interaction).join
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def sorted_interactions
|
|
52
|
-
SortInteractions.call(consumer_contract.interactions)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def consumer_name
|
|
56
|
-
h(markdown_escape consumer_contract.consumer.name)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def provider_name
|
|
60
|
-
h(markdown_escape consumer_contract.provider.name)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def markdown_escape string
|
|
64
|
-
return nil unless string
|
|
65
|
-
string.gsub(Pact::Doc::MARKDOWN_SPECIAL_CHARS_REGEXP) { |char| "\\#{char}" }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def h(text)
|
|
69
|
-
Rack::Utils.escape_html(text)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
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
|
|
@@ -1,40 +0,0 @@
|
|
|
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
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
require "erb"
|
|
2
|
-
require "pact/doc/interaction_view_model"
|
|
3
|
-
require "rack/utils"
|
|
4
|
-
|
|
5
|
-
module Pact
|
|
6
|
-
module Doc
|
|
7
|
-
module Markdown
|
|
8
|
-
class InteractionRenderer
|
|
9
|
-
attr_reader :interaction
|
|
10
|
-
|
|
11
|
-
def initialize interaction, pact
|
|
12
|
-
@interaction = InteractionViewModel.new(interaction, pact)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def render_summary
|
|
16
|
-
formatted_provider_states = h(interaction.formatted_provider_states)
|
|
17
|
-
suffix = formatted_provider_states.empty? ? "" : " given #{formatted_provider_states}"
|
|
18
|
-
"* [#{h(interaction.description(true))}](##{interaction.id})#{suffix}\n\n"
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def render_full_interaction
|
|
22
|
-
if interaction.request == "ASYNC_REQUEST"
|
|
23
|
-
render("/interaction_async.erb")
|
|
24
|
-
else
|
|
25
|
-
render("/interaction.erb")
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def render template_file
|
|
30
|
-
ERB.new(template_string(template_file)).result(binding)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# The template file is written with only ASCII range characters, so we
|
|
34
|
-
# can read as UTF-8. But rendered strings must have same encoding as
|
|
35
|
-
# script encoding because it will joined to strings which are produced by
|
|
36
|
-
# string literal.
|
|
37
|
-
def template_string(template_file)
|
|
38
|
-
File.read(template_contents(template_file), external_encoding: Encoding::UTF_8).force_encoding(__ENCODING__)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def template_contents(template_file)
|
|
42
|
-
File.dirname(__FILE__) + template_file
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def h(text)
|
|
46
|
-
Rack::Utils.escape_html(text)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
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
|
-
|
|
9
|
-
def self.sortable_id interaction
|
|
10
|
-
"#{(interaction.description || '').downcase} #{interaction.response.status} #{(interaction.provider_state || '').downcase}"
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
data/lib/pact/doc.rb
DELETED
|
File without changes
|
|
File without changes
|