class VCR::Cassette
The media VCR
uses to store HTTP interactions for later re-use.
Constants
- VALID_RECORD_MODES
The supported record modes.
* :all -- Record every HTTP interactions; do not play any back. * :none -- Do not record any HTTP interactions; play them back. * :new_episodes -- Playback previously recorded HTTP interactions and record new ones. * :once -- Record the HTTP interactions if the cassette has not already been recorded; otherwise, playback the HTTP interactions.
Attributes
@return [Boolean, nil] Should outdated interactions be recorded back to file
@return [Boolean] Should unused requests be dropped from the cassette?
@return [Boolean, Hash] The cassette’s ERB option. The file will be treated as an
ERB template if this has a truthy value. A hash, if provided, will be used as local variables for the ERB template.
@return [Array<Symbol, call>] List of request matchers. Used to find a response from an
existing HTTP interaction to play back.
@return [#to_s] The name of the cassette. Used to determine the cassette’s file name. @see file
@return [Integer, nil] How frequently (in seconds) the cassette should be re-recorded.
@return [Symbol] The record mode. Determines whether the cassette records HTTP interactions,
plays them back, or does both.
@return [Boolean] The cassette’s record_on_error
mode. When the code that uses the cassette
raises an error (for example a test failure) and record_on_error is set to false, no cassette will be recorded. This is useful when you are TDD'ing an API integration: when an error is raised that often means your request is invalid, so you don't want the cassette to be recorded.
Public Class Methods
@private
# File lib/vcr/deprecations.rb, line 17 def Cassette.const_missing(const) return super unless const == :MissingERBVariableError warn "WARNING: `VCR::Cassette::MissingERBVariableError` is deprecated. Use `VCR::Errors::MissingERBVariableError` instead." Errors::MissingERBVariableError end
@param (see VCR#insert_cassette
) @see VCR#insert_cassette
# File lib/vcr/cassette.rb, line 58 def initialize(name, options = {}) @name = name @options = VCR.configuration.default_cassette_options.merge(options) @mutex = Mutex.new assert_valid_options! extract_options raise_error_unless_valid_record_mode log "Initialized with options: #{@options.inspect}" end
Public Instance Methods
Ejects the current cassette. The cassette will no longer be used. In addition, any newly recorded HTTP interactions will be written to disk.
@note This is not intended to be called directly. Use ‘VCR.eject_cassette` instead.
@param (see VCR#eject_casssette) @see VCR#eject_cassette
# File lib/vcr/cassette.rb, line 78 def eject(options = {}) write_recorded_interactions_to_disk if should_write_recorded_interactions_to_disk? if should_assert_no_unused_interactions? && !options[:skip_no_unused_interactions_assertion] http_interactions.assert_no_unused_interactions! end end
@return [String] The file for this cassette. @raise [NotImplementedError] if the configured cassette persister
does not support resolving file paths.
@note VCR
will take care of sanitizing the cassette name to make it a valid file name.
# File lib/vcr/cassette.rb, line 132 def file unless @persister.respond_to?(:absolute_path_to_file) raise NotImplementedError, "The configured cassette persister does not support resolving file paths" end @persister.absolute_path_to_file(storage_key) end
@private
# File lib/vcr/cassette.rb, line 102 def http_interactions # Without this mutex, under threaded access, an HTTPInteractionList will overwrite # the first. @mutex.synchronize do @http_interactions ||= HTTPInteractionList.new \ should_stub_requests? ? previously_recorded_interactions : [], match_requests_on, @allow_playback_repeats, @parent_list, log_prefix end end
@return [Boolean] false unless wrapped with LinkedCassette
# File lib/vcr/cassette.rb, line 171 def linked? false end
@private
# File lib/vcr/cassette.rb, line 124 def new_recorded_interactions @new_recorded_interactions ||= [] end
@return [Time, nil] The ‘recorded_at` time of the first HTTP interaction
or nil if the cassette has no prior HTTP interactions.
@example
VCR.use_cassette("some cassette") do |cassette| Timecop.freeze(cassette.originally_recorded_at || Time.now) do # ... end end
# File lib/vcr/cassette.rb, line 166 def originally_recorded_at @originally_recorded_at ||= previously_recorded_interactions.map(&:recorded_at).min end
@private
# File lib/vcr/cassette.rb, line 116 def record_http_interaction(interaction) VCR::CassetteMutex.synchronize do log "Recorded HTTP interaction #{request_summary(interaction.request)} => #{response_summary(interaction.response)}" new_recorded_interactions << interaction end end
@return [Boolean] Whether or not the cassette is recording.
# File lib/vcr/cassette.rb, line 140 def recording? case record_mode when :none; false when :once; raw_cassette_bytes.to_s.empty? else true end end
@private
# File lib/vcr/cassette.rb, line 87 def run_failed! @run_failed = true end
@private
# File lib/vcr/cassette.rb, line 92 def run_failed? @run_failed = false unless defined?(@run_failed) @run_failed end
@return [Hash] The hash that will be serialized when the cassette is written to disk.
# File lib/vcr/cassette.rb, line 149 def serializable_hash { "http_interactions" => interactions_to_record.map(&:to_hash), "recorded_with" => "VCR #{VCR.version}" } end
# File lib/vcr/cassette.rb, line 97 def should_write_recorded_interactions_to_disk? !run_failed? || record_on_error end
Private Instance Methods
# File lib/vcr/cassette.rb, line 177 def assert_valid_options! invalid_options = @options.keys - [ :record, :record_on_error, :erb, :match_requests_on, :re_record_interval, :tag, :tags, :update_content_length_header, :allow_playback_repeats, :allow_unused_http_interactions, :exclusive, :serialize_with, :preserve_exact_body_bytes, :decode_compressed_response, :recompress_response, :persist_with, :persister_options, :clean_outdated_http_interactions, :drop_unused_requests ] if invalid_options.size > 0 raise ArgumentError.new("You passed the following invalid options to VCR::Cassette.new: #{invalid_options.inspect}.") end end
# File lib/vcr/cassette.rb, line 324 def deserialized_hash @deserialized_hash ||= @serializer.deserialize(raw_cassette_bytes).tap do |hash| unless hash.is_a?(Hash) && hash['http_interactions'].is_a?(Array) raise Errors::InvalidCassetteFormatError.new \ "#{file} does not appear to be a valid VCR 2.0 cassette. " + "VCR 1.x cassettes are not valid with VCR 2.0. When upgrading from " + "VCR 1.x, it is recommended that you delete all your existing cassettes and " + "re-record them, or use the provided vcr:migrate_cassettes rake task to migrate " + "them. For more info, see the VCR upgrade guide." end end end
# File lib/vcr/cassette.rb, line 191 def extract_options [:record_on_error, :erb, :match_requests_on, :re_record_interval, :clean_outdated_http_interactions, :allow_playback_repeats, :allow_unused_http_interactions, :exclusive, :drop_unused_requests].each do |name| instance_variable_set("@#{name}", @options[name]) end assign_tags @serializer = VCR.cassette_serializers[@options[:serialize_with]] @persister = VCR.cassette_persisters[@options[:persist_with]] @record_mode = should_re_record?(@options[:record]) ? :all : @options[:record] @parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions end
# File lib/vcr/cassette.rb, line 300 def interactions_to_record # We deep-dup the interactions by roundtripping them to/from a hash. # This is necessary because `before_record` can mutate the interactions. merged_interactions.map { |i| HTTPInteraction.from_hash(i.to_hash) }.tap do |interactions| invoke_hook(:before_record, interactions) end end
# File lib/vcr/cassette.rb, line 316 def invoke_hook(type, interactions) interactions.delete_if do |i| i.hook_aware.tap do |hw| VCR.configuration.invoke_hook(type, hw, self) end.ignored? end end
# File lib/vcr/cassette.rb, line 337 def log_prefix @log_prefix ||= "[Cassette: '#{name}'] " end
# File lib/vcr/cassette.rb, line 278 def merged_interactions old_interactions = previously_recorded_interactions if should_remove_matching_existing_interactions? new_interaction_list = HTTPInteractionList.new(new_recorded_interactions, match_requests_on) old_interactions = old_interactions.reject do |i| new_interaction_list.response_for(i.request) end end if should_remove_unused_interactions? new_recorded_interactions else up_to_date_interactions(old_interactions) + new_recorded_interactions end end
# File lib/vcr/cassette.rb, line 213 def previously_recorded_interactions @previously_recorded_interactions ||= if !raw_cassette_bytes.to_s.empty? deserialized_hash['http_interactions'].map { |h| HTTPInteraction.from_hash(h) }.tap do |interactions| invoke_hook(:before_playback, interactions) interactions.reject! do |i| i.request.uri.is_a?(String) && VCR.request_ignorer.ignore?(i.request) end end else [] end end
# File lib/vcr/cassette.rb, line 231 def raise_error_unless_valid_record_mode unless VALID_RECORD_MODES.include?(record_mode) raise ArgumentError.new("#{record_mode} is not a valid cassette record mode. Valid modes are: #{VALID_RECORD_MODES.inspect}") end end
# File lib/vcr/cassette.rb, line 274 def raw_cassette_bytes @raw_cassette_bytes ||= VCR::Cassette::ERBRenderer.new(@persister[storage_key], erb, name).render end
VCR::Logger::Mixin#request_summary
# File lib/vcr/cassette.rb, line 341 def request_summary(request) super(request, match_requests_on) end
# File lib/vcr/cassette.rb, line 270 def should_assert_no_unused_interactions? !(@allow_unused_http_interactions || $!) end
# File lib/vcr/cassette.rb, line 237 def should_re_record?(record_mode) return false unless @re_record_interval return false unless originally_recorded_at return false if record_mode == :none now = Time.now (originally_recorded_at + @re_record_interval < now).tap do |value| info = "previously recorded at: '#{originally_recorded_at}'; now: '#{now}'; interval: #{@re_record_interval} seconds" if !value log "Not re-recording since the interval has not elapsed (#{info})." elsif InternetConnection.available? log "re-recording (#{info})." else log "Not re-recording because no internet connection is available (#{info})." return false end end end
# File lib/vcr/cassette.rb, line 262 def should_remove_matching_existing_interactions? record_mode == :all end
# File lib/vcr/cassette.rb, line 266 def should_remove_unused_interactions? @drop_unused_requests end
# File lib/vcr/cassette.rb, line 258 def should_stub_requests? record_mode != :all end
# File lib/vcr/cassette.rb, line 227 def storage_key @storage_key ||= [name, @serializer.file_extension].join('.') end
# File lib/vcr/cassette.rb, line 295 def up_to_date_interactions(interactions) return interactions unless clean_outdated_http_interactions && re_record_interval interactions.take_while { |x| x[:recorded_at] > Time.now - re_record_interval } end
# File lib/vcr/cassette.rb, line 308 def write_recorded_interactions_to_disk return if new_recorded_interactions.none? hash = serializable_hash return if hash["http_interactions"].none? @persister[storage_key] = @serializer.serialize(hash) end