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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +116 -0
  3. data/Gemfile +1 -4
  4. data/lib/pact_broker/api/contracts/consumer_version_selector_contract.rb +17 -1
  5. data/lib/pact_broker/api/contracts/utf_8_validation.rb +5 -1
  6. data/lib/pact_broker/api/decorators/pacts_for_verification_query_decorator.rb +1 -0
  7. data/lib/pact_broker/api/decorators/publish_contract_decorator.rb +4 -3
  8. data/lib/pact_broker/api/decorators/verification_decorator.rb +12 -2
  9. data/lib/pact_broker/api/decorators/verification_summary_decorator.rb +6 -0
  10. data/lib/pact_broker/api/renderers/html_pact_renderer.rb +6 -69
  11. data/lib/pact_broker/api/renderers/markdown/consumer_contract_renderer.rb +87 -0
  12. data/lib/pact_broker/api/renderers/markdown/interaction_renderer.rb +52 -0
  13. data/lib/pact_broker/api/renderers/markdown/interaction_view_model.rb +162 -0
  14. data/lib/pact_broker/api/renderers/markdown.rb +9 -0
  15. data/lib/pact_broker/api/resources/publish_contracts.rb +10 -6
  16. data/lib/pact_broker/doc/views/provider-pacts-for-verification.markdown +10 -2
  17. data/lib/pact_broker/hash_refinements.rb +2 -2
  18. data/lib/pact_broker/locale/en.yml +3 -0
  19. data/lib/pact_broker/pacts/create_formatted_diff.rb +2 -7
  20. data/lib/pact_broker/pacts/generate_sha.rb +7 -4
  21. data/lib/pact_broker/pacts/pacts_for_verification_repository.rb +41 -21
  22. data/lib/pact_broker/pacts/repository.rb +2 -2
  23. data/lib/pact_broker/pacts/selected_pact.rb +4 -0
  24. data/lib/pact_broker/pacts/selector.rb +20 -2
  25. data/lib/pact_broker/pacts/selectors.rb +4 -0
  26. data/lib/pact_broker/unified_diff.rb +26 -0
  27. data/lib/pact_broker/verifications/repository.rb +1 -1
  28. data/lib/pact_broker/verifications/summary_for_consumer_version.rb +4 -0
  29. data/lib/pact_broker/version.rb +1 -1
  30. data/pact_broker.gemspec +2 -2
  31. metadata +25 -30
  32. data/lib/pact/doc/doc_file.rb +0 -38
  33. data/lib/pact/doc/generate.rb +0 -11
  34. data/lib/pact/doc/generator.rb +0 -82
  35. data/lib/pact/doc/interaction_view_model.rb +0 -157
  36. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +0 -74
  37. data/lib/pact/doc/markdown/generator.rb +0 -24
  38. data/lib/pact/doc/markdown/index_renderer.rb +0 -40
  39. data/lib/pact/doc/markdown/interaction_renderer.rb +0 -51
  40. data/lib/pact/doc/sort_interactions.rb +0 -14
  41. data/lib/pact/doc.rb +0 -5
  42. /data/lib/{pact/doc → pact_broker/api/renderers}/markdown/interaction.erb +0 -0
  43. /data/lib/{pact/doc → pact_broker/api/renderers}/markdown/interaction_async.erb +0 -0
@@ -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
@@ -1,5 +0,0 @@
1
- module Pact
2
- module Doc
3
- MARKDOWN_SPECIAL_CHARS_REGEXP = /[\\`*_\[\](){}#+\-.!|]/
4
- end
5
- end