vcr 2.0.0.rc1 → 2.0.0.rc2

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 (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,27 +1,37 @@
1
1
  module VCR
2
2
  class Cassette
3
+ # @private
3
4
  class HTTPInteractionList
5
+ include Logger
6
+
7
+ # @private
4
8
  module NullList
5
9
  extend self
6
10
  def response_for(*a); nil; end
11
+ def has_interaction_matching?(*a); false; end
7
12
  def has_used_interaction_matching?(*a); false; end
8
13
  def remaining_unused_interaction_count(*a); 0; end
9
14
  end
10
15
 
11
16
  attr_reader :interactions, :request_matchers, :allow_playback_repeats, :parent_list
12
17
 
13
- def initialize(interactions, request_matchers, allow_playback_repeats = false, parent_list = NullList)
18
+ def initialize(interactions, request_matchers, allow_playback_repeats = false, parent_list = NullList, log_prefix = '')
14
19
  @interactions = interactions.dup
15
- @request_matchers = request_matchers.map { |m| VCR.request_matchers[m] }
20
+ @request_matchers = request_matchers
16
21
  @allow_playback_repeats = allow_playback_repeats
17
22
  @parent_list = parent_list
18
23
  @used_interactions = []
24
+ @log_prefix = log_prefix
25
+
26
+ interaction_summaries = interactions.map { |i| "#{request_summary(i.request)} => #{response_summary(i.response)}" }
27
+ log "Initialized HTTPInteractionList with request matchers #{request_matchers.inspect} and #{interactions.size} interaction(s): { #{interaction_summaries.join(', ')} }", 1
19
28
  end
20
29
 
21
30
  def response_for(request)
22
31
  if index = matching_interaction_index_for(request)
23
32
  interaction = @interactions.delete_at(index)
24
33
  @used_interactions.unshift interaction
34
+ log "Found matching interaction for #{request_summary(request)} at index #{index}: #{response_summary(interaction.response)}", 1
25
35
  interaction.response
26
36
  elsif interaction = matching_used_interaction_for(request)
27
37
  interaction.response
@@ -30,6 +40,12 @@ module VCR
30
40
  end
31
41
  end
32
42
 
43
+ def has_interaction_matching?(request)
44
+ !!matching_interaction_index_for(request) ||
45
+ !!matching_used_interaction_for(request) ||
46
+ @parent_list.has_interaction_matching?(request)
47
+ end
48
+
33
49
  def has_used_interaction_matching?(request)
34
50
  @used_interactions.any? { |i| interaction_matches_request?(request, i) }
35
51
  end
@@ -40,6 +56,10 @@ module VCR
40
56
 
41
57
  private
42
58
 
59
+ def request_summary(request)
60
+ super(request, @request_matchers)
61
+ end
62
+
43
63
  def matching_interaction_index_for(request)
44
64
  @interactions.index { |i| interaction_matches_request?(request, i) }
45
65
  end
@@ -50,10 +70,19 @@ module VCR
50
70
  end
51
71
 
52
72
  def interaction_matches_request?(request, interaction)
53
- @request_matchers.all? do |matcher|
54
- matcher.matches?(request, interaction.request)
73
+ log "Checking if #{request_summary(request)} matches #{request_summary(interaction.request)} using #{@request_matchers.inspect}", 1
74
+ @request_matchers.all? do |matcher_name|
75
+ matcher = VCR.request_matchers[matcher_name]
76
+ matcher.matches?(request, interaction.request).tap do |matched|
77
+ matched = matched ? 'matched' : 'did not match'
78
+ log "#{matcher_name} (#{matched}): current request #{request_summary(request)} vs #{request_summary(interaction.request)}", 2
79
+ end
55
80
  end
56
81
  end
82
+
83
+ def log_prefix
84
+ @log_prefix
85
+ end
57
86
  end
58
87
  end
59
88
  end
@@ -1,9 +1,8 @@
1
- require 'yaml'
2
- require 'vcr/structs'
3
- require 'uri'
1
+ require 'vcr'
4
2
 
5
3
  module VCR
6
4
  class Cassette
5
+ # @private
7
6
  class Migrator
8
7
  def initialize(dir, out = $stdout)
9
8
  @dir, @out = dir, out
@@ -1,5 +1,6 @@
1
1
  module VCR
2
2
  class Cassette
3
+ # @private
3
4
  class Reader
4
5
  def initialize(file_name, erb)
5
6
  @file_name, @erb = file_name, erb
@@ -1,15 +1,22 @@
1
1
  module VCR
2
2
  class Cassette
3
+ # Keeps track of the cassette serializers in a hash-like object.
3
4
  class Serializers
4
5
  autoload :YAML, 'vcr/cassette/serializers/yaml'
5
6
  autoload :Syck, 'vcr/cassette/serializers/syck'
6
7
  autoload :Psych, 'vcr/cassette/serializers/psych'
7
8
  autoload :JSON, 'vcr/cassette/serializers/json'
8
9
 
10
+ # @private
9
11
  def initialize
10
12
  @serializers = {}
11
13
  end
12
14
 
15
+ # Gets the named serializer.
16
+ #
17
+ # @param name [Symbol] the name of the serializer
18
+ # @return the named serializer
19
+ # @raise [ArgumentError] if there is not a serializer for the given name
13
20
  def [](name)
14
21
  @serializers.fetch(name) do |_|
15
22
  @serializers[name] = case name
@@ -22,6 +29,11 @@ module VCR
22
29
  end
23
30
  end
24
31
 
32
+ # Registers a serializer.
33
+ #
34
+ # @param name [Symbol] the name of the serializer
35
+ # @param value [#file_extension, #serialize, #deserialize] the serializer object. It must implement
36
+ # +file_extension()+, +serialize(Hash)+ and +deserialize(String)+.
25
37
  def []=(name, value)
26
38
  if @serializers.has_key?(name)
27
39
  warn "WARNING: There is already a VCR cassette serializer registered for #{name.inspect}. Overriding it."
@@ -30,6 +42,16 @@ module VCR
30
42
  @serializers[name] = value
31
43
  end
32
44
  end
45
+
46
+ # @private
47
+ module EncodingErrorHandling
48
+ def handle_encoding_errors
49
+ yield
50
+ rescue *self::ENCODING_ERRORS => e
51
+ e.message << "\nNote: Using VCR's `:preserve_exact_body_bytes` option may help prevent this error in the future."
52
+ raise
53
+ end
54
+ end
33
55
  end
34
56
  end
35
57
 
@@ -3,19 +3,44 @@ require 'multi_json'
3
3
  module VCR
4
4
  class Cassette
5
5
  class Serializers
6
+ # The JSON serializer. Uses `MultiJson` under the covers.
7
+ #
8
+ # @see Psych
9
+ # @see Syck
10
+ # @see YAML
6
11
  module JSON
7
12
  extend self
13
+ extend EncodingErrorHandling
8
14
 
15
+ # @private
16
+ ENCODING_ERRORS = [MultiJson::DecodeError]
17
+ ENCODING_ERRORS << EncodingError if defined?(EncodingError)
18
+
19
+ # The file extension to use for this serializer.
20
+ #
21
+ # @return [String] "json"
9
22
  def file_extension
10
23
  "json"
11
24
  end
12
25
 
26
+ # Serializes the given hash using `MultiJson`.
27
+ #
28
+ # @param [Hash] hash the object to serialize
29
+ # @return [String] the JSON string
13
30
  def serialize(hash)
14
- MultiJson.encode(hash)
31
+ handle_encoding_errors do
32
+ MultiJson.encode(hash)
33
+ end
15
34
  end
16
35
 
36
+ # Deserializes the given string using `MultiJson`.
37
+ #
38
+ # @param [String] string the JSON string
39
+ # @param [Hash] hash the deserialized object
17
40
  def deserialize(string)
18
- MultiJson.decode(string)
41
+ handle_encoding_errors do
42
+ MultiJson.decode(string)
43
+ end
19
44
  end
20
45
  end
21
46
  end
@@ -3,19 +3,43 @@ require 'psych'
3
3
  module VCR
4
4
  class Cassette
5
5
  class Serializers
6
+ # The Psych serializer. Psych is the new YAML engine in ruby 1.9.
7
+ #
8
+ # @see JSON
9
+ # @see Syck
10
+ # @see YAML
6
11
  module Psych
7
12
  extend self
13
+ extend EncodingErrorHandling
8
14
 
15
+ # @private
16
+ ENCODING_ERRORS = [ArgumentError]
17
+
18
+ # The file extension to use for this serializer.
19
+ #
20
+ # @return [String] "yml"
9
21
  def file_extension
10
22
  "yml"
11
23
  end
12
24
 
25
+ # Serializes the given hash using Psych.
26
+ #
27
+ # @param [Hash] hash the object to serialize
28
+ # @return [String] the YAML string
13
29
  def serialize(hash)
14
- ::Psych.dump(hash)
30
+ handle_encoding_errors do
31
+ ::Psych.dump(hash)
32
+ end
15
33
  end
16
34
 
35
+ # Deserializes the given string using Psych.
36
+ #
37
+ # @param [String] string the YAML string
38
+ # @param [Hash] hash the deserialized object
17
39
  def deserialize(string)
18
- ::Psych.load(string)
40
+ handle_encoding_errors do
41
+ ::Psych.load(string)
42
+ end
19
43
  end
20
44
  end
21
45
  end
@@ -3,21 +3,47 @@ require 'yaml'
3
3
  module VCR
4
4
  class Cassette
5
5
  class Serializers
6
+ # The Syck serializer. Syck is the legacy YAML engine in ruby 1.8 and 1.9.
7
+ #
8
+ # @see JSON
9
+ # @see Psych
10
+ # @see YAML
6
11
  module Syck
7
12
  extend self
13
+ extend EncodingErrorHandling
8
14
 
15
+ # @private
16
+ ENCODING_ERRORS = [ArgumentError]
17
+
18
+ # The file extension to use for this serializer.
19
+ #
20
+ # @return [String] "yml"
9
21
  def file_extension
10
22
  "yml"
11
23
  end
12
24
 
25
+ # Serializes the given hash using Syck.
26
+ #
27
+ # @param [Hash] hash the object to serialize
28
+ # @return [String] the YAML string
13
29
  def serialize(hash)
14
- using_syck { ::YAML.dump(hash) }
30
+ handle_encoding_errors do
31
+ using_syck { ::YAML.dump(hash) }
32
+ end
15
33
  end
16
34
 
35
+ # Deserializes the given string using Syck.
36
+ #
37
+ # @param [String] string the YAML string
38
+ # @param [Hash] hash the deserialized object
17
39
  def deserialize(string)
18
- using_syck { ::YAML.load(string) }
40
+ handle_encoding_errors do
41
+ using_syck { ::YAML.load(string) }
42
+ end
19
43
  end
20
44
 
45
+ private
46
+
21
47
  def using_syck
22
48
  return yield unless defined?(::YAML::ENGINE)
23
49
  original_engine = ::YAML::ENGINE.yamler
@@ -3,19 +3,45 @@ require 'yaml'
3
3
  module VCR
4
4
  class Cassette
5
5
  class Serializers
6
+ # The YAML serializer. This will use either Psych or Syck, which ever your
7
+ # ruby interpreter defaults to. You can also force VCR to use Psych or Syck by
8
+ # using one of those serializers.
9
+ #
10
+ # @see JSON
11
+ # @see Psych
12
+ # @see Syck
6
13
  module YAML
7
14
  extend self
15
+ extend EncodingErrorHandling
8
16
 
17
+ # @private
18
+ ENCODING_ERRORS = [ArgumentError]
19
+
20
+ # The file extension to use for this serializer.
21
+ #
22
+ # @return [String] "yml"
9
23
  def file_extension
10
24
  "yml"
11
25
  end
12
26
 
27
+ # Serializes the given hash using YAML.
28
+ #
29
+ # @param [Hash] hash the object to serialize
30
+ # @return [String] the YAML string
13
31
  def serialize(hash)
14
- ::YAML.dump(hash)
32
+ handle_encoding_errors do
33
+ ::YAML.dump(hash)
34
+ end
15
35
  end
16
36
 
37
+ # Deserializes the given string using YAML.
38
+ #
39
+ # @param [String] string the YAML string
40
+ # @param [Hash] hash the deserialized object
17
41
  def deserialize(string)
18
- ::YAML.load(string)
42
+ handle_encoding_errors do
43
+ ::YAML.load(string)
44
+ end
19
45
  end
20
46
  end
21
47
  end
@@ -2,16 +2,109 @@ require 'fileutils'
2
2
  require 'vcr/util/hooks'
3
3
 
4
4
  module VCR
5
+ # Stores the VCR configuration.
5
6
  class Configuration
6
7
  include VCR::Hooks
7
8
  include VCR::VariableArgsBlockCaller
8
9
 
10
+ # Adds a callback that will be called before the recorded HTTP interactions
11
+ # are serialized and written to disk.
12
+ #
13
+ # @example
14
+ # VCR.configure do |c|
15
+ # # Don't record transient 5xx errors
16
+ # c.before_record do |interaction|
17
+ # interaction.ignore! if interaction.response.status.code >= 500
18
+ # end
19
+ #
20
+ # # Modify the response body for cassettes tagged with :twilio
21
+ # c.before_record(:twilio) do |interaction|
22
+ # interaction.response.body.downcase!
23
+ # end
24
+ # end
25
+ #
26
+ # @param tag [(optional) Symbol] Used to apply this hook to only cassettes that match
27
+ # the given tag.
28
+ # @yield the callback
29
+ # @yieldparam interaction [VCR::HTTPInteraction::HookAware] The interaction that will be
30
+ # serialized and written to disk.
31
+ # @yieldparam cassette [(optional) VCR::Cassette] The current cassette.
32
+ # @see #before_playback
9
33
  define_hook :before_record
34
+ def before_record(tag = nil, &block)
35
+ super(filter_from(tag), &block)
36
+ end
37
+
38
+ # Adds a callback that will be called before a previously recorded
39
+ # HTTP interaction is loaded for playback.
40
+ #
41
+ # @example
42
+ # VCR.configure do |c|
43
+ # # Don't playback transient 5xx errors
44
+ # c.before_playback do |interaction|
45
+ # interaction.ignore! if interaction.response.status.code >= 500
46
+ # end
47
+ #
48
+ # # Change a response header for playback
49
+ # c.before_playback(:twilio) do |interaction|
50
+ # interaction.response.headers['X-Foo-Bar'] = 'Bazz'
51
+ # end
52
+ # end
53
+ #
54
+ # @param tag [(optional) Symbol] Used to apply this hook to only cassettes that match
55
+ # the given tag.
56
+ # @yield the callback
57
+ # @yieldparam interaction [VCR::HTTPInteraction::HookAware] The interaction that is being
58
+ # loaded.
59
+ # @yieldparam cassette [(optional) VCR::Cassette] The current cassette.
60
+ # @see #before_record
10
61
  define_hook :before_playback
62
+ def before_playback(tag = nil, &block)
63
+ super(filter_from(tag), &block)
64
+ end
65
+
66
+ # @private
11
67
  define_hook :after_library_hooks_loaded
68
+
69
+ # Adds a callback that will be called with each HTTP request before it is made.
70
+ #
71
+ # @example
72
+ # VCR.configure do |c|
73
+ # c.before_http_request(:real?) do |request|
74
+ # puts "Request: #{request.method} #{request.uri}"
75
+ # end
76
+ # end
77
+ #
78
+ # @param filters [optional splat of #to_proc] one or more filters to apply.
79
+ # The objects provided will be converted to procs using `#to_proc`. If provided,
80
+ # the callback will only be invoked if these procs all return `true`.
81
+ # @yield the callback
82
+ # @yieldparam request [VCR::Request::Typed] the request that is being made
83
+ # @see #after_http_request
84
+ # @see #around_http_request
12
85
  define_hook :before_http_request
86
+
87
+ # Adds a callback that will be called with each HTTP request after it is complete.
88
+ #
89
+ # @example
90
+ # VCR.configure do |c|
91
+ # c.after_http_request(:ignored?) do |request, response|
92
+ # puts "Request: #{request.method} #{request.uri}"
93
+ # puts "Response: #{response.status.code}"
94
+ # end
95
+ # end
96
+ #
97
+ # @param filters [optional splat of #to_proc] one or more filters to apply.
98
+ # The objects provided will be converted to procs using `#to_proc`. If provided,
99
+ # the callback will only be invoked if these procs all return `true`.
100
+ # @yield the callback
101
+ # @yieldparam request [VCR::Request::Typed] the request that is being made
102
+ # @yieldparam response [VCR::Response] the response from the request
103
+ # @see #before_http_request
104
+ # @see #around_http_request
13
105
  define_hook :after_http_request, :prepend
14
106
 
107
+ # @private
15
108
  def initialize
16
109
  @allow_http_connections_when_no_cassette = nil
17
110
  @default_cassette_options = {
@@ -19,47 +112,172 @@ module VCR
19
112
  :match_requests_on => RequestMatcherRegistry::DEFAULT_MATCHERS,
20
113
  :serialize_with => :yaml
21
114
  }
115
+
116
+ self.debug_logger = NullDebugLogger
117
+
118
+ register_built_in_hooks
22
119
  end
23
120
 
121
+ # The directory to read cassettes from and write cassettes to.
122
+ #
123
+ # @overload cassette_library_dir
124
+ # @return [String] the directory to read cassettes from and write cassettes to
125
+ # @overload cassette_library_dir=(dir)
126
+ # @param dir [String] the directory to read cassettes from and write cassettes to
127
+ # @return [void]
128
+ # @example
129
+ # VCR.configure do |c|
130
+ # c.cassette_library_dir = 'spec/cassettes'
131
+ # end
24
132
  attr_reader :cassette_library_dir
133
+
134
+ # Sets the directory to read cassettes from and write cassettes to.
25
135
  def cassette_library_dir=(cassette_library_dir)
26
- @cassette_library_dir = cassette_library_dir
27
136
  FileUtils.mkdir_p(cassette_library_dir) if cassette_library_dir
137
+ @cassette_library_dir = cassette_library_dir ? absolute_path_for(cassette_library_dir) : nil
28
138
  end
29
139
 
140
+ # Default options to apply to every cassette.
141
+ #
142
+ # @overload default_cassette_options
143
+ # @return [Hash] default options to apply to every cassette
144
+ # @overload default_cassette_options=(options)
145
+ # @param options [Hash] default options to apply to every cassette
146
+ # @return [void]
147
+ # @example
148
+ # VCR.configure do |c|
149
+ # c.default_cassette_options = { :record => :new_episodes }
150
+ # end
151
+ # @note {VCR#insert_cassette} for the list of valid options.
30
152
  attr_reader :default_cassette_options
153
+
154
+ # Sets the default options that apply to every cassette.
31
155
  def default_cassette_options=(overrides)
32
156
  @default_cassette_options.merge!(overrides)
33
157
  end
34
158
 
159
+ # Configures which libraries VCR will hook into to intercept HTTP requests.
160
+ #
161
+ # @example
162
+ # VCR.configure do |c|
163
+ # c.hook_into :fakeweb, :typhoeus
164
+ # end
165
+ #
166
+ # @param hooks [Array<Symbol>] List of libraries. Valid values are
167
+ # `:fakeweb`, `:webmock`, `:typhoeus`, `:excon` and `:faraday`.
168
+ # @raise [ArgumentError] when given an unsupported library name.
169
+ # @raise [VCR::Errors::LibraryVersionTooLowError] when the version
170
+ # of a library you are using is too low for VCR to support.
171
+ # @note `:fakeweb` and `:webmock` cannot both be used since they both monkey patch
172
+ # `Net::HTTP`. Otherwise, you can use any combination of these.
35
173
  def hook_into(*hooks)
36
174
  hooks.each { |a| load_library_hook(a) }
37
175
  invoke_hook(:after_library_hooks_loaded)
38
176
  end
39
177
 
178
+ # Registers a request matcher for later use.
179
+ #
180
+ # @example
181
+ # VCR.configure do |c|
182
+ # c.register_request_matcher :port do |request_1, request_2|
183
+ # URI(request_1.uri).port == URI(request_2.uri).port
184
+ # end
185
+ # end
186
+ #
187
+ # VCR.use_cassette("my_cassette", :match_requests_on => [:method, :host, :port]) do
188
+ # # ...
189
+ # end
190
+ #
191
+ # @param name [Symbol] the name of the request matcher
192
+ # @yield the request matcher
193
+ # @yieldparam request_1 [VCR::Request] One request
194
+ # @yieldparam request_2 [VCR::Request] The other request
195
+ # @yieldreturn [Boolean] whether or not these two requests should be considered
196
+ # equivalent
40
197
  def register_request_matcher(name, &block)
41
198
  VCR.request_matchers.register(name, &block)
42
199
  end
43
200
 
201
+ # Specifies host(s) that VCR should ignore.
202
+ #
203
+ # @param hosts [Array<String>] List of hosts to ignore
204
+ # @see #ignore_localhost=
205
+ # @see #ignore_request
44
206
  def ignore_hosts(*hosts)
45
207
  VCR.request_ignorer.ignore_hosts(*hosts)
46
208
  end
47
209
  alias ignore_host ignore_hosts
48
210
 
211
+ # Sets whether or not VCR should ignore localhost requests.
212
+ #
213
+ # @param value [Boolean] the value to set
214
+ # @see #ignore_hosts
215
+ # @see #ignore_request
49
216
  def ignore_localhost=(value)
50
217
  VCR.request_ignorer.ignore_localhost = value
51
218
  end
52
219
 
220
+ # Defines what requests to ignore using a block.
221
+ #
222
+ # @example
223
+ # VCR.configure do |c|
224
+ # c.ignore_request do |request|
225
+ # uri = URI(request.uri)
226
+ # # ignore only localhost requests to port 7500
227
+ # uri.host == 'localhost' && uri.port == 7500
228
+ # end
229
+ # end
230
+ #
231
+ # @yield the callback
232
+ # @yieldparam request [VCR::Request] the HTTP request
233
+ # @yieldreturn [Boolean] whether or not to ignore the request
53
234
  def ignore_request(&block)
54
235
  VCR.request_ignorer.ignore_request(&block)
55
236
  end
56
237
 
238
+ # Determines how VCR treats HTTP requests that are made when
239
+ # no VCR cassette is in use. When set to `true`, requests made
240
+ # when there is no VCR cassette in use will be allowed. When set
241
+ # to `false` (the default), an {VCR::Errors::UnhandledHTTPRequestError}
242
+ # will be raised for any HTTP request made when there is no
243
+ # cassette in use.
244
+ #
245
+ # @overload allow_http_connections_when_no_cassette?
246
+ # @return [Boolean] whether or not HTTP connections are allowed
247
+ # when there is no cassette.
248
+ # @overload allow_http_connections_when_no_cassette=
249
+ # @param value [Boolean] sets whether or not to allow HTTP
250
+ # connections when there is no cassette.
57
251
  attr_writer :allow_http_connections_when_no_cassette
252
+ # @private (documented above)
58
253
  def allow_http_connections_when_no_cassette?
59
254
  !!@allow_http_connections_when_no_cassette
60
255
  end
61
256
 
62
- def filter_sensitive_data(placeholder, tag = nil, &block)
257
+ # Sets up a {#before_record} and a {#before_playback} hook that will
258
+ # insert a placeholder string in the cassette in place of another string.
259
+ # You can use this as a generic way to interpolate a variable into the
260
+ # cassette for a unique string. It's particularly useful for unique
261
+ # sensitive strings like API keys and passwords.
262
+ #
263
+ # @example
264
+ # VCR.configure do |c|
265
+ # # Put "<GITHUB_API_KEY>" in place of the actual API key in
266
+ # # our cassettes so we don't have to commit to source control.
267
+ # c.filter_sensitive_data('<GITHUB_API_KEY>') { GithubClient.api_key }
268
+ #
269
+ # # Put a "<USER_ID>" placeholder variable in our cassettes tagged with
270
+ # # :user_cassette since it can be different for different test runs.
271
+ # c.define_cassette_placeholder('<USER_ID>', :user_cassette) { User.last.id }
272
+ # end
273
+ #
274
+ # @param placeholder [String] The placeholder string.
275
+ # @param tag [Symbol] Set this apply this to only to cassettes
276
+ # with a matching tag; otherwise it will apply to every cassette.
277
+ # @yield block that determines what string to replace
278
+ # @yieldparam interaction [(optional) VCR::HTTPInteraction::HookAware] the HTTP interaction
279
+ # @yieldreturn the string to replace
280
+ def define_cassette_placeholder(placeholder, tag = nil, &block)
63
281
  before_record(tag) do |interaction|
64
282
  interaction.filter!(call_block(block, interaction), placeholder)
65
283
  end
@@ -68,28 +286,123 @@ module VCR
68
286
  interaction.filter!(placeholder, call_block(block, interaction))
69
287
  end
70
288
  end
71
- alias define_cassette_placeholder filter_sensitive_data
289
+ alias filter_sensitive_data define_cassette_placeholder
72
290
 
291
+ # Gets the registry of cassette serializers. Use it to register a custom serializer.
292
+ #
293
+ # @example
294
+ # VCR.configure do |c|
295
+ # c.cassette_serializers[:my_custom_serializer] = my_custom_serializer
296
+ # end
297
+ #
298
+ # @return [VCR::Cassette::Serializers] the cassette serializer registry object.
299
+ # @note Custom serializers must implement the following interface:
300
+ #
301
+ # * `file_extension # => String`
302
+ # * `serialize(Hash) # => String`
303
+ # * `deserialize(String) # => Hash`
73
304
  def cassette_serializers
74
305
  VCR.cassette_serializers
75
306
  end
76
307
 
77
- def around_http_request(&block)
308
+ # Adds a callback that will be executed around each HTTP request.
309
+ #
310
+ # @example
311
+ # VCR.configure do |c|
312
+ # c.around_http_request(lambda {|r| r.uri =~ /api.geocoder.com/}) do |request|
313
+ # # extract an address like "1700 E Pine St, Seattle, WA"
314
+ # # from a query like "address=1700+E+Pine+St%2C+Seattle%2C+WA"
315
+ # address = CGI.unescape(URI(request.uri).query.split('=').last)
316
+ # VCR.use_cassette("geocoding/#{address}", &request)
317
+ # end
318
+ # end
319
+ #
320
+ # @yield the callback
321
+ # @yieldparam request [VCR::Request::FiberAware] the request that is being made
322
+ # @raise [VCR::Errors::NotSupportedError] if the fiber library cannot be loaded.
323
+ # @param filters [optional splat of #to_proc] one or more filters to apply.
324
+ # The objects provided will be converted to procs using `#to_proc`. If provided,
325
+ # the callback will only be invoked if these procs all return `true`.
326
+ # @note This method can only be used on ruby interpreters that support
327
+ # fibers (i.e. 1.9+). On 1.8 you can use separate `before_http_request` and
328
+ # `after_http_request` hooks.
329
+ # @note You _must_ call `request.proceed` or pass the request as a proc on to a
330
+ # method that yields to a block (i.e. `some_method(&request)`).
331
+ # @see #before_http_request
332
+ # @see #after_http_request
333
+ def around_http_request(*filters, &block)
78
334
  require 'fiber'
79
335
  rescue LoadError
80
336
  raise Errors::NotSupportedError.new \
81
337
  "VCR::Configuration#around_http_request requires fibers, " +
82
338
  "which are not available on your ruby intepreter."
83
339
  else
84
- fiber, hook_decaration = nil, caller.first
85
- before_http_request { |request| fiber = start_new_fiber_for(request, block) }
86
- after_http_request { |request| resume_fiber(fiber, hook_decaration) }
340
+ fiber, hook_allowed, hook_decaration = nil, false, caller.first
341
+ before_http_request(*filters) do |request|
342
+ hook_allowed = true
343
+ fiber = start_new_fiber_for(request, block)
344
+ end
345
+
346
+ after_http_request(lambda { hook_allowed }) do |request, response|
347
+ resume_fiber(fiber, response, hook_decaration)
348
+ end
87
349
  end
88
350
 
351
+ # Configures RSpec to use a VCR cassette for any example
352
+ # tagged with `:vcr`.
89
353
  def configure_rspec_metadata!
90
354
  VCR::RSpec::Metadata.configure!
91
355
  end
92
356
 
357
+ # An object to log debug output to.
358
+ #
359
+ # @overload debug_logger
360
+ # @return [#puts] the logger
361
+ # @overload debug_logger=(logger)
362
+ # @param logger [#puts] the logger
363
+ # @return [void]
364
+ # @example
365
+ # VCR.configure do |c|
366
+ # c.debug_logger = $stderr
367
+ # end
368
+ # @example
369
+ # VCR.configure do |c|
370
+ # c.debug_logger = File.open('vcr.log', 'w')
371
+ # end
372
+ attr_accessor :debug_logger
373
+
374
+ # Sets a callback that determines whether or not to base64 encode
375
+ # the bytes of a request or response body during serialization in
376
+ # order to preserve them exactly.
377
+ #
378
+ # @example
379
+ # VCR.configure do |c|
380
+ # c.preserve_exact_body_bytes do |http_message|
381
+ # http_message.body.encoding.name == 'ASCII-8BIT' ||
382
+ # !http_message.body.valid_encoding?
383
+ # end
384
+ # end
385
+ #
386
+ # @yield the callback
387
+ # @yieldparam http_message [#body, #headers] the `VCR::Request` or `VCR::Response` object being serialized
388
+ # @yieldparam cassette [VCR::Cassette] the cassette the http message belongs to
389
+ # @yieldreturn [Boolean] whether or not to preserve the exact bytes for the body of the given HTTP message
390
+ # @return [void]
391
+ # @see #preserve_exact_body_bytes_for?
392
+ # @note This is usually only necessary when the HTTP server returns a response
393
+ # with a non-standard encoding or with a body containing invalid bytes for the given
394
+ # encoding. Note that when you set this, and the block returns true, you sacrifice
395
+ # the human readability of the data in the cassette.
396
+ define_hook :preserve_exact_body_bytes
397
+
398
+ # @return [Boolean] whether or not the body of the given HTTP message should
399
+ # be base64 encoded during serialization in order to preserve the bytes exactly.
400
+ # @param http_message [#body, #headers] the `VCR::Request` or `VCR::Response` object being serialized
401
+ # @see #preserve_exact_body_bytes
402
+ def preserve_exact_body_bytes_for?(http_message)
403
+ invoke_hook(:preserve_exact_body_bytes, http_message, VCR.current_cassette).any?
404
+ end
405
+
93
406
  private
94
407
 
95
408
  def load_library_hook(hook)
@@ -100,8 +413,8 @@ module VCR
100
413
  raise ArgumentError.new("#{hook.inspect} is not a supported VCR HTTP library hook.")
101
414
  end
102
415
 
103
- def resume_fiber(fiber, hook_declaration)
104
- fiber.resume
416
+ def resume_fiber(fiber, response, hook_declaration)
417
+ fiber.resume(response)
105
418
  rescue FiberError
106
419
  raise Errors::AroundHTTPRequestHookError.new \
107
420
  "Your around_http_request hook declared at #{hook_declaration}" +
@@ -110,9 +423,34 @@ module VCR
110
423
 
111
424
  def start_new_fiber_for(request, block)
112
425
  Fiber.new(&block).tap do |fiber|
113
- fiber.resume(request.fiber_aware)
426
+ fiber.resume(Request::FiberAware.new(request))
427
+ end
428
+ end
429
+
430
+ def absolute_path_for(path)
431
+ Dir.chdir(path) { Dir.pwd }
432
+ end
433
+
434
+ def filter_from(tag)
435
+ return lambda { true } unless tag
436
+ lambda { |_, cassette| cassette.tags.include?(tag) }
437
+ end
438
+
439
+ def register_built_in_hooks
440
+ before_playback(:update_content_length_header) do |interaction|
441
+ interaction.response.update_content_length_header
442
+ end
443
+
444
+ preserve_exact_body_bytes do |http_message, cassette|
445
+ cassette && cassette.tags.include?(:preserve_exact_body_bytes)
114
446
  end
115
447
  end
116
448
  end
449
+
450
+ # @private
451
+ module NullDebugLogger
452
+ extend self
453
+ def puts(*); end
454
+ end
117
455
  end
118
456