vcr 2.0.0.rc1 → 2.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/.gitignore +2 -0
  2. data/.limited_red +1 -0
  3. data/.travis.yml +10 -1
  4. data/.yardopts +9 -0
  5. data/CHANGELOG.md +51 -1
  6. data/Gemfile +5 -1
  7. data/LICENSE +1 -1
  8. data/README.md +23 -28
  9. data/Rakefile +63 -18
  10. data/Upgrade.md +200 -0
  11. data/features/.nav +2 -0
  12. data/features/cassettes/automatic_re_recording.feature +19 -15
  13. data/features/cassettes/dynamic_erb.feature +12 -4
  14. data/features/cassettes/exclusive.feature +31 -23
  15. data/features/cassettes/format.feature +54 -30
  16. data/features/cassettes/naming.feature +1 -1
  17. data/features/cassettes/update_content_length_header.feature +16 -12
  18. data/features/configuration/allow_http_connections_when_no_cassette.feature +1 -1
  19. data/features/configuration/debug_logging.feature +52 -0
  20. data/features/configuration/filter_sensitive_data.feature +4 -4
  21. data/features/configuration/hook_into.feature +5 -2
  22. data/features/configuration/ignore_request.feature +5 -3
  23. data/features/configuration/preserve_exact_body_bytes.feature +103 -0
  24. data/features/hooks/after_http_request.feature +17 -4
  25. data/features/hooks/around_http_request.feature +2 -1
  26. data/features/hooks/before_http_request.feature +25 -8
  27. data/features/hooks/before_playback.feature +16 -12
  28. data/features/hooks/before_record.feature +2 -2
  29. data/features/http_libraries/em_http_request.feature +82 -58
  30. data/features/http_libraries/net_http.feature +6 -6
  31. data/features/middleware/faraday.feature +2 -1
  32. data/features/middleware/rack.feature +2 -2
  33. data/features/record_modes/all.feature +19 -15
  34. data/features/record_modes/new_episodes.feature +17 -13
  35. data/features/record_modes/none.feature +15 -11
  36. data/features/record_modes/once.feature +16 -12
  37. data/features/request_matching/body.feature +28 -20
  38. data/features/request_matching/custom_matcher.feature +28 -20
  39. data/features/request_matching/headers.feature +34 -26
  40. data/features/request_matching/host.feature +28 -20
  41. data/features/request_matching/identical_request_sequence.feature +28 -20
  42. data/features/request_matching/method.feature +28 -20
  43. data/features/request_matching/path.feature +28 -20
  44. data/features/request_matching/playback_repeats.feature +28 -20
  45. data/features/request_matching/uri.feature +28 -20
  46. data/features/request_matching/uri_without_param.feature +28 -20
  47. data/features/support/env.rb +7 -6
  48. data/features/support/vcr_cucumber_helpers.rb +1 -0
  49. data/features/test_frameworks/cucumber.feature +8 -8
  50. data/features/test_frameworks/rspec_macro.feature +4 -4
  51. data/features/test_frameworks/rspec_metadata.feature +6 -6
  52. data/features/test_frameworks/shoulda.feature +1 -1
  53. data/features/test_frameworks/test_unit.feature +1 -1
  54. data/lib/vcr.rb +156 -5
  55. data/lib/vcr/cassette.rb +80 -30
  56. data/lib/vcr/cassette/http_interaction_list.rb +33 -4
  57. data/lib/vcr/cassette/migrator.rb +2 -3
  58. data/lib/vcr/cassette/reader.rb +1 -0
  59. data/lib/vcr/cassette/serializers.rb +22 -0
  60. data/lib/vcr/cassette/serializers/json.rb +27 -2
  61. data/lib/vcr/cassette/serializers/psych.rb +26 -2
  62. data/lib/vcr/cassette/serializers/syck.rb +28 -2
  63. data/lib/vcr/cassette/serializers/yaml.rb +28 -2
  64. data/lib/vcr/configuration.rb +348 -10
  65. data/lib/vcr/deprecations.rb +8 -0
  66. data/lib/vcr/errors.rb +40 -0
  67. data/lib/vcr/extensions/net_http_response.rb +12 -11
  68. data/lib/vcr/library_hooks.rb +1 -0
  69. data/lib/vcr/library_hooks/excon.rb +24 -3
  70. data/lib/vcr/library_hooks/fakeweb.rb +32 -16
  71. data/lib/vcr/library_hooks/faraday.rb +3 -0
  72. data/lib/vcr/library_hooks/typhoeus.rb +40 -37
  73. data/lib/vcr/library_hooks/webmock.rb +54 -34
  74. data/lib/vcr/middleware/faraday.rb +13 -0
  75. data/lib/vcr/middleware/rack.rb +35 -0
  76. data/lib/vcr/request_handler.rb +60 -8
  77. data/lib/vcr/request_ignorer.rb +1 -0
  78. data/lib/vcr/request_matcher_registry.rb +28 -0
  79. data/lib/vcr/structs.rb +245 -38
  80. data/lib/vcr/test_frameworks/cucumber.rb +10 -0
  81. data/lib/vcr/test_frameworks/rspec.rb +26 -1
  82. data/lib/vcr/util/hooks.rb +29 -27
  83. data/lib/vcr/util/internet_connection.rb +2 -0
  84. data/lib/vcr/util/logger.rb +25 -0
  85. data/lib/vcr/util/variable_args_block_caller.rb +1 -0
  86. data/lib/vcr/util/version_checker.rb +1 -0
  87. data/lib/vcr/version.rb +8 -1
  88. data/spec/capture_warnings.rb +3 -3
  89. data/spec/monkey_patches.rb +28 -13
  90. data/spec/spec_helper.rb +17 -0
  91. data/spec/support/http_library_adapters.rb +7 -4
  92. data/spec/support/shared_example_groups/hook_into_http_library.rb +96 -32
  93. data/spec/support/shared_example_groups/request_hooks.rb +9 -8
  94. data/spec/support/sinatra_app.rb +3 -1
  95. data/spec/support/vcr_localhost_server.rb +1 -0
  96. data/spec/vcr/cassette/http_interaction_list_spec.rb +119 -54
  97. data/spec/vcr/cassette/migrator_spec.rb +19 -6
  98. data/spec/vcr/cassette/serializers_spec.rb +51 -6
  99. data/spec/vcr/cassette_spec.rb +44 -19
  100. data/spec/vcr/configuration_spec.rb +91 -6
  101. data/spec/vcr/library_hooks/excon_spec.rb +54 -16
  102. data/spec/vcr/library_hooks/fakeweb_spec.rb +12 -21
  103. data/spec/vcr/library_hooks/typhoeus_spec.rb +2 -29
  104. data/spec/vcr/library_hooks/webmock_spec.rb +4 -18
  105. data/spec/vcr/middleware/faraday_spec.rb +1 -16
  106. data/spec/vcr/structs_spec.rb +194 -61
  107. data/spec/vcr/test_frameworks/rspec_spec.rb +10 -0
  108. data/spec/vcr/util/hooks_spec.rb +104 -56
  109. data/spec/vcr/util/version_checker_spec.rb +45 -0
  110. data/spec/vcr_spec.rb +11 -0
  111. data/vcr.gemspec +30 -34
  112. metadata +149 -95
  113. data/spec/support/shared_example_groups/version_checking.rb +0 -34
