vcr 5.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
2
  SHA256:
3
- metadata.gz: fb0121a881f4d4b1ac0f68b5078f5674f137e07a4a0580b70a438f920c5953db
4
- data.tar.gz: 551623fd6fd0317c2510e1a02c36f6a8c3cac4bba72752ffd31907de0e1bfef9
3
+ metadata.gz: 0230f475b5c666e17c97d0b81695860d18a54c40d07dccfb82ab63098fc60084
4
+ data.tar.gz: b79ad4ff6425ef66da8810b75f9cdc787d656c97cb5d60acf31c9ba09a4b6a5d
5
5
  SHA512:
6
- metadata.gz: d6ad287e1f993836c3adac326228312596935313d65313fb60b07b16a962b0b5b9cff3f74db5200ce21e2c5ba5b6e32da0fd445d73fe7024c35308fecfcdc688
7
- data.tar.gz: bcf77106091efc49ae8ee789f076b162eb28491349a2e04ef0a1220ae5b1cfb3501de2e3106d8aa14459c7577207775650232ebf11235fe110c28dfa16614149
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
@@ -50,7 +50,11 @@ module VCR
50
50
  end
51
51
 
52
52
  def load_yaml(cassette)
53
- ::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
54
58
  rescue *@yaml_load_errors
55
59
  return nil
56
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
@@ -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
@@ -66,13 +73,28 @@ module VCR
66
73
  # @param (see VCR#eject_casssette)
67
74
  # @see VCR#eject_cassette
68
75
  def eject(options = {})
69
- write_recorded_interactions_to_disk
76
+ write_recorded_interactions_to_disk if should_write_recorded_interactions_to_disk?
70
77
 
71
78
  if should_assert_no_unused_interactions? && !options[:skip_no_unused_interactions_assertion]
72
79
  http_interactions.assert_no_unused_interactions!
73
80
  end
74
81
  end
75
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
+
76
98
  # @private
77
99
  def http_interactions
78
100
  # Without this mutex, under threaded access, an HTTPInteractionList will overwrite
@@ -151,10 +173,10 @@ module VCR
151
173
 
152
174
  def assert_valid_options!
153
175
  invalid_options = @options.keys - [
154
- :record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
176
+ :record, :record_on_error, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
155
177
  :update_content_length_header, :allow_playback_repeats, :allow_unused_http_interactions,
156
178
  :exclusive, :serialize_with, :preserve_exact_body_bytes, :decode_compressed_response,
157
- :recompress_response, :persist_with, :clean_outdated_http_interactions
179
+ :recompress_response, :persist_with, :persister_options, :clean_outdated_http_interactions
158
180
  ]
159
181
 
160
182
  if invalid_options.size > 0
@@ -163,17 +185,16 @@ module VCR
163
185
  end
164
186
 
165
187
  def extract_options
166
- [: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,
167
189
  :allow_playback_repeats, :allow_unused_http_interactions, :exclusive].each do |name|
168
190
  instance_variable_set("@#{name}", @options[name])
169
191
  end
170
192
 
171
193
  assign_tags
172
194
 
173
- @record_mode = @options[:record]
174
195
  @serializer = VCR.cassette_serializers[@options[:serialize_with]]
175
196
  @persister = VCR.cassette_persisters[@options[:persist_with]]
176
- @record_mode = :all if should_re_record?
197
+ @record_mode = should_re_record?(@options[:record]) ? :all : @options[:record]
177
198
  @parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions
178
199
  end
179
200
 
@@ -209,9 +230,10 @@ module VCR
209
230
  end
210
231
  end
211
232
 
212
- def should_re_record?
233
+ def should_re_record?(record_mode)
213
234
  return false unless @re_record_interval
214
235
  return false unless originally_recorded_at
236
+ return false if record_mode == :none
215
237
 
216
238
  now = Time.now
217
239
 
@@ -492,10 +492,12 @@ module VCR
492
492
  @rspec_metadata_configured = false
493
493
  @default_cassette_options = {
494
494
  :record => :once,
495
+ :record_on_error => true,
495
496
  :match_requests_on => RequestMatcherRegistry::DEFAULT_MATCHERS,
496
497
  :allow_unused_http_interactions => true,
497
498
  :serialize_with => :yaml,
498
- :persist_with => :file_system
499
+ :persist_with => :file_system,
500
+ :persister_options => {}
499
501
  }
500
502
 
501
503
  self.uri_parser = URI
@@ -2,137 +2,121 @@ require 'vcr/util/version_checker'
2
2
  require 'vcr/request_handler'
3
3
  require 'typhoeus'
4
4
 
5
- if Float(Typhoeus::VERSION[/^\d+\.\d+/]) < 0.5
6
- require 'vcr/library_hooks/typhoeus_0.4'
7
- else
8
- VCR::VersionChecker.new('Typhoeus', Typhoeus::VERSION, '0.5.0').check_version!
9
-
10
- module VCR
11
- class LibraryHooks
5
+ module VCR
6
+ class LibraryHooks
7
+ # @private
8
+ module Typhoeus
12
9
  # @private
