vcr 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +1 -0
  4. data/CHANGELOG.md +29 -2
  5. data/Gemfile +3 -3
  6. data/README.md +55 -11
  7. data/Rakefile +45 -4
  8. data/features/.nav +6 -0
  9. data/features/{README.md → about_the_cucumber_features.md} +1 -1
  10. data/features/cassettes/update_content_length_header.feature +106 -0
  11. data/features/configuration/default_cassette_options.feature +20 -2
  12. data/features/configuration/filter_sensitive_data.feature +154 -0
  13. data/features/getting_started.md +67 -0
  14. data/features/record_modes/all.feature +4 -2
  15. data/features/record_modes/new_episodes.feature +8 -2
  16. data/features/record_modes/none.feature +4 -2
  17. data/features/record_modes/once.feature +92 -0
  18. data/features/step_definitions/cli_steps.rb +18 -0
  19. data/lib/vcr.rb +13 -6
  20. data/lib/vcr/cassette.rb +36 -15
  21. data/lib/vcr/config.rb +14 -2
  22. data/lib/vcr/deprecations/cassette.rb +29 -0
  23. data/lib/vcr/deprecations/config.rb +18 -0
  24. data/lib/vcr/deprecations/http_stubbing_adapters/common.rb +9 -0
  25. data/lib/vcr/deprecations/http_stubbing_adapters/fakeweb.rb +11 -0
  26. data/lib/vcr/http_stubbing_adapters/common.rb +1 -1
  27. data/lib/vcr/http_stubbing_adapters/fakeweb.rb +2 -7
  28. data/lib/vcr/http_stubbing_adapters/multi_object_proxy.rb +1 -1
  29. data/lib/vcr/middleware/common.rb +3 -5
  30. data/lib/vcr/rspec.rb +1 -38
  31. data/lib/vcr/structs/http_interaction.rb +29 -0
  32. data/lib/vcr/structs/request.rb +6 -0
  33. data/lib/vcr/structs/response.rb +4 -0
  34. data/lib/vcr/{cucumber_tags.rb → test_frameworks/cucumber.rb} +10 -2
  35. data/lib/vcr/test_frameworks/rspec.rb +37 -0
  36. data/lib/vcr/util/basic_object.rb +32 -28
  37. data/lib/vcr/{hooks.rb → util/hooks.rb} +3 -6
  38. data/lib/vcr/util/internet_connection.rb +1 -1
  39. data/lib/vcr/util/ping.rb +21 -17
  40. data/lib/vcr/util/variable_args_block_caller.rb +12 -0
  41. data/lib/vcr/util/yaml.rb +11 -0
  42. data/lib/vcr/version.rb +1 -1
  43. data/script/FullBuildRakeFile +7 -0
  44. data/spec/monkey_patches.rb +0 -7
  45. data/spec/spec_helper.rb +40 -8
  46. data/spec/support/http_library_adapters.rb +0 -262
  47. data/spec/support/shared_example_groups/http_library.rb +256 -0
  48. data/spec/support/{http_stubbing_adapter.rb → shared_example_groups/http_stubbing_adapter.rb} +15 -3
  49. data/spec/support/shared_example_groups/ignore_localhost_deprecation.rb +28 -0
  50. data/spec/support/{normalizers.rb → shared_example_groups/normalizers.rb} +3 -3
  51. data/spec/support/{version_checker.rb → shared_example_groups/version_checking.rb} +1 -1
  52. data/spec/vcr/cassette_spec.rb +80 -28
  53. data/spec/vcr/config_spec.rb +55 -8
  54. data/spec/vcr/deprecations/cassette_spec.rb +57 -0
  55. data/spec/vcr/deprecations/config_spec.rb +30 -0
  56. data/spec/vcr/deprecations/http_stubbing_adapters/common_spec.rb +7 -0
  57. data/spec/vcr/deprecations/http_stubbing_adapters/fakeweb_spec.rb +16 -0
  58. data/spec/vcr/extensions/net_http_response_spec.rb +1 -3
  59. data/spec/vcr/extensions/net_http_spec.rb +1 -3
  60. data/spec/vcr/http_stubbing_adapters/fakeweb_spec.rb +1 -4
  61. data/spec/vcr/http_stubbing_adapters/faraday_spec.rb +1 -4
  62. data/spec/vcr/http_stubbing_adapters/typhoeus_spec.rb +1 -4
  63. data/spec/vcr/http_stubbing_adapters/webmock_spec.rb +1 -3
  64. data/spec/vcr/middleware/faraday_spec.rb +4 -4
  65. data/spec/vcr/middleware/rack_spec.rb +4 -4
  66. data/spec/vcr/structs/http_interaction_spec.rb +61 -0
  67. data/spec/vcr/structs/request_spec.rb +20 -2
  68. data/spec/vcr/structs/response_spec.rb +23 -1
  69. data/spec/vcr/structs/response_status_spec.rb +1 -1
  70. data/spec/vcr/{cucumber_tags_spec.rb → test_frameworks/cucumber_spec.rb} +12 -8
  71. data/spec/vcr/{rspec_spec.rb → test_frameworks/rspec_spec.rb} +0 -0
  72. data/spec/vcr/{hooks_spec.rb → util/hooks_spec.rb} +3 -3
  73. data/spec/vcr/util/internet_connection_spec.rb +3 -3
  74. data/spec/vcr_spec.rb +3 -3
  75. data/vcr.gemspec +5 -5
  76. metadata +149 -131
  77. data/Gemfile.lock +0 -155
  78. data/lib/vcr/deprecations.rb +0 -54
  79. data/spec/support/disable_warnings.rb +0 -12
  80. data/spec/support/temp_cassette_library_dir.rb +0 -16
  81. data/spec/support/webmock_macros.rb +0 -14
  82. data/spec/vcr/deprecations_spec.rb +0 -139
