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.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/features/about_these_examples.md +18 -0
  3. data/features/cassettes/allow_unused_http_interactions.feature +100 -0
  4. data/features/cassettes/automatic_re_recording.feature +72 -0
  5. data/features/cassettes/decompress.feature +74 -0
  6. data/features/cassettes/dynamic_erb.feature +100 -0
  7. data/features/cassettes/exclusive.feature +126 -0
  8. data/features/cassettes/format.feature +323 -0
  9. data/features/cassettes/freezing_time.feature +68 -0
  10. data/features/cassettes/naming.feature +28 -0
  11. data/features/cassettes/no_cassette.feature +152 -0
  12. data/features/cassettes/update_content_length_header.feature +112 -0
  13. data/features/configuration/allow_http_connections_when_no_cassette.feature +55 -0
  14. data/features/configuration/cassette_library_dir.feature +31 -0
  15. data/features/configuration/debug_logging.feature +59 -0
  16. data/features/configuration/default_cassette_options.feature +100 -0
  17. data/features/configuration/filter_sensitive_data.feature +153 -0
  18. data/features/configuration/hook_into.feature +172 -0
  19. data/features/configuration/ignore_request.feature +192 -0
  20. data/features/configuration/preserve_exact_body_bytes.feature +108 -0
  21. data/features/configuration/query_parser.feature +84 -0
  22. data/features/configuration/uri_parser.feature +89 -0
  23. data/features/getting_started.md +82 -0
  24. data/features/hooks/after_http_request.feature +58 -0
  25. data/features/hooks/around_http_request.feature +57 -0
  26. data/features/hooks/before_http_request.feature +63 -0
  27. data/features/hooks/before_playback.feature +184 -0
  28. data/features/hooks/before_record.feature +172 -0
  29. data/features/http_libraries/em_http_request.feature +250 -0
  30. data/features/http_libraries/net_http.feature +179 -0
  31. data/features/middleware/faraday.feature +56 -0
  32. data/features/middleware/rack.feature +92 -0
  33. data/features/record_modes/all.feature +82 -0
  34. data/features/record_modes/new_episodes.feature +79 -0
  35. data/features/record_modes/none.feature +72 -0
  36. data/features/record_modes/once.feature +95 -0
  37. data/features/request_matching/README.md +30 -0
  38. data/features/request_matching/body.feature +91 -0
  39. data/features/request_matching/body_as_json.feature +90 -0
  40. data/features/request_matching/custom_matcher.feature +135 -0
  41. data/features/request_matching/headers.feature +85 -0
  42. data/features/request_matching/host.feature +95 -0
  43. data/features/request_matching/identical_request_sequence.feature +89 -0
  44. data/features/request_matching/method.feature +96 -0
  45. data/features/request_matching/path.feature +96 -0
  46. data/features/request_matching/playback_repeats.feature +98 -0
  47. data/features/request_matching/query.feature +97 -0
  48. data/features/request_matching/uri.feature +94 -0
  49. data/features/request_matching/uri_without_param.feature +101 -0
  50. data/features/step_definitions/cli_steps.rb +193 -0
  51. data/features/support/env.rb +44 -0
  52. data/features/support/http_lib_filters.rb +53 -0
  53. data/features/test_frameworks/cucumber.feature +211 -0
  54. data/features/test_frameworks/rspec_macro.feature +81 -0
  55. data/features/test_frameworks/rspec_metadata.feature +150 -0
  56. data/features/test_frameworks/test_unit.feature +49 -0
  57. data/lib/vcr.rb +347 -0
  58. data/lib/vcr/cassette.rb +291 -0
  59. data/lib/vcr/cassette/erb_renderer.rb +55 -0
  60. data/lib/vcr/cassette/http_interaction_list.rb +108 -0
  61. data/lib/vcr/cassette/migrator.rb +118 -0
  62. data/lib/vcr/cassette/persisters.rb +42 -0
  63. data/lib/vcr/cassette/persisters/file_system.rb +64 -0
  64. data/lib/vcr/cassette/serializers.rb +57 -0
  65. data/lib/vcr/cassette/serializers/json.rb +48 -0
  66. data/lib/vcr/cassette/serializers/psych.rb +48 -0
  67. data/lib/vcr/cassette/serializers/syck.rb +61 -0
  68. data/lib/vcr/cassette/serializers/yaml.rb +50 -0
  69. data/lib/vcr/configuration.rb +555 -0
  70. data/lib/vcr/deprecations.rb +109 -0
  71. data/lib/vcr/errors.rb +266 -0
  72. data/lib/vcr/extensions/net_http_response.rb +36 -0
  73. data/lib/vcr/library_hooks.rb +18 -0
  74. data/lib/vcr/library_hooks/excon.rb +27 -0
  75. data/lib/vcr/library_hooks/fakeweb.rb +196 -0
  76. data/lib/vcr/library_hooks/faraday.rb +51 -0
  77. data/lib/vcr/library_hooks/typhoeus.rb +120 -0
  78. data/lib/vcr/library_hooks/typhoeus_0.4.rb +103 -0
  79. data/lib/vcr/library_hooks/webmock.rb +164 -0
  80. data/lib/vcr/middleware/excon.rb +221 -0
  81. data/lib/vcr/middleware/excon/legacy_methods.rb +33 -0
  82. data/lib/vcr/middleware/faraday.rb +118 -0
  83. data/lib/vcr/middleware/rack.rb +79 -0
  84. data/lib/vcr/request_handler.rb +114 -0
  85. data/lib/vcr/request_ignorer.rb +43 -0
  86. data/lib/vcr/request_matcher_registry.rb +149 -0
  87. data/lib/vcr/structs.rb +578 -0
  88. data/lib/vcr/tasks/vcr.rake +9 -0
  89. data/lib/vcr/test_frameworks/cucumber.rb +64 -0
  90. data/lib/vcr/test_frameworks/rspec.rb +47 -0
  91. data/lib/vcr/util/hooks.rb +61 -0
  92. data/lib/vcr/util/internet_connection.rb +43 -0
  93. data/lib/vcr/util/logger.rb +59 -0
  94. data/lib/vcr/util/variable_args_block_caller.rb +13 -0
  95. data/lib/vcr/util/version_checker.rb +48 -0
  96. data/lib/vcr/version.rb +34 -0
  97. data/spec/acceptance/threading_spec.rb +34 -0
  98. data/spec/fixtures/cassette_spec/1_x_cassette.yml +110 -0
  99. data/spec/fixtures/cassette_spec/empty.yml +0 -0
  100. data/spec/fixtures/cassette_spec/example.yml +111 -0
  101. data/spec/fixtures/cassette_spec/with_localhost_requests.yml +111 -0
  102. data/spec/fixtures/fake_example_responses.yml +110 -0
  103. data/spec/fixtures/match_requests_on.yml +187 -0
  104. data/spec/lib/vcr/cassette/erb_renderer_spec.rb +53 -0
  105. data/spec/lib/vcr/cassette/http_interaction_list_spec.rb +295 -0
  106. data/spec/lib/vcr/cassette/migrator_spec.rb +195 -0
  107. data/spec/lib/vcr/cassette/persisters/file_system_spec.rb +69 -0
  108. data/spec/lib/vcr/cassette/persisters_spec.rb +39 -0
  109. data/spec/lib/vcr/cassette/serializers_spec.rb +176 -0
  110. data/spec/lib/vcr/cassette_spec.rb +618 -0
  111. data/spec/lib/vcr/configuration_spec.rb +326 -0
  112. data/spec/lib/vcr/deprecations_spec.rb +85 -0
  113. data/spec/lib/vcr/errors_spec.rb +162 -0
  114. data/spec/lib/vcr/extensions/net_http_response_spec.rb +86 -0
  115. data/spec/lib/vcr/library_hooks/excon_spec.rb +104 -0
  116. data/spec/lib/vcr/library_hooks/fakeweb_spec.rb +169 -0
  117. data/spec/lib/vcr/library_hooks/faraday_spec.rb +68 -0
  118. data/spec/lib/vcr/library_hooks/typhoeus_0.4_spec.rb +36 -0
  119. data/spec/lib/vcr/library_hooks/typhoeus_spec.rb +162 -0
  120. data/spec/lib/vcr/library_hooks/webmock_spec.rb +118 -0
  121. data/spec/lib/vcr/library_hooks_spec.rb +51 -0
  122. data/spec/lib/vcr/middleware/faraday_spec.rb +182 -0
  123. data/spec/lib/vcr/middleware/rack_spec.rb +115 -0
  124. data/spec/lib/vcr/request_ignorer_spec.rb +70 -0
  125. data/spec/lib/vcr/request_matcher_registry_spec.rb +345 -0
  126. data/spec/lib/vcr/structs_spec.rb +732 -0
  127. data/spec/lib/vcr/test_frameworks/cucumber_spec.rb +107 -0
  128. data/spec/lib/vcr/test_frameworks/rspec_spec.rb +83 -0
  129. data/spec/lib/vcr/util/hooks_spec.rb +158 -0
  130. data/spec/lib/vcr/util/internet_connection_spec.rb +37 -0
  131. data/spec/lib/vcr/util/version_checker_spec.rb +31 -0
  132. data/spec/lib/vcr/version_spec.rb +27 -0
  133. data/spec/lib/vcr_spec.rb +349 -0
  134. data/spec/monkey_patches.rb +182 -0
  135. data/spec/spec_helper.rb +62 -0
  136. data/spec/support/configuration_stubbing.rb +8 -0
  137. data/spec/support/cucumber_helpers.rb +35 -0
  138. data/spec/support/fixnum_extension.rb +10 -0
  139. data/spec/support/http_library_adapters.rb +289 -0
  140. data/spec/support/limited_uri.rb +21 -0
  141. data/spec/support/ruby_interpreter.rb +7 -0
  142. data/spec/support/shared_example_groups/excon.rb +63 -0
  143. data/spec/support/shared_example_groups/hook_into_http_library.rb +594 -0
  144. data/spec/support/shared_example_groups/request_hooks.rb +59 -0
  145. data/spec/support/sinatra_app.rb +86 -0
  146. data/spec/support/vcr_localhost_server.rb +76 -0
  147. data/spec/support/vcr_stub_helpers.rb +17 -0
  148. metadata +677 -0
@@ -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
@@ -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