require 'webrick' require 'singleton' require 'tap/test/subset_test' module Tap module Test # HttpTest facilitates testing of HTTP requests by initializing a # Webrick server that echos requests, and providing methods to # validate echoed requests. # module HttpTest # Server echos back all requests. class Server include Singleton include WEBrick attr_accessor :server_thread, :server def initialize # set the default log level to warn to prevent general access logs, unless otherwise specified log_level = ENV["WEB_LOG_LEVEL"] ? ENV["WEB_LOG_LEVEL"].upcase : "WARN" logger = Log.new($stderr, Log.const_get( log_level ) ) self.server = HTTPServer.new(:Port => 2000, :Logger => logger, :AccessLog => [ [ logger, AccessLog::COMMON_LOG_FORMAT ], [ logger, AccessLog::REFERER_LOG_FORMAT ]]) server.mount_proc("/") do |req, res| res.body << req.request_line res.body << req.raw_header.join('') # an extra line must be added to delimit the headers from the body. if req.body res.body << "\r\n" res.body << req.body end res['Content-Type'] = "text/html" end end # Starts the server on a new thread. def start_web_server self.server_thread ||= Thread.new { server.start } end end def self.included(base) base.send(:include, Tap::Test::SubsetTest) end # WEB subset of tests. Starts HTTPTest::Server if necessary # and yields to the block. def web_test subset_test("WEB", "w") do Server.instance.start_web_server yield end end REQUEST_ATTRIBUTES = %w{ request_method http_version host port path script_name path_info header cookies query accept accept_charset accept_encoding accept_language user addr peeraddr attributes keep_alive} UNCHECKED_REQUEST_ATTRIBUTES = %w{ request_line unparsed_uri request_uri request_time raw_header query_string} # Parses expected and actual as an http request (using Tap::Net.parse_http_request) # and asserts that all of the REQUEST_ATTRIBUTES are equal. See the parse_http_request # documentation for some important notes, particularly involving "\r\n" vs "\n" and # post requests. def assert_request_equal(expected, actual) e = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) e.parse( StringIO.new(expected) ) a = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) a.parse( StringIO.new(actual) ) errors = [] REQUEST_ATTRIBUTES.each do |attribute| exp = e.send(attribute) act = a.send(attribute) next if exp == act errors << "<#{PP.singleline_pp(exp, '')}> expected for #{attribute} but was:\n<#{PP.singleline_pp(act, '')}>." end if errors.empty? # this rather unecessary assertion is used simply to # make assert_request_equal cause an assertion. assert errors.empty? else flunk errors.join("\n") end end # Convenience method that strips str, strips each line of str, and # then rejoins them using "\r\n" as is typical of HTTP messages. # # strip_align %Q{ # GET /echo HTTP/1.1 # Accept: */* # Host: localhost:2000} # # # => "GET /echo HTTP/1.1\r\nAccept: */*\r\nHost: localhost:2000\r\n" def strip_align(str) str.strip.split(/\r?\n/).collect do |line| "#{line.strip}\r\n" end.compact.join('') end # Turns a hash of parameters into an encoded HTTP query string. # Multiple values for a given key can be specified by an array. # # to_query('key' => 'value', 'array' => ['one', 'two']) # => "array=one&array=two&key=value" # # Note: the order of the parameters in the result is determined # by hash.each_pair and is thus fairly unpredicatable. def to_query(hash) query = [] hash.each_pair do |key,values| values = values.kind_of?(Array) ? values : [values] values.each { |value| query << "#{key}=#{value}" } end URI.encode(query.join('&')) end end end end