vcr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.document +5 -0
  2. data/.gitignore +22 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +156 -0
  5. data/Rakefile +60 -0
  6. data/VERSION +1 -0
  7. data/features/fixtures/vcr_cassettes/1.8.6/cucumber_tags/replay_cassette1.yml +43 -0
  8. data/features/fixtures/vcr_cassettes/1.8.6/cucumber_tags/replay_cassette2.yml +45 -0
  9. data/features/fixtures/vcr_cassettes/1.8.6/nested_replay_cassette.yml +23 -0
  10. data/features/fixtures/vcr_cassettes/1.8.6/not_the_real_response.yml +43 -0
  11. data/features/fixtures/vcr_cassettes/1.8.7/cucumber_tags/replay_cassette1.yml +43 -0
  12. data/features/fixtures/vcr_cassettes/1.8.7/cucumber_tags/replay_cassette2.yml +45 -0
  13. data/features/fixtures/vcr_cassettes/1.8.7/nested_replay_cassette.yml +23 -0
  14. data/features/fixtures/vcr_cassettes/1.8.7/not_the_real_response.yml +43 -0
  15. data/features/fixtures/vcr_cassettes/1.9.1/cucumber_tags/replay_cassette1.yml +43 -0
  16. data/features/fixtures/vcr_cassettes/1.9.1/cucumber_tags/replay_cassette2.yml +61 -0
  17. data/features/fixtures/vcr_cassettes/1.9.1/nested_replay_cassette.yml +31 -0
  18. data/features/fixtures/vcr_cassettes/1.9.1/not_the_real_response.yml +43 -0
  19. data/features/record_response.feature +55 -0
  20. data/features/replay_recorded_response.feature +53 -0
  21. data/features/step_definitions/vcr_steps.rb +88 -0
  22. data/features/support/env.rb +55 -0
  23. data/lib/vcr.rb +48 -0
  24. data/lib/vcr/cassette.rb +89 -0
  25. data/lib/vcr/config.rb +19 -0
  26. data/lib/vcr/cucumber_tags.rb +38 -0
  27. data/lib/vcr/fake_web_extensions.rb +18 -0
  28. data/lib/vcr/net_http_extensions.rb +39 -0
  29. data/lib/vcr/recorded_response.rb +4 -0
  30. data/spec/cassette_spec.rb +185 -0
  31. data/spec/config_spec.rb +27 -0
  32. data/spec/cucumber_tags_spec.rb +71 -0
  33. data/spec/fake_web_extensions_spec.rb +26 -0
  34. data/spec/fixtures/1.8.6/cassette_spec/example.yml +78 -0
  35. data/spec/fixtures/1.8.7/cassette_spec/example.yml +78 -0
  36. data/spec/fixtures/1.9.1/cassette_spec/example.yml +77 -0
  37. data/spec/net_http_extensions_spec.rb +40 -0
  38. data/spec/recorded_response_spec.rb +25 -0
  39. data/spec/spec.opts +2 -0
  40. data/spec/spec_helper.rb +22 -0
  41. data/spec/support/temp_cache_dir.rb +16 -0
  42. data/spec/vcr_spec.rb +95 -0
  43. metadata +154 -0
