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
@@ -1,4 +1,4 @@
1
- require 'typhoeus' if RUBY_INTERPRETER == :mri
1
+ require 'typhoeus' unless RUBY_INTERPRETER == :jruby
2
2
 
3
3
  module MonkeyPatches
4
4
  extend self
@@ -14,7 +14,7 @@ module MonkeyPatches
14
14
 
15
15
  ALL_MONKEY_PATCHES = NET_HTTP_MONKEY_PATCHES.dup
16
16
 
17
- ALL_MONKEY_PATCHES << [Typhoeus::Hydra::Stubbing::SharedMethods, :find_stub_from_request] if RUBY_INTERPRETER == :mri
17
+ ALL_MONKEY_PATCHES << [Typhoeus::Hydra::Stubbing::SharedMethods, :find_stub_from_request] if defined?(::Typhoeus)
18
18
 
19
19
  def enable!(scope)
20
20
  case scope
@@ -24,7 +24,7 @@ module MonkeyPatches
24
24
  when :webmock
25
25
  ::WebMock.reset!
26
26
  ::WebMock::HttpLibAdapters::NetHttpAdapter.enable!
27
- ::WebMock::HttpLibAdapters::TyphoeusAdapter.enable! if RUBY_INTERPRETER == :mri
27
+ ::WebMock::HttpLibAdapters::TyphoeusAdapter.enable! if defined?(::Typhoeus)
28
28
  $original_webmock_callbacks.each do |cb|
29
29
  ::WebMock::CallbackRegistry.add_callback(cb[:options], cb[:block])
30
30
  end
@@ -42,7 +42,7 @@ module MonkeyPatches
42
42
 
43
43
  if defined?(::WebMock)
44
44
  ::WebMock::HttpLibAdapters::NetHttpAdapter.disable!
45
- ::WebMock::HttpLibAdapters::TyphoeusAdapter.disable! unless RUBY_INTERPRETER == :jruby
45
+ ::WebMock::HttpLibAdapters::TyphoeusAdapter.disable! if defined?(::Typhoeus)
46
46
  ::WebMock::CallbackRegistry.reset
47
47
  ::WebMock::StubRegistry.instance.request_stubs = []
48
48
  end
@@ -112,7 +112,7 @@ end
112
112
  # for WebMock to work with them.
113
113
  require 'httpclient'
114
114
 
115
- if RUBY_INTERPRETER == :mri
115
+ unless RUBY_INTERPRETER == :jruby
116
116
  require 'patron'
117
117
  require 'em-http-request'
118
118
  require 'curb'
@@ -1,6 +1,18 @@
1
+ module HeaderDowncaser
2
+ def downcase_headers(headers)
3
+ {}.tap do |downcased|
4
+ headers.each do |k, v|
5
+ downcased[k.downcase] = v
6
+ end
7
+ end
8
+ end
9
+ end
10
+
1
11
  HTTP_LIBRARY_ADAPTERS = {}
2
12
 
3
13
  HTTP_LIBRARY_ADAPTERS['net/http'] = Module.new do
14
+ include HeaderDowncaser
15
+
4
16
  def self.http_library_name; 'Net::HTTP'; end
5
17
 
6
18
  def get_body_string(response); response.body; end
@@ -20,6 +32,14 @@ HTTP_LIBRARY_ADAPTERS['net/http'] = Module.new do
20
32
 
21
33
  http.send_request(method.to_s.upcase, uri.request_uri, body, headers)
22
34
  end
35
+
36
+ DEFAULT_REQUEST_HEADERS = { "Accept"=>["*/*"] }
37
+ DEFAULT_REQUEST_HEADERS['User-Agent'] = ["Ruby"] if RUBY_VERSION =~ /1.9/
38
+
39
+ def normalize_request_headers(headers)
40
+ defined?(super) ? super :
41
+ downcase_headers(headers.merge(DEFAULT_REQUEST_HEADERS))
42
+ end
23
43
  end
