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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +1321 -0
- data/LICENSE.txt +23 -0
- data/bin/pact +4 -0
- data/lib/pact/cli/generate_pact_docs.rb +4 -0
- data/lib/pact/cli/run_pact_verification.rb +99 -0
- data/lib/pact/cli/spec_criteria.rb +26 -0
- data/lib/pact/cli.rb +45 -0
- data/lib/pact/consumer/configuration/configuration_extensions.rb +90 -0
- data/lib/pact/consumer/configuration/dsl.rb +11 -0
- data/lib/pact/consumer/configuration/mock_service.rb +112 -0
- data/lib/pact/consumer/configuration/service_consumer.rb +51 -0
- data/lib/pact/consumer/configuration/service_provider.rb +40 -0
- data/lib/pact/consumer/configuration.rb +10 -0
- data/lib/pact/consumer/consumer_contract_builder.rb +82 -0
- data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
- data/lib/pact/consumer/interaction_builder.rb +45 -0
- data/lib/pact/consumer/rspec.rb +35 -0
- data/lib/pact/consumer/spec_hooks.rb +40 -0
- data/lib/pact/consumer/world.rb +37 -0
- data/lib/pact/consumer.rb +7 -0
- data/lib/pact/doc/README.md +13 -0
- data/lib/pact/doc/doc_file.rb +40 -0
- data/lib/pact/doc/generate.rb +11 -0
- data/lib/pact/doc/generator.rb +82 -0
- data/lib/pact/doc/interaction_view_model.rb +124 -0
- data/lib/pact/doc/markdown/consumer_contract_renderer.rb +68 -0
- data/lib/pact/doc/markdown/generator.rb +26 -0
- data/lib/pact/doc/markdown/index_renderer.rb +43 -0
- data/lib/pact/doc/markdown/interaction.erb +14 -0
- data/lib/pact/doc/markdown/interaction_renderer.rb +43 -0
- data/lib/pact/doc/sort_interactions.rb +16 -0
- data/lib/pact/hal/authorization_header_redactor.rb +32 -0
- data/lib/pact/hal/entity.rb +110 -0
- data/lib/pact/hal/http_client.rb +128 -0
- data/lib/pact/hal/link.rb +112 -0
- data/lib/pact/hal/non_json_entity.rb +28 -0
- data/lib/pact/hash_refinements.rb +17 -0
- data/lib/pact/pact_broker/fetch_pact_uris_for_verification.rb +112 -0
- data/lib/pact/pact_broker/fetch_pacts.rb +103 -0
- data/lib/pact/pact_broker/notices.rb +34 -0
- data/lib/pact/pact_broker/pact_selection_description.rb +66 -0
- data/lib/pact/pact_broker.rb +25 -0
- data/lib/pact/project_root.rb +7 -0
- data/lib/pact/provider/configuration/configuration_extension.rb +69 -0
- data/lib/pact/provider/configuration/dsl.rb +18 -0
- data/lib/pact/provider/configuration/message_provider_dsl.rb +63 -0
- data/lib/pact/provider/configuration/pact_verification.rb +48 -0
- data/lib/pact/provider/configuration/pact_verification_from_broker.rb +126 -0
- data/lib/pact/provider/configuration/service_provider_config.rb +32 -0
- data/lib/pact/provider/configuration/service_provider_dsl.rb +107 -0
- data/lib/pact/provider/configuration.rb +7 -0
- data/lib/pact/provider/context.rb +0 -0
- data/lib/pact/provider/help/console_text.rb +76 -0
- data/lib/pact/provider/help/content.rb +38 -0
- data/lib/pact/provider/help/pact_diff.rb +43 -0
- data/lib/pact/provider/help/prompt_text.rb +49 -0
- data/lib/pact/provider/help/write.rb +56 -0
- data/lib/pact/provider/matchers/messages.rb +66 -0
- data/lib/pact/provider/pact_helper_locator.rb +24 -0
- data/lib/pact/provider/pact_source.rb +40 -0
- data/lib/pact/provider/pact_spec_runner.rb +188 -0
- data/lib/pact/provider/pact_uri.rb +55 -0
- data/lib/pact/provider/pact_verification.rb +17 -0
- data/lib/pact/provider/print_missing_provider_states.rb +35 -0
- data/lib/pact/provider/request.rb +77 -0
- data/lib/pact/provider/rspec/backtrace_formatter.rb +43 -0
- data/lib/pact/provider/rspec/calculate_exit_code.rb +18 -0
- data/lib/pact/provider/rspec/custom_options_file +0 -0
- data/lib/pact/provider/rspec/formatter_rspec_2.rb +76 -0
- data/lib/pact/provider/rspec/formatter_rspec_3.rb +195 -0
- data/lib/pact/provider/rspec/json_formatter.rb +100 -0
- data/lib/pact/provider/rspec/matchers.rb +80 -0
- data/lib/pact/provider/rspec/pact_broker_formatter.rb +76 -0
- data/lib/pact/provider/rspec.rb +234 -0
- data/lib/pact/provider/state/provider_state.rb +180 -0
- data/lib/pact/provider/state/provider_state_configured_modules.rb +15 -0
- data/lib/pact/provider/state/provider_state_manager.rb +42 -0
- data/lib/pact/provider/state/provider_state_proxy.rb +39 -0
- data/lib/pact/provider/state/set_up.rb +13 -0
- data/lib/pact/provider/state/tear_down.rb +13 -0
- data/lib/pact/provider/test_methods.rb +77 -0
- data/lib/pact/provider/verification_report.rb +36 -0
- data/lib/pact/provider/verification_results/create.rb +88 -0
- data/lib/pact/provider/verification_results/publish.rb +143 -0
- data/lib/pact/provider/verification_results/publish_all.rb +50 -0
- data/lib/pact/provider/verification_results/verification_result.rb +40 -0
- data/lib/pact/provider/world.rb +50 -0
- data/lib/pact/provider.rb +3 -0
- data/lib/pact/retry.rb +37 -0
- data/lib/pact/tasks/task_helper.rb +62 -0
- data/lib/pact/tasks/verification_task.rb +95 -0
- data/lib/pact/tasks.rb +2 -0
- data/lib/pact/templates/help.erb +22 -0
- data/lib/pact/templates/provider_state.erb +14 -0
- data/lib/pact/utils/metrics.rb +100 -0
- data/lib/pact/utils/string.rb +35 -0
- data/lib/pact/v2/configuration.rb +23 -0
- data/lib/pact/v2/consumer/grpc_interaction_builder.rb +187 -0
- data/lib/pact/v2/consumer/http_interaction_builder.rb +163 -0
- data/lib/pact/v2/consumer/interaction_contents.rb +54 -0
- data/lib/pact/v2/consumer/message_interaction_builder.rb +280 -0
- data/lib/pact/v2/consumer/mock_server.rb +99 -0
- data/lib/pact/v2/consumer/pact_config/base.rb +24 -0
- data/lib/pact/v2/consumer/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/consumer/pact_config/http.rb +55 -0
- data/lib/pact/v2/consumer/pact_config/message.rb +17 -0
- data/lib/pact/v2/consumer/pact_config.rb +24 -0
- data/lib/pact/v2/consumer.rb +8 -0
- data/lib/pact/v2/matchers/base.rb +67 -0
- data/lib/pact/v2/matchers/v1/equality.rb +19 -0
- data/lib/pact/v2/matchers/v2/regex.rb +19 -0
- data/lib/pact/v2/matchers/v2/type.rb +17 -0
- data/lib/pact/v2/matchers/v3/boolean.rb +17 -0
- data/lib/pact/v2/matchers/v3/date.rb +18 -0
- data/lib/pact/v2/matchers/v3/date_time.rb +18 -0
- data/lib/pact/v2/matchers/v3/decimal.rb +17 -0
- data/lib/pact/v2/matchers/v3/each.rb +42 -0
- data/lib/pact/v2/matchers/v3/include.rb +17 -0
- data/lib/pact/v2/matchers/v3/integer.rb +17 -0
- data/lib/pact/v2/matchers/v3/number.rb +17 -0
- data/lib/pact/v2/matchers/v3/time.rb +18 -0
- data/lib/pact/v2/matchers/v4/each_key.rb +26 -0
- data/lib/pact/v2/matchers/v4/each_key_value.rb +32 -0
- data/lib/pact/v2/matchers/v4/each_value.rb +33 -0
- data/lib/pact/v2/matchers/v4/not_empty.rb +17 -0
- data/lib/pact/v2/matchers.rb +94 -0
- data/lib/pact/v2/native/blocking_verifier.rb +17 -0
- data/lib/pact/v2/native/logger.rb +25 -0
- data/lib/pact/v2/provider/async_message_verifier.rb +28 -0
- data/lib/pact/v2/provider/base_verifier.rb +242 -0
- data/lib/pact/v2/provider/grpc_verifier.rb +38 -0
- data/lib/pact/v2/provider/gruf_server.rb +75 -0
- data/lib/pact/v2/provider/http_server.rb +79 -0
- data/lib/pact/v2/provider/http_verifier.rb +43 -0
- data/lib/pact/v2/provider/message_provider_servlet.rb +79 -0
- data/lib/pact/v2/provider/mixed_verifier.rb +22 -0
- data/lib/pact/v2/provider/pact_broker_proxy.rb +71 -0
- data/lib/pact/v2/provider/pact_broker_proxy_runner.rb +77 -0
- data/lib/pact/v2/provider/pact_config/async.rb +29 -0
- data/lib/pact/v2/provider/pact_config/base.rb +101 -0
- data/lib/pact/v2/provider/pact_config/grpc.rb +26 -0
- data/lib/pact/v2/provider/pact_config/http.rb +27 -0
- data/lib/pact/v2/provider/pact_config/mixed.rb +39 -0
- data/lib/pact/v2/provider/pact_config.rb +26 -0
- data/lib/pact/v2/provider/provider_server_runner.rb +89 -0
- data/lib/pact/v2/provider/provider_state_configuration.rb +32 -0
- data/lib/pact/v2/provider/provider_state_servlet.rb +86 -0
- data/lib/pact/v2/provider.rb +8 -0
- data/lib/pact/v2/railtie.rb +13 -0
- data/lib/pact/v2/rspec/support/pact_consumer_helpers.rb +80 -0
- data/lib/pact/v2/rspec/support/pact_message_helpers.rb +42 -0
- data/lib/pact/v2/rspec/support/pact_provider_helpers.rb +129 -0
- data/lib/pact/v2/rspec/support/waterdrop/pact_waterdrop_client.rb +27 -0
- data/lib/pact/v2/rspec/support/webmock/webmock_helpers.rb +30 -0
- data/lib/pact/v2/rspec.rb +17 -0
- data/lib/pact/v2/tasks/pact.rake +13 -0
- data/lib/pact/v2/version.rb +8 -0
- data/lib/pact/v2.rb +71 -0
- data/lib/pact/version.rb +4 -0
- data/lib/pact.rb +13 -0
- data/lib/tasks/pact.rake +34 -0
- data/pact.gemspec +106 -0
- 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,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
|