Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit of artifice with some tests.

  • Loading branch information...
commit 891ff45cb05f78a9ebbfd414e5e626d4dba1de57 0 parents
@wycats wycats authored
6 Gemfile
@@ -0,0 +1,6 @@
+gem "rack"
+gem "rack-test"
+
+group :test do
+ gem "rspec", "2.0.0.beta.4"
+end
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2010 Yehuda Katz
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
31 README.textile
@@ -0,0 +1,31 @@
+Artifice allows you to replace the Net::HTTP subsystem of Ruby
+with an equivalent that routes all requests to a Rack application.
+
+You can use Sinatra, raw Rack, or even Rails as your application,
+allowing you to build up an equivalent to the remote service you
+are mocking out using familiar and convenient tools to route
+requests and build up responses.
+
+h1. Usage
+
+First, require artifice.
+
+<pre>require "artifice"</pre>
+
+Next, activate artifice using a Rack endpoint.
+
+<pre>Artifice.activate_with(rack_endpoint)</pre>
+
+You're done!
+
+You can also pass a block to @activate_with@:
+
+<pre>Artifice.activate_with(rack_endpoint) do
+ # make some requests using Net::HTTP
+end</pre>
+
+This will replace Net::HTTP for the duration of the block only.
+
+You can deactivate Artifice by calling @deactivate@
+
+<pre>Artifice.deactivate</pre>
23 artifice.gemspec
@@ -0,0 +1,23 @@
+Gem::Specification.new do |s|
+ s.platform = Gem::Platform::RUBY
+ s.name = 'artifice'
+ s.version = '0.5'
+ s.summary = 'Use a Rack application for mock HTTP requests'
+ s.description = 'Replaces Net::HTTP with a subclass that routes all requests to a Rack application'
+ s.required_ruby_version = '>= 1.8.6'
+
+ s.author = 'Yehuda Katz'
+ s.email = 'wycats@gmail.com'
+ s.homepage = 'http://www.yehudakatz.com'
+ s.rubyforge_project = 'artifice'
+
+ s.files = Dir['README.markdown', 'LICENSE', 'lib/**/{*,.[a-z]*}']
+ s.require_path = 'lib'
+
+ s.rdoc_options << '--exclude' << '.'
+ s.has_rdoc = false
+
+ Bundler.definition.dependencies.select {|d| d.groups.include?(:default) }.each do |d|
+ s.add_dependency d.name, d.requirement.to_s
+ end
+end
116 lib/artifice.rb
@@ -0,0 +1,116 @@
+require "rack/test"
+require "net/http"
+require "net/https"
+
+module Artifice
+ NET_HTTP = ::Net::HTTP
+
+ # Activate Artifice with a particular Rack endpoint.
+ #
+ # Calling this method will replace the Net::HTTP system
+ # with a replacement that routes all requests to the
+ # Rack endpoint.
+ #
+ # @param [#call] endpoint A valid Rack endpoint
+ # @yield An optional block that uses Net::HTTP
+ # In this case, Artifice will be used only for
+ # the duration of the block
+ def self.activate_with(endpoint)
+ Net::HTTP.endpoint = endpoint
+ replace_net_http(Artifice::Net::HTTP)
+
+ if block_given?
+ yield
+ deactivate
+ end
+ end
+
+ # Deactivate the Artifice replacement.
+ def self.deactivate
+ replace_net_http(NET_HTTP)
+ end
+
+private
+ def self.replace_net_http(value)
+ ::Net.class_eval do
+ remove_const(:HTTP)
+ const_set(:HTTP, value)
+ end
+ end
+
+ module Net
+ class RackRequest
+ include Rack::Test::Methods
+ attr_reader :app
+
+ def initialize(app)
+ @app = app
+ end
+ end
+
+ class HTTP < ::Net::HTTP
+ class << self
+ attr_accessor :endpoint
+ end
+
+ @newimpl = true
+
+ # We don't need to connect, so blank out this method
+ def connect
+ end
+
+ # Replace the Net::HTTP request method with a method
+ # that converts the request into a Rack request and
+ # dispatches it to the Rack endpoint.
+ #
+ # @param [Net::HTTPRequest] req A Net::HTTPRequest
+ # object, or one if its subclasses
+ # @param [optional, String, #read] body This should
+ # be sent as "rack.input". If it's a String, it will
+ # be converted to a StringIO.
+ #
+ # @yield [Net::HTTPResponse] If a block is provided,
+ # this method will yield the Net::HTTPResponse to
+ # it after the body is read.
+ def request(req, body = nil, &block)
+ rack_request = RackRequest.new(self.class.endpoint)
+
+ req.each_header do |header, value|
+ rack_request.header(header, value)
+ end
+
+ scheme = use_ssl? ? "https" : "http"
+ prefix = "#{scheme}://#{addr_port}"
+
+ response = rack_request.request("#{prefix}#{req.path}",
+ {:method => req.method, :input => body})
+
+ make_net_http_response(response, &block)
+ end
+
+ private
+
+ def make_net_http_response(response)
+ status, headers, body = response.status, response.headers, response.body
+
+ response_string = []
+ response_string << "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
+
+ 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
+ end
+ end
+end
142 spec/artifice_spec.rb
@@ -0,0 +1,142 @@
+require "net/http"
+
+shared_examples_for "a working request" do
+ it "gets response headers" do
+ @response["Content-Type"].should == "text/html"
+ end
+
+ it "sends the host properly" do
+ @response["X-Test-Host"].should == "google.com"
+ end
+end
+
+shared_examples_for "a working GET request" do
+ it_should_behave_like "a working request"
+
+ it "sends the method properly" do
+ @response["X-Test-Method"].should == "GET"
+ end
+end
+
+shared_examples_for "a working POST request" do
+ it_should_behave_like "a working request"
+
+ it "sends the method properly" do
+ @response["X-Test-Method"].should == "POST"
+ end
+
+ it "sends the input properly" do
+ @response["X-Test-Input"].should == "foo=bar"
+ end
+end
+
+shared_examples_for "a working HTTP request" do
+ it "sends the scheme properly" do
+ @response["X-Test-Scheme"].should == "http"
+ end
+
+ it "sends the port properly" do
+ @response["X-Test-Port"].should == "80"
+ end
+end
+
+shared_examples_for "a working HTTPS request" do
+ it "sends the scheme properly" do
+ @response["X-Test-Scheme"].should == "https"
+ end
+
+ it "sends the port properly" do
+ @response["X-Test-Port"].should == "443"
+ end
+end
+
+describe "Artifice" do
+ NET_HTTP = ::Net::HTTP
+
+ FakeApp = proc do |env|
+ [200, {"Content-Type" => "text/html",
+ "X-Test-Method" => env["REQUEST_METHOD"],
+ "X-Test-Input" => env["rack.input"].read,
+ "X-Test-Scheme" => env["rack.url_scheme"],
+ "X-Test-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
+ "X-Test-Port" => env["SERVER_PORT"]},
+ ["Hello world"]
+ ]
+ end
+
+ require "artifice"
+
+ describe "before activating" do
+ it "does not override Net::HTTP" do
+ ::Net::HTTP.should == NET_HTTP
+ end
+ end
+
+ describe "when activating without a block" do
+ after do
+ Artifice.deactivate
+ ::Net::HTTP.should == NET_HTTP
+ end
+
+ before do
+ Artifice.activate_with(FakeApp)
+ end
+
+ it "replaces Net::HTTP" do
+ ::Net::HTTP.should_not == NET_HTTP
+ end
+
+ describe "and making a POST request with Net::HTTP.start {}" do
+ before do
+ @response = Net::HTTP.start("google.com", 80) do |http|
+ http.post("/index", "foo=bar")
+ end
+ end
+
+ it_should_behave_like "a working POST request"
+ it_should_behave_like "a working HTTP request"
+ end
+
+ describe "and making a POST request with Net::HTTP.start" do
+ before do
+ http = Net::HTTP.new("google.com", 443)
+ http.use_ssl = true
+ @response = http.post("/index", "foo=bar")
+ end
+
+ it_should_behave_like "a working POST request"
+ it_should_behave_like "a working HTTPS request"
+ end
+
+ describe "and make a GET request with Net::HTTP.start" do
+ before do
+ http = Net::HTTP.new("google.com", 80)
+ @response = http.get("/get")
+ end
+
+ it_should_behave_like "a working GET request"
+ it_should_behave_like "a working HTTP request"
+ end
+
+ describe "and make a GET request with Net::HTTP.get_response" do
+ before do
+ @response = Net::HTTP.get_response(URI.parse("http://google.com/get"))
+ end
+
+ it_should_behave_like "a working GET request"
+ it_should_behave_like "a working HTTP request"
+ end
+
+ describe "and make a GET request with Net::HTTP::Get.new" do
+ before do
+ Net::HTTP.start('google.com') do |http|
+ req = Net::HTTP::Get.new('/get')
+ @response = http.request(req)
+ end
+ end
+
+ it_should_behave_like "a working GET request"
+ it_should_behave_like "a working HTTP request"
+ end
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.