vcr 2.0.0.rc1 → 2.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/.gitignore +2 -0
  2. data/.limited_red +1 -0
  3. data/.travis.yml +10 -1
  4. data/.yardopts +9 -0
  5. data/CHANGELOG.md +51 -1
  6. data/Gemfile +5 -1
  7. data/LICENSE +1 -1
  8. data/README.md +23 -28
  9. data/Rakefile +63 -18
  10. data/Upgrade.md +200 -0
  11. data/features/.nav +2 -0
  12. data/features/cassettes/automatic_re_recording.feature +19 -15
  13. data/features/cassettes/dynamic_erb.feature +12 -4
  14. data/features/cassettes/exclusive.feature +31 -23
  15. data/features/cassettes/format.feature +54 -30
  16. data/features/cassettes/naming.feature +1 -1
  17. data/features/cassettes/update_content_length_header.feature +16 -12
  18. data/features/configuration/allow_http_connections_when_no_cassette.feature +1 -1
  19. data/features/configuration/debug_logging.feature +52 -0
  20. data/features/configuration/filter_sensitive_data.feature +4 -4
  21. data/features/configuration/hook_into.feature +5 -2
  22. data/features/configuration/ignore_request.feature +5 -3
  23. data/features/configuration/preserve_exact_body_bytes.feature +103 -0
  24. data/features/hooks/after_http_request.feature +17 -4
  25. data/features/hooks/around_http_request.feature +2 -1
  26. data/features/hooks/before_http_request.feature +25 -8
  27. data/features/hooks/before_playback.feature +16 -12
  28. data/features/hooks/before_record.feature +2 -2
  29. data/features/http_libraries/em_http_request.feature +82 -58
  30. data/features/http_libraries/net_http.feature +6 -6
  31. data/features/middleware/faraday.feature +2 -1
  32. data/features/middleware/rack.feature +2 -2
  33. data/features/record_modes/all.feature +19 -15
  34. data/features/record_modes/new_episodes.feature +17 -13
  35. data/features/record_modes/none.feature +15 -11
  36. data/features/record_modes/once.feature +16 -12
  37. data/features/request_matching/body.feature +28 -20
  38. data/features/request_matching/custom_matcher.feature +28 -20
  39. data/features/request_matching/headers.feature +34 -26
  40. data/features/request_matching/host.feature +28 -20
  41. data/features/request_matching/identical_request_sequence.feature +28 -20
  42. data/features/request_matching/method.feature +28 -20
  43. data/features/request_matching/path.feature +28 -20
  44. data/features/request_matching/playback_repeats.feature +28 -20
  45. data/features/request_matching/uri.feature +28 -20
  46. data/features/request_matching/uri_without_param.feature +28 -20
  47. data/features/support/env.rb +7 -6
  48. data/features/support/vcr_cucumber_helpers.rb +1 -0
  49. data/features/test_frameworks/cucumber.feature +8 -8
  50. data/features/test_frameworks/rspec_macro.feature +4 -4
  51. data/features/test_frameworks/rspec_metadata.feature +6 -6
  52. data/features/test_frameworks/shoulda.feature +1 -1
  53. data/features/test_frameworks/test_unit.feature +1 -1
  54. data/lib/vcr.rb +156 -5
  55. data/lib/vcr/cassette.rb +80 -30
  56. data/lib/vcr/cassette/http_interaction_list.rb +33 -4
  57. data/lib/vcr/cassette/migrator.rb +2 -3
  58. data/lib/vcr/cassette/reader.rb +1 -0
  59. data/lib/vcr/cassette/serializers.rb +22 -0
  60. data/lib/vcr/cassette/serializers/json.rb +27 -2
  61. data/lib/vcr/cassette/serializers/psych.rb +26 -2
  62. data/lib/vcr/cassette/serializers/syck.rb +28 -2
  63. data/lib/vcr/cassette/serializers/yaml.rb +28 -2
  64. data/lib/vcr/configuration.rb +348 -10
  65. data/lib/vcr/deprecations.rb +8 -0
  66. data/lib/vcr/errors.rb +40 -0
  67. data/lib/vcr/extensions/net_http_response.rb +12 -11
  68. data/lib/vcr/library_hooks.rb +1 -0
  69. data/lib/vcr/library_hooks/excon.rb +24 -3
  70. data/lib/vcr/library_hooks/fakeweb.rb +32 -16
  71. data/lib/vcr/library_hooks/faraday.rb +3 -0
  72. data/lib/vcr/library_hooks/typhoeus.rb +40 -37
  73. data/lib/vcr/library_hooks/webmock.rb +54 -34
  74. data/lib/vcr/middleware/faraday.rb +13 -0
  75. data/lib/vcr/middleware/rack.rb +35 -0
  76. data/lib/vcr/request_handler.rb +60 -8
  77. data/lib/vcr/request_ignorer.rb +1 -0
  78. data/lib/vcr/request_matcher_registry.rb +28 -0
  79. data/lib/vcr/structs.rb +245 -38
  80. data/lib/vcr/test_frameworks/cucumber.rb +10 -0
  81. data/lib/vcr/test_frameworks/rspec.rb +26 -1
  82. data/lib/vcr/util/hooks.rb +29 -27
  83. data/lib/vcr/util/internet_connection.rb +2 -0
  84. data/lib/vcr/util/logger.rb +25 -0
  85. data/lib/vcr/util/variable_args_block_caller.rb +1 -0
  86. data/lib/vcr/util/version_checker.rb +1 -0
  87. data/lib/vcr/version.rb +8 -1
  88. data/spec/capture_warnings.rb +3 -3
  89. data/spec/monkey_patches.rb +28 -13
  90. data/spec/spec_helper.rb +17 -0
  91. data/spec/support/http_library_adapters.rb +7 -4
  92. data/spec/support/shared_example_groups/hook_into_http_library.rb +96 -32
  93. data/spec/support/shared_example_groups/request_hooks.rb +9 -8
  94. data/spec/support/sinatra_app.rb +3 -1
  95. data/spec/support/vcr_localhost_server.rb +1 -0
  96. data/spec/vcr/cassette/http_interaction_list_spec.rb +119 -54
  97. data/spec/vcr/cassette/migrator_spec.rb +19 -6
  98. data/spec/vcr/cassette/serializers_spec.rb +51 -6
  99. data/spec/vcr/cassette_spec.rb +44 -19
  100. data/spec/vcr/configuration_spec.rb +91 -6
  101. data/spec/vcr/library_hooks/excon_spec.rb +54 -16
  102. data/spec/vcr/library_hooks/fakeweb_spec.rb +12 -21
  103. data/spec/vcr/library_hooks/typhoeus_spec.rb +2 -29
  104. data/spec/vcr/library_hooks/webmock_spec.rb +4 -18
  105. data/spec/vcr/middleware/faraday_spec.rb +1 -16
  106. data/spec/vcr/structs_spec.rb +194 -61
  107. data/spec/vcr/test_frameworks/rspec_spec.rb +10 -0
  108. data/spec/vcr/util/hooks_spec.rb +104 -56
  109. data/spec/vcr/util/version_checker_spec.rb +45 -0
  110. data/spec/vcr_spec.rb +11 -0
  111. data/vcr.gemspec +30 -34
  112. metadata +149 -95
  113. data/spec/support/shared_example_groups/version_checking.rb +0 -34
