vcr 1.3.3 → 1.4.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.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/CHANGELOG.md +16 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +23 -4
  5. data/Guardfile +6 -0
  6. data/README.md +5 -4
  7. data/Rakefile +1 -3
  8. data/TODO.md +3 -0
  9. data/cucumber.yml +3 -3
  10. data/features/cassettes/no_cassette.feature +67 -0
  11. data/features/configuration/allow_http_connections_when_no_cassette.feature +54 -0
  12. data/features/configuration/ignore_localhost.feature +2 -2
  13. data/features/configuration/stub_with.feature +40 -16
  14. data/features/http_libraries/em_http_request.feature +217 -0
  15. data/features/middleware/faraday.feature +89 -0
  16. data/features/middleware/rack.feature +95 -0
  17. data/features/step_definitions/cli_steps.rb +7 -0
  18. data/lib/vcr.rb +48 -4
  19. data/lib/vcr/config.rb +11 -2
  20. data/lib/vcr/extensions/net_http.rb +4 -0
  21. data/lib/vcr/http_stubbing_adapters/common.rb +35 -4
  22. data/lib/vcr/http_stubbing_adapters/faraday.rb +80 -0
  23. data/lib/vcr/http_stubbing_adapters/typhoeus.rb +1 -1
  24. data/lib/vcr/http_stubbing_adapters/webmock.rb +18 -16
  25. data/lib/vcr/middleware/cassette_arguments.rb +18 -0
  26. data/lib/vcr/middleware/common.rb +22 -0
  27. data/lib/vcr/middleware/faraday.rb +79 -0
  28. data/lib/vcr/middleware/rack.rb +13 -0
  29. data/lib/vcr/request_matcher.rb +16 -1
  30. data/lib/vcr/version.rb +1 -1
  31. data/spec/config_spec.rb +27 -2
  32. data/spec/fixtures/1.9.1/fake_example.com_responses.yml +0 -29
  33. data/spec/fixtures/1.9.1/match_requests_on.yml +2 -2
  34. data/spec/fixtures/not_1.9.1/fake_example.com_responses.yml +0 -29
  35. data/spec/http_stubbing_adapters/faraday_spec.rb +84 -0
  36. data/spec/middleware/cassette_arguments_spec.rb +32 -0
  37. data/spec/middleware/faraday_spec.rb +52 -0
  38. data/spec/middleware/rack_spec.rb +54 -0
  39. data/spec/monkey_patches.rb +1 -0
  40. data/spec/request_matcher_spec.rb +36 -0
  41. data/spec/spec_helper.rb +10 -0
  42. data/spec/support/http_library_adapters.rb +113 -25
  43. data/spec/support/http_stubbing_adapter.rb +55 -16
  44. data/spec/vcr_spec.rb +92 -4
  45. data/vcr.gemspec +1 -0
  46. metadata +72 -34
