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.
Files changed (40) hide show
  1. data/CHANGELOG.md +29 -0
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +23 -6
  4. data/README.md +64 -19
  5. data/Rakefile +2 -2
  6. data/example/animal-service/spec/service_consumers/provider_states_for_zoo_app.rb +5 -2
  7. data/lib/pact/consumer/consumer_contract_builder.rb +2 -2
  8. data/lib/pact/consumer/mock_service/app.rb +2 -2
  9. data/lib/pact/consumer/mock_service_interaction_expectation.rb +5 -1
  10. data/lib/pact/consumer_contract/active_support_support.rb +29 -0
  11. data/lib/pact/consumer_contract/consumer_contract.rb +30 -6
  12. data/lib/pact/consumer_contract/interaction.rb +7 -1
  13. data/lib/pact/consumer_contract/request.rb +2 -2
  14. data/lib/pact/consumer_contract/service_consumer.rb +5 -1
  15. data/lib/pact/consumer_contract/service_provider.rb +5 -1
  16. data/lib/pact/matchers/matchers.rb +1 -1
  17. data/lib/pact/provider/pact_spec_runner.rb +100 -72
  18. data/lib/pact/provider/rspec.rb +17 -33
  19. data/lib/pact/provider/verification_report.rb +5 -1
  20. data/lib/pact/shared/request.rb +7 -3
  21. data/lib/pact/something_like.rb +5 -1
  22. data/lib/pact/tasks/task_helper.rb +9 -0
  23. data/lib/pact/tasks/verification_task.rb +74 -73
  24. data/lib/pact/term.rb +15 -1
  25. data/lib/pact/version.rb +1 -1
  26. data/lib/tasks/pact.rake +4 -2
  27. data/pact.gemspec +2 -1
  28. data/spec/features/production_spec.rb +1 -0
  29. data/spec/integration/consumer_spec.rb +1 -0
  30. data/spec/lib/pact/consumer_contract/active_support_support_spec.rb +43 -0
  31. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +1 -1
  32. data/spec/lib/pact/consumer_contract/interaction_spec.rb +3 -3
  33. data/spec/lib/pact/matchers/matchers_spec.rb +10 -1
  34. data/spec/lib/pact/verification_task_spec.rb +102 -79
  35. data/spec/spec_helper.rb +11 -0
  36. data/tasks/pact-test.rake +1 -1
  37. data/tasks/spec.rake +8 -0
  38. metadata +40 -23
  39. data/lib/pact/json_warning.rb +0 -32
  40. 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 as_json
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(as_json_without_body, actual_request.as_json_without_body)
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!
@@ -13,10 +13,14 @@ module Pact
13
13
  name
14
14
  end
15
15
 
16
- def as_json options = {}
16
+ def to_hash
17
17
  {name: name}
18
18
  end
19
19
 
20
+ def as_json options = {}
21
+ to_hash
22
+ end
23
+
20
24
  def self.from_hash hash
21
25
  new(symbolize_keys(hash))
22
26
  end
@@ -13,10 +13,14 @@ module Pact
13
13
  name
14
14
  end
15
15
 
16
- def as_json options = {}
16
+ def to_hash
17
17
  {name: name}
18
18
  end
19
19
 
20
+ def as_json options = {}
21
+ to_hash
22
+ end
23
+
20
24
  def self.from_hash hash
21
25
  new(symbolize_keys(hash))
22
26
  end
@@ -31,7 +31,7 @@ module Pact
31
31
  end
32
32
 
33
33
  def regexp_diff regexp, actual, options
34
- if actual != nil && regexp.match(actual)
34
+ if actual.is_a?(String) && regexp.match(actual)
35
35
  {}
36
36
  else
37
37
  {expected: regexp, actual: actual}