@@ -0,0 +1,67 @@
1
+ ## Getting Started
2
+
3
+ ### Install it
4
+
5
+ [sudo] gem install vcr
6
+ [sudo] gem install fakeweb
7
+
8
+ ### Configure it
9
+
10
+ Create a file named `vcr_setup.rb` with content like:
11
+
12
+ require 'vcr'
13
+
14
+ VCR.config do |c|
15
+ c.cassette_library_dir = 'vcr_cassettes'
16
+ c.stub_with :fakeweb
17
+ c.default_cassette_options = { :record => :once }
18
+ end
19
+
20
+ Ensure this file is required by your test suite before any
21
+ of the tests are run.
22
+
23
+ ### Use it
24
+
25
+ Run your tests. Any tests that make HTTP requests using Net::HTTP will
26
+ raise errors like:
27
+
28
+ FakeWeb::NetConnectNotAllowedError: Real HTTP connections are disabled.
29
+ Unregistered request: GET http://example.com/.
30
+ You can use VCR to automatically record this request and replay it later.
31
+ For more details, visit the VCR documentation at: http://relishapp.com/myronmarston/vcr
32
+
33
+ Find one of these tests (preferably one that uses the same HTTP method and
34
+ request URL every time--if not, you'll have to configure the request matcher).
35
+ Wrap the body of it (or at least the code that makes the HTTP request) in a
36
+ `VCR.use_cassette` block:
37
+
38
+ VCR.use_cassette('whatever cassette name you want') do
39
+ # the body of the test would go here...
40
+ end
41
+
42
+ Run this test. It will record the HTTP request to disk as a cassette (a
43
+ test fixture), with content like:
44
+
45
+ ---
46
+ - !ruby/struct:VCR::HTTPInteraction
47
+ request: !ruby/struct:VCR::Request
48
+ method: :get
49
+ uri: http://example.com:80/
50
+ body:
51
+ headers:
52
+ response: !ruby/struct:VCR::Response
53
+ status: !ruby/struct:VCR::ResponseStatus
54
+ code: 200
55
+ message: OK
56
+ headers:
57
+ content-type:
58
+ - text/html;charset=utf-8
59
+ content-length:
60
+ - "26"
61
+ body: This is the response body.
62
+ http_version: "1.1"
63
+
64
+ Disconnect your computer from the internet. Run the test again.
65
+ It should pass since VCR is automatically replaying the recorded
66
+ response when the request is made.
67
+
@@ -1,7 +1,9 @@
1
1
  Feature: :all
2
2
 
3
- The `:all` record mode records all requests and does not
4
- replay any previously recorded responds.
3
+ The `:all` record mode will:
4
+
5
+ - Record new interactions.
6
+ - Never replay previously recorded interactions.
5
7
 
6
8
  This can be temporarily used to force VCR to re-record
7
9
  a cassette (i.e. to ensure the responses are not out of date)
@@ -1,7 +1,13 @@
1
1
  Feature: :new_episodes
2
2
 
3
- The `:new_episodes` record mode replays previously recorded
4
- requests and records new ones.
3
+ The `:new_episodes` record mode will:
4
+
5
+ - Record new interactions.
6
+ - Replay previously recorded interactions.
7
+
8
+ It is similar to the `:once` record mode, but will _always_ record new
9
+ interactions, even if you have an existing recorded one that is similar
10
+ (but not identical, based on the `:match_request_on` option).
5
11
 
6
12
  Background:
7
13
  Given a file named "setup.rb" with:
@@ -1,7 +1,9 @@
1
1
  Feature: :none
2
2
 
3
- The `:none` record mode allows previously recorded responses
4
- to be replayed, but raises an error for any new requests.
3
+ The `:none` record mode will:
4
+
5
+ - Replay previously recorded interactions.
6
+ - Cause an error to be raised for any new requests.
5
7
 
6
8
  This is useful when your code makes potentially dangerous
7
9
  HTTP requests. The `:none` record mode guarantees that no
@@ -0,0 +1,92 @@
1
+ Feature: :once
2
+
3
+ The `:once` record mode will:
4
+
5
+ - Replay previously recorded interactions.
6
+ - Record new interactions if there is no cassette file.
7
+ - Cause an error to be raised for new requests if there is a cassette file.
8
+
9
+ It is similar to the `:new_episodes` record mode, but will prevent new,
10
+ unexpected requests from being made (i.e. because the request URI changed
11
+ or whatever).
12
+
13
+ `:once` is the default record mode, used when you do not set one.
14
+
15
+ Background:
16
+ Given a file named "setup.rb" with:
17
+ """
18
+ require 'vcr_cucumber_helpers'
19
+
20
+ start_sinatra_app(:port => 7777) do
21
+ get('/') { 'Hello' }
22
+ end
23
+
24
+ require 'vcr'
25
+
26
+ VCR.config do |c|
27
+ c.stub_with :fakeweb
28
+ c.cassette_library_dir = 'cassettes'
29
+ end
30
+ """
31
+ And a previously recorded cassette file "cassettes/example.yml" with:
32
+ """
33
+ ---
34
+ - !ruby/struct:VCR::HTTPInteraction
35
+ request: !ruby/struct:VCR::Request
36
+ method: :get
37
+ uri: http://example.com:80/foo
38
+ body:
39
+ headers:
40
+ response: !ruby/struct:VCR::Response
41
+ status: !ruby/struct:VCR::ResponseStatus
42
+ code: 200
43
+ message: OK
44
+ headers:
45
+ content-type:
46
+ - text/html;charset=utf-8
47
+ content-length:
48
+ - "20"
49
+ body: example.com response
50
+ http_version: "1.1"
51
+ """
52
+
53
+ Scenario: Previously recorded responses are replayed
54
+ Given a file named "replay_recorded_response.rb" with:
55
+ """
56
+ require 'setup'
57
+
58
+ VCR.use_cassette('example', :record => :once) do
59
+ response = Net::HTTP.get_response('example.com', '/foo')
60
+ puts "Response: #{response.body}"
61
+ end
62
+ """
63
+ When I run "ruby replay_recorded_response.rb"
64
+ Then it should pass with "Response: example.com response"
65
+
66
+ Scenario: New requests result in an error when the cassette file exists
67
+ Given a file named "error_for_new_requests_when_cassette_exists.rb" with:
68
+ """
69
+ require 'setup'
70
+
71
+ VCR.use_cassette('example', :record => :once) do
72
+ response = Net::HTTP.get_response('localhost', '/', 7777)
73
+ puts "Response: #{response.body}"
74
+ end
75
+ """
76
+ When I run "ruby error_for_new_requests_when_cassette_exists.rb"
77
+ Then it should fail with "Real HTTP connections are disabled"
78
+
79
+ Scenario: New requests get recorded when there is no cassette file
80
+ Given a file named "record_new_requests.rb" with:
81
+ """
82
+ require 'setup'
83
+
84
+ VCR.use_cassette('example', :record => :once) do
85
+ response = Net::HTTP.get_response('localhost', '/', 7777)
86
+ puts "Response: #{response.body}"
87
+ end
88
+ """
89
+ When I remove the file "cassettes/example.yml"
90
+ And I run "ruby record_new_requests.rb"
91
+ Then it should pass with "Response: Hello"
92
+ And the file "cassettes/example.yml" should contain "body: Hello"
@@ -95,6 +95,24 @@ Then /^the file "([^"]*)" should contain each of these:$/ do |file_name, table|
95
95
  end