@@ -0,0 +1,217 @@
1
+ @exclude-jruby @exclude-rbx
2
+ Feature: EM HTTP Request
3
+
4
+ EM HTTP Request allows multiple simultaneous asynchronous requests.
5
+ (The other HTTP libraries are synchronous). The scenarios below
6
+ demonstrate how VCR can be used with asynchronous em-http requests.
7
+
8
+ Background:
9
+ Given a file named "vcr_setup.rb" with:
10
+ """
11
+ require 'em-http-request'
12
+ require 'vcr_cucumber_helpers'
13
+
14
+ start_sinatra_app(:port => 7777) do
15
+ %w[ foo bar bazz ].each_with_index do |path, index|
16
+ get "/#{path}" do
17
+ sleep index * 0.1 # ensure the async callbacks are invoked in order
18
+ ARGV[0] + ' ' + path
19
+ end
20
+ end
21
+ end
22
+
23
+ require 'vcr'
24
+
25
+ VCR.config do |c|
26
+ c.stub_with :webmock
27
+ c.cassette_library_dir = 'cassettes'
28
+ end
29
+ """
30
+
31
+ Scenario: multiple simultaneous HttpRequest objects
32
+ Given a file named "make_requests.rb" with:
33
+ """
34
+ require 'vcr_setup'
35
+
36
+ VCR.use_cassette('em_http', :record => :new_episodes) do
37
+ EventMachine.run do
38
+ http_array = %w[ foo bar bazz ].map do |p|
39
+ EventMachine::HttpRequest.new("http://localhost:7777/#{p}").get
40
+ end
41
+
42
+ http_array.each do |http|
43
+ http.callback do
44
+ puts http.response
45
+
46
+ if http_array.all? { |h| h.response.to_s != '' }
47
+ EventMachine.stop
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ """
54
+ When I run "ruby make_requests.rb Hello"
55
+ Then the output should contain:
56
+ """
57
+ Hello foo
58
+ Hello bar
59
+ Hello bazz
60
+ """
61
+ And the file "cassettes/em_http.yml" should contain YAML like:
62
+ """
63
+ ---
64
+ - !ruby/struct:VCR::HTTPInteraction
65
+ request: !ruby/struct:VCR::Request
66
+ method: :get
67
+ uri: http://localhost:7777/foo
68
+ body:
69
+ headers:
70
+ response: !ruby/struct:VCR::Response
71
+ status: !ruby/struct:VCR::ResponseStatus
72
+ code: 200
73
+ message: OK
74
+ headers:
75
+ content-type:
76
+ - text/html;charset=utf-8
77
+ content-length:
78
+ - "9"
79
+ body: Hello foo
80
+ http_version: "1.1"
81
+ - !ruby/struct:VCR::HTTPInteraction
82
+ request: !ruby/struct:VCR::Request
83
+ method: :get
84
+ uri: http://localhost:7777/bar
85
+ body:
86
+ headers:
87
+ response: !ruby/struct:VCR::Response
88
+ status: !ruby/struct:VCR::ResponseStatus
89
+ code: 200
90
+ message: OK
91
+ headers:
92
+ content-type:
93
+ - text/html;charset=utf-8
94
+ content-length:
95
+ - "9"
96
+ body: Hello bar
97
+ http_version: "1.1"
98
+ - !ruby/struct:VCR::HTTPInteraction
99
+ request: !ruby/struct:VCR::Request
100
+ method: :get
101
+ uri: http://localhost:7777/bazz
102
+ body:
103
+ headers:
104
+ response: !ruby/struct:VCR::Response
105
+ status: !ruby/struct:VCR::ResponseStatus
106
+ code: 200
107
+ message: OK
108
+ headers:
109
+ content-type:
110
+ - text/html;charset=utf-8
111
+ content-length:
112
+ - "10"
113
+ body: Hello bazz
114
+ http_version: "1.1"
115
+ """
116
+
117
+ When I run "ruby make_requests.rb Goodbye"
118
+ Then the output should contain:
119
+ """
120
+ Hello foo
121
+ Hello bar
122
+ Hello bazz
123
+ """
124
+
125
+ Scenario: MultiRequest
126
+ Given a file named "make_requests.rb" with:
127
+ """
128
+ require 'vcr_setup'
129
+
130
+ VCR.use_cassette('em_http', :record => :new_episodes) do
131
+ EventMachine.run do
132
+ multi = EventMachine::MultiRequest.new
133
+
134
+ %w[ foo bar bazz ].each do |path|
135
+ multi.add(EventMachine::HttpRequest.new("http://localhost:7777/#{path}").get)
136
+ end
137
+
138
+ multi.callback do
139
+ multi.responses[:succeeded].each do |http|
140
+ puts http.response
141
+ end
142
+ EventMachine.stop
143
+ end
144
+ end
145
+ end
146
+ """
147
+ When I run "ruby make_requests.rb Hello"
148
+ Then the output should contain:
149
+ """
150
+ Hello foo
151
+ Hello bar
152
+ Hello bazz
153
+ """
154
+ And the file "cassettes/em_http.yml" should contain YAML like:
155
+ """
156
+ ---
157
+ - !ruby/struct:VCR::HTTPInteraction
158
+ request: !ruby/struct:VCR::Request
159
+ method: :get
160
+ uri: http://localhost:7777/foo
161
+ body:
162
+ headers:
163
+ response: !ruby/struct:VCR::Response
164
+ status: !ruby/struct:VCR::ResponseStatus
165
+ code: 200
166
+ message: OK
167
+ headers:
168
+ content-type:
169
+ - text/html;charset=utf-8
170
+ content-length:
171
+ - "9"
172
+ body: Hello foo
173
+ http_version: "1.1"
174
+ - !ruby/struct:VCR::HTTPInteraction
175
+ request: !ruby/struct:VCR::Request
176
+ method: :get
177
+ uri: http://localhost:7777/bar
178
+ body:
179
+ headers:
180
+ response: !ruby/struct:VCR::Response
181
+ status: !ruby/struct:VCR::ResponseStatus
182
+ code: 200
183
+ message: OK
184
+ headers:
185
+ content-type:
186
+ - text/html;charset=utf-8
187
+ content-length:
188
+ - "9"
189
+ body: Hello bar
190
+ http_version: "1.1"
191
+ - !ruby/struct:VCR::HTTPInteraction
192
+ request: !ruby/struct:VCR::Request
193
+ method: :get
194
+ uri: http://localhost:7777/bazz
195
+ body:
196
+ headers:
197
+ response: !ruby/struct:VCR::Response
198
+ status: !ruby/struct:VCR::ResponseStatus
199
+ code: 200
200
+ message: OK
201
+ headers:
202
+ content-type:
203
+ - text/html;charset=utf-8
204
+ content-length:
205
+ - "10"
206
+ body: Hello bazz
207
+ http_version: "1.1"
208
+ """
209
+
210
+ When I run "ruby make_requests.rb Goodbye"
211
+ Then the output should contain:
212
+ """
213
+ Hello foo
214
+ Hello bar
215
+ Hello bazz
216
+ """
217
+
@@ -0,0 +1,89 @@
1
+ Feature: Faraday middleware
2
+
3
+ VCR provides middleware that can be used with Faraday. You can use this as
4
+ an alternative to Faraday's built-in test adapter.
5
+
6
+ To use VCR with Faraday, you should configure VCR to stub with faraday and
7
+ use the provided middleware. The middleware should come before the Faraday
8
+ HTTP adapter. You should provide the middleware with a block where you set
9
+ the cassette name and options. If your block accepts two arguments, the
10
+ env hash will be yielded, allowing you to dynamically set the cassette name
11
+ and options based on the request environment.
12
+
13
+ Background:
14
+ Given a file named "env_setup.rb" with:
15
+ """
16
+ require 'vcr_cucumber_helpers'
17
+
18
+ request_count = 0
19
+ start_sinatra_app(:port => 7777) do
20
+ get('/:path') { "Hello #{params[:path]} #{request_count += 1}" }
21
+ end
22
+
23
+ require 'vcr'
24
+
25
+ VCR.config do |c|
26
+ c.cassette_library_dir = 'cassettes'
27
+ c.stub_with :faraday
28
+ end
29
+ """
30
+
31
+ Scenario Outline: Use Faraday middleware
32
+ Given a file named "faraday_example.rb" with:
33
+ """
34
+ require 'env_setup'
35
+
36
+ conn = Faraday::Connection.new(:url => 'http://localhost:7777') do |builder|
37
+ builder.use VCR::Middleware::Faraday do |cassette|
38
+ cassette.name 'faraday_example'
39
+ cassette.options :record => :new_episodes
40
+ end
41
+
42
+ builder.adapter :<adapter>
43
+ end
44
+
45
+ puts "Response 1: #{conn.get('/foo').body}"
46
+ puts "Response 2: #{conn.get('/foo').body}"
47
+ """
48
+ When I run "ruby faraday_example.rb"
49
+ Then the output should contain:
50
+ """
51
+ Response 1: Hello foo 1
52
+ Response 2: Hello foo 1
53
+ """
54
+ And the file "cassettes/faraday_example.yml" should contain "body: Hello foo 1"
55
+
56
+ Examples:
57
+ | adapter |
58
+ | net_http |
59
+ | typhoeus |
60
+
61
+ Scenario: Set cassette name based on faraday env
62
+ Given a file named "faraday_example.rb" with:
63
+ """
64
+ require 'env_setup'
65
+
66
+ conn = Faraday::Connection.new(:url => 'http://localhost:7777') do |builder|
67
+ builder.use VCR::Middleware::Faraday do |cassette, env|
68
+ cassette.name env[:url].path.sub(/^\//, '')
69
+ cassette.options :record => :new_episodes
70
+ end
71
+
72
+ builder.adapter :net_http
73
+ end
74
+
75
+ puts "Response 1: #{conn.get('/foo').body}"
76
+ puts "Response 2: #{conn.get('/foo').body}"
77
+ puts "Response 3: #{conn.get('/bar').body}"
78
+ puts "Response 4: #{conn.get('/bar').body}"
79
+ """
80
+ When I run "ruby faraday_example.rb"
81
+ Then the output should contain:
82
+ """
83
+ Response 1: Hello foo 1
84
+ Response 2: Hello foo 1
85
+ Response 3: Hello bar 2
86
+ Response 4: Hello bar 2
87
+ """
88
+ And the file "cassettes/foo.yml" should contain "body: Hello foo 1"
89
+ And the file "cassettes/bar.yml" should contain "body: Hello bar 2"
@@ -0,0 +1,95 @@
1
+ Feature: rack middleware
2
+
3
+ VCR provides a rack middleware that uses a cassette for the duration of
4
+ a request. Simply provide VCR::Middleware::Rack with a block that sets
5
+ the cassette name and options. You can set these based on the rack env
6
+ if your block accepts two arguments.
7
+
8
+ There useful in a couple different ways:
9
+
10
+ * In a rails app, you could use this to log all HTTP API calls made by
11
+ the rails app (using the :all record mode). Of course, this will only
12
+ record HTTP API calls made in the request-response cycle--API calls that
13
+ are offloaded to a background job will not be logged.
14
+ * This can be used as middleware in a simple rack HTTP proxy, to record the
15
+ and replay the proxied requests.
16
+
17
+ Background:
18
+ Given a file named "remote_server.rb" with:
19
+ """
20
+ require 'vcr_cucumber_helpers'
21
+
22
+ request_count = 0
23
+ start_sinatra_app(:port => 7777) do
24
+ get('/:path') { "Hello #{params[:path]} #{request_count += 1}" }
25
+ end
26
+ """
27
+ And a file named "client.rb" with:
28
+ """
29
+ require 'remote_server'
30
+ require 'proxy_server'
31
+ require 'cgi'
32
+
33
+ url = URI.parse("http://localhost:8888?url=#{CGI.escape('http://localhost:7777/foo')}")
34
+
35
+ puts "Response 1: #{Net::HTTP.get_response(url).body}"
36
+ puts "Response 2: #{Net::HTTP.get_response(url).body}"
37
+ """
38
+ And the directory "cassettes" does not exist
39
+
40
+ Scenario: Use VCR rack middleware to record HTTP responses for a simple rack proxy app
41
+ Given a file named "proxy_server.rb" with:
42
+ """
43
+ require 'vcr'
44
+
45
+ start_sinatra_app(:port => 8888) do
46
+ use VCR::Middleware::Rack do |cassette|
47
+ cassette.name 'proxied'
48
+ cassette.options :record => :new_episodes
49
+ end
50
+
51
+ get('/') { Net::HTTP.get_response(URI.parse(params[:url])).body }
52
+ end
53
+
54
+ VCR.config do |c|
55
+ c.cassette_library_dir = 'cassettes'
56
+ c.stub_with :fakeweb
57
+ c.allow_http_connections_when_no_cassette = true
58
+ end
59
+ """
60
+ When I run "ruby client.rb"
61
+ Then the output should contain:
62
+ """
63
+ Response 1: Hello foo 1
64
+ Response 2: Hello foo 1
65
+ """
66
+ And the file "cassettes/proxied.yml" should contain "body: Hello foo 1"
67
+
68
+ Scenario: Set cassette name based on rack request env
69
+ Given a file named "proxy_server.rb" with:
70
+ """
71
+ require 'vcr'
72
+
73
+ start_sinatra_app(:port => 8888) do
74
+ use VCR::Middleware::Rack do |cassette, env|
75
+ cassette.name env['SERVER_NAME']
76
+ cassette.options :record => :new_episodes
77
+ end
78
+
79
+ get('/') { Net::HTTP.get_response(URI.parse(params[:url])).body }
80
+ end
81
+
82
+ VCR.config do |c|
83
+ c.cassette_library_dir = 'cassettes'
84
+ c.stub_with :fakeweb
85
+ c.allow_http_connections_when_no_cassette = true
86
+ end
87
+ """
88
+ When I run "ruby client.rb"
89
+ Then the output should contain:
90
+ """
91
+ Response 1: Hello foo 1
92
+ Response 2: Hello foo 1
93
+ """
94
+ And the file "cassettes/localhost.yml" should contain "body: Hello foo 1"
95
+
@@ -80,3 +80,10 @@ Then /^the file "([^"]*)" should contain each of these:$/ do |file_name, table|
80
80
  end
