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
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
|