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
@@ -44,7 +44,7 @@ module Pact::Consumer
|
|
44
44
|
def has_pact_with service_provider_name, &block
|
45
45
|
ServiceProvider.build(service_provider_name, name, &block)
|
46
46
|
end
|
47
|
-
end
|
47
|
+
end
|
48
48
|
|
49
49
|
def finalize
|
50
50
|
validate
|
@@ -76,7 +76,7 @@ module Pact::Consumer
|
|
76
76
|
@consumer_name = consumer_name
|
77
77
|
end
|
78
78
|
|
79
|
-
dsl do
|
79
|
+
dsl do
|
80
80
|
def mock_service name, &block
|
81
81
|
self.service = MockService.build(name, consumer_name, self.name, &block)
|
82
82
|
end
|
@@ -11,7 +11,7 @@ module Pact
|
|
11
11
|
|
12
12
|
include Pact::Logging
|
13
13
|
|
14
|
-
attr_reader :consumer_contract
|
14
|
+
attr_reader :consumer_contract, :mock_service_base_url
|
15
15
|
|
16
16
|
def initialize(attributes)
|
17
17
|
@interaction_builder = nil
|
@@ -22,6 +22,7 @@ module Pact
|
|
22
22
|
)
|
23
23
|
@consumer_contract.interactions = interactions_for_new_consumer_contract(attributes[:pactfile_write_mode])
|
24
24
|
@interactions_filter = filter(@consumer_contract.interactions, attributes[:pactfile_write_mode])
|
25
|
+
@mock_service_base_url = "http://localhost:#{attributes[:port]}"
|
25
26
|
end
|
26
27
|
|
27
28
|
def given(provider_state)
|
@@ -1,3 +1,10 @@
|
|
1
|
+
#
|
2
|
+
# When running in pactfile_write_mode :overwrite, all interactions are cleared from the
|
3
|
+
# pact file, and all new interactions should be distinct (unique description and provider state).
|
4
|
+
# When running in pactfile_write_mode :update, an interaction with the same description
|
5
|
+
# and provider state as an existing one will just overwrite that one interaction.
|
6
|
+
#
|
7
|
+
|
1
8
|
module Pact
|
2
9
|
module Consumer
|
3
10
|
|
@@ -39,7 +39,7 @@ module Pact
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def configure_logger options
|
42
|
-
options = {log_file:
|
42
|
+
options = {log_file: $stdout}.merge options
|
43
43
|
log_stream = options[:log_file]
|
44
44
|
@logger = Logger.new log_stream
|
45
45
|
@logger.level = Pact.configuration.logger.level
|
@@ -60,10 +60,15 @@ module Pact
|
|
60
60
|
begin
|
61
61
|
relevant_handler = @handlers.detect { |handler| handler.match? env }
|
62
62
|
response = relevant_handler.respond env
|
63
|
-
rescue
|
63
|
+
rescue StandardError => e
|
64
64
|
@logger.error 'Error ocurred in mock service:'
|
65
65
|
@logger.ap e, :error
|
66
66
|
@logger.ap e.backtrace
|
67
|
+
response = [500, {'Content-Type' => 'application/json'}, [{message: e.message, backtrace: e.backtrace}.to_json]]
|
68
|
+
rescue Exception => e
|
69
|
+
@logger.error 'Exception ocurred in mock service:'
|
70
|
+
@logger.ap e, :error
|
71
|
+
@logger.ap e.backtrace
|
67
72
|
raise e
|
68
73
|
end
|
69
74
|
response
|
@@ -2,6 +2,11 @@ require 'pact/matchers/diff_decorator'
|
|
2
2
|
|
3
3
|
module Pact
|
4
4
|
module Consumer
|
5
|
+
|
6
|
+
# Presents the differences between an actual request, and a list of
|
7
|
+
# expected interactions where the methods and paths match the actual request.
|
8
|
+
# This is used to display a helpful message to the user when a request
|
9
|
+
# comes in that doesn't match any of the expected interactions.
|
5
10
|
class InteractionMismatch
|
6
11
|
|
7
12
|
attr_accessor :candidate_interactions, :actual_request
|
@@ -24,7 +29,7 @@ module Pact
|
|
24
29
|
|
25
30
|
def short_summary
|
26
31
|
mismatched_attributes = candiate_diffs.collect(&:mismatched_attributes).flatten.uniq.join(", ").reverse.sub(",", "dna ").reverse #OMG what a hack!
|
27
|
-
actual_request.method_and_path + " (#{mismatched_attributes} did not match)"
|
32
|
+
actual_request.method_and_path + " (request #{mismatched_attributes} did not match)"
|
28
33
|
end
|
29
34
|
|
30
35
|
private
|
@@ -24,7 +24,7 @@ module Pact
|
|
24
24
|
interaction_list.add interaction
|
25
25
|
logger.info "Registered expected interaction #{interaction.request.method_and_path} for #{name}"
|
26
26
|
logger.ap interaction.as_json
|
27
|
-
[200, {}, ['Added
|
27
|
+
[200, {}, ['Added interaction']]
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
data/lib/pact/consumer/rspec.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'pact/consumer'
|
2
|
+
require 'pact/consumer/spec_hooks'
|
3
|
+
require 'pact/consumer/rspec/full_example_description'
|
2
4
|
|
3
5
|
module Pact
|
4
6
|
module Consumer
|
@@ -8,33 +10,25 @@ module Pact
|
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
13
|
+
hooks = Pact::Consumer::SpecHooks.new
|
14
|
+
|
15
|
+
|
11
16
|
RSpec.configure do |config|
|
12
17
|
config.include Pact::Consumer::RSpec, :pact => true
|
13
18
|
|
14
19
|
config.before :all, :pact => true do
|
15
|
-
|
16
|
-
FileUtils.mkdir_p Pact.configuration.pact_dir
|
20
|
+
hooks.before_all
|
17
21
|
end
|
18
22
|
|
19
23
|
config.before :each, :pact => true do | example |
|
20
|
-
|
21
|
-
Pact.configuration.logger.info "Clearing all expectations"
|
22
|
-
Pact::Consumer::AppManager.instance.ports_of_mock_services.each do | port |
|
23
|
-
Pact::Consumer::MockServiceClient.clear_interactions port, example_description
|
24
|
-
end
|
24
|
+
hooks.before_each Pact::Consumer::RSpec::FullExampleDescription.new(example).to_s
|
25
25
|
end
|
26
26
|
|
27
27
|
config.after :each, :pact => true do | example |
|
28
|
-
|
29
|
-
Pact.configuration.logger.info "Verifying interactions for #{example_description}"
|
30
|
-
Pact.configuration.provider_verifications.each do | provider_verification |
|
31
|
-
provider_verification.call example_description
|
32
|
-
end
|
28
|
+
hooks.after_each Pact::Consumer::RSpec::FullExampleDescription.new(example).to_s
|
33
29
|
end
|
34
30
|
|
35
31
|
config.after :suite do
|
36
|
-
|
37
|
-
Pact::Consumer::AppManager.instance.kill_all
|
38
|
-
Pact::Consumer::AppManager.instance.clear_all
|
32
|
+
hooks.after_suite
|
39
33
|
end
|
40
34
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# Creates the full description for an example group
|
3
|
+
#
|
4
|
+
module Pact
|
5
|
+
module Consumer
|
6
|
+
module RSpec
|
7
|
+
class FullExampleDescription
|
8
|
+
|
9
|
+
def initialize example
|
10
|
+
@example = example
|
11
|
+
end
|
12
|
+
|
13
|
+
def parent_group_descriptions
|
14
|
+
@example.example.example_group.parent_groups.collect(&:description).reverse
|
15
|
+
end
|
16
|
+
|
17
|
+
def example_description
|
18
|
+
@example.example.description
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
(parent_group_descriptions + [example_description]).join(" ")
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Pact
|
2
|
+
module Consumer
|
3
|
+
class SpecHooks
|
4
|
+
|
5
|
+
def before_all
|
6
|
+
Pact::Consumer::AppManager.instance.spawn_all
|
7
|
+
FileUtils.mkdir_p Pact.configuration.pact_dir
|
8
|
+
end
|
9
|
+
|
10
|
+
def before_each example_description
|
11
|
+
Pact.configuration.logger.info "Clearing all expectations"
|
12
|
+
Pact::Consumer::AppManager.instance.ports_of_mock_services.each do | port |
|
13
|
+
Pact::Consumer::MockServiceClient.clear_interactions port, example_description
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_each example_description
|
18
|
+
Pact.configuration.logger.info "Verifying interactions for #{example_description}"
|
19
|
+
Pact.configuration.provider_verifications.each do | provider_verification |
|
20
|
+
provider_verification.call example_description
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_suite
|
25
|
+
Pact.configuration.logger.info "After suite"
|
26
|
+
Pact::Consumer::AppManager.instance.kill_all
|
27
|
+
Pact::Consumer::AppManager.instance.clear_all
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -11,42 +11,13 @@ require_relative 'service_provider'
|
|
11
11
|
require_relative 'interaction'
|
12
12
|
require_relative 'request'
|
13
13
|
require_relative 'active_support_support'
|
14
|
+
require_relative 'pact_file'
|
15
|
+
require_relative 'file_name'
|
14
16
|
|
15
17
|
|
16
18
|
|
17
19
|
module Pact
|
18
20
|
|
19
|
-
module PactFile
|
20
|
-
extend self
|
21
|
-
def read uri, options = {}
|
22
|
-
pact = open(uri) { | file | file.read }
|
23
|
-
if options[:save_pactfile_to_tmp]
|
24
|
-
save_pactfile_to_tmp pact, ::File.basename(uri)
|
25
|
-
end
|
26
|
-
pact
|
27
|
-
rescue StandardError => e
|
28
|
-
$stderr.puts "Error reading file from #{uri}"
|
29
|
-
$stderr.puts "#{e.to_s} #{e.backtrace.join("\n")}"
|
30
|
-
raise e
|
31
|
-
end
|
32
|
-
|
33
|
-
def save_pactfile_to_tmp pact, name
|
34
|
-
::FileUtils.mkdir_p Pact.configuration.tmp_dir
|
35
|
-
::File.open(Pact.configuration.tmp_dir + "/#{name}", "w") { |file| file << pact}
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
#TODO move to external file for reuse
|
40
|
-
module FileName
|
41
|
-
def file_name consumer_name, provider_name
|
42
|
-
"#{filenamify(consumer_name)}-#{filenamify(provider_name)}.json"
|
43
|
-
end
|
44
|
-
|
45
|
-
def filenamify name
|
46
|
-
name.downcase.gsub(/\s/, '_')
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
21
|
class ConsumerContract
|
51
22
|
|
52
23
|
include SymbolizeKeys
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Pact
|
2
|
+
|
3
|
+
#TODO move to external file for reuse
|
4
|
+
module FileName
|
5
|
+
def file_name consumer_name, provider_name
|
6
|
+
"#{filenamify(consumer_name)}-#{filenamify(provider_name)}.json"
|
7
|
+
end
|
8
|
+
|
9
|
+
def filenamify name
|
10
|
+
name.downcase.gsub(/\s/, '_')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Pact
|
2
|
+
|
3
|
+
module PactFile
|
4
|
+
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def read uri, options = {}
|
8
|
+
pact = open(uri) { | file | file.read }
|
9
|
+
if options[:save_pactfile_to_tmp]
|
10
|
+
save_pactfile_to_tmp pact, ::File.basename(uri)
|
11
|
+
end
|
12
|
+
pact
|
13
|
+
rescue StandardError => e
|
14
|
+
$stderr.puts "Error reading file from #{uri}"
|
15
|
+
$stderr.puts "#{e.to_s} #{e.backtrace.join("\n")}"
|
16
|
+
raise e
|
17
|
+
end
|
18
|
+
|
19
|
+
def save_pactfile_to_tmp pact, name
|
20
|
+
::FileUtils.mkdir_p Pact.configuration.tmp_dir
|
21
|
+
::File.open(Pact.configuration.tmp_dir + "/#{name}", "w") { |file| file << pact}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'pact/term'
|
2
|
+
require 'pact/consumer_contract/active_support_support'
|
2
3
|
require 'awesome_print'
|
3
4
|
require 'pact/matchers'
|
4
5
|
require 'awesome_print'
|
@@ -6,6 +7,7 @@ require 'rspec'
|
|
6
7
|
|
7
8
|
RSpec::Matchers.define :match_term do |expected|
|
8
9
|
include Pact::Matchers
|
10
|
+
include Pact::ActiveSupportSupport
|
9
11
|
|
10
12
|
match do |actual|
|
11
13
|
if (difference = diff(expected, actual)).any?
|
@@ -17,7 +19,7 @@ RSpec::Matchers.define :match_term do |expected|
|
|
17
19
|
end
|
18
20
|
|
19
21
|
failure_message_for_should do | actual |
|
20
|
-
@message.
|
22
|
+
fix_json_formatting @message.to_json
|
21
23
|
end
|
22
24
|
|
23
25
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'pact/shared/dsl'
|
2
|
+
|
1
3
|
module Pact
|
2
4
|
module Provider
|
3
5
|
|
@@ -15,7 +17,7 @@ module Pact
|
|
15
17
|
|
16
18
|
class ProviderStates
|
17
19
|
def self.provider_state name, &block
|
18
|
-
ProviderState.
|
20
|
+
ProviderState.build(name, current_namespaces.join('.'), &block)
|
19
21
|
end
|
20
22
|
|
21
23
|
def self.register name, provider_state
|
@@ -41,10 +43,7 @@ module Pact
|
|
41
43
|
attr_accessor :name
|
42
44
|
attr_accessor :namespace
|
43
45
|
|
44
|
-
|
45
|
-
def register
|
46
|
-
ProviderStates.register(namespaced(name), self)
|
47
|
-
end
|
46
|
+
extend Pact::DSL
|
48
47
|
|
49
48
|
def initialize name, namespace, &block
|
50
49
|
@name = name
|
@@ -52,32 +51,56 @@ module Pact
|
|
52
51
|
@set_up_defined = false
|
53
52
|
@tear_down_defined = false
|
54
53
|
@no_op_defined = false
|
55
|
-
|
54
|
+
end
|
55
|
+
|
56
|
+
dsl do
|
57
|
+
def set_up &block
|
58
|
+
self.register_set_up &block
|
59
|
+
end
|
60
|
+
|
61
|
+
def tear_down &block
|
62
|
+
self.register_tear_down &block
|
63
|
+
end
|
64
|
+
|
65
|
+
def no_op
|
66
|
+
self.register_no_op
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def register
|
71
|
+
ProviderStates.register(namespaced(name), self)
|
72
|
+
end
|
73
|
+
|
74
|
+
def finalize
|
56
75
|
validate
|
57
76
|
end
|
58
77
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
78
|
+
def register_set_up &block
|
79
|
+
@set_up_block = block
|
80
|
+
@set_up_defined = true
|
81
|
+
end
|
82
|
+
|
83
|
+
def register_tear_down &block
|
84
|
+
@tear_down_block = block
|
85
|
+
@tear_down_defined = true
|
86
|
+
end
|
87
|
+
|
88
|
+
def register_no_op
|
89
|
+
@no_op_defined = true
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_up
|
93
|
+
if @set_up_block
|
64
94
|
instance_eval &@set_up_block
|
65
95
|
end
|
66
96
|
end
|
67
97
|
|
68
|
-
def tear_down
|
69
|
-
if
|
70
|
-
@tear_down_block = block
|
71
|
-
@tear_down_defined = true
|
72
|
-
elsif @tear_down_block
|
98
|
+
def tear_down
|
99
|
+
if @tear_down_block
|
73
100
|
instance_eval &@tear_down_block
|
74
101
|
end
|
75
102
|
end
|
76
103
|
|
77
|
-
def no_op
|
78
|
-
@no_op_defined = true
|
79
|
-
end
|
80
|
-
|
81
104
|
private
|
82
105
|
|
83
106
|
attr_accessor :no_op_defined, :set_up_defined, :tear_down_defined
|
data/lib/pact/version.rb
CHANGED
data/pact.gemspec
CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.license = 'MIT'
|
20
20
|
|
21
21
|
gem.add_runtime_dependency 'randexp', '~> 0.1.7'
|
22
|
-
gem.add_runtime_dependency 'thin'
|
23
22
|
gem.add_runtime_dependency 'rspec', '~> 2.12'
|
24
23
|
gem.add_runtime_dependency 'find_a_port', '~> 1.0.1'
|
25
24
|
gem.add_runtime_dependency 'rack-test', '~> 0.6.2'
|