pact 1.1.1 → 1.2.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/CHANGELOG.md +7 -0
  2. data/Gemfile.lock +42 -39
  3. data/README.md +2 -0
  4. data/documentation/configuration.md +2 -2
  5. data/documentation/faq.md +5 -7
  6. data/documentation/provider-states.md +2 -10
  7. data/example/animal-service/spec/service_consumers/pact_helper.rb +0 -4
  8. data/lib/pact/consumer/configuration/mock_service.rb +2 -0
  9. data/lib/pact/consumer/consumer_contract_builder.rb +58 -58
  10. data/lib/pact/consumer/mock_service/app.rb +4 -1
  11. data/lib/pact/consumer/mock_service/interaction_replay.rb +11 -3
  12. data/lib/pact/consumer/mock_service/missing_interactions_get.rb +2 -2
  13. data/lib/pact/consumer/mock_service/pact_post.rb +33 -0
  14. data/lib/pact/consumer/mock_service/verification_get.rb +1 -2
  15. data/lib/pact/consumer/mock_service_client.rb +14 -5
  16. data/lib/pact/consumer/mock_service_interaction_expectation.rb +1 -1
  17. data/lib/pact/consumer/spec_hooks.rb +2 -0
  18. data/lib/pact/consumer/world.rb +25 -0
  19. data/lib/pact/consumer_contract/consumer_contract.rb +1 -1
  20. data/lib/pact/consumer_contract/consumer_contract_writer.rb +84 -0
  21. data/lib/pact/consumer_contract/file_name.rb +7 -1
  22. data/lib/pact/provider/pact_helper_locator.rb +1 -1
  23. data/lib/pact/provider/pact_spec_runner.rb +3 -9
  24. data/lib/pact/provider/rspec/{formatter.rb → formatter_rspec_2.rb} +2 -2
  25. data/lib/pact/provider/rspec/formatter_rspec_3.rb +96 -0
  26. data/lib/pact/provider/rspec/matchers.rb +79 -19
  27. data/lib/pact/provider/rspec.rb +3 -1
  28. data/lib/pact/provider/state/provider_state_configured_modules.rb +6 -0
  29. data/lib/pact/provider/state/provider_state_manager.rb +3 -3
  30. data/lib/pact/provider/world.rb +2 -8
  31. data/lib/pact/rspec.rb +32 -0
  32. data/lib/pact/version.rb +1 -1
  33. data/pact.gemspec +3 -3
  34. data/spec/features/consumption_spec.rb +6 -1
  35. data/spec/integration/consumer_spec.rb +16 -9
  36. data/spec/integration/pact/consumer_configuration_spec.rb +7 -22
  37. data/spec/lib/pact/app_spec.rb +5 -5
  38. data/spec/lib/pact/configuration_spec.rb +1 -1
  39. data/spec/lib/pact/consumer/app_manager_spec.rb +3 -3
  40. data/spec/lib/pact/consumer/configuration_spec.rb +11 -8
  41. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +3 -101
  42. data/spec/lib/pact/consumer/interaction_builder_spec.rb +8 -8
  43. data/spec/lib/pact/consumer/mock_service/app_spec.rb +2 -2
  44. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +2 -2
  45. data/spec/lib/pact/consumer/mock_service/interaction_replay_spec.rb +12 -0
  46. data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +2 -2
  47. data/spec/lib/pact/consumer/mock_service_client_spec.rb +88 -0
  48. data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +4 -4
  49. data/spec/lib/pact/consumer_contract/consumer_contract_spec.rb +18 -18
  50. data/spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb +111 -0
  51. data/spec/lib/pact/provider/configuration/pact_verification_spec.rb +1 -1
  52. data/spec/lib/pact/provider/pact_helper_locator_spec.rb +2 -2
  53. data/spec/lib/pact/provider/rspec/{formatter_spec.rb → formatter_rspec_2_spec.rb} +14 -4
  54. data/spec/lib/pact/provider/rspec/formatter_rspec_3_spec.rb +72 -0
  55. data/spec/lib/pact/provider/rspec_spec.rb +3 -0
  56. data/spec/lib/pact/provider/state/provider_state_manager_spec.rb +1 -1
  57. data/spec/lib/pact/provider/state/provider_state_proxy_spec.rb +4 -4
  58. data/spec/lib/pact/provider/state/provider_state_spec.rb +7 -7
  59. data/spec/lib/pact/provider/world_spec.rb +8 -8
  60. data/spec/lib/pact/tasks/verification_task_spec.rb +2 -2
  61. data/spec/spec_helper.rb +2 -4
  62. data/spec/support/factories.rb +13 -13
  63. data/spec/support/spec_support.rb +10 -0
  64. data/spec/support/stubbing_using_allow.rb +0 -4
  65. data/tasks/pact-test.rake +12 -8
  66. metadata +27 -24
  67. data/bethtest.rb +0 -30
  68. data/lib/pact/provider/rspec/silent_json_formatter.rb +0 -18
  69. data/spec/support/stubbing.rb +0 -26
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@ Do this to generate your change history
2
2
 
