pact 1.0.30 → 1.0.31
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/.gitignore +1 -0
- data/.travis.yml +2 -2
- data/Gemfile.lock +0 -7
- data/README.md +50 -53
- data/Rakefile +1 -25
- data/documentation/Testing with pact.png +0 -0
- data/documentation/faq.md +45 -0
- data/documentation/raq.md +39 -0
- data/documentation/terminology.md +25 -0
- data/example/animal-service/Gemfile +4 -4
- data/example/animal-service/Gemfile.lock +20 -15
- data/example/animal-service/Rakefile +1 -1
- data/example/animal-service/config.ru +3 -0
- data/example/animal-service/db/animal_db.sqlite3 +0 -0
- data/example/animal-service/db/animals_db.sqlite3 +0 -0
- data/example/animal-service/lib/animal_service/animal_repository.rb +16 -0
- data/example/animal-service/lib/animal_service/api.rb +28 -0
- data/example/animal-service/lib/animal_service/db.rb +5 -0
- data/example/animal-service/spec/service_consumers/pact_helper.rb +10 -16
- data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +13 -3
- data/example/zoo-app/Gemfile +0 -2
- data/example/zoo-app/Gemfile.lock +7 -8
- data/example/zoo-app/lib/zoo_app/animal_service_client.rb +8 -4
- data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +18 -64
- data/example/zoo-app/spec/service_providers/animal_service_client_spec.rb +76 -0
- data/example/zoo-app/spec/service_providers/pact_helper.rb +1 -1
- data/example/zoo-app/spec/spec_helper.rb +0 -2
- data/lib/pact/app.rb +4 -2
- data/lib/pact/consumer/configuration.rb +2 -2
- data/lib/pact/consumer/consumer_contract_builder.rb +2 -1
- data/lib/pact/consumer/interactions_filter.rb +7 -0
- data/lib/pact/consumer/mock_service/app.rb +7 -2
- data/lib/pact/consumer/mock_service/interaction_mismatch.rb +6 -1
- data/lib/pact/consumer/mock_service/interaction_post.rb +1 -1
- data/lib/pact/consumer/mock_service/rack_request_helper.rb +1 -1
- data/lib/pact/consumer/rspec.rb +9 -15
- data/lib/pact/consumer/rspec/full_example_description.rb +28 -0
- data/lib/pact/consumer/spec_hooks.rb +31 -0
- data/lib/pact/consumer_contract/consumer_contract.rb +2 -31
- data/lib/pact/consumer_contract/file_name.rb +13 -0
- data/lib/pact/consumer_contract/pact_file.rb +24 -0
- data/lib/pact/provider/matchers.rb +3 -1
- data/lib/pact/provider/provider_state.rb +43 -20
- data/lib/pact/version.rb +1 -1
- data/pact.gemspec +0 -1
- data/spec/features/consumption_spec.rb +5 -0
- data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +162 -147
- data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
- data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +3 -3
- metadata +19 -25
- data/example/zoo-app/spec/service_providers/animal_service_spec.rb +0 -92
Binary file
|
File without changes
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require_relative 'db'
|
3
|
+
|
4
|
+
module AnimalService
|
5
|
+
class AnimalRepository
|
6
|
+
|
7
|
+
|
8
|
+
def self.find_alligators
|
9
|
+
DATABASE[:animals].find_all
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.find_alligator_by_name name
|
13
|
+
DATABASE[:animals].where(name: name).single_record
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'animal_service/animal_repository'
|
3
|
+
|
4
|
+
module AnimalService
|
5
|
+
|
6
|
+
class Api < Sinatra::Base
|
7
|
+
|
8
|
+
set :raise_errors, false
|
9
|
+
set :show_exceptions, false
|
10
|
+
|
11
|
+
error do
|
12
|
+
e = env['sinatra.error']
|
13
|
+
content_type :json
|
14
|
+
status 500
|
15
|
+
{:error => e.message, :backtrace => e.backtrace}.to_json
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/alligators/:name' do
|
19
|
+
if (alligator = AnimalRepository.find_alligator_by_name(params[:name]))
|
20
|
+
content_type :json
|
21
|
+
alligator.to_json
|
22
|
+
else
|
23
|
+
status 404
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -1,24 +1,18 @@
|
|
1
|
-
|
2
|
-
require_relative "provider_states_for_zoo_app"
|
3
|
-
|
1
|
+
$: << File.expand_path("../../../lib", __FILE__)
|
4
2
|
|
5
|
-
|
3
|
+
require 'pact/provider/rspec'
|
4
|
+
require 'animal_service/db'
|
6
5
|
|
7
|
-
|
8
|
-
response_body = {}
|
9
|
-
if env['PATH_INFO'] == '/alligators'
|
10
|
-
response_body = {'name' => 'Bob'}.to_json
|
11
|
-
end
|
12
|
-
[200, {'Content-Type' => 'application/json'}, [response_body]]
|
13
|
-
end
|
14
|
-
end
|
6
|
+
require_relative "provider_states_for_zoo_app"
|
15
7
|
|
16
8
|
Pact.service_provider 'Animal Service' do
|
17
|
-
app do
|
18
|
-
AnimalService.new
|
19
|
-
end
|
20
|
-
|
21
9
|
honours_pact_with "Zoo App" do
|
22
10
|
pact_uri '../zoo-app/spec/pacts/zoo_app-animal_service.json'
|
23
11
|
end
|
12
|
+
end
|
13
|
+
|
14
|
+
RSpec.configure do | config |
|
15
|
+
config.before do
|
16
|
+
AnimalService::DATABASE[:animals].truncate
|
17
|
+
end
|
24
18
|
end
|
@@ -1,12 +1,22 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'animal_service/db'
|
3
|
+
require 'animal_service/animal_repository'
|
4
|
+
|
1
5
|
Pact.provider_states_for "Zoo App" do
|
2
|
-
|
6
|
+
|
7
|
+
provider_state "there is an alligator named Mary" do
|
3
8
|
set_up do
|
4
|
-
|
9
|
+
AnimalService::DATABASE[:animals].insert(name: 'Mary')
|
5
10
|
end
|
6
|
-
|
7
11
|
end
|
8
12
|
|
9
13
|
provider_state "there is not an alligator named Mary" do
|
10
14
|
no_op
|
11
15
|
end
|
16
|
+
|
17
|
+
provider_state "an error occurs retrieving an alligator" do
|
18
|
+
set_up do
|
19
|
+
AnimalService::AnimalRepository.stub(:find_alligator_by_name).and_raise("Argh!!!")
|
20
|
+
end
|
21
|
+
end
|
12
22
|
end
|
data/example/zoo-app/Gemfile
CHANGED
@@ -1,27 +1,26 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../../
|
3
3
|
specs:
|
4
|
-
pact (1.0.
|
5
|
-
awesome_print (~> 1.1
|
4
|
+
pact (1.0.30)
|
5
|
+
awesome_print (~> 1.1)
|
6
6
|
find_a_port (~> 1.0.1)
|
7
|
-
hashie (~> 2.0)
|
8
7
|
json
|
9
8
|
rack-test (~> 0.6.2)
|
10
9
|
randexp (~> 0.1.7)
|
11
10
|
rspec (~> 2.12)
|
12
11
|
thin
|
13
12
|
thor
|
13
|
+
webrick
|
14
14
|
|
15
15
|
GEM
|
16
16
|
remote: https://rubygems.org/
|
17
17
|
specs:
|
18
|
-
awesome_print (1.
|
18
|
+
awesome_print (1.2.0)
|
19
19
|
coderay (1.0.9)
|
20
20
|
daemons (1.1.9)
|
21
21
|
diff-lcs (1.2.4)
|
22
22
|
eventmachine (1.0.3)
|
23
23
|
find_a_port (1.0.1)
|
24
|
-
hashie (2.0.5)
|
25
24
|
httparty (0.11.0)
|
26
25
|
multi_json (~> 1.0)
|
27
26
|
multi_xml (>= 0.5.2)
|
@@ -47,17 +46,17 @@ GEM
|
|
47
46
|
diff-lcs (>= 1.1.3, < 2.0)
|
48
47
|
rspec-mocks (2.14.3)
|
49
48
|
slop (3.4.6)
|
50
|
-
thin (1.
|
49
|
+
thin (1.6.1)
|
51
50
|
daemons (>= 1.0.9)
|
52
|
-
eventmachine (>= 0.
|
51
|
+
eventmachine (>= 1.0.0)
|
53
52
|
rack (>= 1.0.0)
|
54
53
|
thor (0.18.1)
|
54
|
+
webrick (1.3.1)
|
55
55
|
|
56
56
|
PLATFORMS
|
57
57
|
ruby
|
58
58
|
|
59
59
|
DEPENDENCIES
|
60
|
-
bundler (~> 1.3.0)
|
61
60
|
httparty
|
62
61
|
json (~> 1.6.8)
|
63
62
|
pact!
|
@@ -9,19 +9,23 @@ module ZooApp
|
|
9
9
|
|
10
10
|
def self.find_alligators
|
11
11
|
response = get("/alligators", :headers => {'Accept' => 'application/json'})
|
12
|
-
|
12
|
+
handle_response response do
|
13
13
|
parse_body(response).collect do | hash |
|
14
14
|
ZooApp::Animals::Alligator.new(hash)
|
15
15
|
end
|
16
|
-
else
|
17
|
-
raise response.body
|
18
16
|
end
|
19
17
|
end
|
20
18
|
|
21
19
|
def self.find_alligator_by_name name
|
22
20
|
response = get("/alligators/#{name}", :headers => {'Accept' => 'application/json'})
|
23
|
-
|
21
|
+
handle_response response do
|
24
22
|
ZooApp::Animals::Alligator.new(parse_body(response))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.handle_response response
|
27
|
+
if response.success?
|
28
|
+
yield
|
25
29
|
elsif response.code == 404
|
26
30
|
nil
|
27
31
|
else
|
@@ -7,50 +7,8 @@
|
|
7
7
|
},
|
8
8
|
"interactions": [
|
9
9
|
{
|
10
|
-
"description": "a request for
|
11
|
-
"
|
12
|
-
"method": "get",
|
13
|
-
"path": "/animals"
|
14
|
-
},
|
15
|
-
"response": {
|
16
|
-
"status": 200,
|
17
|
-
"headers": {
|
18
|
-
"Content-Type": "application/json"
|
19
|
-
},
|
20
|
-
"body": {
|
21
|
-
"alligators": [
|
22
|
-
{
|
23
|
-
"name": "Bob"
|
24
|
-
}
|
25
|
-
]
|
26
|
-
}
|
27
|
-
},
|
28
|
-
"provider_state": "there are alligators"
|
29
|
-
},
|
30
|
-
{
|
31
|
-
"description": "a request for alligators",
|
32
|
-
"request": {
|
33
|
-
"method": "get",
|
34
|
-
"path": "/alligators",
|
35
|
-
"headers": {
|
36
|
-
"Accept": "application/json"
|
37
|
-
}
|
38
|
-
},
|
39
|
-
"response": {
|
40
|
-
"status": 200,
|
41
|
-
"headers": {
|
42
|
-
"Content-Type": "application/json"
|
43
|
-
},
|
44
|
-
"body": [
|
45
|
-
{
|
46
|
-
"name": "Bob"
|
47
|
-
}
|
48
|
-
]
|
49
|
-
},
|
50
|
-
"provider_state": "there are alligators"
|
51
|
-
},
|
52
|
-
{
|
53
|
-
"description": "a request for alligator Mary",
|
10
|
+
"description": "a request for an alligator",
|
11
|
+
"provider_state": "there is an alligator named Mary",
|
54
12
|
"request": {
|
55
13
|
"method": "get",
|
56
14
|
"path": "/alligators/Mary",
|
@@ -61,36 +19,30 @@
|
|
61
19
|
"response": {
|
62
20
|
"status": 200,
|
63
21
|
"headers": {
|
64
|
-
"Content-Type": "application/json"
|
22
|
+
"Content-Type": "application/json;charset=utf-8"
|
65
23
|
},
|
66
24
|
"body": {
|
67
25
|
"name": "Mary"
|
68
26
|
}
|
69
|
-
}
|
70
|
-
"provider_state": "there is an alligator named Mary"
|
27
|
+
}
|
71
28
|
},
|
72
29
|
{
|
73
|
-
"description": "a request for
|
30
|
+
"description": "a request for an alligator",
|
31
|
+
"provider_state": "there is not an alligator named Mary",
|
74
32
|
"request": {
|
75
33
|
"method": "get",
|
76
|
-
"path": "/alligators",
|
34
|
+
"path": "/alligators/Mary",
|
77
35
|
"headers": {
|
78
36
|
"Accept": "application/json"
|
79
37
|
}
|
80
38
|
},
|
81
39
|
"response": {
|
82
|
-
"status":
|
83
|
-
|
84
|
-
"Content-Type": "application/json"
|
85
|
-
},
|
86
|
-
"body": {
|
87
|
-
"error": "Argh!!!"
|
88
|
-
}
|
89
|
-
},
|
90
|
-
"provider_state": "an error has occurred"
|
40
|
+
"status": 404
|
41
|
+
}
|
91
42
|
},
|
92
43
|
{
|
93
|
-
"description": "a request for alligator
|
44
|
+
"description": "a request for an alligator",
|
45
|
+
"provider_state": "an error occurs retrieving an alligator",
|
94
46
|
"request": {
|
95
47
|
"method": "get",
|
96
48
|
"path": "/alligators/Mary",
|
@@ -99,17 +51,19 @@
|
|
99
51
|
}
|
100
52
|
},
|
101
53
|
"response": {
|
102
|
-
"status":
|
54
|
+
"status": 500,
|
103
55
|
"headers": {
|
104
|
-
"Content-Type": "application/json"
|
56
|
+
"Content-Type": "application/json;charset=utf-8"
|
57
|
+
},
|
58
|
+
"body": {
|
59
|
+
"error": "Argh!!!"
|
105
60
|
}
|
106
|
-
}
|
107
|
-
"provider_state": "there is not an alligator named Mary"
|
61
|
+
}
|
108
62
|
}
|
109
63
|
],
|
110
64
|
"metadata": {
|
111
65
|
"pact_gem": {
|
112
|
-
"version": "1.0.
|
66
|
+
"version": "1.0.30"
|
113
67
|
}
|
114
68
|
}
|
115
69
|
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative 'pact_helper'
|
2
|
+
require 'zoo_app/animal_service_client'
|
3
|
+
|
4
|
+
module ZooApp
|
5
|
+
describe AnimalServiceClient, :pact => true do
|
6
|
+
|
7
|
+
before do
|
8
|
+
AnimalServiceClient.base_uri animal_service.mock_service_base_url
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ".find_alligator_by_name" do
|
12
|
+
context "when an alligator by the given name exists" do
|
13
|
+
before do
|
14
|
+
animal_service.
|
15
|
+
given("there is an alligator named Mary").
|
16
|
+
upon_receiving("a request for an alligator").
|
17
|
+
with( method: :get,
|
18
|
+
path: '/alligators/Mary',
|
19
|
+
:headers => {'Accept' => 'application/json'} ).
|
20
|
+
will_respond_with(
|
21
|
+
status: 200,
|
22
|
+
headers: { 'Content-Type' => 'application/json;charset=utf-8' },
|
23
|
+
body: { name: 'Mary'}
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns the alligator" do
|
28
|
+
expect(AnimalServiceClient.find_alligator_by_name("Mary")).to eq ZooApp::Animals::Alligator.new(:name => 'Mary')
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when an alligator by the given name does not exist" do
|
34
|
+
|
35
|
+
before do
|
36
|
+
animal_service.
|
37
|
+
given("there is not an alligator named Mary").
|
38
|
+
upon_receiving("a request for an alligator").
|
39
|
+
with(
|
40
|
+
method: :get,
|
41
|
+
path: '/alligators/Mary',
|
42
|
+
headers: {'Accept' => 'application/json'} ).
|
43
|
+
will_respond_with(
|
44
|
+
status: 404
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "returns nil" do
|
49
|
+
expect(AnimalServiceClient.find_alligator_by_name("Mary")).to be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when an error response is returned" do
|
55
|
+
before do
|
56
|
+
animal_service.
|
57
|
+
given("an error occurs retrieving an alligator").
|
58
|
+
upon_receiving("a request for an alligator").
|
59
|
+
with( method: :get,
|
60
|
+
path: '/alligators/Mary',
|
61
|
+
:headers => {'Accept' => 'application/json'} ).
|
62
|
+
will_respond_with(
|
63
|
+
status: 500,
|
64
|
+
headers: { 'Content-Type' => 'application/json;charset=utf-8' },
|
65
|
+
body: {:error => 'Argh!!!'}
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "raises an error" do
|
70
|
+
expect{ AnimalServiceClient.find_alligator_by_name("Mary") }.to raise_error /Argh/
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/pact/app.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'find_a_port'
|
2
2
|
require 'thor'
|
3
|
-
require 'thin'
|
4
3
|
require 'thwait'
|
5
4
|
require 'pact/consumer'
|
5
|
+
require 'rack/handler/webrick'
|
6
6
|
|
7
7
|
module Pact
|
8
8
|
class App < Thor
|
@@ -19,9 +19,11 @@ module Pact
|
|
19
19
|
log.sync = true
|
20
20
|
service_options[:log_file] = log
|
21
21
|
end
|
22
|
+
|
22
23
|
port = options[:port] || FindAPort.available_port
|
23
24
|
mock_service = Consumer::MockService.new(service_options)
|
24
|
-
|
25
|
+
trap(:INT) { Rack::Handler::WEBrick.shutdown }
|
26
|
+
Rack::Handler::WEBrick.run(mock_service, :Port => port, :AccessLog => [])
|
25
27
|
end
|
26
28
|
|
27
29
|
private
|