129 lines
4.9 KiB
Python
129 lines
4.9 KiB
Python
"""Tests of the relay. Run from the project directory:
|
|||
|
|
|
||
|
|
.venv/bin/python -m unittest discover tests
|
||
|
|
"""
|
||
|
|
|
||
|
|
import asyncio
|
||
|
|
import json
|
||
|
|
import pathlib
|
||
|
|
import sys
|
||
|
|
import unittest
|
||
|
|
|
||
|
|
from websockets.asyncio.client import connect
|
||
|
|
from websockets.asyncio.server import serve
|
||
|
|
from websockets.exceptions import ConnectionClosed, InvalidStatus
|
||
|
|
|
||
|
|
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent.parent / "relay"))
|
||
|
|
import kokkoja_relay as relay # noqa: E402
|
||
|
|
|
||
|
|
ROOM_A = "A" * 22
|
||
|
|
ROOM_B = "B" * 22
|
||
|
|
|
||
|
|
|
||
|
|
def to_peer(destination, body):
|
||
|
|
return destination.to_bytes(4, "big") + body
|
||
|
|
|
||
|
|
|
||
|
|
class RelayTest(unittest.IsolatedAsyncioTestCase):
|
||
|
|
async def asyncSetUp(self):
|
||
|
|
relay.rooms.clear()
|
||
|
|
self.server = await serve(relay.handle, "127.0.0.1", 0,
|
||
|
|
process_request=relay.check_path,
|
||
|
|
max_size=relay.MAX_MESSAGE_BYTES)
|
||
|
|
port = self.server.sockets[0].getsockname()[1]
|
||
|
|
self.address = f"ws://127.0.0.1:{port}"
|
||
|
|
|
||
|
|
async def asyncTearDown(self):
|
||
|
|
self.server.close()
|
||
|
|
await self.server.wait_closed()
|
||
|
|
|
||
|
|
async def join(self, room):
|
||
|
|
connection = await connect(f"{self.address}/room/{room}")
|
||
|
|
self.addAsyncCleanup(connection.close)
|
||
|
|
welcome = json.loads(await connection.recv())
|
||
|
|
self.assertEqual(welcome["type"], "welcome")
|
||
|
|
return connection, welcome
|
||
|
|
|
||
|
|
async def test_welcome_join_leave(self):
|
||
|
|
first, welcome = await self.join(ROOM_A)
|
||
|
|
self.assertEqual(welcome, {"type": "welcome", "peer": 1, "peers": []})
|
||
|
|
second, welcome = await self.join(ROOM_A)
|
||
|
|
self.assertEqual(welcome, {"type": "welcome", "peer": 2, "peers": [1]})
|
||
|
|
self.assertEqual(json.loads(await first.recv()), {"type": "join", "peer": 2})
|
||
|
|
await second.close()
|
||
|
|
self.assertEqual(json.loads(await first.recv()), {"type": "leave", "peer": 2})
|
||
|
|
|
||
|
|
async def test_broadcast_reaches_the_others_only(self):
|
||
|
|
first, _ = await self.join(ROOM_A)
|
||
|
|
second, _ = await self.join(ROOM_A)
|
||
|
|
third, _ = await self.join(ROOM_A)
|
||
|
|
await first.recv() # join 2
|
||
|
|
await first.recv() # join 3
|
||
|
|
await second.recv() # join 3
|
||
|
|
await second.send(to_peer(0, b"hello"))
|
||
|
|
self.assertEqual(await first.recv(), to_peer(2, b"hello"))
|
||
|
|
self.assertEqual(await third.recv(), to_peer(2, b"hello"))
|
||
|
|
# The sender gets nothing back: the next thing it receives is this.
|
||
|
|
await first.send(to_peer(0, b"next"))
|
||
|
|
self.assertEqual(await second.recv(), to_peer(1, b"next"))
|
||
|
|
|
||
|
|
async def test_directed_message_reaches_one_peer(self):
|
||
|
|
first, _ = await self.join(ROOM_A)
|
||
|
|
second, _ = await self.join(ROOM_A)
|
||
|
|
third, _ = await self.join(ROOM_A)
|
||
|
|
await first.recv()
|
||
|
|
await first.recv()
|
||
|
|
await second.recv()
|
||
|
|
await first.send(to_peer(3, b"for three"))
|
||
|
|
await first.send(to_peer(0, b"for all"))
|
||
|
|
self.assertEqual(await third.recv(), to_peer(1, b"for three"))
|
||
|
|
self.assertEqual(await third.recv(), to_peer(1, b"for all"))
|
||
|
|
self.assertEqual(await second.recv(), to_peer(1, b"for all"))
|
||
|
|
|
||
|
|
async def test_rooms_are_separate(self):
|
||
|
|
in_a, _ = await self.join(ROOM_A)
|
||
|
|
in_b, welcome = await self.join(ROOM_B)
|
||
|
|
self.assertEqual(welcome["peers"], [])
|
||
|
|
other_a, _ = await self.join(ROOM_A)
|
||
|
|
await in_a.recv() # join
|
||
|
|
await in_b.send(to_peer(0, b"b only"))
|
||
|
|
await other_a.send(to_peer(0, b"a only"))
|
||
|
|
self.assertEqual(await in_a.recv(), to_peer(2, b"a only"))
|
||
|
|
|
||
|
|
async def test_room_is_forgotten_when_empty(self):
|
||
|
|
first, _ = await self.join(ROOM_A)
|
||
|
|
await first.close()
|
||
|
|
await first.wait_closed()
|
||
|
|
for _ in range(50):
|
||
|
|
if not relay.rooms:
|
||
|
|
break
|
||
|
|
await asyncio.sleep(0.01)
|
||
|
|
self.assertEqual(relay.rooms, {})
|
||
|
|
|
||
|
|
async def test_bad_path_is_refused(self):
|
||
|
|
for path in ("/", "/room/short", f"/room/{ROOM_A}/extra", "/room/" + "!" * 22):
|
||
|
|
with self.assertRaises(InvalidStatus):
|
||
|
|
await connect(self.address + path)
|
||
|
|
|
||
|
|
async def test_text_message_closes_the_connection(self):
|
||
|
|
first, _ = await self.join(ROOM_A)
|
||
|
|
await first.send("text is not allowed")
|
||
|
|
with self.assertRaises(ConnectionClosed) as caught:
|
||
|
|
await first.recv()
|
||
|
|
self.assertEqual(caught.exception.rcvd.code, relay.CLOSE_BAD_MESSAGE)
|
||
|
|
|
||
|
|
async def test_room_full(self):
|
||
|
|
relay.MAX_PEERS_PER_ROOM, saved = 2, relay.MAX_PEERS_PER_ROOM
|
||
|
|
try:
|
||
|
|
await self.join(ROOM_A)
|
||
|
|
await self.join(ROOM_A)
|
||
|
|
extra = await connect(f"{self.address}/room/{ROOM_A}")
|
||
|
|
with self.assertRaises(ConnectionClosed) as caught:
|
||
|
|
await extra.recv()
|
||
|
|
self.assertEqual(caught.exception.rcvd.code, relay.CLOSE_ROOM_FULL)
|
||
|
|
finally:
|
||
|
|
relay.MAX_PEERS_PER_ROOM = saved
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|