vcr 4.0.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 806e6d86ccbe7132de58cc1313e0aef6ab0c2d8d
4
- data.tar.gz: e86dd3056697f943fe96721fd2e58163fa900c1e
2
+ SHA256:
3
+ metadata.gz: 902881c25780fa44253365c8f5fd0bb260510d7f754287d74a70275abc2dabce
4
+ data.tar.gz: fe52ddaa831a711b7415c350205a2662e833de2c4a6d58b8b6539170645bd218
5
5
  SHA512:
6
- metadata.gz: 7549fff407299870a1d989c3627a371b1a77d5054ba42b9a3ee52abd1e579acccbe282a1e7077f3f7bf14eceef2ed6a04d8a7a7578895061d6f858436c07be01
7
- data.tar.gz: 427619bde2eb934e06da74ba0888544efc574f437387cd6e00b7e8e3736f760bb5461626e697e4d9725c3d53103d22d9c589f1d809c3aa062550ab9c6fe3d746
6
+ metadata.gz: f79ffb02f15e691a1266c3421043a5ee95f3bd35715687137f139b291c490ee87a93351e0526faa0f29c73deba80ba41740f87d0fc9d50793a6b6d4101c7b116
7
+ data.tar.gz: 3e9276ffc9e2a2ae07f4a11054dcb484a324dc18e28a4badaa06df7454db36cfdbfbf38b95aec40c01a15a40cde56fb6cd9868780e50994fc2f554caf86e209a
@@ -32,7 +32,7 @@ module VCR
32
32
  end
33
33
 
34
34
  def erb_variables
35
- @erb if @erb.is_a?(Hash)
35
+ @erb if @erb.is_a?(Hash) && !@erb.empty?
36
36
  end
37
37
 
38
38
  def template
@@ -40,7 +40,9 @@ module VCR
40
40
  end
41
41
 
42
42
  @@struct_cache = Hash.new do |hash, attributes|
43
- hash[attributes] = Struct.new(*attributes)
43
+ attributes = attributes.map(&:to_sym)
44
+ hash[attributes] = Struct.new(*attributes) unless hash.key?(attributes)
45
+ hash[attributes]
44
46
  end
45
47
 
46
48
  def variables_object
@@ -22,21 +22,26 @@ module VCR
22
22
  @parent_list = parent_list
23
23
  @used_interactions = []
24
24
  @log_prefix = log_prefix
25
+ @mutex = Mutex.new
25
26
 
26
27
  interaction_summaries = interactions.map { |i| "#{request_summary(i.request)} => #{response_summary(i.response)}" }
27
28
  log "Initialized HTTPInteractionList with request matchers #{request_matchers.inspect} and #{interactions.size} interaction(s): { #{interaction_summaries.join(', ')} }", 1
28
29
  end
29
30
 
30
31
  def response_for(request)
31
- if index = matching_interaction_index_for(request)
32
- interaction = @interactions.delete_at(index)
33
- @used_interactions.unshift interaction
34
- log "Found matching interaction for #{request_summary(request)} at index #{index}: #{response_summary(interaction.response)}", 1
35
- interaction.response
36
- elsif interaction = matching_used_interaction_for(request)
37
- interaction.response
38
- else
39
- @parent_list.response_for(request)
32
+ # Without this mutex, under threaded access, the wrong response may be removed
33
+ # out of the (remaining) interactions list (and other problems).
34
+ @mutex.synchronize do
35
+ if index = matching_interaction_index_for(request)
36
+ interaction = @interactions.delete_at(index)
37
+ @used_interactions.unshift interaction
38
+ log "Found matching interaction for #{request_summary(request)} at index #{index}: #{response_summary(interaction.response)}", 1
39
+ interaction.response
40
+ elsif interaction = matching_used_interaction_for(request)
41
+ interaction.response
42
+ else
43
+ @parent_list.response_for(request)
44
+ end
40
45
  end
41
46
  end
42
47
 
@@ -45,17 +45,16 @@ module VCR
45
45
  "recorded_with" => "VCR #{VCR.version}"
46
46
  }
47
47
 
48
- def hash.each
49
- yield 'http_interactions', self['http_interactions']
50
- yield 'recorded_with', self['recorded_with']
51
- end
52
-
53
48
  File.open(cassette, 'w') { |f| f.write ::YAML.dump(hash) }
54
49
  @out.puts " - Migrated #{relative_casssette_name(cassette)}"
55
50
  end
56
51
 
57
52
  def load_yaml(cassette)
