pact 0.1.37 → 1.0.0

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 (65) hide show
  1. data/Gemfile.lock +6 -19
  2. data/example/zoo-app/Gemfile +1 -2
  3. data/example/zoo-app/Gemfile.lock +16 -13
  4. data/example/zoo-app/spec/pacts/zoo_app-animal_service.json +6 -6
  5. data/lib/pact/consumer/app_manager.rb +8 -28
  6. data/lib/pact/consumer/consumer_contract_builder.rb +65 -36
  7. data/lib/pact/consumer/dsl.rb +20 -10
  8. data/lib/pact/consumer/interaction_builder.rb +42 -0
  9. data/lib/pact/consumer/interactions_filter.rb +41 -0
  10. data/lib/pact/consumer/mock_service.rb +26 -19
  11. data/lib/pact/consumer/rspec.rb +2 -3
  12. data/lib/pact/consumer/server.rb +90 -0
  13. data/lib/pact/consumer.rb +6 -3
  14. data/lib/pact/consumer_contract/consumer_contract.rb +103 -0
  15. data/lib/pact/consumer_contract/interaction.rb +70 -0
  16. data/lib/pact/consumer_contract/service_consumer.rb +20 -0
  17. data/lib/pact/consumer_contract/service_provider.rb +20 -0
  18. data/lib/pact/consumer_contract.rb +1 -112
  19. data/lib/pact/{producer → provider}/dsl.rb +15 -4
  20. data/lib/pact/{producer → provider}/matchers.rb +0 -0
  21. data/lib/pact/{producer → provider}/pact_spec_runner.rb +2 -2
  22. data/lib/pact/{producer/producer_state.rb → provider/provider_state.rb} +13 -22
  23. data/lib/pact/provider/rspec.rb +128 -1
  24. data/lib/pact/{producer → provider}/test_methods.rb +12 -12
  25. data/lib/pact/{producer.rb → provider.rb} +0 -0
  26. data/lib/pact/verification_task.rb +4 -4
  27. data/lib/pact/version.rb +1 -1
  28. data/lib/pact.rb +1 -1
  29. data/pact.gemspec +1 -2
  30. data/scratchpad.txt +1 -1
  31. data/spec/features/consumption_spec.rb +15 -23
  32. data/spec/features/production_spec.rb +5 -5
  33. data/spec/features/{producer_states → provider_states}/zebras.rb +3 -3
  34. data/spec/integration/pact/consumer_configuration_spec.rb +3 -66
  35. data/spec/integration/pact/provider_configuration_spec.rb +1 -1
  36. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +59 -15
  37. data/spec/lib/pact/consumer/dsl_spec.rb +4 -5
  38. data/spec/lib/pact/consumer/interaction_builder_spec.rb +91 -0
  39. data/spec/lib/pact/consumer/interactions_spec.rb +64 -0
  40. data/spec/lib/pact/consumer/mock_service_spec.rb +2 -6
  41. data/spec/lib/pact/consumer/service_consumer_spec.rb +1 -1
  42. data/spec/lib/pact/{consumer_contract_spec.rb → consumer_contract/consumer_contract_spec.rb} +72 -28
  43. data/spec/lib/pact/{consumer → consumer_contract}/interaction_spec.rb +49 -64
  44. data/spec/lib/pact/{producer/configuration_dsl_spec.rb → provider/dsl_spec.rb} +29 -28
  45. data/spec/lib/pact/{producer/producer_state_spec.rb → provider/provider_state_spec.rb} +17 -17
  46. data/spec/lib/pact/{producer → provider}/rspec_spec.rb +1 -1
  47. data/spec/lib/pact/verification_task_spec.rb +3 -3
  48. data/spec/spec_helper.rb +1 -0
  49. data/spec/support/a_consumer-a_producer.json +1 -1
  50. data/spec/support/a_consumer-a_provider.json +34 -0
  51. data/spec/support/consumer_contract_template.json +26 -0
  52. data/spec/support/factories.rb +78 -0
  53. data/spec/support/pact_rake_support.rb +1 -1
  54. data/spec/support/test_app_fail.json +1 -1
  55. data/spec/support/test_app_pass.json +1 -1
  56. data/tasks/pact-test.rake +3 -3
  57. metadata +38 -45
  58. data/lib/pact/consumer/configuration_dsl.rb +0 -73
  59. data/lib/pact/consumer/interaction.rb +0 -74
  60. data/lib/pact/consumer/run_condor.rb +0 -4
  61. data/lib/pact/consumer/run_mock_contract_service.rb +0 -13
  62. data/lib/pact/consumer/service_consumer.rb +0 -22
  63. data/lib/pact/consumer/service_producer.rb +0 -23
  64. data/lib/pact/producer/configuration_dsl.rb +0 -62
  65. data/lib/pact/producer/rspec.rb +0 -129
