vcr 2.0.0.beta1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +37 -2
  4. data/Gemfile +2 -2
  5. data/README.md +10 -1
  6. data/Rakefile +43 -7
  7. data/Upgrade.md +45 -0
  8. data/features/.nav +1 -0
  9. data/features/cassettes/automatic_re_recording.feature +19 -17
  10. data/features/cassettes/dynamic_erb.feature +32 -28
  11. data/features/cassettes/exclusive.feature +28 -24
  12. data/features/cassettes/format.feature +213 -31
  13. data/features/cassettes/update_content_length_header.feature +20 -18
  14. data/features/configuration/filter_sensitive_data.feature +4 -4
  15. data/features/configuration/hooks.feature +27 -23
  16. data/features/http_libraries/em_http_request.feature +79 -75
  17. data/features/record_modes/all.feature +14 -14
  18. data/features/record_modes/new_episodes.feature +15 -15
  19. data/features/record_modes/none.feature +15 -15
  20. data/features/record_modes/once.feature +15 -15
  21. data/features/request_matching/body.feature +25 -23
  22. data/features/request_matching/custom_matcher.feature +25 -23
  23. data/features/request_matching/headers.feature +32 -36
  24. data/features/request_matching/host.feature +27 -25
  25. data/features/request_matching/identical_request_sequence.feature +27 -25
  26. data/features/request_matching/method.feature +27 -25
  27. data/features/request_matching/path.feature +27 -25
  28. data/features/request_matching/playback_repeats.feature +27 -25
  29. data/features/request_matching/uri.feature +27 -25
  30. data/features/request_matching/uri_without_param.feature +28 -26
  31. data/features/step_definitions/cli_steps.rb +71 -17
  32. data/features/support/env.rb +3 -1
  33. data/features/support/http_lib_filters.rb +6 -3
  34. data/features/support/vcr_cucumber_helpers.rb +4 -2
  35. data/lib/vcr.rb +6 -2
  36. data/lib/vcr/cassette.rb +75 -51
  37. data/lib/vcr/cassette/migrator.rb +111 -0
  38. data/lib/vcr/cassette/serializers.rb +35 -0
  39. data/lib/vcr/cassette/serializers/json.rb +23 -0
  40. data/lib/vcr/cassette/serializers/psych.rb +24 -0
  41. data/lib/vcr/cassette/serializers/syck.rb +35 -0
  42. data/lib/vcr/cassette/serializers/yaml.rb +24 -0
  43. data/lib/vcr/configuration.rb +6 -1
  44. data/lib/vcr/errors.rb +1 -1
  45. data/lib/vcr/library_hooks/excon.rb +1 -7
  46. data/lib/vcr/library_hooks/typhoeus.rb +6 -22
  47. data/lib/vcr/library_hooks/webmock.rb +1 -1
  48. data/lib/vcr/middleware/faraday.rb +1 -1
  49. data/lib/vcr/request_matcher_registry.rb +43 -30
  50. data/lib/vcr/structs.rb +209 -0
  51. data/lib/vcr/tasks/vcr.rake +9 -0
  52. data/lib/vcr/version.rb +1 -1
  53. data/spec/fixtures/cassette_spec/1_x_cassette.yml +110 -0
  54. data/spec/fixtures/cassette_spec/example.yml +79 -78
  55. data/spec/fixtures/cassette_spec/with_localhost_requests.yml +79 -77
  56. data/spec/fixtures/fake_example.com_responses.yml +78 -76
  57. data/spec/fixtures/match_requests_on.yml +147 -145
  58. data/spec/monkey_patches.rb +5 -5
  59. data/spec/support/http_library_adapters.rb +48 -0
  60. data/spec/support/shared_example_groups/hook_into_http_library.rb +53 -20
  61. data/spec/support/sinatra_app.rb +12 -0
  62. data/spec/vcr/cassette/http_interaction_list_spec.rb +1 -1
  63. data/spec/vcr/cassette/migrator_spec.rb +183 -0
  64. data/spec/vcr/cassette/serializers_spec.rb +122 -0
  65. data/spec/vcr/cassette_spec.rb +147 -83
  66. data/spec/vcr/configuration_spec.rb +11 -1
  67. data/spec/vcr/library_hooks/typhoeus_spec.rb +3 -3
  68. data/spec/vcr/library_hooks/webmock_spec.rb +7 -1
  69. data/spec/vcr/request_ignorer_spec.rb +1 -1
  70. data/spec/vcr/request_matcher_registry_spec.rb +46 -4
  71. data/spec/vcr/structs_spec.rb +309 -0
  72. data/spec/vcr_spec.rb +7 -0
  73. data/vcr.gemspec +9 -12
  74. metadata +75 -61
  75. data/lib/vcr/structs/http_interaction.rb +0 -58
  76. data/lib/vcr/structs/normalizers/body.rb +0 -24
  77. data/lib/vcr/structs/normalizers/header.rb +0 -64
  78. data/lib/vcr/structs/normalizers/status_message.rb +0 -17
  79. data/lib/vcr/structs/normalizers/uri.rb +0 -34
  80. data/lib/vcr/structs/request.rb +0 -13
  81. data/lib/vcr/structs/response.rb +0 -13
  82. data/lib/vcr/structs/response_status.rb +0 -5
  83. data/lib/vcr/util/yaml.rb +0 -11
  84. data/spec/support/shared_example_groups/normalizers.rb +0 -94
  85. data/spec/vcr/structs/http_interaction_spec.rb +0 -89
  86. data/spec/vcr/structs/request_spec.rb +0 -39
  87. data/spec/vcr/structs/response_spec.rb +0 -44
  88. data/spec/vcr/structs/response_status_spec.rb +0 -9
