View on GitHub


0 mins
Test Coverage
module VCR
  # Namespace for VCR errors.
  module Errors
    # Base class for all VCR errors.
    class Error                     < StandardError; end

    # Error raised when VCR is turned off while a cassette is in use.
    # @see VCR#turn_off!
    # @see VCR#turned_off
    class CassetteInUseError        < Error; end

    # Error raised when a VCR cassette is inserted while VCR is turned off.
    # @see VCR#insert_cassette
    # @see VCR#use_cassette
    class TurnedOffError            < Error; end

    # Error raised when an cassette ERB template is rendered and a
    # variable is missing.
    # @see VCR#insert_cassette
    # @see VCR#use_cassette
    class MissingERBVariableError   < Error; end

    # Error raised when the version of one of the libraries that VCR hooks into
    # is too low for VCR to support.
    # @see VCR::Configuration#hook_into
    class LibraryVersionTooLowError < Error; end

    # Error raised when a request matcher is requested that is not registered.
    class UnregisteredMatcherError  < Error; end

    # Error raised when a VCR 1.x cassette is used with VCR 2.
    class InvalidCassetteFormatError < Error; end

    # Error raised when an `around_http_request` hook is used improperly.
    # @see VCR::Configuration#around_http_request
    class AroundHTTPRequestHookError < Error; end

    # Error raised when you attempt to use a VCR feature that is not
    # supported on your ruby interpreter.
    # @see VCR::Configuration#around_http_request
    class NotSupportedError          < Error; end

    # Error raised when you ask VCR to decode a compressed response
    # body but the content encoding isn't one of the known ones.
    # @see VCR::Response#decompress
    class UnknownContentEncodingError < Error; end

    # Error raised when you eject a cassette before all previously
    # recorded HTTP interactions are played back.
    # @note Only applicable when :allow_episode_skipping is false.
    # @see VCR::HTTPInteractionList#assert_no_unused_interactions!
    class UnusedHTTPInteractionError < Error; end

    # Error raised when an HTTP request is made that VCR is unable to handle.
    # @note VCR will raise this to force you to do something about the
    #  HTTP request. The idea is that you want to handle _every_ HTTP
    #  request in your test suite. The error message will give you
    #  suggestions for how to deal with the request.
    class UnhandledHTTPRequestError < Error
      # The HTTP request.
      attr_reader :request

      # Constructs the error.
      # @param [VCR::Request] request the unhandled request.
      def initialize(request)
        @request = request
        super construct_message


      def relish_version_slug
        @relish_version_slug ||= VCR.version.gsub(/\W/, '-')

      def construct_message
        ["", "", "=" * 80,
         "An HTTP request has been made that VCR does not know how to handle:",
         "  #{request_description}\n",
         "=" * 80, "", ""].join("\n")

      def request_description
        "#{request.method.to_s.upcase} #{request.uri}"

      def cassette_description
        if cassette = VCR.current_cassette
          ["VCR is currently using the following cassette:",
           "  - #{cassette.file}",
           "  - :record => #{cassette.record_mode.inspect}",
           "  - :match_requests_on => #{cassette.match_requests_on.inspect}\n",
           "Under the current configuration VCR can not find a suitable HTTP interaction",
           "to replay and is prevented from recording new requests. There are a few ways",
           "you can deal with this:\n"].join("\n")
          ["There is currently no cassette in use. There are a few ways",
           "you can configure VCR to handle this request:\n"].join("\n")

      def formatted_suggestions
        formatted_points, formatted_foot_notes = [], []

        suggestions.each_with_index do |suggestion, index|
          bullet_point, foot_note = suggestion.first, suggestion.last
          formatted_points << format_bullet_point(bullet_point, index)
          formatted_foot_notes << format_foot_note(foot_note, index)


      def format_bullet_point(lines, index)
        lines.first.insert(0, "  * ")
        lines.last << " [#{index + 1}]."
        lines.join("\n    ")

      def format_foot_note(url, index)
        "[#{index + 1}] #{url % relish_version_slug}"

      # List of suggestions for how to configure VCR to handle the request.
        :use_new_episodes => [
          ["You can use the :new_episodes record mode to allow VCR to",
           "record this new request to the existing cassette"],

        :delete_cassette_for_once => [
          ["The current record mode (:once) does not allow new requests to be recorded",
           "to a previously recorded cassette. You can delete the cassette file and re-run",
           "your tests to allow the cassette to be recorded with this request"],

        :deal_with_none => [
          ["The current record mode (:none) does not allow requests to be recorded. You",
           "can temporarily change the record mode to :once, delete the cassette file ",
           "and re-run your tests to allow the cassette to be recorded with this request"],

        :use_a_cassette => [
          ["If you want VCR to record this request and play it back during future test",
           "runs, you should wrap your test (or this portion of your test) in a",
           "`VCR.use_cassette` block"],

        :allow_http_connections_when_no_cassette => [
          ["If you only want VCR to handle requests made while a cassette is in use,",
           "configure `allow_http_connections_when_no_cassette = true`. VCR will",
           "ignore this request since it is made when there is no cassette"],

        :ignore_request => [
          ["If you want VCR to ignore this request (and others like it), you can",
           "set an `ignore_request` callback"],

        :allow_playback_repeats => [
          ["The cassette contains an HTTP interaction that matches this request,",
           "but it has already been played back. If you wish to allow a single HTTP",
           "interaction to be played back multiple times, set the `:allow_playback_repeats`",
           "cassette option"],

        :match_requests_on => [
          ["The cassette contains %s not been",
           "played back. If your request is non-deterministic, you may need to",
           "change your :match_requests_on cassette option to be more lenient",
           "or use a custom request matcher to allow it to match"],

        :try_debug_logger => [
          ["If you're surprised VCR is raising this error",
           "and want insight about how VCR attempted to handle the request,",
           "you can use the debug_logger configuration option to log more details"],

      def suggestion_for(key)
        bullet_point_lines, url = ALL_SUGGESTIONS[key]
        bullet_point_lines =
        url = url.dup
        [bullet_point_lines, url]

      def suggestions
        return no_cassette_suggestions unless cassette = VCR.current_cassette

        [:try_debug_logger, :use_new_episodes, :ignore_request].tap do |suggestions|
          suggestions << :allow_playback_repeats if cassette.http_interactions.has_used_interaction_matching?(request)
! { |k| suggestion_for(k) }

      def no_cassette_suggestions
        [:try_debug_logger, :use_a_cassette, :allow_http_connections_when_no_cassette, :ignore_request].map do |key|

      def record_mode_suggestion
        case VCR.current_cassette.record_mode
        when :none then [:deal_with_none]
        when :once then [:delete_cassette_for_once]
        else []

      def match_requests_on_suggestion
        num_remaining_interactions = VCR.current_cassette.http_interactions.remaining_unused_interaction_count
        return [] if

        interaction_description = if num_remaining_interactions == 1
          "1 HTTP interaction that has"
          "#{num_remaining_interactions} HTTP interactions that have"

        description_lines, link = suggestion_for(:match_requests_on)
        description_lines[0] = description_lines[0] % interaction_description
        [[description_lines, link]]