vcr 1.3.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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