myronmarston/vcr

View on GitHub
lib/vcr/library_hooks/fakeweb.rb

Summary

Maintainability
A
1 hr
Test Coverage
require 'vcr/util/version_checker'
require 'fakeweb'
require 'net/http'
require 'vcr/extensions/net_http_response'
require 'vcr/request_handler'
require 'set'

VCR::VersionChecker.new('FakeWeb', FakeWeb::VERSION, '1.3.0', '1.3').check_version!

module VCR
  class LibraryHooks
    # @private
    module FakeWeb
      # @private
      class RequestHandler < ::VCR::RequestHandler
        attr_reader :net_http, :request, :request_body, :response_block
        def initialize(net_http, request, request_body = nil, &response_block)
          @net_http, @request, @request_body, @response_block =
           net_http,  request,  request_body,  response_block
          @stubbed_response, @vcr_response, @recursing = nil, nil, false
        end

        def handle
          super
        ensure
          invoke_after_request_hook(@vcr_response) unless @recursing
        end

      private

        def externally_stubbed?
          ::FakeWeb.registered_uri?(request_method, uri)
        end

        def on_externally_stubbed_request
          # just perform the request--FakeWeb will handle it
          perform_request(:started)
        end

        def on_recordable_request
          perform_request(net_http.started?, :record_interaction)
        end

        def on_stubbed_by_vcr_request
          with_exclusive_fakeweb_stub(stubbed_response) do
            # force it to be considered started since it doesn't
            # recurse in this case like the others.
            perform_request(:started)
          end
        end

        def on_ignored_request
          perform_request(net_http.started?)
        end

        def perform_request(started, record_interaction = false)
          # Net::HTTP calls #request recursively in certain circumstances.
          # We only want to record the request when the request is started, as
          # that is the final time through #request.
          unless started
            @recursing = true
            request.instance_variable_set(:@__vcr_request_handler, recursive_request_handler)
            return net_http.request_without_vcr(request, request_body, &response_block)
          end

          net_http.request_without_vcr(request, request_body) do |response|
            @vcr_response = vcr_response_from(response)

            if record_interaction
              VCR.record_http_interaction VCR::HTTPInteraction.new(vcr_request, @vcr_response)
            end

            response.extend VCR::Net::HTTPResponse # "unwind" the response
            response_block.call(response) if response_block
          end
        end

        def uri
          @uri ||= ::FakeWeb::Utility.request_uri_as_string(net_http, request)
        end

        def response_hash(response)
          (response.headers || {}).merge(
            :body   => response.body,
            :status => [response.status.code.to_s, response.status.message]
          )
        end

        def with_exclusive_fakeweb_stub(response)
          original_map = ::FakeWeb::Registry.instance.uri_map.dup
          ::FakeWeb.clean_registry
          ::FakeWeb.register_uri(:any, /.*/, response_hash(response))

          begin
            return yield
          ensure
            ::FakeWeb::Registry.instance.uri_map = original_map
          end
        end

        def request_method
          request.method.downcase.to_sym
        end

        def vcr_request
          @vcr_request ||= VCR::Request.new \
            request_method,
            uri,
            (request_body || request.body),
            request.to_hash
        end

        def vcr_response_from(response)
          VCR::Response.new \
            VCR::ResponseStatus.new(response.code.to_i, response.message),
            response.to_hash,
            response.body,
            response.http_version
        end

        def recursive_request_handler
          @recursive_request_handler ||= RecursiveRequestHandler.new(
            @after_hook_typed_request.type, @stubbed_response, @vcr_request,
            @net_http, @request, @request_body, &@response_block
          )
        end
      end

      class RecursiveRequestHandler < RequestHandler
        attr_reader :stubbed_response

        def initialize(request_type, stubbed_response, vcr_request, *args, &response_block)
          @request_type, @stubbed_response, @vcr_request =
           request_type,  stubbed_response,  vcr_request
          super(*args)
        end

        def handle
          set_typed_request_for_after_hook(@request_type)
          send "on_#{@request_type}_request"
        ensure
          invoke_after_request_hook(@vcr_response)
        end

        def request_type(*args)
          @request_type
        end
      end
    end
  end
end

# @private
module Net
  # @private
  class HTTP
    unless method_defined?(:request_with_vcr)
      def request_with_vcr(request, *args, &block)
        if VCR.turned_on?
          handler = request.instance_eval do
            remove_instance_variable(:@__vcr_request_handler) if defined?(@__vcr_request_handler)
          end || VCR::LibraryHooks::FakeWeb::RequestHandler.new(self, request, *args, &block)

          handler.handle
        else
          request_without_vcr(request, *args, &block)
        end
      end

      alias request_without_vcr request
      alias request request_with_vcr
    end
  end
end

# @private
module FakeWeb
  class << self
    # ensure HTTP requests are always allowed; VCR takes care of disallowing
    # them at the appropriate times in its hook
    def allow_net_connect_with_vcr?(*args)
      VCR.turned_on? ? true : allow_net_connect_without_vcr?(*args)
    end

    alias allow_net_connect_without_vcr? allow_net_connect?
    alias allow_net_connect? allow_net_connect_with_vcr?
  end unless respond_to?(:allow_net_connect_with_vcr?)
end

VCR.configuration.after_library_hooks_loaded do
  if defined?(WebMock)
    raise ArgumentError.new("You have configured VCR to hook into both :fakeweb and :webmock. You cannot use both.")
  end
end