data/Gemfile.lock CHANGED
@@ -1,11 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pact (0.1.37)
4
+ pact (2.0.0)
5
5
  awesome_print (~> 1.1.0)
6
- capybara (~> 2.1.0)
7
6
  find_a_port (~> 1.0.1)
8
- hashie (~> 2.0.5)
7
+ hashie (~> 2.0)
9
8
  json
10
9
  rack-test (~> 0.6.2)
11
10
  randexp (~> 0.1.7)
@@ -19,14 +18,8 @@ GEM
19
18
  specs:
20
19
  addressable (2.3.5)
21
20
  awesome_print (1.1.0)
22
- capybara (2.1.0)
23
- mime-types (>= 1.16)
24
- nokogiri (>= 1.3.3)
25
- rack (>= 1.0.0)
26
- rack-test (>= 0.5.4)
27
- xpath (~> 2.0)
28
21
  coderay (1.0.9)
29
- crack (0.4.0)
22
+ crack (0.4.1)
30
23
  safe_yaml (~> 0.9.0)
31
24
  daemons (1.1.9)
32
25
  diff-lcs (1.2.4)
@@ -36,12 +29,8 @@ GEM
36
29
  multipart-post
37
30
  hashie (2.0.5)
38
31
  json (1.8.0)
39
- method_source (0.8.1)
40
- mime-types (1.24)
41
- mini_portile (0.5.1)
32
+ method_source (0.8.2)
42
33
  multipart-post (1.2.0)
43
- nokogiri (1.6.0)
44
- mini_portile (~> 0.5.0)
45
34
  pry (0.9.12.2)
46
35
  coderay (~> 1.0.5)
47
36
  method_source (~> 0.8)
@@ -59,8 +48,8 @@ GEM
59
48
  rspec-expectations (2.14.2)
60
49
  diff-lcs (>= 1.1.3, < 2.0)
61
50
  rspec-mocks (2.14.3)
62
- safe_yaml (0.9.4)
63
- slop (3.4.5)
51
+ safe_yaml (0.9.5)
52
+ slop (3.4.6)
64
53
  thin (1.5.1)
65
54
  daemons (>= 1.0.9)
66
55
  eventmachine (>= 0.12.6)
@@ -69,8 +58,6 @@ GEM
69
58
  webmock (1.9.3)
70
59
  addressable (>= 2.2.7)
71
60
  crack (>= 0.3.2)
72
- xpath (2.0.0)
73
- nokogiri (~> 1.3)
74
61
 
75
62
  PLATFORMS
76
63
  ruby
@@ -1,11 +1,10 @@
1
1
  source 'https://rubygems.org'
2
- source 'http://rea-rubygems/'
3
2
 
4
3
  gem 'bundler', '~> 1.3.0'
5
4
 
6
5
  group :development, :test do
7
6
  gem 'rspec'
8
- gem 'pact'
7
+ gem 'pact', path: '../../'
9
8
  gem 'pry'
10
9
  end
11
10
 