81
81
  end
82
82
 
83
+ Then /^the cassette "([^"]*)" should have the following response bodies:$/ do |file, table|
84
+ interactions = in_current_dir { YAML.load_file(file) }
85
+ actual_response_bodies = interactions.map { |i| i.response.body }
86
+ expected_response_bodies = table.raw.flatten
87
+ actual_response_bodies.should =~ expected_response_bodies
88
+ end
89
+
data/lib/vcr.rb CHANGED
@@ -16,11 +16,25 @@ module VCR
16
16
 
17
17
  LOCALHOST_ALIASES = %w( localhost 127.0.0.1 0.0.0.0 )
18
18
 
19
+ class CassetteInUseError < StandardError; end
20
+ class TurnedOffError < StandardError; end
21
+
22
+ module Middleware
23
+ autoload :CassetteArguments, 'vcr/middleware/cassette_arguments'
24
+ autoload :Common, 'vcr/middleware/common'
25
+ autoload :Faraday, 'vcr/middleware/faraday'
26
+ autoload :Rack, 'vcr/middleware/rack'
27
+ end
28
+
19
29
  def current_cassette
20
30
  cassettes.last
21
31
  end
22
32
 
23
33
  def insert_cassette(*args)
34
+ unless turned_on?
35
+ raise TurnedOffError.new("VCR is turned off. You must turn it on before you can insert a cassette.")
36
+ end
37
+
24
38
  cassette = Cassette.new(*args)