24
44
 
25
45
  HTTP_LIBRARY_ADAPTERS['patron'] = Module.new do
@@ -34,6 +54,10 @@ HTTP_LIBRARY_ADAPTERS['patron'] = Module.new do
34
54
  def make_http_request(method, url, body = nil, headers = {})
35
55
  Patron::Session.new.request(method, url, headers, :data => body || '')
36
56
  end
57
+
58
+ def normalize_request_headers(headers)
59
+ headers.merge('Expect' => [''])
60
+ end
37
61
  end
38
62
 
39
63
  HTTP_LIBRARY_ADAPTERS['httpclient'] = Module.new do
@@ -52,6 +76,10 @@ HTTP_LIBRARY_ADAPTERS['httpclient'] = Module.new do
52
76
  def make_http_request(method, url, body = nil, headers = {})
53
77
  HTTPClient.new.request(method, url, nil, body, headers)
54
78
  end
79
+
80
+ def normalize_request_headers(headers)
81
+ headers
82
+ end
55
83
  end
56
84
 
57
85
  HTTP_LIBRARY_ADAPTERS['em-http-request'] = Module.new do
@@ -73,6 +101,10 @@ HTTP_LIBRARY_ADAPTERS['em-http-request'] = Module.new do
73
101
  end
74
102
  http
75
103
  end
104
+
105
+ def normalize_request_headers(headers)
106
+ headers
107
+ end
76
108
  end
77
109
 
78
110
  HTTP_LIBRARY_ADAPTERS['curb'] = Module.new do
@@ -102,6 +134,10 @@ HTTP_LIBRARY_ADAPTERS['curb'] = Module.new do
102
134
  end
103
135
  end
104
136
  end
137
+
138
+ def normalize_request_headers(headers)
139
+ headers
140
+ end
105
141
  end
106
142
 
107
143
  HTTP_LIBRARY_ADAPTERS['typhoeus'] = Module.new do
@@ -118,6 +154,10 @@ HTTP_LIBRARY_ADAPTERS['typhoeus'] = Module.new do
118
154
  def make_http_request(method, url, body = nil, headers = {})
119
155
  Typhoeus::Request.send(method, url, :body => body, :headers => headers)
120
156
  end
157
+
158
+ def normalize_request_headers(headers)
159
+ headers.merge("User-Agent" => ["Typhoeus - http://github.com/dbalatero/typhoeus/tree/master"])
160
+ end
121
161
  end
122
162
 
123
163
  HTTP_LIBRARY_ADAPTERS['excon'] = Module.new do
@@ -134,6 +174,10 @@ HTTP_LIBRARY_ADAPTERS['excon'] = Module.new do
134
174
  def make_http_request(method, url, body = nil, headers = {})
135
175
  Excon.send(method, url, :body => body, :headers => headers)
136
176
  end
177
+
178
+ def normalize_request_headers(headers)
179
+ headers
180
+ end
137
181
  end
138
182
 
139
183
  %w[ net_http typhoeus patron ].each do |_faraday_adapter|
@@ -179,6 +223,10 @@ end
179
223
  builder.adapter faraday_adapter
180
224
  end
181
225
  end
226
+
227
+ def normalize_request_headers(headers)
228
+ headers
229
+ end
182
230
  end
183
231
  end
184
232
 
@@ -3,6 +3,13 @@ require 'cgi'
3
3
  NET_CONNECT_NOT_ALLOWED_ERROR = /You can use VCR to automatically record this request and replay it later/
4
4
 
5
5
  shared_examples_for "a hook into an HTTP library" do |library, *other|
6
+ include HeaderDowncaser
7
+
8
+ def interactions_from(file)
9
+ hashes = YAML.load_file(File.join(VCR::SPEC_ROOT, 'fixtures', file))['http_interactions']
10
+ hashes.map { |h| VCR::HTTPInteraction.from_hash(h) }
11
+ end
12
+
6
13
  unless adapter_module = HTTP_LIBRARY_ADAPTERS[library]