@@ -1,6 +1,20 @@
1
+ PATH
2
+ remote: ../../
3
+ specs:
4
+ pact (0.1.37)
5
+ awesome_print (~> 1.1.0)
6
+ capybara (~> 2.1.0)
7
+ find_a_port (~> 1.0.1)
8
+ hashie (~> 2.0.5)
9
+ json
10
+ rack-test (~> 0.6.2)
11
+ randexp (~> 0.1.7)
12
+ rspec (~> 2.12)
13
+ thin
14
+ thor
15
+
1
16
  GEM
2
17
  remote: https://rubygems.org/
3
- remote: http://rea-rubygems/
4
18
  specs:
5
19
  awesome_print (1.1.0)
6
20
  capybara (2.1.0)
@@ -26,17 +40,6 @@ GEM
26
40
  multi_xml (0.5.5)
27
41
  nokogiri (1.6.0)
28
42
  mini_portile (~> 0.5.0)
29
- pact (0.1.32)
30
- awesome_print (~> 1.1.0)
31
- capybara (~> 2.1.0)
32
- find_a_port (~> 1.0.1)
33
- hashie (~> 2.0.5)
34
- json
35
- rack-test (~> 0.6.2)
36
- randexp (~> 0.1.7)
37
- rspec (~> 2.12)
38
- thin
39
- thor
40
43
  pry (0.9.12.2)
41
44
  coderay (~> 1.0.5)
42
45
  method_source (~> 0.8)
@@ -70,7 +73,7 @@ DEPENDENCIES
70
73
  bundler (~> 1.3.0)
71
74
  httparty
72
75
  json (~> 1.6.8)
73
- pact
76
+ pact!
74
77
  pry
75
78
  rack (~> 1.5.2)
76
79
  rake
@@ -1,5 +1,5 @@
1
1
  {
2
- "producer": {
2
+ "provider": {
3
3
  "name": "Animal Service"
4
4
  },
5
5
  "consumer": {
@@ -25,7 +25,7 @@
25
25
  ]
26
26
  }
27
27
  },
28
- "producer_state": "there are alligators"
28
+ "provider_state": "there are alligators"
29
29
  },
30
30
  {
31
31
  "description": "a request for alligators",
@@ -47,7 +47,7 @@
47
47
  }
48
48
  ]
49
49
  },
50
- "producer_state": "there are alligators"
50
+ "provider_state": "there are alligators"
51
51
  },
52
52
  {
53
53
  "description": "a request for alligator Mary",
@@ -69,7 +69,7 @@
69
69
  }
70
70
  ]
71
71
  },
72
- "producer_state": "there is an alligator named Mary"
72
+ "provider_state": "there is an alligator named Mary"
73
73
  },
74
74
  {
75
75
  "description": "a request for alligators",
@@ -89,12 +89,12 @@
89
89
  "error": "Argh!!!"
90
90
  }
91
91
  },
92
- "producer_state": "an error has occurred"
92
+ "provider_state": "an error has occurred"
93
93
  }
94
94
  ],
95
95
  "metadata": {
96
96
  "pact_gem": {
97
- "version": "0.1.32"
97
+ "version": "0.1.37"
98
98
  }
99
99
  }
100
100
  }
@@ -1,10 +1,10 @@
1
1
  require 'thwait'
2
- require 'capybara'
3
2
 
4
3
  require 'net/http'
5
4
  require 'uri'
6
5
  require 'find_a_port'
7
6
  require 'pact/logging'
7
+ require 'pact/consumer/server'
8
8
 
9
9
  module Pact
10
10
  module Consumer
@@ -110,9 +110,11 @@ module Pact
110
110
  end
111
111
 
112
112
  def kill
113
- logger.info "Killing #{self}"
114
- Process.kill(9, pid)
115
- Process.wait(pid)
113
+ # TODO: need to work out how to kill
114
+ # logger.info "Killing #{self}"
115
+ # Process.kill(9, pid)
116
+ # Process.wait(pid)
117
+ # self.pid = nil
116
118
  self.pid = nil
117
119
  end
118
120
 
@@ -133,31 +135,9 @@ module Pact
133
135
  end
134
136
 
135
137
  def spawn
136
- # following stolen from https://github.com/jwilger/kookaburra
137
138
  logger.info "Starting app #{self}..."
