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.
- data/.gitignore +1 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.md +37 -2
- data/Gemfile +2 -2
- data/README.md +10 -1
- data/Rakefile +43 -7
- data/Upgrade.md +45 -0
- data/features/.nav +1 -0
- data/features/cassettes/automatic_re_recording.feature +19 -17
- data/features/cassettes/dynamic_erb.feature +32 -28
- data/features/cassettes/exclusive.feature +28 -24
- data/features/cassettes/format.feature +213 -31
- data/features/cassettes/update_content_length_header.feature +20 -18
- data/features/configuration/filter_sensitive_data.feature +4 -4
- data/features/configuration/hooks.feature +27 -23
- data/features/http_libraries/em_http_request.feature +79 -75
- data/features/record_modes/all.feature +14 -14
- data/features/record_modes/new_episodes.feature +15 -15
- data/features/record_modes/none.feature +15 -15
- data/features/record_modes/once.feature +15 -15
- data/features/request_matching/body.feature +25 -23
- data/features/request_matching/custom_matcher.feature +25 -23
- data/features/request_matching/headers.feature +32 -36
- data/features/request_matching/host.feature +27 -25
- data/features/request_matching/identical_request_sequence.feature +27 -25
- data/features/request_matching/method.feature +27 -25
- data/features/request_matching/path.feature +27 -25
- data/features/request_matching/playback_repeats.feature +27 -25
- data/features/request_matching/uri.feature +27 -25
- data/features/request_matching/uri_without_param.feature +28 -26
- data/features/step_definitions/cli_steps.rb +71 -17
- data/features/support/env.rb +3 -1
- data/features/support/http_lib_filters.rb +6 -3
- data/features/support/vcr_cucumber_helpers.rb +4 -2
- data/lib/vcr.rb +6 -2
- data/lib/vcr/cassette.rb +75 -51
- data/lib/vcr/cassette/migrator.rb +111 -0
- data/lib/vcr/cassette/serializers.rb +35 -0
- data/lib/vcr/cassette/serializers/json.rb +23 -0
- data/lib/vcr/cassette/serializers/psych.rb +24 -0
- data/lib/vcr/cassette/serializers/syck.rb +35 -0
- data/lib/vcr/cassette/serializers/yaml.rb +24 -0
- data/lib/vcr/configuration.rb +6 -1
- data/lib/vcr/errors.rb +1 -1
- data/lib/vcr/library_hooks/excon.rb +1 -7
- data/lib/vcr/library_hooks/typhoeus.rb +6 -22
- data/lib/vcr/library_hooks/webmock.rb +1 -1
- data/lib/vcr/middleware/faraday.rb +1 -1
- data/lib/vcr/request_matcher_registry.rb +43 -30
- data/lib/vcr/structs.rb +209 -0
- data/lib/vcr/tasks/vcr.rake +9 -0
- data/lib/vcr/version.rb +1 -1
- data/spec/fixtures/cassette_spec/1_x_cassette.yml +110 -0
- data/spec/fixtures/cassette_spec/example.yml +79 -78
- data/spec/fixtures/cassette_spec/with_localhost_requests.yml +79 -77
- data/spec/fixtures/fake_example.com_responses.yml +78 -76
- data/spec/fixtures/match_requests_on.yml +147 -145
- data/spec/monkey_patches.rb +5 -5
- data/spec/support/http_library_adapters.rb +48 -0
- data/spec/support/shared_example_groups/hook_into_http_library.rb +53 -20
- data/spec/support/sinatra_app.rb +12 -0
- data/spec/vcr/cassette/http_interaction_list_spec.rb +1 -1
- data/spec/vcr/cassette/migrator_spec.rb +183 -0
- data/spec/vcr/cassette/serializers_spec.rb +122 -0
- data/spec/vcr/cassette_spec.rb +147 -83
- data/spec/vcr/configuration_spec.rb +11 -1
- data/spec/vcr/library_hooks/typhoeus_spec.rb +3 -3
- data/spec/vcr/library_hooks/webmock_spec.rb +7 -1
- data/spec/vcr/request_ignorer_spec.rb +1 -1
- data/spec/vcr/request_matcher_registry_spec.rb +46 -4
- data/spec/vcr/structs_spec.rb +309 -0
- data/spec/vcr_spec.rb +7 -0
- data/vcr.gemspec +9 -12
- metadata +75 -61
- data/lib/vcr/structs/http_interaction.rb +0 -58
- data/lib/vcr/structs/normalizers/body.rb +0 -24
- data/lib/vcr/structs/normalizers/header.rb +0 -64
- data/lib/vcr/structs/normalizers/status_message.rb +0 -17
- data/lib/vcr/structs/normalizers/uri.rb +0 -34
- data/lib/vcr/structs/request.rb +0 -13
- data/lib/vcr/structs/response.rb +0 -13
- data/lib/vcr/structs/response_status.rb +0 -5
- data/lib/vcr/util/yaml.rb +0 -11
- data/spec/support/shared_example_groups/normalizers.rb +0 -94
- data/spec/vcr/structs/http_interaction_spec.rb +0 -89
- data/spec/vcr/structs/request_spec.rb +0 -39
- data/spec/vcr/structs/response_spec.rb +0 -44
- data/spec/vcr/structs/response_status_spec.rb +0 -9
data/spec/monkey_patches.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'typhoeus'
|
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
|
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
|
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!
|
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
|
-
|
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.
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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) {
|
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(
|
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-
|
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(:
|
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(:
|
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
|
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
|
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
|
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
|
-
|
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
|
data/spec/support/sinatra_app.rb
CHANGED
@@ -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
|
@@ -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
|
+
|