Skip to content

Commit

Permalink
Allow before_playback/record hooks to prevent playback/recording by c…
Browse files Browse the repository at this point in the history
…alling #ignore! on the yielded interaction.
  • Loading branch information
myronmarston committed Jan 12, 2011
1 parent d5e403d commit f69fbee
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 9 deletions.
79 changes: 78 additions & 1 deletion features/configuration/hooks.feature
Expand Up @@ -8,9 +8,14 @@ Feature: Hooks

To use these, call `config.before_record` or `config.before_playback` in
your `VCR.config` block. Provide a block that accepts 0, 1 or 2 arguments.

The first argument, if the block accepts it, will be an HTTP interaction.
Changes you make to the interaction will be reflected in the recording or
playback. The second argument, if the block accepts it, will be the
playback. You can also call `#ignore!` on the interaction to cause VCR to
ignore it. It will not be recorded or played back (depending on which
kind of hook you use).

The second argument, if the block accepts it, will be the
`VCR::Cassette` instance. This may be useful for hooks that you want to
behave differently for different cassettes.

Expand Down Expand Up @@ -82,6 +87,32 @@ Feature: Hooks
Then the file "cassettes/recording_example.yml" should contain "body: Hello World"
And the file "cassettes/recording_example.yml" should not contain "secret"

Scenario: Prevent recording by ignoring interaction in before_record hook
Given a file named "before_record_ignore.rb" with:
"""
require 'vcr_cucumber_helpers'
start_sinatra_app(:port => 7777) do
get('/') { "Hello World" }
end
require 'vcr'
VCR.config do |c|
c.stub_with :fakeweb
c.cassette_library_dir = 'cassettes'
c.before_record { |i| i.ignore! }
end
VCR.use_cassette('recording_example', :record => :new_episodes) do
response = Net::HTTP.get_response('localhost', '/', 7777)
puts "Response: #{response.body}"
end
"""
When I run "ruby before_record_ignore.rb"
Then it should pass with "Response: Hello World"
And the file "cassettes/recording_example.yml" should not exist

Scenario: Change playback with before_playback hook
Given a file named "before_playback_example.rb" with:
"""
Expand All @@ -104,6 +135,52 @@ Feature: Hooks
When I run "ruby before_playback_example.rb"
Then it should pass with "Response: response from before_playback"

Scenario: Prevent playback by ignoring interaction in before_playback hook
Given a previously recorded cassette file "cassettes/localhost.yml" with:
"""
---
- !ruby/struct:VCR::HTTPInteraction
request: !ruby/struct:VCR::Request
method: :get
uri: http://localhost:7777/
body:
headers:
response: !ruby/struct:VCR::Response
status: !ruby/struct:VCR::ResponseStatus
code: 200
message: OK
headers:
content-type:
- text/html;charset=utf-8
content-length:
- "20"
body: recorded response
http_version: "1.1"
"""
And a file named "before_playback_ignore.rb" with:
"""
require 'vcr_cucumber_helpers'
start_sinatra_app(:port => 7777) do
get('/') { "sinatra response" }
end
require 'vcr'
VCR.config do |c|
c.stub_with :fakeweb
c.cassette_library_dir = 'cassettes'
c.before_playback { |i| i.ignore! }
end
VCR.use_cassette('localhost', :record => :new_episodes) do
response = Net::HTTP.get_response('localhost', '/', 7777)
puts "Response: #{response.body}"
end
"""
When I run "ruby before_playback_ignore.rb"
Then it should pass with "Response: sinatra response"

Scenario: Multiple hooks are run in order
Given a file named "multiple_hooks.rb" with:
"""
Expand Down
20 changes: 12 additions & 8 deletions lib/vcr/cassette.rb
Expand Up @@ -145,18 +145,22 @@ def merged_interactions
end

def write_recorded_interactions_to_disk
if VCR::Config.cassette_library_dir && new_recorded_interactions.size > 0
directory = File.dirname(file)
FileUtils.mkdir_p directory unless File.exist?(directory)
interactions = merged_interactions
invoke_hook(:before_record, interactions)
File.open(file, 'w') { |f| f.write interactions.to_yaml }
end
return unless VCR::Config.cassette_library_dir
return if new_recorded_interactions.empty?

interactions = merged_interactions
invoke_hook(:before_record, interactions)
return if interactions.empty?

directory = File.dirname(file)
FileUtils.mkdir_p directory unless File.exist?(directory)
File.open(file, 'w') { |f| f.write interactions.to_yaml }
end

def invoke_hook(type, interactions)
interactions.each do |i|
interactions.delete_if do |i|
VCR::Config.invoke_hook(type, tag, i, self)
i.ignored?
end
end
end
Expand Down
8 changes: 8 additions & 0 deletions lib/vcr/structs.rb
Expand Up @@ -157,5 +157,13 @@ def self.from_net_http_response(response)
class HTTPInteraction < Struct.new(:request, :response)
extend ::Forwardable
def_delegators :request, :uri, :method

def ignore!
@ignored = true
end

def ignored?
@ignored
end
end
end
35 changes: 35 additions & 0 deletions spec/vcr/cassette_spec.rb
Expand Up @@ -227,6 +227,16 @@
cassette.should have(3).recorded_interactions
end

it 'does not playback any interactions that are ignored in a before_playback hook' do
VCR.config do |c|
c.before_playback { |i| i.ignore! if i.request.uri =~ /foo/ }
end

VCR::Config.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec"
cassette = VCR::Cassette.new('example', :record => record_mode)
cassette.should have(2).recorded_interactions
end

it "stubs the recorded requests with the http stubbing adapter" do
VCR::Config.cassette_library_dir = "#{VCR::SPEC_ROOT}/fixtures/#{YAML_SERIALIZATION_VERSION}/cassette_spec"
VCR.http_stubbing_adapter.should_receive(:stub_requests).with([an_instance_of(VCR::HTTPInteraction)]*3, anything)
Expand Down Expand Up @@ -297,6 +307,31 @@
cassette.eject
end

it 'does not record interactions that have been ignored' do
interaction_1 = VCR::HTTPInteraction.new(:request_1, :response_1)
interaction_2 = VCR::HTTPInteraction.new(:request_2, :response_2)

interaction_1.ignore!

cassette = VCR::Cassette.new('test_cassette')
cassette.stub!(:new_recorded_interactions).and_return([interaction_1, interaction_2])
cassette.eject

saved_recorded_interactions = YAML.load_file(cassette.file)
saved_recorded_interactions.should == [interaction_2]
end

it 'does not write the cassette to disk if all interactions have been ignored' do
interaction_1 = VCR::HTTPInteraction.new(:request_1, :response_1)
interaction_1.ignore!

cassette = VCR::Cassette.new('test_cassette')
cassette.stub!(:new_recorded_interactions).and_return([interaction_1])
cassette.eject

File.should_not exist(cassette.file)
end

it "writes the recorded interactions to a subdirectory if the cassette name includes a directory" do
recorded_interactions = [VCR::HTTPInteraction.new(:the_request, :the_response)]
cassette = VCR::Cassette.new('subdirectory/test_cassette')
Expand Down
11 changes: 11 additions & 0 deletions spec/vcr/structs_spec.rb
Expand Up @@ -173,4 +173,15 @@ def instance(body)
instance.send(attr).should == :the_value
end
end

describe '#ignored?' do
it 'returns false by default' do
should_not be_ignored
end

it 'returns true when #ignore! has been called' do
subject.ignore!
should be_ignored
end
end
end

0 comments on commit f69fbee

Please sign in to comment.