25
39
  cassettes.push(cassette)
26
40
  cassette
@@ -32,11 +46,12 @@ module VCR
32
46
  cassette
33
47
  end
34
48
 
35
- def use_cassette(*args)
36
- insert_cassette(*args)
49
+ def use_cassette(*args, &block)
50
+ cassette = insert_cassette(*args)
37
51
 
38
52
  begin
39
- yield
53
+ # yield the cassette if the block accepts an argument or variable args
54
+ block.arity == 0 ? block.call : block.call(cassette)
40
55
  ensure
41
56
  eject_cassette
42
57
  end
@@ -45,7 +60,7 @@ module VCR
45
60
  def config
46
61
  yield VCR::Config
47
62
  http_stubbing_adapter.check_version!
48
- http_stubbing_adapter.http_connections_allowed = false
63
+ http_stubbing_adapter.set_http_connections_allowed_to_default
49
64
  http_stubbing_adapter.ignore_localhost = VCR::Config.ignore_localhost?
50
65
  end
51
66
 
@@ -65,6 +80,7 @@ module VCR
65
80
  when :fakeweb; HttpStubbingAdapters::FakeWeb
66
81
  when :webmock; HttpStubbingAdapters::WebMock
67
82
  when :typhoeus; HttpStubbingAdapters::Typhoeus
83
+ when :faraday; HttpStubbingAdapters::Faraday
68
84
  else raise ArgumentError.new("#{lib.inspect} is not a supported HTTP stubbing library.")
69
85
  end
70
86
  end
@@ -82,6 +98,34 @@ module VCR
82
98
  cassette.record_http_interaction(interaction)
83
99
  end
84
100
 
101
+ def turned_off
102
+ turn_off!
103
+
104
+ begin
105
+ yield
106
+ ensure
107
+ turn_on!
108
+ end
109
+ end
110
+
111
+ def turn_off!
112
+ if VCR.current_cassette
113
+ raise CassetteInUseError.new("A VCR cassette is currently in use. You must eject it before you can turn VCR off.")
114
+ end
115
+
116
+ VCR.http_stubbing_adapter.http_connections_allowed = true
117
+ @turned_off = true
118
+ end
119
+
120
+ def turn_on!
121
+ VCR.http_stubbing_adapter.set_http_connections_allowed_to_default
122
+ @turned_off = false
123
+ end
124
+
125
+ def turned_on?
126
+ !@turned_off
127
+ end
128
+
85
129
  private
86
130
 
87
131
  def cassettes