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.
- 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
|
+
|