58
- ::YAML.load_file(cassette)
53
+ if ::YAML.respond_to?(:unsafe_load_file)
54
+ ::YAML.unsafe_load_file(cassette)
55
+ else
56
+ ::YAML.load_file(cassette)
57
+ end
59
58
  rescue *@yaml_load_errors
60
59
  return nil
61
60
  end
@@ -55,7 +55,15 @@ module VCR
55
55
  file_extension = '.' + parts.pop
56
56
  end
57
57
 
58
- parts.join('.').gsub(/[^[:word:]\-\/]+/, '_') + file_extension.to_s
58
+ file_name = parts.join('.').gsub(/[^[:word:]\-\/]+/, '_') + file_extension.to_s
59
+ file_name.downcase! if downcase_cassette_names?
60
+ file_name
61
+ end
62
+
63
+ def downcase_cassette_names?
64
+ !!VCR.configuration
65
+ .default_cassette_options
66
+ .dig(:persister_options, :downcase_cassette_names)
59
67
  end
60
68
  end
61
69
  end
@@ -17,9 +17,9 @@ module VCR
17
17
 
18
18
  # The file extension to use for this serializer.
19
19
  #
20
- # @return [String] "gz"
20
+ # @return [String] "zz"
21
21
  def file_extension
22
- 'gz'
22
+ 'zz'
23
23
  end
24
24
 
25
25
  # Serializes the given hash using YAML and Zlib.
@@ -1,9 +1,9 @@
1
- require 'multi_json'
1
+ require 'json'
2
2
 
3
3
  module VCR
4
4
  class Cassette
5
5
  class Serializers
6
- # The JSON serializer. Uses `MultiJson` under the covers.
6
+ # The JSON serializer.
7
7
  #
8
8
  # @see Psych
9
9
  # @see Syck
@@ -11,10 +11,14 @@ module VCR
11
11
  module JSON
12
12
  extend self
13
13
  extend EncodingErrorHandling
14
+ extend SyntaxErrorHandling
14
15
 
15
16
  # @private
16
- ENCODING_ERRORS = [MultiJson::DecodeError, ArgumentError]
17
- ENCODING_ERRORS << EncodingError if defined?(EncodingError)
17
+ ENCODING_ERRORS = [ArgumentError]
18
+ ENCODING_ERRORS << ::JSON::GeneratorError
19
+
20
+ # @private
21
+ SYNTAX_ERRORS = [::JSON::ParserError]
18
22
 
19
23
  # The file extension to use for this serializer.
20
24
  #
@@ -23,23 +27,25 @@ module VCR
23
27
  "json"
24
28
  end
25
29
 
26
- # Serializes the given hash using `MultiJson`.
30
+ # Serializes the given hash using `JSON`.
27
31
  #
28
32
  # @param [Hash] hash the object to serialize
29
33
  # @return [String] the JSON string
30
34
  def serialize(hash)
31
35
  handle_encoding_errors do
32
- MultiJson.encode(hash)
36
+ ::JSON.pretty_generate(hash)
33
37
  end
34
38
  end
35
39
 
36
- # Deserializes the given string using `MultiJson`.
40
+ # Deserializes the given string using `JSON`.
37
41
  #
38
42
  # @param [String] string the JSON string
39
43
  # @return [Hash] the deserialized object
40
44
  def deserialize(string)
41
45
  handle_encoding_errors do
42
- MultiJson.decode(string)
46
+ handle_syntax_errors do
47
+ ::JSON.parse(string)
48
+ end
43
49
  end
44
50
  end
45
51
  end
@@ -11,10 +11,14 @@ module VCR
11
11
  module Psych
12
12
  extend self
13
13
  extend EncodingErrorHandling
14
+ extend SyntaxErrorHandling
14
15
 
15
16
  # @private
16
17
  ENCODING_ERRORS = [ArgumentError]
17
18
 
19
+ # @private
20
+ SYNTAX_ERRORS = [::Psych::SyntaxError]
21
+
18
22
  # The file extension to use for this serializer.
19
23
  #
20
24
  # @return [String] "yml"
@@ -28,7 +32,9 @@ module VCR
28
32
  # @return [String] the YAML string
29
33
  def serialize(hash)
30
34
  handle_encoding_errors do
31
- ::Psych.dump(hash)
35
+ result = ::Psych.dump(hash)
36
+ result.gsub!(": \n", ": null\n") # set canonical null value in order to avoid trailing whitespaces
37
+ result
32
38
  end
33
39
  end
34
40
 