@@ -0,0 +1,55 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'vcr'
3
+
4
+ begin
5
+ require 'ruby-debug'
6
+ Debugger.start
7
+ Debugger.settings[:autoeval] = true if Debugger.respond_to?(:settings)
8
+ rescue LoadError
9
+ # ruby-debug wasn't available so neither can the debugging be
10
+ end
11
+
12
+ require 'spec/expectations'
13
+
14
+ VCR.config do |c|
15
+ c.cache_dir = File.join(File.dirname(__FILE__), '..', 'fixtures', 'vcr_cassettes', RUBY_VERSION)
16
+ end
17
+
18
+ VCR.module_eval do
19
+ def self.completed_cucumber_scenarios
20
+ @completed_cucumber_scenarios ||= []
21
+ end
22
+
23
+ class << self
24
+ attr_accessor :current_cucumber_scenario
25
+ end
26
+ end
27
+
28
+ After do |scenario|
29
+ VCR.completed_cucumber_scenarios << scenario
30
+ end
31
+
32
+ Before do |scenario|
33
+ VCR.current_cucumber_scenario = scenario
34
+ temp_dir = File.join(VCR::Config.cache_dir, 'temp')
35
+ FileUtils.rm_rf(temp_dir) if File.exist?(temp_dir)
36
+ end
37
+
38
+ Before('@copy_not_the_real_response_to_temp') do
39
+ orig_file = File.join(VCR::Config.cache_dir, 'not_the_real_response.yml')
40
+ temp_file = File.join(VCR::Config.cache_dir, 'temp', 'not_the_real_response.yml')
41
+ FileUtils.mkdir_p(File.join(VCR::Config.cache_dir, 'temp'))
42
+ FileUtils.cp orig_file, temp_file
43
+ end
44
+
45
+ at_exit do
46
+ %w(record_cassette1 record_cassette2).each do |tag|
47
+ cache_file = File.join(VCR::Config.cache_dir, 'cucumber_tags', "#{tag}.yml")
48
+ FileUtils.rm_rf(cache_file) if File.exist?(cache_file)
49
+ end
50
+ end
51
+
52
+ VCR.cucumber_tags do |t|
53
+ t.tags '@record_cassette1', '@record_cassette2', :record => :unregistered
54
+ t.tags '@replay_cassette1', '@replay_cassette2', :record => :none
55
+ end
@@ -0,0 +1,48 @@
1
+ require 'vcr/cassette'
2
+ require 'vcr/config'
3
+ require 'vcr/cucumber_tags'
4
+ require 'vcr/fake_web_extensions'
5
+ require 'vcr/net_http_extensions'
6
+ require 'vcr/recorded_response'
7
+
8
+ module VCR
9
+ extend self
10
+
11
+ def current_cassette
12
+ cassettes.last
13
+ end
14
+
15
+ def create_cassette!(*args)
16
+ cassette = Cassette.new(*args)
17
+ cassettes.push(cassette)
18
+ cassette
19
+ end
20
+
21
+ def destroy_cassette!
22
+ cassette = cassettes.pop
23
+ cassette.destroy! if cassette
24
+ cassette
25
+ end
26
+
27
+ def with_cassette(*args)
28
+ create_cassette!(*args)
29
+ yield
30
+ ensure
31
+ destroy_cassette!
32
+ end
33
+
34
+ def config
35
+ yield VCR::Config
36
+ end
37
+
38
+ def cucumber_tags(&block)
39
+ main_object = eval('self', block.binding)
40
+ yield VCR::CucumberTags.new(main_object)
41
+ end
42
+
43
+ private
44
+
45
+ def cassettes
46
+ @cassettes ||= []
47
+ end
48
+ end
@@ -0,0 +1,89 @@
1
+ require 'fileutils'
2
+ require 'yaml'
3
+
4
+ module VCR
5
+ class Cassette
6
+ VALID_RECORD_MODES = [:all, :none, :unregistered].freeze
7
+
8
+ attr_reader :name, :record_mode
9
+
10
+ def initialize(name, options = {})
11
+ @name = name
12
+ @record_mode = options[:record] || VCR::Config.default_cassette_record_mode
13
+ self.class.raise_error_unless_valid_record_mode(record_mode)
14
+ set_fakeweb_allow_net_connect
15
+ load_recorded_responses
16
+ end
17
+
18
+ def destroy!
19
+ write_recorded_responses_to_disk
20
+ deregister_original_recorded_responses
21
+ restore_fakeweb_allow_net_conect
22
+ end
23
+
24
+ def recorded_responses
25
+ @recorded_responses ||= []
26
+ end
27
+
28
+ def store_recorded_response!(recorded_response)
29
+ recorded_responses << recorded_response
30
+ end
31
+
32
+ def cache_file
33
+ File.join(VCR::Config.cache_dir, "#{name.to_s.gsub(/[^\w\-\/]+/, '_')}.yml") if VCR::Config.cache_dir
34
+ end
35
+
36
+ def self.raise_error_unless_valid_record_mode(record_mode)
37
+ unless VALID_RECORD_MODES.include?(record_mode)
38
+ raise ArgumentError.new("#{record_mode} is not a valid cassette record mode. Valid options are: #{VALID_RECORD_MODES.inspect}")
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def new_recorded_responses
45
+ recorded_responses - @original_recorded_responses
46
+ end
47
+
48
+ def should_allow_net_connect?
49
+ [:unregistered, :all].include?(record_mode)
50
+ end
51
+
52
+ def set_fakeweb_allow_net_connect
53
+ @orig_fakeweb_allow_connect = FakeWeb.allow_net_connect?
54
+ FakeWeb.allow_net_connect = should_allow_net_connect?
55
+ end
56
+
57
+ def restore_fakeweb_allow_net_conect
58
+ FakeWeb.allow_net_connect = @orig_fakeweb_allow_connect
59
+ end
60
+
61
+ def load_recorded_responses
62
+ @original_recorded_responses = []
63
+ return if record_mode == :all
64
+
65
+ if cache_file
66
+ @original_recorded_responses = File.open(cache_file, 'r') { |f| YAML.load(f.read) } if File.exist?(cache_file)
67
+ recorded_responses.replace(@original_recorded_responses)
68
+ end
69
+
70
+ recorded_responses.each do |rr|
71
+ FakeWeb.register_uri(rr.method, rr.uri, { :response => rr.response })
72
+ end
73
+ end
74
+
75
+ def write_recorded_responses_to_disk
76
+ if VCR::Config.cache_dir && new_recorded_responses.size > 0
77
+ directory = File.dirname(cache_file)
78
+ FileUtils.mkdir_p directory unless File.exist?(directory)
79
+ File.open(cache_file, 'w') { |f| f.write recorded_responses.to_yaml }
80
+ end
81
+ end
82
+
83
+ def deregister_original_recorded_responses
84
+ @original_recorded_responses.each do |rr|
85
+ FakeWeb.remove_from_registry(rr.method, rr.uri)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,19 @@
1
+ require 'fileutils'
2
+
3
+ module VCR
4
+ class Config
5
+ class << self
6
+ attr_reader :cache_dir
7
+ def cache_dir=(cache_dir)
8
+ @cache_dir = cache_dir
9
+ FileUtils.mkdir_p(cache_dir) if cache_dir
10
+ end
11
+
12
+ attr_reader :default_cassette_record_mode
13
+ def default_cassette_record_mode=(default_cassette_record_mode)
14
+ VCR::Cassette.raise_error_unless_valid_record_mode(default_cassette_record_mode)
15
+ @default_cassette_record_mode = default_cassette_record_mode
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,38 @@
1
+ module VCR
2
+ class CucumberTags
3
+ class << self
4
+ def tags
5
+ @tags.dup
6
+ end
7
+
8
+ def add_tag(tag)
9
+ @tags << tag
10
+ end
11
+ end
12
+
13
+ @tags = []
14
+ def initialize(main_object)
15
+ @main_object = main_object
16
+ end
17
+
18
+ def tags(*tag_names)
19
+ options = tag_names.last.is_a?(::Hash) ? tag_names.pop : {}
20
+ tag_names.each do |tag_name|
21
+ tag_name = "@#{tag_name}" unless tag_name =~ /^@/
22
+ cassette_name = "cucumber_tags/#{tag_name.gsub(/\A@/, '')}"
23
+
24
+ @main_object.instance_eval do
25
+ Before(tag_name) do
26
+ VCR.create_cassette!(cassette_name, options)
27
+ end
28
+
29
+ After(tag_name) do
30
+ VCR.destroy_cassette!
31
+ end
32
+ end
33
+ self.class.add_tag(tag_name)
34
+ end
35
+ end
36
+ alias :tag :tags
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ require 'fake_web'
2
+
3
+ module FakeWeb
4
+ def self.remove_from_registry(method, url)
5
+ Registry.instance.remove(method, url)
6
+ end
7
+
8
+ class Registry #:nodoc:
9
+ def remove(method, url)
10
+ uri_map.delete_if do |uri, method_hash|
11
+ if normalize_uri(uri) == normalize_uri(url)
12
+ method_hash.delete(method)
13
+ method_hash.empty? # there's no point in keeping this entry in the uri map if its method hash is empty...
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ require 'net/http'
2
+
3
+ module Net
4
+ class HTTP
5
+ def request_with_vcr(request, body = nil, &block)
6
+ response = request_without_vcr(request, body, &block)
7
+ __store_response_with_vcr__(response, request)
8
+ response
9
+ end
10
+ alias_method :request_without_vcr, :request
11
+ alias_method :request, :request_with_vcr
12
+
13
+ private
14
+
15
+ def __store_response_with_vcr__(response, request)
16
+ if cassette = VCR.current_cassette
17
+ # Copied from: http://github.com/chrisk/fakeweb/blob/fakeweb-1.2.8/lib/fake_web/ext/net_http.rb#L39-52
18
+ protocol = use_ssl? ? "https" : "http"
19
+
20
+ path = request.path
21
+ path = URI.parse(request.path).request_uri if request.path =~ /^http/
22
+
23
+ if request["authorization"] =~ /^Basic /
24
+ userinfo = FakeWeb::Utility.decode_userinfo_from_header(request["authorization"])
25
+ userinfo = FakeWeb::Utility.encode_unsafe_chars_in_userinfo(userinfo) + "@"
26
+ else
27
+ userinfo = ""
28
+ end
29
+
30
+ uri = "#{protocol}://#{userinfo}#{self.address}:#{self.port}#{path}"
31
+ method = request.method.downcase.to_sym
32
+
33
+ unless FakeWeb.registered_uri?(method, uri)
34
+ cassette.store_recorded_response!(VCR::RecordedResponse.new(method, uri, response))
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,4 @@
1
+ module VCR
2
+ class RecordedResponse < Struct.new(:method, :uri, :response)
3
+ end
4
+ end
@@ -0,0 +1,185 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe VCR::Cassette do
4
+ before(:all) do
5
+ @orig_default_cassette_record_mode = VCR::Config.default_cassette_record_mode
6
+ VCR::Config.default_cassette_record_mode = :unregistered
7
+ end
8
+
9
+ after(:all) do
10
+ VCR::Config.default_cassette_record_mode = :unregistered
11
+ end
12
+
13
+ before(:each) do
14
+ FakeWeb.clean_registry
15
+ end
16
+
17
+ describe '#cache_file' do
18
+ temp_dir File.expand_path(File.dirname(__FILE__) + '/fixtures/cache_file'), :assign_to_cache_dir => true
19
+
20
+ it 'combines the cache_dir with the cassette name' do
21
+ cassette = VCR::Cassette.new('the_cache_file')
22
+ cassette.cache_file.should == File.join(VCR::Config.cache_dir, 'the_cache_file.yml')
23
+ end
24
+
25
+ it 'strips out disallowed characters so that it is a valid file name with no spaces' do
26
+ cassette = VCR::Cassette.new("\nthis \t! is-the_13212_file name")
27
+ cassette.cache_file.should =~ /#{Regexp.escape('_this_is-the_13212_file_name.yml')}$/
28
+ end
29
+
30
+ it 'keeps any path separators' do
31
+ cassette = VCR::Cassette.new("dir/file_name")
32
+ cassette.cache_file.should =~ /#{Regexp.escape('dir/file_name.yml')}$/
33
+ end
34
+
35
+ it 'returns nil if the cache_dir is not set' do
36
+ VCR::Config.cache_dir = nil
37
+ cassette = VCR::Cassette.new('the_cache_file')
38
+ cassette.cache_file.should be_nil
39
+ end
40
+ end
41
+
42
+ describe '#store_recorded_response!' do
43
+ it 'adds the recorded response to #recorded_responses' do
44
+ recorded_response = VCR::RecordedResponse.new(:get, 'http://example.com', :response)
45
+ cassette = VCR::Cassette.new(:test_cassette)
46
+ cassette.recorded_responses.should == []
47
+ cassette.store_recorded_response!(recorded_response)
48
+ cassette.recorded_responses.should == [recorded_response]
49
+ end
50
+ end
51
+
52
+ describe 'on creation' do
53
+ it "raises an error if given an invalid record mode" do
54
+ lambda { VCR::Cassette.new(:test, :record => :not_a_record_mode) }.should raise_error(ArgumentError)
55
+ end
56
+
57
+ VCR::Cassette::VALID_RECORD_MODES.each do |mode|
58
+ it "defaults the record mode to #{mode} when VCR::Config.default_cassette_record_mode is #{mode}" do
59
+ VCR::Config.default_cassette_record_mode = mode
60
+ cassette = VCR::Cassette.new(:test)
61
+ cassette.record_mode.should == mode
62
+ end
63
+ end
64
+
65
+ { :unregistered => true, :all => true, :none => false }.each do |record_mode, allow_fakeweb_connect|
66
+ it "sets FakeWeb.allow_net_connect to #{allow_fakeweb_connect} when the record mode is #{record_mode}" do
67
+ FakeWeb.allow_net_connect = !allow_fakeweb_connect
68
+ VCR::Cassette.new(:name, :record => record_mode)
69
+ FakeWeb.allow_net_connect?.should == allow_fakeweb_connect
70
+ end
71
+ end
72
+
73
+ { :unregistered => true, :all => false, :none => true }.each do |record_mode, load_responses|
74
+ it "#{load_responses ? 'loads' : 'does not load'} the recorded responses from the cached yml file when the record mode is #{record_mode}" do
75
+ VCR::Config.cache_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{RUBY_VERSION}/cassette_spec")
76
+ cassette = VCR::Cassette.new('example', :record => record_mode)
77
+
78
+ if load_responses
79
+ cassette.should have(2).recorded_responses
80
+
81
+ rr1, rr2 = cassette.recorded_responses.first, cassette.recorded_responses.last
82
+
83
+ rr1.method.should == :get
84
+ rr1.uri.should == 'http://example.com:80/'
85
+ rr1.response.body.should =~ /You have reached this web page by typing.+example\.com/
86
+
87
+ rr2.method.should == :get
88
+ rr2.uri.should == 'http://example.com:80/foo'
89
+ rr2.response.body.should =~ /foo was not found on this server/
90
+ else
91
+ cassette.should have(0).recorded_responses
92
+ end
93
+ end
94
+
95
+ it "#{load_responses ? 'registers' : 'does not register'} the recorded responses with fakeweb when the record mode is #{record_mode}" do
96
+ VCR::Config.cache_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{RUBY_VERSION}/cassette_spec")
97
+ cassette = VCR::Cassette.new('example', :record => record_mode)
98
+
99
+ rr1 = FakeWeb.response_for(:get, "http://example.com")
100
+ rr2 = FakeWeb.response_for(:get, "http://example.com/foo")
101
+
102
+ if load_responses
103
+ rr1.should_not be_nil
104
+ rr2.should_not be_nil
105
+ rr1.body.should =~ /You have reached this web page by typing.+example\.com/
106
+ rr2.body.should =~ /foo was not found on this server/
107
+ else
108
+ rr1.should be_nil
109
+ rr2.should be_nil
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ describe '#destroy!' do
116
+ temp_dir File.expand_path(File.dirname(__FILE__) + '/fixtures/cassette_spec_destroy'), :assign_to_cache_dir => true
117
+
118
+ [true, false].each do |orig_allow_net_connect|
119
+ it "resets FakeWeb.allow_net_connect #{orig_allow_net_connect} if it was originally #{orig_allow_net_connect}" do
120
+ FakeWeb.allow_net_connect = orig_allow_net_connect
121
+ cassette = VCR::Cassette.new(:name)
122
+ cassette.destroy!
123
+ FakeWeb.allow_net_connect?.should == orig_allow_net_connect
124
+ end
125
+ end
126
+
127
+ it "writes the recorded responses to disk as yaml" do
128
+ recorded_responses = [
129
+ VCR::RecordedResponse.new(:get, 'http://example.com', :get_example_dot_come_response),
130
+ VCR::RecordedResponse.new(:post, 'http://example.com', :post_example_dot_come_response),
131
+ VCR::RecordedResponse.new(:get, 'http://google.com', :get_google_dot_come_response)
132
+ ]
133
+
134
+ cassette = VCR::Cassette.new(:destroy_test)
135
+ cassette.stub!(:recorded_responses).and_return(recorded_responses)
136
+
137
+ lambda { cassette.destroy! }.should change { File.exist?(cassette.cache_file) }.from(false).to(true)
138
+ saved_recorded_responses = File.open(cassette.cache_file, "r") { |f| YAML.load(f.read) }
139
+ saved_recorded_responses.should == recorded_responses
140
+ end
141
+
142
+ it "writes the recorded responses a subdirectory if the cassette name includes a directory" do
143
+ recorded_responses = [VCR::RecordedResponse.new(:get, 'http://example.com', :get_example_dot_come_response)]
144
+ cassette = VCR::Cassette.new('subdirectory/test_cassette')
145
+ cassette.stub!(:recorded_responses).and_return(recorded_responses)
146
+
147
+ lambda { cassette.destroy! }.should change { File.exist?(cassette.cache_file) }.from(false).to(true)
148
+ saved_recorded_responses = File.open(cassette.cache_file, "r") { |f| YAML.load(f.read) }
149
+ saved_recorded_responses.should == recorded_responses
150
+ end
151
+
152
+ it "writes both old and new recorded responses to disk" do
153
+ cache_file = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{RUBY_VERSION}/cassette_spec/example.yml")
154
+ FileUtils.cp cache_file, File.join(@temp_dir, 'previously_recorded_responses.yml')
155
+ cassette = VCR::Cassette.new('previously_recorded_responses')
156
+ cassette.should have(2).recorded_responses
157
+ new_recorded_response = VCR::RecordedResponse.new(:get, 'http://example.com/bar', :example_dot_com_bar_response)
158
+ cassette.store_recorded_response!(new_recorded_response)
159
+ cassette.destroy!
160
+ saved_recorded_responses = File.open(cassette.cache_file, "r") { |f| YAML.load(f.read) }
161
+ saved_recorded_responses.should have(3).recorded_responses
162
+ saved_recorded_responses.last.should == new_recorded_response
163
+ end
164
+ end
165
+
166
+ describe '#destroy for a cassette with previously recorded responses' do
167
+ it "de-registers the recorded responses from fakeweb" do
168
+ VCR::Config.cache_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{RUBY_VERSION}/cassette_spec")
169
+ cassette = VCR::Cassette.new('example', :record => :none)
170
+ FakeWeb.registered_uri?(:get, 'http://example.com').should be_true
171
+ FakeWeb.registered_uri?(:get, 'http://example.com/foo').should be_true
172
+ cassette.destroy!
173
+ FakeWeb.registered_uri?(:get, 'http://example.com').should be_false
174
+ FakeWeb.registered_uri?(:get, 'http://example.com/foo').should be_false
175
+ end
176
+
177
+ it "does not re-write to disk the previously recorded resposes if there are no new ones" do
178
+ VCR::Config.cache_dir = File.expand_path(File.dirname(__FILE__) + "/fixtures/#{RUBY_VERSION}/cassette_spec")
179
+ yaml_file = File.join(VCR::Config.cache_dir, 'example.yml')
180
+ cassette = VCR::Cassette.new('example', :record => :none)
181
+ File.should_not_receive(:open).with(cassette.cache_file, 'w')
182
+ lambda { cassette.destroy! }.should_not change { File.mtime(yaml_file) }
183
+ end
184
+ end
185
+ end