138
- self.pid = fork do
139
- begin
140
- Capybara.server_port = port
141
- Capybara::Server.new(app).boot
142
-
143
- # This ensures that this forked process keeps running, because the
144
- # actual server is started in a thread by Capybara.
145
- ThreadsWait.all_waits(Thread.list)
146
- rescue Exception => e
147
- logger.error "Error starting up #{self}"
148
- $stderr.puts "Error starting up #{self}"
149
- raise e
150
- end
151
- end
152
-
153
-
154
- wait_until do
155
- begin
156
- Net::HTTP.get_response(URI.parse("http://localhost:#{port}/index.html"))
157
- rescue Errno::ECONNREFUSED
158
- false
159
- end
160
- end
139
+ Pact::Server.new(app, port).boot
140
+ self.pid = 'unknown'
161
141
  logger.info "Started with pid #{pid}"
162
142
  end
163
143
 
@@ -2,6 +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
6
 
6
7
  module Pact
7
8
  module Consumer
@@ -11,52 +12,35 @@ module Pact
11
12
  include Pact::Logging
12
13
 
13
14
  attr_reader :consumer_contract
14
- attr_reader :mock_service_client
15
15
 
16
16
  def initialize(attributes)
17
- @interactions = {}
18
- @producer_state = nil
17
+ @interaction_builder = nil
18
+ @mock_service_client = MockServiceClient.new(attributes[:provider_name], attributes[:port])
19
19
  @consumer_contract = Pact::ConsumerContract.new(
20
20
  :consumer => ServiceConsumer.new(name: attributes[:consumer_name]),
21
- :producer => ServiceProducer.new(name: attributes[:producer_name])
21
+ :provider => ServiceProvider.new(name: attributes[:provider_name])
22
22
  )
23
- @mock_service_client = MockServiceClient.new(attributes[:producer_name], attributes[:port])
24
- if attributes[:pactfile_write_mode] == :update && File.exist?(consumer_contract.pactfile_path)
25
- load_existing_interactions
26
- end
27
- end
28
-
29
- def load_existing_interactions
30
- json = File.read(consumer_contract.pactfile_path)
31
- if json.size > 0
32
- existing_consumer_contract = Pact::ConsumerContract.from_json json
33
- existing_consumer_contract.interactions.each do | interaction |
34
- @interactions["#{interaction.description} given #{interaction.producer_state}"] = interaction
35
- end
36
- consumer_contract.interactions = @interactions.values
37
- end
23
+ @consumer_contract.interactions = interactions_for_new_consumer_contract(attributes[:pactfile_write_mode])
24
+ @interactions_filter = filter(@consumer_contract.interactions, attributes[:pactfile_write_mode])
38
25
  end
39
26
 
40
- def given(producer_state)
41
- @producer_state = producer_state
42
- self
27
+ def given(provider_state)
28
+ interaction_builder.given(provider_state)
43
29
  end
44
30
 
45
31
  def upon_receiving(description)
46
- interaction_builder = InteractionBuilder.new(description, @producer_state)
47
- producer = self
48
- interaction_builder.on_interaction_fully_defined do | interaction |
49
- producer.handle_interaction_fully_defined(interaction)
50
- end
51
- @interactions["#{description} given #{@producer_state}"] ||= interaction_builder.interaction
52
- consumer_contract.interactions = @interactions.values
53
- interaction_builder
32
+ interaction_builder.upon_receiving(description)
54
33
  end
55
34
 
56
- def handle_interaction_fully_defined interaction
57
- mock_service_client.add_expected_interaction interaction
58
- @producer_state = nil
59
- consumer_contract.update_pactfile
35
+ def interaction_builder
36
+ @interaction_builder ||=
37
+ begin
38
+ interaction_builder = InteractionBuilder.new
39
+ interaction_builder.on_interaction_fully_defined do | interaction |
40
+ self.handle_interaction_fully_defined(interaction)
41
+ end
42
+ interaction_builder
43
+ end
60
44
  end
