python/6871/aiohttp/tests/test_web_websocket_functional.py

test_web_websocket_functional.py
"""HTTP websocket server functional tests"""

import asyncio

import pytest

import aiohttp
from aiohttp import helpers, web
from aiohttp._ws_impl import WSMsgType


@asyncio.coroutine
def test_websocket_json(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)
        msg = yield from ws.receive()

        msg_json = msg.json()
        answer = msg_json['test']
        ws.send_str(answer)

        yield from ws.close()
        return ws

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

    ws = yield from client.ws_connect('/')
    expected_value = 'value'
    payload = '{"test": "%s"}' % expected_value
    ws.send_str(payload)

    resp = yield from ws.receive()
    astert resp.data == expected_value


@asyncio.coroutine
def test_websocket_json_invalid_message(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)
        try:
            yield from ws.receive_json()
        except ValueError:
            ws.send_str('ValueError was raised')
        else:
            raise Exception('No Exception')
        finally:
            yield from ws.close()
        return ws

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

    ws = yield from client.ws_connect('/')
    payload = 'NOT A VALID JSON STRING'
    ws.send_str(payload)

    data = yield from ws.receive_str()
    astert 'ValueError was raised' in data


@asyncio.coroutine
def test_websocket_send_json(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)

        data = yield from ws.receive_json()
        ws.send_json(data)

        yield from ws.close()
        return ws

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

    ws = yield from client.ws_connect('/')
    expected_value = 'value'
    ws.send_json({'test': expected_value})

    data = yield from ws.receive_json()
    astert data['test'] == expected_value


@asyncio.coroutine
def test_websocket_receive_json(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)

        data = yield from ws.receive_json()
        answer = data['test']
        ws.send_str(answer)

        yield from ws.close()
        return ws

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

    ws = yield from client.ws_connect('/')
    expected_value = 'value'
    payload = '{"test": "%s"}' % expected_value
    ws.send_str(payload)

    resp = yield from ws.receive()
    astert resp.data == expected_value


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)
        msg = yield from ws.receive_str()
        ws.send_str(msg+'/answer')
        yield from ws.close()
        closed.set_result(1)
        return ws

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

    ws = yield from client.ws_connect('/')
    ws.send_str('ask')
    msg = yield from ws.receive()
    astert msg.type == aiohttp.WSMsgType.TEXT
    astert 'ask/answer' == msg.data

    msg = yield from ws.receive()
    astert msg.type == aiohttp.WSMsgType.CLOSE
    astert msg.data == 1000
    astert msg.extra == ''

    astert ws.closed
    astert ws.close_code == 1000

    yield from closed


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)

        msg = yield from ws.receive_bytes()
        ws.send_bytes(msg+b'/answer')
        yield from ws.close()
        closed.set_result(1)
        return ws

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

    ws = yield from client.ws_connect('/')
    ws.send_bytes(b'ask')
    msg = yield from ws.receive()
    astert msg.type == aiohttp.WSMsgType.BINARY
    astert b'ask/answer' == msg.data

    msg = yield from ws.receive()
    astert msg.type == aiohttp.WSMsgType.CLOSE
    astert msg.data == 1000
    astert msg.extra == ''

    astert ws.closed
    astert ws.close_code == 1000

    yield from closed


@asyncio.coroutine
def test_send_recv_json(loop, test_client):
    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)
        data = yield from ws.receive_json()
        ws.send_json({'response': data['request']})
        yield from ws.close()
        closed.set_result(1)
        return ws

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

    ws = yield from client.ws_connect('/')

    ws.send_str('{"request": "test"}')
    msg = yield from ws.receive()
    data = msg.json()
    astert msg.type == aiohttp.WSMsgType.TEXT
    astert data['response'] == 'test'

    msg = yield from ws.receive()
    astert msg.type == aiohttp.WSMsgType.CLOSE
    astert msg.data == 1000
    astert msg.extra == ''

    yield from ws.close()

    yield from closed


