vcr 0.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.
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