61
45
 
62
46
  def verify example_description
@@ -69,10 +53,55 @@ module Pact
69
53
  mock_service_client.wait_for_interactions wait_max_seconds, poll_interval
70
54
  end
71
55
 
56
+ def handle_interaction_fully_defined interaction
57
+ interactions_filter << interaction
58
+ mock_service_client.add_expected_interaction interaction #TODO: What will happen if duplicate added?
59
+ consumer_contract.update_pactfile
60
+ self.interaction_builder = nil
61
+ end
62
+
72
63
  private
73
64
 
74
- def filenamify name
75
- name.downcase.gsub(/\s/, '_')
65
+ attr_reader :mock_service_client
66
+ attr_reader :interactions_filter
67
+ attr_writer :interaction_builder
68
+
69
+ def interactions_for_new_consumer_contract pactfile_write_mode
70
+ pactfile_write_mode == :update ? existing_interactions : []
71
+ end
72
+
73
+ def filter interactions, pactfile_write_mode
74
+ if pactfile_write_mode == :update
75
+ UpdatableInteractionsFilter.new(interactions)
76
+ else
77
+ DistinctInteractionsFilter.new(interactions)
78
+ end
79
+ end
80
+
81
+ def log_and_puts msg
82
+ $stderr.puts msg
83
+ logger.warn msg
84
+ end
85
+
86
+ def existing_interactions
87
+ interactions = []
88
+ if pactfile_exists?
89
+ begin
90
+ interactions = existing_consumer_contract.interactions
91
+ rescue StandardError => e
92
+ log_and_puts "Could not load existing consumer contract from #{consumer_contract.pactfile_path} due to #{e}"
93
+ log_and_puts "Creating a new file."
94
+ end
95
+ end
96
+ interactions
97
+ end
98
+
99
+ def pactfile_exists?
100
+ File.exist?(consumer_contract.pactfile_path)
101
+ end
102
+
103
+ def existing_consumer_contract
104
+ Pact::ConsumerContract.from_json(File.read(consumer_contract.pactfile_path))
76
105
  end
77
106
 
78
107
  end
@@ -3,6 +3,15 @@ require_relative 'consumer_contract_builders'
3
3
  module Pact::Consumer
4
4
  module DSL
5
5
 
6
+ module Configuration
7
+ def add_provider_verification &block
8
+ provider_verifications << block
9
+ end
10
+ def provider_verifications
11
+ @provider_verifications ||= []
12
+ end
13
+ end
14
+
6
15
  def service_consumer name, &block
7
16
  ServiceConsumerDSL.new(name, &block).create_service_consumer
8
17
  end
@@ -11,7 +20,7 @@ module Pact::Consumer
11
20
 
12
21
  def initialize name, &block
13
22
  @name = name
14
- consumer = Pact::Consumer::ServiceConsumer.new name: @name
23
+ consumer = Pact::ServiceConsumer.new name: @name
15
24
  @app = nil
16
25
  @port = nil
17
26
  instance_eval(&block)
@@ -31,7 +40,7 @@ module Pact::Consumer
31
40
  end
32
41
 
33
42
  def has_pact_with service_provider_name, &block
34
- Producer.new(service_provider_name, @name, &block).create_consumer_contract_builder
43
+ Provider.new(service_provider_name, @name, &block).create_consumer_contract_builder
35
44
  end
36
45
 
37
46
  def create_service_consumer
@@ -48,13 +57,13 @@ module Pact::Consumer
48
57
 
49
58
 
50
59
  #OLD ####
51
- def with_producer name, &block
52
- Producer.new(name, &block).create_consumer_contract_builder
60
+ def with_provider name, &block
61
+ Provider.new(name, &block).create_consumer_contract_builder
53
62
  end
54
63
 
55
- alias_method :with_service_provider, :with_producer
64
+ alias_method :with_service_provider, :with_provider
56
65
 
57
- class Producer
66
+ class Provider
58
67
  def initialize name, consumer_name = Pact.configuration.consumer.name, &block
