pact 1.0.15 → 1.0.18
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 +29 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +23 -6
- data/README.md +64 -19
- data/Rakefile +2 -2
- data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +5 -2
- data/lib/pact/consumer/consumer_contract_builder.rb +2 -2
- data/lib/pact/consumer/mock_service/app.rb +2 -2
- data/lib/pact/consumer/mock_service_interaction_expectation.rb +5 -1
- data/lib/pact/consumer_contract/active_support_support.rb +29 -0
- data/lib/pact/consumer_contract/consumer_contract.rb +30 -6
- data/lib/pact/consumer_contract/interaction.rb +7 -1
- data/lib/pact/consumer_contract/request.rb +2 -2
- data/lib/pact/consumer_contract/service_consumer.rb +5 -1
- data/lib/pact/consumer_contract/service_provider.rb +5 -1
- data/lib/pact/matchers/matchers.rb +1 -1
- data/lib/pact/provider/pact_spec_runner.rb +100 -72
- data/lib/pact/provider/rspec.rb +17 -33
- data/lib/pact/provider/verification_report.rb +5 -1
- data/lib/pact/shared/request.rb +7 -3
- data/lib/pact/something_like.rb +5 -1
- data/lib/pact/tasks/task_helper.rb +9 -0
- data/lib/pact/tasks/verification_task.rb +74 -73
- data/lib/pact/term.rb +15 -1
- data/lib/pact/version.rb +1 -1
- data/lib/tasks/pact.rake +4 -2
- data/pact.gemspec +2 -1
- data/spec/features/production_spec.rb +1 -0
- data/spec/integration/consumer_spec.rb +1 -0
- data/spec/lib/pact/consumer_contract/active_support_support_spec.rb +43 -0
- data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +1 -1
- data/spec/lib/pact/consumer_contract/interaction_spec.rb +3 -3
- data/spec/lib/pact/matchers/matchers_spec.rb +10 -1
- data/spec/lib/pact/verification_task_spec.rb +102 -79
- data/spec/spec_helper.rb +11 -0
- data/tasks/pact-test.rake +1 -1
- data/tasks/spec.rake +8 -0
- metadata +40 -23
- data/lib/pact/json_warning.rb +0 -32
- data/spec/lib/pact/json_warning_spec.rb +0 -39
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'pact/consumer_contract/request'
|
2
2
|
require 'pact/symbolize_keys'
|
3
|
+
require 'pact/consumer_contract/active_support_support'
|
3
4
|
|
4
5
|
module Pact
|
5
6
|
class Interaction
|
7
|
+
include ActiveSupportSupport
|
6
8
|
include SymbolizeKeys
|
7
9
|
|
8
10
|
attr_accessor :description, :request, :response, :provider_state
|
@@ -19,12 +21,16 @@ module Pact
|
|
19
21
|
new(symbolize_keys(hash).merge({request: request}))
|
20
22
|
end
|
21
23
|
|
22
|
-
def
|
24
|
+
def to_hash
|
23
25
|
hash = { :description => @description }
|
24
26
|
hash[:provider_state] = @provider_state if @provider_state #Easier to read when provider state at top
|
25
27
|
hash.merge(:request => @request.as_json, :response => @response)
|
26
28
|
end
|
27
29
|
|
30
|
+
def as_json options = {}
|
31
|
+
fix_all_the_things to_hash
|
32
|
+
end
|
33
|
+
|
28
34
|
def to_json(options = {})
|
29
35
|
as_json.to_json(options)
|
30
36
|
end
|
@@ -35,7 +35,7 @@ module Pact
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def difference(actual_request)
|
38
|
-
request_diff = diff(
|
38
|
+
request_diff = diff(to_hash_without_body, actual_request.to_hash_without_body)
|
39
39
|
unless body.is_a? NullExpectation
|
40
40
|
request_diff.merge(body_difference(actual_request.body))
|
41
41
|
else
|
@@ -51,7 +51,7 @@ module Pact
|
|
51
51
|
|
52
52
|
private
|
53
53
|
|
54
|
-
# Options is a dirty hack to allow Condor to send extra keys in the request,
|
54
|
+
# Options is a dirty hack to allow Condor to send extra keys in the request,
|
55
55
|
# as it's too much work to set up an exactly matching expectation.
|
56
56
|
# Need to implement a proper matching strategy and remove this.
|
57
57
|
# Do not rely on it!
|
@@ -3,79 +3,107 @@ require 'rspec'
|
|
3
3
|
require 'rspec/core'
|
4
4
|
require 'rspec/core/formatters/documentation_formatter'
|
5
5
|
require 'rspec/core/formatters/json_formatter'
|
6
|
+
require 'pact/provider/pact_helper_locator'
|
6
7
|
require_relative 'rspec'
|
7
8
|
|
8
9
|
|
9
10
|
module Pact
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
11
|
+
module Provider
|
12
|
+
class PactSpecRunner
|
13
|
+
|
14
|
+
include Pact::Provider::RSpec::ClassMethods
|
15
|
+
|
16
|
+
SUPPORT_FILE_DEPRECATION_MESSAGE = "The :support_file option is deprecated. " +
|
17
|
+
"The preferred way to specify a support file is to create a pact_helper.rb in one of the following paths: " +
|
18
|
+
Pact::Provider::PactHelperLocater::PACT_HELPER_FILE_PATTERNS.join(", ") +
|
19
|
+
". If you cannot do this, you may use the :pact_helper option in place of the :support_file option."
|
20
|
+
|
21
|
+
attr_reader :spec_definitions
|
22
|
+
attr_reader :options
|
23
|
+
attr_reader :output
|
24
|
+
|
25
|
+
def initialize spec_definitions, options = {}
|
26
|
+
@spec_definitions = spec_definitions
|
27
|
+
@options = options
|
28
|
+
@results = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
begin
|
33
|
+
configure_rspec
|
34
|
+
initialize_specs
|
35
|
+
run_specs
|
36
|
+
ensure
|
37
|
+
::RSpec.reset
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def require_pact_helper spec_definition
|
44
|
+
if spec_definition[:pact_helper]
|
45
|
+
puts "Requiring #{spec_definition[:pact_helper]}"
|
46
|
+
require spec_definition[:pact_helper]
|
47
|
+
elsif spec_definition[:support_file]
|
48
|
+
puts "Requiring #{spec_definition[:support_file]}"
|
49
|
+
$stderr.puts SUPPORT_FILE_DEPRECATION_MESSAGE
|
50
|
+
require spec_definition[:support_file]
|
51
|
+
else
|
52
|
+
puts "Requiring #{Pact::Provider::PactHelperLocater.pact_helper_path}"
|
53
|
+
require 'pact/provider/client_project_pact_helper'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def initialize_specs
|
58
|
+
spec_definitions.each do | spec_definition |
|
59
|
+
require_pact_helper spec_definition
|
60
|
+
options = {
|
61
|
+
consumer: spec_definition[:consumer],
|
62
|
+
save_pactfile_to_tmp: true,
|
63
|
+
criteria: @options[:criteria]
|
64
|
+
}
|
65
|
+
honour_pactfile spec_definition[:uri], options
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def configure_rspec
|
70
|
+
config = ::RSpec.configuration
|
71
|
+
|
72
|
+
config.color = true
|
73
|
+
config.extend Pact::Provider::RSpec::ClassMethods
|
74
|
+
config.include Pact::Provider::RSpec::InstanceMethods
|
75
|
+
config.include Pact::Provider::TestMethods
|
76
|
+
|
77
|
+
config.before :each, :pact => :verify do | example |
|
78
|
+
example_description = "#{example.example.example_group.description} #{example.example.description}"
|
79
|
+
Pact.configuration.logger.info "Running example '#{example_description}'"
|
80
|
+
end
|
81
|
+
|
82
|
+
unless options[:silent]
|
83
|
+
config.error_stream = $stderr
|
84
|
+
config.output_stream = $stdout
|
85
|
+
end
|
86
|
+
|
87
|
+
formatter = ::RSpec::Core::Formatters::DocumentationFormatter.new(config.output)
|
88
|
+
@json_formatter = ::RSpec::Core::Formatters::JsonFormatter.new(StringIO.new)
|
89
|
+
reporter = ::RSpec::Core::Reporter.new(formatter, @json_formatter)
|
90
|
+
config.instance_variable_set(:@reporter, reporter)
|
91
|
+
end
|
92
|
+
|
93
|
+
def run_specs
|
94
|
+
config = ::RSpec.configuration
|
95
|
+
world = ::RSpec::world
|
96
|
+
exit_code = config.reporter.report(world.example_count, nil) do |reporter|
|
97
|
+
begin
|
98
|
+
config.run_hook(:before, :suite)
|
99
|
+
world.example_groups.ordered.map {|g| g.run(reporter)}.all? ? 0 : config.failure_exit_code
|
100
|
+
ensure
|
101
|
+
config.run_hook(:after, :suite)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
@output = @json_formatter.output_hash
|
105
|
+
exit_code
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/pact/provider/rspec.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'open-uri'
|
2
2
|
require 'pact/consumer_contract'
|
3
|
-
require 'pact/json_warning'
|
4
3
|
require 'pact/provider/matchers'
|
5
4
|
require 'pact/provider/test_methods'
|
6
5
|
require 'pact/provider/configuration'
|
@@ -19,28 +18,36 @@ module Pact
|
|
19
18
|
|
20
19
|
include ::RSpec::Core::DSL
|
21
20
|
|
22
|
-
include Pact::JsonWarning
|
23
|
-
|
24
21
|
def honour_pactfile pactfile_uri, options = {}
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
puts "Filtering specs by: #{options[:criteria]}" if options[:criteria]
|
23
|
+
consumer_contract = Pact::ConsumerContract.from_json(read_pact_from(pactfile_uri, options))
|
24
|
+
describe "A pact between #{consumer_contract.consumer.name} and #{consumer_contract.provider.name}" do
|
25
|
+
describe "in #{pactfile_uri}" do
|
26
|
+
honour_consumer_contract consumer_contract, options
|
27
|
+
end
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
def honour_consumer_contract consumer_contract, options = {}
|
32
|
-
check_for_active_support_json
|
33
32
|
describe_consumer_contract consumer_contract, options.merge({:consumer => consumer_contract.consumer.name})
|
34
33
|
end
|
35
34
|
|
36
35
|
private
|
37
36
|
|
38
37
|
def describe_consumer_contract consumer_contract, options
|
39
|
-
consumer_contract.
|
38
|
+
consumer_interactions(consumer_contract, options).each do |interaction|
|
40
39
|
describe_interaction_with_provider_state interaction, options
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
43
|
+
def consumer_interactions(consumer_contract, options)
|
44
|
+
if options[:criteria].nil?
|
45
|
+
consumer_contract.interactions
|
46
|
+
else
|
47
|
+
consumer_contract.find_interactions options[:criteria]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
44
51
|
def describe_interaction_with_provider_state interaction, options
|
45
52
|
if interaction.provider_state
|
46
53
|
describe "Given #{interaction.provider_state}" do
|
@@ -96,24 +103,11 @@ module Pact
|
|
96
103
|
end
|
97
104
|
|
98
105
|
def description_for interaction
|
99
|
-
"#{interaction.description} to #{interaction.request.path}"
|
106
|
+
"#{interaction.description} using #{interaction.request.method.upcase} to #{interaction.request.path}"
|
100
107
|
end
|
101
108
|
|
102
109
|
def read_pact_from uri, options = {}
|
103
|
-
|
104
|
-
if options[:save_pactfile_to_tmp]
|
105
|
-
save_pactfile_to_tmp pact, File.basename(uri)
|
106
|
-
end
|
107
|
-
pact
|
108
|
-
rescue StandardError => e
|
109
|
-
$stderr.puts "Error reading file from #{uri}"
|
110
|
-
$stderr.puts "#{e.to_s} #{e.backtrace.join("\n")}"
|
111
|
-
raise e
|
112
|
-
end
|
113
|
-
|
114
|
-
def save_pactfile_to_tmp pact, name
|
115
|
-
FileUtils.mkdir_p Pact.configuration.tmp_dir
|
116
|
-
File.open(Pact.configuration.tmp_dir + "/#{name}", "w") { |file| file << pact}
|
110
|
+
Pact::PactFile.read(uri, options)
|
117
111
|
end
|
118
112
|
|
119
113
|
end
|
@@ -121,13 +115,3 @@ module Pact
|
|
121
115
|
end
|
122
116
|
end
|
123
117
|
|
124
|
-
RSpec.configure do |config|
|
125
|
-
config.extend Pact::Provider::RSpec::ClassMethods
|
126
|
-
config.include Pact::Provider::RSpec::InstanceMethods
|
127
|
-
config.include Pact::Provider::TestMethods
|
128
|
-
|
129
|
-
config.before :each, :pact => :verify do | example |
|
130
|
-
example_description = "#{example.example.example_group.description} #{example.example.description}"
|
131
|
-
Pact.configuration.logger.info "Running example '#{example_description}'"
|
132
|
-
end
|
133
|
-
end
|
@@ -12,7 +12,7 @@ module Pact::Provider
|
|
12
12
|
@output = options[:output]
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
15
|
+
def to_hash
|
16
16
|
{
|
17
17
|
:consumer => @consumer,
|
18
18
|
:provider => @provider,
|
@@ -21,6 +21,10 @@ module Pact::Provider
|
|
21
21
|
}
|
22
22
|
end
|
23
23
|
|
24
|
+
def as_json options = {}
|
25
|
+
to_hash
|
26
|
+
end
|
27
|
+
|
24
28
|
def to_json(options = {})
|
25
29
|
as_json.to_json(options)
|
26
30
|
end
|
data/lib/pact/shared/request.rb
CHANGED
@@ -24,7 +24,11 @@ module Pact
|
|
24
24
|
as_json.to_json(options)
|
25
25
|
end
|
26
26
|
|
27
|
-
def as_json
|
27
|
+
def as_json options = {}
|
28
|
+
to_hash
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
28
32
|
base_json = {
|
29
33
|
method: method,
|
30
34
|
path: path,
|
@@ -57,9 +61,9 @@ module Pact
|
|
57
61
|
|
58
62
|
def self.key_not_found
|
59
63
|
raise NotImplementedError
|
60
|
-
end
|
64
|
+
end
|
61
65
|
|
62
|
-
def
|
66
|
+
def to_hash_without_body
|
63
67
|
keep_keys = [:method, :path, :headers, :query]
|
64
68
|
as_json.reject{ |key, value| !keep_keys.include? key }
|
65
69
|
end
|
data/lib/pact/something_like.rb
CHANGED
@@ -13,13 +13,17 @@ module Pact
|
|
13
13
|
@contents = contents
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def to_hash
|
17
17
|
{
|
18
18
|
:json_class => self.class.name,
|
19
19
|
:contents => contents
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
23
|
+
def as_json
|
24
|
+
to_hash
|
25
|
+
end
|
26
|
+
|
23
27
|
def to_json opts = {}
|
24
28
|
as_json.to_json opts
|
25
29
|
end
|
@@ -19,5 +19,14 @@ module Pact
|
|
19
19
|
fail
|
20
20
|
end
|
21
21
|
end
|
22
|
+
|
23
|
+
def spec_criteria defaults = {description: nil, provider_state: nil}
|
24
|
+
criteria = {}
|
25
|
+
[:description, :provider_state].each do | key |
|
26
|
+
value = ENV.fetch("PACT_#{key.to_s.upcase}", defaults[key])
|
27
|
+
criteria[key] = Regexp.new(value) unless value.nil?
|
28
|
+
end
|
29
|
+
criteria.any? ? criteria : nil
|
30
|
+
end
|
22
31
|
end
|
23
32
|
end
|
@@ -4,87 +4,88 @@ require 'pact/provider/verification_report'
|
|
4
4
|
require 'pact/tasks/task_helper'
|
5
5
|
|
6
6
|
=begin
|
7
|
-
|
7
|
+
To create a rake pact:verify:<something> task
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
Pact::VerificationTask.new(:head) do | pact |
|
10
|
+
pact.uri 'http://master.cd.vpc.realestate.com.au/browse/BIQ-MAS/latestSuccessful/artifact/JOB2/Pacts/mas-contract_transaction_service.json'
|
11
11
|
pact.uri 'http://master.cd.vpc.realestate.com.au/browse/BIQ-IMAGINARY-CONSUMER/latestSuccessful/artifact/JOB2/Pacts/imaginary_consumer-contract_transaction_service.json'
|
12
|
-
|
12
|
+
end
|
13
13
|
|
14
|
-
|
14
|
+
The pact.uri may be a local file system path or a remote URL.
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
To run a pact:verify:xxx task you need to define a pact_helper.rb, ideally in spec/service_consumers.
|
17
|
+
It should contain your service_provider definition, and load any provider state definition files.
|
18
|
+
It should also load all your app's dependencies (eg by calling out to spec_helper)
|
19
19
|
|
20
|
-
|
20
|
+
Eg.
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
require 'spec_helper'
|
23
|
+
require 'provider_states_for_my_consumer'
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
Pact.service_provider "My Provider" do
|
26
|
+
app { TestApp.new }
|
27
|
+
end
|
28
28
|
|
29
29
|
=end
|
30
30
|
|
31
|
-
|
32
31
|
module Pact
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
32
|
+
class VerificationTask < ::Rake::TaskLib
|
33
|
+
attr_reader :pact_spec_config
|
34
|
+
|
35
|
+
include Pact::TaskHelper
|
36
|
+
def initialize(name)
|
37
|
+
@pact_spec_config = []
|
38
|
+
@name = name
|
39
|
+
yield self
|
40
|
+
rake_task
|
41
|
+
end
|
42
|
+
|
43
|
+
def uri(uri, options = {})
|
44
|
+
@pact_spec_config << {uri: uri, support_file: options[:support_file], pact_helper: options[:pact_helper]}
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :name
|
50
|
+
|
51
|
+
def parse_pactfile config
|
52
|
+
Pact::ConsumerContract.from_uri config[:uri]
|
53
|
+
end
|
54
|
+
|
55
|
+
def publish_report config, output, result, provider_ref, reports_dir
|
56
|
+
consumer_contract = parse_pactfile config
|
57
|
+
#TODO - when checking out a historical version, provider ref will be prod, however it will think it is head. Fix this!!!!
|
58
|
+
report = Provider::VerificationReport.new(
|
59
|
+
:result => result,
|
60
|
+
:output => output,
|
61
|
+
:consumer => {:name => consumer_contract.consumer.name, :ref => name},
|
62
|
+
:provider => {:name => consumer_contract.provider.name, :ref => provider_ref}
|
63
|
+
)
|
64
|
+
|
65
|
+
FileUtils.mkdir_p reports_dir
|
66
|
+
File.open("#{reports_dir}/#{report.report_file_name}", "w") { |file| file << JSON.pretty_generate(report) }
|
67
|
+
end
|
68
|
+
|
69
|
+
def rake_task
|
70
|
+
namespace :pact do
|
71
|
+
desc "Verify provider against the consumer pacts for #{name}"
|
72
|
+
task "verify:#{name}", :description, :provider_state do |t, args|
|
73
|
+
|
74
|
+
options = {criteria: spec_criteria(args)}
|
75
|
+
|
76
|
+
exit_statuses = pact_spec_config.collect do | config |
|
77
|
+
#TODO: Change this to accept the ConsumerContract that is already parsed, so we don't make the same request twice
|
78
|
+
pact_spec_runner = Provider::PactSpecRunner.new([config], options)
|
79
|
+
exit_status = pact_spec_runner.run
|
80
|
+
publish_report config, pact_spec_runner.output, exit_status == 0, 'head', Pact.configuration.reports_dir
|
81
|
+
exit_status
|
82
|
+
end
|
83
|
+
|
84
|
+
handle_verification_failure do
|
85
|
+
exit_statuses.count{ | status | status != 0 }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|