@@ -1,20 +1,30 @@
1
1
  module VCR
2
+ # Provides integration with Cucumber using tags.
2
3
  class CucumberTags
3
4
  class << self
5
+ # @private
4
6
  def tags
5
7
  @tags.dup
6
8
  end
7
9
 
10
+ # @private
8
11
  def add_tag(tag)
9
12
  @tags << tag
10
13
  end
11
14
  end
12
15
 
13
16
  @tags = []
17
+
18
+ # @private
14
19
  def initialize(main_object)
15
20
  @main_object = main_object
16
21
  end
17
22
 
23
+ # Adds +Before+ and +After+ cucumber hooks for the named tags that
24
+ # will cause a VCR cassette to be used for scenarios with matching tags.
25
+ #
26
+ # @param [Array<String>] tag_names the cucumber scenario tags
27
+ # @param [(optional) Hash] options the cassette options
18
28
  def tags(*tag_names)
19
29
  options = tag_names.last.is_a?(::Hash) ? tag_names.pop : {}
20
30
  tag_names.each do |tag_name|
@@ -1,6 +1,30 @@
1
1
  module VCR
2
+ # Integrates VCR with RSpec.
2
3
  module RSpec
4
+
5
+ # Contains macro methods to assist with VCR usage. These methods are
6
+ # intended to be used directly in an RSpec example group. To make these
7
+ # available in your RSpec example groups, extend the module in an individual
8
+ # example group, or configure RSpec to extend the module in all example groups.
9
+ #
10
+ # @example
11
+ # RSpec.configure do |c|
12
+ # c.extend VCR::RSpec::Macros
13
+ # end
14
+ #
3
15
  module Macros
