|
4 | 4 | import contextlib |
5 | 5 | import sys |
6 | 6 | import weakref |
7 | | -from typing import Any, Optional |
| 7 | +from typing import Any, NoReturn, Optional |
| 8 | +from unittest import mock |
8 | 9 |
|
9 | 10 | import pytest |
10 | 11 |
|
@@ -724,6 +725,76 @@ async def handler(request): |
724 | 725 | await ws.close() |
725 | 726 |
|
726 | 727 |
|
| 728 | +async def test_heartbeat_connection_closed( |
| 729 | + loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient |
| 730 | +) -> None: |
| 731 | + """Test that the connection is closed while ping is in progress.""" |
| 732 | + ping_count = 0 |
| 733 | + |
| 734 | + async def handler(request: web.Request) -> NoReturn: |
| 735 | + nonlocal ping_count |
| 736 | + ws_server = web.WebSocketResponse(heartbeat=0.05) |
| 737 | + await ws_server.prepare(request) |
| 738 | + # We patch write here to simulate a connection reset error |
| 739 | + # since if we closed the connection normally, the server would |
| 740 | + # would cancel the heartbeat task and we wouldn't get a ping |
| 741 | + with mock.patch.object( |
| 742 | + ws_server._req.transport, "write", side_effect=ConnectionResetError |
| 743 | + ), mock.patch.object( |
| 744 | + ws_server._writer, "ping", wraps=ws_server._writer.ping |
| 745 | + ) as ping: |
| 746 | + try: |
| 747 | + await ws_server.receive() |
| 748 | + finally: |
| 749 | + ping_count = ping.call_count |
| 750 | + assert False |
| 751 | + |
| 752 | + app = web.Application() |
| 753 | + app.router.add_get("/", handler) |
| 754 | + |
| 755 | + client = await aiohttp_client(app) |
| 756 | + ws = await client.ws_connect("/", autoping=False) |
| 757 | + msg = await ws.receive() |
| 758 | + assert msg.type is aiohttp.WSMsgType.CLOSED |
| 759 | + assert msg.extra is None |
| 760 | + assert ws.close_code == WSCloseCode.ABNORMAL_CLOSURE |
| 761 | + assert ping_count == 1 |
| 762 | + await ws.close() |
| 763 | + |
| 764 | + |
| 765 | +async def test_heartbeat_failure_ends_receive( |
| 766 | + loop: asyncio.AbstractEventLoop, aiohttp_client: AiohttpClient |
| 767 | +) -> None: |
| 768 | + """Test that no heartbeat response to the server ends the receive call.""" |
| 769 | + ws_server_close_code = None |
| 770 | + ws_server_exception = None |
| 771 | + |
| 772 | + async def handler(request: web.Request) -> NoReturn: |
| 773 | + nonlocal ws_server_close_code, ws_server_exception |
| 774 | + ws_server = web.WebSocketResponse(heartbeat=0.05) |
| 775 | + await ws_server.prepare(request) |
| 776 | + try: |
| 777 | + await ws_server.receive() |
| 778 | + finally: |
| 779 | + ws_server_close_code = ws_server.close_code |
| 780 | + ws_server_exception = ws_server.exception() |
| 781 | + assert False |
| 782 | + |
| 783 | + app = web.Application() |
| 784 | + app.router.add_get("/", handler) |
| 785 | + |
| 786 | + client = await aiohttp_client(app) |
| 787 | + ws = await client.ws_connect("/", autoping=False) |
| 788 | + msg = await ws.receive() |
| 789 | + assert msg.type is aiohttp.WSMsgType.PING |
| 790 | + msg = await ws.receive() |
| 791 | + assert msg.type is aiohttp.WSMsgType.CLOSED |
| 792 | + assert ws.close_code == WSCloseCode.ABNORMAL_CLOSURE |
| 793 | + assert ws_server_close_code == WSCloseCode.ABNORMAL_CLOSURE |
| 794 | + assert isinstance(ws_server_exception, asyncio.TimeoutError) |
| 795 | + await ws.close() |
| 796 | + |
| 797 | + |
727 | 798 | async def test_heartbeat_no_pong_send_many_messages( |
728 | 799 | loop: Any, aiohttp_client: Any |
729 | 800 | ) -> None: |
|
0 commit comments