13
- module Typhoeus
14
- # @private
15
- class RequestHandler < ::VCR::RequestHandler
16
- attr_reader :request
17
- def initialize(request)
18
- @request = request
19
- request.block_connection = false if VCR.turned_on?
20
- end
21
-
22
- def vcr_request
23
- @vcr_request ||= VCR::Request.new \
24
- request.options.fetch(:method, :get),
25
- request.url,
26
- request_body,
27
- request.options.fetch(:headers, {})
28
- end
29
-
30
- private
10
+ class RequestHandler < ::VCR::RequestHandler
11
+ attr_reader :request
12
+ def initialize(request)
13
+ @request = request
14
+ request.block_connection = false if VCR.turned_on?
15
+ end
31
16
 
32
- def externally_stubbed?
33
- ::Typhoeus::Expectation.find_by(request)
34
- end
17
+ def vcr_request
18
+ @vcr_request ||= VCR::Request.new \
19
+ request.options.fetch(:method, :get),
20
+ request.url,
21
+ request.encoded_body,
22
+ request.options.fetch(:headers, {})
23
+ end
35
24
 
36
- def set_typed_request_for_after_hook(*args)
37
- super
38
- request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
39
- end
25
+ private
40
26
 
41
- def on_unhandled_request
42
- invoke_after_request_hook(nil)
43
- super
44
- end
27
+ def externally_stubbed?
28
+ ::Typhoeus::Expectation.find_by(request)
29
+ end
45
30
 
46
- def on_stubbed_by_vcr_request
47
- response = ::Typhoeus::Response.new \
48
- :http_version => stubbed_response.http_version,
49
- :code => stubbed_response.status.code,
50
- :status_message => stubbed_response.status.message,
51
- :headers => stubbed_response_headers,
52
- :body => stubbed_response.body,
53
- :effective_url => stubbed_response.adapter_metadata.fetch('effective_url', request.url),
54
- :mock => true
55
-
56
- first_header_line = "HTTP/#{stubbed_response.http_version} #{response.code} #{response.status_message}\r\n"
57
- response.instance_variable_set(:@first_header_line, first_header_line)
58
- response.instance_variable_get(:@options)[:response_headers] =
59
- first_header_line + response.headers.map { |k,v| "#{k}: #{v}"}.join("\r\n")
60
-
61
- response
62
- end
31
+ def set_typed_request_for_after_hook(*args)
32
+ super
33
+ request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
34
+ end
63
35
 
64
- def stubbed_response_headers
65
- @stubbed_response_headers ||= {}.tap do |hash|
66
- stubbed_response.headers.each do |key, values|
67
- hash[key] = values.size == 1 ? values.first : values
68
- end if stubbed_response.headers
69
- end
70
- end
36
+ def on_unhandled_request
37
+ invoke_after_request_hook(nil)
38
+ super
39
+ end
71
40
 
72
- if ::Typhoeus::Request.method_defined?(:encoded_body)
73
- def request_body
74
- request.encoded_body
75
- end
76
- else
77
- def request_body
78
- request.options.fetch(:body, "")
79
- end
80
- end
41
+ def on_stubbed_by_vcr_request
42
+ response = ::Typhoeus::Response.new \
43
+ :http_version => stubbed_response.http_version,
44
+ :code => stubbed_response.status.code,
45
+ :status_message => stubbed_response.status.message,
46
+ :headers => stubbed_response_headers,
47
+ :body => stubbed_response.body,
48
+ :effective_url => stubbed_response.adapter_metadata.fetch('effective_url', request.url),
49
+ :mock => true
50
+
51
+ first_header_line = "HTTP/#{stubbed_response.http_version} #{response.code} #{response.status_message}\r\n"
52
+ response.instance_variable_set(:@first_header_line, first_header_line)
53
+ response.instance_variable_get(:@options)[:response_headers] =
54
+ first_header_line + response.headers.map { |k,v| "#{k}: #{v}"}.join("\r\n")
55
+
56
+ response
81
57
  end
82
58
 
83
- # @private
84
- class << self
85
- def vcr_response_from(response)
86
- VCR::Response.new \
87
- VCR::ResponseStatus.new(response.code, response.status_message),
88
- response.headers,
89
- response.body,
90
- response.http_version,
91
- { "effective_url" => response.effective_url }
59
+ def stubbed_response_headers
60
+ @stubbed_response_headers ||= {}.tap do |hash|
61
+ stubbed_response.headers.each do |key, values|
62
+ hash[key] = values.size == 1 ? values.first : values
63
+ end if stubbed_response.headers
92
64
  end
65
+ end
66
+ end
93
67
 
94
- def collect_chunks(request)
95
- chunks = ''
96
- request.on_body.unshift(
97
- Proc.new do |body, response|
98
- chunks += body
99
- request.instance_variable_set(:@chunked_body, chunks)
100
- end
101
- )
102
- end
68
+ # @private
69
+ class << self
70
+ def vcr_response_from(response)
71
+ VCR::Response.new \
72
+ VCR::ResponseStatus.new(response.code, response.status_message),
73
+ response.headers,
74
+ response.body,
75
+ response.http_version,
76
+ { "effective_url" => response.effective_url }
77
+ end
103
78
 
