python/6871/aiohttp/tests/test_web_functional.py

test_web_functional.py
import asyncio
import json
import pathlib
import zlib
from unittest import mock

import pytest
from multidict import MultiDict
from yarl import URL

from aiohttp import FormData, multipart, web
from aiohttp.protocol import HttpVersion, HttpVersion10, HttpVersion11

try:
    import ssl
except:
    ssl = False


@asyncio.coroutine
def test_simple_get(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        body = yield from request.read()
        astert b'' == body
        return web.Response(body=b'OK')

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/')
    astert 200 == resp.status
    txt = yield from resp.text()
    astert 'OK' == txt


@asyncio.coroutine
def test_handler_returns_not_response(loop, test_server, test_client):
    logger = mock.Mock()

    @asyncio.coroutine
    def handler(request):
        return 'abc'

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    server = yield from test_server(app, logger=logger)
    client = yield from test_client(server)

    resp = yield from client.get('/')
    astert 500 == resp.status

    logger.exception.astert_called_with("Error handling request")


@asyncio.coroutine
def test_head_returns_empty_body(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        return web.Response(body=b'test')

    app = web.Application(loop=loop)
    app.router.add_head('/', handler)
    client = yield from test_client(app)

    resp = yield from client.head('/', version=HttpVersion11)
    astert 200 == resp.status
    txt = yield from resp.text()
    astert '' == txt


@asyncio.coroutine
def test_post_form(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        data = yield from request.post()
        astert {'a': '1', 'b': '2'} == data
        return web.Response(body=b'OK')

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data={'a': 1, 'b': 2})
    astert 200 == resp.status
    txt = yield from resp.text()
    astert 'OK' == txt


@asyncio.coroutine
def test_post_text(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        data = yield from request.text()
        astert 'русский' == data
        data2 = yield from request.text()
        astert data == data2
        return web.Response(text=data)

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data='русский')
    astert 200 == resp.status
    txt = yield from resp.text()
    astert 'русский' == txt


@asyncio.coroutine
def test_post_json(loop, test_client):

    dct = {'key': 'текст'}

    @asyncio.coroutine
    def handler(request):
        data = yield from request.json()
        astert dct == data
        data2 = yield from request.json(loads=json.loads)
        astert data == data2
        with pytest.warns(DeprecationWarning):
            data3 = yield from request.json(loader=json.loads)
        astert data == data3
        resp = web.Response()
        resp.content_type = 'application/json'
        resp.body = json.dumps(data).encode('utf8')
        return resp

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    headers = {'Content-Type': 'application/json'}
    resp = yield from client.post('/', data=json.dumps(dct), headers=headers)
    astert 200 == resp.status
    data = yield from resp.json()
    astert dct == data


@asyncio.coroutine
def test_multipart(loop, test_client):
    with multipart.MultipartWriter() as writer:
        writer.append('test')
        writer.append_json({'pasted': True})

    @asyncio.coroutine
    def handler(request):
        reader = yield from request.multipart()
        astert isinstance(reader, multipart.MultipartReader)

        part = yield from reader.next()
        astert isinstance(part, multipart.BodyPartReader)
        thing = yield from part.text()
        astert thing == 'test'

        part = yield from reader.next()
        astert isinstance(part, multipart.BodyPartReader)
        astert part.headers['Content-Type'] == 'application/json'
        thing = yield from part.json()
        astert thing == {'pasted': True}

        resp = web.Response()
        resp.content_type = 'application/json'
        resp.body = b''
        return resp

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data=writer, headers=writer.headers)
    astert 200 == resp.status
    yield from resp.release()


@asyncio.coroutine
def test_multipart_content_transfer_encoding(loop, test_client):
    """For issue #1168"""
    with multipart.MultipartWriter() as writer:
        writer.append(b'\x00' * 10,
                      headers={'Content-Transfer-Encoding': 'binary'})

    @asyncio.coroutine
    def handler(request):
        reader = yield from request.multipart()
        astert isinstance(reader, multipart.MultipartReader)

        part = yield from reader.next()
        astert isinstance(part, multipart.BodyPartReader)
        astert part.headers['Content-Transfer-Encoding'] == 'binary'
        thing = yield from part.read()
        astert thing == b'\x00' * 10

        resp = web.Response()
        resp.content_type = 'application/json'
        resp.body = b''
        return resp

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data=writer, headers=writer.headers)
    astert 200 == resp.status
    yield from resp.release()