16
+
17
+ # Sets up a +before+ and +after+ hook that will insert and eject a
18
+ # cassette, respectively.
19
+ #
20
+ # @example
21
+ # describe "Some API Client" do
22
+ # use_vcr_cassette "some_api", :record => :new_episodes
23
+ # end
24
+ #
25
+ # @param [(optional) String] name the cassette name; it will be inferred by the example
26
+ # group descriptions if not given.
27
+ # @param [(optional) Hash] options the cassette options
4
28
  def use_vcr_cassette(*args)
5
29
  options = args.last.is_a?(Hash) ? args.pop : {}
6
30
  name = args.first || infer_cassette_name
@@ -33,6 +57,7 @@ module VCR
33
57
  end
34
58
  end
35
59
 
60
+ # @private
36
61
  module Metadata
37
62
  extend self
38
63
 
@@ -50,7 +75,7 @@ module VCR
50
75
 
51
76
  config.around(:each, :vcr => lambda { |v| !!v }) do |example|
52
77
  options = example.metadata[:vcr]
53
- options = {} unless options.is_a?(Hash) # in case it's just :vcr => true
78
+ options = options.is_a?(Hash) ? options.dup : {} # in case it's just :vcr => true
54
79
 
55
80
  cassette_name = options.delete(:cassette_name) ||
56
81
  vcr_cassette_name_for[example.metadata]
@@ -1,20 +1,32 @@
1
1
  require 'vcr/util/variable_args_block_caller'
2
2
 
3
3
  module VCR
4
+ # @private
4
5
  module Hooks
5
6
  include VariableArgsBlockCaller
6
7
 
7
- def self.included(klass)
8
- klass.extend(ClassMethods)
8
+ FilteredHook = Struct.new(:hook, :filters) do
9
+ include VariableArgsBlockCaller
10
+
11
+ def conditionally_invoke(*args)
12
+ filters = Array(self.filters)
13
+ return if filters.any? { |f| !call_block(f.to_proc, *args) }
14
+ call_block(hook, *args)
15
+ end
9
16
  end
10
17
 
11
- def invoke_hook(hook, *args)
12
- invoke_tagged_hook(hook, nil, *args)
18
+ def self.included(klass)
19
+ klass.class_eval do
20
+ extend ClassMethods
21
+ hooks_module = Module.new
22
+ const_set("DefinedHooks", hooks_module)
23
+ include hooks_module
24
+ end
13
25
  end
14
26
 
15
- def invoke_tagged_hook(hook, tag, *args)
16
- hooks_for(hook, tag).map do |callback|
17
- call_block(callback, *args)
27
+ def invoke_hook(hook_type, *args)
28
+ hooks[hook_type].map do |hook|
29
+ hook.conditionally_invoke(*args)
18
30
  end
19
31
  end
20
32
 
