def test_handle_action(self): self.s.handle_command(self.uid1, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid1, Command(0, GameAction(m.REQSTARTGAME))) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid1) self.assertEqual(game, 0) self.assertEqual(action, m.GAMESTATE) self.assertEqual(len(args), 1) gs_dict = json.loads(args[0]) self.assertEqual(gs_dict['expected_action'], m.THINKERORLEAD) a = GameAction(m.THINKERORLEAD, True) self.s.handle_command(self.uid1, Command(0, a)) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid1) self.assertEqual(game, 0) self.assertEqual(action, m.GAMESTATE) self.assertEqual(len(args), 1) gs_dict = json.loads(args[0]) self.assertEqual(gs_dict['expected_action'], m.THINKERTYPE)
def test_create_two_games(self): self.s.handle_command(self.uid1, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid2, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid1, Command(None, GameAction(m.REQGAMELIST))) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid1) self.assertIsNone(game) self.assertEqual(action, m.GAMELIST) self.assertEqual(len(args), 1) # one JSON string records = map(lambda t: GameRecord(**t), json.loads(args[0])) self.assertEqual(len(records), 2) # two games record = records[0] self.assertEqual(record.game_id, 0) self.assertEqual(record.players, ['p1']) self.assertEqual(record.started, False) record = records[1] self.assertEqual(record.game_id, 1) self.assertEqual(record.players, ['p2']) self.assertEqual(record.started, False)
def test_start_game(self): self.s.handle_command(self.uid1, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid1, Command(0, GameAction(m.REQSTARTGAME))) # REQSTARTGAME has two respones: STARTGAME then GAMESTATE user, game, action, args = self.get_response(-2) self.assertEqual(user, self.uid1) self.assertEqual(game, 0) self.assertEqual(action, m.STARTGAME) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid1) self.assertEqual(game, 0) self.assertEqual(action, m.GAMESTATE) self.assertEqual(len(args), 1) # GameState JSON gs_dict = json.loads(args[0]) # Ensure valid JSON self.s.handle_command(self.uid1, Command(0, GameAction(m.REQGAMELIST))) user, game, action, args = self.get_response(-1) records = map(lambda t: GameRecord(**t), json.loads(args[0])) self.assertEqual(len(records), 1) record = records[0] self.assertEqual(record.game_id, 0) self.assertEqual(record.players, ['p1']) self.assertEqual(record.started, True)
def test_join_game(self): self.s.handle_command(self.uid1, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid2, Command(0, GameAction(m.REQJOINGAME))) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid2) self.assertEqual(game, 0) self.assertEqual(action, m.JOINGAME) self.assertEqual(args, []) self.s.handle_command(self.uid2, Command(0, GameAction(m.REQGAMELIST))) user, game, action, args = self.get_response(-1) records = map(lambda t: GameRecord(**t), json.loads(args[0])) self.assertEqual(len(records), 1) record = records[0] self.assertEqual(record.game_id, 0) self.assertEqual(record.players, ['p1', 'p2']) self.assertEqual(record.started, False)
def test_join_game_twice(self): self.s.handle_command(self.uid1, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid1, Command(0, GameAction(m.REQJOINGAME))) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid1) self.assertIsNone(game) self.assertEqual(action, m.SERVERERROR)
def test_start_game_not_joined(self): self.s.handle_command(self.uid1, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid2, Command(0, GameAction(m.REQSTARTGAME))) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid2) self.assertIsNone(game) self.assertEqual(action, m.SERVERERROR)
def test_from_json_bad_command(self): """Raises GameActionError if the valid JSON doesn't represent a valid Command. """ c_json = '{"game":"notagame", "action":{"action": 0, "args": [true]}}' with self.assertRaises(GameActionError): a = Command.from_json(c_json) c_json = '{"game":"0", "action":{"args": [true]}}' with self.assertRaises(ParsingError): a = Command.from_json(c_json)
def test_from_json_bad_command(self): """Raises GameActionError if the valid JSON doesn't represent a valid Command. """ c_json = '{"game":"notagame", "number": 0, "action":{"action": 0, "args": [true]}}' with self.assertRaises(GameActionError): a = Command.from_json(c_json) c_json = '{"game":"0", "number": 0, "action":{"args": [true]}}' with self.assertRaises(ParsingError): a = Command.from_json(c_json)
def test_from_bad_json(self): """Failure to convert bad JSON raises ParsingError. """ c_json = '{"act]]]}' # not valid JSON with self.assertRaises(ParsingError): c = Command.from_json(c_json)
def test_from_bad_json(self): """Failure to convert bad JSON raises ParsingError. """ c_json = '{"act]]]}' # not valid JSON with self.assertRaises(ParsingError): c = Command.from_json(c_json)
def test_handle_bad_action(self): """A bad action will send a SERVERERROR and then the GAMESTATE. """ self.s.handle_command(self.uid1, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid1, Command(0, GameAction(m.REQSTARTGAME))) a = GameAction(m.PATRONFROMDECK, True) self.s.handle_command(self.uid1, Command(0, a)) user, game, action, args = self.get_response(-2) self.assertEqual(user, self.uid1) self.assertEqual(action, m.SERVERERROR) self.assertIsNone(game)
def test_join_full_game(self): uids = [uuid4().int for i in range(5)] for i, uid in enumerate(uids): self.s.register_user(uid, {'name': 'p' + str(i + 1)}) self.s.handle_command(uids[0], Command(None, GameAction(m.REQCREATEGAME))) for uid in uids[1:]: self.s.handle_command(uid, Command(0, GameAction(m.REQJOINGAME))) self.s.handle_command(self.uid1, Command(0, GameAction(m.REQJOINGAME))) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid1) self.assertIsNone(game) self.assertEqual(action, m.SERVERERROR)
def _send_gamestate(self, user, game): """Sends the game state from the specified game to the user as a GAMESTATE command. """ gs = self._get_game_state(user, game) gs_json = json.dumps(gs, sort_keys=True, default=lambda o: o.__dict__) resp = Command(game, GameAction(message.GAMESTATE, gs_json)) self.send_command(user, resp)
def test_join_nonexistent_game(self): self.s.handle_command(self.uid2, Command(0, GameAction(m.REQJOINGAME))) user, game, action, args = self.get_response(0) self.assertEqual(user, self.uid2) self.assertIsNone(game) self.assertEqual(action, m.SERVERERROR)
def test_gamestate(self): self.s.handle_command(self.uid1, Command(None, GameAction(m.REQCREATEGAME))) self.s.handle_command(self.uid1, Command(0, GameAction(m.REQSTARTGAME))) self.s.handle_command(self.uid1, Command(0, GameAction(m.REQGAMESTATE))) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid1) self.assertEqual(game, 0) self.assertEqual(action, m.GAMESTATE) self.assertEqual(len(args), 1) gs_dict = json.loads(args[0]) self.assertEqual(gs_dict['turn_number'], 1)
def test_from_json(self): """Convert JSON dictionary to Command. """ c_json = '{"game":1, "number": 0, "action":{"action": 0, "args": [true]}}' c = Command.from_json(c_json)[0] self.assertEqual(c.action.action, message.THINKERORLEAD) self.assertEqual(c.game, 1) self.assertEqual(c.action.args, [True])
def test_from_json(self): """Convert JSON dictionary to Command. """ c_json = '{"game":1, "action":{"action": 0, "args": [true]}}' c = Command.from_json(c_json) self.assertEqual(c.action.action, message.THINKERORLEAD) self.assertEqual(c.game, 1) self.assertEqual(c.action.args, [True])
def test_to_json(self): """Convert Command to JSON. """ a = GameAction(message.THINKERORLEAD, True) c = Command(1, a) c_json = c.to_json() d = json.loads(c_json) self.assertIn('action', d.keys()) self.assertIn('game', d.keys()) action, args, game = d['action']['action'], d['action']['args'], d['game'] self.assertEqual(action, message.THINKERORLEAD) self.assertEqual(args, [True]) self.assertEqual(game, 1)
def test_to_json(self): """Convert Command to JSON. """ a = GameAction(message.THINKERORLEAD, True) c = Command(1, a) c_json = c.to_json() d = json.loads(c_json) self.assertIn('action', d.keys()) self.assertIn('game', d.keys()) action, args, game = d['action']['action'], d['action']['args'], d[ 'game'] self.assertEqual(action, message.THINKERORLEAD) self.assertEqual(args, [True]) self.assertEqual(game, 1)
def test_multiple_commands_to_json_non_sequential_numbers(self): a0 = GameAction(message.THINKERORLEAD, True) a1 = GameAction(message.THINKERTYPE, True) c0 = Command(1, 0, a0) c1 = Command(1, 3, a1) c0_json, c1_json = [c.to_json() for c in (c0,c1)] list_json = '[{0},{1}]'.format(c0_json, c1_json) with self.assertRaises(ParsingError): commands = Command.from_json(list_json)
def test_gamelist(self): self.s.handle_command(self.uid1, Command(None, GameAction(m.REQGAMELIST))) user, game, action, args = self.get_response(0) self.assertEqual(user, self.uid1) self.assertIsNone(game) self.assertEqual(action, m.GAMELIST) self.assertEqual(len(args), 1) records = json.loads(args[0]) self.assertEqual(records, [])
def test_handle_invalid_actions(self): """The server doens't handle GAMESTATE, CREATEGAME, etc. Those are exclusively server responses sent to the client. """ for game, action, args in [(None, m.CREATEGAME, []), (0, m.JOINGAME, []), (None, m.GAMESTATE, ['notagamestate']), (None, m.GAMELIST, ['notagamelist']), (None, m.LOGIN, ['0']), (0, m.STARTGAME, [])]: self.s.handle_command(self.uid1, Command(game, GameAction(action, *args))) user, game, action, args = self.get_response(-1) self.assertEqual(user, self.uid1) self.assertEqual(action, m.SERVERERROR) self.assertIsNone(game)
def test_multiple_commands_from_json(self): """Convert a list of Commands to JSON. """ a0 = GameAction(message.THINKERORLEAD, True) a1 = GameAction(message.THINKERTYPE, True) c0 = Command(1, 0, a0) c1 = Command(1, 1, a1) c0_json, c1_json = [c.to_json() for c in (c0,c1)] list_json = '[{0},{1}]'.format(c0_json, c1_json) commands = Command.from_json(list_json) self.assertEqual(len(commands), 2) for c, c_orig in zip(commands, (c0,c1)): self.assertEqual(c_orig.action.action, c.action.action) self.assertEqual(c_orig.action.args, c.action.args) self.assertEqual(c_orig.number, c.number) self.assertEqual(c_orig.game, c.game)
def on_message(self, message): lg.debug('WS handler received message: '+str(message)) try: commands = Command.from_json(message) except ParsingError as e: self.message_error_count += 1 if self.message_error_count >= self.MESSAGE_ERROR_THRESHOLD: self.close() else: self.send_command( Command(None, None, GameAction(cloaca.message.SERVERERROR, 'Error parsing message: '+e.message))) return else: user_id = self.current_user['user_id'] game_id = commands[0].game if commands[0].action.action == cloaca.message.LOGIN: lg.debug('Ignoring deprecated LOGIN message.') elif commands[0].action.action == cloaca.message.REQGAMESTATE: lg.debug('Received request for game {0!s}.'.format(game_id)) try: game_encoded = yield self.server.get_game_data(user_id, game_id) except GTRError as e: lg.debug('Sending error') self.send_error(e.message) else: resp = Command(game_id, None, GameAction(cloaca.message.GAMESTATE, game_encoded)) lg.debug('Sending game '+str(game_id)) self.send_command(resp) elif commands[0].action.action == cloaca.message.REQGAMELOG: lg.debug('Received request for log game {0!s}.'.format(game_id)) n_messages, n_start = commands[0].action.args yield self.server.retrieve_and_send_log_messages( user_id, game_id, n_messages, n_start) else: yield self.server.handle_game_actions( game_id, user_id, [[c.number, c.action] for c in commands])
def _send_error(self, user, msg): resp = Command(None, GameAction(message.SERVERERROR, msg)) self.send_command(user, resp)
def handle_command(self, user, command): """Muliplexes the action to helper functions. """ game_id = command.game action = command.action.action args = command.action.args lg.debug('Handling command: {0!s}, {1!s}, {2!s}, {3!s}'.format( user, game_id, action, args)) if action == message.REQGAMESTATE: self._send_gamestate(user, game_id) elif action == message.REQGAMELIST: gl = self._get_game_list() json_list = json.dumps(gl, sort_keys=True, default=lambda o: o.__dict__) resp = Command(game_id, GameAction(message.GAMELIST, json_list)) self.send_command(user, resp) elif action == message.REQJOINGAME: # Game id is the argument here, not the game part of the request try: id_ = self._join_game(user, game_id) except GTRError as e: # I want to filter out the case where we're re-joining. Presumably # the user wants the game state and a join acknowledgement. # They can get this with a GAMESTATE request, though. self._send_error(user, e.message) else: resp = Command(id_, GameAction(message.JOINGAME)) self.send_command(user, resp) # If the game is started, we need the game state gs = self._get_game_state(user, id_) if gs is not None and self.games[id_].started: self._send_gamestate(user, id_) elif action == message.REQSTARTGAME: try: self._start_game(user, game_id) except GTRError as e: self._send_error(user, e.message) else: gs = self._get_game_state(user, game_id) for u in [p.uid for p in gs.players]: resp = Command(game_id, GameAction(message.STARTGAME)) self.send_command(u, resp) self._send_gamestate(u, game_id) elif action == message.REQCREATEGAME: game_id = self._create_game(user) resp = Command(game_id, GameAction(message.JOINGAME)) self.send_command(user, resp) elif action in (message.CREATEGAME, message.JOINGAME, message.GAMESTATE, message.GAMELIST, message.STARTGAME, message.LOGIN): # Todo: send error to client. # It would be better to check if the action is a GameAction # command and return an error otherwise self._send_error(user, 'Invalid server command: ' + str(command.action)) else: # Game commands try: game = self.games[game_id] except IndexError as e: msg = ("Couldn't find game {0:d} in {1!s}").format( game_id, self.games[:10]) lg.warning(msg) self._send_error(user, msg) name = self._userinfo(user)['name'] player_index = game.find_player_index(name) if player_index is None: msg = ('User {0} is not part of game {1:d}, players: {2!s}' ).format(name, game_id, [p.name for p in game.players]) lg.warning(msg) self._send_error(user, msg) lg.debug('Expected action: {0}'.format(str(game.expected_action))) lg.debug('Got action: {0}'.format(repr(action))) lg.debug('Handling action: {0}'.format(repr(action))) i_active_p = game.active_player_index if game.finished: self._send_error(user, 'Game {0} has finished.'.format(game_id)) if i_active_p == player_index: try: game.handle(command.action) except GTRError as e: lg.warning(e.message) self._send_error(user, e.message) except GameOver: lg.info('Game {0} has ended.'.format(game_id)) self._save_backup() else: msg = ('Received action for player {0!s}, ' 'but waiting on player {1!s}').format( player_index, i_active_p) lg.warning(msg) self._send_error(user, msg) for u in [p.uid for p in game.players]: gs = self._get_game_state(u, game_id) self._send_gamestate(u, game_id)