104
- def restore_body_from_chunks(response, request)
105
- response.options[:response_body] = request.instance_variable_get(:@chunked_body)
106
- end
79
+ def collect_chunks(request)
80
+ chunks = ''
81
+ request.on_body.unshift(
82
+ Proc.new do |body, response|
83
+ chunks += body
84
+ request.instance_variable_set(:@chunked_body, chunks)
85
+ end
86
+ )
107
87
  end
108
88
 
109
- ::Typhoeus.on_complete do |response|
110
- request = response.request
89
+ def restore_body_from_chunks(response, request)
90
+ response.options[:response_body] = request.instance_variable_get(:@chunked_body)
91
+ end
92
+ end
111
93
 
112
- restore_body_from_chunks(response, request) if request.streaming?
94
+ ::Typhoeus.on_complete do |response|
95
+ request = response.request
113
96
 
114
- unless VCR.library_hooks.disabled?(:typhoeus)
115
- vcr_response = vcr_response_from(response)
116
- typed_vcr_request = request.send(:remove_instance_variable, :@__typed_vcr_request)
97
+ restore_body_from_chunks(response, request) if request.streaming?
117
98
 
118
- unless request.response.mock
119
- http_interaction = VCR::HTTPInteraction.new(typed_vcr_request, vcr_response)
120
- VCR.record_http_interaction(http_interaction)
121
- end
99
+ unless VCR.library_hooks.disabled?(:typhoeus)
100
+ vcr_response = vcr_response_from(response)
101
+ typed_vcr_request = request.send(:remove_instance_variable, :@__typed_vcr_request)
122
102
 
123
- VCR.configuration.invoke_hook(:after_http_request, typed_vcr_request, vcr_response)
103
+ unless request.response.mock
104
+ http_interaction = VCR::HTTPInteraction.new(typed_vcr_request, vcr_response)
105
+ VCR.record_http_interaction(http_interaction)
124
106
  end
107
+
108
+ VCR.configuration.invoke_hook(:after_http_request, typed_vcr_request, vcr_response)
125
109
  end
110
+ end
126
111
 
127
- ::Typhoeus.before do |request|
128
- collect_chunks(request) if request.streaming?
129
- if response = VCR::LibraryHooks::Typhoeus::RequestHandler.new(request).handle
130
- request.on_headers.each { |cb| cb.call(response) }
131
- request.on_body.each { |cb| cb.call(response.body, response) }
132
- request.finish(response)
133
- else
134
- true
135
- end
112
+ ::Typhoeus.before do |request|
113
+ collect_chunks(request) if request.streaming?
114
+ if response = VCR::LibraryHooks::Typhoeus::RequestHandler.new(request).handle
115
+ request.on_headers.each { |cb| cb.call(response) }
116
+ request.on_body.each { |cb| cb.call(response.body, response) }
117
+ request.finish(response)
118
+ else
119
+ true
136
120
  end
137
121
  end
138
122
  end
@@ -146,4 +130,3 @@ VCR.configuration.after_library_hooks_loaded do
146
130
  WebMock::HttpLibAdapters::TyphoeusAdapter.disable!
147
131
  end
148
132
  end
149
-
@@ -12,8 +12,6 @@ module VCR
12
12
  module WebMock
13
13
  extend self
14
14
 
15
- @global_hook_disabled_requests = {}
16
-
17
15
  def with_global_hook_disabled(request)
18
16
  global_hook_disabled_requests << request
19
17
 
@@ -25,19 +23,12 @@ module VCR
25
23
  end
26
24
 
27
25
  def global_hook_disabled?(request)
28
- requests = @global_hook_disabled_requests[Thread.current.object_id]
26
+ requests = Thread.current[:_vcr_webmock_disabled_requests]
29
27
  requests && requests.include?(request)
30
28
  end
31
29
 
32
30
  def global_hook_disabled_requests
33
- requests = @global_hook_disabled_requests[Thread.current.object_id]
34
- return requests if requests
35
-
36
- ObjectSpace.define_finalizer(Thread.current, lambda {
37
- @global_hook_disabled_requests.delete(Thread.current.object_id)
38
- })
39
-
40
- @global_hook_disabled_requests[Thread.current.object_id] = []
31
+ Thread.current[:_vcr_webmock_disabled_requests] ||= []
41
32
  end
42
33
 
43
34
  # @private
@@ -9,8 +9,8 @@ module VCR
9
9
  include Enumerable
10
10
 
11
11
  # Creates a new list of context-owned cassettes and linked cassettes
12
- # @param [Array] context-owned cassettes
13
- # @param [Array] context-unowned (linked) cassettes
12
+ # @param cassettes [Array] context-owned cassettes
13
+ # @param linked_cassettes [Array] context-unowned (linked) cassettes
14
14
  def initialize(cassettes, linked_cassettes)