@@ -16,37 +16,39 @@ Feature: URI without param(s)
16
16
  Background:
17
17
  Given a previously recorded cassette file "cassettes/example.yml" with:
18
18
  """
19
- ---
20
- - !ruby/struct:VCR::HTTPInteraction
21
- request: !ruby/struct:VCR::Request
22
- method: :get
23
- uri: http://example.com:80/search?q=foo&timestamp=1316920490
24
- body:
25
- headers:
26
- response: !ruby/struct:VCR::Response
27
- status: !ruby/struct:VCR::ResponseStatus
19
+ ---
20
+ http_interactions:
21
+ - request:
22
+ method: get
23
+ uri: http://example.com/search?q=foo&timestamp=1316920490
24
+ body: ''
25
+ headers: {}
26
+ response:
27
+ status:
28
28
  code: 200
29
29
  message: OK
30
- headers:
31
- content-length:
32
- - "12"
30
+ headers:
31
+ Content-Length:
32
+ - '12'
33
33
  body: foo response
34
- http_version: "1.1"
35
- - !ruby/struct:VCR::HTTPInteraction
36
- request: !ruby/struct:VCR::Request
37
- method: :get
38
- uri: http://example.com:80/search?q=bar&timestamp=1296723437
39
- body:
40
- headers:
41
- response: !ruby/struct:VCR::Response
42
- status: !ruby/struct:VCR::ResponseStatus
34
+ http_version: '1.1'
35
+ recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
36
+ - request:
37
+ method: get
38
+ uri: http://example.com/search?q=bar&timestamp=1296723437
39
+ body: ''
40
+ headers: {}
41
+ response:
42
+ status:
43
43
  code: 200
44
44
  message: OK
45
- headers:
46
- content-length:
47
- - "12"
45
+ headers:
46
+ Content-Length:
47
+ - '12'
48
48
  body: bar response
49
- http_version: "1.1"
49
+ http_version: '1.1'
50
+ recorded_at: Tue, 01 Nov 2011 04:58:44 GMT
51
+ recorded_with: VCR 2.0.0
50
52
  """