@@ -38,7 +44,9 @@ module VCR
38
44
  # @return [Hash] the deserialized object
39
45
  def deserialize(string)
40
46
  handle_encoding_errors do
41
- ::Psych.load(string)
47
+ handle_syntax_errors do
48
+ ::Psych.load(string)
49
+ end
42
50
  end
43
51
  end
44
52
  end
@@ -11,10 +11,14 @@ module VCR
11
11
  module Syck
12
12
  extend self
13
13
  extend EncodingErrorHandling
14
+ extend SyntaxErrorHandling
14
15
 
15
16
  # @private
16
17
  ENCODING_ERRORS = [ArgumentError]
17
18
 
19
+ # @private
20
+ SYNTAX_ERRORS = [::Psych::SyntaxError]
21
+
18
22
  # The file extension to use for this serializer.
19
23
  #
20
24
  # @return [String] "yml"
@@ -38,7 +42,9 @@ module VCR
38
42
  # @return [Hash] the deserialized object
39
43
  def deserialize(string)
40
44
  handle_encoding_errors do
41
- using_syck { ::YAML.load(string) }
45
+ handle_syntax_errors do
46
+ using_syck { ::YAML.load(string) }
47
+ end
42
48
  end
43
49
  end
44
50
 
@@ -13,10 +13,14 @@ module VCR
13
13
  module YAML
14
14
  extend self
15
15
  extend EncodingErrorHandling
16
+ extend SyntaxErrorHandling
16
17
 
17
18
  # @private
18
19
  ENCODING_ERRORS = [ArgumentError]
19
20
 
21
+ # @private
22
+ SYNTAX_ERRORS = [::Psych::SyntaxError]
23
+
20
24
  # The file extension to use for this serializer.
21
25
  #
22
26
  # @return [String] "yml"
@@ -30,7 +34,9 @@ module VCR
30
34
  # @return [String] the YAML string
31
35
  def serialize(hash)
32
36
  handle_encoding_errors do
33
- ::YAML.dump(hash)
37
+ result = ::YAML.dump(hash)
38
+ result.gsub!(": \n", ": null\n") # set canonical null value in order to avoid trailing whitespaces
39
+ result
34
40
  end
35
41
  end
36
42
 
@@ -40,7 +46,13 @@ module VCR
40
46
  # @return [Hash] the deserialized object
41
47
  def deserialize(string)
42
48
  handle_encoding_errors do
43
- ::YAML.load(string)
49
+ handle_syntax_errors do
50
+ if ::YAML.respond_to?(:unsafe_load)
51
+ ::YAML.unsafe_load(string)
52
+ else
53
+ ::YAML.load(string)
54
+ end
55
+ end
44
56
  end
45
57
  end
46
58
  end
@@ -54,6 +54,16 @@ module VCR
54
54
  raise
55
55
  end
56
56
  end
57
+
58
+ # @private
59
+ module SyntaxErrorHandling
60
+ def handle_syntax_errors
61
+ yield
62
+ rescue *self::SYNTAX_ERRORS => e
63
+ e.message << "\nNote: This is a VCR cassette. If it is using ERB, you may have forgotten to pass the `:erb` option to `use_cassette`."
64
+ raise
65
+ end
66
+ end
57
67
  end
58
68
  end
59
69
 
data/lib/vcr/cassette.rb CHANGED
@@ -24,6 +24,13 @@ module VCR
24
24
  # plays them back, or does both.
25
25
  attr_reader :record_mode
26
26
 
27
+ # @return [Boolean] The cassette's record_on_error mode. When the code that uses the cassette
28
+ # raises an error (for example a test failure) and record_on_error is set to false, no
29
+ # cassette will be recorded. This is useful when you are TDD'ing an API integration: when
30
+ # an error is raised that often means your request is invalid, so you don't want the cassette
31
+ # to be recorded.
32
+ attr_reader :record_on_error
33
+
27
34
  # @return [Array<Symbol, #call>] List of request matchers. Used to find a response from an
28
35
  # existing HTTP interaction to play back.
29
36
  attr_reader :match_requests_on
@@ -39,6 +46,9 @@ module VCR
39
46
  # @return [Boolean, nil] Should outdated interactions be recorded back to file
40
47
  attr_reader :clean_outdated_http_interactions
41
48
 
49
+ # @return [Boolean] Should unused requests be dropped from the cassette?
50
+ attr_reader :drop_unused_requests
51
+
42
52
  # @return [Array<Symbol>] If set, {VCR::Configuration#before_record} and
43
53
  # {VCR::Configuration#before_playback} hooks with a corresponding tag will apply.