15
15
  @cassettes = cassettes
16
16
  @linked_cassettes = linked_cassettes
@@ -52,8 +52,8 @@ module VCR
52
52
  end
53
53
 
54
54
  # Create a new CassetteList
55
- # @param [Array] context-owned cassettes
56
- # @param [Array] context-unowned (linked) cassettes
55
+ # @param cassettes [Array] context-owned cassettes
56
+ # @param linked_cassettes [Array] context-unowned (linked) cassettes
57
57
  def self.list(cassettes, linked_cassettes)
58
58
  CassetteList.new(cassettes, linked_cassettes)
59
59
  end
@@ -74,7 +74,7 @@ module VCR
74
74
  def after_request(response)
75
75
  vcr_response = vcr_response_for(response)
76
76
 
77
- if should_record?
77
+ if vcr_response && should_record?
78
78
  VCR.record_http_interaction(VCR::HTTPInteraction.new(vcr_request, vcr_response))
79
79
  end
80
80
 
@@ -31,6 +31,11 @@ module VCR
31
31
  RequestHandler.new(@app, env).handle
32
32
  end
33
33
 
34
+ # Close any persistent connections.
35
+ def close
36
+ @app.close if @app.respond_to?(:close)
37
+ end
38
+
34
39
  # @private
35
40
  class RequestHandler < ::VCR::RequestHandler
36
41
  attr_reader :app, :env
@@ -25,6 +25,10 @@ module VCR
25
25
  end
26
26
  end
27
27
 
28
+ def localhost_ignored?
29
+ (LOCALHOST_ALIASES & ignore_hosts.to_a).any?
30
+ end
31
+
28
32
  def ignore_hosts(*hosts)
29
33
  ignored_hosts.merge(hosts)
30
34
  end
@@ -110,12 +110,12 @@ module VCR
110
110
 
111
111
  def register_built_ins
112
112
  register(:method) { |r1, r2| r1.method == r2.method }
113
- register(:uri) { |r1, r2| r1.uri == r2.uri }
113
+ register(:uri) { |r1, r2| r1.parsed_uri == r2.parsed_uri }
114
114
  register(:body) { |r1, r2| r1.body == r2.body }
115
115
  register(:headers) { |r1, r2| r1.headers == r2.headers }
116
116
 
117
117
  register(:host) do |r1, r2|
118
- r1.parsed_uri.host == r2.parsed_uri.host
118
+ r1.parsed_uri.host.chomp('.') == r2.parsed_uri.host.chomp('.')
119
119
  end
120
120
  register(:path) do |r1, r2|
121
121
  r1.parsed_uri.path == r2.parsed_uri.path
data/lib/vcr/structs.rb CHANGED
@@ -62,7 +62,7 @@ module VCR
62
62
  super
63
63
 
64
64
  if body && !body.is_a?(String)
65
- raise ArgumentError, "#{self.class} initialized with an invalid body: #{body.inspect}."
65
+ raise ArgumentError, "#{self.class} initialized with an invalid (non-String) body of class #{body.class}: #{body.inspect}."
66
66
  end
67
67
 
68
68
  # Ensure that the body is a raw string, in case the string instance
@@ -346,9 +346,9 @@ module VCR
346
346
  {
347
347
  'status' => status.to_hash,
348
348
  'headers' => headers,
349
- 'body' => serializable_body,
350
- 'http_version' => http_version
349
+ 'body' => serializable_body
351
350
  }.tap do |hash|
351
+ hash['http_version'] = http_version if http_version
352
352
  hash['adapter_metadata'] = adapter_metadata unless adapter_metadata.empty?
353
353
  end
354
354
  end
@@ -454,9 +454,10 @@ module VCR
454
454
 
455
455
  case type
456
456
  when 'gzip'
457
- args = [StringIO.new(body)]
458
- args << { :encoding => 'ASCII-8BIT' } if ''.respond_to?(:encoding)
459
- yield Zlib::GzipReader.new(*args).read
457
+ gzip_reader_options = {}
458
+ gzip_reader_options[:encoding] = 'ASCII-8BIT' if ''.respond_to?(:encoding)
459
+ yield Zlib::GzipReader.new(StringIO.new(body),
460
+ **gzip_reader_options).read
460
461
  when 'deflate'
461
462
  yield Zlib::Inflate.inflate(body)
462
463
  when 'identity', NilClass
@@ -23,10 +23,10 @@ module VCR
23
23
  # Adds `Before` and `After` cucumber hooks for the named tags that
24
24
  # will cause a VCR cassette to be used for scenarios with matching tags.
25
25
  #
26
- # @param [Array<String>] tag_names the cucumber scenario tags
27
- # @param [(optional) Hash] options the cassette options. Specify
28
- # `:use_scenario_name => true` to automatically name the
29
- # cassette according to the scenario name.
26
+ # @param tag_names [Array<String,Hash>] the cucumber scenario tags. If
27
+ # the last argument is a hash it is treated as cassette options.
28
+ # - `:use_scenario_name => true` to automatically name the
29
+ # cassette according to the scenario name.
30
30
  def tags(*tag_names)