96
96
  end
97
97
 
98
+ Then /^the file "([^"]*)" should contain:$/ do |file_name, expected_content|
99
+ check_file_content(file_name, expected_content, true)
100
+ end
101
+
102
+ Then /^the file "([^"]*)" should contain a YAML fragment like:$/ do |file_name, fragment|
103
+ if defined?(::Psych)
104
+ # psych serializes things slightly differently...
105
+ fragment = fragment.split("\n").map { |s| s.rstrip }.join("\n")
106
+ end
107
+
108
+ # JRuby serializes things a bit differently
109
+ if RUBY_PLATFORM == 'java'
110
+ fragment = fragment.gsub(/^(\s+\-)/,' \1')
111
+ end
112
+
113
+ check_file_content(file_name, fragment, true)
114
+ end
115
+
98
116
  Then /^the cassette "([^"]*)" should have the following response bodies:$/ do |file, table|
99
117
  interactions = in_current_dir { YAML.load_file(file) }
100
118
  actual_response_bodies = interactions.map { |i| i.response.body }
data/lib/vcr.rb CHANGED
@@ -1,11 +1,18 @@
1
+ require 'vcr/util/regexes'
2
+ require 'vcr/util/variable_args_block_caller'
3
+ require 'vcr/util/yaml'
4
+
1
5
  require 'vcr/cassette'