44
54
  attr_reader :tags
@@ -48,6 +58,7 @@ module VCR
48
58
  def initialize(name, options = {})
49
59
  @name = name
50
60
  @options = VCR.configuration.default_cassette_options.merge(options)
61
+ @mutex = Mutex.new
51
62
 
52
63
  assert_valid_options!
53
64
  extract_options
@@ -65,21 +76,40 @@ module VCR
65
76
  # @param (see VCR#eject_casssette)
66
77
  # @see VCR#eject_cassette
67
78
  def eject(options = {})
68
- write_recorded_interactions_to_disk
79
+ write_recorded_interactions_to_disk if should_write_recorded_interactions_to_disk?
69
80
 
70
81
  if should_assert_no_unused_interactions? && !options[:skip_no_unused_interactions_assertion]
71
82
  http_interactions.assert_no_unused_interactions!
72
83
  end
73
84
  end
74
85
 
86
+ # @private
87
+ def run_failed!
88
+ @run_failed = true
89
+ end
90
+
91
+ # @private
92
+ def run_failed?
93
+ @run_failed = false unless defined?(@run_failed)
94
+ @run_failed
95
+ end
96
+
97
+ def should_write_recorded_interactions_to_disk?
98
+ !run_failed? || record_on_error
99
+ end
100
+
75
101
  # @private
76
102
  def http_interactions
77
- @http_interactions ||= HTTPInteractionList.new \
78
- should_stub_requests? ? previously_recorded_interactions : [],
79
- match_requests_on,
80
- @allow_playback_repeats,
81
- @parent_list,
82
- log_prefix
103
+ # Without this mutex, under threaded access, an HTTPInteractionList will overwrite
104
+ # the first.
105
+ @mutex.synchronize do
106
+ @http_interactions ||= HTTPInteractionList.new \
107
+ should_stub_requests? ? previously_recorded_interactions : [],
108
+ match_requests_on,
109
+ @allow_playback_repeats,
110
+ @parent_list,
111
+ log_prefix
112
+ end
83
113
  end
84
114
 
85
115
  # @private
@@ -146,10 +176,11 @@ module VCR
146
176
 
147
177
  def assert_valid_options!
148
178
  invalid_options = @options.keys - [
149
- :record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
179
+ :record, :record_on_error, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
150
180
  :update_content_length_header, :allow_playback_repeats, :allow_unused_http_interactions,
151
181
  :exclusive, :serialize_with, :preserve_exact_body_bytes, :decode_compressed_response,
152
- :persist_with, :clean_outdated_http_interactions
182
+ :recompress_response, :persist_with, :persister_options, :clean_outdated_http_interactions,
183
+ :drop_unused_requests
153
184
  ]
154
185
 
155
186
  if invalid_options.size > 0
@@ -158,24 +189,23 @@ module VCR
158
189
  end
159
190
 
160
191
  def extract_options
161
- [:erb, :match_requests_on, :re_record_interval, :clean_outdated_http_interactions,
162
- :allow_playback_repeats, :allow_unused_http_interactions, :exclusive].each do |name|
192
+ [:record_on_error, :erb, :match_requests_on, :re_record_interval, :clean_outdated_http_interactions,
193
+ :allow_playback_repeats, :allow_unused_http_interactions, :exclusive, :drop_unused_requests].each do |name|
163
194
  instance_variable_set("@#{name}", @options[name])
164
195
  end
165
196
 
166
197
  assign_tags
167
198
 
168
- @record_mode = @options[:record]
169
199
  @serializer = VCR.cassette_serializers[@options[:serialize_with]]
170
200
  @persister = VCR.cassette_persisters[@options[:persist_with]]
171
- @record_mode = :all if should_re_record?
201
+ @record_mode = should_re_record?(@options[:record]) ? :all : @options[:record]
172
202
  @parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions
173
203
  end
174
204
 
175
205
  def assign_tags
176
206
  @tags = Array(@options.fetch(:tags) { @options[:tag] })
177
207
 
178
- [:update_content_length_header, :preserve_exact_body_bytes, :decode_compressed_response].each do |tag|
208
+ [:update_content_length_header, :preserve_exact_body_bytes, :decode_compressed_response, :recompress_response].each do |tag|
179
209
  @tags << tag if @options[tag]
180
210
  end
181
211
  end
@@ -204,9 +234,10 @@ module VCR
204
234
  end
205
235
  end
206
236
 
207
- def should_re_record?
237
+ def should_re_record?(record_mode)
208
238
  return false unless @re_record_interval
