pact 1.0.9 → 1.0.10
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/CHANGELOG.md +20 -0
- data/Gemfile.lock +3 -3
- data/example/animal-service/Gemfile +14 -0
- data/example/animal-service/Gemfile.lock +67 -0
- data/example/animal-service/Rakefile +3 -0
- data/example/animal-service/spec/service_consumers/pact_helper.rb +24 -0
- data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +9 -0
- data/example/zoo-app/Gemfile.lock +2 -15
- data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +21 -6
- data/example/zoo-app/spec/service_providers/animal_service_spec.rb +1 -1
- data/lib/pact/consumer.rb +10 -11
- data/lib/pact/consumer/app_manager.rb +1 -0
- data/lib/pact/consumer/configuration.rb +179 -0
- data/lib/pact/consumer/interaction_builder.rb +1 -2
- data/lib/pact/consumer/mock_service.rb +1 -370
- data/lib/pact/consumer/mock_service/app.rb +70 -0
- data/lib/pact/consumer/mock_service/interaction_delete.rb +28 -0
- data/lib/pact/consumer/mock_service/interaction_list.rb +57 -0
- data/lib/pact/consumer/mock_service/interaction_post.rb +25 -0
- data/lib/pact/consumer/mock_service/interaction_replay.rb +126 -0
- data/lib/pact/consumer/mock_service/missing_interactions_get.rb +26 -0
- data/lib/pact/consumer/mock_service/rack_request_helper.rb +51 -0
- data/lib/pact/consumer/mock_service/startup_poll.rb +22 -0
- data/lib/pact/consumer/mock_service/verification_get.rb +35 -0
- data/lib/pact/consumer/mock_service_client.rb +3 -1
- data/lib/pact/consumer/mock_service_interaction_expectation.rb +33 -0
- data/lib/pact/consumer/request.rb +27 -0
- data/lib/pact/consumer/rspec.rb +1 -4
- data/lib/pact/consumer_contract/consumer_contract.rb +11 -8
- data/lib/pact/consumer_contract/interaction.rb +9 -22
- data/lib/pact/consumer_contract/request.rb +68 -0
- data/lib/pact/consumer_contract/service_consumer.rb +6 -2
- data/lib/pact/consumer_contract/service_provider.rb +6 -2
- data/lib/pact/matchers/index_not_found.rb +20 -0
- data/lib/pact/matchers/matchers.rb +14 -28
- data/lib/pact/matchers/unexpected_index.rb +17 -0
- data/lib/pact/matchers/unexpected_key.rb +17 -0
- data/lib/pact/provider.rb +1 -1
- data/lib/pact/provider/configuration.rb +129 -0
- data/lib/pact/provider/request.rb +59 -0
- data/lib/pact/provider/rspec.rb +4 -5
- data/lib/pact/provider/test_methods.rb +14 -52
- data/lib/pact/shared/dsl.rb +20 -0
- data/lib/pact/shared/key_not_found.rb +24 -0
- data/lib/pact/shared/null_expectation.rb +31 -0
- data/lib/pact/shared/request.rb +68 -0
- data/lib/pact/something_like.rb +7 -1
- data/lib/pact/symbolize_keys.rb +12 -0
- data/lib/pact/tasks.rb +1 -1
- data/lib/pact/tasks/task_helper.rb +23 -0
- data/lib/pact/{verification_task.rb → tasks/verification_task.rb} +4 -4
- data/lib/pact/version.rb +1 -1
- data/lib/tasks/pact.rake +5 -4
- data/pact.gemspec +1 -1
- data/spec/features/consumption_spec.rb +1 -73
- data/spec/features/production_spec.rb +3 -0
- data/spec/integration/consumer_spec.rb +170 -0
- data/spec/integration/pact/consumer_configuration_spec.rb +1 -1
- data/spec/lib/pact/consumer/{dsl_spec.rb → configuration_spec.rb} +10 -9
- data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +2 -2
- data/spec/lib/pact/consumer/interaction_builder_spec.rb +1 -1
- data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +66 -0
- data/spec/lib/pact/consumer/mock_service/rack_request_helper_spec.rb +82 -0
- data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +54 -0
- data/spec/lib/pact/consumer/request_spec.rb +24 -0
- data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +1 -1
- data/spec/lib/pact/consumer_contract/interaction_spec.rb +0 -34
- data/spec/lib/pact/{request_spec.rb → consumer_contract/request_spec.rb} +45 -121
- data/spec/lib/pact/matchers/matchers_spec.rb +29 -13
- data/spec/lib/pact/provider/configuration_spec.rb +163 -0
- data/spec/lib/pact/provider/request_spec.rb +78 -0
- data/spec/lib/pact/provider/test_methods_spec.rb +0 -30
- data/spec/lib/pact/verification_task_spec.rb +1 -1
- data/spec/spec_helper.rb +3 -0
- data/spec/support/factories.rb +5 -1
- data/spec/support/pact_helper.rb +6 -0
- data/spec/support/shared_examples_for_request.rb +83 -0
- data/spec/support/test_app_fail.json +3 -0
- data/spec/support/test_app_pass.json +18 -1
- metadata +68 -31
- data/lib/pact/consumer/dsl.rb +0 -157
- data/lib/pact/pact_task_helper.rb +0 -21
- data/lib/pact/provider/dsl.rb +0 -115
- data/lib/pact/request.rb +0 -167
- data/spec/lib/pact/consumer/mock_service_spec.rb +0 -143
- data/spec/lib/pact/provider/dsl_spec.rb +0 -179
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'pact/reification'
|
3
|
+
require 'pact/shared/null_expectation'
|
4
|
+
|
5
|
+
module Pact
|
6
|
+
module Provider
|
7
|
+
module Request
|
8
|
+
class Replayable
|
9
|
+
|
10
|
+
def initialize expected_request
|
11
|
+
@expected_request = expected_request
|
12
|
+
end
|
13
|
+
|
14
|
+
def method
|
15
|
+
expected_request.method
|
16
|
+
end
|
17
|
+
|
18
|
+
def path
|
19
|
+
expected_request.full_path
|
20
|
+
end
|
21
|
+
|
22
|
+
def body
|
23
|
+
case expected_request.body
|
24
|
+
when String then expected_request.body
|
25
|
+
when NullExpectation then ''
|
26
|
+
else
|
27
|
+
reified_body
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def headers
|
32
|
+
request_headers = {}
|
33
|
+
return request_headers if expected_request.headers.is_a?(Pact::NullExpectation)
|
34
|
+
expected_request.headers.each do |key, value|
|
35
|
+
key = key.upcase
|
36
|
+
if key.match(/CONTENT.TYPE/)
|
37
|
+
request_headers['CONTENT_TYPE'] = value
|
38
|
+
else
|
39
|
+
request_headers['HTTP_' + key.to_s] = value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
request_headers
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
attr_reader :expected_request
|
47
|
+
|
48
|
+
def reified_body
|
49
|
+
rb = Pact::Reification.from_term(expected_request.body)
|
50
|
+
if rb.is_a?(String)
|
51
|
+
rb
|
52
|
+
else
|
53
|
+
JSON.dump(rb)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/pact/provider/rspec.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'open-uri'
|
2
2
|
require 'pact/consumer_contract'
|
3
3
|
require 'pact/json_warning'
|
4
|
-
|
5
|
-
|
6
|
-
require 'pact/provider/
|
4
|
+
require 'pact/provider/matchers'
|
5
|
+
require 'pact/provider/test_methods'
|
6
|
+
require 'pact/provider/configuration'
|
7
7
|
|
8
8
|
module Pact
|
9
9
|
module Provider
|
@@ -90,7 +90,7 @@ module Pact
|
|
90
90
|
if response['body']
|
91
91
|
it "has a matching body" do
|
92
92
|
logger.debug "Response body is #{last_response.body}"
|
93
|
-
expect(
|
93
|
+
expect(parse_body_from_response(last_response)).to match_term response['body']
|
94
94
|
end
|
95
95
|
end
|
96
96
|
end
|
@@ -126,5 +126,4 @@ RSpec.configure do |config|
|
|
126
126
|
config.extend Pact::Provider::RSpec::ClassMethods
|
127
127
|
config.include Pact::Provider::RSpec::InstanceMethods
|
128
128
|
config.include Pact::Provider::TestMethods
|
129
|
-
config.include Pact::Provider::TestMethods
|
130
129
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'pact/logging'
|
2
2
|
require 'rack/test'
|
3
|
-
require 'pact/
|
3
|
+
require 'pact/consumer_contract/interaction'
|
4
4
|
require 'pact/provider/provider_state'
|
5
|
+
require 'pact/provider/request'
|
5
6
|
|
6
7
|
module Pact
|
7
8
|
module Provider
|
@@ -10,12 +11,20 @@ module Pact
|
|
10
11
|
include Pact::Logging
|
11
12
|
include Rack::Test::Methods
|
12
13
|
|
13
|
-
def
|
14
|
-
|
14
|
+
def replay_interaction interaction
|
15
|
+
request = Request::Replayable.new(interaction.request)
|
16
|
+
args = [request.path, request.body, request.headers]
|
17
|
+
|
18
|
+
logger.debug "Sending #{request.method} with #{args}"
|
19
|
+
self.send(request.method, *args)
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_body_from_response rack_response
|
23
|
+
case rack_response.headers['Content-Type']
|
15
24
|
when /json/
|
16
|
-
JSON.load(
|
25
|
+
JSON.load(rack_response.body)
|
17
26
|
else
|
18
|
-
|
27
|
+
rack_response.body
|
19
28
|
end
|
20
29
|
end
|
21
30
|
|
@@ -31,53 +40,6 @@ module Pact
|
|
31
40
|
end
|
32
41
|
end
|
33
42
|
|
34
|
-
def replay_interaction interaction
|
35
|
-
request = interaction.request
|
36
|
-
path = request_path(request)
|
37
|
-
args = [path, request_body(request)]
|
38
|
-
|
39
|
-
if !request.headers.nil?
|
40
|
-
args << request_headers(request)
|
41
|
-
end
|
42
|
-
|
43
|
-
logger.debug "Sending #{request.method} with #{args}"
|
44
|
-
self.send(request.method, *args)
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def request_headers request
|
50
|
-
request_headers = {}
|
51
|
-
request.headers.each do |key, value|
|
52
|
-
key = key.upcase
|
53
|
-
if key.match(/CONTENT.TYPE/)
|
54
|
-
request_headers['CONTENT_TYPE'] = value
|
55
|
-
else
|
56
|
-
request_headers['HTTP_' + key.to_s] = value
|
57
|
-
end
|
58
|
-
end
|
59
|
-
request_headers
|
60
|
-
end
|
61
|
-
|
62
|
-
def request_path request
|
63
|
-
path = request.path
|
64
|
-
query = request.query
|
65
|
-
if query && !query.empty?
|
66
|
-
query = query.generate if query.kind_of? Pact::Term
|
67
|
-
path += "?" + query
|
68
|
-
end
|
69
|
-
path
|
70
|
-
end
|
71
|
-
|
72
|
-
def request_body request
|
73
|
-
body = request.body
|
74
|
-
if !body.nil? #TODO - write test for this where this is a NullExpectation
|
75
|
-
body = JSON.dump(Pact::Reification.from_term(body))
|
76
|
-
else
|
77
|
-
body = ""
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
43
|
def get_provider_state provider_state_name, consumer
|
82
44
|
unless provider_state = ProviderState.get(provider_state_name, :for => consumer)
|
83
45
|
extra = consumer ? " for consumer \"#{consumer}\"" : ""
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Pact
|
4
|
+
# Ripped from http://blog.joecorcoran.co.uk/2013/09/04/simple-pattern-ruby-dsl
|
5
|
+
module DSL
|
6
|
+
def build(*args, &block)
|
7
|
+
base = self.new(*args)
|
8
|
+
delegator_klass = self.const_get('DSLDelegator')
|
9
|
+
delegator = delegator_klass.new(base)
|
10
|
+
delegator.instance_eval(&block)
|
11
|
+
base.finalize
|
12
|
+
base
|
13
|
+
end
|
14
|
+
|
15
|
+
def dsl(&block)
|
16
|
+
delegator_klass = Class.new(SimpleDelegator, &block)
|
17
|
+
self.const_set('DSLDelegator', delegator_klass)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Pact
|
2
|
+
class KeyNotFound
|
3
|
+
def == other
|
4
|
+
other.is_a? KeyNotFound
|
5
|
+
end
|
6
|
+
|
7
|
+
def eql? other
|
8
|
+
self == other
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"<key not found>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_json options = {}
|
16
|
+
to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
def empty?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Pact
|
2
|
+
class NullExpectation
|
3
|
+
def to_s
|
4
|
+
"<No expectation>"
|
5
|
+
end
|
6
|
+
|
7
|
+
def ==(other_object)
|
8
|
+
other_object.is_a? NullExpectation
|
9
|
+
end
|
10
|
+
|
11
|
+
def ===(other_object)
|
12
|
+
other_object.is_a? NullExpectation
|
13
|
+
end
|
14
|
+
|
15
|
+
def eql?(other_object)
|
16
|
+
self == other_object
|
17
|
+
end
|
18
|
+
|
19
|
+
def hash
|
20
|
+
2934820948209428748274238642672
|
21
|
+
end
|
22
|
+
|
23
|
+
def empty?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def nil?
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'pact/matchers'
|
2
|
+
require 'pact/symbolize_keys'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
|
6
|
+
module Request
|
7
|
+
|
8
|
+
class Base
|
9
|
+
include Pact::Matchers
|
10
|
+
include Pact::SymbolizeKeys
|
11
|
+
extend Pact::Matchers
|
12
|
+
|
13
|
+
attr_reader :method, :path, :headers, :body, :query, :options
|
14
|
+
|
15
|
+
def initialize(method, path, headers, body, query)
|
16
|
+
@method = method.to_s
|
17
|
+
@path = path.chomp('/')
|
18
|
+
@headers = headers
|
19
|
+
@body = body
|
20
|
+
@query = query
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json(options = {})
|
24
|
+
as_json.to_json(options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def as_json
|
28
|
+
base_json = {
|
29
|
+
method: method,
|
30
|
+
path: path,
|
31
|
+
}
|
32
|
+
|
33
|
+
base_json.merge!(body: body) unless body.is_a? self.class.key_not_found.class
|
34
|
+
base_json.merge!(headers: headers) unless headers.is_a? self.class.key_not_found.class
|
35
|
+
base_json.merge!(query: query) unless query.is_a? self.class.key_not_found.class
|
36
|
+
base_json
|
37
|
+
end
|
38
|
+
|
39
|
+
def method_and_path
|
40
|
+
"#{method.upcase} #{path}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def full_path
|
44
|
+
fp = ''
|
45
|
+
if path.empty?
|
46
|
+
fp << "/"
|
47
|
+
else
|
48
|
+
fp << path
|
49
|
+
end
|
50
|
+
if query && !query.empty?
|
51
|
+
fp << ("?" + (query.kind_of?(Pact::Term) ? query.generate : query))
|
52
|
+
end
|
53
|
+
fp
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def self.key_not_found
|
59
|
+
raise NotImplementedError
|
60
|
+
end
|
61
|
+
|
62
|
+
def as_json_without_body
|
63
|
+
keep_keys = [:method, :path, :headers, :query]
|
64
|
+
as_json.reject{ |key, value| !keep_keys.include? key }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/pact/something_like.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
+
require 'pact/symbolize_keys'
|
1
2
|
module Pact
|
2
3
|
|
4
|
+
# Specifies that the actual object should be considered a match if
|
5
|
+
# it includes the same keys, and the values of the keys are of the same class.
|
6
|
+
|
3
7
|
class SomethingLike
|
8
|
+
include SymbolizeKeys
|
9
|
+
|
4
10
|
attr_reader :contents
|
5
11
|
|
6
12
|
def initialize contents
|
@@ -19,7 +25,7 @@ module Pact
|
|
19
25
|
end
|
20
26
|
|
21
27
|
def self.json_create hash
|
22
|
-
new(hash
|
28
|
+
new(symbolize_keys(hash))
|
23
29
|
end
|
24
30
|
|
25
31
|
def generate
|
data/lib/pact/tasks.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
load File.expand_path('../tasks/pact.rake', File.dirname(__FILE__))
|
2
|
-
require 'pact/verification_task'
|
2
|
+
require 'pact/tasks/verification_task'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Pact
|
2
|
+
module TaskHelper
|
3
|
+
def failure_message
|
4
|
+
redify(
|
5
|
+
"\n* * * * * * * * * * * * * * * * * * *\n" +
|
6
|
+
"Provider did not honour pact file.\nSee\n * #{Pact.configuration.log_path}\n * #{Pact.configuration.tmp_dir}\nfor logs and pact files." +
|
7
|
+
"\n* * * * * * * * * * * * * * * * * * *\n\n"
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def redify string
|
12
|
+
"\e[31m#{string}\e[m"
|
13
|
+
end
|
14
|
+
|
15
|
+
def handle_verification_failure
|
16
|
+
exit_status = yield
|
17
|
+
if exit_status != 0
|
18
|
+
$stderr.puts failure_message
|
19
|
+
fail
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'rake/tasklib'
|
2
2
|
require 'pact/provider/pact_spec_runner'
|
3
|
-
|
4
|
-
|
3
|
+
require 'pact/provider/verification_report'
|
4
|
+
require 'pact/tasks/task_helper'
|
5
5
|
|
6
6
|
=begin
|
7
7
|
To create a rake pact:verify:<something> task
|
8
8
|
|
9
9
|
Pact::VerificationTask.new(:head) do | pact |
|
10
10
|
pact.uri 'http://master.cd.vpc.realestate.com.au/browse/BIQ-MAS/latestSuccessful/artifact/JOB2/Pacts/mas-contract_transaction_service.json'
|
11
|
-
|
11
|
+
pact.uri 'http://master.cd.vpc.realestate.com.au/browse/BIQ-IMAGINARY-CONSUMER/latestSuccessful/artifact/JOB2/Pacts/imaginary_consumer-contract_transaction_service.json'
|
12
12
|
end
|
13
13
|
|
14
14
|
The pact.uri may be a local file system path or a remote URL.
|
@@ -33,7 +33,7 @@ module Pact
|
|
33
33
|
class VerificationTask < ::Rake::TaskLib
|
34
34
|
attr_reader :pact_spec_config
|
35
35
|
|
36
|
-
include
|
36
|
+
include Pact::TaskHelper
|
37
37
|
def initialize(name)
|
38
38
|
@pact_spec_config = []
|
39
39
|
@name = name
|
data/lib/pact/version.rb
CHANGED
data/lib/tasks/pact.rake
CHANGED
@@ -4,14 +4,15 @@ namespace :pact do
|
|
4
4
|
desc "Verifies the pact files configured in the pact_helper.rb against this service provider."
|
5
5
|
task :verify do
|
6
6
|
require 'pact/provider'
|
7
|
-
require 'pact/
|
7
|
+
require 'pact/tasks/task_helper'
|
8
8
|
require 'pact/provider/client_project_pact_helper'
|
9
9
|
|
10
|
-
include
|
10
|
+
include Pact::TaskHelper
|
11
11
|
|
12
12
|
handle_verification_failure do
|
13
13
|
pact_verifications = Pact.configuration.pact_verifications
|
14
14
|
verification_configs = pact_verifications.collect { | pact_verification | { :uri => pact_verification.uri }}
|
15
|
+
raise "Please configure a pact to verify" if verification_configs.empty?
|
15
16
|
Pact::Provider::PactSpecRunner.new(verification_configs).run
|
16
17
|
end
|
17
18
|
end
|
@@ -19,9 +20,9 @@ namespace :pact do
|
|
19
20
|
desc "Verifies the pact at the given URI against this service provider."
|
20
21
|
task 'verify:at', :pact_uri do | t, args |
|
21
22
|
require 'pact/provider'
|
22
|
-
require 'pact/
|
23
|
+
require 'pact/tasks/task_helper'
|
23
24
|
|
24
|
-
include
|
25
|
+
include Pact::TaskHelper
|
25
26
|
|
26
27
|
handle_verification_failure do
|
27
28
|
puts "Verifying pact at uri #{args[:pact_uri]}"
|