summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Giddins <segiddins@segiddins.me>2016-11-30 15:15:30 -0600
committerSamuel Giddins <segiddins@segiddins.me>2017-06-13 11:12:22 -0500
commit991a5a77d7597575ecde5e696b5a09364ce30618 (patch)
treefe85642e0e00017a5f2488944527e66d13f874a1
parente8fd5795778379bd821e34173b678f76f240fa97 (diff)
downloadbundler-991a5a77d7597575ecde5e696b5a09364ce30618.tar.gz
[Realworld] Use VCR for network requests
-rw-r--r--spec/realworld/edgecases_spec.rb27
-rw-r--r--spec/support/artifice/vcr.rb196
-rw-r--r--spec/support/helpers.rb23
-rw-r--r--spec/support/rubygems_ext.rb2
4 files changed, 232 insertions, 16 deletions
diff --git a/spec/realworld/edgecases_spec.rb b/spec/realworld/edgecases_spec.rb
index 6de20798da..8787a2d40f 100644
--- a/spec/realworld/edgecases_spec.rb
+++ b/spec/realworld/edgecases_spec.rb
@@ -2,17 +2,22 @@
RSpec.describe "real world edgecases", :realworld => true, :sometimes => true do
def rubygems_version(name, requirement)
- require "bundler/source/rubygems/remote"
- require "bundler/fetcher"
- source = Bundler::Source::Rubygems::Remote.new(URI("https://rubygems.org"))
- fetcher = Bundler::Fetcher.new(source)
- index = fetcher.specs([name], nil)
- rubygem = index.search(Gem::Dependency.new(name, requirement)).last
- if rubygem.nil?
- raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \
- "Found specs:\n#{index.send(:specs).inspect}"
- end
- "#{name} (#{rubygem.version})"
+ ruby! <<-RUBY
+ ENV["BUNDLER_SPEC_VCR_CASSETTE_NAME"] = #{RSpec.current_example.full_description.dump}
+ require #{File.expand_path("../../support/artifice/vcr.rb", __FILE__).dump}
+ require "bundler"
+ require "bundler/source/rubygems/remote"
+ require "bundler/fetcher"
+ source = Bundler::Source::Rubygems::Remote.new(URI("https://rubygems.org"))
+ fetcher = Bundler::Fetcher.new(source)
+ index = fetcher.specs([#{name.dump}], nil)
+ rubygem = index.search(Gem::Dependency.new(#{name.dump}, #{requirement.dump})).last
+ if rubygem.nil?
+ raise "Could not find #{name} (#{requirement}) on rubygems.org!\n" \
+ "Found specs:\n\#{index.send(:specs).inspect}"
+ end
+ "#{name} (\#{rubygem.version})"
+ RUBY
end
# there is no rbx-relative-require gem that will install on 1.9
diff --git a/spec/support/artifice/vcr.rb b/spec/support/artifice/vcr.rb
new file mode 100644
index 0000000000..f09f860c8b
--- /dev/null
+++ b/spec/support/artifice/vcr.rb
@@ -0,0 +1,196 @@
+# frozen_string_literal: true
+require File.expand_path("../../path.rb", __FILE__)
+include Spec::Path
+
+$LOAD_PATH.unshift(*Dir[Spec::Path.base_system_gems.join("gems/{vcr}-*/lib")].map(&:to_s))
+
+require "vcr"
+require "vcr/request_handler"
+require "vcr/extensions/net_http_response"
+
+require "net/http"
+if RUBY_VERSION < "1.9"
+ begin
+ require "net/https"
+ rescue LoadError
+ nil # net/https or openssl
+ end
+end # but only for 1.8
+
+VCR.configure do |config|
+ config.cassette_library_dir = File.expand_path("../vcr_cassettes", __FILE__)
+ config.preserve_exact_body_bytes do |_response|
+ true
+ end
+ # config.debug_logger = File.open(File.expand_path("../vcr.log", __FILE__), "w")
+end
+
+VCR.insert_cassette \
+ ENV.fetch("BUNDLER_SPEC_VCR_CASSETTE_NAME"),
+ :record => :new_episodes,
+ :match_requests_on => [:method, :uri, :query]
+
+at_exit { VCR.eject_cassette }
+
+class BundlerVCRHTTP < Net::HTTP
+ # @private
+ class RequestHandler < ::VCR::RequestHandler
+ attr_reader :net_http, :request, :request_body, :response_block
+ def initialize(net_http, request, request_body = nil, &response_block)
+ @net_http = net_http
+ @request = request
+ @request_body = request_body
+ @response_block = response_block
+ @stubbed_response = nil
+ @vcr_response = nil
+ @recursing = false
+ end
+
+ def handle
+ super
+ ensure
+ invoke_after_request_hook(@vcr_response) unless @recursing
+ end
+
+ private
+
+ def on_recordable_request
+ perform_request(net_http.started?, :record_interaction)
+ end
+
+ def on_stubbed_by_vcr_request
+ status = stubbed_response.status
+ headers = stubbed_response.headers
+ body = stubbed_response.body
+
+ response_string = []
+ response_string << "HTTP/1.1 #{status.code} #{status.message}"
+
+ headers.each do |header, value|
+ response_string << "#{header}: #{value}"
+ end
+
+ response_string << "" << body
+
+ response_io = ::Net::BufferedIO.new(StringIO.new(response_string.join("\n")))
+ res = ::Net::HTTPResponse.read_new(response_io)
+
+ res.reading_body(response_io, true) do
+ yield res if block_given?
+ end
+
+ res
+ end
+
+ def on_ignored_request
+ raise "no ignored requests allowed"
+ end
+
+ def perform_request(started, record_interaction = false)
+ # Net::HTTP calls #request recursively in certain circumstances.
+ # We only want to record the request when the request is started, as
+ # that is the final time through #request.
+ unless started
+ @recursing = true
+ request.instance_variable_set(:@__vcr_request_handler, recursive_request_handler)
+ return net_http.request_without_vcr(request, request_body, &response_block)
+ end
+
+ net_http.request_without_vcr(request, request_body) do |response|
+ @vcr_response = vcr_response_from(response)
+
+ if record_interaction
+ VCR.record_http_interaction VCR::HTTPInteraction.new(vcr_request, @vcr_response)
+ end
+
+ response.extend ::VCR::Net::HTTPResponse # "unwind" the response
+ response_block.call(response) if response_block
+ end
+ end
+
+ def uri
+ @uri ||= begin
+ protocol = net_http.use_ssl? ? "https" : "http"
+
+ path = request.path
+ path = URI.parse(request.path).request_uri if request.path =~ /^http/
+
+ "#{protocol}://#{net_http.address}#{path}"
+ end
+ end
+
+ def response_hash(response)
+ (response.headers || {}).merge(
+ :body => response.body,
+ :status => [response.status.code.to_s, response.status.message]
+ )
+ end
+
+ def request_method
+ request.method.downcase.to_sym
+ end
+
+ def vcr_request
+ @vcr_request ||= VCR::Request.new \
+ request_method,
+ uri,
+ (request_body || request.body),
+ request.to_hash
+ end
+
+ def vcr_response_from(response)
+ VCR::Response.new \
+ VCR::ResponseStatus.new(response.code.to_i, response.message),
+ response.to_hash,
+ response.body,
+ response.http_version
+ end
+
+ def recursive_request_handler
+ @recursive_request_handler ||= RecursiveRequestHandler.new(
+ @after_hook_typed_request.type, @stubbed_response, @vcr_request,
+ @net_http, @request, @request_body, &@response_block
+ )
+ end
+ end
+
+ # @private
+ class RecursiveRequestHandler < RequestHandler
+ attr_reader :stubbed_response
+
+ def initialize(request_type, stubbed_response, vcr_request, *args, &response_block)
+ @request_type = request_type
+ @stubbed_response = stubbed_response
+ @vcr_request = vcr_request
+ super(*args)
+ end
+
+ def handle
+ set_typed_request_for_after_hook(@request_type)
+ send "on_#{@request_type}_request"
+ ensure
+ invoke_after_request_hook(@vcr_response)
+ end
+
+ def request_type(*args)
+ @request_type
+ end
+ end
+
+ def request_with_vcr(request, *args, &block)
+ handler = request.instance_eval do
+ remove_instance_variable(:@__vcr_request_handler) if defined?(@__vcr_request_handler)
+ end || RequestHandler.new(self, request, *args, &block)
+
+ handler.handle
+ end
+
+ alias_method :request_without_vcr, :request
+ alias_method :request, :request_with_vcr
+end
+
+# Replace Net::HTTP with our VCR subclass
+::Net.class_eval do
+ remove_const(:HTTP)
+ const_set(:HTTP, BundlerVCRHTTP)
+end
diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb
index f54b25e42a..2d60938fcb 100644
--- a/spec/support/helpers.rb
+++ b/spec/support/helpers.rb
@@ -91,11 +91,24 @@ module Spec
bundle_bin = "-S bundle"
end
+ env = options.delete(:env) || {}
+ env["PATH"].gsub!("#{Path.root}/exe", "") if env["PATH"] && system_bundler
+
requires = options.delete(:requires) || []
- if artifice = options.delete(:artifice) { "fail" unless RSpec.current_example.metadata[:realworld] }
- requires << File.expand_path("../artifice/#{artifice}.rb", __FILE__)
- end
requires << "support/hax"
+
+ artifice = options.delete(:artifice) do
+ if RSpec.current_example.metadata[:realworld]
+ "vcr"
+ else
+ "fail"
+ end
+ end
+ if artifice
+ env["BUNDLER_SPEC_VCR_CASSETTE_NAME"] = RSpec.current_example.full_description
+ requires << File.expand_path("../artifice/#{artifice}", __FILE__)
+ end
+
requires_str = requires.map {|r| "-r#{r}" }.join(" ")
load_path = []
@@ -103,8 +116,8 @@ module Spec
load_path << spec
load_path_str = "-I#{load_path.join(File::PATH_SEPARATOR)}"
- env = (options.delete(:env) || {}).map {|k, v| "#{k}='#{v}'" }.join(" ")
- env["PATH"].gsub!("#{Path.root}/exe", "") if env["PATH"] && system_bundler
+ env = env.map {|k, v| "#{k}='#{v}'" }.join(" ")
+
args = options.map do |k, v|
v == true ? " --#{k}" : " --#{k} #{v}" if v
end.join
diff --git a/spec/support/rubygems_ext.rb b/spec/support/rubygems_ext.rb
index cfc481ef83..c88779b483 100644
--- a/spec/support/rubygems_ext.rb
+++ b/spec/support/rubygems_ext.rb
@@ -19,6 +19,8 @@ module Spec
# 3.0.0 breaks 1.9.2 specs
"builder" => "2.1.2",
"bundler" => "1.12.0",
+ # 3.0 is Ruby 1.9.3+
+ "vcr" => "~> 2.9",
}
# ruby-graphviz is used by the viz tests
deps["ruby-graphviz"] = nil if RUBY_VERSION >= "1.9.3"