vcr 2.0.1 → 2.1.0
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 +2 -0
- data/.travis.yml +0 -4
- data/CHANGELOG.md +17 -1
- data/README.md +5 -2
- data/features/.nav +1 -0
- data/features/cassettes/decompress.feature +70 -0
- data/features/step_definitions/cli_steps.rb +6 -1
- data/features/support/env.rb +1 -1
- data/features/test_frameworks/cucumber.feature +18 -6
- data/lib/vcr.rb +9 -0
- data/lib/vcr/cassette.rb +2 -1
- data/lib/vcr/configuration.rb +4 -0
- data/lib/vcr/errors.rb +5 -0
- data/lib/vcr/structs.rb +85 -12
- data/lib/vcr/test_frameworks/cucumber.rb +4 -2
- data/lib/vcr/version.rb +1 -1
- data/spec/spec_helper.rb +15 -12
- data/spec/vcr/middleware/faraday_spec.rb +4 -5
- data/spec/vcr/structs_spec.rb +74 -0
- data/spec/vcr/test_frameworks/cucumber_spec.rb +29 -2
- data/spec/vcr_spec.rb +6 -0
- data/vcr.gemspec +1 -1
- metadata +9 -8
- data/.document +0 -5
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -11,17 +11,13 @@ rvm:
|
|
11
11
|
- jruby-19mode
|
12
12
|
- rbx-18mode
|
13
13
|
- rbx-19mode
|
14
|
-
- ruby-head
|
15
14
|
branches:
|
16
15
|
only:
|
17
16
|
- master
|
18
17
|
- 1-x-stable
|
19
18
|
- travis-testing
|
20
|
-
- 2-0-stable
|
21
19
|
matrix:
|
22
20
|
allow_failures:
|
23
21
|
- rvm: jruby-19mode
|
24
|
-
- rvm: rbx-18mode
|
25
22
|
- rvm: rbx-19mode
|
26
|
-
- rvm: ruby-head
|
27
23
|
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
## In git
|
2
2
|
|
3
|
-
[Full Changelog](http://github.com/myronmarston/vcr/compare/v2.0.
|
3
|
+
[Full Changelog](http://github.com/myronmarston/vcr/compare/v2.0.1...master)
|
4
|
+
|
5
|
+
* Add new `:use_scenario_name` option to the cucumber tags API. This
|
6
|
+
allows you to use a generic tag (such as `@vcr`) and have the
|
7
|
+
cassettes named based on the feature and scenario rather than based on
|
8
|
+
the tag. Thanks to [Omer Rauchwerger](https://github.com/rauchy) for
|
9
|
+
the implementation and [Chad Jolly](https://github.com/cjolly) for the
|
10
|
+
initial idea and feedback.
|
11
|
+
* Add new `:decode_compressed_response` cassette option. When set to
|
12
|
+
true, VCR will decompress a gzipped or deflated response before
|
13
|
+
recording the cassette, in order to make it more human readable.
|
14
|
+
Thanks to [Mislav Marohnić](https://github.com/mislav) for the
|
15
|
+
idea and implementation.
|
16
|
+
|
17
|
+
## 2.0.1 (March 30, 2012)
|
18
|
+
|
19
|
+
[Full Changelog](http://github.com/myronmarston/vcr/compare/v2.0.0...v2.0.1)
|
4
20
|
|
5
21
|
* Fix encoding logic to not attempt to encode the request or response
|
6
22
|
body on deserialization if there is no encoding specified. This should
|
data/README.md
CHANGED
@@ -112,6 +112,7 @@ Note that as of VCR 2, 1.8.6 and 1.9.1 are not supported.
|
|
112
112
|
and create a topic branch for every separate change you make.
|
113
113
|
* See the [Contributing](https://github.com/myronmarston/vcr/blob/master/CONTRIBUTING.md)
|
114
114
|
guide for instructions on running the specs and features.
|
115
|
+
* Code quality metrics are checked by [Code Climate](https://codeclimate.com/github/myronmarston/vcr).
|
115
116
|
* Documentation is generated with [YARD](http://yardoc.org/) ([cheat sheet](http://cheat.errtheblog.com/s/yard/)).
|
116
117
|
To generate while developing:
|
117
118
|
|
@@ -119,8 +120,6 @@ Note that as of VCR 2, 1.8.6 and 1.9.1 are not supported.
|
|
119
120
|
yard server --reload
|
120
121
|
```
|
121
122
|
|
122
|
-
If you find VCR useful, please recommend me on [working with rails](http://workingwithrails.com/person/16590-myron-marston).
|
123
|
-
|
124
123
|
## Thanks
|
125
124
|
|
126
125
|
* [Aslak Hellesøy](http://github.com/aslakhellesoy) for [Cucumber](http://github.com/aslakhellesoy/cucumber).
|
@@ -142,14 +141,18 @@ Thanks also to the following people who have contributed patches or helpful sugg
|
|
142
141
|
* [Ben Hutton](http://github.com/benhutton)
|
143
142
|
* [Bradley Isotope](https://github.com/bradleyisotope)
|
144
143
|
* [Carlos Kirkconnell](https://github.com/kirkconnell)
|
144
|
+
* [Chad Jolly](https://github.com/cjolly)
|
145
145
|
* [Eric Allam](http://github.com/rubymaverick)
|
146
|
+
* [Ezekiel Templin](https://github.com/ezkl)
|
146
147
|
* [Flaviu Simihaian](https://github.com/closedbracket)
|
147
148
|
* [Jeff Pollard](https://github.com/Fluxx)
|
148
149
|
* [Justin Smestad](https://github.com/jsmestad)
|
149
150
|
* [Karl Baum](https://github.com/kbaum)
|
150
151
|
* [Michael Lavrisha](https://github.com/vrish88)
|
152
|
+
* [Mislav Marohnić](https://github.com/mislav)
|
151
153
|
* [Nathaniel Bibler](https://github.com/nbibler)
|
152
154
|
* [Oliver Searle-Barnes](https://github.com/opsb)
|
155
|
+
* [Omer Rauchwerger](https://github.com/rauchy)
|
153
156
|
* [Paco Guzmán](https://github.com/pacoguzman)
|
154
157
|
* [Ryan Bates](https://github.com/ryanb)
|
155
158
|
* [Sathya Sekaran](https://github.com/sfsekaran)
|
data/features/.nav
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
Feature: Decode compressed response
|
2
|
+
|
3
|
+
When the `:decode_compressed_response` option is set to a truthy value, VCR
|
4
|
+
will decompress "gzip" and "deflate" response bodies before recording. This
|
5
|
+
ensures that these interactions become readable and editable after being
|
6
|
+
serialized.
|
7
|
+
|
8
|
+
This option should be avoided if the actual decompression of response bodies
|
9
|
+
is part of the functionality of the library or app being tested.
|
10
|
+
|
11
|
+
Background:
|
12
|
+
Given a file named "decompress.rb" with:
|
13
|
+
"""ruby
|
14
|
+
require 'zlib'
|
15
|
+
require 'stringio'
|
16
|
+
|
17
|
+
start_sinatra_app(:port => 7777) do
|
18
|
+
get('/') {
|
19
|
+
content = 'The quick brown fox jumps over the lazy dog'
|
20
|
+
io = StringIO.new
|
21
|
+
|
22
|
+
writer = Zlib::GzipWriter.new(io)
|
23
|
+
writer << content
|
24
|
+
writer.close
|
25
|
+
|
26
|
+
headers['Content-Encoding'] = 'gzip'
|
27
|
+
io.string
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'vcr'
|
32
|
+
|
33
|
+
VCR.configure do |c|
|
34
|
+
c.cassette_library_dir = 'cassettes'
|
35
|
+
c.hook_into :fakeweb
|
36
|
+
c.default_cassette_options = { :serialize_with => :syck }
|
37
|
+
end
|
38
|
+
"""
|
39
|
+
|
40
|
+
Scenario: The option is not set by default
|
41
|
+
When I append to file "decompress.rb":
|
42
|
+
"""ruby
|
43
|
+
VCR.use_cassette(:decompress) do
|
44
|
+
Net::HTTP.get_response('localhost', '/', 7777)
|
45
|
+
end
|
46
|
+
"""
|
47
|
+
And I run `ruby decompress.rb`
|
48
|
+
Then the file "cassettes/decompress.yml" should contain a YAML fragment like:
|
49
|
+
"""
|
50
|
+
content-encoding:
|
51
|
+
- gzip
|
52
|
+
"""
|
53
|
+
|
54
|
+
Scenario: The option is enabled
|
55
|
+
When I append to file "decompress.rb":
|
56
|
+
"""ruby
|
57
|
+
VCR.use_cassette(:decompress, :decode_compressed_response => true) do
|
58
|
+
Net::HTTP.get_response('localhost', '/', 7777)
|
59
|
+
end
|
60
|
+
"""
|
61
|
+
And I run `ruby decompress.rb`
|
62
|
+
Then the file "cassettes/decompress.yml" should contain a YAML fragment like:
|
63
|
+
"""
|
64
|
+
content-length:
|
65
|
+
- '43'
|
66
|
+
"""
|
67
|
+
And the file "cassettes/decompress.yml" should contain:
|
68
|
+
"""
|
69
|
+
string: The quick brown fox jumps over the lazy dog
|
70
|
+
"""
|
@@ -96,6 +96,10 @@ When /^I modify the file "([^"]*)" to replace "([^"]*)" with "([^"]*)"$/ do |fil
|
|
96
96
|
modify_file(file_name, orig_text, new_text)
|
97
97
|
end
|
98
98
|
|
99
|
+
When /^I append to file "([^"]*)":$/ do |file_name, content|
|
100
|
+
append_to_file(file_name, "\n" + content)
|
101
|
+
end
|
102
|
+
|
99
103
|
When /^I set the "([^"]*)" environment variable to "([^"]*)"$/ do |var, value|
|
100
104
|
set_env(var, value)
|
101
105
|
end
|
@@ -149,7 +153,8 @@ Then /^the file "([^"]*)" should contain a YAML fragment like:$/ do |file_name,
|
|
149
153
|
|
150
154
|
# Normalize by removing leading and trailing whitespace...
|
151
155
|
file_content = file_content.split("\n").map do |line|
|
152
|
-
|
156
|
+
# Different versions of psych use single vs. double quotes
|
157
|
+
line.strip.gsub('"', "'")
|
153
158
|
end.join("\n")
|
154
159
|
|
155
160
|
file_content.should include(fragment)
|
data/features/support/env.rb
CHANGED
@@ -15,13 +15,18 @@ Feature: Usage with Cucumber
|
|
15
15
|
|
16
16
|
t.tag '@tag3', :cassette => :options
|
17
17
|
t.tags '@tag4, '@tag5', :cassette => :options
|
18
|
+
t.tag '@vcr', :use_scenario_name => true
|
18
19
|
end
|
19
20
|
```
|
20
21
|
|
21
22
|
VCR will use a cassette named "cucumber_tags/<tag_name>" for scenarios
|
22
|
-
with each of these tags.
|
23
|
-
be used, or you can override specific
|
24
|
-
last argument to `#tag` or `#tags`.
|
23
|
+
with each of these tags (Unless the :use_scenario_name option is provided. See below).
|
24
|
+
The configured `default_cassette_options` will be used, or you can override specific
|
25
|
+
options by passing a hash as the last argument to `#tag` or `#tags`.
|
26
|
+
|
27
|
+
You can also have VCR name your cassettes automatically according to the feature
|
28
|
+
and scenario name by providing :use_scenario_name => true to '#tag' or '#tags'.
|
29
|
+
In this case, the cassette will be named "<feature_name>/<scenario_name>".
|
25
30
|
|
26
31
|
@exclude-jruby
|
27
32
|
Scenario: Record HTTP interactions in a scenario by tagging it
|
@@ -47,6 +52,7 @@ Feature: Usage with Cucumber
|
|
47
52
|
VCR.cucumber_tags do |t|
|
48
53
|
t.tag '@localhost_request' # uses default record mode since no options are given
|
49
54
|
t.tags '@disallowed_1', '@disallowed_2', :record => :none
|
55
|
+
t.tag '@vcr', :use_scenario_name => true
|
50
56
|
end
|
51
57
|
"""
|
52
58
|
And a file named "features/step_definitions/steps.rb" with:
|
@@ -78,6 +84,11 @@ Feature: Usage with Cucumber
|
|
78
84
|
When a request is made to "http://localhost:7777/localhost_request_2"
|
79
85
|
Then the response should be "Hello localhost_request_2"
|
80
86
|
|
87
|
+
@vcr
|
88
|
+
Scenario: tagged scenario
|
89
|
+
When a request is made to "http://localhost:7777/localhost_request_1"
|
90
|
+
Then the response should be "Hello localhost_request_1"
|
91
|
+
|
81
92
|
@disallowed_1
|
82
93
|
Scenario: tagged scenario
|
83
94
|
When a request is made to "http://localhost:7777/allowed" within a cassette named "allowed"
|
@@ -90,7 +101,7 @@ Feature: Usage with Cucumber
|
|
90
101
|
"""
|
91
102
|
And the directory "features/cassettes" does not exist
|
92
103
|
When I run `cucumber WITH_SERVER=true features/vcr_example.feature`
|
93
|
-
Then it should fail with "
|
104
|
+
Then it should fail with "4 scenarios (2 failed, 2 passed)"
|
94
105
|
And the output should contain each of the following:
|
95
106
|
| An HTTP request has been made that VCR does not know how to handle: |
|
96
107
|
| GET http://localhost:7777/disallowed_1 |
|
@@ -100,11 +111,12 @@ Feature: Usage with Cucumber
|
|
100
111
|
And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "Hello localhost_request_2"
|
101
112
|
And the file "features/cassettes/nested_cassette.yml" should contain "Hello nested_cassette"
|
102
113
|
And the file "features/cassettes/allowed.yml" should contain "Hello allowed"
|
114
|
+
And the file "features/cassettes/VCR_example/tagged_scenario.yml" should contain "Hello localhost_request_1"
|
103
115
|
|
104
116
|
# Run again without the server; we'll get the same responses because VCR
|
105
117
|
# will replay the recorded responses.
|
106
118
|
When I run `cucumber features/vcr_example.feature`
|
107
|
-
Then it should fail with "
|
119
|
+
Then it should fail with "4 scenarios (2 failed, 2 passed)"
|
108
120
|
And the output should contain each of the following:
|
109
121
|
| An HTTP request has been made that VCR does not know how to handle: |
|
110
122
|
| GET http://localhost:7777/disallowed_1 |
|
@@ -114,4 +126,4 @@ Feature: Usage with Cucumber
|
|
114
126
|
And the file "features/cassettes/cucumber_tags/localhost_request.yml" should contain "Hello localhost_request_2"
|
115
127
|
And the file "features/cassettes/nested_cassette.yml" should contain "Hello nested_cassette"
|
116
128
|
And the file "features/cassettes/allowed.yml" should contain "Hello allowed"
|
117
|
-
|
129
|
+
And the file "features/cassettes/VCR_example/tagged_scenario.yml" should contain "Hello localhost_request_1"
|
data/lib/vcr.rb
CHANGED
@@ -74,6 +74,9 @@ module VCR
|
|
74
74
|
# @option options :update_content_length_header [Boolean] Whether or
|
75
75
|
# not to overwrite the Content-Length header of the responses to
|
76
76
|
# match the length of the response body. Defaults to false.
|
77
|
+
# @option options :decode_compressed_response [Boolean] Whether or
|
78
|
+
# not to decode compressed responses before recording the cassette.
|
79
|
+
# This makes the cassette more human readable. Defaults to false.
|
77
80
|
# @option options :allow_playback_repeats [Boolean] Whether or not to
|
78
81
|
# allow a single HTTP interaction to be played back multiple times.
|
79
82
|
# Defaults to false.
|
@@ -144,6 +147,12 @@ module VCR
|
|
144
147
|
# @see #insert_cassette
|
145
148
|
# @see #eject_cassette
|
146
149
|
def use_cassette(name, options = {}, &block)
|
150
|
+
unless block
|
151
|
+
raise ArgumentError, "`VCR.use_cassette` requires a block. " +
|
152
|
+
"If you cannot wrap your code in a block, use " +
|
153
|
+
"`VCR.insert_cassette` / `VCR.eject_cassette` instead."
|
154
|
+
end
|
155
|
+
|
147
156
|
cassette = insert_cassette(name, options)
|
148
157
|
|
149
158
|
begin
|
data/lib/vcr/cassette.rb
CHANGED
@@ -50,7 +50,7 @@ module VCR
|
|
50
50
|
invalid_options = options.keys - [
|
51
51
|
:record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
|
52
52
|
:update_content_length_header, :allow_playback_repeats, :exclusive,
|
53
|
-
:serialize_with, :preserve_exact_body_bytes
|
53
|
+
:serialize_with, :preserve_exact_body_bytes, :decode_compressed_response
|
54
54
|
]
|
55
55
|
|
56
56
|
if invalid_options.size > 0
|
@@ -65,6 +65,7 @@ module VCR
|
|
65
65
|
@tags = Array(options.fetch(:tags) { options[:tag] })
|
66
66
|
@tags << :update_content_length_header if options[:update_content_length_header]
|
67
67
|
@tags << :preserve_exact_body_bytes if options[:preserve_exact_body_bytes]
|
68
|
+
@tags << :decode_compressed_response if options[:decode_compressed_response]
|
68
69
|
@allow_playback_repeats = options[:allow_playback_repeats]
|
69
70
|
@exclusive = options[:exclusive]
|
70
71
|
@serializer = VCR.cassette_serializers[options[:serialize_with]]
|
data/lib/vcr/configuration.rb
CHANGED
@@ -442,6 +442,10 @@ module VCR
|
|
442
442
|
interaction.response.update_content_length_header
|
443
443
|
end
|
444
444
|
|
445
|
+
before_record(:decode_compressed_response) do |interaction|
|
446
|
+
interaction.response.decompress if interaction.response.compressed?
|
447
|
+
end
|
448
|
+
|
445
449
|
preserve_exact_body_bytes do |http_message, cassette|
|
446
450
|
cassette && cassette.tags.include?(:preserve_exact_body_bytes)
|
447
451
|
end
|
data/lib/vcr/errors.rb
CHANGED
@@ -40,6 +40,11 @@ module VCR
|
|
40
40
|
# @see VCR::Configuration#around_http_request
|
41
41
|
class NotSupportedError < Error; end
|
42
42
|
|
43
|
+
# Error raised when you ask VCR to decode a compressed response
|
44
|
+
# body but the content encoding isn't one of the known ones.
|
45
|
+
# @see VCR::Response#decompress
|
46
|
+
class UnknownContentEncodingError < Error; end
|
47
|
+
|
43
48
|
# Error raised when an HTTP request is made that VCR is unable to handle.
|
44
49
|
# @note VCR will raise this to force you to do something about the
|
45
50
|
# HTTP request. The idea is that you want to handle _every_ HTTP
|
data/lib/vcr/structs.rb
CHANGED
@@ -100,6 +100,7 @@ module VCR
|
|
100
100
|
|
101
101
|
def normalize_headers
|
102
102
|
new_headers = {}
|
103
|
+
@normalized_header_keys = Hash.new {|h,k| k }
|
103
104
|
|
104
105
|
headers.each do |k, v|
|
105
106
|
val_array = case v
|
@@ -109,11 +110,36 @@ module VCR
|
|
109
110
|
end
|
110
111
|
|
111
112
|
new_headers[k] = convert_to_raw_strings(val_array)
|
113
|
+
@normalized_header_keys[k.downcase] = k
|
112
114
|
end if headers
|
113
115
|
|
114
116
|
self.headers = new_headers
|
115
117
|
end
|
116
118
|
|
119
|
+
def header_key(key)
|
120
|
+
key = @normalized_header_keys[key.downcase]
|
121
|
+
key if headers.has_key? key
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_header(key)
|
125
|
+
key = header_key(key)
|
126
|
+
headers[key] if key
|
127
|
+
end
|
128
|
+
|
129
|
+
def edit_header(key, value = nil)
|
130
|
+
if key = header_key(key)
|
131
|
+
value ||= yield headers[key]
|
132
|
+
headers[key] = Array(value)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def delete_header(key)
|
137
|
+
if key = header_key(key)
|
138
|
+
@normalized_header_keys.delete key.downcase
|
139
|
+
headers.delete key
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
117
143
|
def convert_to_raw_strings(array)
|
118
144
|
# Ensure the values are raw strings.
|
119
145
|
# Apparently for Paperclip uploads to S3, headers
|
@@ -268,15 +294,6 @@ module VCR
|
|
268
294
|
undef method
|
269
295
|
end
|
270
296
|
|
271
|
-
# Transforms the request into a fiber aware one by extending
|
272
|
-
# the {FiberAware} module onto the instance. Necessary for the
|
273
|
-
# {VCR::Configuration#around_http_request} hook.
|
274
|
-
#
|
275
|
-
# @return [Request] the request instance
|
276
|
-
def fiber_aware
|
277
|
-
extend FiberAware
|
278
|
-
end
|
279
|
-
|
280
297
|
private
|
281
298
|
|
282
299
|
def without_standard_port(uri)
|
@@ -326,9 +343,65 @@ module VCR
|
|
326
343
|
# Updates the Content-Length response header so that it is
|
327
344
|
# accurate for the response body.
|
328
345
|
def update_content_length_header
|
329
|
-
|
330
|
-
|
331
|
-
|
346
|
+
edit_header('Content-Length') { body ? body.bytesize.to_s : '0' }
|
347
|
+
end
|
348
|
+
|
349
|
+
# The type of encoding.
|
350
|
+
#
|
351
|
+
# @return [String] encoding type
|
352
|
+
def content_encoding
|
353
|
+
enc = get_header('Content-Encoding') and enc.first
|
354
|
+
end
|
355
|
+
|
356
|
+
# Checks if the type of encoding is one of "gzip" or "deflate".
|
357
|
+
def compressed?
|
358
|
+
%w[ gzip deflate ].include? content_encoding
|
359
|
+
end
|
360
|
+
|
361
|
+
# Decodes the compressed body and deletes evidence that it was ever compressed.
|
362
|
+
#
|
363
|
+
# @return self
|
364
|
+
# @raise [VCR::Errors::UnknownContentEncodingError] if the content encoding
|
365
|
+
# is not a known encoding.
|
366
|
+
def decompress
|
367
|
+
self.class.decompress(body, content_encoding) { |new_body|
|
368
|
+
self.body = new_body
|
369
|
+
update_content_length_header
|
370
|
+
delete_header('Content-Encoding')
|
371
|
+
}
|
372
|
+
return self
|
373
|
+
end
|
374
|
+
|
375
|
+
begin
|
376
|
+
require 'zlib'
|
377
|
+
require 'stringio'
|
378
|
+
HAVE_ZLIB = true
|
379
|
+
rescue LoadError
|
380
|
+
HAVE_ZLIB = false
|
381
|
+
end
|
382
|
+
|
383
|
+
# Decode string compressed with gzip or deflate
|
384
|
+
#
|
385
|
+
# @raise [VCR::Errors::UnknownContentEncodingError] if the content encoding
|
386
|
+
# is not a known encoding.
|
387
|
+
def self.decompress(body, type)
|
388
|
+
unless HAVE_ZLIB
|
389
|
+
warn "VCR: cannot decompress response; Zlib not available"
|
390
|
+
return
|
391
|
+
end
|
392
|
+
|
393
|
+
case type
|
394
|
+
when 'gzip'
|
395
|
+
args = [StringIO.new(body)]
|
396
|
+
args << { :encoding => 'ASCII-8BIT' } if ''.respond_to?(:encoding)
|
397
|
+
yield Zlib::GzipReader.new(*args).read
|
398
|
+
when 'deflate'
|
399
|
+
yield Zlib::Inflate.inflate(body)
|
400
|
+
when 'identity', NilClass
|
401
|
+
return
|
402
|
+
else
|
403
|
+
raise Errors::UnknownContentEncodingError, "unknown content encoding: #{type}"
|
404
|
+
end
|
332
405
|
end
|
333
406
|
end
|
334
407
|
|
@@ -24,7 +24,7 @@ module VCR
|
|
24
24
|
# will cause a VCR cassette to be used for scenarios with matching tags.
|
25
25
|
#
|
26
26
|
# @param [Array<String>] tag_names the cucumber scenario tags
|
27
|
-
# @param [(optional) Hash] options the cassette options
|
27
|
+
# @param [(optional) Hash] options the cassette options. Specify :use_scenario_name => true to automatically name the cassette according to the scenario name.
|
28
28
|
def tags(*tag_names)
|
29
29
|
options = tag_names.last.is_a?(::Hash) ? tag_names.pop : {}
|
30
30
|
tag_names.each do |tag_name|
|
@@ -35,7 +35,9 @@ module VCR
|
|
35
35
|
# cucumber has a bug: background steps do not run
|
36
36
|
# within an around hook.
|
37
37
|
# https://gist.github.com/652968
|
38
|
-
@main_object.Before(tag_name) do
|
38
|
+
@main_object.Before(tag_name) do |scenario|
|
39
|
+
options = options.dup
|
40
|
+
cassette_name = "#{scenario.feature.name}/#{scenario.name}" if options.delete(:use_scenario_name)
|
39
41
|
VCR.insert_cassette(cassette_name, options)
|
40
42
|
end
|
41
43
|
|
data/lib/vcr/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,20 +1,23 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
require 'simplecov'
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
add_filter "/features"
|
3
|
+
if RUBY_VERSION =~ /1.9/ && RUBY_ENGINE == 'ruby'
|
4
|
+
require 'simplecov'
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
SimpleCov.start do
|
7
|
+
add_filter "/spec"
|
8
|
+
add_filter "/features"
|
9
|
+
|
10
|
+
# internet_connection mostly contains logic copied from the ruby 1.8.7
|
11
|
+
# stdlib for which I haven't written tests.
|
12
|
+
add_filter "internet_connection"
|
13
|
+
end
|
12
14
|
|
13
|
-
SimpleCov.at_exit do
|
14
|
-
|
15
|
-
|
15
|
+
SimpleCov.at_exit do
|
16
|
+
File.open(File.join(SimpleCov.coverage_path, 'coverage_percent.txt'), 'w') do |f|
|
17
|
+
f.write SimpleCov.result.covered_percent
|
18
|
+
end
|
19
|
+
SimpleCov.result.format!
|
16
20
|
end
|
17
|
-
SimpleCov.result.format!
|
18
21
|
end
|
19
22
|
|
20
23
|
using_git = File.exist?(File.expand_path('../../.git/', __FILE__))
|
@@ -11,7 +11,6 @@ describe VCR::Middleware::Faraday do
|
|
11
11
|
|
12
12
|
context 'when making parallel requests' do
|
13
13
|
include VCRStubHelpers
|
14
|
-
let(:parallel_manager) { ::Faraday::Adapter::Typhoeus.setup_parallel_manager }
|
15
14
|
let(:connection) { ::Faraday.new { |b| b.adapter :typhoeus } }
|
16
15
|
let(:request_url) { "http://localhost:#{VCR::SinatraApp.port}/" }
|
17
16
|
|
@@ -20,7 +19,7 @@ describe VCR::Middleware::Faraday do
|
|
20
19
|
responses = []
|
21
20
|
|
22
21
|
VCR.use_cassette("multiple_parallel") do
|
23
|
-
connection.in_parallel
|
22
|
+
connection.in_parallel do
|
24
23
|
responses << connection.get(request_url)
|
25
24
|
responses << connection.get(request_url)
|
26
25
|
end
|
@@ -36,7 +35,7 @@ describe VCR::Middleware::Faraday do
|
|
36
35
|
|
37
36
|
shared_examples_for "exclusive library hook" do
|
38
37
|
def make_request
|
39
|
-
connection.in_parallel
|
38
|
+
connection.in_parallel { connection.get(request_url) }
|
40
39
|
end
|
41
40
|
|
42
41
|
it 'makes the faraday middleware exclusively enabled for the duration of the request' do
|
@@ -78,7 +77,7 @@ describe VCR::Middleware::Faraday do
|
|
78
77
|
undef make_request
|
79
78
|
def make_request
|
80
79
|
expect {
|
81
|
-
connection.in_parallel
|
80
|
+
connection.in_parallel { connection.get(request_url) }
|
82
81
|
}.to raise_error(VCR::Errors::UnhandledHTTPRequestError)
|
83
82
|
end
|
84
83
|
end
|
@@ -90,7 +89,7 @@ describe VCR::Middleware::Faraday do
|
|
90
89
|
undef make_request
|
91
90
|
def make_request(disabled = false)
|
92
91
|
response = nil
|
93
|
-
connection.in_parallel
|
92
|
+
connection.in_parallel do
|
94
93
|
response = connection.get(request_url)
|
95
94
|
end
|
96
95
|
response
|
data/spec/vcr/structs_spec.rb
CHANGED
@@ -2,6 +2,10 @@
|
|
2
2
|
|
3
3
|
require 'yaml'
|
4
4
|
require 'vcr/structs'
|
5
|
+
require 'vcr/errors'
|
6
|
+
require 'zlib'
|
7
|
+
require 'stringio'
|
8
|
+
require 'uri'
|
5
9
|
|
6
10
|
shared_examples_for "a header normalizer" do
|
7
11
|
let(:instance) do
|
@@ -520,6 +524,76 @@ module VCR
|
|
520
524
|
end
|
521
525
|
end
|
522
526
|
end
|
527
|
+
|
528
|
+
describe '#decompress' do
|
529
|
+
%w[ content-encoding Content-Encoding ].each do |header|
|
530
|
+
context "for the #{header} header" do
|
531
|
+
define_method :instance do |body, content_encoding|
|
532
|
+
headers = { 'content-type' => 'text',
|
533
|
+
'content-length' => body.bytesize.to_s }
|
534
|
+
headers[header] = content_encoding if content_encoding
|
535
|
+
described_class.new(VCR::ResponseStatus.new, headers, body)
|
536
|
+
end
|
537
|
+
|
538
|
+
let(:content) { 'The quick brown fox jumps over the lazy dog' }
|
539
|
+
|
540
|
+
it "does nothing when no compression" do
|
541
|
+
resp = instance('Hello', nil)
|
542
|
+
resp.should_not be_compressed
|
543
|
+
expect {
|
544
|
+
resp.decompress.should equal(resp)
|
545
|
+
}.to_not change { resp.headers['content-length'] }
|
546
|
+
end
|
547
|
+
|
548
|
+
it "does nothing when encoding is 'identity'" do
|
549
|
+
resp = instance('Hello', 'identity')
|
550
|
+
resp.should_not be_compressed
|
551
|
+
expect {
|
552
|
+
resp.decompress.should equal(resp)
|
553
|
+
}.to_not change { resp.headers['content-length'] }
|
554
|
+
end
|
555
|
+
|
556
|
+
it "raises error for unrecognized encoding" do
|
557
|
+
resp = instance('Hello', 'flabbergaster')
|
558
|
+
resp.should_not be_compressed
|
559
|
+
expect { resp.decompress }.
|
560
|
+
to raise_error(Errors::UnknownContentEncodingError, 'unknown content encoding: flabbergaster')
|
561
|
+
end
|
562
|
+
|
563
|
+
it "unzips gzipped response" do
|
564
|
+
io = StringIO.new
|
565
|
+
|
566
|
+
writer = Zlib::GzipWriter.new(io)
|
567
|
+
writer << content
|
568
|
+
writer.close
|
569
|
+
|
570
|
+
gzipped = io.string
|
571
|
+
resp = instance(gzipped, 'gzip')
|
572
|
+
resp.should be_compressed
|
573
|
+
expect {
|
574
|
+
resp.decompress.should equal(resp)
|
575
|
+
resp.should_not be_compressed
|
576
|
+
resp.body.should eq(content)
|
577
|
+
}.to change { resp.headers['content-length'] }.
|
578
|
+
from([gzipped.bytesize.to_s]).
|
579
|
+
to([content.bytesize.to_s])
|
580
|
+
end
|
581
|
+
|
582
|
+
it "inflates deflated response" do
|
583
|
+
deflated = Zlib::Deflate.deflate(content)
|
584
|
+
resp = instance(deflated, 'deflate')
|
585
|
+
resp.should be_compressed
|
586
|
+
expect {
|
587
|
+
resp.decompress.should equal(resp)
|
588
|
+
resp.should_not be_compressed
|
589
|
+
resp.body.should eq(content)
|
590
|
+
}.to change { resp.headers['content-length'] }.
|
591
|
+
from([deflated.bytesize.to_s]).
|
592
|
+
to([content.bytesize.to_s])
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
523
597
|
end
|
524
598
|
end
|
525
599
|
|
@@ -4,6 +4,8 @@ describe VCR::CucumberTags do
|
|
4
4
|
subject { described_class.new(self) }
|
5
5
|
let(:before_blocks_for_tags) { {} }
|
6
6
|
let(:after_blocks_for_tags) { {} }
|
7
|
+
let(:current_scenario) { stub(:name => "My scenario name",
|
8
|
+
:feature => stub(:name => "My feature name")) }
|
7
9
|
|
8
10
|
# define our own Before/After so we can test this in isolation from cucumber's implementation.
|
9
11
|
def Before(tag, &block)
|
@@ -17,9 +19,9 @@ describe VCR::CucumberTags do
|
|
17
19
|
def test_tag(cassette_attribute, tag, expected_value)
|
18
20
|
VCR.current_cassette.should be_nil
|
19
21
|
|
20
|
-
before_blocks_for_tags[tag].call
|
22
|
+
before_blocks_for_tags[tag].call(current_scenario)
|
21
23
|
VCR.current_cassette.send(cassette_attribute).should eq(expected_value)
|
22
|
-
after_blocks_for_tags[tag].call
|
24
|
+
after_blocks_for_tags[tag].call(current_scenario)
|
23
25
|
|
24
26
|
VCR.current_cassette.should be_nil
|
25
27
|
end
|
@@ -47,6 +49,31 @@ describe VCR::CucumberTags do
|
|
47
49
|
test_tag(:record_mode, 'tag1', :none)
|
48
50
|
test_tag(:record_mode, 'tag2', :new_episodes)
|
49
51
|
end
|
52
|
+
|
53
|
+
context 'with :use_scenario_name as an option' do
|
54
|
+
it "uses the scenario's name as the cassette name" do
|
55
|
+
subject.send(tag_method, 'tag1', :use_scenario_name => true)
|
56
|
+
|
57
|
+
test_tag(:name, 'tag1', 'My feature name/My scenario name')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'does not pass :use_scenario_name along the given options to the cassette' do
|
61
|
+
subject.send(tag_method, 'tag1', :use_scenario_name => true)
|
62
|
+
|
63
|
+
VCR::Cassette.should_receive(:new).with(anything, hash_not_including(:use_scenario_name))
|
64
|
+
before_blocks_for_tags['tag1'].call(current_scenario)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'does not modify the options passed to the cassette' do
|
68
|
+
original_options = { :use_scenario_name => true, :record => :none }
|
69
|
+
subject.send(tag_method, 'tag1', original_options)
|
70
|
+
before_blocks_for_tags['tag1'].call(current_scenario)
|
71
|
+
|
72
|
+
original_options.should have(2).items
|
73
|
+
original_options[:use_scenario_name].should eq(true)
|
74
|
+
original_options[:record].should eq(:none)
|
75
|
+
end
|
76
|
+
end
|
50
77
|
end
|
51
78
|
end
|
52
79
|
|
data/spec/vcr_spec.rb
CHANGED
@@ -95,6 +95,12 @@ describe VCR do
|
|
95
95
|
VCR.should_not_receive(:eject_cassette)
|
96
96
|
expect { VCR.use_cassette(:test) { } }.to raise_error(StandardError, 'Boom!')
|
97
97
|
end
|
98
|
+
|
99
|
+
it 'raises a helpful error if no block is given' do
|
100
|
+
expect {
|
101
|
+
VCR.use_cassette(:test)
|
102
|
+
}.to raise_error(/requires a block/)
|
103
|
+
end
|
98
104
|
end
|
99
105
|
|
100
106
|
describe '.http_interactions' do
|
data/vcr.gemspec
CHANGED
@@ -38,7 +38,7 @@ Gem::Specification.new do |s|
|
|
38
38
|
s.add_development_dependency 'sinatra', '~> 1.3.2'
|
39
39
|
s.add_development_dependency 'multi_json', '~> 1.0.3'
|
40
40
|
s.add_development_dependency 'json', '~> 1.6.5'
|
41
|
-
s.add_development_dependency 'limited_red', '~> 0.3.
|
41
|
+
s.add_development_dependency 'limited_red', '~> 0.3.9'
|
42
42
|
s.add_development_dependency 'simplecov', '~> 0.5.3'
|
43
43
|
|
44
44
|
unless RUBY_PLATFORM == 'java'
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vcr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 2
|
8
|
-
- 0
|
9
8
|
- 1
|
10
|
-
|
9
|
+
- 0
|
10
|
+
version: 2.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Myron Marston
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-04-19 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
version_requirements: &id001 !ruby/object:Gem::Requirement
|
@@ -289,12 +289,12 @@ dependencies:
|
|
289
289
|
requirements:
|
290
290
|
- - ~>
|
291
291
|
- !ruby/object:Gem::Version
|
292
|
-
hash:
|
292
|
+
hash: 1
|
293
293
|
segments:
|
294
294
|
- 0
|
295
295
|
- 3
|
296
|
-
-
|
297
|
-
version: 0.3.
|
296
|
+
- 9
|
297
|
+
version: 0.3.9
|
298
298
|
name: limited_red
|
299
299
|
type: :development
|
300
300
|
prerelease: false
|
@@ -404,7 +404,6 @@ extensions: []
|
|
404
404
|
extra_rdoc_files: []
|
405
405
|
|
406
406
|
files:
|
407
|
-
- .document
|
408
407
|
- .gemtest
|
409
408
|
- .gitignore
|
410
409
|
- .gitmodules
|
@@ -425,6 +424,7 @@ files:
|
|
425
424
|
- features/.nav
|
426
425
|
- features/about_these_examples.md
|
427
426
|
- features/cassettes/automatic_re_recording.feature
|
427
|
+
- features/cassettes/decompress.feature
|
428
428
|
- features/cassettes/dynamic_erb.feature
|
429
429
|
- features/cassettes/exclusive.feature
|
430
430
|
- features/cassettes/format.feature
|
@@ -597,6 +597,7 @@ summary: Record your test suite's HTTP interactions and replay them during futur
|
|
597
597
|
test_files:
|
598
598
|
- features/about_these_examples.md
|
599
599
|
- features/cassettes/automatic_re_recording.feature
|
600
|
+
- features/cassettes/decompress.feature
|
600
601
|
- features/cassettes/dynamic_erb.feature
|
601
602
|
- features/cassettes/exclusive.feature
|
602
603
|
- features/cassettes/format.feature
|