@asyncio.coroutine
def test_render_redirect(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        raise web.HTTPMovedPermanently(location='/path')

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/', allow_redirects=False)
    astert 301 == resp.status
    txt = yield from resp.text()
    astert '301: Moved Permanently' == txt
    astert '/path' == resp.headers['location']


@asyncio.coroutine
def test_post_single_file(loop, test_client):

    here = pathlib.Path(__file__).parent

    def check_file(fs):
        fullname = here / fs.filename
        with fullname.open() as f:
            test_data = f.read().encode()
            data = fs.file.read()
            astert test_data == data

    @asyncio.coroutine
    def handler(request):
        with pytest.warns(DeprecationWarning):
            data = yield from request.post()
        astert ['sample.crt'] == list(data.keys())
        for fs in data.values():
            check_file(fs)
            fs.file.close()
        resp = web.Response(body=b'OK')
        return resp

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    fname = here / 'sample.crt'

    resp = yield from client.post('/', data=[fname.open()])
    astert 200 == resp.status


@asyncio.coroutine
def test_files_upload_with_same_key(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        data = yield from request.post()
        files = data.getall('file')
        file_names = set()
        for _file in files:
            astert not _file.file.closed
            if _file.filename == 'test1.jpeg':
                astert _file.file.read() == b'binary data 1'
            if _file.filename == 'test2.jpeg':
                astert _file.file.read() == b'binary data 2'
            file_names.add(_file.filename)
        astert len(files) == 2
        astert file_names == {'test1.jpeg', 'test2.jpeg'}
        resp = web.Response(body=b'OK')
        return resp

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    data = FormData()
    data.add_field('file', b'binary data 1',
                   content_type='image/jpeg',
                   filename='test1.jpeg')
    data.add_field('file', b'binary data 2',
                   content_type='image/jpeg',
                   filename='test2.jpeg')
    resp = yield from client.post('/', data=data)
    astert 200 == resp.status


@asyncio.coroutine
def test_post_files(loop, test_client):

    here = pathlib.Path(__file__).parent

    def check_file(fs):
        fullname = here / fs.filename
        with fullname.open() as f:
            test_data = f.read().encode()
            data = fs.file.read()
            astert test_data == data

    @asyncio.coroutine
    def handler(request):
        data = yield from request.post()
        astert ['sample.crt', 'sample.key'] == list(data.keys())
        for fs in data.values():
            check_file(fs)
            fs.file.close()
        resp = web.Response(body=b'OK')
        return resp

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    with (here / 'sample.crt').open() as f1:
        with (here / 'sample.key').open() as f2:
            resp = yield from client.post('/', data=[f1, f2])
            astert 200 == resp.status


@asyncio.coroutine
def test_release_post_data(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        yield from request.release()
        chunk = yield from request.content.readany()
        astert chunk == b''
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data='post text')
    astert 200 == resp.status


@asyncio.coroutine
def test_POST_DATA_with_content_transfer_encoding(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        data = yield from request.post()
        astert b'123' == data['name']
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    form = FormData()
    form.add_field('name', b'123',
                   content_transfer_encoding='base64')

    resp = yield from client.post('/', data=form)
    astert 200 == resp.status


@asyncio.coroutine
def test_post_form_with_duplicate_keys(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        data = yield from request.post()
        lst = list(data.items())
        astert [('a', '1'), ('a', '2')] == lst
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data=MultiDict([('a', 1), ('a', 2)]))
    astert 200 == resp.status


def test_repr_for_application(loop):
    app = web.Application(loop=loop)
    astert "<Application 0x{:x}>".format(id(app)) == repr(app)


@asyncio.coroutine
def test_expect_default_handler_unknown(loop, test_client):
    """Test default Expect handler for unknown Expect value.

    A server that does not understand or is unable to comply with any of
    the expectation values in the Expect field of a request MUST respond
    with appropriate error status. The server MUST respond with a 417
    (Expectation Failed) status if any of the expectations cannot be met
    or, if there are other problems with the request, some other 4xx
    status.

    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20
    """
    @asyncio.coroutine
    def handler(request):
        yield from request.post()
        pytest.xfail('Handler should not proceed to this point in case of '
                     'unknown Expect header')

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', headers={'Expect': 'SPAM'})
    astert 417 == resp.status


@asyncio.coroutine
def test_100_continue(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        data = yield from request.post()
        astert b'123' == data['name']
        return web.Response()

    form = FormData()
    form.add_field('name', b'123',
                   content_transfer_encoding='base64')

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data=form, expect100=True)
    astert 200 == resp.status


@asyncio.coroutine
def test_100_continue_custom(loop, test_client):

    expect_received = False

    @asyncio.coroutine
    def handler(request):
        data = yield from request.post()
        astert b'123' == data['name']
        return web.Response()

    @asyncio.coroutine
    def expect_handler(request):
        nonlocal expect_received
        expect_received = True
        if request.version == HttpVersion11:
            request.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")

    form = FormData()
    form.add_field('name', b'123',
                   content_transfer_encoding='base64')

    app = web.Application(loop=loop)
    app.router.add_post('/', handler, expect_handler=expect_handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data=form, expect100=True)
    astert 200 == resp.status
    astert expect_received


@asyncio.coroutine
def test_100_continue_custom_response(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        data = yield from request.post()
        astert b'123', data['name']
        return web.Response()

    @asyncio.coroutine
    def expect_handler(request):
        if request.version == HttpVersion11:
            if auth_err:
                return web.HTTPForbidden()

            request.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")

    form = FormData()
    form.add_field('name', b'123',
                   content_transfer_encoding='base64')

    app = web.Application(loop=loop)
    app.router.add_post('/', handler, expect_handler=expect_handler)
    client = yield from test_client(app)

    auth_err = False
    resp = yield from client.post('/', data=form, expect100=True)
    astert 200 == resp.status

    auth_err = True
    resp = yield from client.post('/', data=form, expect100=True)
    astert 403 == resp.status


@asyncio.coroutine
def test_100_continue_for_not_found(loop, test_client):

    app = web.Application(loop=loop)
    client = yield from test_client(app)

    resp = yield from client.post('/not_found', data='data', expect100=True)
    astert 404 == resp.status


@asyncio.coroutine
def test_100_continue_for_not_allowed(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/', expect100=True)
    astert 405 == resp.status


@asyncio.coroutine
def test_http11_keep_alive_default(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/', version=HttpVersion11)
    astert 200 == resp.status
    astert resp.version == HttpVersion11
    astert 'Connection' not in resp.headers


@asyncio.coroutine
def test_http10_keep_alive_default(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/', version=HttpVersion10)
    astert 200 == resp.status
    astert resp.version == HttpVersion10
    astert resp.headers['Connection'] == 'keep-alive'


@asyncio.coroutine
def test_http09_keep_alive_default(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        yield from request.read()
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    headers = {'Connection': 'keep-alive'}  # should be ignored
    resp = yield from client.get('/', version=HttpVersion(0, 9),
                                 headers=headers)
    astert 200 == resp.status
    astert resp.version == HttpVersion(0, 9)
    astert 'Connection' not in resp.headers


@asyncio.coroutine
def test_http10_keep_alive_with_headers_close(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        yield from request.read()
        return web.Response(body=b'OK')

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    headers = {'Connection': 'close'}
    resp = yield from client.get('/', version=HttpVersion10,
                                 headers=headers)
    astert 200 == resp.status
    astert resp.version == HttpVersion10
    astert 'Connection' not in resp.headers


@asyncio.coroutine
def test_http10_keep_alive_with_headers(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        yield from request.read()
        return web.Response(body=b'OK')

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    headers = {'Connection': 'keep-alive'}
    resp = yield from client.get('/', version=HttpVersion10,
                                 headers=headers)
    astert 200 == resp.status
    astert resp.version == HttpVersion10
    astert resp.headers['Connection'] == 'keep-alive'


@asyncio.coroutine
def test_upload_file(loop, test_client):

    here = pathlib.Path(__file__).parent
    fname = here / 'software_development_in_picture.jpg'
    with fname.open('rb') as f:
        data = f.read()

    @asyncio.coroutine
    def handler(request):
        form = yield from request.post()
        raw_data = form['file'].file.read()
        astert data == raw_data
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data={'file': data})
    astert 200 == resp.status


@asyncio.coroutine
def test_upload_file_object(loop, test_client):
    here = pathlib.Path(__file__).parent
    fname = here / 'software_development_in_picture.jpg'
    with fname.open('rb') as f:
        data = f.read()

    @asyncio.coroutine
    def handler(request):
        form = yield from request.post()
        raw_data = form['file'].file.read()
        astert data == raw_data
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    with fname.open('rb') as f:
        resp = yield from client.post('/', data={'file': f})
        astert 200 == resp.status


@asyncio.coroutine
def test_empty_content_for_query_without_body(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        astert not request.has_body
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/')
    astert 200 == resp.status


@asyncio.coroutine
def test_empty_content_for_query_with_body(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        astert request.has_body
        body = yield from request.read()
        return web.Response(body=body)

    app = web.Application(loop=loop)
    app.router.add_post('/', handler)
    client = yield from test_client(app)

    resp = yield from client.post('/', data=b'data')
    astert 200 == resp.status


@asyncio.coroutine
def test_get_with_empty_arg(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        astert 'arg' in request.GET
        astert '' == request.GET['arg']
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/?arg')
    astert 200 == resp.status


@asyncio.coroutine
def test_large_header(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    headers = {'Long-Header': 'ab' * 8129}
    resp = yield from client.get('/', headers=headers)
    astert 400 == resp.status


@asyncio.coroutine
def test_large_header_allowed(loop, test_client, test_server):

    @asyncio.coroutine
    def handler(request):
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    server = yield from test_server(app, max_field_size=81920)
    client = yield from test_client(server)

    headers = {'Long-Header': 'ab' * 8129}
    resp = yield from client.get('/', headers=headers)
    astert 200 == resp.status


@asyncio.coroutine
def test_get_with_empty_arg_with_equal(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        astert 'arg' in request.GET
        astert '' == request.GET['arg']
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/?arg=')
    astert 200 == resp.status


@pytest.mark.xfail  # and had never worked
@asyncio.coroutine
def test_response_with_precompressed_body(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        headers = {'Content-Encoding': 'gzip'}
        deflated_data = zlib.compress(b'mydata')
        return web.Response(body=deflated_data, headers=headers)

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/')
    astert 200 == resp.status
    data = yield from resp.read()
    astert b'mydata' == data
    astert resp.headers.get('Content-Encoding') == 'deflate'


@asyncio.coroutine
def test_stream_response_multiple_chunks(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        resp = web.StreamResponse()
        resp.enable_chunked_encoding()
        yield from resp.prepare(request)
        resp.write(b'x')
        resp.write(b'y')
        resp.write(b'z')
        return resp

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/')
    astert 200 == resp.status
    data = yield from resp.read()
    astert b'xyz' == data


@asyncio.coroutine
def test_start_without_routes(loop, test_client):

    app = web.Application(loop=loop)
    client = yield from test_client(app)

    resp = yield from client.get('/')
    astert 404 == resp.status


@asyncio.coroutine
def test_requests_count(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)
    astert client.handler.requests_count == 0

    resp = yield from client.get('/')
    astert 200 == resp.status
    astert client.handler.requests_count == 1

    resp = yield from client.get('/')
    astert 200 == resp.status
    astert client.handler.requests_count == 2

    resp = yield from client.get('/')
    astert 200 == resp.status
    astert client.handler.requests_count == 3


@asyncio.coroutine
def test_redirect_url(loop, test_client):

    @asyncio.coroutine
    def redirector(request):
        raise web.HTTPFound(location=URL('/redirected'))

    @asyncio.coroutine
    def redirected(request):
        return web.Response()

    app = web.Application(loop=loop)
    app.router.add_get('/redirector', redirector)
    app.router.add_get('/redirected', redirected)

    client = yield from test_client(app)
    resp = yield from client.get('/redirector')
    astert resp.status == 200


@asyncio.coroutine
def test_simple_subapp(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        return web.Response(text="OK")

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    app.router.add_subapp('/path', subapp)

    client = yield from test_client(app)
    resp = yield from client.get('/path/to')
    astert resp.status == 200
    txt = yield from resp.text()
    astert 'OK' == txt


@asyncio.coroutine
def test_subapp_reverse_url(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        return web.HTTPMovedPermanently(
            location=subapp.router['name'].url_for())

    @asyncio.coroutine
    def handler2(request):
        return web.Response(text="OK")

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    subapp.router.add_get('/final', handler2, name='name')
    app.router.add_subapp('/path', subapp)

    client = yield from test_client(app)
    resp = yield from client.get('/path/to')
    astert resp.status == 200
    txt = yield from resp.text()
    astert 'OK' == txt
    astert resp.url_obj.path == '/path/final'


@asyncio.coroutine
def test_subapp_reverse_variable_url(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        return web.HTTPMovedPermanently(
            location=subapp.router['name'].url_for(part='final'))

    @asyncio.coroutine
    def handler2(request):
        return web.Response(text="OK")

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    subapp.router.add_get('/{part}', handler2, name='name')
    app.router.add_subapp('/path', subapp)

    client = yield from test_client(app)
    resp = yield from client.get('/path/to')
    astert resp.status == 200
    txt = yield from resp.text()
    astert 'OK' == txt
    astert resp.url_obj.path == '/path/final'


@asyncio.coroutine
def test_subapp_reverse_static_url(loop, test_client):
    fname = 'software_development_in_picture.jpg'

    @asyncio.coroutine
    def handler(request):
        return web.HTTPMovedPermanently(
            location=subapp.router['name'].url_for(filename=fname))

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    here = pathlib.Path(__file__).parent
    subapp.router.add_static('/static', here, name='name')
    app.router.add_subapp('/path', subapp)

    client = yield from test_client(app)
    resp = yield from client.get('/path/to')
    astert resp.url_obj.path == '/path/static/' + fname
    astert resp.status == 200
    body = yield from resp.read()
    with (here / fname).open('rb') as f:
        astert body == f.read()


@asyncio.coroutine
def test_subapp_app(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        astert request.app is subapp
        return web.HTTPOk(text='OK')

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    app.router.add_subapp('/path/', subapp)

    client = yield from test_client(app)
    resp = yield from client.get('/path/to')
    astert resp.status == 200
    txt = yield from resp.text()
    astert 'OK' == txt


@asyncio.coroutine
def test_subapp_not_found(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        return web.HTTPOk(text='OK')

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    app.router.add_subapp('/path/', subapp)

    client = yield from test_client(app)
    resp = yield from client.get('/path/other')
    astert resp.status == 404


@asyncio.coroutine
def test_subapp_not_found2(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        return web.HTTPOk(text='OK')

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    app.router.add_subapp('/path/', subapp)

    client = yield from test_client(app)
    resp = yield from client.get('/invalid/other')
    astert resp.status == 404


@asyncio.coroutine
def test_subapp_not_allowed(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        return web.HTTPOk(text='OK')

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    app.router.add_subapp('/path/', subapp)

    client = yield from test_client(app)
    resp = yield from client.post('/path/to')
    astert resp.status == 405
    astert resp.headers['Allow'] == 'GET'


@asyncio.coroutine
def test_subapp_cannot_add_app_in_handler(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        request.match_info.add_app(app)
        return web.HTTPOk(text='OK')

    app = web.Application(loop=loop)
    subapp = web.Application(loop=loop)
    subapp.router.add_get('/to', handler)
    app.router.add_subapp('/path/', subapp)

    client = yield from test_client(app)
    resp = yield from client.get('/path/to')
    astert resp.status == 500


@asyncio.coroutine
def test_subapp_middlewares(loop, test_client):
    order = []

    @asyncio.coroutine
    def handler(request):
        return web.HTTPOk(text='OK')

    @asyncio.coroutine
    def middleware_factory(app, handler):

        @asyncio.coroutine
        def middleware(request):
            order.append((1, app))
            resp = yield from handler(request)
            astert 200 == resp.status
            order.append((2, app))
            return resp
        return middleware

    app = web.Application(loop=loop, middlewares=[middleware_factory])
    subapp1 = web.Application(loop=loop, middlewares=[middleware_factory])
    subapp2 = web.Application(loop=loop, middlewares=[middleware_factory])
    subapp2.router.add_get('/to', handler)
    subapp1.router.add_subapp('/b/', subapp2)
    app.router.add_subapp('/a/', subapp1)

    client = yield from test_client(app)
    resp = yield from client.get('/a/b/to')
    astert resp.status == 200
    astert [(1, app), (1, subapp1), (1, subapp2),
            (2, subapp2), (2, subapp1), (2, app)] == order


@asyncio.coroutine
def test_subapp_on_response_prepare(loop, test_client):
    order = []

    @asyncio.coroutine
    def handler(request):
        return web.HTTPOk(text='OK')

    def make_signal(app):

        @asyncio.coroutine
        def on_response(request, response):
            order.append(app)

        return on_response

    app = web.Application(loop=loop)
    app.on_response_prepare.append(make_signal(app))
    subapp1 = web.Application(loop=loop)
    subapp1.on_response_prepare.append(make_signal(subapp1))
    subapp2 = web.Application(loop=loop)
    subapp2.on_response_prepare.append(make_signal(subapp2))
    subapp2.router.add_get('/to', handler)
    subapp1.router.add_subapp('/b/', subapp2)
    app.router.add_subapp('/a/', subapp1)

    client = yield from test_client(app)
    resp = yield from client.get('/a/b/to')
    astert resp.status == 200
    astert [app, subapp1, subapp2] == order


@asyncio.coroutine
def test_subapp_on_startup(loop, test_server):
    order = []

    @asyncio.coroutine
    def on_signal(app):
        order.append(app)

    app = web.Application(loop=loop)
    app.on_startup.append(on_signal)
    subapp1 = web.Application(loop=loop)
    subapp1.on_startup.append(on_signal)
    subapp2 = web.Application(loop=loop)
    subapp2.on_startup.append(on_signal)
    subapp1.router.add_subapp('/b/', subapp2)
    app.router.add_subapp('/a/', subapp1)

    yield from test_server(app)

    astert [app, subapp1, subapp2] == order


@asyncio.coroutine
def test_subapp_on_shutdown(loop, test_server):
    order = []

    def on_signal(app):
        order.append(app)

    app = web.Application(loop=loop)
    app.on_shutdown.append(on_signal)
    subapp1 = web.Application(loop=loop)
    subapp1.on_shutdown.append(on_signal)
    subapp2 = web.Application(loop=loop)
    subapp2.on_shutdown.append(on_signal)
    subapp1.router.add_subapp('/b/', subapp2)
    app.router.add_subapp('/a/', subapp1)

    server = yield from test_server(app)
    yield from server.close()

    astert [app, subapp1, subapp2] == order


@asyncio.coroutine
def test_subapp_on_cleanup(loop, test_server):
    order = []

    @asyncio.coroutine
    def on_signal(app):
        order.append(app)

    app = web.Application(loop=loop)
    app.on_cleanup.append(on_signal)
    subapp1 = web.Application(loop=loop)
    subapp1.on_cleanup.append(on_signal)
    subapp2 = web.Application(loop=loop)
    subapp2.on_cleanup.append(on_signal)
    subapp1.router.add_subapp('/b/', subapp2)
    app.router.add_subapp('/a/', subapp1)

    server = yield from test_server(app)
    yield from server.close()

    astert [app, subapp1, subapp2] == order


@asyncio.coroutine
def test_custom_date_header(loop, test_client):

    @asyncio.coroutine
    def handler(request):
        return web.Response(headers={'Date': 'Sun, 30 Oct 2016 03:13:52 GMT'})

    app = web.Application(loop=loop)
    app.router.add_get('/', handler)
    client = yield from test_client(app)

    resp = yield from client.get('/')
    astert 200 == resp.status
    astert resp.headers['Date'] == 'Sun, 30 Oct 2016 03:13:52 GMT'