2
6
  require 'vcr/config'
3
- require 'vcr/deprecations'
4
7
  require 'vcr/request_matcher'
5
- require 'vcr/util/regexes'
6
8
  require 'vcr/version'
9
+
7
10
  require 'vcr/http_stubbing_adapters/common'
8
11
 
12
+ require 'vcr/deprecations/cassette'
13
+ require 'vcr/deprecations/config'
14
+ require 'vcr/deprecations/http_stubbing_adapters/common'
15
+
9
16
  require 'vcr/structs/normalizers/body'
10
17
  require 'vcr/structs/normalizers/header'
11
18
  require 'vcr/structs/normalizers/status_message'
@@ -16,12 +23,13 @@ require 'vcr/structs/response'
16
23
  require 'vcr/structs/response_status'
17
24
 
18
25
  module VCR
26
+ include VariableArgsBlockCaller
19
27
  extend self
20
28
 
21
29
  autoload :BasicObject, 'vcr/util/basic_object'
22
- autoload :CucumberTags, 'vcr/cucumber_tags'
30
+ autoload :CucumberTags, 'vcr/test_frameworks/cucumber'
23
31
  autoload :InternetConnection, 'vcr/util/internet_connection'
24
- autoload :RSpec, 'vcr/rspec'
32
+ autoload :RSpec, 'vcr/test_frameworks/rspec'
25
33
 
26
34
  LOCALHOST_ALIASES = %w( localhost 127.0.0.1 0.0.0.0 )
27
35
 
@@ -63,8 +71,7 @@ module VCR
63
71
  cassette = insert_cassette(*args)
64
72
 
65
73
  begin
66
- # yield the cassette if the block accepts an argument or variable args
67
- block.arity == 0 ? block.call : block.call(cassette)
74
+ call_block(block, cassette)
68
75
  ensure