@@ -22,36 +34,26 @@ module VCR
22
34
  hooks.clear
23
35
  end
24
36
 
25
- private
26
-
27
37
  def hooks
28
- @hooks ||= Hash.new do |hook_type_hash, hook_type|
29
- hook_type_hash[hook_type] = Hash.new do |tag_hash, tag|
30
- tag_hash[tag] = []
31
- end
38
+ @hooks ||= Hash.new do |hash, hook_type|
39
+ hash[hook_type] = []
32
40
  end
33
41
  end
34
42
 
35
- def hooks_for(hook, tag)
36
- for_hook = hooks[hook]
37
- hooks = for_hook[tag] # matching tagged hooks
38
- hooks += for_hook[nil] unless tag.nil? # untagged hooks
39
- hooks
43
+ def has_hooks_for?(hook_type)
44
+ hooks[hook_type].any?
40
45
  end
41
46
 
47
+ # @private
42
48
  module ClassMethods
43
- def define_hook(hook, prepend = false)
49
+ def define_hook(hook_type, prepend = false)
44
50
  placement_method = prepend ? :unshift : :<<
45
51
 
46
- # We use splat args here because 1.8.7 doesn't allow default
47
- # values for block arguments, so we have to fake it.
48
- define_method hook do |*args, &block|
49
- if args.size > 1
50
- raise ArgumentError.new("wrong number of arguments (#{args.size} for 1)")
52
+ # Put the hook methods in a module so we can override and super to these methods.
53
+ self::DefinedHooks.module_eval do
54
+ define_method hook_type do |*filters, &hook|
55
+ hooks[hook_type].send(placement_method, FilteredHook.new(hook, filters))
51
56
  end
52
-
53
- tag = args.first
54
- hooks[hook][tag].send(placement_method, block)
55
57
  end
56
58
  end
57
59
  end
@@ -9,6 +9,7 @@ module VCR
9
9
  require 'timeout'
10
10
  require "socket"
11
11
 
12
+ # @private
12
13
  module Ping
13
14
  def pingecho(host, timeout=5, service="echo")
14
15
  begin
@@ -27,6 +28,7 @@ module VCR
27
28
  end
28
29
  end
29
30
 
31
+ # @private
30
32
  module InternetConnection
31
33
  extend self
32
34
 
@@ -0,0 +1,25 @@
1
+ module VCR
2
+ # @private
3
+ module Logger
4
+ def log(message, indentation_level = 0)
5
+ indentation = ' ' * indentation_level
6
+ log_message = indentation + log_prefix + message
7
+ VCR.configuration.debug_logger.puts log_message
8
+ end
9
+
10
+ def log_prefix
11
+ ''
12
+ end
13
+
14
+ def request_summary(request, request_matchers)
15
+ attributes = [request.method, request.uri]
16
+ attributes << request.body.to_s[0, 80].inspect if request_matchers.include?(:body)
17
+ attributes << request.headers.inspect if request_matchers.include?(:headers)
18
+ "[#{attributes.join(" ")}]"
19
+ end
20
+
21
+ def response_summary(response)
22
+ "[#{response.status.code} #{response.body[0, 80].inspect}]"
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,5 @@
1
1
  module VCR
2
+ # @private
2
3
  module VariableArgsBlockCaller
3
4
  def call_block(block, *args)
4
5
  if block.arity >= 0
@@ -1,4 +1,5 @@
1
1
  module VCR
2
+ # @private
2
3
  class VersionChecker
3
4
  def initialize(library_name, library_version, min_patch_level, max_minor_version)
4
5
  @library_name, @library_version = library_name, library_version
data/lib/vcr/version.rb CHANGED
@@ -1,9 +1,16 @@
1
1
  module VCR
2
2
  extend self
3
3
 
4
+ # @return [String] the current VCR version.
5
+ # @note This string also has singleton methods:
6
+ #
7
+ # * `major` [Integer] The major version.
8
+ # * `minor` [Integer] The minor version.
9
+ # * `patch` [Integer] The patch version.
10
+ # * `parts` [Array<Integer>] List of the version parts.
4
11
  def version