51
53
 
52
54
  Scenario: Match the URI on all but the timestamp query parameter
@@ -65,7 +67,7 @@ Feature: URI without param(s)
65
67
  end
66
68
 
67
69
  def search_uri(q)
68
- "http://example.com:80/search?q=#{q}&timestamp=#{Time.now.to_i}"
70
+ "http://example.com/search?q=#{q}&timestamp=#{Time.now.to_i}"
69
71
  end
70
72
 
71
73
  VCR.use_cassette('example') do
@@ -1,26 +1,66 @@
1
1
  require 'vcr'
2
+ require 'multi_json'
2
3
 
3
4
  module VCRHelpers
4
5
 
5
- def normalize_cassette_structs(content)
6
- structs = YAML.load(content)
6
+ def normalize_cassette_hash(cassette_hash)
7
+ cassette_hash['recorded_with'] = "VCR #{VCR.version}"
8
+ cassette_hash['http_interactions'].map! { |h| normalize_http_interaction(h) }
9
+ cassette_hash
10
+ end
7
11
 
8
- # Remove non-deterministic headers
9
- structs.each do |s|
10
- s.response.headers.reject! { |k, v| %w[ server date ].include?(k) }
12
+ def normalize_headers(object)
13
+ object.headers = {} and return if object.headers.nil?
14
+ object.headers = {}.tap do |hash|
15
+ object.headers.each do |key, value|
16
+ hash[key.downcase] = value
17
+ end
11
18
  end
19
+ end
20
+
21
+ def static_timestamp
22
+ @static_timestamp ||= Time.now
23
+ end
24
+
25
+ def normalize_http_interaction(hash)
26
+ VCR::HTTPInteraction.from_hash(hash).tap do |i|
27
+ normalize_headers(i.request)
28
+ normalize_headers(i.response)
29
+
30
+ i.recorded_at &&= static_timestamp
31
+ i.request.body ||= ''
32
+ i.response.body ||= ''
33
+ i.response.status.message ||= ''
34
+
35
+ # Remove non-deterministic headers and headers
36
+ # that get added by a particular HTTP library (but not by others)
37
+ i.response.headers.reject! { |k, v| %w[ server date connection ].include?(k) }
38
+ i.request.headers.reject! { |k, v| %w[ accept user-agent connection expect ].include?(k) }
39
+
40
+ # Some HTTP libraries include an extra space ("OK " instead of "OK")
41
+ i.response.status.message = i.response.status.message.strip
12
42
 
13
- case @stubbing_lib_for_current_scenario
14
- when /excon|faraday/
43
+ if @scenario_parameters.to_s =~ /excon|faraday/
15
44
  # Excon/Faraday do not expose the status message or http version,
16
45
  # so we have no way to record these attributes.
17
- structs.each do |s|
18
- s.response.status.message = nil
19
- s.response.http_version = nil
20
- end
46
+ i.response.status.message = nil
47
+ i.response.http_version = nil
48
+ elsif @scenario_parameters.to_s.include?('webmock')
49
+ # WebMock does not expose the HTTP version so we have no way to record it
50
+ i.response.http_version = nil
51
+ end
21
52
  end
53
+ end
22
54
 
23
- structs
55
+ def normalize_cassette_content(content)
56
+ return content unless @scenario_parameters.to_s.include?('patron')
57
+ cassette_hash = YAML.load(content)
58
+ cassette_hash['http_interactions'].map! do |hash|
59
+ VCR::HTTPInteraction.from_hash(hash).tap do |i|
60
+ i.request.headers = (i.request.headers || {}).merge!('Expect' => [''])
61
+ end.to_hash
62
+ end
63
+ YAML.dump(cassette_hash)
24
64
  end
25
65
 
26
66
  def modify_file(file_name, orig_text, new_text)
