hs-pact-mock_service 3.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +494 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +137 -0
  6. data/bin/pact-mock-service +3 -0
  7. data/bin/pact-stub-service +3 -0
  8. data/lib/pact/consumer/mock_service/cors_origin_header_middleware.rb +31 -0
  9. data/lib/pact/consumer/mock_service/error_handler.rb +31 -0
  10. data/lib/pact/consumer/mock_service/rack_request_helper.rb +65 -0
  11. data/lib/pact/consumer/mock_service/set_location.rb +28 -0
  12. data/lib/pact/consumer/server.rb +111 -0
  13. data/lib/pact/consumer_contract/consumer_contract_decorator.rb +53 -0
  14. data/lib/pact/consumer_contract/consumer_contract_writer.rb +181 -0
  15. data/lib/pact/consumer_contract/interaction_decorator.rb +40 -0
  16. data/lib/pact/consumer_contract/request_decorator.rb +88 -0
  17. data/lib/pact/consumer_contract/response_decorator.rb +47 -0
  18. data/lib/pact/mock_service/app.rb +85 -0
  19. data/lib/pact/mock_service/app_manager.rb +156 -0
  20. data/lib/pact/mock_service/cli/custom_thor.rb +74 -0
  21. data/lib/pact/mock_service/cli/pidfile.rb +99 -0
  22. data/lib/pact/mock_service/cli.rb +213 -0
  23. data/lib/pact/mock_service/client.rb +79 -0
  24. data/lib/pact/mock_service/control_server/app.rb +42 -0
  25. data/lib/pact/mock_service/control_server/delegator.rb +44 -0
  26. data/lib/pact/mock_service/control_server/index.rb +25 -0
  27. data/lib/pact/mock_service/control_server/mock_service_creator.rb +32 -0
  28. data/lib/pact/mock_service/control_server/mock_services.rb +26 -0
  29. data/lib/pact/mock_service/control_server/require_pacticipant_headers.rb +20 -0
  30. data/lib/pact/mock_service/control_server/run.rb +73 -0
  31. data/lib/pact/mock_service/errors.rb +9 -0
  32. data/lib/pact/mock_service/interaction_decorator.rb +49 -0
  33. data/lib/pact/mock_service/interactions/actual_interactions.rb +36 -0
  34. data/lib/pact/mock_service/interactions/candidate_interactions.rb +15 -0
  35. data/lib/pact/mock_service/interactions/expected_interactions.rb +18 -0
  36. data/lib/pact/mock_service/interactions/interaction_diff_message.rb +45 -0
  37. data/lib/pact/mock_service/interactions/interaction_mismatch.rb +74 -0
  38. data/lib/pact/mock_service/interactions/interactions_filter.rb +66 -0
  39. data/lib/pact/mock_service/interactions/verification.rb +52 -0
  40. data/lib/pact/mock_service/interactions/verified_interactions.rb +20 -0
  41. data/lib/pact/mock_service/logger.rb +40 -0
  42. data/lib/pact/mock_service/request_decorator.rb +36 -0
  43. data/lib/pact/mock_service/request_handlers/base_administration_request_handler.rb +42 -0
  44. data/lib/pact/mock_service/request_handlers/base_request_handler.rb +30 -0
  45. data/lib/pact/mock_service/request_handlers/index_get.rb +23 -0
  46. data/lib/pact/mock_service/request_handlers/interaction_delete.rb +38 -0
  47. data/lib/pact/mock_service/request_handlers/interaction_post.rb +43 -0
  48. data/lib/pact/mock_service/request_handlers/interaction_replay.rb +191 -0
  49. data/lib/pact/mock_service/request_handlers/interactions_put.rb +44 -0
  50. data/lib/pact/mock_service/request_handlers/log_get.rb +27 -0
  51. data/lib/pact/mock_service/request_handlers/missing_interactions_get.rb +33 -0
  52. data/lib/pact/mock_service/request_handlers/options.rb +64 -0
  53. data/lib/pact/mock_service/request_handlers/pact_post.rb +38 -0
  54. data/lib/pact/mock_service/request_handlers/session_delete.rb +32 -0
  55. data/lib/pact/mock_service/request_handlers/verification_get.rb +74 -0
  56. data/lib/pact/mock_service/request_handlers.rb +42 -0
  57. data/lib/pact/mock_service/response_decorator.rb +31 -0
  58. data/lib/pact/mock_service/run.rb +125 -0
  59. data/lib/pact/mock_service/server/respawn.rb +20 -0
  60. data/lib/pact/mock_service/server/spawn.rb +37 -0
  61. data/lib/pact/mock_service/server/wait_for_server_up.rb +44 -0
  62. data/lib/pact/mock_service/server/webrick_request_monkeypatch.rb +15 -0
  63. data/lib/pact/mock_service/session.rb +96 -0
  64. data/lib/pact/mock_service/spawn.rb +86 -0
  65. data/lib/pact/mock_service/version.rb +5 -0
  66. data/lib/pact/mock_service.rb +2 -0
  67. data/lib/pact/stub_service/cli.rb +71 -0
  68. data/lib/pact/support/expand_file_list.rb +26 -0
  69. metadata +399 -0