5
12
  @version ||= begin
6
- string = '2.0.0.rc1'
13
+ string = '2.0.0.rc2'
7
14
 
8
15
  def string.parts
9
16
  split('.').map { |p| p.to_i }
@@ -1,11 +1,11 @@
1
+ require 'rubygems' if RUBY_VERSION =~ /^1\.8/
2
+ require 'rspec/core'
3
+ require 'rspec/expectations'
1
4
  require 'tempfile'
2
5
  stderr_file = Tempfile.new("vcr.stderr")
3
6
  $stderr.reopen(stderr_file.path)
4
7
  current_dir = Dir.pwd
5
8
 
6
- require 'rubygems' if RUBY_VERSION =~ /^1\.8/
7
- require 'rspec/core'
8
-
9
9
  RSpec.configure do |config|
10
10
  config.after(:suite) do
11
11
  stderr_file.rewind
@@ -14,8 +14,6 @@ module MonkeyPatches
14
14
 
15
15
  ALL_MONKEY_PATCHES = NET_HTTP_MONKEY_PATCHES.dup
16
16
 
17
- ALL_MONKEY_PATCHES << [Typhoeus::Hydra::Stubbing::SharedMethods, :find_stub_from_request] if defined?(::Typhoeus)
18
-
19
17
  def enable!(scope)
20
18
  case scope
21
19
  when :fakeweb
@@ -25,12 +23,21 @@ module MonkeyPatches
25
23
  ::WebMock.reset!
26
24
  ::WebMock::HttpLibAdapters::NetHttpAdapter.enable!
27
25
  ::WebMock::HttpLibAdapters::TyphoeusAdapter.enable! if defined?(::Typhoeus)
26
+ ::WebMock::HttpLibAdapters::ExconAdapter.enable! if defined?(::Excon)
28
27
  $original_webmock_callbacks.each do |cb|
29
28
  ::WebMock::CallbackRegistry.add_callback(cb[:options], cb[:block])
30
29
  end
31
30
  when :typhoeus
32
- Typhoeus::Hydra.global_hooks = $original_typhoeus_hooks
33
- realias Typhoeus::Hydra::Stubbing::SharedMethods, :find_stub_from_request, :with_vcr
31
+ ::Typhoeus::Hydra.global_hooks = $original_typhoeus_global_hooks
32
+ ::Typhoeus::Hydra.stub_finders.clear
33
+ $original_typhoeus_stub_finders.each do |finder|
34
+ ::Typhoeus::Hydra.stub_finders << finder
35
+ end
36
+ when :excon
37
+ $original_excon_stubs.each do |stub|
38
+ ::Excon.stubs << stub
39
+ end
40
+ ::Excon.defaults[:mock] = true
34
41
  when :vcr
35
42
  realias Net::HTTP, :request, :with_vcr
36
43
  else raise ArgumentError.new("Unexpected scope: #{scope}")
@@ -43,12 +50,19 @@ module MonkeyPatches
43
50
  if defined?(::WebMock::HttpLibAdapters)
44
51
  ::WebMock::HttpLibAdapters::NetHttpAdapter.disable!
45
52
  ::WebMock::HttpLibAdapters::TyphoeusAdapter.disable! if defined?(::Typhoeus)
53
+ ::WebMock::HttpLibAdapters::ExconAdapter.disable! if defined?(::Excon)
46
54
  ::WebMock::CallbackRegistry.reset
47
55
  ::WebMock::StubRegistry.instance.request_stubs = []
48
56
  end
49
57
 
50
58
  if defined?(::Typhoeus)
51
- Typhoeus::Hydra.clear_global_hooks
59
+ ::Typhoeus::Hydra.clear_global_hooks
60
+ ::Typhoeus::Hydra.stub_finders.clear
61
+ end
62
+
63
+ if defined?(::Excon)
64
+ ::Excon.stubs.clear
65
+ ::Excon.defaults[:mock] = false
52
66
  end
