From 0bdaea4591fae05a1a93b6ddcf066f92e8f224f4 Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Sat, 25 Dec 2021 14:09:59 +0100 Subject: Move WS code outside the main into swiftstory.interface.ws.py Also, the GameManager object is not created globally anymore when importing the module. Signed-off-by: Olivier Gayot --- swiftstory/__main__.py | 83 ++++------------------------------------ swiftstory/interface/__init__.py | 0 swiftstory/interface/ws.py | 80 ++++++++++++++++++++++++++++++++++++++ tests/test_swiftstory.py | 56 --------------------------- tests/test_ws_interface.py | 61 +++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 131 deletions(-) create mode 100644 swiftstory/interface/__init__.py create mode 100644 swiftstory/interface/ws.py delete mode 100644 tests/test_swiftstory.py create mode 100644 tests/test_ws_interface.py diff --git a/swiftstory/__main__.py b/swiftstory/__main__.py index 99176a0..6b7111d 100644 --- a/swiftstory/__main__.py +++ b/swiftstory/__main__.py @@ -4,86 +4,17 @@ import argparse import asyncio -import contextlib -import json import logging -import websockets.server -import websockets.exceptions -import swiftstory.game_manager -from swiftstory.exception import WrongAction, JoinError -from swiftstory.client import Client -from swiftstory.status import error +from websockets.server import serve - -game_manager = swiftstory.game_manager.GameManager() - - -def message_received_handler(client, message): - def join_game(): - try: - game_name = json_msg['game_name'] - except KeyError: - return error('field `game_name\' is required') - else: - lang = json_msg.get('lang') - return client.join_game(game_name, lang) - - def designate_card(): - card_id = None - try: - card_id = int(json_msg['card_id']) - except (KeyError, TypeError): - pass - finally: - return client.designate_card(card_id) - - def play_white_card(): - try: - card_id = int(json_msg['card_id']) - except KeyError: - return error('field `card_id\' is required') - else: - return client.play_white_card(card_id) - - opcodes_map = { - "join_game": join_game, - "view_player_cards": lambda: client.view_player_cards(), - "view_black_card": lambda: client.view_black_card(), - "view_played_cards": lambda: client.view_played_cards(), - "pick_black_card": lambda: client.pick_black_card(), - "collect_cards": lambda: client.collect_cards(), - "designate_card": designate_card, - "play_white_card": play_white_card, - } - - try: - json_msg = json.loads(message) - except json.JSONDecodeError: - return error('badly formatted json') - - try: - return opcodes_map[json_msg["op"]]() - except (KeyError, TypeError): - return error('invalid command') - except WrongAction as e: - return error(str(e)) - except JoinError as e: - logging.warning("player could not join game: %s", e.__repr__()) - return error(str(e)) - - -async def connection_handler(websocket, path): - client = Client(websocket, game_manager) - - with contextlib.suppress(websockets.exceptions.ConnectionClosed): - async for message in client.socket: - await client.socket.send(message_received_handler(client, message)) - - client.disconnect() +from swiftstory.game_manager import GameManager +from swiftstory.interface.ws import WebsocketsInterface def main(): + """ Entry point: we create the game manager and start the Websockets + server. """ parser = argparse.ArgumentParser() parser.add_argument('--listen', type=str, default='0.0.0.0') parser.add_argument('--port', type=int, default=1236) @@ -91,7 +22,9 @@ def main(): logging.basicConfig(level=logging.INFO) - start_server = websockets.server.serve(connection_handler, args['listen'], args['port']) + ws_interface = WebsocketsInterface(GameManager()) + start_server = serve(ws_interface.connection_handler, + args['listen'], args['port']) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever() diff --git a/swiftstory/interface/__init__.py b/swiftstory/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/swiftstory/interface/ws.py b/swiftstory/interface/ws.py new file mode 100644 index 0000000..d63e71c --- /dev/null +++ b/swiftstory/interface/ws.py @@ -0,0 +1,80 @@ +import asyncio +import contextlib +import json +import logging + +import websockets.exceptions +from websockets.server import WebSocketServerProtocol + +from swiftstory.client import Client +from swiftstory.exception import WrongAction, JoinError +from swiftstory.game_manager import GameManager +from swiftstory.status import error + + +class WebsocketsInterface: + """ Interface with WebSockets for SwiftStory. """ + def __init__(self, game_manager: GameManager): + self.game_manager = game_manager + + async def connection_handler(self, websocket: WebSocketServerProtocol, path: str): + client = Client(websocket, self.game_manager) + + with contextlib.suppress(websockets.exceptions.ConnectionClosed): + async for message in client.socket: + await client.socket.send(self.message_received_handler(client, message)) + + client.disconnect() + + def message_received_handler(self, client, message): + def join_game(): + try: + game_name = json_msg['game_name'] + except KeyError: + return error('field `game_name\' is required') + else: + lang = json_msg.get('lang') + return client.join_game(game_name, lang) + + def designate_card(): + card_id = None + try: + card_id = int(json_msg['card_id']) + except (KeyError, TypeError): + pass + finally: + return client.designate_card(card_id) + + def play_white_card(): + try: + card_id = int(json_msg['card_id']) + except KeyError: + return error('field `card_id\' is required') + else: + return client.play_white_card(card_id) + + opcodes_map = { + "join_game": join_game, + "view_player_cards": lambda: client.view_player_cards(), + "view_black_card": lambda: client.view_black_card(), + "view_played_cards": lambda: client.view_played_cards(), + "pick_black_card": lambda: client.pick_black_card(), + "collect_cards": lambda: client.collect_cards(), + "designate_card": designate_card, + "play_white_card": play_white_card, + } + + try: + json_msg = json.loads(message) + except json.JSONDecodeError: + return error('badly formatted json') + + try: + return opcodes_map[json_msg["op"]]() + except (KeyError, TypeError): + return error('invalid command') + except WrongAction as e: + return error(str(e)) + except JoinError as e: + logging.warning("player could not join game: %s", e.__repr__()) + return error(str(e)) diff --git a/tests/test_swiftstory.py b/tests/test_swiftstory.py deleted file mode 100644 index aac1865..0000000 --- a/tests/test_swiftstory.py +++ /dev/null @@ -1,56 +0,0 @@ -import unittest - -import swiftstory.__main__ as SwiftStory -from swiftstory.status import error - - -class TestSwiftStory(unittest.TestCase): - def test_receive_invalid_json(self): - self.assertEqual( - error("badly formatted json"), - SwiftStory.message_received_handler(client=None, message="{invalid_json}") - ) - - def test_receive_json_array(self): - self.assertEqual( - error("invalid command"), - SwiftStory.message_received_handler(client=None, message='[]') - ) - - def test_receive_json_number(self): - self.assertEqual( - error("invalid command"), - SwiftStory.message_received_handler(client=None, message='2.3') - ) - - def test_receive_json_null(self): - self.assertEqual( - error("invalid command"), - SwiftStory.message_received_handler(client=None, message='null') - ) - - def test_receive_unknown_command(self): - self.assertEqual( - error("invalid command"), - SwiftStory.message_received_handler(client=None, message='{"op": "unknown"}') - ) - - def test_receive_without_command(self): - self.assertEqual( - error("invalid command"), - SwiftStory.message_received_handler(client=None, message='{}') - ) - - def test_play_card_not_specified(self): - payload = '{"op": "play_white_card"}' - self.assertEqual( - error("field `card_id' is required"), - SwiftStory.message_received_handler(client=None, message=payload) - ) - - def test_join_game_not_specified(self): - payload = '{"op": "join_game"}' - self.assertEqual( - error("field `game_name' is required"), - SwiftStory.message_received_handler(client=None, message=payload) - ) diff --git a/tests/test_ws_interface.py b/tests/test_ws_interface.py new file mode 100644 index 0000000..6776334 --- /dev/null +++ b/tests/test_ws_interface.py @@ -0,0 +1,61 @@ +import unittest + +from swiftstory.interface.ws import WebsocketsInterface +from swiftstory.status import error + + +class TestWebsocketsInterface(unittest.TestCase): + def setUp(self): + # XXX Passing None as the GameManager. Only OK if we don't try to join + # a game. + self.interface = WebsocketsInterface(None) + + def test_receive_invalid_json(self): + self.assertEqual( + error("badly formatted json"), + self.interface.message_received_handler(client=None, message="{invalid_json}") + ) + + def test_receive_json_array(self): + self.assertEqual( + error("invalid command"), + self.interface.message_received_handler(client=None, message='[]') + ) + + def test_receive_json_number(self): + self.assertEqual( + error("invalid command"), + self.interface.message_received_handler(client=None, message='2.3') + ) + + def test_receive_json_null(self): + self.assertEqual( + error("invalid command"), + self.interface.message_received_handler(client=None, message='null') + ) + + def test_receive_unknown_command(self): + self.assertEqual( + error("invalid command"), + self.interface.message_received_handler(client=None, message='{"op": "unknown"}') + ) + + def test_receive_without_command(self): + self.assertEqual( + error("invalid command"), + self.interface.message_received_handler(client=None, message='{}') + ) + + def test_play_card_not_specified(self): + payload = '{"op": "play_white_card"}' + self.assertEqual( + error("field `card_id' is required"), + self.interface.message_received_handler(client=None, message=payload) + ) + + def test_join_game_not_specified(self): + payload = '{"op": "join_game"}' + self.assertEqual( + error("field `game_name' is required"), + self.interface.message_received_handler(client=None, message=payload) + ) -- cgit v1.2.3