pact 0.1.28

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 (72) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +83 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +238 -0
  7. data/Rakefile +33 -0
  8. data/bin/pact +4 -0
  9. data/lib/pact/app.rb +32 -0
  10. data/lib/pact/configuration.rb +54 -0
  11. data/lib/pact/consumer/app_manager.rb +177 -0
  12. data/lib/pact/consumer/configuration_dsl.rb +71 -0
  13. data/lib/pact/consumer/consumer_contract_builder.rb +79 -0
  14. data/lib/pact/consumer/consumer_contract_builders.rb +10 -0
  15. data/lib/pact/consumer/dsl.rb +98 -0
  16. data/lib/pact/consumer/interaction.rb +70 -0
  17. data/lib/pact/consumer/mock_service.rb +340 -0
  18. data/lib/pact/consumer/rspec.rb +43 -0
  19. data/lib/pact/consumer/run_condor.rb +4 -0
  20. data/lib/pact/consumer/run_mock_contract_service.rb +13 -0
  21. data/lib/pact/consumer/service_consumer.rb +22 -0
  22. data/lib/pact/consumer/service_producer.rb +23 -0
  23. data/lib/pact/consumer.rb +7 -0
  24. data/lib/pact/consumer_contract.rb +110 -0
  25. data/lib/pact/json_warning.rb +23 -0
  26. data/lib/pact/logging.rb +14 -0
  27. data/lib/pact/matchers/matchers.rb +85 -0
  28. data/lib/pact/matchers.rb +1 -0
  29. data/lib/pact/producer/configuration_dsl.rb +62 -0
  30. data/lib/pact/producer/matchers.rb +22 -0
  31. data/lib/pact/producer/pact_spec_runner.rb +57 -0
  32. data/lib/pact/producer/producer_state.rb +81 -0
  33. data/lib/pact/producer/rspec.rb +127 -0
  34. data/lib/pact/producer/test_methods.rb +89 -0
  35. data/lib/pact/producer.rb +1 -0
  36. data/lib/pact/rake_task.rb +64 -0
  37. data/lib/pact/reification.rb +26 -0
  38. data/lib/pact/request.rb +109 -0
  39. data/lib/pact/term.rb +40 -0
  40. data/lib/pact/verification_task.rb +57 -0
  41. data/lib/pact/version.rb +3 -0
  42. data/lib/pact.rb +5 -0
  43. data/lib/tasks/pact.rake +6 -0
  44. data/pact.gemspec +36 -0
  45. data/scratchpad.txt +36 -0
  46. data/spec/features/consumption_spec.rb +146 -0
  47. data/spec/features/producer_states/zebras.rb +28 -0
  48. data/spec/features/production_spec.rb +160 -0
  49. data/spec/integration/pact/configuration_spec.rb +65 -0
  50. data/spec/lib/pact/configuration_spec.rb +35 -0
  51. data/spec/lib/pact/consumer/app_manager_spec.rb +41 -0
  52. data/spec/lib/pact/consumer/consumer_contract_builder_spec.rb +87 -0
  53. data/spec/lib/pact/consumer/dsl_spec.rb +52 -0
  54. data/spec/lib/pact/consumer/interaction_spec.rb +108 -0
  55. data/spec/lib/pact/consumer/mock_service_spec.rb +147 -0
  56. data/spec/lib/pact/consumer/service_consumer_spec.rb +11 -0
  57. data/spec/lib/pact/consumer_contract_spec.rb +125 -0
  58. data/spec/lib/pact/matchers/matchers_spec.rb +354 -0
  59. data/spec/lib/pact/producer/configuration_dsl_spec.rb +101 -0
  60. data/spec/lib/pact/producer/producer_state_spec.rb +103 -0
  61. data/spec/lib/pact/producer/rspec_spec.rb +48 -0
  62. data/spec/lib/pact/reification_spec.rb +43 -0
  63. data/spec/lib/pact/request_spec.rb +316 -0
  64. data/spec/lib/pact/term_spec.rb +36 -0
  65. data/spec/lib/pact/verification_task_spec.rb +64 -0
  66. data/spec/spec_helper.rb +5 -0
  67. data/spec/support/a_consumer-a_producer.json +34 -0
  68. data/spec/support/pact_rake_support.rb +41 -0
  69. data/spec/support/test_app_fail.json +22 -0
  70. data/spec/support/test_app_pass.json +21 -0
  71. data/tasks/pact-test.rake +19 -0
  72. metadata +381 -0
