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,79 @@
1
+ require 'net/http'
2
+ require 'pact/mock_service/interaction_decorator'
3
+ require 'pact/errors'
4
+
5
+ module Pact
6
+ module MockService
7
+
8
+ class VerificationFailedError < Pact::Error; end
9
+ class AddInteractionError < Pact::Error; end
10
+ class WritePactError < Pact::Error; end
11
+
12
+ class Client
13
+
14
+ MOCK_SERVICE_ADMINISTRATON_HEADERS = {'X-Pact-Mock-Service' => 'true'}
15
+
16
+ def initialize port, host = 'localhost'
17
+ @http = Net::HTTP.new(host, port)
18
+ end
19
+
20
+ def verify example_description
21
+ response = http.request_get("/interactions/verification?example_description=#{CGI.escape(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
22
+ raise VerificationFailedError.new("\e[31m#{response.body}\e[m") unless response.is_a? Net::HTTPSuccess
23
+ end
24
+
25
+ def log msg
26
+ http.request_get("/log?msg=#{CGI.escape(msg)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
27
+ end
28
+
29
+ def wait_for_interactions wait_max_seconds, poll_interval
30
+ wait_until_true(wait_max_seconds, poll_interval) do
31
+ response = http.request_get("/interactions/missing", MOCK_SERVICE_ADMINISTRATON_HEADERS)
32
+ JSON.parse(response.body)['size'] == 0
33
+ end
34
+ end
35
+
36
+ def clear_interactions example_description
37
+ http.delete("/interactions?example_description=#{CGI.escape(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
38
+ end
39
+
40
+ def add_expected_interaction interaction
41
+ response = http.request_post(
42
+ '/interactions',
43
+ interaction_json(interaction),
44
+ MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json")
45
+ )
46
+ raise AddInteractionError.new("\e[31m#{response.body}\e[m") unless response.is_a? Net::HTTPSuccess
47
+ end
48
+
49
+ def self.clear_interactions mock_service_base_url, example_description
50
+ uri = URI(mock_service_base_url)
51
+ Net::HTTP.new(uri.host, uri.port).delete("/interactions?example_description=#{CGI.escape(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
52
+ end
53
+
54
+ def write_pact pacticipant_details
55
+ response = http.request_post("/pact", pacticipant_details.to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
56
+ raise WritePactError.new("\e[31m#{response.body}\e[m") unless response.is_a? Net::HTTPSuccess
57
+ response.body
58
+ end
59
+
60
+ private
61
+
62
+ attr_reader :http
63
+
64
+ #todo: in need a better home (where can we move it?)
65
+ def wait_until_true timeout=3, interval=0.1
66
+ time_limit = Time.now + timeout
67
+ loop do
68
+ result = yield
69
+ return if result || Time.now >= time_limit
70
+ sleep interval
71
+ end
72
+ end
73
+
74
+ def interaction_json interaction
75
+ Pact::MockService::InteractionDecorator.new(interaction).to_json
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,42 @@
1
+ require 'pact/consumer/mock_service/rack_request_helper'
2
+ require 'pact/mock_service/control_server/require_pacticipant_headers'
3
+ require 'pact/mock_service/control_server/index'
4
+ require 'pact/mock_service/control_server/mock_services'
5
+ require 'pact/mock_service/control_server/mock_service_creator'
6
+ require 'rack'
7
+ require 'rack/cascade'
8
+
9
+ module Pact
10
+ module MockService
11
+ module ControlServer
12
+ class App
13
+
14
+ include Pact::Consumer::RackRequestHelper
15
+
16
+ def initialize options = {}
17
+ @mock_services = mock_services = MockServices.new([])
18
+ @app = Rack::Builder.new {
19
+ run Rack::Cascade.new([
20
+ Index.new,
21
+ Rack::Builder.new {
22
+ use RequirePacticipantHeaders
23
+ run Rack::Cascade.new([
24
+ mock_services,
25
+ MockServiceCreator.new(mock_services, options)
26
+ ])
27
+ }
28
+ ])
29
+ }
30
+ end
31
+
32
+ def call env
33
+ @app.call(env)
34
+ end
35
+
36
+ def shutdown
37
+ @mock_services.shutdown
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,44 @@
1
+ # Delegates the incoming request that was sent to the control server
2
+ # to the underlying MockService
3
+ # if the X-Pact-Consumer and X-Pact-Provider headers match
4
+ # the consumer and provider for this MockService.
5
+
6
+ module Pact
7
+ module MockService
8
+ module ControlServer
9
+
10
+ class Delegator
11
+
12
+ HTTP_X_PACT_CONSUMER = 'HTTP_X_PACT_CONSUMER'.freeze
13
+ HTTP_X_PACT_PROVIDER = 'HTTP_X_PACT_PROVIDER'.freeze
14
+ PACT_MOCK_SERVICE_HEADER = {'HTTP_X_PACT_MOCK_SERVICE' => 'true'}.freeze
15
+ NOT_FOUND_RESPONSE = [404, {}, []].freeze
16
+
17
+ def initialize app, consumer_name, provider_name
18
+ @app = app
19
+ @consumer_name = consumer_name
20
+ @provider_name = provider_name
21
+ end
22
+
23
+ def call env
24
+ return NOT_FOUND_RESPONSE unless consumer_and_provider_headers_match?(env)
25
+ delegate env
26
+ end
27
+
28
+ def shutdown
29
+ @app.shutdown
30
+ end
31
+
32
+ private
33
+
34
+ def consumer_and_provider_headers_match? env
35
+ env[HTTP_X_PACT_CONSUMER] == @consumer_name && env[HTTP_X_PACT_PROVIDER] == @provider_name
36
+ end
37
+
38
+ def delegate env
39
+ @app.call(env.merge(PACT_MOCK_SERVICE_HEADER))
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ module Pact
2
+ module MockService
3
+ module ControlServer
4
+ class Index
5
+
6
+ HTTP_X_PACT_MOCK_SERVICE = 'HTTP_X_PACT_MOCK_SERVICE'
7
+ PATH_INFO = 'PATH_INFO'
8
+ INDEX_RESPONSE = [200, {'Content-Type' => 'text/plain'}, ['Control server running']].freeze
9
+ NOT_FOUND_RESPONSE = [404, {}, []].freeze
10
+
11
+ def call env
12
+ if is_index_request_with_mock_service_header? env
13
+ INDEX_RESPONSE
14
+ else
15
+ NOT_FOUND_RESPONSE
16
+ end
17
+ end
18
+
19
+ def is_index_request_with_mock_service_header? env
20
+ env[HTTP_X_PACT_MOCK_SERVICE] && env[PATH_INFO].chomp("/").size == 0
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ require 'pact/mock_service/spawn'
2
+ require 'pact/mock_service/control_server/delegator'
3
+ require 'find_a_port'
4
+ require 'pact/mock_service/server/wait_for_server_up'
5
+
6
+ # Create a new MockService on a random port and delegate the incoming request to it
7
+
8
+ module Pact
9
+ module MockService
10
+ module ControlServer
11
+ class MockServiceCreator
12
+
13
+ attr_reader :options
14
+
15
+ def initialize mock_services, options
16
+ @mock_services = mock_services
17
+ @options = options
18
+ end
19
+
20
+ def call env
21
+ consumer_name = env['HTTP_X_PACT_CONSUMER']
22
+ provider_name = env['HTTP_X_PACT_PROVIDER']
23
+ port = FindAPort.available_port
24
+ mock_service = Pact::MockService::Spawn.(consumer_name, provider_name, options[:host] || 'localhost', port, options)
25
+ delegator = Delegator.new(mock_service, consumer_name, provider_name)
26
+ @mock_services.add(delegator)
27
+ delegator.call(env)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ require 'rack'
2
+ require 'rack/cascade'
3
+
4
+ module Pact
5
+ module MockService
6
+ module ControlServer
7
+ class MockServices < Rack::Cascade
8
+
9
+ def add app
10
+ mock_services << app
11
+ super
12
+ end
13
+
14
+ def shutdown
15
+ mock_services.each(&:shutdown)
16
+ end
17
+
18
+ private
19
+
20
+ def mock_services
21
+ @mock_services ||= []
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ module Pact
2
+ module MockService
3
+ module ControlServer
4
+ class RequirePacticipantHeaders
5
+
6
+ def initialize app
7
+ @app = app
8
+ end
9
+
10
+ def call env
11
+ if env['HTTP_X_PACT_CONSUMER'] && env['HTTP_X_PACT_PROVIDER']
12
+ @app.call(env)
13
+ else
14
+ [500, {}, ["Please specify the consumer name and the provider name by setting the X-Pact-Consumer and X-Pact-Provider headers"]]
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,73 @@
1
+ require 'pact/mock_service/control_server/app'
2
+ require 'pact/mock_service/server/webrick_request_monkeypatch'
3
+
4
+ module Pact
5
+ module MockService
6
+ module ControlServer
7
+ class Run
8
+
9
+ def self.call options
10
+ new(options).call
11
+ end
12
+
13
+ def initialize options
14
+ @options = options
15
+ end
16
+
17
+ def call
18
+ trap(:INT) { shutdown }
19
+ trap(:TERM) { shutdown }
20
+
21
+ # https://github.com/rack/rack/blob/ae78184e5c1fcf4ac133171ed4b47b0548cd9b44/lib/rack/handler/webrick.rb#L32
22
+ # Rack adapter for webrick uses class variable for the server which contains the port,
23
+ # so if we use it more than once in the same process, we lose the reference to the first
24
+ # server, and can't shut it down. So, keep a manual reference to the Webrick server, and
25
+ # shut it down directly rather than use Rack::Handler::WEBrick.shutdown
26
+ # Ruby!
27
+ Rack::Handler::WEBrick.run(control_server, **webbrick_opts) do | server |
28
+ @webrick_server = server
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :options
35
+
36
+ def control_server
37
+ @control_server ||= Pact::MockService::ControlServer::App.new control_server_options
38
+ end
39
+
40
+ def shutdown
41
+ unless @shutting_down
42
+ @shutting_down = true
43
+ begin
44
+ @control_server.shutdown
45
+ ensure
46
+ @webrick_server.shutdown
47
+ end
48
+ end
49
+ end
50
+
51
+ def control_server_options
52
+ {
53
+ log_dir: options[:log_dir] || "log",
54
+ pact_dir: options[:pact_dir] || ".",
55
+ unique_pact_file_names: options[:unique_pact_file_names],
56
+ cors_enabled: options[:cors] || false,
57
+ ssl: options[:ssl],
58
+ host: options[:host],
59
+ pact_specification_version: options[:pact_specification_version]
60
+ }
61
+ end
62
+
63
+ def webbrick_opts
64
+ {
65
+ :Port => options[:port],
66
+ :Host => options[:host],
67
+ :AccessLog => []
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,9 @@
1
+ require 'pact/errors'
2
+
3
+ module Pact
4
+ module MockService
5
+ class AlmostDuplicateInteractionError < Pact::Error; end
6
+
7
+ class SameSameButDifferentError < ::Pact::Error; end
8
+ end
9
+ end
@@ -0,0 +1,49 @@
1
+ require 'pact/shared/active_support_support'
2
+ require 'pact/mock_service/request_decorator'
3
+ require 'pact/mock_service/response_decorator'
4
+
5
+ # Represents the Interaction in the form required by the MockService
6
+ # The json generated will be posted to the MockService to register the expectation
7
+
8
+ module Pact
9
+ module MockService
10
+ class InteractionDecorator
11
+
12
+ include ActiveSupportSupport
13
+
14
+ def initialize interaction
15
+ @interaction = interaction
16
+ end
17
+
18
+ def as_json options = {}
19
+ fix_all_the_things to_hash
20
+ end
21
+
22
+ def to_json(options = {})
23
+ as_json.to_json(options)
24
+ end
25
+
26
+ def to_hash
27
+ hash = { :description => interaction.description }
28
+ hash[:providerState] = interaction.provider_state if interaction.provider_state
29
+ hash[:request] = decorate_request.as_json
30
+ hash[:response] = decorate_response.as_json
31
+ hash[:metadata] = interaction.metadata
32
+ hash
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :interaction
38
+
39
+ def decorate_request
40
+ RequestDecorator.new(interaction.request)
41
+ end
42
+
43
+ def decorate_response
44
+ ResponseDecorator.new(interaction.response)
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,36 @@
1
+ require 'pact/mock_service/interactions/candidate_interactions'
2
+
3
+ module Pact
4
+ module MockService
5
+ module Interactions
6
+ class ActualInteractions
7
+
8
+ attr_reader :matched_interactions, :interaction_mismatches, :unexpected_requests
9
+
10
+ def initialize
11
+ clear
12
+ end
13
+
14
+ # For testing, sigh
15
+ def clear
16
+ @matched_interactions = []
17
+ @interaction_mismatches = []
18
+ @unexpected_requests = []
19
+ end
20
+
21
+ def register_matched interaction
22
+ @matched_interactions << interaction
23
+ end
24
+
25
+ def register_unexpected_request request
26
+ @unexpected_requests << request
27
+ end
28
+
29
+ def register_interaction_mismatch interaction_mismatch
30
+ @interaction_mismatches << interaction_mismatch
31
+ end
32
+
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ module Pact
2
+ module MockService
3
+ module Interactions
4
+ class CandidateInteractions < Array
5
+
6
+ def matching_interactions actual_request
7
+ select do | candidate_interaction |
8
+ candidate_interaction.request.matches? actual_request
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,18 @@
1
+ require 'pact/mock_service/interactions/candidate_interactions'
2
+
3
+ module Pact
4
+ module MockService
5
+ module Interactions
6
+ class ExpectedInteractions < Array
7
+
8
+ def find_candidate_interactions actual_request
9
+ Pact::MockService::Interactions::CandidateInteractions.new(
10
+ select do | interaction |
11
+ interaction.request.matches_route? actual_request
12
+ end
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ require 'pact/shared/json_differ'
2
+ require 'pact/mock_service/interaction_decorator'
3
+
4
+ module Pact
5
+ module MockService
6
+ module Interactions
7
+ class InteractionDiffMessage
8
+
9
+ def initialize previous_interaction, new_interaction
10
+ @previous_interaction = previous_interaction
11
+ @new_interaction = new_interaction
12
+ end
13
+
14
+ def to_s
15
+ "An interaction with same description (#{new_interaction.description.inspect}) and provider state (#{new_interaction.provider_state.inspect}) but a different #{differences} has already been used. Please use a different description or provider state, or remove any random data in the interaction."
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :previous_interaction, :new_interaction
21
+
22
+ def differences
23
+ diff = Pact::JsonDiffer.call(previous_interaction_hash, new_interaction_hash, allow_unexpected_keys: false)
24
+ diff.keys.collect do | parent_key |
25
+ diff[parent_key].keys.collect do | child_key |
26
+ "#{parent_key} #{child_key}"
27
+ end
28
+ end.flatten.join(", ").reverse.sub(",", "dna ").reverse
29
+ end
30
+
31
+ def previous_interaction_hash
32
+ raw_hash previous_interaction
33
+ end
34
+
35
+ def new_interaction_hash
36
+ raw_hash new_interaction
37
+ end
38
+
39
+ def raw_hash interaction
40
+ JSON.parse(Pact::MockService::InteractionDecorator.new(interaction).to_json)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,74 @@
1
+ module Pact
2
+ module MockService
3
+ module Interactions
4
+
5
+ # expected interactions where the methods and paths match the actual request.
6
+ # This is used to display a helpful message to the user when a request
7
+ # comes in that doesn't match any of the expected interactions.
8
+ class InteractionMismatch
9
+
10
+ attr_accessor :candidate_interactions, :actual_request
11
+
12
+ # Assumes the method and path matches...
13
+
14
+ def initialize candidate_interactions, actual_request
15
+ @candidate_interactions = candidate_interactions
16
+ @actual_request = actual_request
17
+ @candiate_diffs = candidate_interactions.collect{ | candidate_interaction| CandidateDiff.new(candidate_interaction, actual_request)}
18
+ end
19
+
20
+ def to_hash
21
+ candiate_diffs.collect(&:to_hash)
22
+ end
23
+
24
+ def to_s
25
+ candiate_diffs.collect(&:to_s).join("\n")
26
+ end
27
+
28
+ def short_summary
29
+ mismatched_attributes = candiate_diffs.collect(&:mismatched_attributes).flatten.uniq.join(", ").reverse.sub(",", "dna ").reverse #OMG what a hack!
30
+ actual_request.method_and_path + " (request #{mismatched_attributes} did not match)"
31
+ end
32
+
33
+ private
34
+
35
+ attr_accessor :candiate_diffs
36
+
37
+ class CandidateDiff
38
+
39
+ attr_accessor :candidate_interaction, :actual_request
40
+
41
+ def initialize candidate_interaction, actual_request
42
+ @candidate_interaction = candidate_interaction
43
+ @actual_request = actual_request
44
+ end
45
+
46
+ def mismatched_attributes
47
+ diff.keys
48
+ end
49
+
50
+ def to_hash
51
+ summary = {:description => candidate_interaction.description}
52
+ summary[:provider_state] = candidate_interaction.provider_state if candidate_interaction.provider_state
53
+ summary.merge(diff)
54
+ end
55
+
56
+ def to_s
57
+ [
58
+ "Diff with interaction: #{candidate_interaction.description_with_provider_state_quoted}",
59
+ diff_formatter.call(diff, {colour: false})
60
+ ].join("\n")
61
+ end
62
+
63
+ def diff_formatter
64
+ Pact.configuration.diff_formatter_for_content_type(candidate_interaction.request.content_type)
65
+ end
66
+
67
+ def diff
68
+ @diff ||= candidate_interaction.request.difference(actual_request)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,66 @@
1
+ require 'pact/mock_service/errors'
2
+ require 'pact/mock_service/interactions/interaction_diff_message'
3
+
4
+ #
5
+ # When running in pactfile_write_mode :overwrite, all interactions are cleared from the
6
+ # pact file, and all new interactions should be distinct (unique description and provider state).
7
+ # When running in pactfile_write_mode :update, an interaction with the same description
8
+ # and provider state as an existing one will just overwrite that one interaction.
9
+ # When running in pactfile_write_mode :merge, an interaction with the same description and provider
10
+ # state must be identical to the existing one, otherwise an exception will be raised.
11
+
12
+ module Pact
13
+ module MockService
14
+ module Interactions
15
+
16
+ def self.filter existing_interactions, pactfile_write_mode
17
+ if pactfile_write_mode == :update
18
+ UpdatableInteractionsFilter.new(existing_interactions)
19
+ elsif pactfile_write_mode == :merge
20
+ MergingInteractionsFilter.new(existing_interactions)
21
+ else
22
+ existing_interactions
23
+ end
24
+ end
25
+
26
+ #TODO: think of a better word than filter
27
+ class InteractionsFilter
28
+ def initialize interactions = []
29
+ @interactions = interactions
30
+ end
31
+
32
+ def index_of interaction
33
+ @interactions.find_index{ |i| i.matches_criteria?(description: interaction.description, provider_state: interaction.provider_state)}
34
+ end
35
+ end
36
+
37
+ class UpdatableInteractionsFilter < InteractionsFilter
38
+ def << interaction
39
+ if (ndx = index_of(interaction))
40
+ @interactions[ndx] = interaction
41
+ else
42
+ @interactions << interaction
43
+ end
44
+ end
45
+ end
46
+
47
+ class MergingInteractionsFilter < InteractionsFilter
48
+ def << interaction
49
+ if (ndx = index_of(interaction))
50
+ if same_same_but_different?(@interactions[ndx], interaction)
51
+ message = Interactions::InteractionDiffMessage.new(@interactions[ndx], interaction).to_s
52
+ raise SameSameButDifferentError, message
53
+ end
54
+ @interactions[ndx] = interaction
55
+ else
56
+ @interactions << interaction
57
+ end
58
+ end
59
+
60
+ def same_same_but_different?(existing_interaction, new_interaction)
61
+ existing_interaction != new_interaction
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end