31
31
  original_options = tag_names.last.is_a?(::Hash) ? tag_names.pop : {}
32
32
  tag_names.each do |tag_name|
@@ -47,10 +47,21 @@ module VCR
47
47
  scenario.scenario_outline.name,
48
48
  scenario.name.split("\n").first
49
49
  ].join("/")
50
- else
50
+ elsif scenario.respond_to?(:feature)
51
51
  [ scenario.feature.name.split("\n").first,
52
52
  scenario.name.split("\n").first
53
53
  ].join("/")
54
+ elsif scenario.location.lines.min == scenario.location.lines.max
55
+ # test case from a regular scenario in cucumber version 4
56
+ [ scenario.location.file.split("/").last.split(".").first,
57
+ scenario.name.split("\n").first
58
+ ].join("/")
59
+ else
60
+ # test case from a scenario with examples ("scenario outline") in cucumber version 4
61
+ [ scenario.location.file.split("/").last.split(".").first,
62
+ scenario.name.split("\n").first,
63
+ "Example at line #{scenario.location.lines.max}"
64
+ ].join("/")
54
65
  end
55
66
  else
56
67
  "cucumber_tags/#{tag_name.gsub(/\A@/, '')}"
@@ -5,38 +5,50 @@ module VCR
5
5
  module Metadata
6
6
  extend self
7
7
 
8
+ def vcr_cassette_name_for(metadata)
9
+ description =
10
+ if metadata[:description].empty?
11
+ # we have an "it { is_expected.to be something }" block
12
+ metadata[:scoped_id]
13
+ else
14
+ metadata[:description]
15
+ end
16
+ example_group =
17
+ if metadata.key?(:example_group)
18
+ metadata[:example_group]
19
+ else
20
+ metadata[:parent_example_group]
21
+ end
22
+
23
+ if example_group
24
+ [vcr_cassette_name_for(example_group), description].join('/')
25
+ else
26
+ description
27
+ end
28
+ end
29
+
8
30
  def configure!
9
31
  ::RSpec.configure do |config|
10
- vcr_cassette_name_for = lambda do |metadata|
11
- description = if metadata[:description].empty?
12
- # we have an "it { is_expected.to be something }" block
13
- metadata[:scoped_id]
14
- else
15
- metadata[:description]
16
- end
17
- example_group = if metadata.key?(:example_group)
18
- metadata[:example_group]
19
- else
20
- metadata[:parent_example_group]
21
- end
22
-
23
- if example_group
24
- [vcr_cassette_name_for[example_group], description].join('/')
25
- else
26
- description
27
- end
28
- end
29
32
 
30
33
  when_tagged_with_vcr = { :vcr => lambda { |v| !!v } }
31
34
 
32
35
  config.before(:each, when_tagged_with_vcr) do |ex|
33
36
  example = ex.respond_to?(:metadata) ? ex : ex.example
34
37
 
38
+ cassette_name = nil
35
39
  options = example.metadata[:vcr]
36
- options = options.is_a?(Hash) ? options.dup : {} # in case it's just :vcr => true
40
+ options = case options
41
+ when Hash #=> vcr: { cassette_name: 'foo' }
42
+ options.dup
43
+ when String #=> vcr: 'bar'
44
+ cassette_name = options.dup
45
+ {}
46
+ else #=> :vcr or vcr: true
47
+ {}
48
+ end
37
49
 
38
- cassette_name = options.delete(:cassette_name) ||
39
- vcr_cassette_name_for[example.metadata]
50
+ cassette_name ||= options.delete(:cassette_name) ||
51
+ VCR::RSpec::Metadata.vcr_cassette_name_for(example.metadata)
40
52
  VCR.insert_cassette(cassette_name, options)
41
53
  end
42
54
 
data/lib/vcr/version.rb CHANGED
@@ -10,7 +10,7 @@ module VCR
10
10
  # * `parts` [Array<Integer>] List of the version parts.
11
11
  def version
12
12
  @version ||= begin
13
- string = '5.0.0'
13
+ string = +'6.1.0'
14
14
 
15
15
  def string.parts
16
16
  split('.').map { |p| p.to_i }
@@ -28,7 +28,7 @@ module VCR
28
28
  parts[2]
29
29
  end
30
30
 
31
- string
31
+ string.freeze
32
32
  end
33
33
  end
34
34
  end
data/lib/vcr.rb CHANGED
@@ -52,7 +52,7 @@ module VCR
52
52
  # Inserts the named cassette using the given cassette options.
53
53
  # New HTTP interactions, if allowed by the cassette's `:record` option, will
54
54
  # be recorded to the cassette. The cassette's existing HTTP interactions
55
- # will be used to stub requests, unless prevented by the cassete's
55
+ # will be used to stub requests, unless prevented by the cassette's
56
56
  # `:record` option.
57
57
  #
58
58
  # @example
@@ -107,6 +107,12 @@ module VCR
107
107
  # @option options :persist_with [Symbol] Which cassette persister to
