pact-mock_service 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +29 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +100 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +314 -0
  10. data/Rakefile +6 -0
  11. data/bin/pact-mock-service +3 -0
  12. data/lib/pact/consumer/app_manager.rb +159 -0
  13. data/lib/pact/consumer/interactions_filter.rb +48 -0
  14. data/lib/pact/consumer/mock_service/app.rb +84 -0
  15. data/lib/pact/consumer/mock_service/interaction_delete.rb +33 -0
  16. data/lib/pact/consumer/mock_service/interaction_list.rb +76 -0
  17. data/lib/pact/consumer/mock_service/interaction_mismatch.rb +73 -0
  18. data/lib/pact/consumer/mock_service/interaction_post.rb +31 -0
  19. data/lib/pact/consumer/mock_service/interaction_replay.rb +139 -0
  20. data/lib/pact/consumer/mock_service/log_get.rb +28 -0
  21. data/lib/pact/consumer/mock_service/missing_interactions_get.rb +30 -0
  22. data/lib/pact/consumer/mock_service/mock_service_administration_endpoint.rb +31 -0
  23. data/lib/pact/consumer/mock_service/pact_post.rb +33 -0
  24. data/lib/pact/consumer/mock_service/rack_request_helper.rb +51 -0
  25. data/lib/pact/consumer/mock_service/verification_get.rb +68 -0
  26. data/lib/pact/consumer/mock_service.rb +2 -0
  27. data/lib/pact/consumer/mock_service_client.rb +65 -0
  28. data/lib/pact/consumer/mock_service_interaction_expectation.rb +37 -0
  29. data/lib/pact/consumer/request.rb +27 -0
  30. data/lib/pact/consumer/server.rb +90 -0
  31. data/lib/pact/consumer_contract/consumer_contract_writer.rb +84 -0
  32. data/lib/pact/mock_service/cli.rb +49 -0
  33. data/lib/pact/mock_service/version.rb +5 -0
  34. data/lib/pact/mock_service.rb +1 -0
  35. data/pact-mock-service.gemspec +41 -0
  36. data/spec/lib/pact/consumer/app_manager_spec.rb +42 -0
  37. data/spec/lib/pact/consumer/mock_service/app_spec.rb +52 -0
  38. data/spec/lib/pact/consumer/mock_service/interaction_list_spec.rb +78 -0
  39. data/spec/lib/pact/consumer/mock_service/interaction_mismatch_spec.rb +70 -0
  40. data/spec/lib/pact/consumer/mock_service/interaction_replay_spec.rb +12 -0
  41. data/spec/lib/pact/consumer/mock_service/rack_request_helper_spec.rb +88 -0
  42. data/spec/lib/pact/consumer/mock_service/verification_get_spec.rb +142 -0
  43. data/spec/lib/pact/consumer/mock_service_client_spec.rb +88 -0
  44. data/spec/lib/pact/consumer/mock_service_interaction_expectation_spec.rb +54 -0
  45. data/spec/lib/pact/consumer/service_consumer_spec.rb +11 -0
  46. data/spec/lib/pact/consumer_contract/consumer_contract_writer_spec.rb +111 -0
  47. data/spec/spec_helper.rb +16 -0
  48. data/spec/support/a_consumer-a_producer.json +32 -0
  49. data/spec/support/a_consumer-a_provider.json +32 -0
  50. data/spec/support/active_support_if_configured.rb +6 -0
  51. data/spec/support/app_for_config_ru.rb +4 -0
  52. data/spec/support/case-insensitive-response-header-matching.json +21 -0
  53. data/spec/support/case-insensitive-response-header-matching.rb +15 -0
  54. data/spec/support/consumer_contract_template.json +24 -0
  55. data/spec/support/dsl_spec_support.rb +7 -0
  56. data/spec/support/factories.rb +82 -0
  57. data/spec/support/generated_index.md +4 -0
  58. data/spec/support/generated_markdown.md +55 -0
  59. data/spec/support/interaction_view_model.json +63 -0
  60. data/spec/support/interaction_view_model_with_terms.json +50 -0
  61. data/spec/support/markdown_pact.json +48 -0
  62. data/spec/support/missing_provider_states_output.txt +25 -0
  63. data/spec/support/options.json +21 -0
  64. data/spec/support/options_app.rb +15 -0
  65. data/spec/support/pact_helper.rb +57 -0
  66. data/spec/support/shared_examples_for_request.rb +94 -0
  67. data/spec/support/spec_support.rb +20 -0
  68. data/spec/support/stubbing.json +22 -0
  69. data/spec/support/stubbing_using_allow.rb +29 -0
  70. data/spec/support/term.json +48 -0
  71. data/spec/support/test_app_fail.json +61 -0
  72. data/spec/support/test_app_pass.json +38 -0
  73. data/spec/support/test_app_with_right_content_type_differ.json +23 -0
  74. data/tasks/spec.rake +6 -0
  75. metadata +388 -0