3
3
  git log --pretty=format:' * %h - %s (%an, %ad)'
4
4
 
5
+ ### 1.2.1.rc1 (13 June 2014)
6
+
7
+ * b8d1586 - Making RSpec::Mocks::ExampleMethods available in set_up and tear_down, so the allow method is available without configuration.
8
+ * 5ec17aa - Updating code to work with RSpec 3 (bethesque, Wed Jun 11 22:09:30 2014 +1000)
9
+ * 1227b71 - RESTifying the endpoints on the mock server (bethesque, Tue Jun 10 10:06:33 2014 +1000)
10
+ * 57409b5 - Moved pact writing into mock server, so the mock server can be reused by consumer libraries in other languages. (bethesque, Thu Jun
11
+
5
12
  ### 1.1.1 (3 June 2014)
6
13
 
7
14
  * 503a3f4 - The pact verify executable now adds lib to the load path before requiring the pact_helper. (bethesque, Tue Jun 3 09:26:46 2014 +1000)
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pact (1.1.1)
4
+ pact (1.2.1.rc1)
5
5
  awesome_print (~> 1.1)
6
6
  find_a_port (~> 1.0.1)
7
7
  json
8
8
  rack-test (~> 0.6.2)
9
9
  randexp (~> 0.1.7)
10
- rspec (~> 2.12)
10
+ rspec (>= 2.14)
11
11
  term-ansicolor (~> 1.0)
12
12
  thor
13
13
  webrick
@@ -15,57 +15,60 @@ PATH
15
15
  GEM
16
16
  remote: https://rubygems.org/
17
17
  specs:
18
- activesupport (4.0.0)
19
- i18n (~> 0.6, >= 0.6.4)
20
- minitest (~> 4.2)
21
- multi_json (~> 1.3)
18
+ activesupport (4.1.1)
19
+ i18n (~> 0.6, >= 0.6.9)
20
+ json (~> 1.7, >= 1.7.7)
21
+ minitest (~> 5.1)
22
22
  thread_safe (~> 0.1)
23
- tzinfo (~> 0.3.37)
24
- addressable (2.3.5)
25
- atomic (1.1.14)
23
+ tzinfo (~> 1.1)
24
+ addressable (2.3.6)
26
25
  awesome_print (1.2.0)
27
- coderay (1.0.9)
28
- crack (0.4.1)
29
- safe_yaml (~> 0.9.0)
26
+ coderay (1.1.0)
27
+ crack (0.4.2)
28
+ safe_yaml (~> 1.0.0)
30
29
  diff-lcs (1.2.5)
31
- fakefs (0.4.2)
30
+ fakefs (0.5.2)
31
+ faraday (0.9.0)
32
+ multipart-post (>= 1.2, < 3)
32
33
  find_a_port (1.0.1)
33
- hashie (2.0.5)
34
- i18n (0.6.5)
34
+ hashie (2.1.1)
35
+ i18n (0.6.9)
35
36
  json (1.8.1)
36
37
  method_source (0.8.2)
37
- minitest (4.7.5)
38
- multi_json (1.8.2)
39
- pry (0.9.12.2)
40
- coderay (~> 1.0.5)
41
- method_source (~> 0.8)
38
+ minitest (5.3.4)
39
+ multipart-post (2.0.0)
40
+ pry (0.10.0)
41
+ coderay (~> 1.1.0)
42
+ method_source (~> 0.8.1)
42
43
  slop (~> 3.4)
43
44
  rack (1.5.2)
44
45
  rack-test (0.6.2)
45
46
  rack (>= 1.0)
46
47
  rake (10.0.4)
47
48
  randexp (0.1.7)
48
- rspec (2.99.0)
49
- rspec-core (~> 2.99.0)
50
- rspec-expectations (~> 2.99.0)
51
- rspec-mocks (~> 2.99.0)
52
- rspec-core (2.99.0)
53
- rspec-expectations (2.99.0)
54
- diff-lcs (>= 1.1.3, < 2.0)
55
- rspec-fire (1.3.0)
56
- rspec (>= 2.11, < 4)
57
- rspec-mocks (2.99.0)
58
- safe_yaml (0.9.5)
59
- slop (3.4.6)
49
+ rspec (3.0.0)
50
+ rspec-core (~> 3.0.0)
51
+ rspec-expectations (~> 3.0.0)
52
+ rspec-mocks (~> 3.0.0)
53
+ rspec-core (3.0.0)
54
+ rspec-support (~> 3.0.0)
55
+ rspec-expectations (3.0.0)
56
+ diff-lcs (>= 1.2.0, < 2.0)
57
+ rspec-support (~> 3.0.0)
58
+ rspec-mocks (3.0.1)
59
+ rspec-support (~> 3.0.0)
60
+ rspec-support (3.0.0)
61
+ safe_yaml (1.0.3)
62
+ slop (3.5.0)
60
63
  term-ansicolor (1.3.0)
61
64
  tins (~> 1.0)
62
65
  thor (0.19.1)
63
- thread_safe (0.1.3)
64
- atomic
66
+ thread_safe (0.3.4)
65
67
  tins (1.3.0)
66
- tzinfo (0.3.38)
67
- webmock (1.9.3)
68
- addressable (>= 2.2.7)
68
+ tzinfo (1.2.1)
69
+ thread_safe (~> 0.1)
70
+ webmock (1.18.0)
71
+ addressable (>= 2.3.6)
69
72
  crack (>= 0.3.2)
70
73
  webrick (1.3.1)
71
74
 
@@ -75,9 +78,9 @@ PLATFORMS
75
78
  DEPENDENCIES
76
79
  activesupport
77
80
  fakefs (~> 0.4)
81
+ faraday
78
82
  hashie (~> 2.0)
79
83
  pact!
80
84
  pry
81
85
  rake (~> 10.0.3)
82
- rspec-fire
83
- webmock (~> 1.9.3)
86
+ webmock (~> 1.18.0)
data/README.md CHANGED
@@ -274,6 +274,8 @@ As in all things, there are good ways to implement Pacts, and there are not so g
274
274
 
275
275
  ## Links
276
276
 
277
+ [Simplifying microservices testing with pacts](http://dius.com.au/2014/05/19/simplifying-micro-service-testing-with-pacts/) - Ron Holshausen (one of the original pact authors)
278
+
277
279
  [Pact specification](https://github.com/bethesque/pact-specification)
278
280
 
279
281
  [Integrated tests are a scam](http://vimeo.com/80533536) - J.B. Rainsberger
@@ -158,9 +158,9 @@ Pact uses RSpec and Rack::Test to create dynamic specs based on the pact files.
158
158
 
159
159
  ```ruby
160
160
  Pact.configure do | config |
161
- config.include RSpec::Mocks::ExampleMethods
161
+ config.include MyTestHelperMethods
162
162
  end
163
163
  ```
164
164
 
165
- To make modules available in the provider state set_up and tear_down blocks, include them in the configuration as shown below. One common use of this is to include RSpec::Mocks::ExampleMethods to make the `allow()` method available.
165
+ To make modules available in the provider state set_up and tear_down blocks, include them in the configuration as shown below. One common use of this to include factory methods for setting up data so that the provider states file doesn't get too bloated.
166
166
 
data/documentation/faq.md CHANGED
@@ -20,9 +20,9 @@ Unlike Webmock:
20
20
 
21
21
  ### How can I handle versioning?
22
22
 
23
- Consumer driven contracts to some extent allows you to do away with versioning. As long as all your contract tests pass, you should be able to deploy changes without versioning the API. If you need to make a breaking change to a provider, you can do it in a multiple step process - add the new fields/endpoints to the provider and deploy. Update the consumers to use the new fields, then deploy. Remove the old fields/endpoints from the provider and deploy. At each step of the process, all the contract tests remain green.
23
+ Consumer driven contracts to some extent allows you to do away with versioning. As long as all your contract tests pass, you should be able to deploy changes without versioning the API. If you need to make a breaking change to a provider, you can do it in a multiple step process - add the new fields/endpoints to the provider and deploy. Update the consumers to use the new fields/endpoints, then deploy. Remove the old fields/endpoints from the provider and deploy. At each step of the process, all the contract tests remain green.
24
24
 
25
- Using a [Pact Broker]((https://github.com/bethesque/pact_broker), you can tag the production version of a pact when you make a release of a consumer. Then, any changes that you make to the provider can be checked agains the production version of the pact, as well as the latest version, to ensure backward compatiblity.
25
+ Using a [Pact Broker](https://github.com/bethesque/pact_broker), you can tag the production version of a pact when you make a release of a consumer. Then, any changes that you make to the provider can be checked agains the production version of the pact, as well as the latest version, to ensure backward compatiblity.
26
26
 
27
27
  If you need to support multiple versions of the provider API concurrently, then you will probably be specifying which version your consumer uses by setting a header, or using a different URL component. As these are actually different requests, the interactions can be verified in the same pact without any problems.
28
28
 
@@ -70,14 +70,12 @@ end
70
70
 
71
71
  ### Should the database or any other part of the provider be stubbed?
72
72
 
73
- This is a hotly debated issue.
74
-
75
73
  The pact authors' experience with using pacts to test microservices has been that using the set_up hooks to populate the database, and running pact:verify with all the real provider code has worked very well, and gives us full confidence that the end to end scenario will work in the deployed code.
76
74
 
77
- However, if you have a large and complex provider, you might decide to stub some of your application code. You will definitly need to stub calls to downstream systems. Make sure, when you stub, that you don't stub the code that actually parses the requests, because otherwise the consumer could be sending absolute rubbish, and you won't be checking it.
75
+ However, if you have a large and complex provider, you might decide to stub some of your application code. You will definitly need to stub calls to downstream systems or to set up error scenarios. Make sure, if you stub, that you don't stub the code that actually parses the request and pulls the expected data out, because otherwise the consumer could be sending absolute rubbish, and the pact:verify won't fail because that code won't get executed. If the validation happens when you insert a record into the datasource, either don't stub anything, or rethink your validation code.
78
76
 
79
- ### Why are the pacts generated and not hand coded?
77
+ ### Why are the pacts generated and not static?
80
78
 
81
79
  * Maintainability: Pact is "contract by example", and the examples may involve large quantities of JSON. Maintaining the JSON files by hand would be both time consuming and error prone. By dynamically creating the pacts, you have the option to keep your expectations in fixture files, or to generate them from your domain (the recommended approach, as it ensures your domain objects and their JSON representations in the pacts can never get out of sync).
82
80
 
83
- * Provider states: Dynamically setting expectations on the mock server allows the use of provider states, meaning you can make the same request more than once, with different expected responses, allowing you to properly test all your code paths.
81
+ * Provider states: Dynamically setting expectations on the mock server allows the use of provider states, meaning you can make the same request in different tests, with different expected responses. This allows you to properly test all the code paths in your consumer (eg. with different response codes, or different states of the resource). If all the interactions were loaded at start up from a static file, the mock server wouldn't know which response to return. See this [gist](https://gist.github.com/bethesque/7fa8947c107f92ace9a4) as an example.
@@ -158,18 +158,10 @@ end
158
158
 
159
159
  ### Including modules for use in set_up and tear_down
160
160
 
161
- To use RSpec's `allow()` syntax, include `RSpec::Mocks::ExampleMethods` in the configuration.
161
+ Any modules included this way will be available in the set_up and tear_down blocks. One common use of this to include factory methods for setting up data so that the provider states file doesn't get too bloated.
162
162
 
163
163
  ```ruby
164
164
  Pact.configure do | config |
165
- config.include RSpec::Mocks::ExampleMethods
166
- end
167
- ```
168
-
169
- Any modules included this way will be available in the set_up and tear_down blocks eg. test data helper methods.
170
-
171
- ```ruby
172
- Pact.configure do | config |
173
- config.include MyFactoryMethods
165
+ config.include MyTestHelperMethods
174
166
  end
175
167
  ```
@@ -2,10 +2,6 @@ require 'pact/provider/rspec'
2
2
 
3
3
  require "./spec/service_consumers/provider_states_for_zoo_app"
4
4
 
5
- Pact.configure do | config |
6
- config.include RSpec::Mocks::ExampleMethods
7
- end
8
-
9
5
  Pact.service_provider 'Animal Service' do
10
6
 
11
7
  honours_pact_with "Zoo App" do
@@ -1,6 +1,7 @@
1
1
  require 'pact/consumer/app_manager'
2
2
  require 'pact/consumer/consumer_contract_builder'
3
3
  require 'pact/consumer/consumer_contract_builders'
4
+ require 'pact/consumer/world'
4
5
 
5
6
  module Pact
6
7
  module Consumer
@@ -78,6 +79,7 @@ module Pact
78
79
  Pact::Consumer::ConsumerContractBuilders.send(:define_method, @name.to_sym) do
79
80
  consumer_contract_builder
80
81
  end
82
+ Pact.consumer_world.add_consumer_contract_builder consumer_contract_builder
81
83
  end
82
84
 
83
85
  def validate
@@ -2,7 +2,7 @@ require 'uri'
2
2
  require 'json/add/regexp'
3
3
  require 'pact/logging'
4
4
  require 'pact/consumer/mock_service_client'
5
- require_relative 'interactions_filter'
5
+ require 'pact/consumer/interaction_builder'
6
6
 
7
7
  module Pact
8
8
  module Consumer
@@ -15,13 +15,12 @@ module Pact
15
15
 
16
16
  def initialize(attributes)
17
17
  @interaction_builder = nil
18
- @mock_service_client = MockServiceClient.new(attributes[:provider_name], attributes[:port])
19
- @consumer_contract = Pact::ConsumerContract.new(
20
- :consumer => ServiceConsumer.new(name: attributes[:consumer_name]),
21
- :provider => ServiceProvider.new(name: attributes[:provider_name])
22
- )
23
- @consumer_contract.interactions = interactions_for_new_consumer_contract(attributes[:pactfile_write_mode])
24
- @interactions_filter = filter(@consumer_contract.interactions, attributes[:pactfile_write_mode])
18
+ @consumer_contract_details = {
19
+ consumer: {name: attributes[:consumer_name]},
20
+ provider: {name: attributes[:provider_name]},
21
+ pactfile_write_mode: attributes[:pactfile_write_mode].to_s
22
+ }
23
+ @mock_service_client = MockServiceClient.new(attributes[:port])
25
24
  @mock_service_base_url = "http://localhost:#{attributes[:port]}"
26
25
  end
27
26
 
@@ -52,6 +51,10 @@ module Pact
52
51
  mock_service_client.log msg
53
52
  end
54
53
 
54
+ def write_pact
55
+ mock_service_client.write_pact @consumer_contract_details
56
+ end
57
+
55
58
  def wait_for_interactions options
56
59
  wait_max_seconds = options.fetch(:wait_max_seconds, 3)
57
60
  poll_interval = options.fetch(:poll_interval, 0.1)
@@ -59,65 +62,62 @@ module Pact
59
62
  end
60
63
 
61
64
  def handle_interaction_fully_defined interaction
62
- interactions_filter << interaction
63
65
  mock_service_client.add_expected_interaction interaction #TODO: What will happen if duplicate added?
64
- consumer_contract.update_pactfile
65
66
  self.interaction_builder = nil
66
67
  end
67
68
 
68
69
  private
69
70
 
70
71
  attr_reader :mock_service_client
71
- attr_reader :interactions_filter
72
72
  attr_writer :interaction_builder
73
73
 
74
- def interactions_for_new_consumer_contract pactfile_write_mode
75
- pactfile_write_mode == :update ? existing_interactions : []
76
- end
77
-
78
- def filter interactions, pactfile_write_mode
79
- if pactfile_write_mode == :update
80
- UpdatableInteractionsFilter.new(interactions)
81
- else
82
- DistinctInteractionsFilter.new(interactions)
83
- end
84
- end
85
-
86
- def warn_and_stderr msg
87
- Pact.configuration.error_stream.puts msg
88
- logger.warn msg
89
- end
90
-
91
- def info_and_puts msg
92
- $stdout.puts msg
93
- logger.info msg
94
- end
95
-
96
- def existing_interactions
97
- interactions = []
98
- if pactfile_exists?
99
- begin
100
- interactions = existing_consumer_contract.interactions
101
- info_and_puts "*****************************************************************************"
102
- info_and_puts "Updating existing file .#{consumer_contract.pactfile_path.gsub(Dir.pwd, '')} as config.pactfile_write_mode is :update"
103
- info_and_puts "Only interactions defined in this test run will be updated."
104
- info_and_puts "As interactions are identified by description and provider state, pleased note that if either of these have changed, the old interactions won't be removed from the pact file until the specs are next run with :pactfile_write_mode => :overwrite."
105
- info_and_puts "*****************************************************************************"
106
- rescue StandardError => e
107
- warn_and_stderr "Could not load existing consumer contract from #{consumer_contract.pactfile_path} due to #{e}"
108
- warn_and_stderr "Creating a new file."
109
- end
110
- end
111
- interactions
112
- end
113
-
114
- def pactfile_exists?
115
- File.exist?(consumer_contract.pactfile_path)
116
- end
117
-
118
- def existing_consumer_contract
119
- Pact::ConsumerContract.from_uri(consumer_contract.pactfile_path)
120
- end
74
+ # def interactions_for_new_consumer_contract pactfile_write_mode
75
+ # pactfile_write_mode == :update ? existing_interactions : []
76
+ # end
77
+
78
+ # def filter interactions, pactfile_write_mode
79
+ # if pactfile_write_mode == :update
80
+ # UpdatableInteractionsFilter.new(interactions)
81
+ # else
82
+ # DistinctInteractionsFilter.new(interactions)
83
+ # end
84
+ # end
85
+
86
+ # def warn_and_stderr msg
87
+ # Pact.configuration.error_stream.puts msg
88
+ # logger.warn msg
89
+ # end
90
+
91
+ # def info_and_puts msg
92
+ # $stdout.puts msg
93
+ # logger.info msg
94
+ # end
95
+
96
+ # def existing_interactions
97
+ # interactions = []
98
+ # if pactfile_exists?
99
+ # begin
100
+ # interactions = existing_consumer_contract.interactions
101
+ # info_and_puts "*****************************************************************************"
102
+ # info_and_puts "Updating existing file .#{consumer_contract.pactfile_path.gsub(Dir.pwd, '')} as config.pactfile_write_mode is :update"
103
+ # info_and_puts "Only interactions defined in this test run will be updated."
104
+ # info_and_puts "As interactions are identified by description and provider state, pleased note that if either of these have changed, the old interactions won't be removed from the pact file until the specs are next run with :pactfile_write_mode => :overwrite."
105
+ # info_and_puts "*****************************************************************************"
106
+ # rescue StandardError => e
107
+ # warn_and_stderr "Could not load existing consumer contract from #{consumer_contract.pactfile_path} due to #{e}"
108
+ # warn_and_stderr "Creating a new file."
109
+ # end
110
+ # end
111
+ # interactions
112
+ # end
113
+
114
+ # def pactfile_exists?
115
+ # File.exist?(consumer_contract.pactfile_path)
116
+ # end
117
+
118
+ # def existing_consumer_contract
119
+ # Pact::ConsumerContract.from_uri(consumer_contract.pactfile_path)
120
+ # end
121
121
 
122
122
  end
123
123
  end
@@ -11,6 +11,7 @@ require 'pact/consumer/mock_service/interaction_replay'
11
11
  require 'pact/consumer/mock_service/missing_interactions_get'
12
12
  require 'pact/consumer/mock_service/verification_get'
13
13
  require 'pact/consumer/mock_service/log_get'
14
+ require 'pact/consumer/mock_service/pact_post'
14
15
 
15
16
  AwesomePrint.defaults = {
16
17
  indent: -2,
@@ -28,13 +29,15 @@ module Pact
28
29
  interaction_list = InteractionList.new
29
30
 
30
31
  @name = options.fetch(:name, "MockService")
32
+ interactions = []
31
33
  @handlers = [
32
34
  MissingInteractionsGet.new(@name, @logger, interaction_list),
33
35
  VerificationGet.new(@name, @logger, interaction_list, log_description),
34
36
  InteractionPost.new(@name, @logger, interaction_list),
35
37
  InteractionDelete.new(@name, @logger, interaction_list),
36
38
  LogGet.new(@name, @logger),
37
- InteractionReplay.new(@name, @logger, interaction_list)
39
+ PactPost.new(@name, @logger, interactions),
40
+ InteractionReplay.new(@name, @logger, interaction_list, interactions)
38
41
  ]
39
42
  end
40
43
 
@@ -1,6 +1,8 @@
1
1
  require 'pact/matchers'
2
2
  require 'pact/consumer/mock_service/rack_request_helper'
3
3
  require 'pact/consumer/mock_service/interaction_mismatch'
4
+ require 'pact/consumer_contract'
5
+ require 'pact/consumer/interactions_filter'
4
6
 
5
7
  module Pact
6
8
  module Consumer
@@ -9,12 +11,13 @@ module Pact
9
11
  include Pact::Matchers
10
12
  include RackRequestHelper
11
13
 
12
- attr_accessor :name, :logger, :interaction_list
14
+ attr_accessor :name, :logger, :interaction_list, :interactions
13
15
 
14
- def initialize name, logger, interaction_list
16
+ def initialize name, logger, interaction_list, interactions
15
17
  @name = name
16
18
  @logger = logger
17
19
  @interaction_list = interaction_list
20
+ @interactions = DistinctInteractionsFilter.new(interactions)
18
21
  end
19
22
 
20
23
  def match? env
@@ -27,6 +30,10 @@ module Pact
27
30
 
28
31
  private
29
32
 
33
+ def add_verified_interaction interaction
34
+ interactions << interaction
35
+ end
36
+
30
37
  def find_response request_hash
31
38
  actual_request = Request::Actual.from_hash(request_hash)
32
39
  logger.info "Received request #{actual_request.method_and_path}"
@@ -51,6 +58,7 @@ module Pact
51
58
 
52
59
  def handle_matched_interaction interaction
53
60
  interaction_list.register_matched interaction
61
+ add_verified_interaction interaction
54
62
  response = response_from(interaction.response)
55
63
  logger.info "Found matching response for #{interaction.request.method_and_path}"
56
64
  logger.debug pretty_generate(interaction.response)
@@ -110,7 +118,7 @@ module Pact
110
118
  end
111
119
 
112
120
  def response_from response
113
- [response['status'], (response['headers'] || {}).to_hash, [render_body(response['body'])]]
121
+ [response['status'], (response['headers'] || {}).to_hash, [render_body(Pact::Reification.from_term(response['body']))]]
114
122
  end
115
123
 
116
124
  def render_body body
@@ -12,7 +12,7 @@ module Pact
12
12
  end
13
13
 
14
14
  def request_path
15
- '/number_of_missing_interactions'
15
+ '/interactions/missing'
16
16
  end
17
17
 
18
18
  def request_method
@@ -22,7 +22,7 @@ module Pact
22
22
  def respond env
23
23
  number_of_missing_interactions = @interaction_list.missing_interactions.size
24
24
  logger.info "Number of missing interactions for mock \"#{name}\" = #{number_of_missing_interactions}"
25
- [200, {}, ["#{number_of_missing_interactions}"]]
25
+ [200, {}, [{size: number_of_missing_interactions}.to_json]]
26
26
  end
27
27
 
28
28
  end
@@ -0,0 +1,33 @@
1
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
2
+ require 'pact/consumer_contract/consumer_contract_writer'
3
+
4
+ module Pact
5
+ module Consumer
6
+ class PactPost < MockServiceAdministrationEndpoint
7
+
8
+ attr_accessor :consumer_contract, :interactions
9
+
10
+ def initialize name, logger, interactions
11
+ super name, logger
12
+ @interactions = interactions
13
+ end
14
+
15
+ def request_path
16
+ '/pact'
17
+ end
18
+
19
+ def request_method
20
+ 'POST'
21
+ end
22
+
23
+ def respond env
24
+ consumer_contract_details = JSON.parse(env['rack.input'].string, symbolize_names: true)
25
+ logger.info "Writing pact with details #{consumer_contract_details}"
26
+ consumer_contract_writer = ConsumerContractWriter.new(consumer_contract_details.merge(interactions: interactions), logger)
27
+ json = consumer_contract_writer.write
28
+
29
+ [200, {'Content-Type' =>'application/json'}, [json]]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -14,7 +14,7 @@ module Pact
14
14
  end
15
15
 
16
16
  def request_path
17
- '/verify'
17
+ '/interactions/verification'
18
18
  end
19
19
 
20
20
  def request_method
@@ -26,7 +26,6 @@ module Pact
26
26
  logger.info "Verifying - interactions matched for example \"#{example_description(env)}\""
27
27
  [200, {'Content-Type' => 'text/plain'}, ['Interactions matched']]
28
28
  else
29
-
30
29
  error_message = FailureMessage.new(interaction_list).to_s
31
30
  logger.warn "Verifying - actual interactions do not match expected interactions for example \"#{example_description(env)}\". \n#{error_message}"
32
31
  logger.warn error_message
@@ -1,3 +1,4 @@
1
+ require 'net/http'
1
2
  require 'pact/consumer/mock_service_interaction_expectation'
2
3
 
3
4
  module Pact
@@ -6,12 +7,12 @@ module Pact
6
7
 
7
8
  MOCK_SERVICE_ADMINISTRATON_HEADERS = {'X-Pact-Mock-Service' => 'true'}
8
9
 
9
- def initialize name, port
10
+ def initialize port
10
11
  @http = Net::HTTP.new('localhost', port)
11
12
  end
12
13
 
13
14
  def verify example_description
14
- response = http.request_get("/verify?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
15
+ response = http.request_get("/interactions/verification?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
15
16
  raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
16
17
  end
17
18
 
@@ -21,8 +22,8 @@ module Pact
21
22
 
22
23
  def wait_for_interactions wait_max_seconds, poll_interval
23
24
  wait_until_true(wait_max_seconds, poll_interval) do
24
- response = http.request_get("/number_of_missing_interactions", MOCK_SERVICE_ADMINISTRATON_HEADERS)
25
- response.body == '0'
25
+ response = http.request_get("/interactions/missing", MOCK_SERVICE_ADMINISTRATON_HEADERS)
26
+ JSON.parse(response.body)['size'] == 0
26
27
  end
27
28
  end
28
29
 
@@ -31,14 +32,22 @@ module Pact
31
32
  end
32
33
 
33
34
  def add_expected_interaction interaction
34
- http.request_post('/interactions', MockServiceInteractionExpectation.new(interaction).to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS)
35
+ response = http.request_post('/interactions', MockServiceInteractionExpectation.new(interaction).to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
36
+ raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
35
37
  end
36
38
 
37
39
  def self.clear_interactions port, example_description
38
40
  Net::HTTP.new("localhost", port).delete("/interactions?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
39
41
  end
40
42
 
43
+ def write_pact pacticipant_details
44
+ response = http.request_post("/pact", pacticipant_details.to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
45
+ raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
46
+ response.body
47
+ end
48
+
41
49
  private
50
+
42
51
  attr_reader :http
43
52
 
44
53
  #todo: in need a better home (where can we move it?)
@@ -16,7 +16,7 @@ module Pact
16
16
  hash[:provider_state] = interaction.provider_state if interaction.provider_state
17
17
  options = interaction.request.options.empty? ? {} : { options: interaction.request.options}
18
18
  hash[:request] = interaction.request.as_json.merge(options)
19
- hash[:response] = Reification.from_term(interaction.response)
19
+ hash[:response] = interaction.response
20
20
  hash
21
21
  end
22
22
 
@@ -1,4 +1,5 @@
1
1
  require 'pact/doc/generate'
2
+ require 'pact/consumer/world'
2
3
 
3
4
  module Pact
4
5
  module Consumer
@@ -24,6 +25,7 @@ module Pact
24
25
  end
25
26
 
26
27
  def after_suite
28
+ Pact.consumer_world.consumer_contract_builders.each { | c | c.write_pact }
27
29
  Pact::Doc::Generate.call
28
30
  Pact::Consumer::AppManager.instance.kill_all
29
31
  Pact::Consumer::AppManager.instance.clear_all
@@ -0,0 +1,25 @@
1
+ module Pact
2
+
3
+ def self.consumer_world
4
+ @consumer_world ||= Pact::Consumer::World.new
5
+ end
6
+
7
+ # internal api, for testing only
8
+ def self.clear_consumer_world
9
+ @consumer_world = nil
10
+ end
11
+
12
+ module Consumer
13
+ class World
14
+
15
+ def consumer_contract_builders
16
+ @consumer_contract_builders ||= []
17
+ end
18
+
19
+ def add_consumer_contract_builder consumer_contract_builder
20
+ consumer_contract_builders << consumer_contract_builder
21
+ end
22
+
23
+ end
24
+ end
25
+ end