pact 1.0.9 → 1.0.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/pact/consumer/rspec.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
-
require 'pact/consumer_contract'
|
2
1
|
require 'pact/logging'
|
3
2
|
require 'pact/json_warning'
|
4
|
-
require '
|
3
|
+
require 'pact/something_like'
|
4
|
+
require 'pact/symbolize_keys'
|
5
|
+
require 'pact/term'
|
5
6
|
require 'pact/version'
|
7
|
+
require 'date'
|
6
8
|
require 'open-uri'
|
7
|
-
require 'pact/term'
|
8
|
-
require 'pact/something_like'
|
9
9
|
require_relative 'service_consumer'
|
10
10
|
require_relative 'service_provider'
|
11
11
|
require_relative 'interaction'
|
12
|
+
require_relative 'request'
|
12
13
|
|
13
14
|
|
14
15
|
|
@@ -27,6 +28,7 @@ module Pact
|
|
27
28
|
|
28
29
|
class ConsumerContract
|
29
30
|
|
31
|
+
include SymbolizeKeys
|
30
32
|
include Logging
|
31
33
|
include JsonWarning
|
32
34
|
include FileName
|
@@ -58,11 +60,12 @@ module Pact
|
|
58
60
|
as_json.to_json(options)
|
59
61
|
end
|
60
62
|
|
61
|
-
def self.from_hash(
|
63
|
+
def self.from_hash(hash)
|
64
|
+
hash = symbolize_keys(hash)
|
62
65
|
new({
|
63
|
-
:interactions =>
|
64
|
-
:consumer => ServiceConsumer.from_hash(
|
65
|
-
:provider => ServiceProvider.from_hash(
|
66
|
+
:interactions => hash[:interactions].collect { |hash| Interaction.from_hash(hash)},
|
67
|
+
:consumer => ServiceConsumer.from_hash(hash[:consumer]),
|
68
|
+
:provider => ServiceProvider.from_hash(hash[:provider])
|
66
69
|
})
|
67
70
|
end
|
68
71
|
|
@@ -1,7 +1,9 @@
|
|
1
|
-
require 'pact/request'
|
1
|
+
require 'pact/consumer_contract/request'
|
2
|
+
require 'pact/symbolize_keys'
|
2
3
|
|
3
4
|
module Pact
|
4
5
|
class Interaction
|
6
|
+
include SymbolizeKeys
|
5
7
|
|
6
8
|
attr_accessor :description, :request, :response, :provider_state
|
7
9
|
|
@@ -13,35 +15,20 @@ module Pact
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.from_hash hash
|
16
|
-
|
17
|
-
|
18
|
-
:request => Pact::Request::Expected.from_hash(hash['request']),
|
19
|
-
:response => hash['response']
|
20
|
-
)
|
18
|
+
request = Pact::Request::Expected.from_hash(hash['request'])
|
19
|
+
new(symbolize_keys(hash).merge({request: request}))
|
21
20
|
end
|
22
21
|
|
23
22
|
def as_json
|
24
|
-
{
|
25
|
-
|
26
|
-
|
27
|
-
:response => @response,
|
28
|
-
}.tap{ | hash | hash[:provider_state] = @provider_state if @provider_state }
|
23
|
+
hash = { :description => @description }
|
24
|
+
hash[:provider_state] = @provider_state if @provider_state #Easier to read when provider state at top
|
25
|
+
hash.merge(:request => @request.as_json, :response => @response)
|
29
26
|
end
|
30
27
|
|
31
28
|
def to_json(options = {})
|
32
29
|
as_json.to_json(options)
|
33
30
|
end
|
34
31
|
|
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
32
|
def matches_criteria? criteria
|
46
33
|
criteria.each do | key, value |
|
47
34
|
unless match_criterion self.send(key.to_s), value
|
@@ -64,7 +51,7 @@ module Pact
|
|
64
51
|
end
|
65
52
|
|
66
53
|
def to_s
|
67
|
-
|
54
|
+
JSON.pretty_generate(self)
|
68
55
|
end
|
69
56
|
end
|
70
57
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'pact/shared/request'
|
2
|
+
require 'pact/shared/null_expectation'
|
3
|
+
|
4
|
+
module Pact
|
5
|
+
|
6
|
+
module Request
|
7
|
+
|
8
|
+
class Expected < Pact::Request::Base
|
9
|
+
|
10
|
+
DEFAULT_OPTIONS = {:allow_unexpected_keys => false}.freeze
|
11
|
+
attr_accessor :options #Temporary hack
|
12
|
+
|
13
|
+
def self.from_hash(hash)
|
14
|
+
sym_hash = symbolize_keys hash
|
15
|
+
method = sym_hash.fetch(:method)
|
16
|
+
path = sym_hash.fetch(:path)
|
17
|
+
query = sym_hash.fetch(:query, key_not_found)
|
18
|
+
headers = sym_hash.fetch(:headers, key_not_found)
|
19
|
+
body = sym_hash.fetch(:body, key_not_found)
|
20
|
+
options = sym_hash.fetch(:options, {})
|
21
|
+
new(method, path, headers, body, query, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(method, path, headers, body, query, options = {})
|
25
|
+
super(method, path, headers, body, query)
|
26
|
+
@options = options
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches?(actual_request)
|
30
|
+
difference(actual_request).empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def matches_route? actual_request
|
34
|
+
diff({:method => method, :path => path}, {:method => actual_request.method, :path => actual_request.path}).empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def difference(actual_request)
|
38
|
+
request_diff = diff(as_json_without_body, actual_request.as_json_without_body)
|
39
|
+
unless body.is_a? NullExpectation
|
40
|
+
request_diff.merge(body_difference(actual_request.body))
|
41
|
+
else
|
42
|
+
request_diff
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def self.key_not_found
|
49
|
+
Pact::NullExpectation.new
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Options is a dirty hack to allow Condor to send extra keys in the request,
|
55
|
+
# as it's too much work to set up an exactly matching expectation.
|
56
|
+
# Need to implement a proper matching strategy and remove this.
|
57
|
+
# Do not rely on it!
|
58
|
+
def runtime_options
|
59
|
+
DEFAULT_OPTIONS.merge(symbolize_keys(options))
|
60
|
+
end
|
61
|
+
|
62
|
+
def body_difference(actual_body)
|
63
|
+
diff({:body => body}, {body: actual_body}, allow_unexpected_keys: runtime_options[:allow_unexpected_keys_in_body])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
|
+
require 'pact/symbolize_keys'
|
2
|
+
|
1
3
|
module Pact
|
2
4
|
class ServiceConsumer
|
5
|
+
include SymbolizeKeys
|
6
|
+
|
3
7
|
attr_accessor :name
|
4
8
|
def initialize options
|
5
9
|
@name = options[:name]
|
@@ -13,8 +17,8 @@ module Pact
|
|
13
17
|
{name: name}
|
14
18
|
end
|
15
19
|
|
16
|
-
def self.from_hash
|
17
|
-
|
20
|
+
def self.from_hash hash
|
21
|
+
new(symbolize_keys(hash))
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -1,5 +1,9 @@
|
|
1
|
+
require 'pact/symbolize_keys'
|
2
|
+
|
1
3
|
module Pact
|
2
4
|
class ServiceProvider
|
5
|
+
include SymbolizeKeys
|
6
|
+
|
3
7
|
attr_accessor :name
|
4
8
|
def initialize options
|
5
9
|
@name = options[:name] || '[provider name unknown - please update the pact gem in the consumer project to the latest version and regenerate the pacts]'
|
@@ -13,8 +17,8 @@ module Pact
|
|
13
17
|
{name: name}
|
14
18
|
end
|
15
19
|
|
16
|
-
def self.from_hash
|
17
|
-
|
20
|
+
def self.from_hash hash
|
21
|
+
new(symbolize_keys(hash))
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
@@ -1,32 +1,19 @@
|
|
1
1
|
require 'awesome_print'
|
2
2
|
require 'pact/term'
|
3
3
|
require 'pact/something_like'
|
4
|
+
require 'pact/shared/null_expectation'
|
5
|
+
require 'pact/shared/key_not_found'
|
6
|
+
require 'pact/matchers/unexpected_key'
|
7
|
+
require 'pact/matchers/unexpected_index'
|
8
|
+
require 'pact/matchers/index_not_found'
|
4
9
|
|
5
10
|
module Pact
|
6
11
|
module Matchers
|
7
12
|
|
8
13
|
NO_DIFF_INDICATOR = 'no difference here!'
|
9
|
-
|
14
|
+
#UnexpectedKey.new = '<key not to be present>'
|
10
15
|
DEFAULT_OPTIONS = {allow_unexpected_keys: true, structure: false}.freeze
|
11
16
|
|
12
|
-
class KeyNotFound
|
13
|
-
def == other
|
14
|
-
other.is_a? KeyNotFound
|
15
|
-
end
|
16
|
-
|
17
|
-
def eql? other
|
18
|
-
self == other
|
19
|
-
end
|
20
|
-
|
21
|
-
def to_s
|
22
|
-
"<key not found>"
|
23
|
-
end
|
24
|
-
|
25
|
-
def to_json options = {}
|
26
|
-
to_s
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
17
|
def diff expected, actual, opts = {}
|
31
18
|
options = DEFAULT_OPTIONS.merge(opts)
|
32
19
|
case expected
|
@@ -53,11 +40,7 @@ module Pact
|
|
53
40
|
|
54
41
|
def array_diff expected, actual, options
|
55
42
|
if actual.is_a? Array
|
56
|
-
|
57
|
-
actual_array_diff expected, actual, options
|
58
|
-
else
|
59
|
-
{expected: expected, actual: actual}
|
60
|
-
end
|
43
|
+
actual_array_diff expected, actual, options
|
61
44
|
else
|
62
45
|
{expected: expected, actual: actual}
|
63
46
|
end
|
@@ -66,8 +49,11 @@ module Pact
|
|
66
49
|
def actual_array_diff expected, actual, options
|
67
50
|
difference = []
|
68
51
|
diff_found = false
|
69
|
-
expected.
|
70
|
-
|
52
|
+
length = [expected.length, actual.length].max
|
53
|
+
length.times do | index|
|
54
|
+
expected_item = expected.fetch(index, Pact::UnexpectedIndex.new)
|
55
|
+
actual_item = actual.fetch(index, Pact::IndexNotFound.new)
|
56
|
+
if (item_diff = diff(expected_item, actual_item, options)).any?
|
71
57
|
diff_found = true
|
72
58
|
difference << item_diff
|
73
59
|
else
|
@@ -79,7 +65,7 @@ module Pact
|
|
79
65
|
|
80
66
|
def actual_hash_diff expected, actual, options
|
81
67
|
difference = expected.keys.inject({}) do |diff, key|
|
82
|
-
if (diff_at_key = diff(expected[key], actual.fetch(key, KeyNotFound.new), options)).any?
|
68
|
+
if (diff_at_key = diff(expected[key], actual.fetch(key, Pact::KeyNotFound.new), options)).any?
|
83
69
|
diff[key] = diff_at_key
|
84
70
|
end
|
85
71
|
diff
|
@@ -92,7 +78,7 @@ module Pact
|
|
92
78
|
{}
|
93
79
|
else
|
94
80
|
(actual.keys - expected.keys).inject({}) do | diff, key |
|
95
|
-
diff[key] = {:expected =>
|
81
|
+
diff[key] = {:expected => UnexpectedKey.new, :actual => actual[key]}
|
96
82
|
diff
|
97
83
|
end
|
98
84
|
end
|
data/lib/pact/provider.rb
CHANGED
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'pact/provider/pact_verification'
|
2
|
+
require 'pact/shared/dsl'
|
3
|
+
module Pact
|
4
|
+
|
5
|
+
module Provider
|
6
|
+
|
7
|
+
module DSL
|
8
|
+
def service_provider name, &block
|
9
|
+
Configuration::ServiceProviderDSL.build(name, &block)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Pact.send(:extend, Pact::Provider::DSL)
|
14
|
+
|
15
|
+
module Configuration
|
16
|
+
|
17
|
+
module ConfigurationExtension
|
18
|
+
def provider= provider
|
19
|
+
@provider = provider
|
20
|
+
end
|
21
|
+
|
22
|
+
def provider
|
23
|
+
if defined? @provider
|
24
|
+
@provider
|
25
|
+
else
|
26
|
+
raise "Please configure your provider. See the Provider section in the README for examples."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_pact_verification verification
|
31
|
+
pact_verifications << verification
|
32
|
+
end
|
33
|
+
|
34
|
+
def pact_verifications
|
35
|
+
@pact_verifications ||= []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Pact::Configuration.send(:include, ConfigurationExtension)
|
40
|
+
|
41
|
+
class ServiceProviderConfig
|
42
|
+
|
43
|
+
def initialize &app_block
|
44
|
+
@app_block = app_block
|
45
|
+
end
|
46
|
+
|
47
|
+
def app
|
48
|
+
@app_block.call
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class PactVerification
|
53
|
+
extend Pact::DSL
|
54
|
+
|
55
|
+
attr_accessor :consumer_name, :pact_uri, :ref
|
56
|
+
|
57
|
+
def initialize consumer_name, options = {}
|
58
|
+
@consumer_name = consumer_name
|
59
|
+
@ref = options.fetch(:ref, :head)
|
60
|
+
@pact_uri = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
dsl do
|
64
|
+
def pact_uri pact_uri
|
65
|
+
self.pact_uri = pact_uri
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def finalize
|
70
|
+
validate
|
71
|
+
create_pact_verification
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def create_pact_verification
|
77
|
+
verification = Pact::Provider::PactVerification.new(consumer_name, pact_uri, ref)
|
78
|
+
Pact.configuration.add_pact_verification verification
|
79
|
+
end
|
80
|
+
|
81
|
+
def validate
|
82
|
+
raise "Please provide a pact_uri for the verification" unless pact_uri
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
class ServiceProviderDSL
|
88
|
+
extend Pact::DSL
|
89
|
+
|
90
|
+
attr_accessor :name, :app_block
|
91
|
+
|
92
|
+
def initialize name
|
93
|
+
@name = name
|
94
|
+
@app_block = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
dsl do
|
98
|
+
def app &block
|
99
|
+
self.app_block = block
|
100
|
+
end
|
101
|
+
|
102
|
+
def honours_pact_with consumer_name, options = {}, &block
|
103
|
+
create_pact_verification consumer_name, options, &block
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def create_pact_verification consumer_name, options, &block
|
108
|
+
PactVerification.build(consumer_name, options, &block)
|
109
|
+
end
|
110
|
+
|
111
|
+
def finalize
|
112
|
+
validate
|
113
|
+
create_service_provider
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def validate
|
119
|
+
raise "Please provide a name for the Provider" unless name && !name.strip.empty?
|
120
|
+
raise "Please configure an app for the Provider" unless app_block
|
121
|
+
end
|
122
|
+
|
123
|
+
def create_service_provider
|
124
|
+
Pact.configuration.provider = ServiceProviderConfig.new(&@app_block)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|