vcr 4.0.0 → 6.1.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: 0230f475b5c666e17c97d0b81695860d18a54c40d07dccfb82ab63098fc60084
4
+ data.tar.gz: b79ad4ff6425ef66da8810b75f9cdc787d656c97cb5d60acf31c9ba09a4b6a5d
5
5
  SHA512:
6
- metadata.gz: 7549fff407299870a1d989c3627a371b1a77d5054ba42b9a3ee52abd1e579acccbe282a1e7077f3f7bf14eceef2ed6a04d8a7a7578895061d6f858436c07be01
7
- data.tar.gz: 427619bde2eb934e06da74ba0888544efc574f437387cd6e00b7e8e3736f760bb5461626e697e4d9725c3d53103d22d9c589f1d809c3aa062550ab9c6fe3d746
6
+ metadata.gz: 22d462bea8b4048575d82db89ecc36327be825267d76f69fa366ca98894b691210b6e859f4ab7d7acaf5fef537749b010ea166dcb09af642677bf402d6aaf855
7
+ data.tar.gz: 9f2a224345cabd78d03c0ac5f687b6261c7e1e34fe64c24dca7c4c46057de6122ec74d58c11ff3e3aefb8cc848efda899441c65c3d7dd0bdad86fbcccda9163d
@@ -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
@@ -48,6 +55,7 @@ module VCR
48
55
  def initialize(name, options = {})
49
56
  @name = name
50
57
  @options = VCR.configuration.default_cassette_options.merge(options)
58
+ @mutex = Mutex.new
51
59
 
52
60
  assert_valid_options!
53
61
  extract_options
@@ -65,21 +73,40 @@ module VCR
65
73
  # @param (see VCR#eject_casssette)
66
74
  # @see VCR#eject_cassette
67
75
  def eject(options = {})
68
- write_recorded_interactions_to_disk
76
+ write_recorded_interactions_to_disk if should_write_recorded_interactions_to_disk?
69
77
 
70
78
  if should_assert_no_unused_interactions? && !options[:skip_no_unused_interactions_assertion]
71
79
  http_interactions.assert_no_unused_interactions!
72
80
  end
73
81
  end
74
82
 
83
+ # @private
84
+ def run_failed!
85
+ @run_failed = true
86
+ end
87
+
88
+ # @private
89
+ def run_failed?
90
+ @run_failed = false unless defined?(@run_failed)
91
+ @run_failed
92
+ end
93
+
94
+ def should_write_recorded_interactions_to_disk?
95
+ !run_failed? || record_on_error
96
+ end
97
+
75
98
  # @private
76
99
  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
100
+ # Without this mutex, under threaded access, an HTTPInteractionList will overwrite
101
+ # the first.
102
+ @mutex.synchronize do
103
+ @http_interactions ||= HTTPInteractionList.new \
104
+ should_stub_requests? ? previously_recorded_interactions : [],
105
+ match_requests_on,
106
+ @allow_playback_repeats,
107
+ @parent_list,
108
+ log_prefix
109
+ end
83
110
  end
84
111
 
85
112
  # @private
@@ -146,10 +173,10 @@ module VCR
146
173
 
147
174
  def assert_valid_options!
148
175
  invalid_options = @options.keys - [
149
- :record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
176
+ :record, :record_on_error, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
150
177
  :update_content_length_header, :allow_playback_repeats, :allow_unused_http_interactions,
151
178
  :exclusive, :serialize_with, :preserve_exact_body_bytes, :decode_compressed_response,
152
- :persist_with, :clean_outdated_http_interactions
179
+ :recompress_response, :persist_with, :persister_options, :clean_outdated_http_interactions
153
180
  ]
154
181
 
155
182
  if invalid_options.size > 0
@@ -158,24 +185,23 @@ module VCR
158
185
  end
159
186
 
160
187
  def extract_options
161
- [:erb, :match_requests_on, :re_record_interval, :clean_outdated_http_interactions,
188
+ [:record_on_error, :erb, :match_requests_on, :re_record_interval, :clean_outdated_http_interactions,
162
189
  :allow_playback_repeats, :allow_unused_http_interactions, :exclusive].each do |name|
163
190
  instance_variable_set("@#{name}", @options[name])
164
191
  end
165
192
 
166
193
  assign_tags
167
194
 
168
- @record_mode = @options[:record]
169
195
  @serializer = VCR.cassette_serializers[@options[:serialize_with]]
170
196
  @persister = VCR.cassette_persisters[@options[:persist_with]]
171
- @record_mode = :all if should_re_record?
197
+ @record_mode = should_re_record?(@options[:record]) ? :all : @options[:record]
172
198
  @parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions
173
199
  end
174
200
 
175
201
  def assign_tags
176
202
  @tags = Array(@options.fetch(:tags) { @options[:tag] })
177
203
 
178
- [:update_content_length_header, :preserve_exact_body_bytes, :decode_compressed_response].each do |tag|
204
+ [:update_content_length_header, :preserve_exact_body_bytes, :decode_compressed_response, :recompress_response].each do |tag|
179
205
  @tags << tag if @options[tag]
180
206
  end
181
207
  end
@@ -204,9 +230,10 @@ module VCR
204
230
  end
205
231
  end
206
232
 
207
- def should_re_record?
233
+ def should_re_record?(record_mode)
208
234
  return false unless @re_record_interval
209
235
  return false unless originally_recorded_at
236
+ return false if record_mode == :none
210
237
 
211
238
  now = Time.now
212
239
 
@@ -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,12 @@ 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,
488
498
  :serialize_with => :yaml,
489
- :persist_with => :file_system
499
+ :persist_with => :file_system,
500
+ :persister_options => {}
490
501
  }
491
502
 
492
503
  self.uri_parser = URI
@@ -550,6 +561,10 @@ module VCR
550
561
  end
551
562
 
552
563
  def register_built_in_hooks
564
+ before_playback(:recompress_response) do |interaction|
565
+ interaction.response.recompress if interaction.response.vcr_decompressed?
566
+ end
567
+
553
568
  before_playback(:update_content_length_header) do |interaction|
554
569
  interaction.response.update_content_length_header
555
570
  end
@@ -571,4 +586,3 @@ module VCR
571
586
  define_hook :after_library_hooks_loaded
572
587
  end
573
588
  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