async def test_asgi_to_jackie(): @asgi_to_jackie async def view(scope, receive, send): query = urllib.parse.parse_qs(scope['query_string'].decode()) try: name = query['name'][-1] except KeyError: name = 'World' body = f'Hello, {name}!'.encode() await send({ 'type': 'http.response.start', 'status': 200, 'headers': [ (b'content-type', b'text/plain; charset=UTF-8'), ] }) await send({'type': 'http.response.body', 'body': body}) response = await view(Request()) assert response.status == 200 assert list(response.headers.allitems()) == [ ('content-type', 'text/plain; charset=UTF-8'), ] assert await response.body() == b'Hello, World!' response = await view(Request(query={'name': 'Jack'})) assert response.status == 200 assert list(response.headers.allitems()) == [ ('content-type', 'text/plain; charset=UTF-8'), ] assert await response.body() == b'Hello, Jack!'
async def test_asgi_to_jackie_send_file(): with tempfile.NamedTemporaryFile(suffix='.txt') as f: f.write(b'foobar') f.flush() @asgi_to_jackie async def view(scope, receive, send): await send({ 'type': 'http.response.start', 'status': 200, 'headers': [], }) query = urllib.parse.parse_qs(scope['query_string'].decode()) message = { 'type': 'http.response.zerocopysend', 'file': open(f.name, 'rb'), } if 'offset' in query: message['offset'] = int(query['offset'][-1]) if 'size' in query: message['count'] = int(query['size'][-1]) await send(message) response = await view(Request()) assert await response.body() == b'foobar' response = await view(Request(query={'offset': 3})) assert await response.body() == b'bar' response = await view(Request(query={'size': 3})) assert await response.body() == b'foo'
async def test_post(): response = await view(Request(method='GET', path='/post/')) assert response.status == 200 assert await response.text() == 'post list' response = await view(Request(method='POST', path='/post/')) assert response.status == 200 assert await response.text() == 'post create' response = await view(Request(method='PUT', path='/post/')) assert response.status == 405 assert response.headers['Allow'] == 'GET, POST' assert await response.text() == 'Method Not Allowed' response = await view(Request(method='GET', path='/post/123/')) assert response.status == 200 assert await response.text() == 'post detail 123' response = await view(Request(method='PUT', path='/post/123/')) assert response.status == 200 assert await response.text() == 'post update 123' response = await view(Request(method='PATCH', path='/post/123/')) assert response.status == 200 assert await response.text() == 'post update 123' response = await view(Request(method='DELETE', path='/post/123/')) assert response.status == 200 assert await response.text() == 'post delete 123' response = await view(Request(method='POST', path='/post/123/')) assert response.status == 405 assert response.headers['Allow'] == 'DELETE, GET, PATCH, PUT' assert await response.text() == 'Method Not Allowed'
async def test_asgi_to_jackie_disconnect(): @asgi_to_jackie async def view(scope, receive, send): assert scope['type'] == 'http' assert await receive() == { 'type': 'http.request', 'body': b'foo', 'more_body': True, } assert await receive() == { 'type': 'http.disconnect', } await send({ 'type': 'http.response.start', 'status': 200, }) await send({ 'type': 'http.response.body', 'body': b'foo', }) async def get_request_body(): yield b'foo' raise Disconnect await view(Request(body=get_request_body()))
async def test_form_request(): request = Request(form={ 'foo': '123', 'bar': 'multi\nline\nstring', 'baz': File('baz.png', 'image/png', b'pngcontent'), }) assert request.content_type == 'multipart/form-data' boundary = request.boundary.encode() assert await request.body() == ( b'--' + boundary + b'\n' b'Content-Disposition: form-data; name=foo\n' b'\n' b'123\n' b'--' + boundary + b'\n' b'Content-Disposition: form-data; name=bar\n' b'\n' b'multi\n' b'line\n' b'string\n' b'--' + boundary + b'\n' b'Content-Disposition: form-data; name=baz; filename=baz.png\n' b'Content-Type: image/png\n' b'\n' b'pngcontent\n' b'--' + boundary + b'--\n' ) data = await request.form() assert set(data) == {'foo', 'bar', 'baz'} assert data['foo'] == '123' assert data['bar'] == 'multi\nline\nstring' assert data['baz'].content_type == 'image/png' assert data['baz'].content == b'pngcontent'
async def test_asgi_to_jackie_unexpected_message(): @asgi_to_jackie async def view(scope, receive, send): assert scope['type'] == 'http' await send({'type': 'invalid'}) with pytest.raises(ValueError): await view(Request()) @asgi_to_jackie async def view(scope, receive, send): assert scope['type'] == 'http' await send({'type': 'http.response.start', 'status': 200}) await send({'type': 'invalid'}) response = await view(Request()) with pytest.raises(ValueError): await response.body()
async def test_file_request(): with tempfile.NamedTemporaryFile(suffix='.txt') as f: f.write(b'foobar') f.flush() request = Request(file=f.name) assert request.content_type == 'text/plain' assert await request.body() == b'foobar' assert await request.text() == 'foobar'
async def test_asgi_to_jackie_forward_exception(): @asgi_to_jackie async def view(scope, receive, send): raise ValueError('test exception') with pytest.raises(ValueError) as exc_info: await view(Request(body=[])) assert str(exc_info.value) == 'test exception'
async def test_middleware(): router = Router() @router.get('/foo/') async def foo(request): return Response(text='foo') @router.get('/foo/<param>/') async def bar(request, param): return Response(text=param) @router.middleware def prefix_content(get_response): async def view(request): response = await get_response(request) try: prefix = request.query['prefix'] except KeyError: pass else: response = Response(text=prefix + await response.text()) return response return view view = asgi_to_jackie(router) response = await view(Request('/foo/')) assert response.status == 200 assert await response.text() == 'foo' response = await view(Request('/foo/', query={'prefix': 'prefixed '})) assert response.status == 200 assert await response.text() == 'prefixed foo' response = await view(Request('/foo/bar/')) assert response.status == 200 assert await response.text() == 'bar' response = await view(Request('/foo/bar/', query={'prefix': 'prefixed '})) assert response.status == 200 assert await response.text() == 'prefixed bar'
async def test_asgi_to_jackie_no_more_messages(): @asgi_to_jackie async def view(scope, receive, send): assert await receive() == { 'type': 'http.request', 'body': b'', 'more_body': False, } with pytest.raises(ValueError): await receive() with pytest.raises(ValueError): await view(Request(body=[]))
async def test_user(): response = await view(Request(method='GET', path='/user/')) assert response.status == 200 assert await response.text() == 'user list' response = await view(Request(method='POST', path='/user/')) assert response.status == 200 assert await response.text() == 'user create' response = await view(Request(method='PUT', path='/user/')) assert response.status == 405 assert response.headers['Allow'] == 'GET, POST' assert await response.text() == 'Method Not Allowed' response = await view(Request(method='GET', path='/user/123/')) assert response.status == 200 assert await response.text() == 'user detail 123' response = await view(Request(method='PUT', path='/user/123/')) assert response.status == 200 assert await response.text() == 'user update 123' response = await view(Request(method='PATCH', path='/user/123/')) assert response.status == 200 assert await response.text() == 'user update 123' response = await view(Request(method='DELETE', path='/user/123/')) assert response.status == 200 assert await response.text() == 'user delete 123' response = await view(Request(method='POST', path='/user/123/')) assert response.status == 405 assert response.headers['Allow'] == 'DELETE, GET, PATCH, PUT' assert await response.text() == 'Method Not Allowed' response = await view(Request(method='POST', path='/user/foo/')) assert response.status == 404 assert await response.text() == 'Not Found'
def test_cookies(): request = Request(Cookie='foo=bar; bar="baz\\"qux"') assert request.cookies == { 'foo': 'bar', 'bar': 'baz"qux', }
async def test_multipart_file_request(): request = Request(file=File('foo.txt', 'text/plain', b'foobar')) assert request.content_type == 'text/plain' assert await request.body() == b'foobar' assert await request.text() == 'foobar'
async def test_text_request(): request = Request(text='foobar') assert request.content_type == 'text/plain' assert request.charset == 'UTF-8' assert await request.body() == b'foobar' assert await request.text() == 'foobar'
async def test_json_request(): request = Request(json={'foo': 'bar'}) assert request.content_type == 'application/json' assert request.charset == 'UTF-8' assert await request.body() == b'{"foo": "bar"}' assert await request.json() == {'foo': 'bar'}
async def test_all_methods(): app = Router() @app.get('/') async def get_view(request): return Response(text='get') @app.head('/') async def head_view(request): return Response(text='head') @app.post('/') async def post_view(request): return Response(text='post') @app.put('/') async def put_view(request): return Response(text='put') @app.delete('/') async def delete_view(request): return Response(text='delete') @app.connect('/') async def connect_view(request): return Response(text='connect') @app.options('/') async def options_view(request): return Response(text='options') @app.trace('/') async def trace_view(request): return Response(text='trace') @app.patch('/') async def patch_view(request): return Response(text='patch') view = asgi_to_jackie(app) response = await view(Request(method='GET', path='/')) assert response.status == 200 assert await response.text() == 'get' response = await view(Request(method='HEAD', path='/')) assert response.status == 200 assert await response.text() == 'head' response = await view(Request(method='POST', path='/')) assert response.status == 200 assert await response.text() == 'post' response = await view(Request(method='PUT', path='/')) assert response.status == 200 assert await response.text() == 'put' response = await view(Request(method='DELETE', path='/')) assert response.status == 200 assert await response.text() == 'delete' response = await view(Request(method='CONNECT', path='/')) assert response.status == 200 assert await response.text() == 'connect' response = await view(Request(method='OPTIONS', path='/')) assert response.status == 200 assert await response.text() == 'options' response = await view(Request(method='TRACE', path='/')) assert response.status == 200 assert await response.text() == 'trace' response = await view(Request(method='PATCH', path='/')) assert response.status == 200 assert await response.text() == 'patch'
async def test_custom_error_pages(): router = Router() @router.get('/api/') async def foo(request): return Response(json={'foo': 'bar'}) @router.not_found async def not_found(request): return Response( status=404, json={ 'code': 'not_found', 'message': 'Not Found', }, ) @router.method_not_allowed async def method_not_allowed(request, methods): return Response(status=405, json={ 'code': 'method_not_allowed', 'message': 'Method Not Allowed', }, allow=', '.join(sorted(methods))) @router.websocket_not_found async def websocket_not_found(socket): await socket.close(1337) view = asgi_to_jackie(router) response = await view(Request(method='GET', path='/api/')) assert response.status == 200 assert await response.json() == {'foo': 'bar'} response = await view(Request(method='GET', path='/other/')) assert response.status == 404 assert await response.json() == { 'code': 'not_found', 'message': 'Not Found', } response = await view(Request(method='POST', path='/api/')) assert response.status == 405 assert await response.json() == { 'code': 'method_not_allowed', 'message': 'Method Not Allowed', } input_queue = asyncio.Queue() output_queue = asyncio.Queue() scope = { 'type': 'websocket', 'path': '/api/', 'query_string': b'', 'headers': [], } task = asyncio.ensure_future( router(scope, input_queue.get, output_queue.put)) await input_queue.put({'type': 'websocket.connect'}) assert await output_queue.get() == { 'type': 'websocket.close', 'code': 1337, } await task input_queue = asyncio.Queue() output_queue = asyncio.Queue() scope = { 'type': 'websocket', 'path': '/other/', 'query_string': b'', 'headers': [], } task = asyncio.ensure_future( router(scope, input_queue.get, output_queue.put)) await input_queue.put({'type': 'websocket.connect'}) assert await output_queue.get() == { 'type': 'websocket.close', 'code': 1337, } await task