Test client
This commit is contained in:
18
docs/api.rst
18
docs/api.rst
@@ -60,6 +60,24 @@ and coroutines.
|
|||||||
:inherited-members:
|
:inherited-members:
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
``microdot_test_client`` module
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
The ``microdot_test_client`` module defines a test client that can be used to
|
||||||
|
create automated tests for the Microdot server.
|
||||||
|
|
||||||
|
``TestClient`` class
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: microdot_test_client.TestClient
|
||||||
|
:members:
|
||||||
|
|
||||||
|
``TestResponse`` class
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: microdot_test_client.TestResponse
|
||||||
|
:members:
|
||||||
|
|
||||||
``microdot_wsgi`` module
|
``microdot_wsgi`` module
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,22 @@ Maintaing Secure User Sessions
|
|||||||
`hashlib <https://github.com/miguelgrinberg/micropython-lib/blob/ujwt-module/python-stdlib/hashlib>`_,
|
`hashlib <https://github.com/miguelgrinberg/micropython-lib/blob/ujwt-module/python-stdlib/hashlib>`_,
|
||||||
`warnings <https://github.com/micropython/micropython-lib/blob/master/python-stdlib/warnings/warnings.py>`_
|
`warnings <https://github.com/micropython/micropython-lib/blob/master/python-stdlib/warnings/warnings.py>`_
|
||||||
|
|
||||||
|
Test Client
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. list-table::
|
||||||
|
:align: left
|
||||||
|
|
||||||
|
* - Compatibility
|
||||||
|
- | CPython & MicroPython
|
||||||
|
|
||||||
|
* - Required Microdot source files
|
||||||
|
- | `microdot.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot.py>`_
|
||||||
|
| `microdot_test_client.py <https://github.com/miguelgrinberg/microdot/tree/main/src/microdot_test_client.py>`_
|
||||||
|
|
||||||
|
* - Required external dependencies
|
||||||
|
- | None
|
||||||
|
|
||||||
Deploying on a Production Web Server
|
Deploying on a Production Web Server
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
@@ -727,5 +727,5 @@ Another option is to create a response object directly in the route function::
|
|||||||
Standard cookies do not offer sufficient privacy and security controls, so
|
Standard cookies do not offer sufficient privacy and security controls, so
|
||||||
never store sensitive information in them unless you are adding additional
|
never store sensitive information in them unless you are adding additional
|
||||||
protection mechanisms such as encryption or cryptographic signing. The
|
protection mechanisms such as encryption or cryptographic signing. The
|
||||||
:ref:`session <Maintaing Secure User Sessions>` extension implements signed cookies that prevent tampering
|
:ref:`session <Maintaing Secure User Sessions>` extension implements signed
|
||||||
by malicious actors.
|
cookies that prevent tampering by malicious actors.
|
||||||
|
|||||||
@@ -485,7 +485,7 @@ class Response():
|
|||||||
can_flush = hasattr(stream, 'flush')
|
can_flush = hasattr(stream, 'flush')
|
||||||
try:
|
try:
|
||||||
for body in self.body_iter():
|
for body in self.body_iter():
|
||||||
if isinstance(body, str):
|
if isinstance(body, str): # pragma: no cover
|
||||||
body = body.encode()
|
body = body.encode()
|
||||||
stream.write(body)
|
stream.write(body)
|
||||||
if can_flush: # pragma: no cover
|
if can_flush: # pragma: no cover
|
||||||
|
|||||||
189
src/microdot_test_client.py
Normal file
189
src/microdot_test_client.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
import json
|
||||||
|
from microdot import Request
|
||||||
|
|
||||||
|
|
||||||
|
class TestResponse:
|
||||||
|
"""A response object issued by the Microdot test client."""
|
||||||
|
def __init__(self, res):
|
||||||
|
#: The numeric status code returned by the server.
|
||||||
|
self.status_code = res.status_code
|
||||||
|
#: The text reason associated with the status response, such as
|
||||||
|
#: ``'OK'`` or ``'NOT FOUND'``.
|
||||||
|
self.reason = res.reason
|
||||||
|
#: A dictionary with the response headers.
|
||||||
|
self.headers = res.headers
|
||||||
|
#: The body of the response, as a bytes object.
|
||||||
|
self.body = b''
|
||||||
|
for body in res.body_iter():
|
||||||
|
if isinstance(body, str):
|
||||||
|
body = body.encode()
|
||||||
|
self.body += body
|
||||||
|
try:
|
||||||
|
#: The body of the response, decoded to a UTF-8 string. Set to
|
||||||
|
#: ``None`` if the response cannot be represented as UTF-8 text.
|
||||||
|
self.text = self.body.decode()
|
||||||
|
except ValueError:
|
||||||
|
self.text = None
|
||||||
|
#: The body of the JSON response, decoded to a dictionary or list. Set
|
||||||
|
#: ``Note`` if the response does not have a JSON payload.
|
||||||
|
self.json = None
|
||||||
|
for name, value in self.headers.items(): # pragma: no branch
|
||||||
|
if name.lower() == 'content-type':
|
||||||
|
if value.lower() == 'application/json':
|
||||||
|
self.json = json.loads(self.text)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class TestClient:
|
||||||
|
"""A test client for Microdot.
|
||||||
|
|
||||||
|
:param app: The Microdot application instance.
|
||||||
|
:param cookies: A dictionary of cookies to use when sending requests to the
|
||||||
|
application.
|
||||||
|
|
||||||
|
The following example shows how to create a test client for an application
|
||||||
|
and send a test request::
|
||||||
|
|
||||||
|
from microdot import Microdot
|
||||||
|
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.get('/')
|
||||||
|
def index():
|
||||||
|
return 'Hello, World!'
|
||||||
|
|
||||||
|
def test_hello_world(self):
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.get('/')
|
||||||
|
assert res.status_code == 200
|
||||||
|
assert res.text == 'Hello, World!'
|
||||||
|
"""
|
||||||
|
def __init__(self, app, cookies=None):
|
||||||
|
self.app = app
|
||||||
|
self.cookies = cookies or {}
|
||||||
|
|
||||||
|
def request(self, method, path, headers=None, body=None):
|
||||||
|
if headers is None: # pragma: no branch
|
||||||
|
headers = {}
|
||||||
|
if body is None:
|
||||||
|
body = b''
|
||||||
|
elif isinstance(body, (dict, list)):
|
||||||
|
body = json.dumps(body).encode()
|
||||||
|
if 'Content-Type' not in headers and \
|
||||||
|
'content-type' not in headers: # pragma: no cover
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
elif isinstance(body, str):
|
||||||
|
body = body.encode()
|
||||||
|
if body and 'Content-Length' not in headers and \
|
||||||
|
'content-length' not in headers:
|
||||||
|
headers['Content-Length'] = str(len(body))
|
||||||
|
cookies = ''
|
||||||
|
for name, value in self.cookies.items():
|
||||||
|
if cookies:
|
||||||
|
cookies += '; '
|
||||||
|
cookies += name + '=' + value
|
||||||
|
if cookies:
|
||||||
|
if 'Cookie' in headers:
|
||||||
|
headers['Cookie'] += '; ' + cookies
|
||||||
|
else:
|
||||||
|
headers['Cookie'] = cookies
|
||||||
|
request_bytes = '{method} {path} HTTP/1.0\n'.format(
|
||||||
|
method=method, path=path)
|
||||||
|
if 'Host' not in headers: # pragma: no branch
|
||||||
|
headers['Host'] = 'example.com:1234'
|
||||||
|
for header, value in headers.items():
|
||||||
|
request_bytes += '{header}: {value}\n'.format(
|
||||||
|
header=header, value=value)
|
||||||
|
request_bytes = request_bytes.encode() + b'\n' + body
|
||||||
|
|
||||||
|
req = Request.create(self.app, BytesIO(request_bytes),
|
||||||
|
('127.0.0.1', 1234))
|
||||||
|
res = self.app.dispatch_request(req)
|
||||||
|
res.complete()
|
||||||
|
|
||||||
|
for name, value in res.headers.items():
|
||||||
|
if name.lower() == 'set-cookie':
|
||||||
|
for cookie in value:
|
||||||
|
cookie_name, cookie_value = cookie.split('=', 1)
|
||||||
|
cookie_options = cookie_value.split(';')
|
||||||
|
delete = False
|
||||||
|
for option in cookie_options[1:]:
|
||||||
|
if option.strip().lower().startswith('expires='):
|
||||||
|
_, e = option.strip().split('=', 1)
|
||||||
|
# this is a very limited parser for cookie expiry
|
||||||
|
# that only detects a cookie deletion request when
|
||||||
|
# the date is 1/1/1970
|
||||||
|
if '1 jan 1970' in e.lower(): # pragma: no branch
|
||||||
|
delete = True
|
||||||
|
break
|
||||||
|
if delete:
|
||||||
|
if cookie_name in self.cookies: # pragma: no branch
|
||||||
|
del self.cookies[cookie_name]
|
||||||
|
else:
|
||||||
|
self.cookies[cookie_name] = cookie_options[0]
|
||||||
|
return TestResponse(res)
|
||||||
|
|
||||||
|
def get(self, path, headers=None):
|
||||||
|
"""Send a GET request to the application.
|
||||||
|
|
||||||
|
:param path: The request URL.
|
||||||
|
:param headers: A dictionary of headers to send with the request.
|
||||||
|
|
||||||
|
This method returns a
|
||||||
|
:class:`TestResponse <microdot_test_client.TestResponse>` object.
|
||||||
|
"""
|
||||||
|
return self.request('GET', path, headers=headers)
|
||||||
|
|
||||||
|
def post(self, path, headers=None, body=None):
|
||||||
|
"""Send a POST request to the application.
|
||||||
|
|
||||||
|
:param path: The request URL.
|
||||||
|
:param headers: A dictionary of headers to send with the request.
|
||||||
|
:param body: The request body. If a dictionary or list is provided,
|
||||||
|
a JSON-encoded body will be sent. A string body is encoded
|
||||||
|
to bytes as UTF-8. A bytes body is sent as-is.
|
||||||
|
|
||||||
|
This method returns a
|
||||||
|
:class:`TestResponse <microdot_test_client.TestResponse>` object.
|
||||||
|
"""
|
||||||
|
return self.request('POST', path, headers=headers, body=body)
|
||||||
|
|
||||||
|
def put(self, path, headers=None, body=None):
|
||||||
|
"""Send a PUT request to the application.
|
||||||
|
|
||||||
|
:param path: The request URL.
|
||||||
|
:param headers: A dictionary of headers to send with the request.
|
||||||
|
:param body: The request body. If a dictionary or list is provided,
|
||||||
|
a JSON-encoded body will be sent. A string body is encoded
|
||||||
|
to bytes as UTF-8. A bytes body is sent as-is.
|
||||||
|
|
||||||
|
This method returns a
|
||||||
|
:class:`TestResponse <microdot_test_client.TestResponse>` object.
|
||||||
|
"""
|
||||||
|
return self.request('PUT', path, headers=headers, body=body)
|
||||||
|
|
||||||
|
def patch(self, path, headers=None, body=None):
|
||||||
|
"""Send a PATCH request to the application.
|
||||||
|
|
||||||
|
:param path: The request URL.
|
||||||
|
:param headers: A dictionary of headers to send with the request.
|
||||||
|
:param body: The request body. If a dictionary or list is provided,
|
||||||
|
a JSON-encoded body will be sent. A string body is encoded
|
||||||
|
to bytes as UTF-8. A bytes body is sent as-is.
|
||||||
|
|
||||||
|
This method returns a
|
||||||
|
:class:`TestResponse <microdot_test_client.TestResponse>` object.
|
||||||
|
"""
|
||||||
|
return self.request('PATCH', path, headers=headers, body=body)
|
||||||
|
|
||||||
|
def delete(self, path, headers=None):
|
||||||
|
"""Send a DELETE request to the application.
|
||||||
|
|
||||||
|
:param path: The request URL.
|
||||||
|
:param headers: A dictionary of headers to send with the request.
|
||||||
|
|
||||||
|
This method returns a
|
||||||
|
:class:`TestResponse <microdot_test_client.TestResponse>` object.
|
||||||
|
"""
|
||||||
|
return self.request('DELETE', path, headers=headers)
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
from tests.microdot.test_multidict import TestMultiDict
|
from .test_multidict import TestMultiDict
|
||||||
from tests.microdot.test_request import TestRequest
|
from .test_request import TestRequest
|
||||||
from tests.microdot.test_response import TestResponse
|
from .test_response import TestResponse
|
||||||
from tests.microdot.test_url_pattern import TestURLPattern
|
from .test_url_pattern import TestURLPattern
|
||||||
from tests.microdot.test_microdot import TestMicrodot
|
from .test_microdot import TestMicrodot
|
||||||
|
|
||||||
from tests.microdot_asyncio.test_request_asyncio import TestRequestAsync
|
from .test_request_asyncio import TestRequestAsync
|
||||||
from tests.microdot_asyncio.test_response_asyncio import TestResponseAsync
|
from .test_response_asyncio import TestResponseAsync
|
||||||
from tests.microdot_asyncio.test_microdot_asyncio import TestMicrodotAsync
|
from .test_microdot_asyncio import TestMicrodotAsync
|
||||||
|
|
||||||
from tests.microdot_utemplate.test_utemplate import TestUTemplate
|
from .test_utemplate import TestUTemplate
|
||||||
|
|
||||||
|
from .test_session import TestSession
|
||||||
|
|||||||
@@ -1,466 +0,0 @@
|
|||||||
import sys
|
|
||||||
import unittest
|
|
||||||
from microdot import Microdot, Response
|
|
||||||
from tests import mock_socket
|
|
||||||
|
|
||||||
|
|
||||||
def mock_create_thread(f, *args, **kwargs):
|
|
||||||
f(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class TestMicrodot(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
# mock socket module
|
|
||||||
self.original_socket = sys.modules['microdot'].socket
|
|
||||||
self.original_create_thread = sys.modules['microdot'].create_thread
|
|
||||||
sys.modules['microdot'].socket = mock_socket
|
|
||||||
sys.modules['microdot'].create_thread = mock_create_thread
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
# restore original socket module
|
|
||||||
sys.modules['microdot'].socket = self.original_socket
|
|
||||||
sys.modules['microdot'].create_thread = self.original_create_thread
|
|
||||||
|
|
||||||
def _add_shutdown(self, app):
|
|
||||||
@app.route('/shutdown')
|
|
||||||
def shutdown(req):
|
|
||||||
app.shutdown()
|
|
||||||
return ''
|
|
||||||
|
|
||||||
mock_socket.add_request('GET', '/shutdown')
|
|
||||||
|
|
||||||
def test_get_request(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nfoo'))
|
|
||||||
|
|
||||||
def test_post_request(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
@app.route('/', methods=['POST'])
|
|
||||||
def index_post(req):
|
|
||||||
return Response('bar')
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('POST', '/')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nbar'))
|
|
||||||
|
|
||||||
def test_empty_request(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.FakeStream(b'\n')
|
|
||||||
mock_socket._requests.append(fd)
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 400 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 11\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nBad request'))
|
|
||||||
|
|
||||||
def test_method_decorators(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.get('/get')
|
|
||||||
def get(req):
|
|
||||||
return 'GET'
|
|
||||||
|
|
||||||
@app.post('/post')
|
|
||||||
def post(req):
|
|
||||||
return 'POST'
|
|
||||||
|
|
||||||
@app.put('/put')
|
|
||||||
def put(req):
|
|
||||||
return 'PUT'
|
|
||||||
|
|
||||||
@app.patch('/patch')
|
|
||||||
def patch(req):
|
|
||||||
return 'PATCH'
|
|
||||||
|
|
||||||
@app.delete('/delete')
|
|
||||||
def delete(req):
|
|
||||||
return 'DELETE'
|
|
||||||
|
|
||||||
methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fds = [mock_socket.add_request(method, '/' + method.lower())
|
|
||||||
for method in methods]
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
for fd, method in zip(fds, methods):
|
|
||||||
self.assertTrue(fd.response.endswith(
|
|
||||||
b'\r\n\r\n' + method.encode()))
|
|
||||||
|
|
||||||
def test_tuple_responses(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/body')
|
|
||||||
def one(req):
|
|
||||||
return 'one'
|
|
||||||
|
|
||||||
@app.route('/body-status')
|
|
||||||
def two(req):
|
|
||||||
return 'two', 202
|
|
||||||
|
|
||||||
@app.route('/body-headers')
|
|
||||||
def three(req):
|
|
||||||
return '<p>three</p>', {'Content-Type': 'text/html'}
|
|
||||||
|
|
||||||
@app.route('/body-status-headers')
|
|
||||||
def four(req):
|
|
||||||
return '<p>four</p>', 202, {'Content-Type': 'text/html'}
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/body')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\none'))
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/body-status')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 202 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\ntwo'))
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/body-headers')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Type: text/html\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n<p>three</p>'))
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/body-status-headers')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 202 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Type: text/html\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n<p>four</p>'))
|
|
||||||
|
|
||||||
def test_before_after_request(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.before_request
|
|
||||||
def before_request(req):
|
|
||||||
if req.path == '/bar':
|
|
||||||
@req.after_request
|
|
||||||
def after_request(req, res):
|
|
||||||
res.headers['X-Two'] = '2'
|
|
||||||
return res
|
|
||||||
return 'bar', 202
|
|
||||||
req.g.message = 'baz'
|
|
||||||
|
|
||||||
@app.after_request
|
|
||||||
def after_request_one(req, res):
|
|
||||||
res.headers['X-One'] = '1'
|
|
||||||
|
|
||||||
@app.after_request
|
|
||||||
def after_request_two(req, res):
|
|
||||||
res.set_cookie('foo', 'bar')
|
|
||||||
return res
|
|
||||||
|
|
||||||
@app.route('/bar')
|
|
||||||
def bar(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
@app.route('/baz')
|
|
||||||
def baz(req):
|
|
||||||
return req.g.message
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/bar')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 202 N/A\r\n'))
|
|
||||||
self.assertIn(b'X-One: 1\r\n', fd.response)
|
|
||||||
self.assertIn(b'X-Two: 2\r\n', fd.response)
|
|
||||||
self.assertIn(b'Set-Cookie: foo=bar\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nbar'))
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/baz')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'X-One: 1\r\n', fd.response)
|
|
||||||
self.assertIn(b'Set-Cookie: foo=bar\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nbaz'))
|
|
||||||
|
|
||||||
def test_400(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.FakeStream(b'\n')
|
|
||||||
mock_socket._requests.append(fd)
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 400 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 11\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nBad request'))
|
|
||||||
|
|
||||||
def test_400_handler(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.errorhandler(400)
|
|
||||||
def handle_400(req):
|
|
||||||
return '400'
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.FakeStream(b'\n')
|
|
||||||
mock_socket._requests.append(fd)
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n400'))
|
|
||||||
|
|
||||||
def test_404(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/foo')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 404 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 9\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nNot found'))
|
|
||||||
|
|
||||||
def test_404_handler(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
|
||||||
def handle_404(req):
|
|
||||||
return '404'
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/foo')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n404'))
|
|
||||||
|
|
||||||
def test_405(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/foo')
|
|
||||||
def index(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('POST', '/foo')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 405 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 9\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nNot found'))
|
|
||||||
|
|
||||||
def test_405_handler(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/foo')
|
|
||||||
def index(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
@app.errorhandler(405)
|
|
||||||
def handle_404(req):
|
|
||||||
return '405'
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('POST', '/foo')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n405'))
|
|
||||||
|
|
||||||
def test_413(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/foo', body='x' * 17000)
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 413 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 17\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nPayload too large'))
|
|
||||||
|
|
||||||
def test_413_handler(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 'foo'
|
|
||||||
|
|
||||||
@app.errorhandler(413)
|
|
||||||
def handle_413(req):
|
|
||||||
return '413', 400
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/foo', body='x' * 17000)
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 400 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n413'))
|
|
||||||
|
|
||||||
def test_500(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 1 / 0
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 500 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 21\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nInternal server error'))
|
|
||||||
|
|
||||||
def test_500_handler(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 1 / 0
|
|
||||||
|
|
||||||
@app.errorhandler(500)
|
|
||||||
def handle_500(req):
|
|
||||||
return '501', 501
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 501 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n501'))
|
|
||||||
|
|
||||||
def test_exception_handler(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
return 1 / 0
|
|
||||||
|
|
||||||
@app.errorhandler(ZeroDivisionError)
|
|
||||||
def handle_div_zero(req, exc):
|
|
||||||
return '501', 501
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 501 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n501'))
|
|
||||||
|
|
||||||
def test_streaming(self):
|
|
||||||
app = Microdot()
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index(req):
|
|
||||||
def stream():
|
|
||||||
yield 'foo'
|
|
||||||
yield b'bar'
|
|
||||||
return stream()
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nfoobar'))
|
|
||||||
|
|
||||||
def test_mount(self):
|
|
||||||
subapp = Microdot()
|
|
||||||
|
|
||||||
@subapp.before_request
|
|
||||||
def before(req):
|
|
||||||
req.g.before = 'before'
|
|
||||||
|
|
||||||
@subapp.after_request
|
|
||||||
def after(req, res):
|
|
||||||
return res.body + b':after'
|
|
||||||
|
|
||||||
@subapp.errorhandler(404)
|
|
||||||
def not_found(req):
|
|
||||||
return '404', 404
|
|
||||||
|
|
||||||
@subapp.route('/app')
|
|
||||||
def index(req):
|
|
||||||
return req.g.before + ':foo'
|
|
||||||
|
|
||||||
app = Microdot()
|
|
||||||
app.mount(subapp, url_prefix='/sub')
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/app')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 404 N/A\r\n'))
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\n404'))
|
|
||||||
|
|
||||||
mock_socket.clear_requests()
|
|
||||||
fd = mock_socket.add_request('GET', '/sub/app')
|
|
||||||
self._add_shutdown(app)
|
|
||||||
app.run()
|
|
||||||
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
|
||||||
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
|
||||||
self.assertTrue(fd.response.endswith(b'\r\n\r\nbefore:foo:after'))
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Autogenerated file
|
|
||||||
def render(name):
|
|
||||||
yield """Hello, """
|
|
||||||
yield str(name)
|
|
||||||
yield """!
|
|
||||||
"""
|
|
||||||
@@ -10,7 +10,7 @@ from microdot_asyncio import Microdot as MicrodotAsync, Request as RequestAsync
|
|||||||
from microdot_jinja import render_template, init_templates
|
from microdot_jinja import render_template, init_templates
|
||||||
from tests.mock_socket import get_request_fd, get_async_request_fd
|
from tests.mock_socket import get_request_fd, get_async_request_fd
|
||||||
|
|
||||||
init_templates('tests/microdot_jinja/templates')
|
init_templates('tests/templates')
|
||||||
|
|
||||||
|
|
||||||
def _run(coro):
|
def _run(coro):
|
||||||
@@ -21,7 +21,7 @@ def _run(coro):
|
|||||||
'not supported under MicroPython')
|
'not supported under MicroPython')
|
||||||
class TestUTemplate(unittest.TestCase):
|
class TestUTemplate(unittest.TestCase):
|
||||||
def test_render_template(self):
|
def test_render_template(self):
|
||||||
s = render_template('hello.txt', name='foo')
|
s = render_template('hello.jinja.txt', name='foo')
|
||||||
self.assertEqual(s, 'Hello, foo!')
|
self.assertEqual(s, 'Hello, foo!')
|
||||||
|
|
||||||
def test_render_template_in_app(self):
|
def test_render_template_in_app(self):
|
||||||
@@ -29,7 +29,7 @@ class TestUTemplate(unittest.TestCase):
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index(req):
|
def index(req):
|
||||||
return render_template('hello.txt', name='foo')
|
return render_template('hello.jinja.txt', name='foo')
|
||||||
|
|
||||||
req = Request.create(app, get_request_fd('GET', '/'), 'addr')
|
req = Request.create(app, get_request_fd('GET', '/'), 'addr')
|
||||||
res = app.dispatch_request(req)
|
res = app.dispatch_request(req)
|
||||||
@@ -41,7 +41,7 @@ class TestUTemplate(unittest.TestCase):
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def index(req):
|
async def index(req):
|
||||||
return render_template('hello.txt', name='foo')
|
return render_template('hello.jinja.txt', name='foo')
|
||||||
|
|
||||||
req = _run(RequestAsync.create(
|
req = _run(RequestAsync.create(
|
||||||
app, get_async_request_fd('GET', '/'), 'addr'))
|
app, get_async_request_fd('GET', '/'), 'addr'))
|
||||||
529
tests/test_microdot.py
Normal file
529
tests/test_microdot.py
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from microdot import Microdot, Response
|
||||||
|
from microdot_test_client import TestClient
|
||||||
|
from tests import mock_socket
|
||||||
|
|
||||||
|
|
||||||
|
def mock_create_thread(f, *args, **kwargs):
|
||||||
|
f(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMicrodot(unittest.TestCase):
|
||||||
|
def _mock_socket(self):
|
||||||
|
self.original_socket = sys.modules['microdot'].socket
|
||||||
|
self.original_create_thread = sys.modules['microdot'].create_thread
|
||||||
|
sys.modules['microdot'].socket = mock_socket
|
||||||
|
sys.modules['microdot'].create_thread = mock_create_thread
|
||||||
|
|
||||||
|
def _unmock_socket(self):
|
||||||
|
sys.modules['microdot'].socket = self.original_socket
|
||||||
|
sys.modules['microdot'].create_thread = self.original_create_thread
|
||||||
|
|
||||||
|
def _add_shutdown(self, app):
|
||||||
|
@app.route('/shutdown')
|
||||||
|
def shutdown(req):
|
||||||
|
app.shutdown()
|
||||||
|
return ''
|
||||||
|
|
||||||
|
mock_socket.add_request('GET', '/shutdown')
|
||||||
|
|
||||||
|
def test_get_request(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.get('/')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'foo')
|
||||||
|
self.assertEqual(res.body, b'foo')
|
||||||
|
self.assertEqual(res.json, None)
|
||||||
|
|
||||||
|
def test_post_request(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.route('/', methods=['POST'])
|
||||||
|
def index_post(req):
|
||||||
|
return Response('bar')
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.post('/')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.headers['Content-Length'], '3')
|
||||||
|
self.assertEqual(res.text, 'bar')
|
||||||
|
|
||||||
|
def test_empty_request(self):
|
||||||
|
self._mock_socket()
|
||||||
|
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.FakeStream(b'\n')
|
||||||
|
mock_socket._requests.append(fd)
|
||||||
|
self._add_shutdown(app)
|
||||||
|
app.run()
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 400 N/A\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 11\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\nBad request'))
|
||||||
|
|
||||||
|
self._unmock_socket()
|
||||||
|
|
||||||
|
def test_method_decorators(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.get('/get')
|
||||||
|
def get(req):
|
||||||
|
return 'GET'
|
||||||
|
|
||||||
|
@app.post('/post')
|
||||||
|
def post(req):
|
||||||
|
return 'POST'
|
||||||
|
|
||||||
|
@app.put('/put')
|
||||||
|
def put(req):
|
||||||
|
return 'PUT'
|
||||||
|
|
||||||
|
@app.patch('/patch')
|
||||||
|
def patch(req):
|
||||||
|
return 'PATCH'
|
||||||
|
|
||||||
|
@app.delete('/delete')
|
||||||
|
def delete(req):
|
||||||
|
return 'DELETE'
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||||
|
for method in methods:
|
||||||
|
res = getattr(client, method.lower())('/' + method.lower())
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, method)
|
||||||
|
|
||||||
|
def test_headers(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return req.headers.get('X-Foo')
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.get('/', headers={'X-Foo': 'bar'})
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'bar')
|
||||||
|
|
||||||
|
def test_cookies(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return req.cookies['one'] + req.cookies['two'] + \
|
||||||
|
req.cookies['three']
|
||||||
|
|
||||||
|
client = TestClient(app, cookies={'one': '1', 'two': '2'})
|
||||||
|
res = client.get('/', headers={'Cookie': 'three=3'})
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, '123')
|
||||||
|
|
||||||
|
def test_binary_payload(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.post('/')
|
||||||
|
def index(req):
|
||||||
|
return req.body
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.post('/', body=b'foo')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'foo')
|
||||||
|
|
||||||
|
def test_json_payload(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.post('/dict')
|
||||||
|
def json_dict(req):
|
||||||
|
print(req.headers)
|
||||||
|
return req.json.get('foo')
|
||||||
|
|
||||||
|
@app.post('/list')
|
||||||
|
def json_list(req):
|
||||||
|
return req.json[0]
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
res = client.post('/dict', body={'foo': 'bar'})
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'bar')
|
||||||
|
|
||||||
|
res = client.post('/list', body=['foo', 'bar'])
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'foo')
|
||||||
|
|
||||||
|
def test_tuple_responses(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/body')
|
||||||
|
def one(req):
|
||||||
|
return 'one'
|
||||||
|
|
||||||
|
@app.route('/body-status')
|
||||||
|
def two(req):
|
||||||
|
return 'two', 202
|
||||||
|
|
||||||
|
@app.route('/body-headers')
|
||||||
|
def three(req):
|
||||||
|
return '<p>three</p>', {'Content-Type': 'text/html'}
|
||||||
|
|
||||||
|
@app.route('/body-status-headers')
|
||||||
|
def four(req):
|
||||||
|
return '<p>four</p>', 202, {'Content-Type': 'text/html'}
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
res = client.get('/body')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'one')
|
||||||
|
|
||||||
|
res = client.get('/body-status')
|
||||||
|
self.assertEqual(res.status_code, 202)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'two')
|
||||||
|
|
||||||
|
res = client.get('/body-headers')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/html')
|
||||||
|
self.assertEqual(res.text, '<p>three</p>')
|
||||||
|
|
||||||
|
res = client.get('/body-status-headers')
|
||||||
|
self.assertEqual(res.status_code, 202)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/html')
|
||||||
|
self.assertEqual(res.text, '<p>four</p>')
|
||||||
|
|
||||||
|
def test_before_after_request(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.before_request
|
||||||
|
def before_request(req):
|
||||||
|
if req.path == '/bar':
|
||||||
|
@req.after_request
|
||||||
|
def after_request(req, res):
|
||||||
|
res.headers['X-Two'] = '2'
|
||||||
|
return res
|
||||||
|
return 'bar', 202
|
||||||
|
req.g.message = 'baz'
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def after_request_one(req, res):
|
||||||
|
res.headers['X-One'] = '1'
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def after_request_two(req, res):
|
||||||
|
res.set_cookie('foo', 'bar')
|
||||||
|
return res
|
||||||
|
|
||||||
|
@app.route('/bar')
|
||||||
|
def bar(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.route('/baz')
|
||||||
|
def baz(req):
|
||||||
|
return req.g.message
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
res = client.get('/bar')
|
||||||
|
self.assertEqual(res.status_code, 202)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.headers['Set-Cookie'], ['foo=bar'])
|
||||||
|
self.assertEqual(res.headers['X-One'], '1')
|
||||||
|
self.assertEqual(res.headers['X-Two'], '2')
|
||||||
|
self.assertEqual(res.text, 'bar')
|
||||||
|
self.assertEqual(client.cookies['foo'], 'bar')
|
||||||
|
|
||||||
|
res = client.get('/baz')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.headers['Set-Cookie'], ['foo=bar'])
|
||||||
|
self.assertEqual(res.headers['X-One'], '1')
|
||||||
|
self.assertFalse('X-Two' in res.headers)
|
||||||
|
self.assertEqual(res.headers['Content-Length'], '3')
|
||||||
|
self.assertEqual(res.text, 'baz')
|
||||||
|
|
||||||
|
def test_400(self):
|
||||||
|
self._mock_socket()
|
||||||
|
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.FakeStream(b'\n')
|
||||||
|
mock_socket._requests.append(fd)
|
||||||
|
self._add_shutdown(app)
|
||||||
|
app.run()
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 400 N/A\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 11\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\nBad request'))
|
||||||
|
|
||||||
|
self._unmock_socket()
|
||||||
|
|
||||||
|
def test_400_handler(self):
|
||||||
|
self._mock_socket()
|
||||||
|
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.errorhandler(400)
|
||||||
|
def handle_400(req):
|
||||||
|
return '400'
|
||||||
|
|
||||||
|
mock_socket.clear_requests()
|
||||||
|
fd = mock_socket.FakeStream(b'\n')
|
||||||
|
mock_socket._requests.append(fd)
|
||||||
|
self._add_shutdown(app)
|
||||||
|
app.run()
|
||||||
|
self.assertTrue(fd.response.startswith(b'HTTP/1.0 200 OK\r\n'))
|
||||||
|
self.assertIn(b'Content-Length: 3\r\n', fd.response)
|
||||||
|
self.assertIn(b'Content-Type: text/plain\r\n', fd.response)
|
||||||
|
self.assertTrue(fd.response.endswith(b'\r\n\r\n400'))
|
||||||
|
|
||||||
|
self._unmock_socket()
|
||||||
|
|
||||||
|
def test_404(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.post('/foo')
|
||||||
|
self.assertEqual(res.status_code, 404)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'Not found')
|
||||||
|
|
||||||
|
def test_404_handler(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def handle_404(req):
|
||||||
|
return '404'
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.post('/foo')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, '404')
|
||||||
|
|
||||||
|
def test_405(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/foo')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.post('/foo')
|
||||||
|
self.assertEqual(res.status_code, 405)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'Not found')
|
||||||
|
|
||||||
|
def test_405_handler(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/foo')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.errorhandler(405)
|
||||||
|
def handle_405(req):
|
||||||
|
return '405', 405
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.patch('/foo')
|
||||||
|
self.assertEqual(res.status_code, 405)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, '405')
|
||||||
|
|
||||||
|
def test_413(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.post('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.post('/foo', body='x' * 17000)
|
||||||
|
self.assertEqual(res.status_code, 413)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'Payload too large')
|
||||||
|
|
||||||
|
def test_413_handler(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 'foo'
|
||||||
|
|
||||||
|
@app.errorhandler(413)
|
||||||
|
def handle_413(req):
|
||||||
|
return '413', 400
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.post('/foo', body='x' * 17000)
|
||||||
|
self.assertEqual(res.status_code, 400)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, '413')
|
||||||
|
|
||||||
|
def test_500(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 1 / 0
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.get('/')
|
||||||
|
self.assertEqual(res.status_code, 500)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'Internal server error')
|
||||||
|
|
||||||
|
def test_500_handler(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 1 / 0
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def handle_500(req):
|
||||||
|
return '501', 501
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.get('/')
|
||||||
|
self.assertEqual(res.status_code, 501)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, '501')
|
||||||
|
|
||||||
|
def test_exception_handler(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
return 1 / 0
|
||||||
|
|
||||||
|
@app.errorhandler(ZeroDivisionError)
|
||||||
|
def handle_div_zero(req, exc):
|
||||||
|
return '501', 501
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.get('/')
|
||||||
|
self.assertEqual(res.status_code, 501)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, '501')
|
||||||
|
|
||||||
|
def test_json_response(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/dict')
|
||||||
|
def json_dict(req):
|
||||||
|
return {'foo': 'bar'}
|
||||||
|
|
||||||
|
@app.route('/list')
|
||||||
|
def json_list(req):
|
||||||
|
return ['foo', 'bar']
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
res = client.get('/dict')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'application/json')
|
||||||
|
self.assertEqual(res.json, {'foo': 'bar'})
|
||||||
|
|
||||||
|
res = client.get('/list')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'application/json')
|
||||||
|
self.assertEqual(res.json, ['foo', 'bar'])
|
||||||
|
|
||||||
|
def test_binary_response(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/bin')
|
||||||
|
def index(req):
|
||||||
|
return b'\xff\xfe', {'Content-Type': 'application/octet-stream'}
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.get('/bin')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'],
|
||||||
|
'application/octet-stream')
|
||||||
|
self.assertEqual(res.text, None)
|
||||||
|
self.assertEqual(res.json, None)
|
||||||
|
self.assertEqual(res.body, b'\xff\xfe')
|
||||||
|
|
||||||
|
def test_streaming(self):
|
||||||
|
app = Microdot()
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index(req):
|
||||||
|
def stream():
|
||||||
|
yield 'foo'
|
||||||
|
yield b'bar'
|
||||||
|
return stream()
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
res = client.get('/')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'foobar')
|
||||||
|
|
||||||
|
def test_mount(self):
|
||||||
|
subapp = Microdot()
|
||||||
|
|
||||||
|
@subapp.before_request
|
||||||
|
def before(req):
|
||||||
|
req.g.before = 'before'
|
||||||
|
|
||||||
|
@subapp.after_request
|
||||||
|
def after(req, res):
|
||||||
|
return res.body + b':after'
|
||||||
|
|
||||||
|
@subapp.errorhandler(404)
|
||||||
|
def not_found(req):
|
||||||
|
return '404', 404
|
||||||
|
|
||||||
|
@subapp.route('/app')
|
||||||
|
def index(req):
|
||||||
|
return req.g.before + ':foo'
|
||||||
|
|
||||||
|
app = Microdot()
|
||||||
|
app.mount(subapp, url_prefix='/sub')
|
||||||
|
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
res = client.get('/app')
|
||||||
|
self.assertEqual(res.status_code, 404)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, '404')
|
||||||
|
|
||||||
|
res = client.get('/sub/app')
|
||||||
|
self.assertEqual(res.status_code, 200)
|
||||||
|
self.assertEqual(res.headers['Content-Type'], 'text/plain')
|
||||||
|
self.assertEqual(res.text, 'before:foo:after')
|
||||||
@@ -9,7 +9,7 @@ from microdot_asyncio import Microdot as MicrodotAsync, Request as RequestAsync
|
|||||||
from microdot_utemplate import render_template, init_templates
|
from microdot_utemplate import render_template, init_templates
|
||||||
from tests.mock_socket import get_request_fd, get_async_request_fd
|
from tests.mock_socket import get_request_fd, get_async_request_fd
|
||||||
|
|
||||||
init_templates('tests/microdot_utemplate/templates')
|
init_templates('tests/templates')
|
||||||
|
|
||||||
|
|
||||||
def _run(coro):
|
def _run(coro):
|
||||||
@@ -18,7 +18,7 @@ def _run(coro):
|
|||||||
|
|
||||||
class TestUTemplate(unittest.TestCase):
|
class TestUTemplate(unittest.TestCase):
|
||||||
def test_render_template(self):
|
def test_render_template(self):
|
||||||
s = list(render_template('hello.txt', name='foo'))
|
s = list(render_template('hello.utemplate.txt', name='foo'))
|
||||||
self.assertEqual(s, ['Hello, ', 'foo', '!\n'])
|
self.assertEqual(s, ['Hello, ', 'foo', '!\n'])
|
||||||
|
|
||||||
def test_render_template_in_app(self):
|
def test_render_template_in_app(self):
|
||||||
@@ -26,7 +26,7 @@ class TestUTemplate(unittest.TestCase):
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index(req):
|
def index(req):
|
||||||
return render_template('hello.txt', name='foo')
|
return render_template('hello.utemplate.txt', name='foo')
|
||||||
|
|
||||||
req = Request.create(app, get_request_fd('GET', '/'), 'addr')
|
req = Request.create(app, get_request_fd('GET', '/'), 'addr')
|
||||||
res = app.dispatch_request(req)
|
res = app.dispatch_request(req)
|
||||||
@@ -38,7 +38,7 @@ class TestUTemplate(unittest.TestCase):
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
async def index(req):
|
async def index(req):
|
||||||
return render_template('hello.txt', name='foo')
|
return render_template('hello.utemplate.txt', name='foo')
|
||||||
|
|
||||||
req = _run(RequestAsync.create(
|
req = _run(RequestAsync.create(
|
||||||
app, get_async_request_fd('GET', '/'), 'addr'))
|
app, get_async_request_fd('GET', '/'), 'addr'))
|
||||||
Reference in New Issue
Block a user