mustwin-vcr 2.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/features/about_these_examples.md +18 -0
- data/features/cassettes/allow_unused_http_interactions.feature +100 -0
- data/features/cassettes/automatic_re_recording.feature +72 -0
- data/features/cassettes/decompress.feature +74 -0
- data/features/cassettes/dynamic_erb.feature +100 -0
- data/features/cassettes/exclusive.feature +126 -0
- data/features/cassettes/format.feature +323 -0
- data/features/cassettes/freezing_time.feature +68 -0
- data/features/cassettes/naming.feature +28 -0
- data/features/cassettes/no_cassette.feature +152 -0
- data/features/cassettes/update_content_length_header.feature +112 -0
- data/features/configuration/allow_http_connections_when_no_cassette.feature +55 -0
- data/features/configuration/cassette_library_dir.feature +31 -0
- data/features/configuration/debug_logging.feature +59 -0
- data/features/configuration/default_cassette_options.feature +100 -0
- data/features/configuration/filter_sensitive_data.feature +153 -0
- data/features/configuration/hook_into.feature +172 -0
- data/features/configuration/ignore_request.feature +192 -0
- data/features/configuration/preserve_exact_body_bytes.feature +108 -0
- data/features/configuration/query_parser.feature +84 -0
- data/features/configuration/uri_parser.feature +89 -0
- data/features/getting_started.md +82 -0
- data/features/hooks/after_http_request.feature +58 -0
- data/features/hooks/around_http_request.feature +57 -0
- data/features/hooks/before_http_request.feature +63 -0
- data/features/hooks/before_playback.feature +184 -0
- data/features/hooks/before_record.feature +172 -0
- data/features/http_libraries/em_http_request.feature +250 -0
- data/features/http_libraries/net_http.feature +179 -0
- data/features/middleware/faraday.feature +56 -0
- data/features/middleware/rack.feature +92 -0
- data/features/record_modes/all.feature +82 -0
- data/features/record_modes/new_episodes.feature +79 -0
- data/features/record_modes/none.feature +72 -0
- data/features/record_modes/once.feature +95 -0
- data/features/request_matching/README.md +30 -0
- data/features/request_matching/body.feature +91 -0
- data/features/request_matching/body_as_json.feature +90 -0
- data/features/request_matching/custom_matcher.feature +135 -0
- data/features/request_matching/headers.feature +85 -0
- data/features/request_matching/host.feature +95 -0
- data/features/request_matching/identical_request_sequence.feature +89 -0
- data/features/request_matching/method.feature +96 -0
- data/features/request_matching/path.feature +96 -0
- data/features/request_matching/playback_repeats.feature +98 -0
- data/features/request_matching/query.feature +97 -0
- data/features/request_matching/uri.feature +94 -0
- data/features/request_matching/uri_without_param.feature +101 -0
- data/features/step_definitions/cli_steps.rb +193 -0
- data/features/support/env.rb +44 -0
- data/features/support/http_lib_filters.rb +53 -0
- data/features/test_frameworks/cucumber.feature +211 -0
- data/features/test_frameworks/rspec_macro.feature +81 -0
- data/features/test_frameworks/rspec_metadata.feature +150 -0
- data/features/test_frameworks/test_unit.feature +49 -0
- data/lib/vcr.rb +347 -0
- data/lib/vcr/cassette.rb +291 -0
- data/lib/vcr/cassette/erb_renderer.rb +55 -0
- data/lib/vcr/cassette/http_interaction_list.rb +108 -0
- data/lib/vcr/cassette/migrator.rb +118 -0
- data/lib/vcr/cassette/persisters.rb +42 -0
- data/lib/vcr/cassette/persisters/file_system.rb +64 -0
- data/lib/vcr/cassette/serializers.rb +57 -0
- data/lib/vcr/cassette/serializers/json.rb +48 -0
- data/lib/vcr/cassette/serializers/psych.rb +48 -0
- data/lib/vcr/cassette/serializers/syck.rb +61 -0
- data/lib/vcr/cassette/serializers/yaml.rb +50 -0
- data/lib/vcr/configuration.rb +555 -0
- data/lib/vcr/deprecations.rb +109 -0
- data/lib/vcr/errors.rb +266 -0
- data/lib/vcr/extensions/net_http_response.rb +36 -0
- data/lib/vcr/library_hooks.rb +18 -0
- data/lib/vcr/library_hooks/excon.rb +27 -0
- data/lib/vcr/library_hooks/fakeweb.rb +196 -0
- data/lib/vcr/library_hooks/faraday.rb +51 -0
- data/lib/vcr/library_hooks/typhoeus.rb +120 -0
- data/lib/vcr/library_hooks/typhoeus_0.4.rb +103 -0
- data/lib/vcr/library_hooks/webmock.rb +164 -0
- data/lib/vcr/middleware/excon.rb +221 -0
- data/lib/vcr/middleware/excon/legacy_methods.rb +33 -0
- data/lib/vcr/middleware/faraday.rb +118 -0
- data/lib/vcr/middleware/rack.rb +79 -0
- data/lib/vcr/request_handler.rb +114 -0
- data/lib/vcr/request_ignorer.rb +43 -0
- data/lib/vcr/request_matcher_registry.rb +149 -0
- data/lib/vcr/structs.rb +578 -0
- data/lib/vcr/tasks/vcr.rake +9 -0
- data/lib/vcr/test_frameworks/cucumber.rb +64 -0
- data/lib/vcr/test_frameworks/rspec.rb +47 -0
- data/lib/vcr/util/hooks.rb +61 -0
- data/lib/vcr/util/internet_connection.rb +43 -0
- data/lib/vcr/util/logger.rb +59 -0
- data/lib/vcr/util/variable_args_block_caller.rb +13 -0
- data/lib/vcr/util/version_checker.rb +48 -0
- data/lib/vcr/version.rb +34 -0
- data/spec/acceptance/threading_spec.rb +34 -0
- data/spec/fixtures/cassette_spec/1_x_cassette.yml +110 -0
- data/spec/fixtures/cassette_spec/empty.yml +0 -0
- data/spec/fixtures/cassette_spec/example.yml +111 -0
- data/spec/fixtures/cassette_spec/with_localhost_requests.yml +111 -0
- data/spec/fixtures/fake_example_responses.yml +110 -0
- data/spec/fixtures/match_requests_on.yml +187 -0
- data/spec/lib/vcr/cassette/erb_renderer_spec.rb +53 -0
- data/spec/lib/vcr/cassette/http_interaction_list_spec.rb +295 -0
- data/spec/lib/vcr/cassette/migrator_spec.rb +195 -0
- data/spec/lib/vcr/cassette/persisters/file_system_spec.rb +69 -0
- data/spec/lib/vcr/cassette/persisters_spec.rb +39 -0
- data/spec/lib/vcr/cassette/serializers_spec.rb +176 -0
- data/spec/lib/vcr/cassette_spec.rb +618 -0
- data/spec/lib/vcr/configuration_spec.rb +326 -0
- data/spec/lib/vcr/deprecations_spec.rb +85 -0
- data/spec/lib/vcr/errors_spec.rb +162 -0
- data/spec/lib/vcr/extensions/net_http_response_spec.rb +86 -0
- data/spec/lib/vcr/library_hooks/excon_spec.rb +104 -0
- data/spec/lib/vcr/library_hooks/fakeweb_spec.rb +169 -0
- data/spec/lib/vcr/library_hooks/faraday_spec.rb +68 -0
- data/spec/lib/vcr/library_hooks/typhoeus_0.4_spec.rb +36 -0
- data/spec/lib/vcr/library_hooks/typhoeus_spec.rb +162 -0
- data/spec/lib/vcr/library_hooks/webmock_spec.rb +118 -0
- data/spec/lib/vcr/library_hooks_spec.rb +51 -0
- data/spec/lib/vcr/middleware/faraday_spec.rb +182 -0
- data/spec/lib/vcr/middleware/rack_spec.rb +115 -0
- data/spec/lib/vcr/request_ignorer_spec.rb +70 -0
- data/spec/lib/vcr/request_matcher_registry_spec.rb +345 -0
- data/spec/lib/vcr/structs_spec.rb +732 -0
- data/spec/lib/vcr/test_frameworks/cucumber_spec.rb +107 -0
- data/spec/lib/vcr/test_frameworks/rspec_spec.rb +83 -0
- data/spec/lib/vcr/util/hooks_spec.rb +158 -0
- data/spec/lib/vcr/util/internet_connection_spec.rb +37 -0
- data/spec/lib/vcr/util/version_checker_spec.rb +31 -0
- data/spec/lib/vcr/version_spec.rb +27 -0
- data/spec/lib/vcr_spec.rb +349 -0
- data/spec/monkey_patches.rb +182 -0
- data/spec/spec_helper.rb +62 -0
- data/spec/support/configuration_stubbing.rb +8 -0
- data/spec/support/cucumber_helpers.rb +35 -0
- data/spec/support/fixnum_extension.rb +10 -0
- data/spec/support/http_library_adapters.rb +289 -0
- data/spec/support/limited_uri.rb +21 -0
- data/spec/support/ruby_interpreter.rb +7 -0
- data/spec/support/shared_example_groups/excon.rb +63 -0
- data/spec/support/shared_example_groups/hook_into_http_library.rb +594 -0
- data/spec/support/shared_example_groups/request_hooks.rb +59 -0
- data/spec/support/sinatra_app.rb +86 -0
- data/spec/support/vcr_localhost_server.rb +76 -0
- data/spec/support/vcr_stub_helpers.rb +17 -0
- metadata +677 -0
data/lib/vcr.rb
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
require 'vcr/util/logger'
|
|
2
|
+
require 'vcr/util/variable_args_block_caller'
|
|
3
|
+
|
|
4
|
+
require 'vcr/cassette'
|
|
5
|
+
require 'vcr/cassette/serializers'
|
|
6
|
+
require 'vcr/cassette/persisters'
|
|
7
|
+
require 'vcr/configuration'
|
|
8
|
+
require 'vcr/deprecations'
|
|
9
|
+
require 'vcr/errors'
|
|
10
|
+
require 'vcr/library_hooks'
|
|
11
|
+
require 'vcr/request_ignorer'
|
|
12
|
+
require 'vcr/request_matcher_registry'
|
|
13
|
+
require 'vcr/structs'
|
|
14
|
+
require 'vcr/version'
|
|
15
|
+
|
|
16
|
+
# The main entry point for VCR.
|
|
17
|
+
# @note This module is extended onto itself; thus, the methods listed
|
|
18
|
+
# here as instance methods are available directly off of VCR.
|
|
19
|
+
module VCR
|
|
20
|
+
include VariableArgsBlockCaller
|
|
21
|
+
include Errors
|
|
22
|
+
|
|
23
|
+
extend self
|
|
24
|
+
|
|
25
|
+
autoload :CucumberTags, 'vcr/test_frameworks/cucumber'
|
|
26
|
+
autoload :InternetConnection, 'vcr/util/internet_connection'
|
|
27
|
+
|
|
28
|
+
module RSpec
|
|
29
|
+
autoload :Metadata, 'vcr/test_frameworks/rspec'
|
|
30
|
+
autoload :Macros, 'vcr/deprecations'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module Middleware
|
|
34
|
+
autoload :Faraday, 'vcr/middleware/faraday'
|
|
35
|
+
autoload :Rack, 'vcr/middleware/rack'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The currently active cassette.
|
|
39
|
+
#
|
|
40
|
+
# @return [nil, VCR::Cassette] The current cassette or nil if there is
|
|
41
|
+
# no current cassette.
|
|
42
|
+
def current_cassette
|
|
43
|
+
cassettes.last
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Inserts the named cassette using the given cassette options.
|
|
47
|
+
# New HTTP interactions, if allowed by the cassette's `:record` option, will
|
|
48
|
+
# be recorded to the cassette. The cassette's existing HTTP interactions
|
|
49
|
+
# will be used to stub requests, unless prevented by the cassete's
|
|
50
|
+
# `:record` option.
|
|
51
|
+
#
|
|
52
|
+
# @example
|
|
53
|
+
# VCR.insert_cassette('twitter', :record => :new_episodes)
|
|
54
|
+
#
|
|
55
|
+
# # ...later, after making an HTTP request:
|
|
56
|
+
#
|
|
57
|
+
# VCR.eject_cassette
|
|
58
|
+
#
|
|
59
|
+
# @param name [#to_s] The name of the cassette. VCR will sanitize
|
|
60
|
+
# this to ensure it is a valid file name.
|
|
61
|
+
# @param options [Hash] The cassette options. The given options will
|
|
62
|
+
# be merged with the configured default_cassette_options.
|
|
63
|
+
# @option options :record [:all, :none, :new_episodes, :once] The record mode.
|
|
64
|
+
# @option options :erb [Boolean, Hash] Whether or not to evaluate the
|
|
65
|
+
# cassette as an ERB template. Defaults to false. A hash can be used
|
|
66
|
+
# to provide the ERB template with local variables.
|
|
67
|
+
# @option options :match_requests_on [Array<Symbol, #call>] List of request matchers
|
|
68
|
+
# to use to determine what recorded HTTP interaction to replay. Defaults to
|
|
69
|
+
# [:method, :uri]. The built-in matchers are :method, :uri, :host, :path, :headers
|
|
70
|
+
# and :body. You can also pass the name of a registered custom request matcher or
|
|
71
|
+
# any object that responds to #call.
|
|
72
|
+
# @option options :re_record_interval [Integer] When given, the
|
|
73
|
+
# cassette will be re-recorded at the given interval, in seconds.
|
|
74
|
+
# @option options :tag [Symbol] Used to apply tagged `before_record`
|
|
75
|
+
# and `before_playback` hooks to the cassette.
|
|
76
|
+
# @option options :tags [Array<Symbol>] Used to apply multiple tags to
|
|
77
|
+
# a cassette so that tagged `before_record` and `before_playback` hooks
|
|
78
|
+
# will apply to the cassette.
|
|
79
|
+
# @option options :update_content_length_header [Boolean] Whether or
|
|
80
|
+
# not to overwrite the Content-Length header of the responses to
|
|
81
|
+
# match the length of the response body. Defaults to false.
|
|
82
|
+
# @option options :decode_compressed_response [Boolean] Whether or
|
|
83
|
+
# not to decode compressed responses before recording the cassette.
|
|
84
|
+
# This makes the cassette more human readable. Defaults to false.
|
|
85
|
+
# @option options :allow_playback_repeats [Boolean] Whether or not to
|
|
86
|
+
# allow a single HTTP interaction to be played back multiple times.
|
|
87
|
+
# Defaults to false.
|
|
88
|
+
# @option options :allow_unused_http_interactions [Boolean] If set to
|
|
89
|
+
# false, an error will be raised if a cassette is ejected before all
|
|
90
|
+
# previously recorded HTTP interactions have been used.
|
|
91
|
+
# Defaults to true. Note that when an error has already occurred
|
|
92
|
+
# (as indicated by the `$!` variable) unused interactions will be
|
|
93
|
+
# allowed so that we don't silence the original error (which is almost
|
|
94
|
+
# certainly more interesting/important).
|
|
95
|
+
# @option options :exclusive [Boolean] Whether or not to use only this
|
|
96
|
+
# cassette and to completely ignore any cassettes in the cassettes stack.
|
|
97
|
+
# Defaults to false.
|
|
98
|
+
# @option options :serialize_with [Symbol] Which serializer to use.
|
|
99
|
+
# Valid values are :yaml, :syck, :psych, :json or any registered
|
|
100
|
+
# custom serializer. Defaults to :yaml.
|
|
101
|
+
# @option options :persist_with [Symbol] Which cassette persister to
|
|
102
|
+
# use. Defaults to :file_system. You can also register and use a
|
|
103
|
+
# custom persister.
|
|
104
|
+
# @option options :preserve_exact_body_bytes [Boolean] Whether or not
|
|
105
|
+
# to base64 encode the bytes of the requests and responses for this cassette
|
|
106
|
+
# when serializing it. See also `VCR::Configuration#preserve_exact_body_bytes`.
|
|
107
|
+
#
|
|
108
|
+
# @return [VCR::Cassette] the inserted cassette
|
|
109
|
+
#
|
|
110
|
+
# @raise [ArgumentError] when the given cassette is already being used.
|
|
111
|
+
# @raise [VCR::Errors::TurnedOffError] when VCR has been turned off
|
|
112
|
+
# without using the :ignore_cassettes option.
|
|
113
|
+
# @raise [VCR::Errors::MissingERBVariableError] when the `:erb` option
|
|
114
|
+
# is used and the ERB template requires variables that you did not provide.
|
|
115
|
+
#
|
|
116
|
+
# @note If you use this method you _must_ call `eject_cassette` when you
|
|
117
|
+
# are done. It is generally recommended that you use {#use_cassette}
|
|
118
|
+
# unless your code-under-test cannot be run as a block.
|
|
119
|
+
#
|
|
120
|
+
def insert_cassette(name, options = {})
|
|
121
|
+
if turned_on?
|
|
122
|
+
if cassettes.any? { |c| c.name == name }
|
|
123
|
+
raise ArgumentError.new("There is already a cassette with the same name (#{name}). You cannot nest multiple cassettes with the same name.")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
cassette = Cassette.new(name, options)
|
|
127
|
+
cassettes.push(cassette)
|
|
128
|
+
cassette
|
|
129
|
+
elsif !ignore_cassettes?
|
|
130
|
+
message = "VCR is turned off. You must turn it on before you can insert a cassette. " +
|
|
131
|
+
"Or you can use the `:ignore_cassettes => true` option to completely ignore cassette insertions."
|
|
132
|
+
raise TurnedOffError.new(message)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Ejects the current cassette. The cassette will no longer be used.
|
|
137
|
+
# In addition, any newly recorded HTTP interactions will be written to
|
|
138
|
+
# disk.
|
|
139
|
+
#
|
|
140
|
+
# @param options [Hash] Eject options.
|
|
141
|
+
# @option options :skip_no_unused_interactions_assertion [Boolean]
|
|
142
|
+
# If `true` is given, this will skip the "no unused HTTP interactions"
|
|
143
|
+
# assertion enabled by the `:allow_unused_http_interactions => false`
|
|
144
|
+
# cassette option. This is intended for use when your test has had
|
|
145
|
+
# an error, but your test framework has already handled it.
|
|
146
|
+
# @return [VCR::Cassette, nil] the ejected cassette if there was one
|
|
147
|
+
def eject_cassette(options = {})
|
|
148
|
+
cassette = cassettes.last
|
|
149
|
+
cassette.eject(options) if cassette
|
|
150
|
+
cassette
|
|
151
|
+
ensure
|
|
152
|
+
cassettes.pop
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Inserts a cassette using the given name and options, runs the given
|
|
156
|
+
# block, and ejects the cassette.
|
|
157
|
+
#
|
|
158
|
+
# @example
|
|
159
|
+
# VCR.use_cassette('twitter', :record => :new_episodes) do
|
|
160
|
+
# # make an HTTP request
|
|
161
|
+
# end
|
|
162
|
+
#
|
|
163
|
+
# @param (see #insert_cassette)
|
|
164
|
+
# @option (see #insert_cassette)
|
|
165
|
+
# @yield Block to run while this cassette is in use.
|
|
166
|
+
# @yieldparam cassette [(optional) VCR::Cassette] the cassette that has
|
|
167
|
+
# been inserted.
|
|
168
|
+
# @raise (see #insert_cassette)
|
|
169
|
+
# @return [void]
|
|
170
|
+
# @see #insert_cassette
|
|
171
|
+
# @see #eject_cassette
|
|
172
|
+
def use_cassette(name, options = {}, &block)
|
|
173
|
+
unless block
|
|
174
|
+
raise ArgumentError, "`VCR.use_cassette` requires a block. " +
|
|
175
|
+
"If you cannot wrap your code in a block, use " +
|
|
176
|
+
"`VCR.insert_cassette` / `VCR.eject_cassette` instead."
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
cassette = insert_cassette(name, options)
|
|
180
|
+
|
|
181
|
+
begin
|
|
182
|
+
call_block(block, cassette)
|
|
183
|
+
ensure
|
|
184
|
+
eject_cassette
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Used to configure VCR.
|
|
189
|
+
#
|
|
190
|
+
# @example
|
|
191
|
+
# VCR.configure do |c|
|
|
192
|
+
# c.some_config_option = true
|
|
193
|
+
# end
|
|
194
|
+
#
|
|
195
|
+
# @yield the configuration block
|
|
196
|
+
# @yieldparam config [VCR::Configuration] the configuration object
|
|
197
|
+
# @return [void]
|
|
198
|
+
def configure
|
|
199
|
+
yield configuration
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# @return [VCR::Configuration] the VCR configuration.
|
|
203
|
+
def configuration
|
|
204
|
+
@configuration ||= Configuration.new
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Sets up `Before` and `After` cucumber hooks in order to
|
|
208
|
+
# use VCR with particular cucumber tags.
|
|
209
|
+
#
|
|
210
|
+
# @example
|
|
211
|
+
# VCR.cucumber_tags do |t|
|
|
212
|
+
# t.tags "tag1", "tag2"
|
|
213
|
+
# t.tag "@some_other_tag", :record => :new_episodes
|
|
214
|
+
# end
|
|
215
|
+
#
|
|
216
|
+
# @yield the cucumber tags configuration block
|
|
217
|
+
# @yieldparam t [VCR::CucumberTags] Cucumber tags config object
|
|
218
|
+
# @return [void]
|
|
219
|
+
# @see VCR::CucumberTags#tags
|
|
220
|
+
def cucumber_tags(&block)
|
|
221
|
+
main_object = eval('self', block.binding)
|
|
222
|
+
yield VCR::CucumberTags.new(main_object)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Turns VCR off for the duration of a block.
|
|
226
|
+
#
|
|
227
|
+
# @param (see #turn_off!)
|
|
228
|
+
# @return [void]
|
|
229
|
+
# @raise (see #turn_off!)
|
|
230
|
+
# @see #turn_off!
|
|
231
|
+
# @see #turn_on!
|
|
232
|
+
# @see #turned_on?
|
|
233
|
+
def turned_off(options = {})
|
|
234
|
+
turn_off!(options)
|
|
235
|
+
|
|
236
|
+
begin
|
|
237
|
+
yield
|
|
238
|
+
ensure
|
|
239
|
+
turn_on!
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Turns VCR off, so that it no longer handles every HTTP request.
|
|
244
|
+
#
|
|
245
|
+
# @param options [Hash] hash of options
|
|
246
|
+
# @option options :ignore_cassettes [Boolean] controls what happens when a cassette is
|
|
247
|
+
# inserted while VCR is turned off. If `true` is passed, the cassette insertion
|
|
248
|
+
# will be ignored; otherwise a {VCR::Errors::TurnedOffError} will be raised.
|
|
249
|
+
#
|
|
250
|
+
# @return [void]
|
|
251
|
+
# @raise [VCR::Errors::CassetteInUseError] if there is currently a cassette in use
|
|
252
|
+
# @raise [ArgumentError] if you pass an invalid option
|
|
253
|
+
def turn_off!(options = {})
|
|
254
|
+
if VCR.current_cassette
|
|
255
|
+
raise CassetteInUseError, "A VCR cassette is currently in use (#{VCR.current_cassette.name}). " +
|
|
256
|
+
"You must eject it before you can turn VCR off."
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
@ignore_cassettes = options[:ignore_cassettes]
|
|
260
|
+
invalid_options = options.keys - [:ignore_cassettes]
|
|
261
|
+
if invalid_options.any?
|
|
262
|
+
raise ArgumentError.new("You passed some invalid options: #{invalid_options.inspect}")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
@turned_off = true
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Turns on VCR, if it has previously been turned off.
|
|
269
|
+
# @return [void]
|
|
270
|
+
# @see #turn_off!
|
|
271
|
+
# @see #turned_off
|
|
272
|
+
# @see #turned_on?
|
|
273
|
+
def turn_on!
|
|
274
|
+
@turned_off = false
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# @return whether or not VCR is turned on
|
|
278
|
+
# @note Normally VCR is _always_ turned on; it will only be off if you have
|
|
279
|
+
# explicitly turned it off.
|
|
280
|
+
# @see #turn_on!
|
|
281
|
+
# @see #turn_off!
|
|
282
|
+
# @see #turned_off
|
|
283
|
+
def turned_on?
|
|
284
|
+
!@turned_off
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# @private
|
|
288
|
+
def http_interactions
|
|
289
|
+
return current_cassette.http_interactions if current_cassette
|
|
290
|
+
VCR::Cassette::HTTPInteractionList::NullList
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# @private
|
|
294
|
+
def real_http_connections_allowed?
|
|
295
|
+
return current_cassette.recording? if current_cassette
|
|
296
|
+
!!(configuration.allow_http_connections_when_no_cassette? || @turned_off)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# @return [RequestMatcherRegistry] the request matcher registry
|
|
300
|
+
def request_matchers
|
|
301
|
+
@request_matchers ||= RequestMatcherRegistry.new
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# @private
|
|
305
|
+
def request_ignorer
|
|
306
|
+
@request_ignorer ||= RequestIgnorer.new
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# @private
|
|
310
|
+
def library_hooks
|
|
311
|
+
@library_hooks ||= LibraryHooks.new
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# @private
|
|
315
|
+
def cassette_serializers
|
|
316
|
+
@cassette_serializers ||= Cassette::Serializers.new
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# @private
|
|
320
|
+
def cassette_persisters
|
|
321
|
+
@cassette_persisters ||= Cassette::Persisters.new
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# @private
|
|
325
|
+
def record_http_interaction(interaction)
|
|
326
|
+
return unless cassette = current_cassette
|
|
327
|
+
return if VCR.request_ignorer.ignore?(interaction.request)
|
|
328
|
+
|
|
329
|
+
cassette.record_http_interaction(interaction)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
private
|
|
333
|
+
|
|
334
|
+
def ignore_cassettes?
|
|
335
|
+
@ignore_cassettes
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def cassettes
|
|
339
|
+
@cassettes ||= []
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def initialize_ivars
|
|
343
|
+
@turned_off = false
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
initialize_ivars # to avoid warnings
|
|
347
|
+
end
|
data/lib/vcr/cassette.rb
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
require 'vcr/cassette/http_interaction_list'
|
|
2
|
+
require 'vcr/cassette/erb_renderer'
|
|
3
|
+
require 'vcr/cassette/serializers'
|
|
4
|
+
|
|
5
|
+
module VCR
|
|
6
|
+
# The media VCR uses to store HTTP interactions for later re-use.
|
|
7
|
+
class Cassette
|
|
8
|
+
include Logger::Mixin
|
|
9
|
+
|
|
10
|
+
# The supported record modes.
|
|
11
|
+
#
|
|
12
|
+
# * :all -- Record every HTTP interactions; do not play any back.
|
|
13
|
+
# * :none -- Do not record any HTTP interactions; play them back.
|
|
14
|
+
# * :new_episodes -- Playback previously recorded HTTP interactions and record new ones.
|
|
15
|
+
# * :once -- Record the HTTP interactions if the cassette has not already been recorded;
|
|
16
|
+
# otherwise, playback the HTTP interactions.
|
|
17
|
+
VALID_RECORD_MODES = [:all, :none, :new_episodes, :once]
|
|
18
|
+
|
|
19
|
+
# @return [#to_s] The name of the cassette. Used to determine the cassette's file name.
|
|
20
|
+
# @see #file
|
|
21
|
+
attr_reader :name
|
|
22
|
+
|
|
23
|
+
# @return [Symbol] The record mode. Determines whether the cassette records HTTP interactions,
|
|
24
|
+
# plays them back, or does both.
|
|
25
|
+
attr_reader :record_mode
|
|
26
|
+
|
|
27
|
+
# @return [Array<Symbol, #call>] List of request matchers. Used to find a response from an
|
|
28
|
+
# existing HTTP interaction to play back.
|
|
29
|
+
attr_reader :match_requests_on
|
|
30
|
+
|
|
31
|
+
# @return [Boolean, Hash] The cassette's ERB option. The file will be treated as an
|
|
32
|
+
# ERB template if this has a truthy value. A hash, if provided, will be used as local
|
|
33
|
+
# variables for the ERB template.
|
|
34
|
+
attr_reader :erb
|
|
35
|
+
|
|
36
|
+
# @return [Integer, nil] How frequently (in seconds) the cassette should be re-recorded.
|
|
37
|
+
attr_reader :re_record_interval
|
|
38
|
+
|
|
39
|
+
# @return [Array<Symbol>] If set, {VCR::Configuration#before_record} and
|
|
40
|
+
# {VCR::Configuration#before_playback} hooks with a corresponding tag will apply.
|
|
41
|
+
attr_reader :tags
|
|
42
|
+
|
|
43
|
+
# @param (see VCR#insert_cassette)
|
|
44
|
+
# @see VCR#insert_cassette
|
|
45
|
+
def initialize(name, options = {})
|
|
46
|
+
@name = name
|
|
47
|
+
@options = VCR.configuration.default_cassette_options.merge(options)
|
|
48
|
+
|
|
49
|
+
assert_valid_options!
|
|
50
|
+
extract_options
|
|
51
|
+
raise_error_unless_valid_record_mode
|
|
52
|
+
|
|
53
|
+
log "Initialized with options: #{@options.inspect}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Ejects the current cassette. The cassette will no longer be used.
|
|
57
|
+
# In addition, any newly recorded HTTP interactions will be written to
|
|
58
|
+
# disk.
|
|
59
|
+
#
|
|
60
|
+
# @note This is not intended to be called directly. Use `VCR.eject_cassette` instead.
|
|
61
|
+
#
|
|
62
|
+
# @param (see VCR#eject_casssette)
|
|
63
|
+
# @see VCR#eject_cassette
|
|
64
|
+
def eject(options = {})
|
|
65
|
+
write_recorded_interactions_to_disk
|
|
66
|
+
|
|
67
|
+
if should_assert_no_unused_interactions? && !options[:skip_no_unused_interactions_assertion]
|
|
68
|
+
http_interactions.assert_no_unused_interactions!
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @private
|
|
73
|
+
def http_interactions
|
|
74
|
+
@http_interactions ||= HTTPInteractionList.new \
|
|
75
|
+
should_stub_requests? ? previously_recorded_interactions : [],
|
|
76
|
+
match_requests_on,
|
|
77
|
+
@allow_playback_repeats,
|
|
78
|
+
@parent_list,
|
|
79
|
+
log_prefix
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @private
|
|
83
|
+
def record_http_interaction(interaction)
|
|
84
|
+
log "Recorded HTTP interaction #{request_summary(interaction.request)} => #{response_summary(interaction.response)}"
|
|
85
|
+
new_recorded_interactions << interaction
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @private
|
|
89
|
+
def new_recorded_interactions
|
|
90
|
+
@new_recorded_interactions ||= []
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @return [String] The file for this cassette.
|
|
94
|
+
# @raise [NotImplementedError] if the configured cassette persister
|
|
95
|
+
# does not support resolving file paths.
|
|
96
|
+
# @note VCR will take care of sanitizing the cassette name to make it a valid file name.
|
|
97
|
+
def file
|
|
98
|
+
unless @persister.respond_to?(:absolute_path_to_file)
|
|
99
|
+
raise NotImplementedError, "The configured cassette persister does not support resolving file paths"
|
|
100
|
+
end
|
|
101
|
+
@persister.absolute_path_to_file(storage_key)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @return [Boolean] Whether or not the cassette is recording.
|
|
105
|
+
def recording?
|
|
106
|
+
case record_mode
|
|
107
|
+
when :none; false
|
|
108
|
+
when :once; raw_cassette_bytes.to_s.empty?
|
|
109
|
+
else true
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @return [Hash] The hash that will be serialized when the cassette is written to disk.
|
|
114
|
+
def serializable_hash
|
|
115
|
+
{
|
|
116
|
+
"http_interactions" => interactions_to_record.map(&:to_hash),
|
|
117
|
+
"recorded_with" => "VCR #{VCR.version}"
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Time, nil] The `recorded_at` time of the first HTTP interaction
|
|
122
|
+
# or nil if the cassette has no prior HTTP interactions.
|
|
123
|
+
#
|
|
124
|
+
# @example
|
|
125
|
+
#
|
|
126
|
+
# VCR.use_cassette("some cassette") do |cassette|
|
|
127
|
+
# Timecop.freeze(cassette.originally_recorded_at || Time.now) do
|
|
128
|
+
# # ...
|
|
129
|
+
# end
|
|
130
|
+
# end
|
|
131
|
+
def originally_recorded_at
|
|
132
|
+
@originally_recorded_at ||= previously_recorded_interactions.map(&:recorded_at).min
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def assert_valid_options!
|
|
138
|
+
invalid_options = @options.keys - [
|
|
139
|
+
:record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
|
|
140
|
+
:update_content_length_header, :allow_playback_repeats, :allow_unused_http_interactions,
|
|
141
|
+
:exclusive, :serialize_with, :preserve_exact_body_bytes, :decode_compressed_response,
|
|
142
|
+
:persist_with
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
if invalid_options.size > 0
|
|
146
|
+
raise ArgumentError.new("You passed the following invalid options to VCR::Cassette.new: #{invalid_options.inspect}.")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def extract_options
|
|
151
|
+
[:erb, :match_requests_on, :re_record_interval,
|
|
152
|
+
:allow_playback_repeats, :allow_unused_http_interactions, :exclusive].each do |name|
|
|
153
|
+
instance_variable_set("@#{name}", @options[name])
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
assign_tags
|
|
157
|
+
|
|
158
|
+
@record_mode = @options[:record]
|
|
159
|
+
@serializer = VCR.cassette_serializers[@options[:serialize_with]]
|
|
160
|
+
@persister = VCR.cassette_persisters[@options[:persist_with]]
|
|
161
|
+
@record_mode = :all if should_re_record?
|
|
162
|
+
@parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def assign_tags
|
|
166
|
+
@tags = Array(@options.fetch(:tags) { @options[:tag] })
|
|
167
|
+
|
|
168
|
+
[:update_content_length_header, :preserve_exact_body_bytes, :decode_compressed_response].each do |tag|
|
|
169
|
+
@tags << tag if @options[tag]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def previously_recorded_interactions
|
|
174
|
+
@previously_recorded_interactions ||= if !raw_cassette_bytes.to_s.empty?
|
|
175
|
+
deserialized_hash['http_interactions'].map { |h| HTTPInteraction.from_hash(h) }.tap do |interactions|
|
|
176
|
+
invoke_hook(:before_playback, interactions)
|
|
177
|
+
|
|
178
|
+
interactions.reject! do |i|
|
|
179
|
+
i.request.uri.is_a?(String) && VCR.request_ignorer.ignore?(i.request)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
else
|
|
183
|
+
[]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def storage_key
|
|
188
|
+
@storage_key ||= [name, @serializer.file_extension].join('.')
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def raise_error_unless_valid_record_mode
|
|
192
|
+
unless VALID_RECORD_MODES.include?(record_mode)
|
|
193
|
+
raise ArgumentError.new("#{record_mode} is not a valid cassette record mode. Valid modes are: #{VALID_RECORD_MODES.inspect}")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def should_re_record?
|
|
198
|
+
return false unless @re_record_interval
|
|
199
|
+
return false unless originally_recorded_at
|
|
200
|
+
|
|
201
|
+
now = Time.now
|
|
202
|
+
|
|
203
|
+
(originally_recorded_at + @re_record_interval < now).tap do |value|
|
|
204
|
+
info = "previously recorded at: '#{originally_recorded_at}'; now: '#{now}'; interval: #{@re_record_interval} seconds"
|
|
205
|
+
|
|
206
|
+
if !value
|
|
207
|
+
log "Not re-recording since the interval has not elapsed (#{info})."
|
|
208
|
+
elsif InternetConnection.available?
|
|
209
|
+
log "re-recording (#{info})."
|
|
210
|
+
else
|
|
211
|
+
log "Not re-recording because no internet connection is available (#{info})."
|
|
212
|
+
return false
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def should_stub_requests?
|
|
218
|
+
record_mode != :all
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def should_remove_matching_existing_interactions?
|
|
222
|
+
record_mode == :all
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def should_assert_no_unused_interactions?
|
|
226
|
+
!(@allow_unused_http_interactions || $!)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def raw_cassette_bytes
|
|
230
|
+
@raw_cassette_bytes ||= VCR::Cassette::ERBRenderer.new(@persister[storage_key], erb, name).render
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def merged_interactions
|
|
234
|
+
old_interactions = previously_recorded_interactions
|
|
235
|
+
|
|
236
|
+
if should_remove_matching_existing_interactions?
|
|
237
|
+
new_interaction_list = HTTPInteractionList.new(new_recorded_interactions, match_requests_on)
|
|
238
|
+
old_interactions = old_interactions.reject do |i|
|
|
239
|
+
new_interaction_list.response_for(i.request)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
old_interactions + new_recorded_interactions
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def interactions_to_record
|
|
247
|
+
# We deep-dup the interactions by roundtripping them to/from a hash.
|
|
248
|
+
# This is necessary because `before_record` can mutate the interactions.
|
|
249
|
+
merged_interactions.map { |i| HTTPInteraction.from_hash(i.to_hash) }.tap do |interactions|
|
|
250
|
+
invoke_hook(:before_record, interactions)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def write_recorded_interactions_to_disk
|
|
255
|
+
return if new_recorded_interactions.none?
|
|
256
|
+
hash = serializable_hash
|
|
257
|
+
return if hash["http_interactions"].none?
|
|
258
|
+
|
|
259
|
+
@persister[storage_key] = @serializer.serialize(hash)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def invoke_hook(type, interactions)
|
|
263
|
+
interactions.delete_if do |i|
|
|
264
|
+
i.hook_aware.tap do |hw|
|
|
265
|
+
VCR.configuration.invoke_hook(type, hw, self)
|
|
266
|
+
end.ignored?
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def deserialized_hash
|
|
271
|
+
@deserialized_hash ||= @serializer.deserialize(raw_cassette_bytes).tap do |hash|
|
|
272
|
+
unless hash.is_a?(Hash) && hash['http_interactions'].is_a?(Array)
|
|
273
|
+
raise Errors::InvalidCassetteFormatError.new \
|
|
274
|
+
"#{file} does not appear to be a valid VCR 2.0 cassette. " +
|
|
275
|
+
"VCR 1.x cassettes are not valid with VCR 2.0. When upgrading from " +
|
|
276
|
+
"VCR 1.x, it is recommended that you delete all your existing cassettes and " +
|
|
277
|
+
"re-record them, or use the provided vcr:migrate_cassettes rake task to migrate " +
|
|
278
|
+
"them. For more info, see the VCR upgrade guide."
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def log_prefix
|
|
284
|
+
@log_prefix ||= "[Cassette: '#{name}'] "
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def request_summary(request)
|
|
288
|
+
super(request, match_requests_on)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|