209
239
  return false unless originally_recorded_at
240
+ return false if record_mode == :none
210
241
 
211
242
  now = Time.now
212
243
 
@@ -232,6 +263,10 @@ module VCR
232
263
  record_mode == :all
233
264
  end
234
265
 
266
+ def should_remove_unused_interactions?
267
+ @drop_unused_requests
268
+ end
269
+
235
270
  def should_assert_no_unused_interactions?
236
271
  !(@allow_unused_http_interactions || $!)
237
272
  end
@@ -250,7 +285,11 @@ module VCR
250
285
  end
251
286
  end
252
287
 
253
- up_to_date_interactions(old_interactions) + new_recorded_interactions
288
+ if should_remove_unused_interactions?
289
+ new_recorded_interactions
290
+ else
291
+ up_to_date_interactions(old_interactions) + new_recorded_interactions
292
+ end
254
293
  end
255
294
 
256
295
  def up_to_date_interactions(interactions)
@@ -77,6 +77,15 @@ module VCR
77
77
  end
78
78
  alias ignore_host ignore_hosts
79
79
 
80
+ # Specifies host(s) that VCR should stop ignoring.
81
+ #
82
+ # @param hosts [Array<String>] List of hosts to unignore
83
+ # @see #ignore_hosts
84
+ def unignore_hosts(*hosts)
85
+ VCR.request_ignorer.unignore_hosts(*hosts)
86
+ end
87
+ alias unignore_host unignore_hosts
88
+
80
89
  # Sets whether or not VCR should ignore localhost requests.
81
90
  #
82
91
  # @param value [Boolean] the value to set
@@ -222,7 +231,7 @@ module VCR
222
231
 
223
232
  before_playback(tag) do |interaction|
224
233
  orig_text = call_block(block, interaction)
225
- log "before_playback: replacing #{placeholder.inspect} with #{orig_text.inspect}"
234
+ log "before_playback: replacing #{orig_text.inspect} with #{placeholder.inspect}"
226
235
  interaction.filter!(placeholder, orig_text)
227
236
  end
228
237
  end
@@ -483,10 +492,13 @@ module VCR
483
492
  @rspec_metadata_configured = false
484
493
  @default_cassette_options = {
485
494
  :record => :once,
495
+ :record_on_error => true,
486
496
  :match_requests_on => RequestMatcherRegistry::DEFAULT_MATCHERS,
487
497
  :allow_unused_http_interactions => true,
498
+ :drop_unused_requests => false,
488
499
  :serialize_with => :yaml,
489
- :persist_with => :file_system
500
+ :persist_with => :file_system,
501
+ :persister_options => {}
490
502
  }
491
503
 
492
504
  self.uri_parser = URI
@@ -550,6 +562,10 @@ module VCR
550
562
  end
551
563
 
552
564
  def register_built_in_hooks
565
+ before_playback(:recompress_response) do |interaction|
566
+ interaction.response.recompress if interaction.response.vcr_decompressed?
567
+ end
568
+
553
569
  before_playback(:update_content_length_header) do |interaction|
554
570
  interaction.response.update_content_length_header
555
571
  end
@@ -571,4 +587,3 @@ module VCR
571
587
  define_hook :after_library_hooks_loaded
572
588
  end
573
589
  end
574
-
data/lib/vcr/errors.rb CHANGED
@@ -88,18 +88,7 @@ module VCR
88
88
  end
89
89
 
90
90
  def current_cassettes
91
- @cassettes ||= begin
92
- cassettes = VCR.cassettes.to_a.reverse
93
-
94
- begin
95
- loop do
96
- break unless VCR.eject_cassette
97
- end
98
- rescue EjectLinkedCassetteError
99
- end
100
-
101
- cassettes
102
- end
91
+ @cassettes ||= VCR.cassettes.to_a.reverse
103
92
  end
104
93
 
105
94
  def request_description
@@ -22,6 +22,14 @@ VCR.configuration.after_library_hooks_loaded do
22
22
  # (i.e. to double record requests or whatever).
23
23
  if defined?(WebMock::HttpLibAdapters::ExconAdapter)
24
24
  WebMock::HttpLibAdapters::ExconAdapter.disable!
25
+
26
+ if defined?(::RSpec)
27
+ ::RSpec.configure do |config|
28
+ config.before(:suite) do
29
+ WebMock::HttpLibAdapters::ExconAdapter.disable!
30
+ end
31
+ end
32
+ end
25
33
  end
26
34
  end
27
35