@@ -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
- module Provider
11
- class PactSpecRunner
12
-
13
- include Pact::Provider::RSpec::ClassMethods
14
-
15
- attr_reader :spec_definitions
16
- attr_reader :options
17
- attr_reader :output
18
-
19
- def initialize spec_definitions, options = {}
20
- @spec_definitions = spec_definitions
21
- @options = options
22
- @results = nil
23
- end
24
-
25
- def run
26
- initialize_specs
27
- configure_rspec
28
- run_specs
29
- end
30
-
31
- private
32
-
33
- def require_pact_helper spec_definition
34
- if spec_definition[:support_file]
35
- $stderr.puts "Specifying a support_file is deprecated. Please create a pact_helper.rb instead."
36
- require spec_definition[:support_file]
37
- else
38
- require 'pact/provider/client_project_pact_helper'
39
- end
40
- end
41
-
42
- def initialize_specs
43
- spec_definitions.each do | spec_definition |
44
- require_pact_helper spec_definition
45
- options = {consumer: spec_definition[:consumer], save_pactfile_to_tmp: true}
46
- honour_pactfile spec_definition[:uri], options
47
- end
48
- end
49
-
50
- def configure_rspec
51
- config = ::RSpec.configuration
52
- config.color = true
53
-
54
- unless options[:silent]
55
- config.error_stream = $stderr
56
- config.output_stream = $stdout
57
- end
58
-
59
- formatter = ::RSpec::Core::Formatters::DocumentationFormatter.new(config.output)
60
- @json_formatter = ::RSpec::Core::Formatters::JsonFormatter.new(StringIO.new)
61
- reporter = ::RSpec::Core::Reporter.new(formatter, @json_formatter)
62
- config.instance_variable_set(:@reporter, reporter)
63
- end
64
-
65
- def run_specs
66
- config = ::RSpec.configuration
67
- world = ::RSpec::world
68
- exit_code = config.reporter.report(world.example_count, nil) do |reporter|
69
- begin
70
- config.run_hook(:before, :suite)
71
- world.example_groups.ordered.map {|g| g.run(reporter)}.all? ? 0 : config.failure_exit_code
72
- ensure
73
- config.run_hook(:after, :suite)
74
- end
75
- end
76
- @output = @json_formatter.output_hash
77
- exit_code
78
- end
79
- end
80
- end
81
- end
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
@@ -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
- describe "Pact in #{pactfile_uri}" do
26
- consumer_contract = Pact::ConsumerContract.from_json(read_pact_from(pactfile_uri, options))
27
- honour_consumer_contract consumer_contract, options
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.interactions.each do |interaction|
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
- pact = open(uri) { | file | file.read }
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 as_json
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
@@ -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 as_json_without_body
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
@@ -13,13 +13,17 @@ module Pact
13
13
  @contents = contents
14
14
  end
15
15
 
16
- def as_json
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
- To create a rake pact:verify:<something> task
7
+ To create a rake pact:verify:<something> task
8
8
 
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'
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
- end
12
+ end
13
13
 
14
- The pact.uri may be a local file system path or a remote URL.
14
+ The pact.uri may be a local file system path or a remote URL.
15
15
 
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)
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
- Eg.
20
+ Eg.
21
21
 
22
- require 'spec_helper'
23
- require 'provider_states_for_my_consumer'
22
+ require 'spec_helper'
23
+ require 'provider_states_for_my_consumer'
24
24
 
25
- Pact.service_provider "My Provider" do
26
- app { TestApp.new }
27
- end
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
- class VerificationTask < ::Rake::TaskLib
34
- attr_reader :pact_spec_config
35
-
36
- include Pact::TaskHelper
37
- def initialize(name)
38
- @pact_spec_config = []
39
- @name = name
40
- yield self
41
- rake_task
42
- end
43
-
44
- def uri(uri, options = {})
45
- @pact_spec_config << {uri: uri, support_file: options[:support_file]}
46
- end
47
-
48
- private
49
-
50
- attr_reader :name
51
-
52
- def parse_pactfile config
53
- Pact::ConsumerContract.from_uri config[:uri]
54
- end
55
-
56
- def publish_report config, output, result, provider_ref, reports_dir
57
- consumer_contract = parse_pactfile config
58
- #TODO - when checking out a historical version, provider ref will be prod, however it will think it is head. Fix this!!!!
59
- report = Provider::VerificationReport.new(
60
- :result => result,
61
- :output => output,
62
- :consumer => {:name => consumer_contract.consumer.name, :ref => name},
63
- :provider => {:name => consumer_contract.provider.name, :ref => provider_ref}
64
- )
65
-
66
- FileUtils.mkdir_p reports_dir
67
- File.open("#{reports_dir}/#{report.report_file_name}", "w") { |file| file << JSON.pretty_generate(report) }
68
- end
69
-
70
- def rake_task
71
- namespace :pact do
72
- desc "Verify provider against the consumer pacts for #{name}"
73
- task "verify:#{name}" do
74
-
75
- exit_statuses = pact_spec_config.collect do | config |
76
- #TODO: Change this to accept the ConsumerContract that is already parsed, so we don't make the same request twice
77
- pact_spec_runner = Provider::PactSpecRunner.new([config])
78
- exit_status = pact_spec_runner.run
79
- publish_report config, pact_spec_runner.output, exit_status == 0, 'head', Pact.configuration.reports_dir
80
- exit_status
81
- end
82
-
83
- handle_verification_failure do
84
- exit_statuses.count{ | status | status != 0 }
85
- end
86
- end
87
- end
88
- end
89
- end
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