require 'vcr/util/version_checker' require 'vcr/request_handler' require 'webmock' VCR::VersionChecker.new('WebMock', WebMock.version, '1.8.0').check_version! WebMock.enable! module VCR class LibraryHooks # @private module WebMock extend self def with_global_hook_disabled(request) global_hook_disabled_requests << request begin yield ensure global_hook_disabled_requests.delete(request) end end def global_hook_disabled?(request) requests = Thread.current[:_vcr_webmock_disabled_requests] requests && requests.include?(request) end def global_hook_disabled_requests Thread.current[:_vcr_webmock_disabled_requests] ||= [] end # @private module Helpers def vcr_request_for(webmock_request) VCR::Request.new \ webmock_request.method, webmock_request.uri.to_s, webmock_request.body, request_headers_for(webmock_request) end # @private def vcr_response_for(webmock_response) VCR::Response.new \ VCR::ResponseStatus.new(*webmock_response.status), webmock_response.headers, webmock_response.body, nil end if defined?(::Excon) # @private def request_headers_for(webmock_request) return nil unless webmock_request.headers # WebMock hooks deeply into a Excon at a place where it manually adds a "Host" # header, but this isn't a header we actually care to store... webmock_request.headers.dup.tap do |headers| headers.delete("Host") end end else # @private def request_headers_for(webmock_request) webmock_request.headers end end def typed_request_for(webmock_request, remove = false) if webmock_request.instance_variables.find { |v| v.to_sym == :@__typed_vcr_request } meth = remove ? :remove_instance_variable : :instance_variable_get return webmock_request.send(meth, :@__typed_vcr_request) end warn <<-EOS.gsub(/^\s+\|/, '') |WARNING: There appears to be a bug in WebMock's after_request hook | and VCR is attempting to work around it. Some VCR features | may not work properly. EOS Request::Typed.new(vcr_request_for(webmock_request), :unknown) end end class RequestHandler < ::VCR::RequestHandler include Helpers attr_reader :request def initialize(request) @request = request end private def externally_stubbed? # prevent infinite recursion... VCR::LibraryHooks::WebMock.with_global_hook_disabled(request) do ::WebMock.registered_request?(request) end end def set_typed_request_for_after_hook(*args) super request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request) end def vcr_request @vcr_request ||= vcr_request_for(request) end def on_externally_stubbed_request # nil allows WebMock to handle the request nil end def on_unhandled_request invoke_after_request_hook(nil) super end def on_stubbed_by_vcr_request { :body => stubbed_response.body.dup, # Excon mutates the body, so we must dup it :-( :status => [stubbed_response.status.code.to_i, stubbed_response.status.message], :headers => stubbed_response.headers } end end extend Helpers ::WebMock.globally_stub_request do |req| global_hook_disabled?(req) ? nil : RequestHandler.new(req).handle end ::WebMock.after_request(:real_requests_only => true) do |request, response| unless VCR.library_hooks.disabled?(:webmock) http_interaction = VCR::HTTPInteraction.new \ typed_request_for(request), vcr_response_for(response) VCR.record_http_interaction(http_interaction) end end ::WebMock.after_request do |request, response| unless VCR.library_hooks.disabled?(:webmock) VCR.configuration.invoke_hook \ :after_http_request, typed_request_for(request, :remove), vcr_response_for(response) end end end end end # @private module WebMock class << self # ensure HTTP requests are always allowed; VCR takes care of disallowing # them at the appropriate times in its hook def net_connect_allowed_with_vcr?(*args) VCR.turned_on? ? true : net_connect_allowed_without_vcr?(*args) end alias net_connect_allowed_without_vcr? net_connect_allowed? alias net_connect_allowed? net_connect_allowed_with_vcr? end unless respond_to?(:net_connect_allowed_with_vcr?) end