pact-v2 2.0.0.pre.preview1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1321 -0
  3. data/LICENSE.txt +23 -0
  4. data/bin/pact +4 -0
  5. data/lib/pact/cli/generate_pact_docs.rb +4 -0
  6. data/lib/pact/cli/run_pact_verification.rb +99 -0
  7. data/lib/pact/cli/spec_criteria.rb +26 -0
  8. data/lib/pact/cli.rb +45 -0
  9. data/lib/pact/consumer/configuration/configuration_extensions.rb +90 -0
  10. data/lib/pact/consumer/configuration/dsl.rb +11 -0
  11. data/lib/pact/consumer/configuration/mock_service.rb +112 -0
  12. data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
  13. data/lib/pact/consumer/configuration/service_provider.rb +40 -0
  14. data/lib/pact/consumer/configuration.rb +10 -0
  15. data/lib/pact/consumer/consumer_contract_builder.rb +82 -0
  16. data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
  17. data/lib/pact/consumer/interaction_builder.rb +45 -0
  18. data/lib/pact/consumer/rspec.rb +35 -0
  19. data/lib/pact/consumer/spec_hooks.rb +40 -0
  20. data/lib/pact/consumer/world.rb +37 -0
  21. data/lib/pact/consumer.rb +7 -0
  22. data/lib/pact/doc/README.md +13 -0
  23. data/lib/pact/doc/doc_file.rb +40 -0
  24. data/lib/pact/doc/generate.rb +11 -0
  25. data/lib/pact/doc/generator.rb +82 -0
  26. data/lib/pact/doc/interaction_view_model.rb +124 -0
  27. data/lib/pact/doc/markdown/consumer_contract_renderer.rb +68 -0
  28. data/lib/pact/doc/markdown/generator.rb +26 -0
  29. data/lib/pact/doc/markdown/index_renderer.rb +43 -0
  30. data/lib/pact/doc/markdown/interaction.erb +14 -0
  31. data/lib/pact/doc/markdown/interaction_renderer.rb +43 -0
  32. data/lib/pact/doc/sort_interactions.rb +16 -0
  33. data/lib/pact/hal/authorization_header_redactor.rb +32 -0
  34. data/lib/pact/hal/entity.rb +110 -0
  35. data/lib/pact/hal/http_client.rb +128 -0
  36. data/lib/pact/hal/link.rb +112 -0
  37. data/lib/pact/hal/non_json_entity.rb +28 -0
  38. data/lib/pact/hash_refinements.rb +17 -0
  39. data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +112 -0
  40. data/lib/pact/pact_broker/fetch_pacts.rb +103 -0
  41. data/lib/pact/pact_broker/notices.rb +34 -0
  42. data/lib/pact/pact_broker/pact_selection_description.rb +66 -0
  43. data/lib/pact/pact_broker.rb +25 -0
  44. data/lib/pact/project_root.rb +7 -0
  45. data/lib/pact/provider/configuration/configuration_extension.rb +69 -0
  46. data/lib/pact/provider/configuration/dsl.rb +18 -0
  47. data/lib/pact/provider/configuration/message_provider_dsl.rb +63 -0
  48. data/lib/pact/provider/configuration/pact_verification.rb +48 -0
  49. data/lib/pact/provider/configuration/pact_verification_from_broker.rb +126 -0
  50. data/lib/pact/provider/configuration/service_provider_config.rb +32 -0
  51. data/lib/pact/provider/configuration/service_provider_dsl.rb +107 -0
  52. data/lib/pact/provider/configuration.rb +7 -0
  53. data/lib/pact/provider/context.rb +0 -0
  54. data/lib/pact/provider/help/console_text.rb +76 -0
  55. data/lib/pact/provider/help/content.rb +38 -0
  56. data/lib/pact/provider/help/pact_diff.rb +43 -0
  57. data/lib/pact/provider/help/prompt_text.rb +49 -0
  58. data/lib/pact/provider/help/write.rb +56 -0
  59. data/lib/pact/provider/matchers/messages.rb +66 -0
  60. data/lib/pact/provider/pact_helper_locator.rb +24 -0
  61. data/lib/pact/provider/pact_source.rb +40 -0
  62. data/lib/pact/provider/pact_spec_runner.rb +188 -0
  63. data/lib/pact/provider/pact_uri.rb +55 -0
  64. data/lib/pact/provider/pact_verification.rb +17 -0
  65. data/lib/pact/provider/print_missing_provider_states.rb +35 -0
  66. data/lib/pact/provider/request.rb +77 -0
  67. data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
  68. data/lib/pact/provider/rspec/calculate_exit_code.rb +18 -0
  69. data/lib/pact/provider/rspec/custom_options_file +0 -0
  70. data/lib/pact/provider/rspec/formatter_rspec_2.rb +76 -0
  71. data/lib/pact/provider/rspec/formatter_rspec_3.rb +195 -0
  72. data/lib/pact/provider/rspec/json_formatter.rb +100 -0
  73. data/lib/pact/provider/rspec/matchers.rb +80 -0
  74. data/lib/pact/provider/rspec/pact_broker_formatter.rb +76 -0
  75. data/lib/pact/provider/rspec.rb +234 -0
  76. data/lib/pact/provider/state/provider_state.rb +180 -0
  77. data/lib/pact/provider/state/provider_state_configured_modules.rb +15 -0
  78. data/lib/pact/provider/state/provider_state_manager.rb +42 -0
  79. data/lib/pact/provider/state/provider_state_proxy.rb +39 -0
  80. data/lib/pact/provider/state/set_up.rb +13 -0
  81. data/lib/pact/provider/state/tear_down.rb +13 -0
  82. data/lib/pact/provider/test_methods.rb +77 -0
  83. data/lib/pact/provider/verification_report.rb +36 -0
  84. data/lib/pact/provider/verification_results/create.rb +88 -0
  85. data/lib/pact/provider/verification_results/publish.rb +143 -0
  86. data/lib/pact/provider/verification_results/publish_all.rb +50 -0
  87. data/lib/pact/provider/verification_results/verification_result.rb +40 -0
  88. data/lib/pact/provider/world.rb +50 -0
  89. data/lib/pact/provider.rb +3 -0
  90. data/lib/pact/retry.rb +37 -0
  91. data/lib/pact/tasks/task_helper.rb +62 -0
  92. data/lib/pact/tasks/verification_task.rb +95 -0
  93. data/lib/pact/tasks.rb +2 -0
  94. data/lib/pact/templates/help.erb +22 -0
  95. data/lib/pact/templates/provider_state.erb +14 -0
  96. data/lib/pact/utils/metrics.rb +100 -0
  97. data/lib/pact/utils/string.rb +35 -0
  98. data/lib/pact/v2/configuration.rb +23 -0
  99. data/lib/pact/v2/consumer/grpc_interaction_builder.rb +187 -0
  100. data/lib/pact/v2/consumer/http_interaction_builder.rb +163 -0
  101. data/lib/pact/v2/consumer/interaction_contents.rb +54 -0
  102. data/lib/pact/v2/consumer/message_interaction_builder.rb +280 -0
  103. data/lib/pact/v2/consumer/mock_server.rb +99 -0
  104. data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
  105. data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
  106. data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
  107. data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
  108. data/lib/pact/v2/consumer/pact_config.rb +24 -0
  109. data/lib/pact/v2/consumer.rb +8 -0
  110. data/lib/pact/v2/matchers/base.rb +67 -0
  111. data/lib/pact/v2/matchers/v1/equality.rb +19 -0
  112. data/lib/pact/v2/matchers/v2/regex.rb +19 -0
  113. data/lib/pact/v2/matchers/v2/type.rb +17 -0
  114. data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
  115. data/lib/pact/v2/matchers/v3/date.rb +18 -0
  116. data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
  117. data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
  118. data/lib/pact/v2/matchers/v3/each.rb +42 -0
  119. data/lib/pact/v2/matchers/v3/include.rb +17 -0
  120. data/lib/pact/v2/matchers/v3/integer.rb +17 -0
  121. data/lib/pact/v2/matchers/v3/number.rb +17 -0
  122. data/lib/pact/v2/matchers/v3/time.rb +18 -0
  123. data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
  124. data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
  125. data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
  126. data/lib/pact/v2/matchers/v4/not_empty.rb +17 -0
  127. data/lib/pact/v2/matchers.rb +94 -0
  128. data/lib/pact/v2/native/blocking_verifier.rb +17 -0
  129. data/lib/pact/v2/native/logger.rb +25 -0
  130. data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
  131. data/lib/pact/v2/provider/base_verifier.rb +242 -0
  132. data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
  133. data/lib/pact/v2/provider/gruf_server.rb +75 -0
  134. data/lib/pact/v2/provider/http_server.rb +79 -0
  135. data/lib/pact/v2/provider/http_verifier.rb +43 -0
  136. data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
  137. data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
  138. data/lib/pact/v2/provider/pact_broker_proxy.rb +71 -0
  139. data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
  140. data/lib/pact/v2/provider/pact_config/async.rb +29 -0
  141. data/lib/pact/v2/provider/pact_config/base.rb +101 -0
  142. data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
  143. data/lib/pact/v2/provider/pact_config/http.rb +27 -0
  144. data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
  145. data/lib/pact/v2/provider/pact_config.rb +26 -0
  146. data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
  147. data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
  148. data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
  149. data/lib/pact/v2/provider.rb +8 -0
  150. data/lib/pact/v2/railtie.rb +13 -0
  151. data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +80 -0
  152. data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
  153. data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
  154. data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
  155. data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
  156. data/lib/pact/v2/rspec.rb +17 -0
  157. data/lib/pact/v2/tasks/pact.rake +13 -0
  158. data/lib/pact/v2/version.rb +8 -0
  159. data/lib/pact/v2.rb +71 -0
  160. data/lib/pact/version.rb +4 -0
  161. data/lib/pact.rb +13 -0
  162. data/lib/tasks/pact.rake +34 -0
  163. data/pact.gemspec +106 -0
  164. metadata +529 -0