@@ -45,11 +85,11 @@ Given /^the directory "([^"]*)" does not exist$/ do |dir|
45
85
  end
46
86
 
47
87
  Given /^a previously recorded cassette file "([^"]*)" with:$/ do |file_name, content|
48
- write_file(file_name, content)
88
+ write_file(file_name, normalize_cassette_content(content))
49
89
  end
50
90
 
51
- Given /^(\d+) days have passed since the cassette was recorded$/ do |day_count|
52
- set_env('DAYS_PASSED', day_count)
91
+ Given /^it is (.*)$/ do |date_string|
92
+ set_env('DATE_STRING', date_string)
53
93
  end
54
94
 
55
95
  When /^I modify the file "([^"]*)" to replace "([^"]*)" with "([^"]*)"$/ do |file_name, orig_text, new_text|
@@ -76,7 +116,21 @@ end
76
116
 
77
117
  Then /^the file "([^"]*)" should contain YAML like:$/ do |file_name, expected_content|
78
118
  actual_content = in_current_dir { File.read(file_name) }
79
- normalize_cassette_structs(actual_content).should == normalize_cassette_structs(expected_content)
119
+ normalize_cassette_hash(YAML.load(actual_content)).should == normalize_cassette_hash(YAML.load(expected_content))
120
+ end
121
+
122
+ Then /^the file "([^"]*)" should contain JSON like:$/ do |file_name, expected_content|
123
+ actual_content = in_current_dir { File.read(file_name) }
124
+ actual = MultiJson.decode(actual_content)
125
+ expected = MultiJson.decode(expected_content)
126
+ normalize_cassette_hash(actual).should == normalize_cassette_hash(expected)
127
+ end
128
+
129
+ Then /^the file "([^"]*)" should contain ruby like:$/ do |file_name, expected_content|
130
+ actual_content = in_current_dir { File.read(file_name) }
131
+ actual = eval(actual_content)
132
+ expected = eval(expected_content)
133
+ normalize_cassette_hash(actual).should == normalize_cassette_hash(expected)
80
134
  end
81
135
 
82
136
  Then /^the file "([^"]*)" should contain each of these:$/ do |file_name, table|
@@ -103,7 +157,7 @@ Then /^the file "([^"]*)" should contain a YAML fragment like:$/ do |file_name,
103
157
  end
104
158
 
105
159
  Then /^the cassette "([^"]*)" should have the following response bodies:$/ do |file, table|
106
- interactions = in_current_dir { YAML.load_file(file) }
160
+ interactions = in_current_dir { YAML.load_file(file) }['http_interactions'].map { |h| VCR::HTTPInteraction.from_hash(h) }
107
161
  actual_response_bodies = interactions.map { |i| i.response.body }
108
162
  expected_response_bodies = table.raw.flatten
109
163
  actual_response_bodies.should =~ expected_response_bodies
@@ -2,6 +2,8 @@ require 'rubygems'
2
2
  require 'bundler'
3
3
  Bundler.setup
4
4
 
5
+ require 'ruby-debug' if !defined?(RUBY_ENGINE) && RUBY_VERSION != '1.9.3' && !ENV['CI']
6
+
5
7
  require 'aruba/cucumber'
6
8
 
7
9
  cucumer_helpers_file = '../../features/support/vcr_cucumber_helpers'
@@ -24,6 +26,6 @@ else
24
26
  end
25
27
 
26
28
  Before do
27
- @aruba_timeout_seconds = RUBY_PLATFORM == 'java' ? 60 : 10
29
+ @aruba_timeout_seconds = RUBY_PLATFORM == 'java' ? 60 : 20
28
30
  end
29
31
 
@@ -18,7 +18,10 @@ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
18
18
  # it's probably a bug in it or rbx...so ignore it, for now.
19
19
 
20
20
  # I'm getting errors in the curb C extension in rbx.