53
67
  end
54
68
 
@@ -118,15 +132,13 @@ unless RUBY_INTERPRETER == :jruby
118
132
  require 'curb'
119
133
 
120
134
  require 'vcr/library_hooks/typhoeus'
121
- $original_typhoeus_hooks = Typhoeus::Hydra.global_hooks.dup
122
-
123
- # define an alias that we can re-alias to in the future
124
- Typhoeus::Hydra::Stubbing::SharedMethods.class_eval do
125
- alias find_stub_from_request_with_vcr find_stub_from_request
126
- end
135
+ $typhoeus_after_loaded_hook = VCR.configuration.hooks[:after_library_hooks_loaded].last
136
+ $original_typhoeus_global_hooks = Typhoeus::Hydra.global_hooks.dup
137
+ $original_typhoeus_stub_finders = Typhoeus::Hydra.stub_finders.dup
127
138
  end
128
139
 
129
140
  require 'vcr/library_hooks/fakeweb'
141
+ $fakeweb_after_loaded_hook = VCR.configuration.hooks[:after_library_hooks_loaded].last
130
142
 
131
143
  # All Net::HTTP monkey patches have now been loaded, so capture the
132
144
  # appropriate method definitions so we can disable them later.
@@ -139,14 +151,17 @@ MonkeyPatches.disable_all!
139
151
  require 'vcr/library_hooks/webmock'
140
152
  $original_webmock_callbacks = ::WebMock::CallbackRegistry.callbacks
141
153
 
154
+ require 'vcr/library_hooks/excon'
155
+ $excon_after_loaded_hook = VCR.configuration.hooks[:after_library_hooks_loaded].last
156
+ $original_excon_stubs = ::Excon.stubs.dup
157
+
142
158
  # disable all by default; we'll enable specific ones when we need them
143
159
  MonkeyPatches.disable_all!
144
160
 
145
161
  RSpec.configure do |config|
146
- [:fakeweb, :webmock, :vcr, :typhoeus].each do |scope|
162
+ [:fakeweb, :webmock, :vcr, :typhoeus, :excon].each do |scope|
147
163
  config.before(:all, :with_monkey_patches => scope) { MonkeyPatches.enable!(scope) }
148
164
  config.after(:all, :with_monkey_patches => scope) { MonkeyPatches.disable_all! }
149
165
  end
150
166
  end
151
167
 
152
- require 'vcr/library_hooks/excon'
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,21 @@
1
1
  require 'rubygems'
2
+ require 'simplecov'
3
+
4
+ SimpleCov.start do
5
+ add_filter "/spec"
6
+ add_filter "/features"
7
+
8
+ # internet_connection mostly contains logic copied from the ruby 1.8.7
9
+ # stdlib for which I haven't written tests.
10
+ add_filter "internet_connection"
11
+ end
12
+
13
+ SimpleCov.at_exit do
14
+ File.open(File.join(SimpleCov.coverage_path, 'coverage_percent.txt'), 'w') do |f|
15
+ f.write SimpleCov.result.covered_percent
16
+ end
17
+ SimpleCov.result.format!
18
+ end
2
19
 
3
20
  using_git = File.exist?(File.expand_path('../../.git/', __FILE__))
4
21
  if using_git
@@ -90,7 +90,8 @@ HTTP_LIBRARY_ADAPTERS['em-http-request'] = Module.new do
90
90
  end
91
91
 
92
92
  def get_header(header_key, response)
93
- response.response_header[header_key.upcase.gsub('-', '_')].split(', ')
93
+ values = response.response_header[header_key.upcase.gsub('-', '_')]
94
+ values.is_a?(Array) ? values : values.split(', ')
94
95
  end
95
96
 
96
97
  def make_http_request(method, url, body = nil, headers = {})
@@ -116,11 +117,13 @@ HTTP_LIBRARY_ADAPTERS['curb'] = Module.new do
116
117
 
