HTTPリクエストのモックを作る

SimpleHTTPRequestHandlerを利用してHTTPリクエストのモック(MockConnection)を作りました。というのはたぶん奇妙な言い方で、SimpleHTTPRequestHandlerの動作を確認してみました。

#!/usr/bin/env python2.6
import unittest
from test import test_support
from SimpleHTTPServer import SimpleHTTPRequestHandler
import os
import StringIO
import re
class MockConnection(object):
    def __init__(self, path):
        self.path = path
    def makefile(self, mode='r', bufsize=-1):
        if mode == 'wb':
            return StringIO.StringIO()
        else:
            return open(self.path, mode, bufsize)
class TestRequestHandler(SimpleHTTPRequestHandler):
    def __init__(self, request, client_address, server):
        SimpleHTTPRequestHandler.__init__(self, request, client_address, server)
    def setup(self):
        SimpleHTTPRequestHandler.setup(self)
        self.rfile.seek(0)
    def finish(self):
        self.response = self.wfile.getvalue()
        SimpleHTTPRequestHandler.finish(self)
    def getresponse(self):
        return self.response
WEEK_REG =     "(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)"
MONTH_REG =    "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)"
DATETIME_REG = " %s, \d{2} %s \d{4} \d{2}:\d{2}:\d{2} GMT" % (WEEK_REG, MONTH_REG)
HEADER_REG =   "HTTP/1\.0 %d %s\r\n"\
               "Server: SimpleHTTP/0\.6 Python/2\.6\.5\r\n"\
               "Date:%s\r\n"\
               "Content-(?:T|t)ype: %s\r\n"
ERROR_REG =    "<head>\n"\
               "<title>Error response</title>\n"\
               "</head>\n"\
               "<body>\n"\
               "<h1>Error response</h1>\n"\
               "<p>Error code %d\.\n"\
               "<p>Message: %s\.\n"\
               "<p>Error code explanation: %d = %s\.\n"\
               "</body>\n" 
class HandlerTests(unittest.TestCase):
    @staticmethod
    def make_handler(request):
        path = "tmp.txt"
        f = open(path, 'aw')
        f.write(request)
        f.close()
        con = MockConnection(path)
        return TestRequestHandler(con, ('', 2323), None)
    def tearDown(self):
        path = "tmp.txt"
        if os.path.exists(path):
            os.remove(path)    
class HTTPRequestHandlerTests(HandlerTests):
    def test_mock_connection_09(self):
        request = "GET /_files/hello.txt HTTP/0.9\r\n"\
                  "Connection: close\r\n"\
                  "\r\n"
        self.assertEqual("Hello World\n", self.make_handler(request).getresponse())
    @staticmethod
    def is_match(buf, values, request):
        exp = ''.join(buf)
        return re.match(exp % values, HTTPRequestHandlerTests.make_handler(request).getresponse()) is not None
    def test_mock_connection_GET_200(self):
        request = "GET /_files/hello.txt HTTP/1.0\r\n"\
                  "Connection: close\r\n"\
                  "\r\n"
        buf = [HEADER_REG,
               "Content-Length: \d+\r\n"
               "Last-Modified:%s\r\n"
               "\r\n"
               "Hello World\n"
              ]
        self.assertTrue(self.is_match(buf, (200, "OK", DATETIME_REG, "text/plain", DATETIME_REG), request))
    def test_mock_connection_HEAD_200(self):
        request = "HEAD /_files/hello.txt HTTP/1.0\r\n"\
                  "Connection: close\r\n"\
                  "\r\n"
        buf = [HEADER_REG,
               "Content-Length: \d+\r\n"
               "Last-Modified:%s\r\n"
               "\r\n"
              ]
        self.assertTrue(self.is_match(buf, (200, "OK", DATETIME_REG, "text/plain", DATETIME_REG), request)) 
    def test_mock_connection_dir(self):
        request = "GET /_files/ HTTP/1.0\r\n"\
                  "Connection: close\r\n"\
                  "\r\n"
        buf = [HEADER_REG,        
        "Content-Length: \d+\r\n"
        "\r\n"
        "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\"><html>\n"
        "<title>Directory listing for /_files/</title>\n"
        "<body>\n<h2>Directory listing for /_files/</h2>\n"
        "<hr>\n"
        "<ul>\n"
        "<li><a href=\"hello.txt\">hello.txt</a>\n"
        "</ul>\n"
        "<hr>\n"
        "</body>\n"
        "</html>\n"
        ]
        self.assertTrue(self.is_match(buf, (200, "OK", DATETIME_REG, "text/html"), request))
    def test_mock_connection_501(self):
        request = "SPAM /_files/hello.txt HTTP/1.0\r\n"\
                  "Connection: close\r\n"\
                  "\r\n"        
        code = 501
        shortmessage =  "Unsupported method \('SPAM'\)"
        longmessage = "Server does not support this operation"
        self.assertTrue(self.is_match([HEADER_REG, "Connection: close\r\n\r\n", ERROR_REG],
          (code, shortmessage, DATETIME_REG,\
        "text/html", code, shortmessage, code, longmessage), request))
    def test_mock_connection_404(self):
        request = "GET /no_such_file.txt HTTP/1.0\r\n"\
                  "Connection: close\r\n"\
                  "\r\n"        
        code = 404
        shortmessage = "File not found"
        longmessage  = "Nothing matches the given URI"
        self.assertTrue(self.is_match([HEADER_REG, "Connection: close\r\n\r\n", ERROR_REG],
          (code, shortmessage, DATETIME_REG,\
        "text/html", code, shortmessage, code, longmessage), request)) 

