hs-pact-mock_service 3.9.2

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 (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