59
68
  @name = name
60
69
  @service = nil
@@ -80,7 +89,7 @@ module Pact::Consumer
80
89
  def consumer_contract_builder_from_attributes
81
90
  consumer_contract_builder_fields = {
82
91
  :consumer_name => @consumer_name,
83
- :producer_name => @name,
92
+ :provider_name => @name,
84
93
  :pactfile_write_mode => Pact.configuration.pactfile_write_mode
85
94
  }
86
95
  @service.configure_consumer_contract_builder consumer_contract_builder_fields
@@ -111,7 +120,7 @@ module Pact::Consumer
111
120
  def configure_consumer_contract_builder consumer_contract_builder_fields
112
121
  validate
113
122
  unless @standalone
114
- AppManager.instance.register_mock_service_for consumer_contract_builder_fields[:producer_name], "http://localhost:#{@port}"
123
+ AppManager.instance.register_mock_service_for consumer_contract_builder_fields[:provider_name], "http://localhost:#{@port}"
115
124
  end
116
125
  consumer_contract_builder = Pact::Consumer::ConsumerContractBuilder.new consumer_contract_builder_fields.merge({port: @port})
117
126
  create_mock_services_module_method consumer_contract_builder
@@ -121,7 +130,7 @@ module Pact::Consumer
121
130
 
122
131
 
123
132
  def setup_verification consumer_contract_builder
124
- Pact.configuration.add_producer_verification do | example_description |
133
+ Pact.configuration.add_provider_verification do | example_description |
125
134
  consumer_contract_builder.verify example_description
126
135
  end
127
136
  end
@@ -144,4 +153,5 @@ module Pact::Consumer
144
153
  end
145
154
  end
146
155
 
147
- Pact.send(:extend, Pact::Consumer::DSL)
156
+ Pact.send(:extend, Pact::Consumer::DSL)
157
+ Pact::Configuration.send(:include, Pact::Consumer::DSL::Configuration)
@@ -0,0 +1,42 @@
1
+ require 'net/http'
2
+ require 'pact/reification'
3
+ require 'pact/request'
4
+ require 'pact/consumer_contract/interaction'
5
+
6
+ module Pact
7
+ module Consumer
8
+ class InteractionBuilder
9
+
10
+ attr_reader :interaction
11
+
12
+ def initialize
13
+ @interaction = Interaction.new
14
+ end
15
+
16
+ def upon_receiving description
17
+ @interaction.description = description
18
+ self
19
+ end
20
+
21
+ def given provider_state
22
+ @interaction.provider_state = provider_state.nil? ? nil : provider_state.to_s
23
+ self
24
+ end
25
+
26
+ def with(request_details)
27
+ interaction.request = Request::Expected.from_hash(request_details)
28
+ self
29
+ end
30
+
31
+ def will_respond_with(response)
32
+ interaction.response = response
33
+ @callback.call interaction
34
+ self
35
+ end
36
+
37
+ def on_interaction_fully_defined &block
38
+ @callback = block
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ module Pact
2
+ module Consumer
3
+
4
+ #TODO: think of a better word than filter
5
+ class InteractionsFilter
6
+ def initialize interactions = []
7
+ @interactions = interactions
8
+ end
9
+
10
+ def index_of interaction
11
+ @interactions.find_index{ |i| i.matches_criteria?(description: interaction.description, provider_state: interaction.provider_state)}
12
+ end
13
+ end
14
+
15
+ class UpdatableInteractionsFilter < InteractionsFilter
16
+
17
+ def << interaction
18
+ if (ndx = index_of(interaction))
19
+ @interactions[ndx] = interaction
20
+ else
21
+ @interactions << interaction
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ class DistinctInteractionsFilter < InteractionsFilter
28
+
29
+ def << interaction
30
+ if (ndx = index_of(interaction))
31
+ if @interactions[ndx] != interaction
32
+ raise "Interaction with same description (#{interaction.description}) and provider state (#{interaction.provider_state}) already exists"
33
+ end
34
+ else
35
+ @interactions << interaction
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end