class HTTPRequestHandlerMethodTests(HandlerTests):    
    def test_parse_request_valid(self):
        handler = self.make_handler("")
        handler.rfile = StringIO.StringIO("GET /_files/hello.txt HTTP/1.1\r\n"
                                          "Connection: close\r\n"
                                          "\r\n"
                                          )
        handler.raw_requestline = handler.rfile.readline()
        self.assertTrue(handler.parse_request())
        self.assertEqual("GET", handler.command)
        self.assertEqual("/_files/hello.txt", handler.path)
        self.assertEqual("HTTP/1.1", handler.request_version)
    def test_parse_request_bad_request_version(self):
        handler = self.make_handler("")
        handler.rfile = StringIO.StringIO("GET /_files/hello.txt BAD_REQUEST_VERSION\r\n"
                                          "Connection: close\r\n"
                                          "\r\n"
                                          )
        handler.wfile = StringIO.StringIO()
        handler.raw_requestline = handler.rfile.readline()
        self.assertFalse(handler.parse_request())
        code = 400
        shortmessage = "Bad request version \('BAD_REQUEST_VERSION'\)"
        longmessage =  "Bad request syntax or unsupported method"
        source = ERROR_REG % (code, shortmessage, code, longmessage)
        self.assertTrue(re.match(source, handler.wfile.getvalue()) is not None)
        handler.finish()
    def test_parse_request_invalid_http_version(self):
        handler = self.make_handler("")
        handler.rfile = StringIO.StringIO("GET /_files/hello.txt HTTP/2.0\r\n"
                                          "Connection: close\r\n"
                                          "\r\n"
                                          )
        handler.wfile = StringIO.StringIO()
        handler.raw_requestline = handler.rfile.readline()
        self.assertFalse(handler.parse_request())
        code = 505
        shortmessage = "Invalid HTTP Version \(2.0\)"
        longmessage =  "Cannot fulfill request\."
        source = ERROR_REG % (code, shortmessage, code, longmessage)
        self.assertTrue(re.match(source, handler.wfile.getvalue()) is not None)
        handler.finish()
    def test_parse_bad_http_0_9_request_type(self):
        handler = self.make_handler("")
        handler.rfile = StringIO.StringIO("HEAD /_files/hello.txt \r\n"
                                          "Connection: close\r\n"
                                          "\r\n"
                                          )
        handler.wfile = StringIO.StringIO()
        handler.raw_requestline = handler.rfile.readline()
        self.assertFalse(handler.parse_request())
        code = 400
        shortmessage = "Bad HTTP/0.9 request type \('HEAD'\)"
        longmessage =  "Bad request syntax or unsupported method"
        source = ERROR_REG % (code, shortmessage, code, longmessage)
        self.assertTrue(re.match(source, handler.wfile.getvalue()) is not None)
        handler.finish()
    def test_parse_bad_request_syntax(self):
        handler = self.make_handler("")
        handler.rfile = StringIO.StringIO("ONE_WORD_CASE\r\n"
                                          "Connection: close\r\n"
                                          "\r\n"
                                          )
        handler.wfile = StringIO.StringIO()
        handler.raw_requestline = handler.rfile.readline()
        self.assertFalse(handler.parse_request())
        code = 400
        shortmessage = "Bad request syntax \('ONE_WORD_CASE'\)"
        longmessage =  "Bad request syntax or unsupported method"
        source = ERROR_REG % (code, shortmessage, code, longmessage)
        self.assertTrue(re.match(source, handler.wfile.getvalue()) is not None)
        handler.finish()
    def test_send_message(self):
        handler = self.make_handler("")
        handler.rfile = StringIO.StringIO("GET PATH HTTP/1.0\r\n"
                                          "Connection: close\r\n"
                                          "\r\n"
                                          )
        handler.raw_requestline = handler.rfile.readline()
        self.assertTrue(handler.parse_request())
        handler.wfile = StringIO.StringIO()
        handler.close_connection = 0
        handler.send_header("keyword", "value") 
        self.assertEqual(
        "keyword: value\r\n"
        , handler.wfile.getvalue())
        self.assertEqual(0, handler.close_connection)
        handler.finish()
    def test_send_message_close(self):
        handler = self.make_handler("")
        handler.rfile = StringIO.StringIO("GET PATH HTTP/1.0\r\n"
                                          "Connection: close\r\n"
                                          "\r\n"
                                          )
        handler.raw_requestline = handler.rfile.readline()
        self.assertTrue(handler.parse_request())
        handler.wfile = StringIO.StringIO()
        handler.close_connection = 0
        handler.send_header("Connection", "close") 
        self.assertEqual("Connection: close\r\n"
        , handler.wfile.getvalue())
        self.assertEqual(1, handler.close_connection)
        handler.finish()
    def test_send_response(self):
        handler = self.make_handler("")
        handler.rfile = StringIO.StringIO("GET PATH HTTP/1.0\r\n"
                                          "Connection: close\r\n"
                                          "\r\n"
                                          )
        handler.raw_requestline = handler.rfile.readline()
        self.assertTrue(handler.parse_request())
        handler.wfile = StringIO.StringIO()
        handler.send_response(1024, "message")
        source = "HTTP/1.0 1024 message\r\n"\
                 "Server: SimpleHTTP/0.6 Python/2.6.5\r\n"\
                 "Date:%s\r\n" % DATETIME_REG
        self.assertTrue(re.match(source, handler.wfile.getvalue()) is not None)
        handler.finish()
if __name__ == '__main__':
    test_support.run_unittest(HTTPRequestHandlerTests, HTTPRequestHandlerMethodTests)