vcr 4.0.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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