117
118
  def get_header(header_key, response)
118
119
  headers = response.header_str.split("\r\n")[1..-1]
120
+ value = nil
119
121
  headers.each do |h|
120
- if h =~ /^#{Regexp.escape(header_key)}: (.*)$/
121
- return $1.split(', ')
122
- end
122
+ next unless h =~ /^#{Regexp.escape(header_key)}: (.*)$/
123
+ new_value = $1.split(', ')
124
+ value = value ? Array(value) + Array(new_value) : new_value
123
125
  end
126
+ value
124
127
  end
125
128
 
126
129
  def make_http_request(method, url, body = nil, headers = {})
@@ -27,28 +27,10 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
27
27
  end
28
28
  end
29
29
 
30
- define_method :should_be_pending do
31
- if header_count == 2
32
- [
33
- 'HTTP Client',
34
- 'EM HTTP Request',
35
- 'Curb'
36
- ].include?(adapter_module.http_library_name)
37
- end
38
- end
39
-
40
30
  it 'returns the same header value when recording and replaying' do
41
- pending "There appears to be a bug in the the HTTP stubbing library", :if => should_be_pending do
42
- (recorded_val = get_set_cookie_header).should_not be_nil
43
- replayed_val = get_set_cookie_header
44
-
45
- # we don't care about order differences if the values are arrays
46
- if recorded_val.is_a?(Array) && replayed_val.is_a?(Array)
47
- replayed_val.should =~ recorded_val
48
- else
49
- replayed_val.should eq(recorded_val)
50
- end
51
- end
31
+ (recorded_val = get_set_cookie_header).should_not be_nil
32
+ replayed_val = get_set_cookie_header
33
+ replayed_val.should eq(recorded_val)
52
34
  end
53
35
  end
54
36
  end
@@ -109,6 +91,22 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
109
91
  test_playback "with an encoded ampersand", "http://example.com:80/search?q=#{CGI.escape("Q&A")}"
110
92
  end
111
93
 
94
+ it 'does not query the http interaction list excessively' do
95
+ call_count = 0
96
+ [:has_interaction_matching?, :response_for].each do |method_name|
97
+ orig_meth = VCR.http_interactions.method(method_name)
98
+ VCR.http_interactions.stub(method_name) do |*args|
99
+ call_count += 1
100
+ orig_meth.call(*args)
101
+ end
102
+ end
103
+
104
+ VCR.insert_cassette('foo')
105
+ make_http_request(:get, "http://localhost:#{VCR::SinatraApp.port}/foo")
106
+
107
+ call_count.should eq(1)
108
+ end
109
+
112
110
  describe "using the library's stubbing/disconnection APIs" do
113
111
  let!(:request_url) { "http://localhost:#{VCR::SinatraApp.port}/foo" }
114
112
 
@@ -151,13 +149,6 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
151
149
 
152
150
  describe "request hooks" do
153
151
  context 'when there is an around_http_request hook' do
154
- before(:each) do
155
- # ensure that all the other library hooks are disabled so that we don't
156
- # get double-hookage (such as for WebMock and Typhoeus both invoking the
157
- # hooks for a typhoeus request)
158
- VCR.library_hooks.stub(:disabled?) { |lib_name| lib_name != library_hook_name }
159
- end
160
-
161
152
  let(:request_url) { "http://localhost:#{VCR::SinatraApp.port}/foo" }
162
153
 
163
154
  it 'yields the request to the block' do
@@ -175,6 +166,19 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
175
166
  yielded_request.uri.should eq(request_url)
176
167
  end
177
168
 
169
+ it 'returns the response from request.proceed' do
170
+ response = nil
171
+ VCR.configuration.around_http_request do |request|
172
+ response = request.proceed
173
+ end
174
+
175
+ VCR.use_cassette('new_cassette') do
176
+ make_http_request(:get, request_url)
177
+ end
178
+
179
+ response.body.should eq("FOO!")
180
+ end
181
+
178
182
  it 'can be used to use a cassette for a request' do