21
- UNSUPPORTED_HTTP_LIBS = %w[ patron em-http-request curb ]
21
+
22
+ # Faraday and Typhoeus should be buildable on rbx, but the travis build times out,
23
+ # so we skip them to speed up the build on travis.
24
+ UNSUPPORTED_HTTP_LIBS = %w[ patron em-http-request curb faraday typhoeus ]
22
25
  elsif RUBY_PLATFORM == 'java'
23
26
  # These gems have C extensions and can't install on JRuby.
24
27
  c_dependent_libs = %w[ typhoeus patron curb em-http-request ]
@@ -44,8 +47,8 @@ end
44
47
  # logic in our step definitions based on the http stubbing library.
45
48
  Before do |scenario|
46
49
  if scenario.respond_to?(:cell_values)
47
- @stubbing_lib_for_current_scenario = scenario.cell_values.find { |v| v =~ /fakeweb|webmock|typhoeus|faraday|excon/ }
50
+ @scenario_parameters = scenario.cell_values
48
51
  else
49
- @stubbing_lib_for_current_scenario = nil
52
+ @scenario_parameters = nil
50
53
  end
51
54
  end
@@ -1,3 +1,5 @@
1
+ require 'date'
2
+
1
3
  # This file gets symlinked into the tmp/aruba directory before
2
4
  # each scenario so that it is available to be required in them.
3
5
  $LOAD_PATH << '../../spec' unless $LOAD_PATH.include?('../../spec')
@@ -15,9 +17,9 @@ if running_under_aruba
15
17
  end
16
18
  end
17
19
 
18
- if ENV['DAYS_PASSED']
20
+ if ENV['DATE_STRING']
19
21
  require 'timecop'
20
- Timecop.travel(Time.now + ENV['DAYS_PASSED'].to_i.days)
22
+ Timecop.travel(Date.parse(ENV['DATE_STRING']))
21
23
  end
22
24
 
23
25
  def include_http_adapter_for(lib)
data/lib/vcr.rb CHANGED
@@ -1,16 +1,16 @@
1
1
  require 'vcr/util/variable_args_block_caller'
2
- require 'vcr/util/yaml'
3
2
 
4
3
  require 'vcr/cassette'
4
+ require 'vcr/cassette/serializers'
5
5
  require 'vcr/configuration'
6
6
  require 'vcr/deprecations'
7
7
  require 'vcr/errors'
8
8
  require 'vcr/library_hooks'
9
9
  require 'vcr/request_ignorer'
10
10
  require 'vcr/request_matcher_registry'
11
+ require 'vcr/structs'
11
12
  require 'vcr/version'
12
13
 
13
- require 'vcr/structs/http_interaction'
14
14
 
15
15
  module VCR
16
16
  include VariableArgsBlockCaller
@@ -85,6 +85,10 @@ module VCR
85
85
  @library_hooks ||= LibraryHooks.new
86
86
  end
87
87
 
88
+ def cassette_serializers
89
+ @cassette_serializers ||= Cassette::Serializers.new
90
+ end
91
+
88
92
  def configuration
89
93
  @configuration ||= Configuration.new
90
94
  end
data/lib/vcr/cassette.rb CHANGED
@@ -1,21 +1,22 @@
1
1
  require 'fileutils'
2
2
  require 'erb'
3
- require 'set'
4
3
 
5
- require 'vcr/cassette/reader'
6
4
  require 'vcr/cassette/http_interaction_list'
5
+ require 'vcr/cassette/reader'
6
+ require 'vcr/cassette/serializers'
7
7
 
8
8
  module VCR
9
9
  class Cassette
10
10
  VALID_RECORD_MODES = [:all, :none, :new_episodes, :once]
11
11
 
12
- attr_reader :name, :record_mode, :match_requests_on, :erb, :re_record_interval, :tag, :http_interactions
12
+ attr_reader :name, :record_mode, :match_requests_on, :erb, :re_record_interval, :tag
13
13
 
14
14
  def initialize(name, options = {})
