pact 1.0.15 → 1.0.18

Sign up to get free protection for your applications and to get access to all the features.
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