179
183
  VCR.configuration.around_http_request do |request|
180
184
  VCR.use_cassette('new_cassette', &request)
@@ -232,20 +236,80 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
232
236
 
233
237
  3.times { make_http_request(:get, request_url) }
234
238
  end
239
+
240
+ it 'allows the hook to be filtered' do
241
+ order = []
242
+ VCR.configure do |c|
243
+ c.ignore_request { |r| true }
244
+ c.around_http_request(lambda { |r| r.uri =~ /foo/}) do |request|
245
+ order << :before_foo
246
+ request.proceed
247
+ order << :after_foo
248
+ end
249
+
250
+ c.around_http_request(lambda { |r| r.uri !~ /foo/}) do |request|
251
+ order << :before_not_foo
252
+ request.proceed
253
+ order << :after_not_foo
254
+ end
255
+ end
256
+
257
+ make_http_request(:get, request_url)
258
+ order.should eq([:before_foo, :after_foo])
259
+ end
260
+
261
+ it 'ensures that both around/before are invoked or neither' do
262
+ order = []
263
+ allow_1, allow_2 = false, true
264
+ VCR.configure do |c|
265
+ c.ignore_request { |r| true }
266
+ c.around_http_request(lambda { |r| allow_1 = !allow_1 }) do |request|
267
+ order << :before_1
268
+ request.proceed
269
+ order << :after_1
270
+ end
271
+
272
+ c.around_http_request(lambda { |r| allow_2 = !allow_2 }) do |request|
273
+ order << :before_2
274
+ request.proceed
275
+ order << :after_2
276
+ end
277
+ end
278
+
279
+ make_http_request(:get, request_url)
280
+ order.should eq([:before_1, :after_1])
281
+ end
235
282
  end if RUBY_VERSION >= '1.9'
236
283
 
284
+ it 'correctly assigns the correct type to both before and after request hooks, even if they are different' do
285
+ before_type = after_type = nil
286
+ VCR.configuration.before_http_request do |request|
287
+ before_type = request.type
288
+ VCR.insert_cassette('example')
289
+ end
290
+
291
+ VCR.configuration.after_http_request do |request|
292
+ after_type = request.type
293
+ VCR.eject_cassette
294
+ end
295
+
296
+ make_http_request(:get, "http://localhost:#{VCR::SinatraApp.port}/foo")
297
+ before_type.should be(:unhandled)
298
+ after_type.should be(:recordable)
299
+ end
300
+
237
301
  context "when the request is ignored" do
238
302
  before(:each) do
239
303
  VCR.configuration.ignore_request { |r| true }
240
304
  end
241
305
 
242
- it_behaves_like "request hooks", library_hook_name
306
+ it_behaves_like "request hooks", library_hook_name, :ignored
243
307
  end
244
308
 
245
309
  context 'when the request is recorded' do
246
310
  let!(:inserted_cassette) { VCR.insert_cassette('new_cassette') }
247
311
 
248
- it_behaves_like "request hooks", library_hook_name do
312
+ it_behaves_like "request hooks", library_hook_name, :recordable do
249
313
  let(:string_in_cassette) { 'example.com get response 1 with path=foo' }
250
314
 
251
315
  it 'plays back the cassette when a request is made' do
@@ -277,11 +341,11 @@ shared_examples_for "a hook into an HTTP library" do |library_hook_name, library
277
341
  stub_requests([http_interaction(request_url)], [:method, :uri])
278
342
  end
279
343
 
280
- it_behaves_like "request hooks", library_hook_name
344
+ it_behaves_like "request hooks", library_hook_name, :stubbed
281
345
  end
282
346
 
283
347
  context 'when the request is not allowed' do
284
- it_behaves_like "request hooks", library_hook_name do
348
+ it_behaves_like "request hooks", library_hook_name, :unhandled do
285
349
  undef assert_expected_response
286
350
  def assert_expected_response(response)
287
351
  response.should be_nil