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.
Files changed (51) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +2 -2
  3. data/Gemfile.lock +0 -7
  4. data/README.md +50 -53
  5. data/Rakefile +1 -25
  6. data/documentation/Testing with pact.png +0 -0
  7. data/documentation/faq.md +45 -0
  8. data/documentation/raq.md +39 -0
  9. data/documentation/terminology.md +25 -0
  10. data/example/animal-service/Gemfile +4 -4
  11. data/example/animal-service/Gemfile.lock +20 -15
  12. data/example/animal-service/Rakefile +1 -1
  13. data/example/animal-service/config.ru +3 -0
  14. data/example/animal-service/db/animal_db.sqlite3 +0 -0
  15. data/example/animal-service/db/animals_db.sqlite3 +0 -0
  16. data/example/animal-service/lib/animal_service/animal_repository.rb +16 -0
  17. data/example/animal-service/lib/animal_service/api.rb +28 -0
  18. data/example/animal-service/lib/animal_service/db.rb +5 -0
  19. data/example/animal-service/spec/service_consumers/pact_helper.rb +10 -16
  20. data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +13 -3
  21. data/example/zoo-app/Gemfile +0 -2
  22. data/example/zoo-app/Gemfile.lock +7 -8
  23. data/example/zoo-app/lib/zoo_app/animal_service_client.rb +8 -4
  24. data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +18 -64
  25. data/example/zoo-app/spec/service_providers/animal_service_client_spec.rb +76 -0
  26. data/example/zoo-app/spec/service_providers/pact_helper.rb +1 -1
  27. data/example/zoo-app/spec/spec_helper.rb +0 -2
  28. data/lib/pact/app.rb +4 -2
  29. data/lib/pact/consumer/configuration.rb +2 -2
  30. data/lib/pact/consumer/consumer_contract_builder.rb +2 -1
  31. data/lib/pact/consumer/interactions_filter.rb +7 -0
  32. data/lib/pact/consumer/mock_service/app.rb +7 -2
  33. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +6 -1
  34. data/lib/pact/consumer/mock_service/interaction_post.rb +1 -1
  35. data/lib/pact/consumer/mock_service/rack_request_helper.rb +1 -1
  36. data/lib/pact/consumer/rspec.rb +9 -15
  37. data/lib/pact/consumer/rspec/full_example_description.rb +28 -0
  38. data/lib/pact/consumer/spec_hooks.rb +31 -0
  39. data/lib/pact/consumer_contract/consumer_contract.rb +2 -31
  40. data/lib/pact/consumer_contract/file_name.rb +13 -0
  41. data/lib/pact/consumer_contract/pact_file.rb +24 -0
  42. data/lib/pact/provider/matchers.rb +3 -1
  43. data/lib/pact/provider/provider_state.rb +43 -20
  44. data/lib/pact/version.rb +1 -1
  45. data/pact.gemspec +0 -1
  46. data/spec/features/consumption_spec.rb +5 -0
  47. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +162 -147
  48. data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
  49. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +3 -3
  50. metadata +19 -25
  51. data/example/zoo-app/spec/service_providers/animal_service_spec.rb +0 -92
@@ -1,3 +1,3 @@
1
1
  require 'pact/tasks'
2
2
 
3
- :default => ['pact:verify']
3
+ task :default => 'pact:verify'
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/lib/animal_service/api'
2
+
3
+ run AnimalService::Api
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
@@ -0,0 +1,5 @@
1
+ require 'sequel'
2
+
3
+ module AnimalService
4
+ DATABASE ||= Sequel.connect(adapter: 'sqlite', database: './db/animal_db.sqlite3')
5
+ end
@@ -1,24 +1,18 @@
1
- require 'pact/provider/rspec'
2
- require_relative "provider_states_for_zoo_app"
3
-
1
+ $: << File.expand_path("../../../lib", __FILE__)
4
2
 
5
- class AnimalService
3
+ require 'pact/provider/rspec'
4
+ require 'animal_service/db'
6
5
 
7
- def call env
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
- provider_state "there are alligators" do
6
+
7
+ provider_state "there is an alligator named Mary" do
3
8
  set_up do
4
- #AlligatorRepo.save(Alligator.name("Mary"))
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
@@ -1,7 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'bundler', '~> 1.3.0'
4
-
5
3
  group :development, :test do
6
4
  gem 'rspec'
7
5
  gem 'pact', path: '../../'
@@ -1,27 +1,26 @@
1
1
  PATH
2
2
  remote: ../../
3
3
  specs:
4
- pact (1.0.9)
5
- awesome_print (~> 1.1.0)
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.1.0)
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.5.1)
49
+ thin (1.6.1)
51
50
  daemons (>= 1.0.9)
52
- eventmachine (>= 0.12.6)
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
- if response.success?
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
- if response.success?
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 animals",
11
- "request": {
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 alligators",
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": 500,
83
- "headers": {
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 Mary",
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": 404,
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.9"
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
@@ -3,7 +3,7 @@ require 'pact/consumer/rspec'
3
3
 
4
4
  Pact.service_consumer 'Zoo App' do
5
5
  has_pact_with "Animal Service" do
6
- service :animal_service do
6
+ mock_service :animal_service do
7
7
  port 1234
8
8
  end
9
9
  end
@@ -1,7 +1,5 @@
1
1
  $: << File.expand_path("../../lib", __FILE__)
2
2
 
3
- require 'zoo_app/animal_service_client'
4
-
5
3
  RSpec.configure do | config |
6
4
  config.color = true
7
5
  config.formatter = :documentation
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
- Thin::Server.start("0.0.0.0", port, mock_service)
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