@@ -0,0 +1,68 @@
1
+ require 'pact/consumer/mock_service/mock_service_administration_endpoint'
2
+
3
+ module Pact
4
+ module Consumer
5
+ class VerificationGet < MockServiceAdministrationEndpoint
6
+
7
+ include RackRequestHelper
8
+ attr_accessor :interaction_list, :log_description
9
+
10
+ def initialize name, logger, interaction_list, log_description
11
+ super name, logger
12
+ @interaction_list = interaction_list
13
+ @log_description = log_description
14
+ end
15
+
16
+ def request_path
17
+ '/interactions/verification'
18
+ end
19
+
20
+ def request_method
21
+ 'GET'
22
+ end
23
+
24
+ def respond env
25
+ if interaction_list.all_matched?
26
+ logger.info "Verifying - interactions matched for example \"#{example_description(env)}\""
27
+ [200, {'Content-Type' => 'text/plain'}, ['Interactions matched']]
28
+ else
29
+ error_message = FailureMessage.new(interaction_list).to_s
30
+ logger.warn "Verifying - actual interactions do not match expected interactions for example \"#{example_description(env)}\". \n#{error_message}"
31
+ logger.warn error_message
32
+ [500, {'Content-Type' => 'text/plain'}, ["Actual interactions do not match expected interactions for mock #{name}.\n\n#{error_message}See #{log_description} for details."]]
33
+ end
34
+ end
35
+
36
+ def example_description env
37
+ params_hash(env)['example_description']
38
+ end
39
+
40
+ class FailureMessage
41
+
42
+ def initialize interaction_list
43
+ @interaction_list = interaction_list
44
+ end
45
+
46
+ def to_s
47
+ titles_and_summaries.collect do | title, summaries |
48
+ "#{title}:\n\t#{summaries.join("\n\t")}\n\n" if summaries.any?
49
+ end.compact.join
50
+
51
+ end
52
+
53
+ private
54
+
55
+ attr_reader :interaction_list
56
+
57
+ def titles_and_summaries
58
+ {
59
+ "Incorrect requests" => interaction_list.interaction_mismatches_summaries,
60
+ "Missing requests" => interaction_list.missing_interactions_summaries,
61
+ "Unexpected requests" => interaction_list.unexpected_requests_summaries,
62
+ }
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,2 @@
1
+ require 'pact/consumer/mock_service/app'
2
+
@@ -0,0 +1,65 @@
1
+ require 'net/http'
2
+ require 'pact/consumer/mock_service_interaction_expectation'
3
+
4
+ module Pact
5
+ module Consumer
6
+ class MockServiceClient
7
+
8
+ MOCK_SERVICE_ADMINISTRATON_HEADERS = {'X-Pact-Mock-Service' => 'true'}
9
+
10
+ def initialize port
11
+ @http = Net::HTTP.new('localhost', port)
12
+ end
13
+
14
+ def verify example_description
15
+ response = http.request_get("/interactions/verification?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
16
+ raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
17
+ end
18
+
19
+ def log msg
20
+ http.request_get("/log?msg=#{URI.encode(msg)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
21
+ end
22
+
23
+ def wait_for_interactions wait_max_seconds, poll_interval
24
+ wait_until_true(wait_max_seconds, poll_interval) do
25
+ response = http.request_get("/interactions/missing", MOCK_SERVICE_ADMINISTRATON_HEADERS)
26
+ JSON.parse(response.body)['size'] == 0
27
+ end
28
+ end
29
+
30
+ def clear_interactions example_description
31
+ http.delete("/interactions?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
32
+ end
33
+
34
+ def add_expected_interaction interaction
35
+ response = http.request_post('/interactions', MockServiceInteractionExpectation.new(interaction).to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
36
+ raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
37
+ end
38
+
39
+ def self.clear_interactions port, example_description
40
+ Net::HTTP.new("localhost", port).delete("/interactions?example_description=#{URI.encode(example_description)}", MOCK_SERVICE_ADMINISTRATON_HEADERS)
41
+ end
42
+
43
+ def write_pact pacticipant_details
44
+ response = http.request_post("/pact", pacticipant_details.to_json, MOCK_SERVICE_ADMINISTRATON_HEADERS.merge("Content-Type" => "application/json"))
45
+ raise "\e[31m#{response.body}\e[m" unless response.is_a? Net::HTTPSuccess
46
+ response.body
47
+ end
48
+
49
+ private
50
+
51
+ attr_reader :http
52
+
53
+ #todo: in need a better home (where can we move it?)
54
+ def wait_until_true timeout=3, interval=0.1
55
+ time_limit = Time.now + timeout
56
+ loop do
57
+ result = yield
58
+ return if result || Time.now >= time_limit
59
+ sleep interval
60
+ end
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,37 @@
1
+ require 'pact/reification'
2
+
3
+ # Represents the Interaction in the form required by the MockService
4
+ # The json generated will be posted to the MockService to register the expectation
5
+ module Pact
6
+ module Consumer
7
+ class MockServiceInteractionExpectation
8
+
9
+
10
+ def initialize interaction
11
+ @interaction = interaction
12
+ end
13
+
14
+ def to_hash
15
+ hash = {:description => interaction.description}
16
+ hash[:provider_state] = interaction.provider_state if interaction.provider_state
17
+ options = interaction.request.options.empty? ? {} : { options: interaction.request.options}
18
+ hash[:request] = interaction.request.as_json.merge(options)
19
+ hash[:response] = interaction.response
20
+ hash
21
+ end
22
+
23
+ def as_json options = {}
24
+ to_hash
25
+ end
26
+
27
+ def to_json opts = {}
28
+ as_json.to_json(opts)
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :interaction
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ require 'pact/shared/request'
2
+ require 'pact/shared/key_not_found'
3
+
4
+ module Pact
5
+ module Consumer
6
+ module Request
7
+ class Actual < Pact::Request::Base
8
+
9
+ def self.from_hash(hash)
10
+ sym_hash = symbolize_keys hash
11
+ method = sym_hash.fetch(:method)
12
+ path = sym_hash.fetch(:path)
13
+ query = sym_hash.fetch(:query)
14
+ headers = sym_hash.fetch(:headers)
15
+ body = sym_hash.fetch(:body, nil)
16
+ new(method, path, headers, body, query)
17
+ end
18
+
19
+ protected
20
+
21
+ def self.key_not_found
22
+ nil
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,90 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'rack'
4
+
5
+ # Copied shamelessly from Capybara
6
+ module Pact
7
+ class Server
8
+ class Middleware
9
+ attr_accessor :error
10
+
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ if env["PATH_INFO"] == "/__identify__"
17
+ [200, {}, [@app.object_id.to_s]]
18
+ else
19
+ begin
20
+ @app.call(env)
21
+ rescue StandardError => e
22
+ @error = e unless @error
23
+ raise e
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ class << self
30
+ def ports
31
+ @ports ||= {}
32
+ end
33
+ end
34
+
35
+ attr_reader :app, :port
36
+
37
+ def initialize(app, port)
38
+ @app = app
39
+ @middleware = Middleware.new(@app)
40
+ @server_thread = nil # supress warnings
41
+ @port = port
42
+ end
43
+
44
+ def reset_error!
45
+ @middleware.error = nil
46
+ end
47
+
48
+ def error
49
+ @middleware.error
50
+ end
51
+
52
+ def host
53
+ "localhost"
54
+ end
55
+
56
+ def responsive?
57
+ return false if @server_thread && @server_thread.join(0)
58
+
59
+ res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
60
+
61
+ if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
62
+ return res.body == @app.object_id.to_s
63
+ end
64
+ rescue SystemCallError
65
+ return false
66
+ end
67
+
68
+ def run_default_server(app, port)
69
+ require 'rack/handler/webrick'
70
+ Rack::Handler::WEBrick.run(app, :Port => port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0))
71
+ end
72
+
73
+ def boot
74
+ unless responsive?
75
+ Pact::Server.ports[@app.object_id] = @port
76
+
77
+ @server_thread = Thread.new do
78
+ run_default_server(@middleware, @port)
79
+ end
80
+
81
+ Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
82
+ end
83
+ rescue Timeout::Error
84
+ raise "Rack application timed out during boot"
85
+ else
86
+ self
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,84 @@
1
+ require 'pact/consumer_contract'
2
+ require 'pact/consumer/interactions_filter'
3
+ require 'pact/consumer_contract/file_name'
4
+
5
+ module Pact
6
+
7
+ class ConsumerContractWriter
8
+
9
+ attr_reader :consumer_contract_details, :pactfile_write_mode, :interactions, :logger
10
+
11
+ def initialize consumer_contract_details, logger
12
+ @logger = logger
13
+ @consumer_contract_details = consumer_contract_details
14
+ @pactfile_write_mode = consumer_contract_details.fetch(:pactfile_write_mode, :overwrite).to_sym
15
+ @interactions = consumer_contract_details.fetch(:interactions)
16
+ end
17
+
18
+ def consumer_contract
19
+ @consumer_contract ||= Pact::ConsumerContract.new(
20
+ consumer: ServiceConsumer.new(name: consumer_contract_details[:consumer][:name]),
21
+ provider: ServiceProvider.new(name: consumer_contract_details[:provider][:name]),
22
+ interactions: interactions_for_new_consumer_contract)
23
+ end
24
+
25
+ def write
26
+ consumer_contract.update_pactfile
27
+ consumer_contract.to_json
28
+ end
29
+
30
+ def interactions_for_new_consumer_contract
31
+ if pactfile_write_mode == :update
32
+ merged_interactions = existing_interactions
33
+ filter = Consumer::UpdatableInteractionsFilter.new(merged_interactions)
34
+ interactions.each {|i| filter << i }
35
+ merged_interactions
36
+ else
37
+ interactions
38
+ end
39
+ end
40
+
41
+ def existing_interactions
42
+ interactions = []
43
+ if pactfile_exists?
44
+ begin
45
+ interactions = existing_consumer_contract.interactions
46
+ info_and_puts "*****************************************************************************"
47
+ info_and_puts "Updating existing file .#{pactfile_path.gsub(Dir.pwd, '')} as config.pactfile_write_mode is :update"
48
+ info_and_puts "Only interactions defined in this test run will be updated."
49
+ info_and_puts "As interactions are identified by description and provider state, pleased note that if either of these have changed, the old interactions won't be removed from the pact file until the specs are next run with :pactfile_write_mode => :overwrite."
50
+ info_and_puts "*****************************************************************************"
51
+ rescue StandardError => e
52
+ warn_and_stderr "Could not load existing consumer contract from #{pactfile_path} due to #{e}"
53
+ logger.error e
54
+ logger.error e.backtrace
55
+ warn_and_stderr "Creating a new file."
56
+ end
57
+ end
58
+ interactions
59
+ end
60
+
61
+ def pactfile_exists?
62
+ File.exist?(pactfile_path)
63
+ end
64
+
65
+ def pactfile_path
66
+ Pact::FileName.file_path consumer_contract_details[:consumer][:name], consumer_contract_details[:provider][:name]
67
+ end
68
+
69
+ def existing_consumer_contract
70
+ Pact::ConsumerContract.from_uri(pactfile_path)
71
+ end
72
+
73
+ def warn_and_stderr msg
74
+ Pact.configuration.error_stream.puts msg
75
+ logger.warn msg
76
+ end
77
+
78
+ def info_and_puts msg
79
+ $stdout.puts msg
80
+ logger.info msg
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,49 @@
1
+ require 'find_a_port'
2
+ require 'thor'
3
+ require 'thwait'
4
+ require 'rack/handler/webrick'
5
+
6
+ # TODO rename CLI so as not to clash
7
+
8
+ module Pact
9
+ module MockService
10
+ class CLI < Thor
11
+
12
+ desc 'service', "Start a mock service"
13
+ method_option :port, aliases: "-p", desc: "Port on which to run the service"
14
+ method_option :log, aliases: "-l", desc: "File to which to log output"
15
+ method_option :quiet, aliases: "-q", desc: "If true, no admin messages will be shown"
16
+
17
+ def service
18
+ RunStandaloneMockService.call(options)
19
+ end
20
+
21
+ default_task :service
22
+
23
+ private
24
+
25
+ def log message
26
+ puts message unless options[:quiet]
27
+ end
28
+ end
29
+
30
+ class RunStandaloneMockService
31
+
32
+ def self.call options
33
+ require 'pact/consumer/mock_service/app'
34
+ service_options = {}
35
+ if options[:log]
36
+ log = File.open(options[:log], 'w')
37
+ log.sync = true
38
+ service_options[:log_file] = log
39
+ end
40
+
41
+ port = options[:port] || FindAPort.available_port
42
+ mock_service = Pact::Consumer::MockService.new(service_options)
43
+ trap(:INT) { Rack::Handler::WEBrick.shutdown }
44
+ Rack::Handler::WEBrick.run(mock_service, :Port => port, :AccessLog => [])
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ module Pact
2
+ module MockService
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'pact/consumer/mock_service'
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pact/mock_service/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "pact-mock_service"
8
+ gem.version = Pact::MockService::VERSION
9
+ gem.authors = ["James Fraser", "Sergei Matheson", "Brent Snook", "Ronald Holshausen", "Beth Skurrie"]
10
+ gem.email = ["james.fraser@alumni.swinburne.edu", "sergei.matheson@gmail.com", "brent@fuglylogic.com", "uglyog@gmail.com", "bskurrie@dius.com.au"]
11
+ gem.summary = %q{Provides a mock service for use with Pact}
12
+ gem.homepage = "https://github.com/bethesque/pact-mock_service"
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+ gem.license = 'MIT'
19
+
20
+ gem.add_runtime_dependency 'rack'
21
+ gem.add_runtime_dependency 'rspec', '>=2.14'
22
+ gem.add_runtime_dependency 'find_a_port', '~> 1.0.1'
23
+ gem.add_runtime_dependency 'rack-test', '~> 0.6.2'
24
+ gem.add_runtime_dependency 'awesome_print', '~> 1.1'
25
+ gem.add_runtime_dependency 'thor'
26
+ gem.add_runtime_dependency 'json' #Not locking down a version because buncher gem requires 1.6, while other projects use 1.7.
27
+ gem.add_runtime_dependency 'webrick'
28
+ gem.add_runtime_dependency 'term-ansicolor', '~> 1.0'
29
+
30
+ unless ENV['X_PACT_DEVELOPMENT']
31
+ gem.add_runtime_dependency 'pact-support', '~> 0.0.1'
32
+ end
33
+
34
+ gem.add_development_dependency 'rake', '~> 10.0.3'
35
+ gem.add_development_dependency 'webmock', '~> 1.18.0'
36
+ gem.add_development_dependency 'pry'
37
+ gem.add_development_dependency 'fakefs', '~> 0.4'
38
+ gem.add_development_dependency 'hashie', '~> 2.0'
39
+ gem.add_development_dependency 'activesupport'
40
+ gem.add_development_dependency 'faraday'
41
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'pact/consumer/app_manager'
3
+
4
+ module Pact::Consumer
5
+ describe AppManager do
6
+ before do
7
+ AppManager.instance.clear_all
8
+ end
9
+
10
+ describe "start_service_for" do
11
+ before do
12
+ allow_any_instance_of(AppRegistration).to receive(:spawn) # Don't want process actually spawning during the tests
13
+ end
14
+ let(:name) { 'some_service'}
15
+ context "for http://localhost" do
16
+ let(:url) { 'http://localhost:1234'}
17
+ it "starts a mock service at the given port on localhost" do
18
+ expect_any_instance_of(AppRegistration).to receive(:spawn)
19
+ AppManager.instance.register_mock_service_for name, url
20
+ AppManager.instance.spawn_all
21
+ end
22
+
23
+ it "registers the mock service as running on the given port" do
24
+ AppManager.instance.register_mock_service_for name, url
25
+ expect(AppManager.instance.app_registered_on?(1234)).to eq true
26
+ end
27
+ end
28
+ context "for https://" do
29
+ let(:url) { 'https://localhost:1234'}
30
+ it "should throw an unsupported error" do
31
+ expect { AppManager.instance.register_mock_service_for name, url }.to raise_error "Currently only http is supported"
32
+ end
33
+ end
34
+ context "for a host other than localhost" do
35
+ let(:url) { 'http://aserver:1234'}
36
+ it "should throw an unsupported error" do
37
+ expect { AppManager.instance.register_mock_service_for name, url }.to raise_error "Currently only services on localhost are supported"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+ require 'tempfile'
4
+
5
+ module Pact
6
+ module Consumer
7
+
8
+ describe MockService do
9
+
10
+ include Rack::Test::Methods
11
+
12
+ def app
13
+ MockService.new(log_file: temp_file)
14
+ end
15
+
16
+ let(:temp_file) { Tempfile.new('log') }
17
+
18
+ after do
19
+ temp_file.close
20
+ temp_file.unlink
21
+ end
22
+
23
+ context "when a StandardError is encountered" do
24
+ let(:response) { JSON.parse(last_response.body)}
25
+ let(:interaction_replay) { double(InteractionReplay, :match? => true)}
26
+
27
+ before do
28
+ expect(InteractionReplay).to receive(:new).and_return(interaction_replay)
29
+ expect(interaction_replay).to receive(:respond).and_raise("an error")
30
+ end
31
+
32
+ subject { get "/" }
33
+
34
+ it "returns a json error" do
35
+ subject
36
+ expect(last_response.content_type).to eq 'application/json'
37
+ end
38
+
39
+ it "includes the error message" do
40
+ subject
41
+ expect(response['message']).to eq "an error"
42
+ end
43
+
44
+ it "includes the backtrace" do
45
+ subject
46
+ expect(response['backtrace']).to be_instance_of Array
47
+ end
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+ require 'pact/consumer/mock_service/interaction_list'
3
+
4
+ module Pact::Consumer
5
+
6
+ describe InteractionList do
7
+ shared_context "unexpected requests and missed interactions" do
8
+ let(:expected_interaction) { InteractionFactory.create }
9
+ let(:unexpected_request) { RequestFactory.create_actual method: 'put' }
10
+ let(:candidate_interaction) { double("Pact::Interaction") }
11
+ let(:candidate_interactions) { [candidate_interaction] }
12
+ let(:interaction_mismatch) { instance_double("Pact::Consumer::InteractionMismatch", :short_summary => 'blah', :candidate_interactions => candidate_interactions)}
13
+ subject {
14
+ interactionList = InteractionList.new
15
+ interactionList.add expected_interaction
16
+ interactionList.register_unexpected_request unexpected_request
17
+ interactionList.register_interaction_mismatch interaction_mismatch
18
+ interactionList
19
+ }
20
+ end
21
+
22
+ shared_context "no unexpected requests or missed interactions exist" do
23
+ let(:expected_interaction) { InteractionFactory.create }
24
+ subject {
25
+ interactionList = InteractionList.new
26
+ interactionList.add expected_interaction
27
+ interactionList.register_matched expected_interaction
28
+ interactionList
29
+ }
30
+ end
31
+
32
+ describe "interaction_diffs" do
33
+ context "when unexpected requests and missed interactions exist" do
34
+ include_context "unexpected requests and missed interactions"
35
+ let(:expected_diff) {
36
+ {:missing_interactions=>["GET /path"],
37
+ :unexpected_requests=>["PUT /path?query"],
38
+ :interaction_mismatches => ['blah']}
39
+ }
40
+ it "returns the unexpected requests and missed interactions" do
41
+ expect(subject.interaction_diffs).to eq expected_diff
42
+ end
43
+ end
44
+
45
+ context "when no unexpected requests or missed interactions exist" do
46
+ include_context "no unexpected requests or missed interactions exist"
47
+ let(:expected_diff) {
48
+ {}
49
+ }
50
+ it "returns an empty hash" do
51
+ expect(subject.interaction_diffs).to eq expected_diff
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "all_matched?" do
57
+ context "when unexpected requests or missed interactions exist" do
58
+ include_context "unexpected requests and missed interactions"
59
+ it "returns false" do
60
+ expect(subject.all_matched?).to be false
61
+ end
62
+ end
63
+ context "when unexpected requests or missed interactions do not exist" do
64
+ include_context "no unexpected requests or missed interactions exist"
65
+ it "returns false" do
66
+ expect(subject.all_matched?).to be true
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "missing_interactions_summaries" do
72
+ include_context "unexpected requests and missed interactions"
73
+ it "returns a list of the method and paths for each missing interaction" do
74
+ expect(subject.missing_interactions_summaries).to eq ["GET /path"]
75
+ end
76
+ end
77
+ end
78
+ end