@@ -1,6 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
3
  Bundler.setup
4
+ require 'limited_red/plugins/cucumber' unless ENV['CI']
4
5
 
5
6
  require 'ruby-debug' if !defined?(RUBY_ENGINE) && RUBY_VERSION != '1.9.3' && !ENV['CI']
6
7
 
@@ -14,6 +15,12 @@ end
14
15
  Before do
15
16
  load_paths, requires = ['../../lib'], []
16
17
 
18
+ # Put any bundler-managed gems (such as :git gems) on the load path for when aruba shells out.
19
+ # Alternatively, we could hook up aruba to use bundler when it shells out, but invoking bundler
20
+ # for each and every time aruba starts ruby would slow everything down. We really only need it for
21
+ # bundler-managed gems.
22
+ load_paths.push($LOAD_PATH.grep %r|bundler/gems|)
23
+
17
24
  if RUBY_VERSION < '1.9'
18
25
  requires << "rubygems"
19
26
  else
@@ -24,12 +31,6 @@ Before do
24
31
  requires.map! { |r| "-r#{r}" }
25
32
  set_env('RUBYOPT', "-I#{load_paths.join(':')} #{requires.join(' ')}")
26
33
 
27
- if RUBY_PLATFORM == 'java'
28
- # ideas taken from: http://blog.headius.com/2010/03/jruby-startup-time-tips.html
29
- set_env('JRUBY_OPTS', '-X-C') # disable JIT since these processes are so short lived
30
- set_env('JAVA_OPTS', '-d32') # force jRuby to use client JVM for faster startup times
31
- end
32
-
33
34
  if additional_paths.any?