@@ -0,0 +1,64 @@
1
+ require 'pact/mock_service/request_handlers/base_request_handler'
2
+
3
+ module Pact
4
+ module MockService
5
+ module RequestHandlers
6
+ class Options < BaseRequestHandler
7
+
8
+ attr_reader :name, :logger, :cors_enabled
9
+
10
+ HTTP_ACCESS_CONTROL_REQUEST_METHOD = "HTTP_ACCESS_CONTROL_REQUEST_METHOD".freeze
11
+ HTTP_ACCESS_CONTROL_REQUEST_HEADERS = "HTTP_ACCESS_CONTROL_REQUEST_HEADERS".freeze
12
+ ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials".freeze
13
+ ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin".freeze
14
+ ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods".freeze
15
+ ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers".freeze
16
+ AUTHORIZATION = "authorization".freeze
17
+ COOKIE = "cookie".freeze
18
+ HTTP_ORIGIN = "HTTP_ORIGIN".freeze
19
+ ALL_METHODS = "DELETE, POST, GET, HEAD, PUT, TRACE, CONNECT, PATCH".freeze
20
+ REQUEST_METHOD = "REQUEST_METHOD".freeze
21
+ OPTIONS = "OPTIONS".freeze
22
+ X_PACT_MOCK_SERVICE_REGEXP = /x-pact-mock-service/i
23
+
24
+ def initialize name, logger, cors_enabled
25
+ @name = name
26
+ @logger = logger
27
+ @cors_enabled = cors_enabled
28
+ end
29
+
30
+ def match? env
31
+ is_options_request?(env) && (cors_enabled || is_administration_request?(env))
32
+ end
33
+
34
+ def respond env
35
+ cors_headers = {
36
+ ACCESS_CONTROL_ALLOW_ORIGIN => env.fetch(HTTP_ORIGIN,'*'),
37
+ ACCESS_CONTROL_ALLOW_HEADERS => env.fetch(HTTP_ACCESS_CONTROL_REQUEST_HEADERS, '*'),
38
+ ACCESS_CONTROL_ALLOW_METHODS => ALL_METHODS
39
+ }
40
+
41
+ if is_request_with_credentials?(env)
42
+ cors_headers[ACCESS_CONTROL_ALLOW_CREDENTIALS] = "true"
43
+ end
44
+
45
+ logger.info "Received OPTIONS request for mock service administration endpoint #{env[HTTP_ACCESS_CONTROL_REQUEST_METHOD]} #{env['PATH_INFO']}. Returning CORS headers: #{cors_headers}."
46
+ [200, cors_headers, []]
47
+ end
48
+
49
+ def is_options_request? env
50
+ env[REQUEST_METHOD] == OPTIONS
51
+ end
52
+
53
+ def is_administration_request? env
54
+ (env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS] || '').match(X_PACT_MOCK_SERVICE_REGEXP)
55
+ end
56
+
57
+ def is_request_with_credentials? env
58
+ headers = (env[HTTP_ACCESS_CONTROL_REQUEST_HEADERS] || '').split(",").map { |header| header.strip.downcase }
59
+ headers.include?(AUTHORIZATION) || headers.include?(COOKIE)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,38 @@
1
+ require 'pact/mock_service/request_handlers/base_administration_request_handler'
2
+ require 'pact/consumer_contract/consumer_contract_writer'
3
+
4
+ module Pact
5
+ module MockService
6
+ module RequestHandlers
7
+ class PactPost < BaseAdministrationRequestHandler
8
+
9
+ attr_accessor :consumer_contract, :verified_interactions, :default_options, :session
10
+
11
+ def initialize name, logger, session
12
+ super name, logger
13
+ @verified_interactions = session.verified_interactions
14
+ @default_options = {}
15
+ @default_options.merge!(session.consumer_contract_details)
16
+ @session = session
17
+ end
18
+
19
+ def request_path
20
+ '/pact'
21
+ end
22
+
23
+ def request_method
24
+ 'POST'
25
+ end
26
+
27
+ def respond env
28
+ body = env['rack.input'].string
29
+ consumer_contract_details = body.size > 0 ? JSON.parse(body, symbolize_names: true) : {}
30
+ consumer_contract_params = default_options.merge(consumer_contract_details.merge(interactions: verified_interactions))
31
+ consumer_contract_writer = ConsumerContractWriter.new(consumer_contract_params, logger)
32
+ session.record_pact_written
33
+ json_response(consumer_contract_writer.write)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ require 'pact/mock_service/request_handlers/base_administration_request_handler'
2
+
3
+ module Pact
4
+ module MockService
5
+ module RequestHandlers
6
+
7
+ class SessionDelete < BaseAdministrationRequestHandler
8
+
9
+ attr_accessor :session
10
+
11
+ def initialize name, logger, session
12
+ super name, logger
13
+ @session = session
14
+ end
15
+
16
+ def request_path
17
+ '/session'
18
+ end
19
+
20
+ def request_method
21
+ 'DELETE'
22
+ end
23
+
24
+ def respond env
25
+ session.clear_all
26
+ logger.info "Cleared session"
27
+ text_response 'Cleared session'
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,74 @@
1
+ require 'pact/mock_service/request_handlers/base_administration_request_handler'
2
+
3
+ module Pact
4
+ module MockService
5
+ module RequestHandlers
6
+ class VerificationGet < BaseAdministrationRequestHandler
7
+
8
+ def initialize name, logger, session
9
+ super name, logger
10
+ @expected_interactions = session.expected_interactions
11
+ @actual_interactions = session.actual_interactions
12
+ end
13
+
14
+ def request_path
15
+ '/interactions/verification'
16
+ end
17
+
18
+ def request_method
19
+ 'GET'
20
+ end
21
+
22
+ def respond env
23
+ verification = Pact::MockService::Interactions::Verification.new(expected_interactions, actual_interactions)
24
+ example_desc = example_description(env)
25
+ example_desc = example_desc ? " for example #{example_desc.inspect}" : ''
26
+ if verification.all_matched?
27
+ logger.info "Verifying - interactions matched#{example_desc}"
28
+ text_response('Interactions matched')
29
+ else
30
+ error_message = FailureMessage.new(verification).to_s
31
+ logger.warn "Verifying - actual interactions do not match expected interactions#{example_desc}. \n#{error_message}"
32
+ logger.warn error_message
33
+ response_message = "Actual interactions do not match expected interactions for mock #{name}.\n\n#{error_message}See #{logger.description} for details."
34
+ text_response(response_message, 500)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ attr_accessor :expected_interactions, :actual_interactions
41
+
42
+ def example_description env
43
+ params_hash(env).fetch("example_description", [])[0]
44
+ end
45
+
46
+ class FailureMessage
47
+
48
+ def initialize verification
49
+ @verification = verification
50
+ end
51
+
52
+ def to_s
53
+ titles_and_summaries.collect do | title, summaries |
54
+ "#{title}:\n\t#{summaries.join("\n\t")}\n\n" if summaries.any?
55
+ end.compact.join + verification.interaction_mismatches.collect(&:to_s).join("\n\n") + "\n"
56
+
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :verification
62
+
63
+ def titles_and_summaries
64
+ {
65
+ "Incorrect requests" => verification.interaction_mismatches_summaries,
66
+ "Missing requests" => verification.missing_interactions_summaries,
67
+ "Unexpected requests" => verification.unexpected_requests_summaries,
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,42 @@
1
+ require 'pact/mock_service/request_handlers/interaction_post'
2
+ require 'pact/mock_service/request_handlers/interactions_put'
3
+ require 'pact/mock_service/request_handlers/index_get'
4
+ require 'pact/mock_service/request_handlers/interaction_delete'
5
+ require 'pact/mock_service/request_handlers/interaction_replay'
6
+ require 'pact/mock_service/request_handlers/log_get'
7
+ require 'pact/mock_service/request_handlers/options'
8
+ require 'pact/mock_service/request_handlers/missing_interactions_get'
9
+ require 'pact/mock_service/request_handlers/pact_post'
10
+ require 'pact/mock_service/request_handlers/session_delete'
11
+ require 'pact/mock_service/request_handlers/verification_get'
12
+ require 'pact/consumer/request'
13
+ require 'pact/support'
14
+
15
+ module Pact
16
+ module MockService
17
+ module RequestHandlers
18
+
19
+ def self.new *args
20
+ App.new(*args)
21
+ end
22
+
23
+ class App < ::Rack::Cascade
24
+ def initialize name, logger, session, options
25
+ super [
26
+ Options.new(name, logger, options[:cors_enabled]),
27
+ SessionDelete.new(name, logger, session),
28
+ MissingInteractionsGet.new(name, logger, session),
29
+ VerificationGet.new(name, logger, session),
30
+ InteractionPost.new(name, logger, session, Pact::SpecificationVersion.new(options.fetch(:pact_specification_version))),
31
+ InteractionsPut.new(name, logger, session, Pact::SpecificationVersion.new(options.fetch(:pact_specification_version))),
32
+ InteractionDelete.new(name, logger, session),
33
+ LogGet.new(name, logger),
34
+ PactPost.new(name, logger, session),
35
+ IndexGet.new(name, logger),
36
+ InteractionReplay.new(name, logger, session, options[:cors_enabled], options[:stub_pactfile_paths])
37
+ ]
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ module Pact
2
+ module MockService
3
+ class ResponseDecorator
4
+
5
+ def initialize response
6
+ @response = response
7
+ end
8
+
9
+ def to_json(options = {})
10
+ as_json.to_json(options)
11
+ end
12
+
13
+ def as_json options = {}
14
+ to_hash
15
+ end
16
+
17
+ def to_hash
18
+ hash = {}
19
+ hash[:status] = response.status if response.specified?(:status)
20
+ hash[:headers] = response.headers if response.specified?(:headers)
21
+ hash[:body] = response.body if response.specified?(:body)
22
+ hash
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :response
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,125 @@
1
+ require 'find_a_port'
2
+ require 'pact/mock_service/app'
3
+ require 'pact/consumer/mock_service/set_location'
4
+ require 'pact/mock_service/run'
5
+ require 'pact/mock_service/server/webrick_request_monkeypatch'
6
+ require 'pact/specification_version'
7
+
8
+ module Pact
9
+ module MockService
10
+ class Run
11
+
12
+ def self.call options
13
+ new(options).call
14
+ end
15
+
16
+ def initialize options
17
+ @options = options
18
+ end
19
+
20
+ def call
21
+ require 'pact/mock_service/app'
22
+
23
+ trap(:INT) { call_shutdown_hooks }
24
+ trap(:TERM) { call_shutdown_hooks }
25
+
26
+ require_monkeypatch
27
+
28
+ Rack::Handler::WEBrick.run(mock_service, **webbrick_opts)
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :options
34
+
35
+ def mock_service
36
+ @mock_service ||= begin
37
+ mock_service = Pact::MockService.new(service_options)
38
+ Pact::Consumer::SetLocation.new(mock_service, base_url)
39
+ end
40
+ end
41
+
42
+ def call_shutdown_hooks
43
+ unless @shutting_down
44
+ @shutting_down = true
45
+ begin
46
+ mock_service.shutdown
47
+ ensure
48
+ Rack::Handler::WEBrick.shutdown
49
+ end
50
+ end
51
+ end
52
+
53
+ def service_options
54
+ # dummy pact_specification_version is needed to stop RequestHandlers blowing up
55
+ service_options = {
56
+ pact_dir: options[:pact_dir],
57
+ log_level: options[:log_level],
58
+ unique_pact_file_names: options[:unique_pact_file_names],
59
+ consumer: options[:consumer],
60
+ provider: options[:provider],
61
+ broker_token: options[:broker_token],
62
+ broker_username: options[:broker_username],
63
+ broker_password: options[:broker_password],
64
+ cors_enabled: options[:cors],
65
+ pact_specification_version: options[:pact_specification_version] || Pact::SpecificationVersion::NIL_VERSION.to_s,
66
+ pactfile_write_mode: options[:pact_file_write_mode],
67
+ stub_pactfile_paths: options[:stub_pactfile_paths]
68
+ }
69
+ service_options[:log_file] = open_log_file if options[:log]
70
+ service_options
71
+ end
72
+
73
+ def open_log_file
74
+ FileUtils.mkdir_p File.dirname(options[:log])
75
+ log = File.open(options[:log], 'w')
76
+ log.sync = true
77
+ log
78
+ end
79
+
80
+ def webbrick_opts
81
+ # By default, the webrick logs go to $stderr, which then show up as an ERROR
82
+ # log in pact-go, so it was changed to $stdout.
83
+ # $stdout needs sync = true for pact-js to determine the port dynamically from
84
+ # the output (otherwise it does not flush in time for the port to be read)
85
+ $stdout.sync = true
86
+ opts = {
87
+ :Port => port,
88
+ :Host => host,
89
+ :AccessLog => [],
90
+ :Logger => WEBrick::BasicLog.new($stdout)
91
+ }
92
+ opts.merge!({
93
+ :SSLCertificate => OpenSSL::X509::Certificate.new(File.open(options[:sslcert]).read) }) if options[:sslcert]
94
+ opts.merge!({
95
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.open(options[:sslkey]).read) }) if options[:sslkey]
96
+ opts.merge!(ssl_opts) if options[:ssl]
97
+ opts.merge!(options[:webbrick_options]) if options[:webbrick_options]
98
+ opts
99
+ end
100
+
101
+ def ssl_opts
102
+ {
103
+ :SSLEnable => true,
104
+ :SSLCertName => [ ["CN", host] ]
105
+ }
106
+ end
107
+
108
+ def port
109
+ @port ||= (options[:port] || FindAPort.available_port).to_i
110
+ end
111
+
112
+ def host
113
+ @host ||= options[:host] || "localhost"
114
+ end
115
+
116
+ def base_url
117
+ options[:ssl] ? "https://#{host}:#{port}" : "http://#{host}:#{port}"
118
+ end
119
+
120
+ def require_monkeypatch
121
+ require options[:monkeypatch] if options[:monkeypatch]
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,20 @@
1
+ require 'pact/mock_service/server/spawn'
2
+
3
+ module Pact
4
+ module MockService
5
+ module Server
6
+ class Respawn
7
+
8
+ def self.call pidfile, port, ssl = false
9
+ if pidfile.file_exists_and_process_running?
10
+ pidfile.kill_process
11
+ end
12
+
13
+ Spawn.(pidfile, port, ssl) do
14
+ yield
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,37 @@
1
+ require 'pact/mock_service/server/wait_for_server_up'
2
+
3
+ module Pact
4
+ module MockService
5
+ module Server
6
+ class Spawn
7
+
8
+ class PortUnavailableError < StandardError; end
9
+
10
+ def self.call pidfile, host, port, ssl = false
11
+ if pidfile.can_start?
12
+ if port_available? host, port
13
+ pid = fork do
14
+ yield
15
+ end
16
+ pidfile.pid = pid
17
+ Process.detach(pid)
18
+ Server::WaitForServerUp.(host, port, {ssl: ssl})
19
+ pidfile.write
20
+ else
21
+ raise PortUnavailableError.new("ERROR: Port #{port} already in use.")
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.port_available? host, port
27
+ server = TCPServer.new(host, port)
28
+ true
29
+ rescue
30
+ false
31
+ ensure
32
+ server.close if server
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ require 'timeout'
2
+ require 'net/http'
3
+ require 'openssl'
4
+
5
+ module Pact
6
+ module MockService
7
+ module Server
8
+ class WaitForServerUp
9
+
10
+ def self.call(host, port, options = {ssl: false})
11
+ tries = 0
12
+ responsive = false
13
+ while !(responsive = responsive?(host, port, options)) && tries < 100
14
+ tries += 1
15
+ sleep 1
16
+ end
17
+ raise "Timed out waiting for server to start up on port #{port}" if !responsive
18
+ end
19
+
20
+ def self.responsive? host, port, options
21
+ http = Net::HTTP.new(host, port)
22
+ if options[:ssl]
23
+ http.use_ssl = true
24
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
25
+ scheme = 'https'
26
+ else
27
+ scheme = 'http'
28
+ end
29
+ http.start {
30
+ request = Net::HTTP::Get.new "#{scheme}://#{host}:#{port}/"
31
+ request['X-Pact-Mock-Service'] = true
32
+ response = http.request request
33
+ response.code == '200'
34
+ }
35
+ rescue SystemCallError => e
36
+ return false
37
+ rescue EOFError
38
+ return false
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,15 @@
1
+ module WEBrick
2
+ class HTTPRequest
3
+ alias_method :pact_original_meta_vars, :meta_vars
4
+
5
+ def meta_vars
6
+ original_underscored_headers = []
7
+ self.each{|key, val| original_underscored_headers << key if key.include?("_") }
8
+ # This header allows us to restore the original format (eg. underscored) of the headers
9
+ # when parsing the incoming Rack env back to a response object.
10
+ vars = pact_original_meta_vars
11
+ vars["X_PACT_UNDERSCORED_HEADER_NAMES"] = original_underscored_headers.join(",")
12
+ vars
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,96 @@
1
+ require 'pact/mock_service/interactions/expected_interactions'
2
+ require 'pact/mock_service/interactions/actual_interactions'
3
+ require 'pact/mock_service/interactions/verified_interactions'
4
+ require 'pact/mock_service/interaction_decorator'
5
+ require 'pact/mock_service/interactions/interaction_diff_message'
6
+ require 'pact/mock_service/errors'
7
+
8
+ module Pact
9
+ module MockService
10
+ class Session
11
+
12
+ attr_reader :expected_interactions, :actual_interactions, :verified_interactions, :consumer_contract_details, :logger
13
+
14
+ def initialize options
15
+ @pact_written = false
16
+ @logger = options[:logger]
17
+ @expected_interactions = Interactions::ExpectedInteractions.new
18
+ @actual_interactions = Interactions::ActualInteractions.new
19
+ @verified_interactions = Interactions::VerifiedInteractions.new
20
+ @warn_on_too_many_interactions = options[:warn_on_too_many_interactions] || false
21
+ @max_concurrent_interactions_before_warning = get_max_concurrent_interactions_before_warning
22
+ @consumer_contract_details = {
23
+ pact_dir: options[:pact_dir],
24
+ consumer: {name: options[:consumer]},
25
+ provider: {name: options[:provider]},
26
+ interactions: verified_interactions,
27
+ pact_specification_version: options[:pact_specification_version],
28
+ pactfile_write_mode: options[:pactfile_write_mode]
29
+ }
30
+ end
31
+
32
+ def pact_written?
33
+ @pact_written
34
+ end
35
+
36
+ def record_pact_written
37
+ @pact_written = true
38
+ end
39
+
40
+ def set_expected_interactions interactions
41
+ clear_expected_and_actual_interactions
42
+ interactions.each do | interaction |
43
+ add_expected_interaction interaction
44
+ end
45
+ end
46
+
47
+ def clear_expected_and_actual_interactions
48
+ expected_interactions.clear
49
+ actual_interactions.clear
50
+ end
51
+
52
+ def clear_all
53
+ expected_interactions.clear
54
+ actual_interactions.clear
55
+ verified_interactions.clear
56
+ @pact_written = false
57
+ end
58
+
59
+ def add_expected_interaction interaction
60
+ if (previous_interaction = interaction_already_verified_with_same_description_and_provider_state_but_not_equal(interaction))
61
+ handle_almost_duplicate_interaction previous_interaction, interaction
62
+ else
63
+ really_add_expected_interaction interaction
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ attr_reader :warn_on_too_many_interactions, :max_concurrent_interactions_before_warning
70
+
71
+ def really_add_expected_interaction interaction
72
+ expected_interactions << interaction
73
+ logger.info "Registered expected interaction #{interaction.request.method_and_path}"
74
+ logger.debug JSON.pretty_generate InteractionDecorator.new(interaction)
75
+ if warn_on_too_many_interactions && expected_interactions.size > max_concurrent_interactions_before_warning
76
+ logger.warn "You currently have #{expected_interactions.size} interactions mocked at the same time. This suggests the scope of your consumer tests is larger than recommended, and you may find them hard to debug and maintain. See https://pact.io/too-many-interactions for more information."
77
+ end
78
+ end
79
+
80
+ def handle_almost_duplicate_interaction previous_interaction, interaction
81
+ message = Interactions::InteractionDiffMessage.new(previous_interaction, interaction).to_s
82
+ logger.error message
83
+ raise SameSameButDifferentError, message
84
+ end
85
+
86
+ def interaction_already_verified_with_same_description_and_provider_state_but_not_equal interaction
87
+ other = verified_interactions.find_matching_description_and_provider_state interaction
88
+ other && other != interaction ? other : nil
89
+ end
90
+
91
+ def get_max_concurrent_interactions_before_warning
92
+ ENV['PACT_MAX_CONCURRENT_INTERACTIONS_BEFORE_WARNING'] ? ENV['PACT_MAX_CONCURRENT_INTERACTIONS_BEFORE_WARNING'].to_i : 3
93
+ end
94
+ end
95
+ end
96
+ end