108
108
  # use. Defaults to :file_system. You can also register and use a
109
109
  # custom persister.
110
+ # @option options :persister_options [Hash] Pass options to the
111
+ # persister specified in `persist_with`. Currently available options for the file_system persister:
112
+ # - `:downcase_cassette_names`: when `true`, names of cassettes will be
113
+ # normalized in lowercase before reading and writing, which can avoid
114
+ # confusion when using both case-sensitive and case-insensitive file
115
+ # systems.
110
116
  # @option options :preserve_exact_body_bytes [Boolean] Whether or not
111
117
  # to base64 encode the bytes of the requests and responses for this cassette
112
118
  # when serializing it. See also `VCR::Configuration#preserve_exact_body_bytes`.
@@ -186,6 +192,9 @@ module VCR
186
192
 
187
193
  begin
188
194
  call_block(block, cassette)
195
+ rescue StandardError
196
+ cassette.run_failed!
197
+ raise
189
198
  ensure
190
199
  eject_cassette
191
200
  end
@@ -194,13 +203,13 @@ module VCR
194
203
  # Inserts multiple cassettes the given names
195
204
  #
196
205
  # @example
197
- # cassettes = [
198
- # { name: 'github' },
199
- # { name: 'apple', options: { erb: true } }
200
- # ]
201
- # VCR.use_cassettes() do
202
- # # make multiple HTTP requests
203
- # end
206
+ # cassettes = [
207
+ # { name: 'github' },
208
+ # { name: 'apple', options: { erb: true } }
209
+ # ]
210
+ # VCR.use_cassettes(cassettes) do
211
+ # # make multiple HTTP requests
212
+ # end
204
213
  def use_cassettes(cassettes, &block)
205
214
  cassette = cassettes.pop
206
215
  use_cassette(cassette[:name], cassette[:options] || {}) do
@@ -257,6 +266,7 @@ module VCR
257
266
  # @see #turn_off!
258
267
  # @see #turn_on!
259
268
  # @see #turned_on?
269
+ # @see #turned_on
260
270
  def turned_off(options = {})
261
271
  turn_off!(options)
262
272
 
@@ -292,11 +302,28 @@ module VCR
292
302
  set_context_value(:turned_off, true)
293
303
  end
294
304
 
305
+ # Turns on VCR, for the duration of a block.
306
+ # @param (see #turn_off!)
307
+ # @return [void]
308
+ # @see #turn_off!
309
+ # @see #turned_off
310
+ # @see #turned_on?
311
+ def turned_on(options = {})
312
+ turn_on!
313
+
314
+ begin
315
+ yield
316
+ ensure
317
+ turn_off!(options)
318
+ end
319
+ end
320
+
295
321
  # Turns on VCR, if it has previously been turned off.
296
322
  # @return [void]
297
323
  # @see #turn_off!
298
324
  # @see #turned_off
299
325
  # @see #turned_on?
326
+ # @see #turned_on
300
327
  def turn_on!
301
328
  set_context_value(:turned_off, false)
302
329
  end
@@ -308,6 +335,9 @@ module VCR
308
335
  # @see #turn_off!
309
336
  # @see #turned_off
310
337
  def turned_on?
338
+ linked_context = current_context[:linked_context]
339
+ return !linked_context[:turned_off] if linked_context
340
+
311
341
  !context_value(:turned_off)
312
342
  end
313
343
 
metadata CHANGED
@@ -1,14 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vcr
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Myron Marston
8
- autorequire:
8
+ - Kurtis Rainbolt-Greene
9
+ - Olle Jonsson
10
+ autorequire:
9
11
  bindir: bin
10
12
  cert_chain: []
11
- date: 2019-05-27 00:00:00.000000000 Z
13
+ date: 2022-03-13 00:00:00.000000000 Z
12
14
  dependencies:
13
15
  - !ruby/object:Gem::Dependency
14
16
  name: bundler
@@ -44,28 +46,28 @@ dependencies:
44
46
  requirements:
45
47
  - - "~>"
46
48
  - !ruby/object:Gem::Version
47
- version: 3.1.4
49
+ version: 3.4.4
48
50
  type: :development
49
51
  prerelease: false
50
52
  version_requirements: !ruby/object:Gem::Requirement
51
53
  requirements:
52
54
  - - "~>"
53
55
  - !ruby/object:Gem::Version
54
- version: 3.1.4
56
+ version: 3.4.4
55
57
  - !ruby/object:Gem::Dependency
56
58
  name: rake
57
59
  requirement: !ruby/object:Gem::Requirement
58
60
  requirements:
59
- - - "~>"
61
+ - - ">="
60
62
  - !ruby/object:Gem::Version
61
- version: '10.1'
63
+ version: 12.3.3
62
64
  type: :development
63
65
  prerelease: false
64
66
  version_requirements: !ruby/object:Gem::Requirement
65
67
  requirements:
66
- - - "~>"
68
+ - - ">="
67
69
  - !ruby/object:Gem::Version
68
- version: '10.1'
70
+ version: 12.3.3
69
71
  - !ruby/object:Gem::Dependency