@@ -0,0 +1,234 @@
1
+ require 'open-uri'
2
+ require 'pact/consumer_contract'
3
+ require 'pact/provider/rspec/matchers'
4
+ require 'pact/provider/test_methods'
5
+ require 'pact/provider/configuration'
6
+ require 'pact/provider/matchers/messages'
7
+
8
+
9
+ module Pact
10
+ module Provider
11
+ module RSpec
12
+
13
+ module InstanceMethods
14
+ def app
15
+ Pact.configuration.provider.app
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ EMPTY_ARRAY = [].freeze
21
+
22
+ include ::RSpec::Core::DSL
23
+
24
+ def honour_pactfile pact_source, pact_json, options
25
+ pact_uri = pact_source.uri
26
+ Pact.configuration.output_stream.puts "INFO: Reading pact at #{pact_uri}"
27
+ consumer_contract = Pact::ConsumerContract.from_json(pact_json)
28
+
29
+ suffix = pact_uri.metadata[:pending] ? " [PENDING]": ""
30
+ example_group_description = "Verifying a pact between #{consumer_contract.consumer.name} and #{consumer_contract.provider.name}#{suffix}"
31
+ example_group_metadata = { pactfile_uri: pact_uri, pact_criteria: options[:criteria] }
32
+
33
+ ::RSpec.describe example_group_description, example_group_metadata do
34
+ honour_consumer_contract consumer_contract, options.merge(
35
+ pact_json: pact_json,
36
+ pact_uri: pact_uri,
37
+ pact_source: pact_source,
38
+ consumer_contract: consumer_contract,
39
+ criteria: options[:criteria]
40
+ )
41
+ end
42
+ end
43
+
44
+ def honour_consumer_contract consumer_contract, options = {}
45
+ describe_consumer_contract consumer_contract, options.merge(consumer: consumer_contract.consumer.name, pact_context: InteractionContext.new)
46
+ end
47
+
48
+ private
49
+
50
+ def describe_consumer_contract consumer_contract, options
51
+ consumer_interactions(consumer_contract, options).tap{ |interactions|
52
+ if interactions.empty?
53
+ # If there are no interactions, the documentation formatter never fires to print this out,
54
+ # so print it out here.
55
+ Pact.configuration.output_stream.puts "DEBUG: All interactions for #{options[:pact_uri]} have been filtered out by criteria: #{options[:criteria]}" if options[:criteria] && options[:criteria].any?
56
+ end
57
+ }.each do |interaction|
58
+ describe_interaction_with_provider_state interaction, options
59
+ end
60
+ end
61
+
62
+ def consumer_interactions(consumer_contract, options)
63
+ if options[:criteria].nil?
64
+ consumer_contract.interactions
65
+ else
66
+ consumer_contract.find_interactions(options[:criteria])
67
+ end
68
+ end
69
+
70
+ def describe_interaction_with_provider_state interaction, options
71
+ if interaction.provider_state
72
+ describe "Given #{interaction.provider_state}" do
73
+ describe_interaction interaction, options
74
+ end
75
+ else
76
+ describe_interaction interaction, options
77
+ end
78
+ end
79
+
80
+ def describe_interaction interaction, options
81
+ # pact_uri and pact_interaction are used by
82
+ # Pact::Provider::RSpec::PactBrokerFormatter
83
+
84
+ # pact_interaction_example_description is used by
85
+ # Pact::Provider::RSpec::Formatter and Pact::Provider::RSpec::Formatter2
86
+
87
+ # pact: verify is used to allow RSpec before and after hooks.
88
+ metadata = {
89
+ pact: :verify,
90
+ pact_interaction: interaction,
91
+ pact_interaction_example_description: interaction_description_for_rerun_command(interaction),
92
+ pact_uri: options[:pact_uri],
93
+ pact_source: options[:pact_source],
94
+ pact_ignore_failures: options[:pact_source].pending? || options[:ignore_failures],
95
+ pact_consumer_contract: options[:consumer_contract]
96
+ }
97
+
98
+ describe description_for(interaction), metadata do
99
+
100
+ interaction_context = InteractionContext.new
101
+ pact_context = options[:pact_context]
102
+
103
+ before do | example |
104
+ interaction_context.run_once :before do
105
+ Pact.configuration.logger.info "Running example '#{Pact::RSpec.full_description(example)}'"
106
+ provider_states_result = set_up_provider_states interaction.provider_states, options[:consumer]
107
+ state_params = provider_states_result[interaction.provider_state];
108
+ replay_interaction interaction, options[:request_customizer], state_params
109
+ interaction_context.last_response = last_response
110
+ end
111
+ end
112
+
113
+ after do
114
+ interaction_context.run_once :after do
115
+ tear_down_provider_states interaction.provider_states, options[:consumer]
116
+ end
117
+ end
118
+
119
+ if interaction.respond_to?(:message?) && interaction.message?
120
+ describe_message Pact::Response.new(interaction.response), interaction_context
121
+ else
122
+ describe "with #{interaction.request.method_and_path}" do
123
+ describe_response Pact::Response.new(interaction.response), interaction_context
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def describe_message expected_response, interaction_context
130
+ include Pact::RSpec::Matchers
131
+ extend Pact::Matchers::Messages
132
+
133
+
134
+ let(:expected_contents) { expected_response.body[:contents].as_json }
135
+ let(:response) { interaction_context.last_response }
136
+ let(:differ) { Pact.configuration.body_differ_for_content_type diff_content_type }
137
+ let(:diff_formatter) { Pact.configuration.diff_formatter_for_content_type diff_content_type }
138
+ let(:diff_options) { { with: differ, diff_formatter: diff_formatter } }
139
+ let(:diff_content_type) { 'application/json' }
140
+ let(:response_body) { parse_body_from_response(response) }
141
+ let(:actual_contents) { response_body['contents'] }
142
+
143
+ it "has matching content" do | example |
144
+ if response.status != 200
145
+ raise "An error was raised while verifying the message. The response body is: #{response.body}"
146
+ end
147
+ set_metadata(example, :pact_actual_contents, actual_contents)
148
+ expect(actual_contents).to match_term expected_contents, diff_options, example
149
+ end
150
+ end
151
+
152
+ def describe_response expected_response, interaction_context
153
+
154
+ describe "returns a response which" do
155
+
156
+ include Pact::RSpec::Matchers
157
+ extend Pact::Matchers::Messages
158
+
159
+ let(:expected_response_status) { expected_response.status }
160
+ let(:expected_response_body) { expected_response.body }
161
+ let(:response) { interaction_context.last_response }
162
+ let(:response_status) { response.status }
163
+ let(:response_body) { parse_body_from_response(response) }
164
+ let(:differ) { Pact.configuration.body_differ_for_content_type diff_content_type }
165
+ let(:diff_formatter) { Pact.configuration.diff_formatter_for_content_type diff_content_type }
166
+ let(:expected_content_type) { Pact::Headers.new(expected_response.headers || {})['Content-Type'] }
167
+ let(:actual_content_type) { response.headers['Content-Type']}
168
+ let(:diff_content_type) { String === expected_content_type ? expected_content_type : actual_content_type } # expected_content_type may be a Regexp
169
+ let(:diff_options) { { with: differ, diff_formatter: diff_formatter } }
170
+
171
+ if expected_response.status
172
+ it "has status code #{expected_response.status}" do | example |
173
+ set_metadata(example, :pact_actual_status, response_status)
174
+ expect(response_status).to eql expected_response_status
175
+ end
176
+ end
177
+
178
+ if expected_response.headers
179
+ describe "includes headers" do
180
+ expected_response.headers.each do |name, expected_header_value|
181
+ it "\"#{name}\" which #{expected_desc_for_it(expected_header_value)}" do | example |
182
+ set_metadata(example, :pact_actual_headers, response.headers)
183
+ header_value = response.headers[name]
184
+ expect(header_value).to match_header(name, expected_header_value)
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ if expected_response.body
191
+ it "has a matching body" do | example |
192
+ set_metadata(example, :pact_actual_body, response_body)
193
+ expect(response_body).to match_term expected_response_body, diff_options, example
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ def description_for interaction
200
+ interaction.provider_state ? interaction.description : interaction.description.capitalize
201
+ end
202
+
203
+ def interaction_description_for_rerun_command interaction
204
+ description_for(interaction).capitalize + ( interaction.provider_state ? " given #{interaction.provider_state}" : "")
205
+ end
206
+ end
207
+
208
+ # The "arrange" and "act" parts of the test really only need to be run once,
209
+ # however, stubbing is not supported in before :all, so this is a
210
+ # wee hack to enable before :all like functionality using before :each.
211
+ # In an ideal world, the test setup and execution should be quick enough for
212
+ # the difference between :all and :each to be unnoticable, but the annoying
213
+ # reality is, sometimes it does make a difference. This is for you, V!
214
+
215
+ class InteractionContext
216
+
217
+ attr_accessor :last_response
218
+
219
+ def initialize
220
+ @already_run = []
221
+ end
222
+
223
+ def run_once hook
224
+ unless @already_run.include?(hook)
225
+ yield
226
+ @already_run << hook
227
+ end
228
+ end
229
+
230
+ end
231
+ end
232
+ end
233
+ end
234
+
@@ -0,0 +1,180 @@
1
+ require 'pact/shared/dsl'
2
+ require 'pact/provider/state/provider_state_configured_modules'
3
+
4
+ module Pact
5
+ module Provider::State
6
+
7
+ BASE_PROVIDER_STATE_NAME = "__base_provider_state__"
8
+
9
+ module DSL
10
+ def provider_state name, &block
11
+ ProviderStates.provider_state(name, &block).register
12
+ end
13
+
14
+ def set_up &block
15
+ ProviderStates.base_provider_state.register.register_set_up(&block)
16
+ end
17
+
18
+ def tear_down &block
19
+ ProviderStates.base_provider_state.register_tear_down(&block)
20
+ end
21
+
22
+ def provider_states_for name, &block
23
+ ProviderStates.current_namespaces << name
24
+ instance_eval(&block)
25
+ ProviderStates.current_namespaces.pop
26
+ end
27
+ end
28
+
29
+ class ProviderStates
30
+ def self.provider_state name, &block
31
+ ProviderState.build(name, current_namespaces.join('.'), &block)
32
+ end
33
+
34
+ def self.base_provider_state
35
+ fullname = namespaced_name BASE_PROVIDER_STATE_NAME, {:for => current_namespaces.first }
36
+ provider_states[fullname] ||= ProviderState.new(BASE_PROVIDER_STATE_NAME, current_namespaces.join('.'))
37
+ provider_states[fullname]
38
+ end
39
+
40
+ def self.register name, provider_state
41
+ provider_states[name] = provider_state
42
+ end
43
+
44
+ def self.provider_states
45
+ @@provider_states ||= {}
46
+ end
47
+
48
+ def self.current_namespaces
49
+ @@current_namespaces ||= []
50
+ end
51
+
52
+ def self.get name, options = {}
53
+ fullname = namespaced_name name, options
54
+ (provider_states[fullname] || provider_states[fullname.to_sym] || provider_states[name])
55
+ end
56
+
57
+ def self.get_base opts = {}
58
+ fullname = namespaced_name BASE_PROVIDER_STATE_NAME, opts
59
+ provider_states[fullname] || NoOpProviderState
60
+ end
61
+
62
+ def self.namespaced_name name, options = {}
63
+ options[:for] ? "#{options[:for]}.#{name}" : name
64
+ end
65
+ end
66
+
67
+ class ProviderState
68
+
69
+ attr_accessor :name
70
+ attr_accessor :namespace
71
+
72
+ extend Pact::DSL
73
+
74
+ def initialize name, namespace, &block
75
+ @name = name
76
+ @namespace = namespace
77
+ @set_up_defined = false
78
+ @tear_down_defined = false
79
+ @no_op_defined = false
80
+ end
81
+
82
+ dsl do
83
+ def set_up &block
84
+ self.register_set_up(&block)
85
+ end
86
+
87
+ def tear_down &block
88
+ self.register_tear_down(&block)
89
+ end
90
+
91
+ def no_op
92
+ self.register_no_op
93
+ end
94
+ end
95
+
96
+ def register
97
+ ProviderStates.register(namespaced(name), self)
98
+ self
99
+ end
100
+
101
+ def finalize
102
+ validate
103
+ end
104
+
105
+ def register_set_up &block
106
+ @set_up_block = block
107
+ @set_up_defined = true
108
+ end
109
+
110
+ def register_tear_down &block
111
+ @tear_down_block = block
112
+ @tear_down_defined = true
113
+ end
114
+
115
+ def register_no_op
116
+ @no_op_defined = true
117
+ end
118
+
119
+ def set_up params = {}
120
+ if @set_up_block
121
+ include_provider_state_configured_modules
122
+ instance_exec params, &@set_up_block
123
+ end
124
+ end
125
+
126
+ def tear_down params = {}
127
+ if @tear_down_block
128
+ include_provider_state_configured_modules
129
+ instance_exec params, &@tear_down_block
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ attr_accessor :no_op_defined, :set_up_defined, :tear_down_defined
136
+
137
+ def validate
138
+ if no_op_defined && set_up_defined
139
+ raise error_message_for_extra_block 'set_up'
140
+ elsif no_op_defined && tear_down_defined
141
+ raise error_message_for_extra_block 'tear_down'
142
+ elsif !(no_op_defined || set_up_defined || tear_down_defined)
143
+ raise "Please provide a set_up or tear_down block for provider state \"#{name}\". If there is no data to set up or tear down, you can use \"no_op\" instead."
144
+ end
145
+ end
146
+
147
+ def error_message_for_extra_block block_name
148
+ "Provider state \"#{name}\" has been defined as a no_op but it also has a #{block_name} block. Please remove one or the other."
149
+ end
150
+
151
+ def namespaced(name)
152
+ if namespace.empty?
153
+ name
154
+ else
155
+ "#{namespace}.#{name}"
156
+ end
157
+ end
158
+
159
+ def include_provider_state_configured_modules
160
+ # Doing this at runtime means the order of the Pact configuration block
161
+ # and the provider state declarations doesn't matter.
162
+ # Using include ProviderStateConfiguredModules on the class doesn't seem to work -
163
+ # modules dynamically added to ProviderStateConfiguredModules don't seem to be
164
+ # included in the including class.
165
+ self.extend(ProviderStateConfiguredModules) unless self.singleton_class.ancestors.include?(ProviderStateConfiguredModules)
166
+ end
167
+ end
168
+
169
+ class NoOpProviderState
170
+
171
+ def self.set_up params = {}
172
+
173
+ end
174
+
175
+ def self.tear_down params = {}
176
+
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,15 @@
1
+ require 'rspec/mocks'
2
+
3
+ module Pact
4
+ module Provider
5
+ module State
6
+ module ProviderStateConfiguredModules
7
+
8
+ include ::RSpec::Mocks::ExampleMethods
9
+
10
+ # Placeholder for modules configured using config.include
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ module Pact
2
+ module Provider::State
3
+ class ProviderStateManager
4
+
5
+ attr_reader :provider_state_name, :params, :consumer
6
+
7
+ def initialize provider_state_name, params, consumer
8
+ @provider_state_name = provider_state_name
9
+ @params = params
10
+ @consumer = consumer
11
+ end
12
+
13
+ def set_up_provider_state
14
+ get_global_base_provider_state.set_up(params)
15
+ get_consumer_base_provider_state.set_up(params)
16
+ if provider_state_name
17
+ get_provider_state.set_up(params)
18
+ end
19
+ end
20
+
21
+ def tear_down_provider_state
22
+ if provider_state_name
23
+ get_provider_state.tear_down(params)
24
+ end
25
+ get_consumer_base_provider_state.tear_down(params)
26
+ get_global_base_provider_state.tear_down(params)
27
+ end
28
+
29
+ def get_provider_state
30
+ Pact.provider_world.provider_states.get(provider_state_name, :for => consumer)
31
+ end
32
+
33
+ def get_consumer_base_provider_state
34
+ Pact.provider_world.provider_states.get_base(:for => consumer)
35
+ end
36
+
37
+ def get_global_base_provider_state
38
+ Pact.provider_world.provider_states.get_base
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ module Pact
2
+ module Provider::State
3
+ class ProviderStateProxy
4
+
5
+ attr_reader :missing_provider_states
6
+
7
+ def initialize
8
+ @missing_provider_states = {}
9
+ end
10
+
11
+ def get name, options = {}
12
+ unless provider_state = ProviderStates.get(name, options)
13
+ register_missing_provider_state name, options[:for]
14
+ raise error_message name, options[:for]
15
+ end
16
+ provider_state
17
+ end
18
+
19
+ def get_base options = {}
20
+ ProviderStates.get_base options
21
+ end
22
+
23
+ private
24
+
25
+ def error_message name, consumer
26
+ "Could not find provider state \"#{name}\" for consumer #{consumer}"
27
+ end
28
+
29
+ def register_missing_provider_state name, consumer
30
+ missing_states_for(consumer) << name unless missing_states_for(consumer).include?(name)
31
+ end
32
+
33
+ def missing_states_for consumer
34
+ @missing_provider_states[consumer] ||= []
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ require 'pact/provider/state/provider_state_manager'
2
+
3
+ module Pact
4
+ module Provider
5
+ module State
6
+ class SetUp
7
+ def self.call provider_state_name, consumer, options = {}
8
+ State::ProviderStateManager.new(provider_state_name, options[:params], consumer).set_up_provider_state
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'pact/provider/state/provider_state_manager'
2
+
3
+ module Pact
4
+ module Provider
5
+ module State
6
+ class TearDown
7
+ def self.call provider_state_name, consumer, options = {}
8
+ State::ProviderStateManager.new(provider_state_name, options[:params], consumer).tear_down_provider_state
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,77 @@
1
+ require 'pact/logging'
2
+ require 'rack/test'
3
+ require 'pact/consumer_contract/interaction'
4
+ require 'pact/provider/state/provider_state'
5
+ require 'pact/provider/state/provider_state_proxy'
6
+ require 'pact/provider/request'
7
+ require 'pact/provider/world'
8
+ require 'pact/provider/state/provider_state_manager'
9
+
10
+ module Pact
11
+ module Provider
12
+ module TestMethods
13
+
14
+ include Pact::Logging
15
+ include Rack::Test::Methods
16
+
17
+ def replay_interaction interaction, request_customizer = nil, state_params = nil
18
+ request = Request::Replayable.new(interaction.request, state_params)
19
+ request = request_customizer.call(request, interaction) if request_customizer
20
+ args = [request.path, request.body, request.headers]
21
+
22
+ logger.info "Sending #{request.method.upcase} request to path: \"#{request.path}\" with headers: #{request.headers}, see debug logs for body"
23
+ logger.debug "body :#{request.body}"
24
+ response = if self.respond_to?(:custom_request)
25
+ self.custom_request(request.method.upcase, *args)
26
+ else
27
+ self.send(request.method.downcase, *args)
28
+ end
29
+ logger.info "Received response with status: #{response.status}, headers: #{response.headers}, see debug logs for body"
30
+ logger.debug "body: #{response.body}"
31
+ end
32
+
33
+ def parse_body_from_response rack_response
34
+ case rack_response.headers['Content-Type']
35
+ when /json/
36
+ # For https://github.com/pact-foundation/pact-net/issues/237
37
+ # Only required for the pact-ruby-standalone ¯\_(ツ)_/¯
38
+ JSON.load("[#{rack_response.body}]").first
39
+ else
40
+ rack_response.body
41
+ end
42
+ end
43
+
44
+ def set_up_provider_states provider_states, consumer, options = {}
45
+ provider_states_result = {};
46
+ # If there are no provider state, execute with an nil state to ensure global and base states are executed
47
+ Pact.configuration.provider_state_set_up.call(nil, consumer, options) if provider_states.nil? || provider_states.empty?
48
+ provider_states.each do | provider_state |
49
+ result = Pact.configuration.provider_state_set_up.call(provider_state.name, consumer, options.merge(params: provider_state.params))
50
+ if result.is_a?(Hash)
51
+ provider_states_result[provider_state.name] = result
52
+ end
53
+ end
54
+
55
+ provider_states_result
56
+ end
57
+
58
+ def tear_down_provider_states provider_states, consumer, options = {}
59
+ # If there are no provider state, execute with an nil state to ensure global and base states are executed
60
+ Pact.configuration.provider_state_tear_down.call(nil, consumer, options) if provider_states.nil? || provider_states.empty?
61
+ provider_states.reverse_each do | provider_state |
62
+ Pact.configuration.provider_state_tear_down.call(provider_state.name, consumer, options.merge(params: provider_state.params))
63
+ end
64
+ end
65
+
66
+ def set_metadata example, key, value
67
+ Pact::RSpec.with_rspec_3 do
68
+ example.metadata[key] = value
69
+ end
70
+
71
+ Pact::RSpec.with_rspec_2 do
72
+ example.example.metadata[key] = value
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,36 @@
1
+ require 'pact/consumer_contract'
2
+
3
+ module Pact::Provider
4
+ class VerificationReport
5
+
6
+ include Pact::FileName
7
+
8
+ def initialize (options)
9
+ @consumer = options[:consumer]
10
+ @provider = options[:provider]
11
+ @result = options[:result]
12
+ @output = options[:output]
13
+ end
14
+
15
+ def to_hash
16
+ {
17
+ :consumer => @consumer,
18
+ :provider => @provider,
19
+ :result => @result,
20
+ :output => @output
21
+ }
22
+ end
23
+
24
+ def as_json options = {}
25
+ to_hash
26
+ end
27
+
28
+ def to_json(options = {})
29
+ as_json.to_json(options)
30
+ end
31
+
32
+ def report_file_name
33
+ file_name("#{@consumer[:name]}_#{@consumer[:ref]}", "#{@provider[:name]}_#{@provider[:ref]}")
34
+ end
35
+ end
36
+ end