15
15
  options = VCR.configuration.default_cassette_options.merge(options)
16
16
  invalid_options = options.keys - [
17
17
  :record, :erb, :match_requests_on, :re_record_interval, :tag,
18
- :update_content_length_header, :allow_playback_repeats, :exclusive
18
+ :update_content_length_header, :allow_playback_repeats, :exclusive,
19
+ :serialize_with
19
20
  ]
20
21
 
21
22
  if invalid_options.size > 0
@@ -28,22 +29,44 @@ module VCR
28
29
  @match_requests_on = options[:match_requests_on]
29
30
  @re_record_interval = options[:re_record_interval]
30
31
  @tag = options[:tag]
31
- @record_mode = :all if should_re_record?
32
32
  @update_content_length_header = options[:update_content_length_header]
33
33
  @allow_playback_repeats = options[:allow_playback_repeats]
34
34
  @exclusive = options[:exclusive]
35
+ @serializer = VCR.cassette_serializers[options[:serialize_with]]
36
+ @record_mode = :all if should_re_record?
37
+ @parent_list = @exclusive ? HTTPInteractionList::NullList.new : VCR.http_interactions
35
38
 
36
39
  raise_error_unless_valid_record_mode
37
-
38
- load_recorded_interactions
39
40
  end
40
41
 
41
42
  def eject
42
43
  write_recorded_interactions_to_disk
43
44
  end
44
45
 
45
- def recorded_interactions
46
- @recorded_interactions ||= []
46
+ def previously_recorded_interactions
47
+ @previously_recorded_interactions ||= if file && File.size?(file)
48
+ deserialized_hash['http_interactions'].map { |h| HTTPInteraction.from_hash(h) }.tap do |interactions|
49
+ invoke_hook(:before_playback, interactions)
50
+
51
+ interactions.reject! do |i|
52
+ i.request.uri.is_a?(String) && VCR.request_ignorer.ignore?(i.request)
53
+ end
54
+
55
+ if update_content_length_header?
56
+ interactions.each { |i| i.response.update_content_length_header }
57
+ end
58
+ end
59
+ else
60
+ []
61
+ end
62
+ end
63
+
64
+ def http_interactions
65
+ @http_interactions ||= HTTPInteractionList.new \
66
+ should_stub_requests? ? previously_recorded_interactions : [],
67
+ match_requests_on,
68
+ @allow_playback_repeats,
69
+ @parent_list
47
70
  end
48
71
 
49
72
  def record_http_interaction(interaction)
@@ -55,7 +78,8 @@ module VCR
55
78
  end
56
79
 
57
80
  def file
58
- File.join(VCR.configuration.cassette_library_dir, "#{sanitized_name}.yml") if VCR.configuration.cassette_library_dir
81
+ return nil unless VCR.configuration.cassette_library_dir
82
+ File.join(VCR.configuration.cassette_library_dir, "#{sanitized_name}.#{@serializer.file_extension}")
59
83
  end
60
84
 
61
85
  def update_content_length_header?
@@ -70,6 +94,13 @@ module VCR
70
94
  end
71
95
  end
72
96
 
97
+ def serializable_hash
98
+ {
99
+ "http_interactions" => interactions_to_record.map(&:to_hash),
100
+ "recorded_with" => "VCR #{VCR.version}"
101
+ }
102
+ end
103
+
73
104
  private
74
105
 
75
106
  def sanitized_name
@@ -83,10 +114,16 @@ module VCR
83
114
  end
84
115
 
85
116
  def should_re_record?
86
- @re_record_interval &&
87
- File.exist?(file) &&
88
- File.stat(file).mtime + @re_record_interval < Time.now &&
89
- InternetConnection.available?
117
+ return false unless @re_record_interval
118
+ return false unless earliest_interaction_recorded_at
119
+ return false unless File.exist?(file)
120
+ return false unless InternetConnection.available?
121
+
122
+ earliest_interaction_recorded_at + @re_record_interval < Time.now
123
+ end
124
+
125
+ def earliest_interaction_recorded_at
126
+ previously_recorded_interactions.map(&:recorded_at).min
90
127
  end
