pact 0.1.37 → 1.0.0

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