69
76
  eject_cassette
70
77
  end
@@ -1,5 +1,4 @@
1
1
  require 'fileutils'
2
- require 'yaml'
3
2
  require 'erb'
4
3
  require 'set'
5
4
 
@@ -7,13 +6,21 @@ require 'vcr/cassette/reader'
7
6
 
8
7
  module VCR
9
8
  class Cassette
10
- VALID_RECORD_MODES = [:all, :none, :new_episodes]
9
+ VALID_RECORD_MODES = [:all, :none, :new_episodes, :once]
11
10
 
12
11
  attr_reader :name, :record_mode, :match_requests_on, :erb, :re_record_interval, :tag
13
12
 
14
13
  def initialize(name, options = {})
15
14
  options = VCR::Config.default_cassette_options.merge(options)
16
- invalid_options = options.keys - [:record, :erb, :allow_real_http, :match_requests_on, :re_record_interval, :tag]
15
+ invalid_options = options.keys - [
16
+ :record,
17
+ :erb,
18
+ :allow_real_http,
19
+ :match_requests_on,
20
+ :re_record_interval,
21
+ :tag,
22
+ :update_content_length_header
23
+ ]
17
24
 
18
25
  if invalid_options.size > 0
19
26
  raise ArgumentError.new("You passed the following invalid options to VCR::Cassette.new: #{invalid_options.inspect}.")
@@ -26,6 +33,7 @@ module VCR
26
33
  @re_record_interval = options[:re_record_interval]
27
34
  @tag = options[:tag]
28
35
  @record_mode = :all if should_re_record?
36
+ @update_content_length_header = options[:update_content_length_header]
29
37
 
30
38
  deprecate_old_cassette_options(options)
31
39
  raise_error_unless_valid_record_mode
@@ -57,6 +65,10 @@ module VCR
57
65
  File.join(VCR::Config.cassette_library_dir, "#{sanitized_name}.yml") if VCR::Config.cassette_library_dir
58
66
  end
59
67
 
68
+ def update_content_length_header?
69
+ @update_content_length_header
70
+ end
71
+
60
72
  private
61
73
 
62
74
  def sanitized_name
@@ -65,7 +77,7 @@ module VCR
65
77
 
66
78
  def raise_error_unless_valid_record_mode
67
79
  unless VALID_RECORD_MODES.include?(record_mode)
68
- raise ArgumentError.new("#{record_mode} is not a valid cassette record mode. Valid options are: #{VALID_RECORD_MODES.inspect}")
80
+ raise ArgumentError.new("#{record_mode} is not a valid cassette record mode. Valid modes are: #{VALID_RECORD_MODES.inspect}")
69
81
  end
70
82
  end
71
83
 
@@ -77,7 +89,11 @@ module VCR
77
89
  end
78
90
 
79
91
  def should_allow_http_connections?
80
- record_mode != :none
92
+ case record_mode
93
+ when :none; false
94
+ when :once; !File.size?(file)
95
+ else true
96
+ end
81
97
  end
82
98
 
83
99
  def should_stub_requests?
@@ -101,18 +117,23 @@ module VCR
101
117
  VCR.http_stubbing_adapter.create_stubs_checkpoint(self)
102
118
  if file && File.size?(file)
103
119
  begin
104
- interactions = YAML.load(raw_yaml_content)
105
- invoke_hook(:before_playback, interactions)
106
-
107
- interactions.reject! do |i|
108
- i.request.uri.is_a?(String) && VCR::Config.uri_should_be_ignored?(i.request.uri)
109
- end
110
-
111
- recorded_interactions.replace(interactions)
112
- rescue TypeError
120
+ interactions = VCR::YAML.load(raw_yaml_content)
121
+ rescue TypeError, ArgumentError # Syck raises TypeError, Psych raises ArgumentError
113
122
  raise unless raw_yaml_content =~ /VCR::RecordedResponse/
114
123
  raise "The VCR cassette #{sanitized_name}.yml uses an old format that is now deprecated. VCR provides a rake task to migrate your old cassettes to the new format. See http://github.com/myronmarston/vcr/blob/master/CHANGELOG.md for more info."