34
35
  existing_paths = ENV['PATH'].split(':')
35
36
  set_env('PATH', (additional_paths + existing_paths).join(':'))
@@ -38,6 +38,7 @@ def start_sinatra_app(options, &block)
38
38
  require 'sinatra'
39
39
  require 'support/vcr_localhost_server'
40
40
  klass = Class.new(Sinatra::Base)
41
+ klass.disable :protection
41
42
  klass.class_eval(&block)
42
43
 
43
44
  VCR::LocalhostServer.new(klass.new, options[:port])
@@ -96,10 +96,10 @@ Feature: Usage with Cucumber
96
96
  | GET http://localhost:7777/disallowed_1 |
97
97
  | An HTTP request has been made that VCR does not know how to handle: |
98
98
  | GET http://localhost:7777/disallowed_2 |
99
- And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "body: Hello localhost_request_1"
100
- And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "body: Hello localhost_request_2"
101
- And the file "features/cassettes/nested_cassette.yml" should contain "body: Hello nested_cassette"
102
- And the file "features/cassettes/allowed.yml" should contain "body: Hello allowed"
99
+ And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "Hello localhost_request_1"
100
+ And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "Hello localhost_request_2"
101
+ And the file "features/cassettes/nested_cassette.yml" should contain "Hello nested_cassette"
102
+ And the file "features/cassettes/allowed.yml" should contain "Hello allowed"
103
103
 
104
104
  # Run again without the server; we'll get the same responses because VCR
105
105
  # will replay the recorded responses.
@@ -110,8 +110,8 @@ Feature: Usage with Cucumber
110
110
  | GET http://localhost:7777/disallowed_1 |
111
111
  | An HTTP request has been made that VCR does not know how to handle: |
112
112
  | GET http://localhost:7777/disallowed_2 |
113
- And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "body: Hello localhost_request_1"
114
- And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "body: Hello localhost_request_2"
115
- And the file "features/cassettes/nested_cassette.yml" should contain "body: Hello nested_cassette"
116
- And the file "features/cassettes/allowed.yml" should contain "body: Hello allowed"
113
+ And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "Hello localhost_request_1"
114
+ And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "Hello localhost_request_2"
115
+ And the file "features/cassettes/nested_cassette.yml" should contain "Hello nested_cassette"
116
+ And the file "features/cassettes/allowed.yml" should contain "Hello allowed"
117
117
 
@@ -76,8 +76,8 @@ Feature: Usage with RSpec macro
76
76
  """
77
77
  When I run `rspec spec/vcr_example_spec.rb`
78
78
  Then the output should contain "2 examples, 0 failures"
79
- And the file "spec/cassettes/VCR-RSpec_integration/without_an_explicit_cassette_name.yml" should contain "body: Hello"
80
- And the file "spec/cassettes/net_http_example.yml" should contain "body: Hello"
79
+ And the file "spec/cassettes/VCR-RSpec_integration/without_an_explicit_cassette_name.yml" should contain "Hello"
80
+ And the file "spec/cassettes/net_http_example.yml" should contain "Hello"
81
81
 
82
82
  @rspec-1 @exclude-jruby
83
83
  Scenario: Use `use_vcr_cassette` macro with RSpec 1
@@ -101,6 +101,6 @@ Feature: Usage with RSpec macro
101
101
  """
102
102
  When I run `spec spec/vcr_example_spec.rb`
103
103
  Then the output should contain "2 examples, 0 failures"
104
- And the file "spec/cassettes/VCR-RSpec_integration/without_an_explicit_cassette_name.yml" should contain "body: Hello"
105
- And the file "spec/cassettes/net_http_example.yml" should contain "body: Hello"
104
+ And the file "spec/cassettes/VCR-RSpec_integration/without_an_explicit_cassette_name.yml" should contain "Hello"
105
+ And the file "spec/cassettes/net_http_example.yml" should contain "Hello"
106
106
 
@@ -31,7 +31,7 @@ Feature: Usage with RSpec metadata
31
31
 
32
32
  Scenario: Use `:vcr` metadata
33
33
  Given a file named "spec/vcr_example_spec.rb" with:
34
- """
34
+ """ruby
35
35
  start_sinatra_app(:port => 7777) do
36
36
  get('/') { "Hello" }
37
37
  end
@@ -66,14 +66,14 @@ Feature: Usage with RSpec metadata
66
66
  """
67
67
  When I run `rspec spec/vcr_example_spec.rb`
68
68
  Then it should pass with "4 examples, 0 failures"
69
- And the file "spec/cassettes/VCR_example_group_metadata/records_an_http_request.yml" should contain "body: Hello"
70
- And the file "spec/cassettes/VCR_example_group_metadata/records_another_http_request.yml" should contain "body: Hello"
71
- And the file "spec/cassettes/VCR_example_group_metadata/in_a_nested_example_group/records_another_one.yml" should contain "body: Hello"
72
- And the file "spec/cassettes/VCR_example_metadata/records_an_http_request.yml" should contain "body: Hello"
69
+ And the file "spec/cassettes/VCR_example_group_metadata/records_an_http_request.yml" should contain "Hello"
70
+ And the file "spec/cassettes/VCR_example_group_metadata/records_another_http_request.yml" should contain "Hello"
71
+ And the file "spec/cassettes/VCR_example_group_metadata/in_a_nested_example_group/records_another_one.yml" should contain "Hello"
72
+ And the file "spec/cassettes/VCR_example_metadata/records_an_http_request.yml" should contain "Hello"
73
73
 
74
74
  Scenario: Pass a hash to set the cassette options
75
75
  Given a file named "spec/vcr_example_spec.rb" with:
76
- """
76
+ """ruby
77
77
  require 'spec_helper'
78
78
 
79
79
  describe "Using an options hash", :vcr => { :cassette_name => "example", :record => :new_episodes } do
@@ -56,7 +56,7 @@ Feature: Usage with Shoulda
56
56
  When I set the "SERVER" environment variable to "true"
57
57
  And I run `ruby -Itest test/vcr_example_test.rb`
58
58
  Then it should pass with "1 tests, 1 assertions, 0 failures, 0 errors"
59
- And the file "test/fixtures/vcr_cassettes/shoulda_example.yml" should contain "body: Hello"
59
+ And the file "test/fixtures/vcr_cassettes/shoulda_example.yml" should contain "Hello"
60
60
 
61
61
  # Run again without starting the sinatra server so the response will be replayed
62
62
  When I set the "SERVER" environment variable to "false"
@@ -38,7 +38,7 @@ Feature: Usage with Test::Unit
38
38
  When I set the "SERVER" environment variable to "true"
39
39
  And I run `ruby -Itest test/vcr_example_test.rb`
40
40
  Then it should pass with "1 tests, 1 assertions, 0 failures, 0 errors"
41
- And the file "test/fixtures/vcr_cassettes/test_unit_example.yml" should contain "body: Hello"
41
+ And the file "test/fixtures/vcr_cassettes/test_unit_example.yml" should contain "Hello"
42
42
 
43
43
  # Run again without starting the sinatra server so the response will be replayed
44
44
  When I set the "SERVER" environment variable to "false"
data/lib/vcr.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'vcr/util/logger'
1
2
  require 'vcr/util/variable_args_block_caller'
2
3
 
3
4
  require 'vcr/cassette'
@@ -11,7 +12,9 @@ require 'vcr/request_matcher_registry'
11
12
  require 'vcr/structs'
12
13
  require 'vcr/version'
13
14
 
14
-
15
+ # The main entry point for VCR.
16
+ # @note This module is extended onto itself; thus, the methods listed
17
+ # here as instance methods are available directly off of VCR.
15
18
  module VCR
16
19
  include VariableArgsBlockCaller
17
20
  include Errors
@@ -27,10 +30,75 @@ module VCR
27
30
  autoload :Rack, 'vcr/middleware/rack'
28
31
  end
29
32
 
33
+ # The currently active cassette.
34
+ #
35
+ # @return [nil, VCR::Cassette] The current cassette or nil if there is
36
+ # no current cassette.
30
37
  def current_cassette
31
38
  cassettes.last
32
39
  end
33
40
 
41
+ # Inserts the named cassette using the given cassette options.
42
+ # New HTTP interactions, if allowed by the cassette's `:record` option, will
43
+ # be recorded to the cassette. The cassette's existing HTTP interactions
44
+ # will be used to stub requests, unless prevented by the cassete's
45
+ # `:record` option.
46
+ #
47
+ # @example
48
+ # VCR.insert_cassette('twitter', :record => :new_episodes)
49
+ #
50
+ # # ...later, after making an HTTP request:
51
+ #
52
+ # VCR.eject_cassette
53
+ #
54
+ # @param name [#to_s] The name of the cassette. VCR will sanitize
55
+ # this to ensure it is a valid file name.
56
+ # @param options [Hash] The cassette options. The given options will
57
+ # be merged with the configured default_cassette_options.
58
+ # @option options :record [:all, :none, :new_episodes, :once] The record mode.
59
+ # @option options :erb [Boolean, Hash] Whether or not to evaluate the
60
+ # cassette as an ERB template. Defaults to false. A hash can be used
61
+ # to provide the ERB template with local variables.
62
+ # @option options :match_requests_on [Array<Symbol, #call>] List of request matchers
63
+ # to use to determine what recorded HTTP interaction to replay. Defaults to
64
+ # [:method, :uri]. The built-in matchers are :method, :uri, :host, :path, :headers
65
+ # and :body. You can also pass the name of a registered custom request matcher or
66
+ # any object that responds to #call.
67
+ # @option options :re_record_interval [Integer] When given, the
68
+ # cassette will be re-recorded at the given interval, in seconds.
69
+ # @option options :tag [Symbol] Used to apply tagged `before_record`
70
+ # and `before_playback` hooks to the cassette.
71
+ # @option options :tags [Array<Symbol>] Used to apply multiple tags to
72
+ # a cassette so that tagged `before_record` and `before_playback` hooks
73
+ # will apply to the cassette.
74
+ # @option options :update_content_length_header [Boolean] Whether or
75
+ # not to overwrite the Content-Length header of the responses to
76
+ # match the length of the response body. Defaults to false.
77
+ # @option options :allow_playback_repeats [Boolean] Whether or not to
78
+ # allow a single HTTP interaction to be played back multiple times.
79
+ # Defaults to false.
80
+ # @option options :exclusive [Boolean] Whether or not to use only this
81
+ # cassette and to completely ignore any cassettes in the cassettes stack.
82
+ # Defaults to false.
83
+ # @option options :serialize_with [Symbol] Which serializer to use.
84
+ # Valid values are :yaml, :syck, :psych, :json or any registered
85
+ # custom serializer. Defaults to :yaml.
86
+ # @option options :preserve_exact_body_bytes [Boolean] Whether or not
87
+ # to base64 encode the bytes of the requests and responses for this cassette
88
+ # when serializing it. See also `VCR::Configuration#preserve_exact_body_bytes`.
89
+ #
90
+ # @return [VCR::Cassette] the inserted cassette
91
+ #
92
+ # @raise [ArgumentError] when the given cassette is already being used.
93
+ # @raise [VCR::Errors::TurnedOffError] when VCR has been turned off
94
+ # without using the :ignore_cassettes option.
95
+ # @raise [VCR::Errors::MissingERBVariableError] when the `:erb` option
96
+ # is used and the ERB template requires variables that you did not provide.
97
+ #
98
+ # @note If you use this method you _must_ call `eject_cassette` when you
99
+ # are done. It is generally recommended that you use {#use_cassette}
100
+ # unless your code-under-test cannot be run as a block.
101
+ #
34
102
  def insert_cassette(name, options = {})
35
103
  if turned_on?
36
104
  if cassettes.any? { |c| c.name == name }
@@ -47,14 +115,36 @@ module VCR
47
115
  end
48
116
  end
49
117
 
118
+ # Ejects the current cassette. The cassette will no longer be used.
119
+ # In addition, any newly recorded HTTP interactions will be written to
120
+ # disk.
121
+ #
122
+ # @return [VCR::Cassette, nil] the ejected cassette if there was one
50
123
  def eject_cassette
51
- cassette = cassettes.pop
124
+ cassette = cassettes.last
52
125
  cassette.eject if cassette
53
- cassette
126
+ cassettes.pop
54
127
  end
55
128
 
56
- def use_cassette(*args, &block)
57
- cassette = insert_cassette(*args)
129
+ # Inserts a cassette using the given name and options, runs the given
130
+ # block, and ejects the cassette.
131
+ #
132
+ # @example
133
+ # VCR.use_cassette('twitter', :record => :new_episodes) do
134
+ # # make an HTTP request
135
+ # end
136
+ #
137
+ # @param (see #insert_cassette)
138
+ # @option (see #insert_cassette)
139
+ # @yield Block to run while this cassette is in use.
140
+ # @yieldparam cassette [(optional) VCR::Cassette] the cassette that has
141
+ # been inserted.
142
+ # @raise (see #insert_cassette)
143
+ # @return [void]
144
+ # @see #insert_cassette
145
+ # @see #eject_cassette
146
+ def use_cassette(name, options = {}, &block)
147
+ cassette = insert_cassette(name, options)
58
148
 
59
149
  begin
60
150
  call_block(block, cassette)
@@ -63,45 +153,76 @@ module VCR
63
153
  end
64
154
  end
65
155
 
156
+ # @private
66
157
  def http_interactions
67
158
  return current_cassette.http_interactions if current_cassette
68
159
  VCR::Cassette::HTTPInteractionList::NullList
69
160
  end
70
161
 
162
+ # @private
71
163
  def real_http_connections_allowed?
72
164
  return current_cassette.recording? if current_cassette
73
165
  configuration.allow_http_connections_when_no_cassette? || @turned_off
74
166
  end
75
167
 
168
+ # @return [RequestMatcherRegistry] the request matcher registry
76
169
  def request_matchers
77
170
  @request_matchers ||= RequestMatcherRegistry.new
78
171
  end
79
172
 
173
+ # @private
80
174
  def request_ignorer
81
175
  @request_ignorer ||= RequestIgnorer.new
82
176
  end
83
177
 
178
+ # @private
84
179
  def library_hooks
85
180
  @library_hooks ||= LibraryHooks.new
86
181
  end
87
182
 
183
+ # @private
88
184
  def cassette_serializers
89
185
  @cassette_serializers ||= Cassette::Serializers.new
90
186
  end
91
187
 
188
+ # @return [VCR::Configuration] the VCR configuration.
92
189
  def configuration
93
190
  @configuration ||= Configuration.new
94
191
  end
95
192
 
193
+ # Used to configure VCR.
194
+ #
195
+ # @example
196
+ # VCR.configure do |c|
197
+ # c.some_config_option = true
198
+ # end
199
+ #
200
+ # @yield the configuration block
201
+ # @yieldparam config [VCR::Configuration] the configuration object
202
+ # @return [void]
96
203
  def configure
97
204
  yield configuration
98
205
  end
99
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
100
220
  def cucumber_tags(&block)
101
221
  main_object = eval('self', block.binding)
102
222
  yield VCR::CucumberTags.new(main_object)
103
223
  end
104
224
 
225
+ # @private
105
226
  def record_http_interaction(interaction)
106
227
  return unless cassette = current_cassette
107
228
  return if VCR.request_ignorer.ignore?(interaction.request)
@@ -109,6 +230,14 @@ module VCR
109
230
  cassette.record_http_interaction(interaction)
110
231
  end
111
232
 
233
+ # Turns VCR off for the duration of a block.
234
+ #
235
+ # @param (see #turn_off!)
236
+ # @return [void]
237
+ # @raise (see #turn_off!)
238
+ # @see #turn_off!
239
+ # @see #turn_on!
240
+ # @see #turned_on?
112
241
  def turned_off(options = {})
113
242
  turn_off!(options)
114
243
 
@@ -119,6 +248,16 @@ module VCR
119
248
  end
120
249
  end
121
250
 
251
+ # Turns VCR off, so that it no longer handles every HTTP request.
252
+ #
253
+ # @param options [Hash] hash of options
254
+ # @option options :ignore_cassettes [Boolean] controls what happens when a cassette is
255
+ # inserted while VCR is turned off. If +true+ is passed, the cassette insertion
256
+ # will be ignored; otherwise a {VCR::Errors::TurnedOffError} will be raised.
257
+ #
258
+ # @return [void]
259
+ # @raise [VCR::Errors::CassetteInUseError] if there is currently a cassette in use
260
+ # @raise [ArgumentError] if you pass an invalid option
122
261
  def turn_off!(options = {})
123
262
  if VCR.current_cassette
124
263
  raise CassetteInUseError.new("A VCR cassette is currently in use. You must eject it before you can turn VCR off.")
@@ -133,14 +272,26 @@ module VCR
133
272
  @turned_off = true
134
273
  end
135
274
 
275
+ # Turns on VCR, if it has previously been turned off.
276
+ # @return [void]
277
+ # @see #turn_off!
278
+ # @see #turned_off
279
+ # @see #turned_on?
136
280
  def turn_on!
137
281
  @turned_off = false
138
282
  end
139
283
 
284
+ # @return whether or not VCR is turned on
285
+ # @note Normally VCR is _always_ turned on; it will only be off if you have
286
+ # explicitly turned it off.
287
+ # @see #turn_on!
288
+ # @see #turn_off!
289
+ # @see #turned_off
140
290
  def turned_on?
141
291
  !@turned_off
142
292
  end
143
293
 
294
+ # @private
144
295
  def ignore_cassettes?
145
296
  @ignore_cassettes
146
297
  end
data/lib/vcr/cassette.rb CHANGED
@@ -6,17 +6,51 @@ require 'vcr/cassette/reader'
6
6
  require 'vcr/cassette/serializers'
7
7
 
8
8
  module VCR
9
+ # The media VCR uses to store HTTP interactions for later re-use.
9
10
  class Cassette
11
+ include Logger
12
+
13
+ # The supported record modes.
14
+ #
15
+ # * :all -- Record every HTTP interactions; do not play any back.
16
+ # * :none -- Do not record any HTTP interactions; play them back.
17
+ # * :new_episodes -- Playback previously recorded HTTP interactions and record new ones.
18
+ # * :once -- Record the HTTP interactions if the cassette has not already been recorded;
19
+ # otherwise, playback the HTTP interactions.
10
20
  VALID_RECORD_MODES = [:all, :none, :new_episodes, :once]
11
21
 
12
- attr_reader :name, :record_mode, :match_requests_on, :erb, :re_record_interval, :tag
22
+ # @return [#to_s] The name of the cassette. Used to determine the cassette's file name.
23
+ # @see #file
24
+ attr_reader :name
13
25
 
26
+ # @return [Symbol] The record mode. Determines whether the cassette records HTTP interactions,
27
+ # plays them back, or does both.
28
+ attr_reader :record_mode
29
+
30
+ # @return [Array<Symbol, #call>] List of request matchers. Used to find a response from an
31
+ # existing HTTP interaction to play back.
32
+ attr_reader :match_requests_on
33
+
34
+ # @return [Boolean, Hash] The cassette's ERB option. The file will be treated as an
35
+ # ERB template if this has a truthy value. A hash, if provided, will be used as local
36
+ # variables for the ERB template.
37
+ attr_reader :erb
38
+
39
+ # @return [Integer, nil] How frequently (in seconds) the cassette should be re-recorded.
40
+ attr_reader :re_record_interval
41
+
42
+ # @return [Array<Symbol>] If set, {VCR::Configuration#before_record} and
43
+ # {VCR::Configuration#before_playback} hooks with a corresponding tag will apply.
44
+ attr_reader :tags
45
+
46
+ # @param (see VCR#insert_cassette)
47
+ # @see VCR#insert_cassette
14
48
  def initialize(name, options = {})
15
49
  options = VCR.configuration.default_cassette_options.merge(options)
16
50
  invalid_options = options.keys - [
17
- :record, :erb, :match_requests_on, :re_record_interval, :tag,
51
+ :record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
18
52
  :update_content_length_header, :allow_playback_repeats, :exclusive,
19
- :serialize_with
53
+ :serialize_with, :preserve_exact_body_bytes
20
54
  ]
21
55
 
22
56
  if invalid_options.size > 0
@@ -28,8 +62,9 @@ module VCR
28
62
  @erb = options[:erb]
29
63
  @match_requests_on = options[:match_requests_on]
30
64
  @re_record_interval = options[:re_record_interval]
31
- @tag = options[:tag]
32
- @update_content_length_header = options[:update_content_length_header]
65
+ @tags = Array(options.fetch(:tags) { options[:tag] })
66
+ @tags << :update_content_length_header if options[:update_content_length_header]
67
+ @tags << :preserve_exact_body_bytes if options[:preserve_exact_body_bytes]
33
68
  @allow_playback_repeats = options[:allow_playback_repeats]
34
69
  @exclusive = options[:exclusive]
35
70
  @serializer = VCR.cassette_serializers[options[:serialize_with]]
@@ -37,55 +72,46 @@ module VCR
37
72
  @parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions
38
73
 
39
74
  raise_error_unless_valid_record_mode
75
+
76
+ log "Initialized with options: #{options.inspect}"
40
77
  end
41
78
 
79
+ # Ejects the current cassette. The cassette will no longer be used.
80
+ # In addition, any newly recorded HTTP interactions will be written to
81
+ # disk.
42
82
  def eject
43
83
  write_recorded_interactions_to_disk
44
84
  end
45
85
 
46
- def previously_recorded_interactions
47
- @previously_recorded_interactions ||= if file && File.size?(file)
48
- deserialized_hash['http_interactions'].map { |h| HTTPInteraction.from_hash(h) }.tap do |interactions|
49
- invoke_hook(:before_playback, interactions)
50
-
51
- interactions.reject! do |i|
52
- i.request.uri.is_a?(String) && VCR.request_ignorer.ignore?(i.request)
53
- end
54
-
55
- if update_content_length_header?
56
- interactions.each { |i| i.response.update_content_length_header }
57
- end
58
- end
59
- else
60
- []
61
- end
62
- end
63
-
86
+ # @private
64
87
  def http_interactions
65
88
  @http_interactions ||= HTTPInteractionList.new \
66
89
  should_stub_requests? ? previously_recorded_interactions : [],
67
90
  match_requests_on,
68
91
  @allow_playback_repeats,
69
- @parent_list
92
+ @parent_list,
93
+ log_prefix
70
94
  end
71
95
 
96
+ # @private
72
97
  def record_http_interaction(interaction)
98
+ log "Recorded HTTP interaction #{request_summary(interaction.request)} => #{response_summary(interaction.response)}"
73
99
  new_recorded_interactions << interaction
74
100
  end
75
101
 
102
+ # @private
76
103
  def new_recorded_interactions
77
104
  @new_recorded_interactions ||= []
78
105
  end
79
106
 
107
+ # @return [String] The file for this cassette.
108
+ # @note VCR will take care of sanitizing the cassette name to make it a valid file name.
80
109
  def file
81
110
  return nil unless VCR.configuration.cassette_library_dir
82
111
  File.join(VCR.configuration.cassette_library_dir, "#{sanitized_name}.#{@serializer.file_extension}")
83
112
  end
84
113
 
85
- def update_content_length_header?
86
- @update_content_length_header
87
- end
88
-
114
+ # @return [Boolean] Whether or not the cassette is recording.
89
115
  def recording?
90
116
  case record_mode
91
117
  when :none; false
@@ -94,6 +120,7 @@ module VCR
94
120
  end
95
121
  end
96
122
 
123
+ # @return [Hash] The hash that will be serialized when the cassette is written to disk.
97
124
  def serializable_hash
98
125
  {
99
126
  "http_interactions" => interactions_to_record.map(&:to_hash),
@@ -103,6 +130,20 @@ module VCR
103
130
 
104
131
  private
105
132
 
133
+ def previously_recorded_interactions
134
+ @previously_recorded_interactions ||= if file && File.size?(file)
135
+ deserialized_hash['http_interactions'].map { |h| HTTPInteraction.from_hash(h) }.tap do |interactions|
136
+ invoke_hook(:before_playback, interactions)
137
+
138
+ interactions.reject! do |i|
139
+ i.request.uri.is_a?(String) && VCR.request_ignorer.ignore?(i.request)
140
+ end
141
+ end
142
+ else
143
+ []
144
+ end
145
+ end
146
+
106
147
  def sanitized_name
107
148
  name.to_s.gsub(/[^\w\-\/]+/, '_')
108
149
  end
@@ -170,8 +211,9 @@ module VCR
170
211
 
171
212
  def invoke_hook(type, interactions)
172
213
  interactions.delete_if do |i|
173
- VCR.configuration.invoke_tagged_hook(type, tag, i, self)
174
- i.ignored?
214
+ i.hook_aware.tap do |hw|
215
+ VCR.configuration.invoke_hook(type, hw, self)
216
+ end.ignored?
175
217
  end
176
218
  end
177
219
 
@@ -187,5 +229,13 @@ module VCR
187
229
  end
188
230
  end
189
231
  end
232
+
233
+ def log_prefix
234
+ @log_prefix ||= "[Cassette: '#{name}'] "
235
+ end
236
+
237
+ def request_summary(request)
238
+ super(request, match_requests_on)
239
+ end
190
240
  end
191
241
  end