@@ -0,0 +1,71 @@
1
+ require_relative 'service_consumer'
2
+ require_relative 'consumer_contract_builders'
3
+ require_relative '../configuration'
4
+
5
+ module Pact
6
+ module Consumer
7
+
8
+
9
+ module Configuration
10
+ def add_producer_verification &block
11
+ producer_verifications << block
12
+ end
13
+ def producer_verifications
14
+ @producer_verifications ||= []
15
+ end
16
+ end
17
+
18
+ module ConfigurationDSL
19
+
20
+ def consumer &block
21
+ if block_given?
22
+ @consumer = ConsumerDSL.new(&block).create_service_consumer
23
+ elsif @consumer
24
+ @consumer
25
+ else
26
+ raise "Please configure a consumer before configuring producers"
27
+ end
28
+ end
29
+
30
+ class ConsumerDSL
31
+
32
+ def initialize &block
33
+ @app = nil
34
+ @port = nil
35
+ @name = nil
36
+ instance_eval(&block)
37
+ end
38
+
39
+ def validate
40
+ raise "Please provide a consumer name" unless @name
41
+ raise "Please provide a port for the consumer app" if @app && !@port
42
+ end
43
+
44
+ def name name
45
+ @name = name
46
+ end
47
+
48
+ def app app
49
+ @app = app
50
+ end
51
+
52
+ def port port
53
+ @port = port
54
+ end
55
+
56
+ def create_service_consumer
57
+ validate
58
+ register_consumer_app if @app
59
+ Pact::Consumer::ServiceConsumer.new name: @name
60
+ end
61
+
62
+ def register_consumer_app
63
+ Pact::Consumer::AppManager.instance.register @app, @port
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ Pact::Configuration.send(:include, Pact::Consumer::ConfigurationDSL)
71
+ Pact::Configuration.send(:include, Pact::Consumer::Configuration)
@@ -0,0 +1,79 @@
1
+ require 'uri'
2
+ require 'json/add/regexp'
3
+ require 'pact/json_warning'
4
+ require 'pact/logging'
5
+
6
+ module Pact
7
+ module Consumer
8
+ class ConsumerContractBuilder
9
+
10
+ include Pact::JsonWarning
11
+ include Pact::Logging
12
+
13
+ attr_reader :uri
14
+ attr_reader :consumer_contract
15
+ attr_reader :pactfile_write_mode
16
+
17
+ def initialize(attributes)
18
+ @interactions = {}
19
+ @producer_state = nil
20
+ @pactfile_write_mode = attributes[:pactfile_write_mode]
21
+ @consumer_contract = Pact::ConsumerContract.new(
22
+ :consumer => ServiceConsumer.new(name: attributes[:consumer_name]),
23
+ :producer => ServiceProducer.new(name: attributes[:producer_name])
24
+ )
25
+ @uri = URI("http://localhost:#{attributes[:port]}")
26
+ @http = Net::HTTP.new(uri.host, uri.port)
27
+ if pactfile_write_mode == :update && File.exist?(consumer_contract.pactfile_path)
28
+ load_existing_interactions
29
+ end
30
+ end
31
+
32
+ def load_existing_interactions
33
+ json = File.read(consumer_contract.pactfile_path)
34
+ if json.size > 0
35
+ existing_consumer_contract = Pact::ConsumerContract.from_json json
36
+ existing_consumer_contract.interactions.each do | interaction |
37
+ @interactions["#{interaction.description} given #{interaction.producer_state}"] = interaction
38
+ end
39
+ consumer_contract.interactions = @interactions.values
40
+ end
41
+ end
42
+
43
+ def given(producer_state)
44
+ @producer_state = producer_state
45
+ self
46
+ end
47
+
48
+ def upon_receiving(description)
49
+ interaction_builder = InteractionBuilder.new(description, @producer_state)
50
+ producer = self
51
+ interaction_builder.on_interaction_fully_defined do | interaction |
52
+ producer.handle_interaction_fully_defined(interaction)
53
+ end
54
+ @interactions["#{description} given #{@producer_state}"] ||= interaction_builder.interaction
55
+ consumer_contract.interactions = @interactions.values
56
+ interaction_builder
57
+ end
58
+
59
+ def handle_interaction_fully_defined interaction
60
+ @http.request_post('/interactions', interaction.to_json_with_generated_response)
61
+ @producer_state = nil
62
+ consumer_contract.update_pactfile
63
+ end
64
+
65
+ def verify example_description
66
+ http = Net::HTTP.new(uri.host, uri.port)
67
+ response = http.request_get("/verify?example_description=#{URI.encode(example_description)}")
68
+ raise response.body unless response.is_a? Net::HTTPSuccess
69
+ end
70
+
71
+ private
72
+
73
+ def filenamify name
74
+ name.downcase.gsub(/\s/, '_')
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,10 @@
1
+ module Pact
2
+ module Consumer
3
+ module ConsumerContractBuilders
4
+ # Placeholder for mock service methods which will be dynamically created
5
+ # from the Pact configuration.
6
+
7
+ # To be included in RSpec
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,98 @@
1
+ require_relative 'consumer_contract_builders'
2
+
3
+ module Pact::Consumer
4
+ module DSL
5
+ def with_producer name, &block
6
+ Producer.new(name, &block).create_consumer_contract_builder
7
+ end
8
+
9
+ class Producer
10
+ def initialize name, &block
11
+ @name = name
12
+ @service = nil
13
+ instance_eval(&block)
14
+ end
15
+
16
+ def service name, &block
17
+ @service = Service.new(name, &block)
18
+ end
19
+
20
+ alias_method :mock_service, :service
21
+
22
+ def create_consumer_contract_builder
23
+ validate
24
+ consumer_contract_builder_from_attributes
25
+ end
26
+
27
+ def validate
28
+ raise "Please configure a service for #{@name}" unless @service
29
+ end
30
+
31
+ def consumer_contract_builder_from_attributes
32
+ consumer_contract_builder_fields = {
33
+ :consumer_name => Pact.configuration.consumer.name,
34
+ :producer_name => @name,
35
+ :pactfile_write_mode => Pact.configuration.pactfile_write_mode
36
+ }
37
+ @service.configure_consumer_contract_builder consumer_contract_builder_fields
38
+ end
39
+ end
40
+
41
+ class Service
42
+ def initialize name, &block
43
+ @name = name
44
+ @port = nil
45
+ @standalone = false
46
+ @verify = false
47
+ instance_eval(&block)
48
+ end
49
+
50
+ def port port
51
+ @port = port
52
+ end
53
+
54
+ def standalone standalone
55
+ @standalone = standalone
56
+ end
57
+
58
+ def verify verify
59
+ @verify = verify
60
+ end
61
+
62
+ def configure_consumer_contract_builder consumer_contract_builder_fields
63
+ validate
64
+ unless @standalone
65
+ AppManager.instance.register_mock_service_for consumer_contract_builder_fields[:producer_name], "http://localhost:#{@port}"
66
+ end
67
+ consumer_contract_builder = Pact::Consumer::ConsumerContractBuilder.new consumer_contract_builder_fields.merge({port: @port})
68
+ create_mock_services_module_method consumer_contract_builder
69
+ setup_verification(consumer_contract_builder) if @verify
70
+ consumer_contract_builder
71
+ end
72
+
73
+
74
+ def setup_verification consumer_contract_builder
75
+ Pact.configuration.add_producer_verification do | example_description |
76
+ consumer_contract_builder.verify example_description
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # This makes the consumer_contract_builder available via a module method with the given identifier
83
+ # as the method name.
84
+ # I feel this should be defined somewhere else, but I'm not sure where
85
+ def create_mock_services_module_method consumer_contract_builder
86
+ Pact::Consumer::ConsumerContractBuilders.send(:define_method, @name.to_sym) do
87
+ consumer_contract_builder
88
+ end
89
+ end
90
+
91
+ def validate
92
+ raise "Please provide a port for service #{@name}" unless @port
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ Pact.send(:extend, Pact::Consumer::DSL)
@@ -0,0 +1,70 @@
1
+ require 'net/http'
2
+ require 'pact/reification'
3
+ require 'pact/request'
4
+ #require 'json/add/core'
5
+
6
+ module Pact
7
+ module Consumer
8
+
9
+ class Interaction
10
+
11
+ attr_accessor :description, :request, :response, :producer_state
12
+
13
+ def initialize options
14
+ @description = options[:description]
15
+ @request = options[:request]
16
+ @response = options[:response]
17
+ @producer_state = options[:producer_state]
18
+ end
19
+
20
+ def self.from_hash options
21
+ new(:description => options['description'],
22
+ :producer_state => options['producer_state'],
23
+ :request => Pact::Request::Expected.from_hash(options['request']),
24
+ :response => options['response']
25
+ )
26
+ end
27
+
28
+ def as_json
29
+ {
30
+ :description => @description,
31
+ :request => @request.as_json,
32
+ :response => @response,
33
+ }.tap{ | hash | hash[:producer_state] = @producer_state if @producer_state }
34
+ end
35
+
36
+ def to_json(options = {})
37
+ as_json.to_json(options)
38
+ end
39
+
40
+
41
+ def to_json_with_generated_response
42
+ as_json.tap { | hash | hash[:response] = Reification.from_term(response) }.to_json
43
+ end
44
+ end
45
+
46
+ class InteractionBuilder
47
+
48
+ attr_reader :interaction
49
+
50
+ def initialize(description, producer_state)
51
+ producer_state = producer_state.nil? ? nil : producer_state.to_s
52
+ @interaction = Interaction.new(:description => description, :producer_state => producer_state)
53
+ end
54
+
55
+ def with(request_details)
56
+ interaction.request = Request::Expected.from_hash(request_details)
57
+ self
58
+ end
59
+
60
+ def will_respond_with(response)
61
+ interaction.response = response
62
+ @callback.call interaction
63
+ end
64
+
65
+ def on_interaction_fully_defined &block
66
+ @callback = block
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,340 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+ require 'hashie'
5
+ require 'singleton'
6
+ require 'logger'
7
+ require 'awesome_print'
8
+ require 'awesome_print/core_ext/logger' #For some reason we get an error indicating that the method 'ap' is private unless we load this specifically
9
+ require 'json/add/regexp'
10
+ require 'pact/matchers'
11
+
12
+ AwesomePrint.defaults = {
13
+ indent: -2,
14
+ plain: true,
15
+ index: false
16
+ }
17
+
18
+ module Pact
19
+ module Consumer
20
+
21
+ class InteractionList
22
+ include Singleton
23
+
24
+ attr_reader :interactions
25
+ attr_reader :unexpected_requests
26
+
27
+ def initialize
28
+ clear
29
+ end
30
+
31
+ # For testing, sigh
32
+ def clear
33
+ @interactions = []
34
+ @matched_interactions = []
35
+ @unexpected_requests = []
36
+ end
37
+
38
+ def add interactions
39
+ @interactions << interactions
40
+ end
41
+
42
+ def register_matched interaction
43
+ @matched_interactions << interaction
44
+ end
45
+
46
+ def register_unexpected request
47
+ @unexpected_requests << request
48
+ end
49
+
50
+ def all_matched?
51
+ interaction_diffs.empty?
52
+ end
53
+
54
+ def missing_interactions
55
+ @interactions - @matched_interactions
56
+ end
57
+
58
+ def interaction_diffs
59
+ {
60
+ :missing_interactions => missing_interactions,
61
+ :unexpected_requests => unexpected_requests
62
+ }.inject({}) do | hash, pair |
63
+ hash[pair.first] = pair.last if pair.last.any?
64
+ hash
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ module RackHelper
71
+ def params_hash env
72
+ env["QUERY_STRING"].split("&").collect{| param| param.split("=")}.inject({}){|params, param| params[param.first] = URI.decode(param.last); params }
73
+ end
74
+ end
75
+
76
+ class StartupPoll
77
+
78
+ def initialize name, logger
79
+ @name = name
80
+ @logger = logger
81
+ end
82
+
83
+ def match? env
84
+ env['REQUEST_PATH'] == '/index.html' &&
85
+ env['REQUEST_METHOD'] == 'GET'
86
+ end
87
+
88
+ def respond env
89
+ @logger.info "#{@name} started up"
90
+ [200, {}, ['Started up fine']]
91
+ end
92
+ end
93
+
94
+ class CapybaraIdentify
95
+
96
+ def initialize name, logger
97
+ @name = name
98
+ @logger = logger
99
+ end
100
+
101
+ def match? env
102
+ env["PATH_INFO"] == "/__identify__"
103
+ end
104
+
105
+ def respond env
106
+ [200, {}, [object_id.to_s]]
107
+ end
108
+ end
109
+
110
+ class InteractionDelete
111
+
112
+ include RackHelper
113
+
114
+ def initialize name, logger
115
+ @name = name
116
+ @logger = logger
117
+ end
118
+
119
+ def match? env
120
+ env['REQUEST_PATH'].start_with?('/interactions') &&
121
+ env['REQUEST_METHOD'] == 'DELETE'
122
+ end
123
+
124
+ def respond env
125
+ InteractionList.instance.clear
126
+ @logger.info "Cleared interactions before example \"#{params_hash(env)['example_description']}\""
127
+ [200, {}, ['Deleted interactions']]
128
+ end
129
+ end
130
+
131
+ class InteractionPost
132
+
133
+ def initialize name, logger
134
+ @name = name
135
+ @logger = logger
136
+ end
137
+
138
+ def match? env
139
+ env['REQUEST_PATH'] == '/interactions' &&
140
+ env['REQUEST_METHOD'] == 'POST'
141
+ end
142
+
143
+ def respond env
144
+ interactions = Hashie::Mash.new(JSON.load(env['rack.input'].string))
145
+ InteractionList.instance.add interactions
146
+ @logger.info "Added interaction to #{@name}"
147
+ @logger.ap interactions
148
+ [200, {}, ['Added interactions']]
149
+ end
150
+ end
151
+
152
+ module RequestExtractor
153
+
154
+ REQUEST_KEYS = Hashie::Mash.new({
155
+ 'REQUEST_METHOD' => :method,
156
+ 'REQUEST_PATH' => :path,
157
+ 'QUERY_STRING' => :query,
158
+ 'rack.input' => :body
159
+ })
160
+
161
+ def request_from env
162
+ request = env.inject({}) do |memo, (k, v)|
163
+ request_key = REQUEST_KEYS[k]
164
+ memo[request_key] = v if request_key
165
+ memo
166
+ end
167
+
168
+ mashed_request = Hashie::Mash.new request
169
+ mashed_request[:headers] = headers_from env
170
+ body_string = mashed_request[:body].read
171
+
172
+ if body_string.empty?
173
+ mashed_request.delete :body
174
+ else
175
+ body_is_json = mashed_request[:headers]['Content-Type'] =~ /json/
176
+ mashed_request[:body] = body_is_json ? JSON.parse(body_string) : body_string
177
+ end
178
+ mashed_request[:method] = mashed_request[:method].downcase
179
+ mashed_request
180
+ end
181
+
182
+ def headers_from env
183
+ headers = env.reject{ |key, value| !(key.start_with?("HTTP") || key == 'CONTENT_TYPE')}
184
+ headers.inject({}) do | hash, header |
185
+ hash[standardise_header(header.first)] = header.last
186
+ hash
187
+ end
188
+ end
189
+
190
+ def standardise_header header
191
+ header.gsub(/^HTTP_/, '').split("_").collect{|word| word[0] + word[1..-1].downcase}.join("-")
192
+ end
193
+ end
194
+
195
+ class InteractionReplay
196
+ include Pact::Matchers
197
+ include RequestExtractor
198
+
199
+ def initialize name, logger
200
+ @name = name
201
+ @logger = logger
202
+ end
203
+
204
+ def match? env
205
+ true # default handler
206
+ end
207
+
208
+ def respond env
209
+ find_response request_from(env)
210
+ end
211
+
212
+ private
213
+
214
+ def find_response raw_request
215
+ actual_request = Request::Actual.from_hash(raw_request)
216
+ @logger.info "#{@name} received request"
217
+ @logger.ap actual_request.as_json
218
+ candidates = []
219
+ matching_interactions = InteractionList.instance.interactions.select do |interaction|
220
+ expected_request = Request::Expected.from_hash(interaction.request.merge(:description => interaction.description))
221
+ candidates << expected_request if expected_request.matches_route? actual_request
222
+ expected_request.match actual_request
223
+ end
224
+ if matching_interactions.size > 1
225
+ @logger.info "Multiple interactions found on #{@name}:"
226
+ @logger.ap matching_interactions
227
+ raise 'Multiple interactions found!'
228
+ end
229
+ if matching_interactions.empty?
230
+ handle_unrecognised_request(actual_request, candidates)
231
+ else
232
+ response = response_from(matching_interactions.first.response)
233
+ InteractionList.instance.register_matched matching_interactions.first
234
+ @logger.info "Found matching response on #{@name}:"
235
+ @logger.ap response
236
+ response
237
+ end
238
+ end
239
+
240
+ def handle_unrecognised_request request, candidates
241
+ @logger.ap "No interaction found on #{@name} for request \"#{candidates.map(&:description).join(', ')}\""
242
+ @logger.ap 'Interaction diffs for that route:'
243
+ interaction_diff = candidates.map do |candidate|
244
+ diff(candidate.as_json, request.as_json)
245
+ end.to_a
246
+ @logger.ap(interaction_diff)
247
+ response = {message: "No interaction found for #{request.path}", interaction_diff: interaction_diff}
248
+ [500, {'Content-Type' => 'application/json'}, [response.to_json]]
249
+ end
250
+
251
+ def response_from response
252
+ [response.status, (response.headers || {}).to_hash, [render_body(response.body)]]
253
+ end
254
+
255
+ def render_body body
256
+ return '' unless body
257
+ body.kind_of?(String) ? body.force_encoding('utf-8') : body.to_json
258
+ end
259
+
260
+ def logger_info_ap msg
261
+ @logger.info msg
262
+ end
263
+ end
264
+
265
+ class VerificationGet
266
+
267
+ include RackHelper
268
+
269
+ def initialize name, logger, log_description
270
+ @name = name
271
+ @logger = logger
272
+ @log_description = log_description
273
+ end
274
+
275
+ def match? env
276
+ env['REQUEST_PATH'].start_with?('/verify') &&
277
+ env['REQUEST_METHOD'] == 'GET'
278
+ end
279
+
280
+ def respond env
281
+ if InteractionList.instance.all_matched?
282
+ @logger.info "Veryifying - interactions matched for example \"#{example_description(env)}\""
283
+ [200, {}, ['Interactions matched']]
284
+ else
285
+ @logger.warn "Verifying - actual interactions do not match expected interactions for example \"#{example_description(env)}\". Missing interactions:"
286
+ @logger.ap InteractionList.instance.interaction_diffs, :warn
287
+ [500, {}, ["Actual interactions do not match expected interactions for mock #{@name}. See #{@log_description} for details."]]
288
+ end
289
+ end
290
+
291
+ def example_description env
292
+ params_hash(env)['example_description']
293
+ end
294
+ end
295
+
296
+ class MockService
297
+
298
+ def initialize options = {}
299
+ options = {log_file: STDOUT}.merge options
300
+ log_stream = options[:log_file]
301
+ @logger = Logger.new log_stream
302
+
303
+ log_description = if log_stream.is_a? File
304
+ File.absolute_path(log_stream).gsub(Dir.pwd + "/", '')
305
+ else
306
+ "standard out/err"
307
+ end
308
+
309
+ @name = options.fetch(:name, "MockService")
310
+ @handlers = [
311
+ StartupPoll.new(@name, @logger),
312
+ CapybaraIdentify.new(@name, @logger),
313
+ VerificationGet.new(@name, @logger, log_description),
314
+ InteractionPost.new(@name, @logger),
315
+ InteractionDelete.new(@name, @logger),
316
+ InteractionReplay.new(@name, @logger)
317
+ ]
318
+ end
319
+
320
+ def to_s
321
+ "#{@name} #{super.to_s}"
322
+ end
323
+
324
+ def call env
325
+ response = []
326
+ begin
327
+ relevant_handler = @handlers.detect { |handler| handler.match? env }
328
+ response = relevant_handler.respond env
329
+ rescue Exception => e
330
+ @logger.ap 'Error ocurred in mock service:'
331
+ @logger.ap e
332
+ @logger.ap e.backtrace
333
+ raise e
334
+ end
335
+ response
336
+ end
337
+
338
+ end
339
+ end
340
+ end