70
72
  name: pry
71
73
  requirement: !ruby/object:Gem::Requirement
@@ -150,48 +152,74 @@ dependencies:
150
152
  - - ">="
151
153
  - !ruby/object:Gem::Version
152
154
  version: '0'
155
+ - !ruby/object:Gem::Dependency
156
+ name: hashdiff
157
+ requirement: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: 1.0.0.beta1
162
+ - - "<"
163
+ - !ruby/object:Gem::Version
164
+ version: 2.0.0
165
+ type: :development
166
+ prerelease: false
167
+ version_requirements: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: 1.0.0.beta1
172
+ - - "<"
173
+ - !ruby/object:Gem::Version
174
+ version: 2.0.0
153
175
  - !ruby/object:Gem::Dependency
154
176
  name: cucumber
155
177
  requirement: !ruby/object:Gem::Requirement
156
178
  requirements:
157
179
  - - "~>"
158
180
  - !ruby/object:Gem::Version
159
- version: 2.0.2
181
+ version: '7.0'
160
182
  type: :development
161
183
  prerelease: false
162
184
  version_requirements: !ruby/object:Gem::Requirement
163
185
  requirements:
164
186
  - - "~>"
165
187
  - !ruby/object:Gem::Version
166
- version: 2.0.2
188
+ version: '7.0'
167
189
  - !ruby/object:Gem::Dependency
168
190
  name: aruba
169
191
  requirement: !ruby/object:Gem::Requirement
170
192
  requirements:
171
193
  - - "~>"
172
194
  - !ruby/object:Gem::Version
173
- version: 0.5.3
195
+ version: 0.14.14
174
196
  type: :development
175
197
  prerelease: false
176
198
  version_requirements: !ruby/object:Gem::Requirement
177
199
  requirements:
178
200
  - - "~>"
179
201
  - !ruby/object:Gem::Version
180
- version: 0.5.3
202
+ version: 0.14.14
181
203
  - !ruby/object:Gem::Dependency
182
204
  name: faraday
183
205
  requirement: !ruby/object:Gem::Requirement
184
206
  requirements:
185
- - - "~>"
207
+ - - ">="
186
208
  - !ruby/object:Gem::Version
187
209
  version: 0.11.0
210
+ - - "<"
211
+ - !ruby/object:Gem::Version
212
+ version: 2.0.0
188
213
  type: :development
189
214
  prerelease: false
190
215
  version_requirements: !ruby/object:Gem::Requirement
191
216
  requirements:
192
- - - "~>"
217
+ - - ">="
193
218
  - !ruby/object:Gem::Version
194
219
  version: 0.11.0
220
+ - - "<"
221
+ - !ruby/object:Gem::Version
222
+ version: 2.0.0
195
223
  - !ruby/object:Gem::Dependency
196
224
  name: httpclient
197
225
  requirement: !ruby/object:Gem::Requirement
@@ -210,14 +238,14 @@ dependencies:
210
238
  name: excon
211
239
  requirement: !ruby/object:Gem::Requirement
212
240
  requirements:
213
- - - '='
241
+ - - ">="
214
242
  - !ruby/object:Gem::Version
215
243
  version: 0.62.0
216
244
  type: :development
217
245
  prerelease: false
218
246
  version_requirements: !ruby/object:Gem::Requirement
219
247
  requirements:
220
- - - '='
248
+ - - ">="
221
249
  - !ruby/object:Gem::Version
222
250
  version: 0.62.0
223
251
  - !ruby/object:Gem::Dependency
@@ -234,20 +262,6 @@ dependencies:
234
262
  - - ">="
235
263
  - !ruby/object:Gem::Version
236
264
  version: '0'
237
- - !ruby/object:Gem::Dependency
238
- name: multi_json
239
- requirement: !ruby/object:Gem::Requirement
240
- requirements:
241
- - - ">="
242
- - !ruby/object:Gem::Version
243
- version: '0'
244
- type: :development
245
- prerelease: false
246
- version_requirements: !ruby/object:Gem::Requirement
247
- requirements:
248
- - - ">="
249
- - !ruby/object:Gem::Version
250
- version: '0'
251
265
  - !ruby/object:Gem::Dependency
252
266
  name: json
253
267
  requirement: !ruby/object:Gem::Requirement
@@ -307,7 +321,7 @@ dependencies:
307
321
  description: Record your test suite's HTTP interactions and replay them during future
308
322
  test runs for fast, deterministic, accurate tests.
309
323
  email:
310
- - myron.marston@gmail.com
324
+ - kurtis@rainbolt-greene.online
311
325
  executables: []
312
326
  extensions: []
313
327
  extra_rdoc_files: []
@@ -332,7 +346,6 @@ files:
332
346
  - lib/vcr/library_hooks/excon.rb
333
347
  - lib/vcr/library_hooks/faraday.rb
334
348
  - lib/vcr/library_hooks/typhoeus.rb
335
- - lib/vcr/library_hooks/typhoeus_0.4.rb
336
349
  - lib/vcr/library_hooks/webmock.rb
