pact 0.1.37 → 1.0.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.
- data/Gemfile.lock +6 -19
- data/example/zoo-app/Gemfile +1 -2
- data/example/zoo-app/Gemfile.lock +16 -13
- data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +6 -6
- data/lib/pact/consumer/app_manager.rb +8 -28
- data/lib/pact/consumer/consumer_contract_builder.rb +65 -36
- data/lib/pact/consumer/dsl.rb +20 -10
- data/lib/pact/consumer/interaction_builder.rb +42 -0
- data/lib/pact/consumer/interactions_filter.rb +41 -0
- data/lib/pact/consumer/mock_service.rb +26 -19
- data/lib/pact/consumer/rspec.rb +2 -3
- data/lib/pact/consumer/server.rb +90 -0
- data/lib/pact/consumer.rb +6 -3
- data/lib/pact/consumer_contract/consumer_contract.rb +103 -0
- data/lib/pact/consumer_contract/interaction.rb +70 -0
- data/lib/pact/consumer_contract/service_consumer.rb +20 -0
- data/lib/pact/consumer_contract/service_provider.rb +20 -0
- data/lib/pact/consumer_contract.rb +1 -112
- data/lib/pact/{producer → provider}/dsl.rb +15 -4
- data/lib/pact/{producer → provider}/matchers.rb +0 -0
- data/lib/pact/{producer → provider}/pact_spec_runner.rb +2 -2
- data/lib/pact/{producer/producer_state.rb → provider/provider_state.rb} +13 -22
- data/lib/pact/provider/rspec.rb +128 -1
- data/lib/pact/{producer → provider}/test_methods.rb +12 -12
- data/lib/pact/{producer.rb → provider.rb} +0 -0
- data/lib/pact/verification_task.rb +4 -4
- data/lib/pact/version.rb +1 -1
- data/lib/pact.rb +1 -1
- data/pact.gemspec +1 -2
- data/scratchpad.txt +1 -1
- data/spec/features/consumption_spec.rb +15 -23
- data/spec/features/production_spec.rb +5 -5
- data/spec/features/{producer_states → provider_states}/zebras.rb +3 -3
- data/spec/integration/pact/consumer_configuration_spec.rb +3 -66
- data/spec/integration/pact/provider_configuration_spec.rb +1 -1
- data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +59 -15
- data/spec/lib/pact/consumer/dsl_spec.rb +4 -5
- data/spec/lib/pact/consumer/interaction_builder_spec.rb +91 -0
- data/spec/lib/pact/consumer/interactions_spec.rb +64 -0
- data/spec/lib/pact/consumer/mock_service_spec.rb +2 -6
- data/spec/lib/pact/consumer/service_consumer_spec.rb +1 -1
- data/spec/lib/pact/{consumer_contract_spec.rb → consumer_contract/consumer_contract_spec.rb} +72 -28
- data/spec/lib/pact/{consumer → consumer_contract}/interaction_spec.rb +49 -64
- data/spec/lib/pact/{producer/configuration_dsl_spec.rb → provider/dsl_spec.rb} +29 -28
- data/spec/lib/pact/{producer/producer_state_spec.rb → provider/provider_state_spec.rb} +17 -17
- data/spec/lib/pact/{producer → provider}/rspec_spec.rb +1 -1
- data/spec/lib/pact/verification_task_spec.rb +3 -3
- data/spec/spec_helper.rb +1 -0
- data/spec/support/a_consumer-a_producer.json +1 -1
- data/spec/support/a_consumer-a_provider.json +34 -0
- data/spec/support/consumer_contract_template.json +26 -0
- data/spec/support/factories.rb +78 -0
- data/spec/support/pact_rake_support.rb +1 -1
- data/spec/support/test_app_fail.json +1 -1
- data/spec/support/test_app_pass.json +1 -1
- data/tasks/pact-test.rake +3 -3
- metadata +38 -45
- data/lib/pact/consumer/configuration_dsl.rb +0 -73
- data/lib/pact/consumer/interaction.rb +0 -74
- data/lib/pact/consumer/run_condor.rb +0 -4
- data/lib/pact/consumer/run_mock_contract_service.rb +0 -13
- data/lib/pact/consumer/service_consumer.rb +0 -22
- data/lib/pact/consumer/service_producer.rb +0 -23
- data/lib/pact/producer/configuration_dsl.rb +0 -62
- data/lib/pact/producer/rspec.rb +0 -129
@@ -19,7 +19,7 @@ module Pact
|
|
19
19
|
module Consumer
|
20
20
|
|
21
21
|
class InteractionList
|
22
|
-
include Singleton
|
22
|
+
#include Singleton
|
23
23
|
|
24
24
|
attr_reader :interactions
|
25
25
|
attr_reader :unexpected_requests
|
@@ -112,9 +112,10 @@ module Pact
|
|
112
112
|
|
113
113
|
include RackHelper
|
114
114
|
|
115
|
-
def initialize name, logger
|
115
|
+
def initialize name, logger, interaction_list
|
116
116
|
@name = name
|
117
117
|
@logger = logger
|
118
|
+
@interaction_list = interaction_list
|
118
119
|
end
|
119
120
|
|
120
121
|
def match? env
|
@@ -123,7 +124,7 @@ module Pact
|
|
123
124
|
end
|
124
125
|
|
125
126
|
def respond env
|
126
|
-
|
127
|
+
@interaction_list.clear
|
127
128
|
@logger.info "Cleared interactions before example \"#{params_hash(env)['example_description']}\""
|
128
129
|
[200, {}, ['Deleted interactions']]
|
129
130
|
end
|
@@ -131,9 +132,10 @@ module Pact
|
|
131
132
|
|
132
133
|
class InteractionPost
|
133
134
|
|
134
|
-
def initialize name, logger
|
135
|
+
def initialize name, logger, interaction_list
|
135
136
|
@name = name
|
136
137
|
@logger = logger
|
138
|
+
@interaction_list = interaction_list
|
137
139
|
end
|
138
140
|
|
139
141
|
def match? env
|
@@ -143,7 +145,7 @@ module Pact
|
|
143
145
|
|
144
146
|
def respond env
|
145
147
|
interactions = Hashie::Mash.new(JSON.load(env['rack.input'].string))
|
146
|
-
|
148
|
+
@interaction_list.add interactions
|
147
149
|
@logger.info "Added interaction to #{@name}"
|
148
150
|
@logger.ap interactions
|
149
151
|
[200, {}, ['Added interactions']]
|
@@ -197,9 +199,10 @@ module Pact
|
|
197
199
|
include Pact::Matchers
|
198
200
|
include RequestExtractor
|
199
201
|
|
200
|
-
def initialize name, logger
|
202
|
+
def initialize name, logger, interaction_list
|
201
203
|
@name = name
|
202
204
|
@logger = logger
|
205
|
+
@interaction_list = interaction_list
|
203
206
|
end
|
204
207
|
|
205
208
|
def match? env
|
@@ -217,7 +220,7 @@ module Pact
|
|
217
220
|
@logger.info "#{@name} received request"
|
218
221
|
@logger.ap actual_request.as_json
|
219
222
|
candidates = []
|
220
|
-
matching_interactions =
|
223
|
+
matching_interactions = @interaction_list.interactions.select do |interaction|
|
221
224
|
expected_request = Request::Expected.from_hash(interaction.request.merge(:description => interaction.description))
|
222
225
|
candidates << expected_request if expected_request.matches_route? actual_request
|
223
226
|
expected_request.match actual_request
|
@@ -231,7 +234,7 @@ module Pact
|
|
231
234
|
handle_unrecognised_request(actual_request, candidates)
|
232
235
|
else
|
233
236
|
response = response_from(matching_interactions.first.response)
|
234
|
-
|
237
|
+
@interaction_list.register_matched matching_interactions.first
|
235
238
|
@logger.info "Found matching response on #{@name}:"
|
236
239
|
@logger.ap response
|
237
240
|
response
|
@@ -239,7 +242,7 @@ module Pact
|
|
239
242
|
end
|
240
243
|
|
241
244
|
def handle_unrecognised_request request, candidates
|
242
|
-
|
245
|
+
@interaction_list.register_unexpected request
|
243
246
|
@logger.error "No interaction found on #{@name} amongst expected requests \"#{candidates.map(&:description).join(', ')}\""
|
244
247
|
@logger.error 'Interaction diffs for that route:'
|
245
248
|
interaction_diff = candidates.map do |candidate|
|
@@ -267,9 +270,10 @@ module Pact
|
|
267
270
|
class MissingInteractionsGet
|
268
271
|
include RackHelper
|
269
272
|
|
270
|
-
def initialize name, logger
|
273
|
+
def initialize name, logger, interaction_list
|
271
274
|
@name = name
|
272
275
|
@logger = logger
|
276
|
+
@interaction_list = interaction_list
|
273
277
|
end
|
274
278
|
|
275
279
|
def match? env
|
@@ -278,7 +282,7 @@ module Pact
|
|
278
282
|
end
|
279
283
|
|
280
284
|
def respond env
|
281
|
-
number_of_missing_interactions =
|
285
|
+
number_of_missing_interactions = @interaction_list.missing_interactions.size
|
282
286
|
@logger.info "Number of missing interactions for mock \"#{@name}\" = #{number_of_missing_interactions}"
|
283
287
|
[200, {}, ["#{number_of_missing_interactions}"]]
|
284
288
|
end
|
@@ -289,10 +293,11 @@ module Pact
|
|
289
293
|
|
290
294
|
include RackHelper
|
291
295
|
|
292
|
-
def initialize name, logger, log_description
|
296
|
+
def initialize name, logger, log_description, interaction_list
|
293
297
|
@name = name
|
294
298
|
@logger = logger
|
295
299
|
@log_description = log_description
|
300
|
+
@interaction_list = interaction_list
|
296
301
|
end
|
297
302
|
|
298
303
|
def match? env
|
@@ -301,12 +306,12 @@ module Pact
|
|
301
306
|
end
|
302
307
|
|
303
308
|
def respond env
|
304
|
-
if
|
309
|
+
if @interaction_list.all_matched?
|
305
310
|
@logger.info "Verifying - interactions matched for example \"#{example_description(env)}\""
|
306
311
|
[200, {}, ['Interactions matched']]
|
307
312
|
else
|
308
313
|
@logger.warn "Verifying - actual interactions do not match expected interactions for example \"#{example_description(env)}\". Interaction diffs:"
|
309
|
-
@logger.ap
|
314
|
+
@logger.ap @interaction_list.interaction_diffs, :warn
|
310
315
|
[500, {}, ["Actual interactions do not match expected interactions for mock #{@name}. See #{@log_description} for details."]]
|
311
316
|
end
|
312
317
|
end
|
@@ -329,15 +334,17 @@ module Pact
|
|
329
334
|
"standard out/err"
|
330
335
|
end
|
331
336
|
|
337
|
+
interaction_list = InteractionList.new
|
338
|
+
|
332
339
|
@name = options.fetch(:name, "MockService")
|
333
340
|
@handlers = [
|
334
341
|
StartupPoll.new(@name, @logger),
|
335
342
|
CapybaraIdentify.new(@name, @logger),
|
336
|
-
MissingInteractionsGet.new(@name, @logger),
|
337
|
-
VerificationGet.new(@name, @logger, log_description),
|
338
|
-
InteractionPost.new(@name, @logger),
|
339
|
-
InteractionDelete.new(@name, @logger),
|
340
|
-
InteractionReplay.new(@name, @logger)
|
343
|
+
MissingInteractionsGet.new(@name, @logger, interaction_list),
|
344
|
+
VerificationGet.new(@name, @logger, log_description, interaction_list),
|
345
|
+
InteractionPost.new(@name, @logger, interaction_list),
|
346
|
+
InteractionDelete.new(@name, @logger, interaction_list),
|
347
|
+
InteractionReplay.new(@name, @logger, interaction_list)
|
341
348
|
]
|
342
349
|
end
|
343
350
|
|
data/lib/pact/consumer/rspec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require_relative '../configuration'
|
2
2
|
require_relative '../consumer'
|
3
3
|
require_relative 'dsl'
|
4
|
-
require_relative 'configuration_dsl'
|
5
4
|
require 'pact/consumer/consumer_contract_builder'
|
6
5
|
|
7
6
|
module Pact
|
@@ -31,8 +30,8 @@ RSpec.configure do |config|
|
|
31
30
|
config.after :each, :pact => true do | example |
|
32
31
|
example_description = "#{example.example.example_group.description} #{example.example.description}"
|
33
32
|
Pact.configuration.logger.info "Verifying interactions for #{example_description}"
|
34
|
-
Pact.configuration.
|
35
|
-
|
33
|
+
Pact.configuration.provider_verifications.each do | provider_verification |
|
34
|
+
provider_verification.call example_description
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
# Copied shamelessly from Capybara
|
6
|
+
module Pact
|
7
|
+
class Server
|
8
|
+
class Middleware
|
9
|
+
attr_accessor :error
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
if env["PATH_INFO"] == "/__identify__"
|
17
|
+
[200, {}, [@app.object_id.to_s]]
|
18
|
+
else
|
19
|
+
begin
|
20
|
+
@app.call(env)
|
21
|
+
rescue StandardError => e
|
22
|
+
@error = e unless @error
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def ports
|
31
|
+
@ports ||= {}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :app, :port
|
36
|
+
|
37
|
+
def initialize(app, port)
|
38
|
+
@app = app
|
39
|
+
@middleware = Middleware.new(@app)
|
40
|
+
@server_thread = nil # supress warnings
|
41
|
+
@port = port
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset_error!
|
45
|
+
@middleware.error = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def error
|
49
|
+
@middleware.error
|
50
|
+
end
|
51
|
+
|
52
|
+
def host
|
53
|
+
"localhost"
|
54
|
+
end
|
55
|
+
|
56
|
+
def responsive?
|
57
|
+
return false if @server_thread && @server_thread.join(0)
|
58
|
+
|
59
|
+
res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
|
60
|
+
|
61
|
+
if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
|
62
|
+
return res.body == @app.object_id.to_s
|
63
|
+
end
|
64
|
+
rescue SystemCallError
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
|
68
|
+
def run_default_server(app, port)
|
69
|
+
require 'rack/handler/webrick'
|
70
|
+
Rack::Handler::WEBrick.run(app, :Port => port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0))
|
71
|
+
end
|
72
|
+
|
73
|
+
def boot
|
74
|
+
unless responsive?
|
75
|
+
Pact::Server.ports[@app.object_id] = @port
|
76
|
+
|
77
|
+
@server_thread = Thread.new do
|
78
|
+
run_default_server(@middleware, @port)
|
79
|
+
end
|
80
|
+
|
81
|
+
Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
|
82
|
+
end
|
83
|
+
rescue Timeout::Error
|
84
|
+
raise "Rack application timed out during boot"
|
85
|
+
else
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
data/lib/pact/consumer.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'consumer_contract'
|
2
2
|
require_relative 'consumer/consumer_contract_builder'
|
3
|
+
require_relative 'consumer/consumer_contract_builders'
|
4
|
+
require_relative 'consumer/dsl'
|
5
|
+
require_relative 'consumer/interaction_builder'
|
3
6
|
require_relative 'consumer/mock_service'
|
7
|
+
require_relative 'consumer/mock_service_client'
|
4
8
|
require_relative 'consumer/app_manager'
|
5
9
|
require_relative 'term'
|
6
|
-
require_relative 'request'
|
7
|
-
require_relative 'consumer_contract'
|
10
|
+
require_relative 'request'
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'pact/consumer_contract'
|
2
|
+
require 'pact/logging'
|
3
|
+
require 'pact/json_warning'
|
4
|
+
require 'date'
|
5
|
+
require 'pact/version'
|
6
|
+
require_relative 'service_consumer'
|
7
|
+
require_relative 'service_provider'
|
8
|
+
require_relative 'interaction'
|
9
|
+
|
10
|
+
module Pact
|
11
|
+
class ConsumerContract
|
12
|
+
|
13
|
+
include Pact::Logging
|
14
|
+
include Pact::JsonWarning
|
15
|
+
|
16
|
+
attr_accessor :interactions
|
17
|
+
attr_accessor :consumer
|
18
|
+
attr_accessor :provider
|
19
|
+
|
20
|
+
def initialize(attributes = {})
|
21
|
+
@interactions = attributes[:interactions] || []
|
22
|
+
@consumer = attributes[:consumer]
|
23
|
+
@provider = attributes[:provider]
|
24
|
+
end
|
25
|
+
|
26
|
+
def as_json(options = {})
|
27
|
+
{
|
28
|
+
provider: @provider.as_json,
|
29
|
+
consumer: @consumer.as_json,
|
30
|
+
interactions: @interactions.collect(&:as_json),
|
31
|
+
metadata: {
|
32
|
+
pact_gem: {
|
33
|
+
version: Pact::VERSION
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_json(options = {})
|
40
|
+
as_json(options).to_json(options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.from_hash(obj)
|
44
|
+
new({
|
45
|
+
:interactions => obj['interactions'].collect { |hash| Interaction.from_hash(hash)},
|
46
|
+
:consumer => ServiceConsumer.from_hash(obj['consumer']),
|
47
|
+
:provider => ServiceProvider.from_hash(obj['provider'] || {})
|
48
|
+
})
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.from_json string
|
52
|
+
deserialised_object = JSON.load(maintain_backwards_compatiblity_with_producer_keys(string))
|
53
|
+
from_hash(deserialised_object)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.maintain_backwards_compatiblity_with_producer_keys string
|
57
|
+
string.gsub('"producer":', '"provider":').gsub('"producer_state":', '"provider_state":')
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_interaction criteria
|
61
|
+
interactions = find_interactions criteria
|
62
|
+
if interactions.size == 0
|
63
|
+
raise "Could not find interaction matching #{criteria} in pact file between #{consumer.name} and #{provider.name}."
|
64
|
+
elsif interactions.size > 1
|
65
|
+
raise "Found more than 1 interaction matching #{criteria} in pact file between #{consumer.name} and #{provider.name}."
|
66
|
+
end
|
67
|
+
interactions.first
|
68
|
+
end
|
69
|
+
|
70
|
+
def find_interactions criteria
|
71
|
+
interactions.select{ | interaction| interaction.matches_criteria?(criteria)}
|
72
|
+
end
|
73
|
+
|
74
|
+
def each
|
75
|
+
interactions.each do | interaction |
|
76
|
+
yield interaction
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def pact_file_name
|
81
|
+
"#{filenamify(consumer.name)}-#{filenamify(provider.name)}.json"
|
82
|
+
end
|
83
|
+
|
84
|
+
def pactfile_path
|
85
|
+
raise 'You must first specify a consumer and service name' unless (consumer && consumer.name && provider && provider.name)
|
86
|
+
@pactfile_path ||= File.join(Pact.configuration.pact_dir, pact_file_name)
|
87
|
+
end
|
88
|
+
|
89
|
+
def update_pactfile
|
90
|
+
logger.debug "Updating pact file for #{provider.name} at #{pactfile_path}"
|
91
|
+
check_for_active_support_json
|
92
|
+
File.open(pactfile_path, 'w') do |f|
|
93
|
+
f.write JSON.pretty_generate(self)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def filenamify name
|
100
|
+
name.downcase.gsub(/\s/, '_')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'pact/request'
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
class Interaction
|
5
|
+
|
6
|
+
attr_accessor :description, :request, :response, :provider_state
|
7
|
+
|
8
|
+
def initialize attributes = {}
|
9
|
+
@description = attributes[:description]
|
10
|
+
@request = attributes[:request]
|
11
|
+
@response = attributes[:response]
|
12
|
+
@provider_state = attributes[:provider_state]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.from_hash hash
|
16
|
+
new(:description => hash['description'],
|
17
|
+
:provider_state => hash['provider_state'],
|
18
|
+
:request => Pact::Request::Expected.from_hash(hash['request']),
|
19
|
+
:response => hash['response']
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_json
|
24
|
+
{
|
25
|
+
:description => @description,
|
26
|
+
:request => @request.as_json,
|
27
|
+
:response => @response,
|
28
|
+
}.tap{ | hash | hash[:provider_state] = @provider_state if @provider_state }
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_json(options = {})
|
32
|
+
as_json.to_json(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def as_json_for_mock_service
|
36
|
+
{:response => Reification.from_term(response), :request => @request.as_json_with_options, :description => description }.
|
37
|
+
tap{ | hash | hash[:provider_state] = @provider_state if @provider_state }
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_json_for_mock_service
|
41
|
+
as_json_for_mock_service.to_json
|
42
|
+
end
|
43
|
+
|
44
|
+
# Move this to interaction
|
45
|
+
def matches_criteria? criteria
|
46
|
+
criteria.each do | key, value |
|
47
|
+
unless match_criterion self.send(key.to_s), value
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def match_criterion target, criterion
|
55
|
+
target == criterion || (criterion.is_a?(Regexp) && criterion.match(target))
|
56
|
+
end
|
57
|
+
|
58
|
+
def == other
|
59
|
+
other.is_a?(Interaction) && as_json == other.as_json
|
60
|
+
end
|
61
|
+
|
62
|
+
def eq? other
|
63
|
+
self == other
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
to_json
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pact
|
2
|
+
class ServiceConsumer
|
3
|
+
attr_accessor :name
|
4
|
+
def initialize options
|
5
|
+
@name = options[:name]
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
name
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_json options = {}
|
13
|
+
{name: name}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_hash obj
|
17
|
+
ServiceConsumer.new(:name => obj['name'])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pact
|
2
|
+
class ServiceProvider
|
3
|
+
attr_accessor :name
|
4
|
+
def initialize options
|
5
|
+
@name = options[:name] || '[provider name unknown - please update the pact gem in the consumer project to the latest version and regenerate the pacts]'
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
name
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_json options = {}
|
13
|
+
{name: name}
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_hash obj
|
17
|
+
ServiceProvider.new(:name => obj['name'])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -1,112 +1 @@
|
|
1
|
-
require 'pact/
|
2
|
-
require 'pact/consumer/service_producer'
|
3
|
-
require 'pact/consumer/interaction'
|
4
|
-
require 'pact/logging'
|
5
|
-
require 'pact/json_warning'
|
6
|
-
require 'date'
|
7
|
-
require 'pact/version'
|
8
|
-
|
9
|
-
module Pact
|
10
|
-
class ConsumerContract
|
11
|
-
|
12
|
-
include Pact::Logging
|
13
|
-
include Pact::JsonWarning
|
14
|
-
|
15
|
-
attr_accessor :interactions
|
16
|
-
attr_accessor :consumer
|
17
|
-
attr_accessor :producer
|
18
|
-
|
19
|
-
def initialize(attributes = {})
|
20
|
-
@interactions = attributes[:interactions] || []
|
21
|
-
@consumer = attributes[:consumer]
|
22
|
-
@producer = attributes[:producer]
|
23
|
-
end
|
24
|
-
|
25
|
-
def as_json(options = {})
|
26
|
-
{
|
27
|
-
producer: @producer.as_json,
|
28
|
-
consumer: @consumer.as_json,
|
29
|
-
interactions: @interactions.collect(&:as_json),
|
30
|
-
metadata: {
|
31
|
-
pact_gem: {
|
32
|
-
version: Pact::VERSION
|
33
|
-
}
|
34
|
-
}
|
35
|
-
}
|
36
|
-
end
|
37
|
-
|
38
|
-
def to_json(options = {})
|
39
|
-
as_json(options).to_json(options)
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.from_hash(obj)
|
43
|
-
new({
|
44
|
-
:interactions => obj['interactions'].collect { |hash| Pact::Consumer::Interaction.from_hash(hash)},
|
45
|
-
:consumer => Pact::Consumer::ServiceConsumer.from_hash(obj['consumer']),
|
46
|
-
:producer => Pact::Consumer::ServiceProducer.from_hash(obj['producer'] || {})
|
47
|
-
})
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.from_json string
|
51
|
-
deserialised_object = JSON.load(string)
|
52
|
-
from_hash(deserialised_object)
|
53
|
-
end
|
54
|
-
|
55
|
-
def find_interaction criteria
|
56
|
-
interactions = find_interactions criteria
|
57
|
-
if interactions.size == 0
|
58
|
-
raise "Could not find interaction matching #{criteria} in pact file between #{consumer.name} and #{producer.name}."
|
59
|
-
elsif interactions.size > 1
|
60
|
-
raise "Found more than 1 interaction matching #{criteria} in pact file between #{consumer.name} and #{producer.name}."
|
61
|
-
end
|
62
|
-
interactions.first
|
63
|
-
end
|
64
|
-
|
65
|
-
def find_interactions criteria
|
66
|
-
interactions.select{ | interaction| match_criteria? interaction, criteria}
|
67
|
-
end
|
68
|
-
|
69
|
-
def each
|
70
|
-
interactions.each do | interaction |
|
71
|
-
yield interaction
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# Move this to interaction
|
76
|
-
def match_criteria? interaction, criteria
|
77
|
-
criteria.each do | key, value |
|
78
|
-
unless match_criterion interaction.send(key.to_s), value
|
79
|
-
return false
|
80
|
-
end
|
81
|
-
end
|
82
|
-
true
|
83
|
-
end
|
84
|
-
|
85
|
-
def match_criterion target, criterion
|
86
|
-
target == criterion || (criterion.is_a?(Regexp) && criterion.match(target))
|
87
|
-
end
|
88
|
-
|
89
|
-
def pact_file_name
|
90
|
-
"#{filenamify(consumer.name)}-#{filenamify(producer.name)}.json"
|
91
|
-
end
|
92
|
-
|
93
|
-
def pactfile_path
|
94
|
-
raise 'You must first specify a consumer and service name' unless (consumer && consumer.name && producer && producer.name)
|
95
|
-
@pactfile_path ||= File.join(Pact.configuration.pact_dir, pact_file_name)
|
96
|
-
end
|
97
|
-
|
98
|
-
def update_pactfile
|
99
|
-
logger.debug "Updating pact file for #{producer.name} at #{pactfile_path}"
|
100
|
-
check_for_active_support_json
|
101
|
-
File.open(pactfile_path, 'w') do |f|
|
102
|
-
f.write JSON.pretty_generate(self)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
private
|
107
|
-
|
108
|
-
def filenamify name
|
109
|
-
name.downcase.gsub(/\s/, '_')
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
1
|
+
require 'pact/consumer_contract/consumer_contract'
|
@@ -1,12 +1,21 @@
|
|
1
1
|
module Pact
|
2
2
|
|
3
|
-
module
|
3
|
+
module Provider
|
4
4
|
module DSL
|
5
5
|
|
6
|
+
#TODO: Move this into a module, out of configuration
|
6
7
|
module Configuration
|
7
8
|
def provider= provider
|
8
|
-
@
|
9
|
+
@provider = provider
|
9
10
|
end
|
11
|
+
|
12
|
+
def provider
|
13
|
+
if defined? @provider
|
14
|
+
@provider
|
15
|
+
else
|
16
|
+
raise "Please configure your provider. See the Provider section in the README for examples."
|
17
|
+
end
|
18
|
+
end
|
10
19
|
end
|
11
20
|
|
12
21
|
Pact::Configuration.send(:include, Configuration)
|
@@ -39,8 +48,8 @@ module Pact
|
|
39
48
|
end
|
40
49
|
|
41
50
|
def validate
|
42
|
-
raise "Please provide a name for the
|
43
|
-
raise "Please configure an app for the
|
51
|
+
raise "Please provide a name for the Provider" unless @name && !@name.strip.empty?
|
52
|
+
raise "Please configure an app for the Provider" unless @app_block
|
44
53
|
end
|
45
54
|
|
46
55
|
def name name
|
@@ -59,3 +68,5 @@ module Pact
|
|
59
68
|
end
|
60
69
|
end
|
61
70
|
end
|
71
|
+
|
72
|
+
Pact.send(:extend, Pact::Provider::DSL)
|
File without changes
|
@@ -5,10 +5,10 @@ require 'rspec/core/formatters/documentation_formatter'
|
|
5
5
|
require_relative 'rspec'
|
6
6
|
|
7
7
|
module Pact
|
8
|
-
module
|
8
|
+
module Provider
|
9
9
|
class PactSpecRunner
|
10
10
|
|
11
|
-
extend Pact::
|
11
|
+
extend Pact::Provider::RSpec::ClassMethods
|
12
12
|
|
13
13
|
def self.run(spec_definitions, options = {})
|
14
14
|
initialize_specs spec_definitions
|