vcr 2.0.0.beta1 → 2.0.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+