7
14
  raise ArgumentError.new("No http library adapter module could be found for #{library}")
8
15
  end
@@ -54,6 +61,26 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
54
61
  end
55
62
  end
56
63
 
64
+ def self.test_record_and_playback(description, query)
65
+ describe "a request to a URL #{description}" do
66
+ define_method :get_body do
67
+ VCR.use_cassette('record_and_playback', :record => :once) do
68
+ get_body_string make_http_request(:get, "http://localhost:#{VCR::SinatraApp.port}/record-and-playback?#{query}")
69
+ end
70
+ end
71
+
72
+ it "properly records and playsback a request with a URL #{description}" do
73
+ recorded_body = get_body
74
+ played_back_body = get_body
75
+ played_back_body.should eq(recorded_body)
76
+ end
77
+ end
78
+ end
79
+
80
+ test_record_and_playback "with spaces encoded as +", "q=a+b"
81
+ test_record_and_playback "with spaces encoded as %20", "q=a%20b"
82
+ test_record_and_playback "with a complex escaped query param", "q=#{CGI.escape("A&(! 234k !@ kasdj232\#$ kjw35")}"
83
+
57
84
  describe 'making an HTTP request' do
58
85
  let(:status) { VCR::ResponseStatus.new(200, 'OK') }
59
86
  let(:interaction) { VCR::HTTPInteraction.new(request, response) }
@@ -72,7 +99,7 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
72
99
  end
73
100
  end
74
101
 
75
- def self.test_url(description, url)
102
+ def self.test_playback(description, url)
76
103
  context "when a URL #{description} has been stubbed" do
77
104
  let(:request) { VCR::Request.new(:get, url) }
78
105
  let(:response) { VCR::Response.new(status, nil, response_body, '1.1') }
@@ -83,23 +110,27 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
83
110
  end
84
111
  end
85
112
 
86
- test_url "using https and no explicit port", "https://example.com/foo"
87
- test_url "using https and port 443", "https://example.com:443/foo"
88
- test_url "using https and some other port", "https://example.com:5190/foo"
89
- test_url "that has query params", "http://example.com/search?q=param"
90
- test_url "with spaces encoded as +", "http://example.com/search?q=a+b"
91
- test_url "with spaces encoded as %20", "http://example.com/search?q=a%20b"
92
- test_url "with an encoded ampersand", "http://example.com:80/search?q=#{CGI.escape("Q&A")}"
93
- test_url "with a complex escaped query param", "http://example.com:80/search?q=#{CGI.escape("A&(! 234k !@ kasdj232\#$ kjw35")}"
113
+ test_playback "using https and no explicit port", "https://example.com/foo"
114
+ test_playback "using https and port 443", "https://example.com:443/foo"
115
+ test_playback "using https and some other port", "https://example.com:5190/foo"
116
+ test_playback "that has query params", "http://example.com/search?q=param"
117
+ test_playback "with an encoded ampersand", "http://example.com:80/search?q=#{CGI.escape("Q&A")}"
94
118
  end
95
119
 
96
120
  describe '.stub_requests using specific match_attributes' do
97
121
  before(:each) { VCR.stub(:real_http_connections_allowed? => false) }
98
- let(:interactions) { VCR::YAML.load_file(File.join(VCR::SPEC_ROOT, 'fixtures', 'match_requests_on.yml')) }
122
+ let(:interactions) { interactions_from('match_requests_on.yml') }
123
+
124
+ let(:normalized_interactions) do
125
+ interactions.each do |i|
126
+ i.request.headers = normalize_request_headers(i.request.headers)
127
+ end
128
+ interactions
129
+ end
99
130
 
100
131
  def self.matching_on(attribute, valid, invalid, &block)
101
132
  describe ":#{attribute}" do
102
- let(:perform_stubbing) { stub_requests(interactions, [attribute]) }
133
+ let(:perform_stubbing) { stub_requests(normalized_interactions, [attribute]) }
103
134
 
104
135
  before(:each) { perform_stubbing }
105
136
  module_eval(&block)
@@ -146,7 +177,7 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
146
177
  end
147
178
  end
148
179
 
149
- matching_on :headers, {{ 'X-HTTP-HEADER1' => 'val1' } => 'val1 header response', { 'X-HTTP-HEADER1' => 'val2' } => 'val2 header response' }, { 'X-HTTP-HEADER1' => 'val3' } do
180
+ matching_on :headers, {{ 'X-Http-Header1' => 'val1' } => 'val1 header response', { 'X-Http-Header1' => 'val2' } => 'val2 header response' }, { 'X-Http-Header1' => 'val3' } do
150
181
  def make_http_request(headers)
151
182
  make_request(:get, "http://wrong-domain.com/wrong/path", nil, headers)
152
183
  end
@@ -166,7 +197,7 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
166
197
  let(:recorded_interaction) do
167
198
  interaction = nil
168
199
  VCR.should_receive(:record_http_interaction) { |i| interaction = i }
169
- make_http_request(:get, url)
200
+ make_http_request(:post, url, "the body", { 'X-Http-Foo' => 'bar' })
170
201
  interaction
171
202
  end
172
203
 
@@ -182,15 +213,16 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
182
213
  end
183
214
 
184
215
  it 'records the request method' do
185
- recorded_interaction.request.method.should eq(:get)
216
+ recorded_interaction.request.method.should eq(:post)
186
217
  end
187
218
 
188
219
  it 'records the request body' do
189
- recorded_interaction.request.body.should be_nil
220
+ recorded_interaction.request.body.should eq("the body")
190
221
  end
191
222
 
192
223
  it 'records the request headers' do
193
- recorded_interaction.request.headers.should be_nil
224
+ headers = downcase_headers(recorded_interaction.request.headers)
225
+ headers.should include('x-http-foo' => ['bar'])
194
226
  end
195
227
 
196
228
  it 'records the response status code' do
@@ -198,7 +230,7 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
198
230
  end
199
231
 
200
232
  it 'records the response status message' do
201
- recorded_interaction.response.status.message.should eq('OK')
233
+ recorded_interaction.response.status.message.strip.should eq('OK')
202
234
  end unless other.include?(:status_message_not_exposed)
203
235
 
204
236
  it 'records the response body' do
@@ -206,7 +238,8 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
206
238
  end
207
239
 
208
240
  it 'records the response headers' do
209
- recorded_interaction.response.headers['content-type'].should eq(["text/html;charset=utf-8"])
241
+ headers = downcase_headers(recorded_interaction.response.headers)
242
+ headers.should include('content-type' => ["text/html;charset=utf-8"])
210
243
  end
211
244
  end
212
245
  else
@@ -244,9 +277,9 @@ shared_examples_for "a hook into an HTTP library" do |library, *other|
244
277
  end
245
278
 
246
279
  context 'when some requests are stubbed' do
280
+ let(:interactions) { interactions_from('fake_example.com_responses.yml') }
247
281
  before(:each) do
248
- @recorded_interactions = VCR::YAML.load_file(File.join(VCR::SPEC_ROOT, 'fixtures', 'fake_example.com_responses.yml'))
249
- stub_requests(@recorded_interactions, VCR::RequestMatcherRegistry::DEFAULT_MATCHERS)
282
+ stub_requests(interactions, VCR::RequestMatcherRegistry::DEFAULT_MATCHERS)
250
283
  end
251
284
 
252
285
  it 'gets the stubbed responses when requests are made to http://example.com/foo, and does not record them' do
@@ -18,6 +18,10 @@ module VCR
18
18
  "FOO!"
19
19
  end
20
20
 
21
+ post '/foo' do
22
+ "FOO!"
23
+ end
24
+
21
25
  get '/set-cookie-headers/1' do
22
26
  headers 'Set-Cookie' => 'foo'
23
27
  'header set'
@@ -28,6 +32,14 @@ module VCR
28
32
  'header set'
29
33
  end
30
34
 
35
+ # we use a global counter so that every response is different;
36
+ # this ensures that the test demonstrates that the response
37
+ # is being played back (and not running a 2nd real request)
38
+ $record_and_playback_response_count ||= 0
39
+ get '/record-and-playback' do
40
+ "Response #{$record_and_playback_response_count += 1}"
41
+ end
42
+
31
43
  def self.port
32
44
  server.port
33
45
  end
@@ -1,6 +1,6 @@
1
1
  require 'vcr/cassette/http_interaction_list'
2
2
  require 'vcr/request_matcher_registry'
3
- require 'vcr/structs/http_interaction'
3
+ require 'vcr/structs'
4
4
  require 'uri'
5
5
 
6
6
  module VCR
@@ -0,0 +1,183 @@
1
+ require 'tmpdir'
2
+ require 'vcr/cassette/migrator'
3
+
4
+ describe VCR::Cassette::Migrator do
5
+ let(:original_contents) { <<-EOF
6
+ ---
7
+ - !ruby/struct:VCR::HTTPInteraction
8
+ request: !ruby/struct:VCR::Request
9
+ method: :get
10
+ uri: http://example.com:80/foo
11
+ body:
12
+ headers:
13
+ response: !ruby/struct:VCR::Response
14
+ status: !ruby/struct:VCR::ResponseStatus
15
+ code: 200
16
+ message: OK
17
+ headers:
18
+ content-type:
19
+ - text/html;charset=utf-8
20
+ content-length:
21
+ - "9"
22
+ body: Hello foo
23
+ http_version: "1.1"
24
+ - !ruby/struct:VCR::HTTPInteraction
25
+ request: !ruby/struct:VCR::Request
26
+ method: :get
27
+ uri: http://localhost:7777/bar
28
+ body:
29
+ headers:
30
+ response: !ruby/struct:VCR::Response
31
+ status: !ruby/struct:VCR::ResponseStatus
32
+ code: 200
33
+ message: OK
34
+ headers:
35
+ content-type:
36
+ - text/html;charset=utf-8
37
+ content-length:
38
+ - "9"
39
+ body: Hello bar
40
+ http_version: "1.1"
41
+ EOF
42
+ }
43
+
44
+ let(:updated_contents) { <<-EOF
45
+ ---
46
+ http_interactions:
47
+ - request:
48
+ method: get
49
+ uri: http://example.com/foo
50
+ body: ""
51
+ headers: {}
52
+
53
+ response:
54
+ status:
55
+ code: 200
56
+ message: OK
57
+ headers:
58
+ Content-Type:
59
+ - text/html;charset=utf-8
60
+ Content-Length:
61
+ - "9"
62
+ body: Hello foo
63
+ http_version: "1.1"
64
+ recorded_at: Wed, 04 May 2011 12:30:00 GMT
65
+ - request:
66
+ method: get
67
+ uri: http://localhost:7777/bar
68
+ body: ""
69
+ headers: {}
70
+
71
+ response:
72
+ status:
73
+ code: 200
74
+ message: OK
75
+ headers:
76
+ Content-Type:
77
+ - text/html;charset=utf-8
78
+ Content-Length:
79
+ - "9"
80
+ body: Hello bar
81
+ http_version: "1.1"
82
+ recorded_at: Wed, 04 May 2011 12:30:00 GMT
83
+ recorded_with: VCR 1.11.3
84
+ EOF
85
+ }
86
+
87
+ attr_accessor :dir
88
+
89
+ around(:each) do |example|
90
+ Dir.mktmpdir do |dir|
91
+ self.dir = dir
92
+ example.run
93
+ end
94
+ end
95
+
96
+ # JRuby serializes YAML with some slightly different whitespace.
97
+ before(:each) do
98
+ [original_contents, updated_contents].each do |contents|
99
+ contents.gsub!(/^(\s+)-/, '\1 -')
100
+ end
101
+ updated_contents.gsub!(/^(- | )/, ' \1')
102
+ end if RUBY_PLATFORM == 'java'
103
+
104
+ # Use syck on all rubies for consistent results...
105
+ before(:each) do
106
+ YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE)
107
+ end
108
+
109
+ after(:each) do
110
+ YAML::ENGINE.yamler = 'psych' if defined?(YAML::ENGINE)
111
+ end
112
+
113
+ let(:filemtime) { Time.utc(2011, 5, 4, 12, 30) }
114
+ let(:out_io) { StringIO.new }
115
+ let(:file_name) { File.join(dir, "example.yml") }
116
+ let(:output) { out_io.rewind; out_io.read }
117
+
118
+ subject { described_class.new(dir, out_io) }
119
+
120
+ before(:each) do
121
+ File.stub(:mtime).with(file_name).and_return(filemtime)
122
+ end
123
+
124
+ it 'migrates a cassette from the 1.x to 2.x format' do
125
+ File.open(file_name, 'w') { |f| f.write(original_contents) }
126
+ subject.migrate!
127
+ File.read(file_name).should eq(updated_contents)
128
+ output.should match(/Migrated example.yml/)
129
+ end
130
+
131
+ it 'ignores files that do not contain arrays' do
132
+ File.open(file_name, 'w') { |f| f.write(true.to_yaml) }
133
+ subject.migrate!
134
+ File.read(file_name).should eq(true.to_yaml)
135
+ output.should match(/Ignored example.yml since it does not appear to be a valid VCR 1.x cassette/)
136
+ end
137
+
138
+ it 'ignores files that contain YAML arrays of other things' do
139
+ File.open(file_name, 'w') { |f| f.write([{}, {}].to_yaml) }
140
+ subject.migrate!
141
+ File.read(file_name).should eq([{}, {}].to_yaml)
142
+ output.should match(/Ignored example.yml since it does not appear to be a valid VCR 1.x cassette/)
143
+ end
144
+
145
+ it 'ignores URIs that have sensitive data substitutions' do
146
+ modified_contents = original_contents.gsub('example.com', '<HOST>')
147
+ File.open(file_name, 'w') { |f| f.write(modified_contents) }
148
+ subject.migrate!
149
+ File.read(file_name).should eq(updated_contents.gsub('example.com', '<HOST>:80'))
150
+ end
151
+
152
+ it 'ignores files that are empty' do
153
+ File.open(file_name, 'w') { |f| f.write('') }
154
+ subject.migrate!
155
+ File.read(file_name).should eq('')
156
+ output.should match(/Ignored example.yml since it could not be parsed as YAML/)
157
+ end
158
+
159
+ shared_examples_for "ignoring invalid YAML" do
160
+ it 'ignores files that cannot be parsed as valid YAML (such as ERB cassettes)' do
161
+ modified_contents = original_contents.gsub(/\A---/, "---\n<% 3.times do %>")
162
+ modified_contents = modified_contents.gsub(/\z/, "<% end %>")
163
+ File.open(file_name, 'w') { |f| f.write(modified_contents) }
164
+ subject.migrate!
165
+ File.read(file_name).should eq(modified_contents)
166
+ output.should match(/Ignored example.yml since it could not be parsed as YAML/)
167
+ end
168
+ end
169
+
170
+ context 'with syck' do
171
+ it_behaves_like "ignoring invalid YAML"
172
+ end
173
+
174
+ context 'with psych' do
175
+ before(:each) do
176
+ pending "psych not available" unless defined?(YAML::ENGINE)
177
+ YAML::ENGINE.yamler = 'psych'
178
+ end
179
+
180
+ it_behaves_like "ignoring invalid YAML"
181
+ end
182
+ end
183
+