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
@@ -1,21 +0,0 @@
|
|
1
|
-
module PactTaskHelper
|
2
|
-
def failure_message
|
3
|
-
redify(
|
4
|
-
"\n* * * * * * * * * * * * * * * * * * *\n" +
|
5
|
-
"Provider did not honour pact file.\nSee\n * #{Pact.configuration.log_path}\n * #{Pact.configuration.tmp_dir}\nfor logs and pact files." +
|
6
|
-
"\n* * * * * * * * * * * * * * * * * * *\n\n"
|
7
|
-
)
|
8
|
-
end
|
9
|
-
|
10
|
-
def redify string
|
11
|
-
"\e[31m#{string}\e[m"
|
12
|
-
end
|
13
|
-
|
14
|
-
def handle_verification_failure
|
15
|
-
exit_status = yield
|
16
|
-
if exit_status != 0
|
17
|
-
$stderr.puts failure_message
|
18
|
-
fail
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
data/lib/pact/provider/dsl.rb
DELETED
@@ -1,115 +0,0 @@
|
|
1
|
-
require_relative 'pact_verification'
|
2
|
-
|
3
|
-
module Pact
|
4
|
-
|
5
|
-
module Provider
|
6
|
-
module DSL
|
7
|
-
|
8
|
-
#TODO: Move this into a module, out of configuration
|
9
|
-
module Configuration
|
10
|
-
def provider= provider
|
11
|
-
@provider = provider
|
12
|
-
end
|
13
|
-
|
14
|
-
def provider
|
15
|
-
if defined? @provider
|
16
|
-
@provider
|
17
|
-
else
|
18
|
-
raise "Please configure your provider. See the Provider section in the README for examples."
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def add_pact_verification verification
|
23
|
-
pact_verifications << verification
|
24
|
-
end
|
25
|
-
def pact_verifications
|
26
|
-
@pact_verifications ||= []
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
Pact::Configuration.send(:include, Configuration)
|
31
|
-
|
32
|
-
def service_provider name, &block
|
33
|
-
service_provider = ServiceProviderDSL.new(name, &block).create_service_provider
|
34
|
-
Pact.configuration.provider = service_provider
|
35
|
-
service_provider
|
36
|
-
end
|
37
|
-
|
38
|
-
class ServiceProviderConfig
|
39
|
-
attr_accessor :name
|
40
|
-
|
41
|
-
def initialize name, &app_block
|
42
|
-
@name = name
|
43
|
-
@app_block = app_block
|
44
|
-
end
|
45
|
-
|
46
|
-
def app
|
47
|
-
@app_block.call
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
class VerificationDSL
|
52
|
-
def initialize consumer_name, options = {}, &block
|
53
|
-
@consumer_name = consumer_name
|
54
|
-
@ref = options.fetch(:ref, :head)
|
55
|
-
@pact_uri = nil
|
56
|
-
instance_eval(&block)
|
57
|
-
end
|
58
|
-
|
59
|
-
def pact_uri pact_uri, options = {}
|
60
|
-
@pact_uri = pact_uri
|
61
|
-
end
|
62
|
-
|
63
|
-
def task task
|
64
|
-
@task = task
|
65
|
-
end
|
66
|
-
|
67
|
-
def create_verification
|
68
|
-
validate
|
69
|
-
Pact::Provider::PactVerification.new(@consumer_name, @pact_uri, @ref)
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
def validate
|
75
|
-
raise "Please provide a pact_uri for the verification" unless @pact_uri
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
class ServiceProviderDSL
|
81
|
-
|
82
|
-
def initialize name, &block
|
83
|
-
@name = name
|
84
|
-
@app = nil
|
85
|
-
instance_eval(&block)
|
86
|
-
end
|
87
|
-
|
88
|
-
def validate
|
89
|
-
raise "Please provide a name for the Provider" unless @name && !@name.strip.empty?
|
90
|
-
raise "Please configure an app for the Provider" unless @app_block
|
91
|
-
end
|
92
|
-
|
93
|
-
def name name
|
94
|
-
@name = name
|
95
|
-
end
|
96
|
-
|
97
|
-
def app &block
|
98
|
-
@app_block = block
|
99
|
-
end
|
100
|
-
|
101
|
-
def honours_pact_with consumer_name, options = {}, &app_block
|
102
|
-
verification = VerificationDSL.new(consumer_name, options, &app_block).create_verification
|
103
|
-
Pact.configuration.add_pact_verification verification
|
104
|
-
end
|
105
|
-
|
106
|
-
def create_service_provider
|
107
|
-
validate
|
108
|
-
ServiceProviderConfig.new(@name, &@app_block)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
Pact.send(:extend, Pact::Provider::DSL)
|
data/lib/pact/request.rb
DELETED
@@ -1,167 +0,0 @@
|
|
1
|
-
require 'pact/matchers'
|
2
|
-
require 'pact/reification'
|
3
|
-
|
4
|
-
module Pact
|
5
|
-
|
6
|
-
module Request
|
7
|
-
|
8
|
-
class NullExpectation
|
9
|
-
def to_s
|
10
|
-
"<No expectation>"
|
11
|
-
end
|
12
|
-
|
13
|
-
def ==(other_object)
|
14
|
-
other_object.is_a? NullExpectation
|
15
|
-
end
|
16
|
-
|
17
|
-
def ===(other_object)
|
18
|
-
other_object.is_a? NullExpectation
|
19
|
-
end
|
20
|
-
|
21
|
-
def eql?(other_object)
|
22
|
-
self == other_object
|
23
|
-
end
|
24
|
-
|
25
|
-
def hash
|
26
|
-
2934820948209428748274238642672
|
27
|
-
end
|
28
|
-
|
29
|
-
def empty?
|
30
|
-
true
|
31
|
-
end
|
32
|
-
|
33
|
-
def nil?
|
34
|
-
true
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
class Base
|
39
|
-
include Pact::Matchers
|
40
|
-
extend Pact::Matchers
|
41
|
-
|
42
|
-
NULL_EXPECTATION = NullExpectation.new
|
43
|
-
|
44
|
-
attr_reader :method, :path, :headers, :body, :query, :options
|
45
|
-
|
46
|
-
def self.from_hash(hash)
|
47
|
-
sym_hash = symbolize_keys hash
|
48
|
-
method = sym_hash.fetch(:method)
|
49
|
-
path = sym_hash.fetch(:path)
|
50
|
-
query = sym_hash.fetch(:query, NULL_EXPECTATION)
|
51
|
-
headers = sym_hash.fetch(:headers, NULL_EXPECTATION)
|
52
|
-
body = sym_hash.fetch(:body, NULL_EXPECTATION)
|
53
|
-
new(method, path, headers, body, query)
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.symbolize_keys hash
|
57
|
-
hash.inject({}) { |memo, (k,v)| memo[k.to_sym] = v; memo }
|
58
|
-
end
|
59
|
-
|
60
|
-
def initialize(method, path, headers, body, query)
|
61
|
-
@method = method.to_s
|
62
|
-
@path = path.chomp('/')
|
63
|
-
@headers = headers
|
64
|
-
@body = body
|
65
|
-
@query = query
|
66
|
-
end
|
67
|
-
|
68
|
-
def to_json(options = {})
|
69
|
-
as_json.to_json(options)
|
70
|
-
end
|
71
|
-
|
72
|
-
def as_json
|
73
|
-
base_json = {
|
74
|
-
method: method,
|
75
|
-
path: path,
|
76
|
-
}
|
77
|
-
|
78
|
-
base_json.merge!(body: body) unless body.is_a? NullExpectation
|
79
|
-
base_json.merge!(headers: headers) unless headers.is_a? NullExpectation
|
80
|
-
base_json.merge!(query: query) unless query.is_a? NullExpectation
|
81
|
-
base_json
|
82
|
-
end
|
83
|
-
|
84
|
-
def as_json_without_body
|
85
|
-
keep_keys = [:method, :path, :headers, :query]
|
86
|
-
as_json.reject{ |key, value| !keep_keys.include? key }
|
87
|
-
end
|
88
|
-
|
89
|
-
def short_description
|
90
|
-
"#{method} #{full_path}"
|
91
|
-
end
|
92
|
-
|
93
|
-
def full_path
|
94
|
-
fp = ''
|
95
|
-
if path.empty?
|
96
|
-
fp << "/"
|
97
|
-
else
|
98
|
-
fp << path
|
99
|
-
end
|
100
|
-
if query && !query.empty?
|
101
|
-
fp << ("?" + query)
|
102
|
-
end
|
103
|
-
fp
|
104
|
-
end
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
class Expected < Base
|
109
|
-
|
110
|
-
DEFAULT_OPTIONS = {:allow_unexpected_keys => false}.freeze
|
111
|
-
attr_accessor :description
|
112
|
-
attr_accessor :options
|
113
|
-
|
114
|
-
def self.from_hash(hash)
|
115
|
-
request = super
|
116
|
-
request.description = hash.fetch(:description, nil)
|
117
|
-
request.options = symbolize_keys(hash).fetch(:options, {})
|
118
|
-
request
|
119
|
-
end
|
120
|
-
|
121
|
-
def initialize(method, path, headers, body, query, options = {})
|
122
|
-
super(method, path, headers, body, query)
|
123
|
-
@options = options
|
124
|
-
end
|
125
|
-
|
126
|
-
def match(actual_request)
|
127
|
-
difference(actual_request).empty?
|
128
|
-
end
|
129
|
-
|
130
|
-
def matches_route? actual_request
|
131
|
-
diff({:method => method, :path => path}, {:method => actual_request.method, :path => actual_request.path}).empty?
|
132
|
-
end
|
133
|
-
|
134
|
-
def difference(actual_request)
|
135
|
-
request_diff = diff(as_json_without_body, actual_request.as_json_without_body)
|
136
|
-
unless body.is_a? NullExpectation
|
137
|
-
request_diff.merge(body_difference(actual_request.body))
|
138
|
-
else
|
139
|
-
request_diff
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def body_difference(actual_body)
|
144
|
-
diff({:body => body}, {body: actual_body}, allow_unexpected_keys: runtime_options[:allow_unexpected_keys_in_body])
|
145
|
-
end
|
146
|
-
|
147
|
-
def as_json_with_options
|
148
|
-
as_json.merge( options.empty? ? {} : { options: options} )
|
149
|
-
end
|
150
|
-
|
151
|
-
def generated_body
|
152
|
-
Pact::Reification.from_term(body)
|
153
|
-
end
|
154
|
-
|
155
|
-
# Don't want to put the default options in the pact json just yet, so calculating these at run time, rather than assigning
|
156
|
-
# the result to @options
|
157
|
-
def runtime_options
|
158
|
-
DEFAULT_OPTIONS.merge(self.class.symbolize_keys(options))
|
159
|
-
end
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
class Actual < Base
|
164
|
-
end
|
165
|
-
|
166
|
-
end
|
167
|
-
end
|
@@ -1,143 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'pact/consumer/mock_service'
|
3
|
-
|
4
|
-
module Pact::Consumer
|
5
|
-
|
6
|
-
describe InteractionList do
|
7
|
-
shared_context "unexpected requests and missed interactions" do
|
8
|
-
let(:expected_call) { {request: 'blah'} }
|
9
|
-
let(:unexpected_call) { Pact::Request::Actual.from_hash(path: '/path', method: 'get') }
|
10
|
-
subject {
|
11
|
-
interactionList = InteractionList.new
|
12
|
-
interactionList.add expected_call
|
13
|
-
interactionList.register_unexpected unexpected_call
|
14
|
-
interactionList
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
|
-
shared_context "no unexpected requests or missed interactions exist" do
|
19
|
-
let(:expected_call) { {request: 'blah'} }
|
20
|
-
let(:unexpected_call) { Pact::Request::Actual.from_hash(path: '/path', method: 'get') }
|
21
|
-
subject {
|
22
|
-
interactionList = InteractionList.new
|
23
|
-
interactionList.add expected_call
|
24
|
-
interactionList.register_matched expected_call
|
25
|
-
interactionList
|
26
|
-
}
|
27
|
-
end
|
28
|
-
|
29
|
-
describe "interaction_diffs" do
|
30
|
-
context "when unexpected requests and missed interactions exist" do
|
31
|
-
include_context "unexpected requests and missed interactions"
|
32
|
-
let(:expected) {
|
33
|
-
{:missing_interactions=>[{:request=>"blah"}], :unexpected_requests=>[{:method=>"get", :path=>"/path"}]}
|
34
|
-
}
|
35
|
-
it "returns the unexpected requests and missed interactions" do
|
36
|
-
expect(subject.interaction_diffs).to eq expected
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
context "when no unexpected requests or missed interactions exist" do
|
41
|
-
include_context "no unexpected requests or missed interactions exist"
|
42
|
-
let(:expected) {
|
43
|
-
{}
|
44
|
-
}
|
45
|
-
it "returns an empty hash" do
|
46
|
-
expect(subject.interaction_diffs).to eq expected
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
describe "all_matched?" do
|
52
|
-
context "when unexpected requests or missed interactions exist" do
|
53
|
-
include_context "unexpected requests and missed interactions"
|
54
|
-
it "returns false" do
|
55
|
-
expect(subject.all_matched?).to be_false
|
56
|
-
end
|
57
|
-
end
|
58
|
-
context "when unexpected requests or missed interactions do not exist" do
|
59
|
-
include_context "no unexpected requests or missed interactions exist"
|
60
|
-
it "returns false" do
|
61
|
-
expect(subject.all_matched?).to be_true
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
describe RequestExtractor do
|
68
|
-
class TestSubject
|
69
|
-
include RequestExtractor
|
70
|
-
end
|
71
|
-
|
72
|
-
let(:rack_env) {
|
73
|
-
{
|
74
|
-
"CONTENT_LENGTH" => "16",
|
75
|
-
"CONTENT_TYPE" => content_type,
|
76
|
-
"GATEWAY_INTERFACE" => "CGI/1.1",
|
77
|
-
"PATH_INFO" => "/donuts",
|
78
|
-
"QUERY_STRING" => "",
|
79
|
-
"REMOTE_ADDR" => "127.0.0.1",
|
80
|
-
"REMOTE_HOST" => "localhost",
|
81
|
-
"REQUEST_METHOD" => "POST",
|
82
|
-
"REQUEST_URI" => "http://localhost:4321/donuts",
|
83
|
-
"SCRIPT_NAME" => "",
|
84
|
-
"SERVER_NAME" => "localhost",
|
85
|
-
"SERVER_PORT" => "4321",
|
86
|
-
"SERVER_PROTOCOL" => "HTTP/1.1",
|
87
|
-
"SERVER_SOFTWARE" => "WEBrick/1.3.1 (Ruby/1.9.3/2013-02-22)",
|
88
|
-
"HTTP_ACCEPT" => "text/plain",
|
89
|
-
"HTTP_USER_AGENT" => "Ruby",
|
90
|
-
"HTTP_HOST" => "localhost:4321",
|
91
|
-
"rack.version" => [1, 2 ],
|
92
|
-
"rack.input" => StringIO.new(body),
|
93
|
-
"rack.errors" => nil,
|
94
|
-
"rack.multithread" => true,
|
95
|
-
"rack.multiprocess" => false,
|
96
|
-
"rack.run_once" => false,
|
97
|
-
"rack.url_scheme" => "http",
|
98
|
-
"HTTP_VERSION" => "HTTP/1.1",
|
99
|
-
"REQUEST_PATH" => "/donuts"
|
100
|
-
}
|
101
|
-
}
|
102
|
-
|
103
|
-
subject { TestSubject.new }
|
104
|
-
|
105
|
-
let(:expected_request) {
|
106
|
-
{
|
107
|
-
"query" => "",
|
108
|
-
"method" => "post",
|
109
|
-
"body" => expected_body,
|
110
|
-
"path" => "/donuts",
|
111
|
-
"headers" => {
|
112
|
-
"Content-Type" => content_type,
|
113
|
-
"Accept" => "text/plain",
|
114
|
-
"User-Agent" => "Ruby",
|
115
|
-
"Host" => "localhost:4321",
|
116
|
-
"Version" => "HTTP/1.1"
|
117
|
-
}
|
118
|
-
}
|
119
|
-
}
|
120
|
-
|
121
|
-
context "with a text body" do
|
122
|
-
let(:content_type) { "application/x-www-form-urlencoded" }
|
123
|
-
let(:body) { 'this is the body' }
|
124
|
-
let(:expected_body) { body }
|
125
|
-
|
126
|
-
it "extracts the body" do
|
127
|
-
expect(subject.request_from(rack_env)).to eq expected_request
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
context "with a json body" do
|
132
|
-
let(:content_type) { "application/json" }
|
133
|
-
let(:body) { '{"a" : "body" }' }
|
134
|
-
let(:expected_body) { {"a" => "body"} }
|
135
|
-
|
136
|
-
it "extracts the body" do
|
137
|
-
expect(subject.request_from(rack_env)).to eq expected_request
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
|
142
|
-
end
|
143
|
-
end
|