337
350
  - lib/vcr/linked_cassette.rb
338
351
  - lib/vcr/middleware/excon.rb
@@ -354,9 +367,10 @@ files:
354
367
  - lib/vcr/version.rb
355
368
  homepage: https://relishapp.com/vcr/vcr/docs
356
369
  licenses:
370
+ - Hippocratic-2.1
357
371
  - MIT
358
372
  metadata: {}
359
- post_install_message:
373
+ post_install_message:
360
374
  rdoc_options: []
361
375
  require_paths:
362
376
  - lib
@@ -364,15 +378,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
364
378
  requirements:
365
379
  - - ">="
366
380
  - !ruby/object:Gem::Version
367
- version: 1.9.3
381
+ version: '2.6'
368
382
  required_rubygems_version: !ruby/object:Gem::Requirement
369
383
  requirements:
370
384
  - - ">="
371
385
  - !ruby/object:Gem::Version
372
386
  version: '0'
373
387
  requirements: []
374
- rubygems_version: 3.0.3
375
- signing_key:
388
+ rubygems_version: 3.2.7
389
+ signing_key:
376
390
  specification_version: 4
377
391
  summary: Record your test suite's HTTP interactions and replay them during future
378
392
  test runs for fast, deterministic, accurate tests.
@@ -1,103 +0,0 @@
1
- VCR::VersionChecker.new('Typhoeus', Typhoeus::VERSION, '0.3.2').check_version!
2
-
3
- module VCR
4
- class LibraryHooks
5
- # @private
6
- module Typhoeus
7
- # @private
8
- class RequestHandler < ::VCR::RequestHandler
9
- attr_reader :request
10
- def initialize(request)
11
- @request = request
12
- end
13
-
14
- def vcr_request
15
- @vcr_request ||= VCR::Request.new \
16
- request.method,
17
- request.url,
18
- request.body,
19
- request.headers
20
- end
21
-
22
- private
23
-
24
- def externally_stubbed?
25
- ::Typhoeus::Hydra.stubs.detect { |stub| stub.matches?(request) }
26
- end
27
-
28
- def set_typed_request_for_after_hook(*args)
29
- super
30
- request.instance_variable_set(:@__typed_vcr_request, @after_hook_typed_request)
31
- end
32
-
33
- def on_unhandled_request
34
- invoke_after_request_hook(nil)
35
- super
36
- end
37
-
38
- def on_stubbed_by_vcr_request
39
- ::Typhoeus::Response.new \
40
- :http_version => stubbed_response.http_version,
41
- :code => stubbed_response.status.code,
42
- :status_message => stubbed_response.status.message,
43
- :headers_hash => stubbed_response_headers,
44
- :body => stubbed_response.body
45
- end
46
-
47
- def stubbed_response_headers
48
- @stubbed_response_headers ||= {}.tap do |hash|
49
- stubbed_response.headers.each do |key, values|
50
- hash[key] = values.size == 1 ? values.first : values
51
- end if stubbed_response.headers
52
- end
53
- end
54
- end
55
-
56
- # @private
57
- def self.vcr_response_from(response)
58
- VCR::Response.new \
59
- VCR::ResponseStatus.new(response.code, response.status_message),
60
- response.headers_hash,
61
- response.body,
62
- response.http_version
63
- end
64
-
65
- ::Typhoeus::Hydra.after_request_before_on_complete do |request|
66
- unless VCR.library_hooks.disabled?(:typhoeus)
67
- vcr_response = vcr_response_from(request.response)
68
- typed_vcr_request = request.send(:remove_instance_variable, :@__typed_vcr_request)
69
-
70
- unless request.response.mock?
71
- http_interaction = VCR::HTTPInteraction.new(typed_vcr_request, vcr_response)
72
- VCR.record_http_interaction(http_interaction)
73
- end
74
-
75
- VCR.configuration.invoke_hook(:after_http_request, typed_vcr_request, vcr_response)
76
- end
77
- end
78
-
79
- ::Typhoeus::Hydra.register_stub_finder do |request|
80
- VCR::LibraryHooks::Typhoeus::RequestHandler.new(request).handle
81
- end
82
- end
83
- end
84
- end
85
-
86
- # @private
87
- module Typhoeus
88
- class << Hydra
89
- # ensure HTTP requests are always allowed; VCR takes care of disallowing
90
- # them at the appropriate times in its hook
91
- def allow_net_connect_with_vcr?(*args)
92
- VCR.turned_on? ? true : allow_net_connect_without_vcr?
93
- end
94
-
95
- alias allow_net_connect_without_vcr? allow_net_connect?
96
- alias allow_net_connect? allow_net_connect_with_vcr?
97
- end unless Hydra.respond_to?(:allow_net_connect_with_vcr?)
98
- end
99
-
100
- VCR.configuration.after_library_hooks_loaded do
101
- ::Kernel.warn "WARNING: VCR's Typhoeus 0.4 integration is deprecated and will be removed in VCR 3.0."
102
- end
103
-