115
124
  end
125
+
126
+ invoke_hook(:before_playback, interactions)
127
+
128
+ interactions.reject! do |i|
129
+ i.request.uri.is_a?(String) && VCR::Config.uri_should_be_ignored?(i.request.uri)
130
+ end
131
+
132
+ if update_content_length_header?
133
+ interactions.each { |i| i.response.update_content_length_header }
134
+ end
135
+
136
+ recorded_interactions.replace(interactions)
116
137
  end
117
138
 
118
139
  if should_stub_requests?
@@ -152,7 +173,7 @@ module VCR
152
173
 
153
174
  directory = File.dirname(file)
154
175
  FileUtils.mkdir_p directory unless File.exist?(directory)
155
- File.open(file, 'w') { |f| f.write interactions.to_yaml }
176
+ File.open(file, 'w') { |f| f.write VCR::YAML.dump(interactions) }
156
177
  end
157
178
 
158
179
  def invoke_hook(type, interactions)
@@ -1,9 +1,10 @@
1
1
  require 'fileutils'
2
- require 'vcr/hooks'
2
+ require 'vcr/util/hooks'
3
3
 
4
4
  module VCR
5
5
  module Config
6
6
  include VCR::Hooks
7
+ include VCR::VariableArgsBlockCaller
7
8
  extend self
8
9
 
9
10
  define_hook :before_record
@@ -18,7 +19,8 @@ module VCR
18
19
  attr_writer :default_cassette_options
19
20
  def default_cassette_options
20
21
  @default_cassette_options ||= {}
21
- @default_cassette_options.merge!(:match_requests_on => RequestMatcher::DEFAULT_MATCH_ATTRIBUTES) unless @default_cassette_options.has_key?(:match_requests_on)
22
+ @default_cassette_options[:match_requests_on] ||= RequestMatcher::DEFAULT_MATCH_ATTRIBUTES
23
+ @default_cassette_options[:record] ||= :once
22
24
  @default_cassette_options
23
25
  end
24
26
 
@@ -57,6 +59,16 @@ module VCR
57
59
  !!@allow_http_connections_when_no_cassette
58
60
  end
59
61
 
62
+ def filter_sensitive_data(placeholder, tag = nil, &block)
63
+ before_record(tag) do |interaction|
64
+ interaction.filter!(call_block(block, interaction), placeholder)
65
+ end
66
+
67
+ before_playback(tag) do |interaction|
68
+ interaction.filter!(placeholder, call_block(block, interaction))
69
+ end
70
+ end
71
+
60
72
  def uri_should_be_ignored?(uri)
61
73
  uri = URI.parse(uri) unless uri.respond_to?(:host)
62
74
  ignored_hosts.include?(uri.host)
@@ -0,0 +1,29 @@
1
+ module VCR
2
+ class Cassette
3
+ def allow_real_http_requests_to?(uri)
4
+ warn "WARNING: VCR::Cassette#allow_real_http_requests_to? is deprecated and should no longer be used."
5
+ VCR::Config.uri_should_be_ignored?(uri.to_s)
6
+ end
7
+
8
+ private
9
+
10
+ def deprecate_old_cassette_options(options)
11
+ message = "VCR's :allow_real_http cassette option is deprecated. Instead, use the ignore_localhost configuration option."
12
+ if options[:allow_real_http] == :localhost
13
+ @original_ignored_hosts = VCR::Config.ignored_hosts.dup
14
+ VCR::Config.ignored_hosts.clear
15
+ VCR::Config.ignore_hosts *VCR::LOCALHOST_ALIASES
16
+ Kernel.warn "WARNING: #{message}"
17
+ elsif options[:allow_real_http]
18
+ raise ArgumentError.new(message)
19
+ end
20
+ end
21
+
22
+ def restore_ignore_localhost_for_deprecation
23
+ if defined?(@original_ignored_hosts)
24
+ VCR::Config.ignored_hosts.clear
25
+ VCR::Config.ignore_hosts *@original_ignored_hosts
26
+ end
27
+ end
28
+ end
29
+ end