#!/usr/bin/env micropython """ Unittests for Tiny Web MIT license (C) Konstantin Belyalov 2017-2018 """ import unittest import uos as os import uerrno as errno import uasyncio as asyncio from tinyweb import webserver from tinyweb.server import urldecode_plus, parse_query_string from tinyweb.server import request, HTTPException # Helper to delete file def delete_file(fn): # "unlink" gets renamed to "remove" in micropython, # so support both if hasattr(os, 'unlink'): os.unlink(fn) else: os.remove(fn) # HTTP headers helpers def HDR(str): return '{}\r\n'.format(str) HDRE = '\r\n' class mockReader(): """Mock for coroutine reader class""" def __init__(self, lines): if type(lines) is not list: lines = [lines] self.lines = lines self.idx = 0 async def readline(self): self.idx += 1 # Convert and return str to bytes return self.lines[self.idx - 1].encode() def readexactly(self, n): return self.readline() class mockWriter(): """Mock for coroutine writer class""" def __init__(self, generate_expection=None): """ keyword arguments: generate_expection - raise exception when calling send() """ self.s = 1 self.history = [] self.closed = False self.generate_expection = generate_expection async def awrite(self, buf, off=0, sz=-1): if sz == -1: sz = len(buf) - off if self.generate_expection: raise self.generate_expection # Save biffer into history - so to be able to assert then self.history.append(buf[:sz]) async def aclose(self): self.closed = True async def mock_wait_for(coro, timeout): await coro def run_coro(coro): # Mock wait_for() function with simple dummy asyncio.wait_for = (lambda c, t: await c) """Simple helper to run coroutine""" for i in coro: pass # Tests class Utils(unittest.TestCase): def testUrldecode(self): runs = [('abc%20def', 'abc def'), ('abc%%20def', 'abc% def'), ('%%%', '%%%'), ('%20%20', ' '), ('abc', 'abc'), ('a%25%25%25c', 'a%%%c'), ('a++b', 'a b'), ('+%25+', ' % '), ('+%2B+', ' + '), ('%20+%2B+%41', ' + A'), ] for r in runs: self.assertEqual(urldecode_plus(r[0]), r[1]) def testParseQueryString(self): runs = [('k1=v2', {'k1': 'v2'}), ('k1=v2&k11=v11', {'k1': 'v2', 'k11': 'v11'}), ('k1=v2&k11=', {'k1': 'v2', 'k11': ''}), ('k1=+%20', {'k1': ' '}), ('%6b1=+%20', {'k1': ' '}), ('k1=%3d1', {'k1': '=1'}), ('11=22%26&%3d=%3d', {'11': '22&', '=': '='}), ] for r in runs: self.assertEqual(parse_query_string(r[0]), r[1]) class ServerParts(unittest.TestCase): def testRequestLine(self): runs = [('GETT / HTTP/1.1', 'GETT', '/'), ('TTEG\t/blah\tHTTP/1.1', 'TTEG', '/blah'), ('POST /qq/?q=q HTTP', 'POST', '/qq/', 'q=q'), ('POST /?q=q BSHT', 'POST', '/', 'q=q'), ('POST /?q=q&a=a JUNK', 'POST', '/', 'q=q&a=a')] for r in runs: try: req = request(mockReader(r[0])) run_coro(req.read_request_line()) self.assertEqual(r[1].encode(), req.method) self.assertEqual(r[2].encode(), req.path) if len(r) > 3: self.assertEqual(r[3].encode(), req.query_string) except Exception: self.fail('exception on payload --{}--'.format(r[0])) def testRequestLineEmptyLinesBefore(self): req = request(mockReader(['\n', '\r\n', 'GET /?a=a HTTP/1.1'])) run_coro(req.read_request_line()) self.assertEqual(b'GET', req.method) self.assertEqual(b'/', req.path) self.assertEqual(b'a=a', req.query_string) def testRequestLineNegative(self): runs = ['', '\t\t', ' ', ' / HTTP/1.1', 'GET', 'GET /', 'GET / ' ] for r in runs: with self.assertRaises(HTTPException): req = request(mockReader(r)) run_coro(req.read_request_line()) def testHeadersSimple(self): req = request(mockReader([HDR('Host: google.com'), HDRE])) run_coro(req.read_headers([b'Host'])) self.assertEqual(req.headers, {b'Host': b'google.com'}) def testHeadersSpaces(self): req = request(mockReader([HDR('Host: \t google.com \t '), HDRE])) run_coro(req.read_headers([b'Host'])) self.assertEqual(req.headers, {b'Host': b'google.com'}) def testHeadersEmptyValue(self): req = request(mockReader([HDR('Host:'), HDRE])) run_coro(req.read_headers([b'Host'])) self.assertEqual(req.headers, {b'Host': b''}) def testHeadersMultiple(self): req = request(mockReader([HDR('Host: google.com'), HDR('Junk: you blah'), HDR('Content-type: file'), HDRE])) hdrs = {b'Host': b'google.com', b'Junk': b'you blah', b'Content-type': b'file'} run_coro(req.read_headers([b'Host', b'Junk', b'Content-type'])) self.assertEqual(req.headers, hdrs) def testUrlFinderExplicit(self): urls = [('/', 1), ('/%20', 2), ('/a/b', 3), ('/aac', 5)] junk = ['//', '', '/a', '/aa', '/a/fhhfhfhfhfhf'] # Create server, add routes srv = webserver() for u in urls: srv.add_route(u[0], u[1]) # Search them all for u in urls: # Create mock request object with "pre-parsed" url path rq = request(mockReader([])) rq.path = u[0].encode() f, args = srv._find_url_handler(rq) self.assertEqual(u[1], f) # Some simple negative cases for j in junk: rq = request(mockReader([])) rq.path = j.encode() f, args = srv._find_url_handler(rq) self.assertIsNone(f) self.assertIsNone(args) def testUrlFinderParameterized(self): srv = webserver() # Add few routes srv.add_route('/', 0) srv.add_route('/<user_name>', 1) srv.add_route('/a/<id>', 2) # Check first url (non param) rq = request(mockReader([])) rq.path = b'/' f, args = srv._find_url_handler(rq) self.assertEqual(f, 0) # Check second url rq.path = b'/user1' f, args = srv._find_url_handler(rq) self.assertEqual(f, 1) self.assertEqual(args['_param_name'], 'user_name') self.assertEqual(rq._param, 'user1') # Check third url rq.path = b'/a/123456' f, args = srv._find_url_handler(rq) self.assertEqual(f, 2) self.assertEqual(args['_param_name'], 'id') self.assertEqual(rq._param, '123456') # When param is empty and there is no non param endpoint rq.path = b'/a/' f, args = srv._find_url_handler(rq) self.assertEqual(f, 2) self.assertEqual(rq._param, '') def testUrlFinderNegative(self): srv = webserver() # empty URL is not allowed with self.assertRaises(ValueError): srv.add_route('', 1) # Query string is not allowed with self.assertRaises(ValueError): srv.add_route('/?a=a', 1) # Duplicate urls srv.add_route('/duppp', 1) with self.assertRaises(ValueError): srv.add_route('/duppp', 1) # We want to test decorators as well server_for_decorators = webserver() @server_for_decorators.route('/uid/<user_id>') @server_for_decorators.route('/uid2/<user_id>') async def route_for_decorator(req, resp, user_id): await resp.start_html() await resp.send('YO, {}'.format(user_id)) @server_for_decorators.resource('/rest1/<user_id>') def resource_for_decorator1(data, user_id): return {'name': user_id} @server_for_decorators.resource('/rest2/<user_id>') async def resource_for_decorator2(data, user_id): yield '{"name": user_id}' class ServerFull(unittest.TestCase): def setUp(self): self.dummy_called = False self.data = {} # "Register" one connection into map for dedicated decor server server_for_decorators.conns[id(1)] = None self.hello_world_history = ['HTTP/1.0 200 MSG\r\n' + 'Content-Type: text/html\r\n\r\n', '<html><h1>Hello world</h1></html>'] # Create one more server - to simplify bunch of tests self.srv = webserver() self.srv.conns[id(1)] = None def testRouteDecorator1(self): """Test @.route() decorator""" # First decorator rdr = mockReader(['GET /uid/man1 HTTP/1.1\r\n', HDRE]) wrt = mockWriter() # "Send" request run_coro(server_for_decorators._handler(rdr, wrt)) # Ensure that proper response "sent" expected = ['HTTP/1.0 200 MSG\r\n' + 'Content-Type: text/html\r\n\r\n', 'YO, man1'] self.assertEqual(wrt.history, expected) self.assertTrue(wrt.closed) def testRouteDecorator2(self): # Second decorator rdr = mockReader(['GET /uid2/man2 HTTP/1.1\r\n', HDRE]) wrt = mockWriter() # Re-register connection server_for_decorators.conns[id(1)] = None # "Send" request run_coro(server_for_decorators._handler(rdr, wrt)) # Ensure that proper response "sent" expected = ['HTTP/1.0 200 MSG\r\n' + 'Content-Type: text/html\r\n\r\n', 'YO, man2'] self.assertEqual(wrt.history, expected) self.assertTrue(wrt.closed) def testResourceDecorator1(self): """Test @.resource() decorator""" rdr = mockReader(['GET /rest1/man1 HTTP/1.1\r\n', HDRE]) wrt = mockWriter() run_coro(server_for_decorators._handler(rdr, wrt)) expected = ['HTTP/1.0 200 MSG\r\n' 'Access-Control-Allow-Origin: *\r\n' + 'Access-Control-Allow-Headers: *\r\n' + 'Content-Length: 16\r\n' + 'Access-Control-Allow-Methods: GET\r\n' + 'Content-Type: application/json\r\n\r\n', '{"name": "man1"}'] self.assertEqual(wrt.history, expected) self.assertTrue(wrt.closed) def testResourceDecorator2(self): rdr = mockReader(['GET /rest2/man2 HTTP/1.1\r\n', HDRE]) wrt = mockWriter() run_coro(server_for_decorators._handler(rdr, wrt)) expected = ['HTTP/1.1 200 MSG\r\n' + 'Access-Control-Allow-Methods: GET\r\n' + 'Connection: close\r\n' + 'Access-Control-Allow-Headers: *\r\n' + 'Content-Type: application/json\r\n' + 'Transfer-Encoding: chunked\r\n' + 'Access-Control-Allow-Origin: *\r\n\r\n', '11\r\n', '{"name": user_id}', '\r\n', '0\r\n\r\n' ] self.assertEqual(wrt.history, expected) self.assertTrue(wrt.closed) async def dummy_handler(self, req, resp): """Dummy URL handler. It just records the fact - it has been called""" self.dummy_req = req self.dummy_resp = resp self.dummy_called = True async def dummy_post_handler(self, req, resp): self.data = await req.read_parse_form_data() async def hello_world_handler(self, req, resp): await resp.start_html() await resp.send('<html><h1>Hello world</h1></html>') async def redirect_handler(self, req, resp): await resp.redirect('/blahblah', msg='msg:)') def testStartHTML(self): """Verify that request.start_html() works well""" self.srv.add_route('/', self.hello_world_handler) rdr = mockReader(['GET / HTTP/1.1\r\n', HDR('Host: blah.com'), HDRE]) wrt = mockWriter() # "Send" request run_coro(self.srv._handler(rdr, wrt)) # Ensure that proper response "sent" self.assertEqual(wrt.history, self.hello_world_history) self.assertTrue(wrt.closed) def testRedirect(self): """Verify that request.start_html() works well""" self.srv.add_route('/', self.redirect_handler) rdr = mockReader(['GET / HTTP/1.1\r\n', HDR('Host: blah.com'), HDRE]) wrt = mockWriter() # "Send" request run_coro(self.srv._handler(rdr, wrt)) # Ensure that proper response "sent" exp = ['HTTP/1.0 302 MSG\r\n' + 'Location: /blahblah\r\nContent-Length: 5\r\n\r\n', 'msg:)'] self.assertEqual(wrt.history, exp) def testRequestBodyUnknownType(self): """Unknow HTTP body test - empty dict expected""" self.srv.add_route('/', self.dummy_post_handler, methods=['POST']) rdr = mockReader(['POST / HTTP/1.1\r\n', HDR('Host: blah.com'), HDR('Content-Length: 5'), HDRE, '12345']) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) # Check extracted POST body self.assertEqual(self.data, {}) def testRequestBodyJson(self): """JSON encoded POST body""" self.srv.add_route('/', self.dummy_post_handler, methods=['POST'], save_headers=['Content-Type', 'Content-Length']) rdr = mockReader(['POST / HTTP/1.1\r\n', HDR('Content-Type: application/json'), HDR('Content-Length: 10'), HDRE, '{"a": "b"}']) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) # Check parsed POST body self.assertEqual(self.data, {'a': 'b'}) def testRequestBodyUrlencoded(self): """Regular HTML form""" self.srv.add_route('/', self.dummy_post_handler, methods=['POST'], save_headers=['Content-Type', 'Content-Length']) rdr = mockReader(['POST / HTTP/1.1\r\n', HDR('Content-Type: application/x-www-form-urlencoded; charset=UTF-8'), HDR('Content-Length: 10'), HDRE, 'a=b&c=%20d']) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) # Check parsed POST body self.assertEqual(self.data, {'a': 'b', 'c': ' d'}) def testRequestBodyNegative(self): """Regular HTML form""" self.srv.add_route('/', self.dummy_post_handler, methods=['POST'], save_headers=['Content-Type', 'Content-Length']) rdr = mockReader(['POST / HTTP/1.1\r\n', HDR('Content-Type: application/json'), HDR('Content-Length: 9'), HDRE, 'some junk']) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) # payload broken - HTTP 400 expected self.assertEqual(wrt.history, ['HTTP/1.0 400 MSG\r\n\r\n']) def testRequestLargeBody(self): """Max Body size check""" self.srv.add_route('/', self.dummy_post_handler, methods=['POST'], save_headers=['Content-Type', 'Content-Length'], max_body_size=5) rdr = mockReader(['POST / HTTP/1.1\r\n', HDR('Content-Type: application/json'), HDR('Content-Length: 9'), HDRE, 'some junk']) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) # payload broken - HTTP 400 expected self.assertEqual(wrt.history, ['HTTP/1.0 413 MSG\r\n\r\n']) async def route_parameterized_handler(self, req, resp, user_name): await resp.start_html() await resp.send('<html>Hello, {}</html>'.format(user_name)) def testRouteParameterized(self): """Verify that route with params works fine""" self.srv.add_route('/db/<user_name>', self.route_parameterized_handler) rdr = mockReader(['GET /db/user1 HTTP/1.1\r\n', HDR('Host: junk.com'), HDRE]) wrt = mockWriter() # "Send" request run_coro(self.srv._handler(rdr, wrt)) # Ensure that proper response "sent" expected = ['HTTP/1.0 200 MSG\r\n' + 'Content-Type: text/html\r\n\r\n', '<html>Hello, user1</html>'] self.assertEqual(wrt.history, expected) self.assertTrue(wrt.closed) def testParseHeadersOnOff(self): """Verify parameter parse_headers works""" self.srv.add_route('/', self.dummy_handler, save_headers=['H1', 'H2']) rdr = mockReader(['GET / HTTP/1.1\r\n', HDR('H1: blah.com'), HDR('H2: lalalla'), HDR('Junk: fsdfmsdjfgjsdfjunk.com'), HDRE]) # "Send" request wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) self.assertTrue(self.dummy_called) # Check for headers - only 2 of 3 should be collected, others - ignore hdrs = {b'H1': b'blah.com', b'H2': b'lalalla'} self.assertEqual(self.dummy_req.headers, hdrs) self.assertTrue(wrt.closed) def testDisallowedMethod(self): """Verify that server respects allowed methods""" self.srv.add_route('/', self.hello_world_handler) self.srv.add_route('/post_only', self.dummy_handler, methods=['POST']) rdr = mockReader(['GET / HTTP/1.0\r\n', HDRE]) # "Send" GET request, by default GET is enabled wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) self.assertEqual(wrt.history, self.hello_world_history) self.assertTrue(wrt.closed) # "Send" GET request to POST only location self.srv.conns[id(1)] = None self.dummy_called = False rdr = mockReader(['GET /post_only HTTP/1.1\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) # Hanlder should not be called - method not allowed self.assertFalse(self.dummy_called) exp = ['HTTP/1.0 405 MSG\r\n\r\n'] self.assertEqual(wrt.history, exp) # Connection must be closed self.assertTrue(wrt.closed) def testAutoOptionsMethod(self): """Test auto implementation of OPTIONS method""" self.srv.add_route('/', self.hello_world_handler, methods=['POST', 'PUT', 'DELETE']) self.srv.add_route('/disabled', self.hello_world_handler, auto_method_options=False) rdr = mockReader(['OPTIONS / HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 200 MSG\r\n' + 'Access-Control-Allow-Headers: *\r\n' 'Content-Length: 0\r\n' 'Access-Control-Allow-Origin: *\r\n' 'Access-Control-Allow-Methods: POST, PUT, DELETE\r\n\r\n'] self.assertEqual(wrt.history, exp) self.assertTrue(wrt.closed) def testPageNotFound(self): """Verify that malformed request generates proper response""" rdr = mockReader(['GET /not_existing HTTP/1.1\r\n', HDR('Host: blah.com'), HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 404 MSG\r\n\r\n'] self.assertEqual(wrt.history, exp) # Connection must be closed self.assertTrue(wrt.closed) def testMalformedRequest(self): """Verify that malformed request generates proper response""" rdr = mockReader(['GET /\r\n', HDR('Host: blah.com'), HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 400 MSG\r\n\r\n'] self.assertEqual(wrt.history, exp) # Connection must be closed self.assertTrue(wrt.closed) class ResourceGetPost(): """Simple REST API resource class with just two methods""" def get(self, data): return {'data1': 'junk'} def post(self, data): return data class ResourceGetParam(): """Parameterized REST API resource""" def __init__(self): self.user_id = 'user_id' def get(self, data, user_id): return {self.user_id: user_id} class ResourceGetArgs(): """REST API resource with additional arguments""" def get(self, data, arg1, arg2): return {'arg1': arg1, 'arg2': arg2} class ResourceGenerator(): """REST API with generator as result""" async def get(self, data): yield 'longlongchunkchunk1' yield 'chunk2' # unicode support yield '\u265E' class ResourceNegative(): """To cover negative test cases""" def delete(self, data): # Broken pipe emulation raise OSError(32, '', '') def put(self, data): # Simple unhandled expection raise Exception('something') class ServerResource(unittest.TestCase): def setUp(self): self.srv = webserver() self.srv.conns[id(1)] = None self.srv.add_resource(ResourceGetPost, '/') self.srv.add_resource(ResourceGetParam, '/param/<user_id>') self.srv.add_resource(ResourceGetArgs, '/args', arg1=1, arg2=2) self.srv.add_resource(ResourceGenerator, '/gen') self.srv.add_resource(ResourceNegative, '/negative') def testOptions(self): # Ensure that only GET/POST methods are allowed: rdr = mockReader(['OPTIONS / HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 200 MSG\r\n' + 'Access-Control-Allow-Headers: *\r\n' 'Content-Length: 0\r\n' 'Access-Control-Allow-Origin: *\r\n' 'Access-Control-Allow-Methods: GET, POST\r\n\r\n'] self.assertEqual(wrt.history, exp) def testGet(self): rdr = mockReader(['GET / HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 200 MSG\r\n' + 'Access-Control-Allow-Origin: *\r\n' 'Access-Control-Allow-Headers: *\r\n' 'Content-Length: 17\r\n' 'Access-Control-Allow-Methods: GET, POST\r\n' 'Content-Type: application/json\r\n\r\n', '{"data1": "junk"}'] self.assertEqual(wrt.history, exp) def testGetWithParam(self): rdr = mockReader(['GET /param/123 HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 200 MSG\r\n' + 'Access-Control-Allow-Origin: *\r\n' 'Access-Control-Allow-Headers: *\r\n' 'Content-Length: 18\r\n' 'Access-Control-Allow-Methods: GET\r\n' 'Content-Type: application/json\r\n\r\n', '{"user_id": "123"}'] self.assertEqual(wrt.history, exp) def testGetWithArgs(self): rdr = mockReader(['GET /args HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 200 MSG\r\n' + 'Access-Control-Allow-Origin: *\r\n' 'Access-Control-Allow-Headers: *\r\n' 'Content-Length: 22\r\n' 'Access-Control-Allow-Methods: GET\r\n' 'Content-Type: application/json\r\n\r\n', '{"arg1": 1, "arg2": 2}'] self.assertEqual(wrt.history, exp) def testGenerator(self): rdr = mockReader(['GET /gen HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.1 200 MSG\r\n' + 'Access-Control-Allow-Methods: GET\r\n' + 'Connection: close\r\n' + 'Access-Control-Allow-Headers: *\r\n' + 'Content-Type: application/json\r\n' + 'Transfer-Encoding: chunked\r\n' + 'Access-Control-Allow-Origin: *\r\n\r\n', '13\r\n', 'longlongchunkchunk1', '\r\n', '6\r\n', 'chunk2', '\r\n', # next chunk is 1 char len UTF-8 string '3\r\n', '\u265E', '\r\n', '0\r\n\r\n'] self.assertEqual(wrt.history, exp) def testPost(self): # Ensure that parameters from query string / body will be combined as well rdr = mockReader(['POST /?qs=qs1 HTTP/1.0\r\n', HDR('Content-Length: 17'), HDR('Content-Type: application/json'), HDRE, '{"body": "body1"}']) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 200 MSG\r\n' + 'Access-Control-Allow-Origin: *\r\n' 'Access-Control-Allow-Headers: *\r\n' 'Content-Length: 30\r\n' 'Access-Control-Allow-Methods: GET, POST\r\n' 'Content-Type: application/json\r\n\r\n', '{"qs": "qs1", "body": "body1"}'] self.assertEqual(wrt.history, exp) def testInvalidMethod(self): rdr = mockReader(['PUT / HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 405 MSG\r\n\r\n'] self.assertEqual(wrt.history, exp) def testException(self): rdr = mockReader(['PUT /negative HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 500 MSG\r\n\r\n'] self.assertEqual(wrt.history, exp) def testBrokenPipe(self): rdr = mockReader(['DELETE /negative HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) self.assertEqual(wrt.history, []) class StaticContent(unittest.TestCase): def setUp(self): self.srv = webserver() self.srv.conns[id(1)] = None self.tempfn = '__tmp.html' self.ctype = None self.etype = None self.max_age = 2592000 with open(self.tempfn, 'wb') as f: f.write('someContent blah blah') def tearDown(self): try: delete_file(self.tempfn) except OSError: pass async def send_file_handler(self, req, resp): await resp.send_file(self.tempfn, content_type=self.ctype, content_encoding=self.etype, max_age=self.max_age) def testSendFileManual(self): """Verify send_file works great with manually defined parameters""" self.ctype = 'text/plain' self.etype = 'gzip' self.max_age = 100 self.srv.add_route('/', self.send_file_handler) rdr = mockReader(['GET / HTTP/1.0\r\n', HDRE]) wrt = mockWriter() run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 200 MSG\r\n' + 'Cache-Control: max-age=100, public\r\n' 'Content-Type: text/plain\r\n' 'Content-Length: 21\r\n' 'Content-Encoding: gzip\r\n\r\n', bytearray(b'someContent blah blah')] self.assertEqual(wrt.history, exp) self.assertTrue(wrt.closed) def testSendFileNotFound(self): """Verify 404 error for non existing files""" self.srv.add_route('/', self.send_file_handler) rdr = mockReader(['GET / HTTP/1.0\r\n', HDRE]) wrt = mockWriter() # Intentionally delete file before request delete_file(self.tempfn) run_coro(self.srv._handler(rdr, wrt)) exp = ['HTTP/1.0 404 MSG\r\n\r\n'] self.assertEqual(wrt.history, exp) self.assertTrue(wrt.closed) def testSendFileConnectionReset(self): self.srv.add_route('/', self.send_file_handler) rdr = mockReader(['GET / HTTP/1.0\r\n', HDRE]) # tell mockWrite to raise error during send() wrt = mockWriter(generate_expection=OSError(errno.ECONNRESET)) run_coro(self.srv._handler(rdr, wrt)) # there should be no payload due to connected reset self.assertEqual(wrt.history, []) self.assertTrue(wrt.closed) if __name__ == '__main__': unittest.main()