@asyncio.coroutine
def test_close_timeout(loop, test_client):
    aborted = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse(timeout=0.1)
        yield from ws.prepare(request)
        astert 'request' == (yield from ws.receive_str())
        ws.send_str('reply')
        begin = ws._loop.time()
        astert (yield from ws.close())
        elapsed = ws._loop.time() - begin
        astert elapsed < 0.201, \
            'close() should have returned before ' \
            'at most 2x timeout.'
        astert ws.close_code == 1006
        astert isinstance(ws.exception(), asyncio.TimeoutError)
        aborted.set_result(1)
        return ws

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

    ws = yield from client.ws_connect('/')
    ws.send_str('request')
    astert 'reply' == (yield from ws.receive_str())

    # The server closes here.  Then the client sends bogus messages with an
    # internval shorter than server-side close timeout, to make the server
    # hanging indefinitely.
    yield from asyncio.sleep(0.08, loop=loop)
    msg = yield from ws._reader.read()
    astert msg.type == WSMsgType.CLOSE
    ws.send_str('hang')
    yield from asyncio.sleep(0.08, loop=loop)
    ws.send_str('hang')
    yield from asyncio.sleep(0.08, loop=loop)
    ws.send_str('hang')
    yield from asyncio.sleep(0.08, loop=loop)
    ws.send_str('hang')
    yield from asyncio.sleep(0.08, loop=loop)
    astert (yield from aborted)

    yield from ws.close()


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)
        yield from ws.receive()

        msg = yield from ws.receive()
        astert msg.type == WSMsgType.CLOSE
        astert msg.data == 1000
        astert msg.extra == 'exit message'
        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', autoclose=False, autoping=False)
    ws.ping()
    ws.send_str('ask')

    msg = yield from ws.receive()
    astert msg.type == WSMsgType.PONG
    yield from ws.close(code=1000, message='exit message')
    yield from closed


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)

        ws.ping('data')
        yield from ws.receive()
        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', autoping=False)

    msg = yield from ws.receive()
    astert msg.type == WSMsgType.PING
    astert msg.data == b'data'
    ws.pong()
    yield from ws.close()
    yield from closed


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)

        yield from ws.receive()
        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', autoping=False)

    ws.ping('data')
    msg = yield from ws.receive()
    astert msg.type == WSMsgType.PONG
    astert msg.data == b'data'
    ws.pong()
    yield from ws.close()


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse(autoping=False)
        yield from ws.prepare(request)

        msg = yield from ws.receive()
        astert msg.type == WSMsgType.PING
        ws.pong('data')

        msg = yield from ws.receive()
        astert msg.type == WSMsgType.CLOSE
        astert msg.data == 1000
        astert msg.extra == 'exit message'
        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', autoping=False)

    ws.ping('data')
    msg = yield from ws.receive()
    astert msg.type == WSMsgType.PONG
    astert msg.data == b'data'

    yield from ws.close(code=1000, message='exit message')

    yield from closed


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        ws.set_status(200)
        astert 200 == ws.status
        yield from ws.prepare(request)
        astert 101 == ws.status
        yield from ws.close()
        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', autoping=False)

    yield from ws.close()
    yield from closed
    yield from ws.close()


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse(protocols=('foo', 'bar'))
        yield from ws.prepare(request)
        yield from ws.close()
        astert 'bar' == ws.protocol
        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', protocols=('eggs', 'bar'))

    yield from ws.close()
    yield from closed


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse(protocols=('foo', 'bar'))
        yield from ws.prepare(request)
        yield from ws.close()
        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', autoclose=False,
                                      protocols=('eggs', 'bar'))

    msg = yield from ws.receive()
    astert msg.type == WSMsgType.CLOSE
    yield from ws.close()
    yield from closed


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse(
            autoclose=False, protocols=('foo', 'bar'))
        yield from ws.prepare(request)

        msg = yield from ws.receive()
        astert msg.type == WSMsgType.CLOSE
        astert not ws.closed
        yield from ws.close()
        astert ws.closed
        astert ws.close_code == 1007

        msg = yield from ws.receive()
        astert msg.type == WSMsgType.CLOSED

        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', autoclose=False,
                                      protocols=('eggs', 'bar'))

    yield from ws.close(code=1007)
    msg = yield from ws.receive()
    astert msg.type == WSMsgType.CLOSED
    yield from closed


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

    closed = helpers.create_future(loop)

    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse(protocols=('foo', 'bar'))
        yield from ws.prepare(request)
        yield from ws.close()
        closed.set_result(None)
        return ws

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

    ws = yield from client.ws_connect('/', autoclose=False, autoping=False,
                                      protocols=('eggs', 'bar'))

    msg = yield from ws.receive()
    astert msg.type == WSMsgType.CLOSE

    ws.send_str('text')
    ws.send_bytes(b'bytes')
    ws.ping()

    yield from ws.close()
    yield from closed


@asyncio.coroutine
def test_receive_msg(loop, test_client):
    @asyncio.coroutine
    def handler(request):
        ws = web.WebSocketResponse()
        yield from ws.prepare(request)

        with pytest.warns(DeprecationWarning):
            msg = yield from ws.receive_msg()
            astert msg.data == b'data'
        yield from ws.close()
        return ws

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

    ws = yield from client.ws_connect('/')
    ws.send_bytes(b'data')
    yield from ws.close()