91
128
 
92
129
  def should_stub_requests?
@@ -97,42 +134,12 @@ module VCR
97
134
  record_mode == :all
98
135
  end
99
136
 
100
- def load_recorded_interactions
101
- if file && File.size?(file)
102
- interactions = VCR::YAML.load(raw_yaml_content)
103
-
104
- invoke_hook(:before_playback, interactions)
105
-
106
- interactions.reject! do |i|
107
- i.request.uri.is_a?(String) && VCR.request_ignorer.ignore?(i.request)
108
- end
109
-
110
- if update_content_length_header?
111
- interactions.each { |i| i.response.update_content_length_header }
112
- end
113
-
114
- recorded_interactions.replace(interactions)
115
- end
116
-
117
- interactions = should_stub_requests? ? recorded_interactions : []
118
-
119
- @http_interactions = HTTPInteractionList.new(
120
- interactions,
121
- match_requests_on,
122
- @allow_playback_repeats,
123
- parent_list)
124
- end
125
-
126
- def parent_list
127
- @exclusive ? HTTPInteractionList::NullList.new : VCR.http_interactions
128
- end
129
-
130
137
  def raw_yaml_content
131
138
  VCR::Cassette::Reader.new(file, erb).read
132
139
  end
133
140
 
134
141
  def merged_interactions
135
- old_interactions = recorded_interactions
142
+ old_interactions = previously_recorded_interactions
136
143
 
137
144
  if should_remove_matching_existing_interactions?
138
145
  new_interaction_list = HTTPInteractionList.new(new_recorded_interactions, match_requests_on)
@@ -144,17 +151,21 @@ module VCR
144
151
  old_interactions + new_recorded_interactions
145
152
  end
146
153
 
154
+ def interactions_to_record
155
+ merged_interactions.tap do |interactions|
156
+ invoke_hook(:before_record, interactions)
157
+ end
158
+ end
159
+
147
160
  def write_recorded_interactions_to_disk
148
161
  return unless VCR.configuration.cassette_library_dir
149
- return if new_recorded_interactions.empty?
150
-
151
- interactions = merged_interactions
152
- invoke_hook(:before_record, interactions)
153
- return if interactions.empty?
162
+ return if new_recorded_interactions.none?
163
+ hash = serializable_hash
164
+ return if hash["http_interactions"].none?
154
165
 
155
166
  directory = File.dirname(file)
156
167
  FileUtils.mkdir_p directory unless File.exist?(directory)
157
- File.open(file, 'w') { |f| f.write VCR::YAML.dump(interactions) }
168
+ File.open(file, 'w') { |f| f.write @serializer.serialize(hash) }
158
169
  end
159
170
 
160
171
  def invoke_hook(type, interactions)
@@ -163,5 +174,18 @@ module VCR
163
174
  i.ignored?
164
175
  end
165
176
  end
177
+
178
+ def deserialized_hash
179
+ @deserialized_hash ||= @serializer.deserialize(raw_yaml_content).tap do |hash|
180
+ unless hash.is_a?(Hash) && hash['http_interactions'].is_a?(Array)
181
+ raise Errors::InvalidCassetteFormatError.new \
182
+ "#{file} does not appear to be a valid VCR 2.0 cassette. " +
183
+ "VCR 1.x cassettes are not valid with VCR 2.0. When upgrading from " +
184
+ "VCR 1.x, it is recommended that you delete all your existing cassettes and " +
185
+ "re-record them, or use the provided vcr:migrate_cassettes rake task to migrate " +
186
+ "them. For more info, see the VCR upgrade guide."
187
+ end
188
+ end
189
+ end
166
190
  end
167
191
  end