def setUp(self): super(TestChessManager, self).setUp() self.manager = ChessManager(fakeredis.FakeStrictRedis()) self.board_id = self.manager.create_board( white_username='******', black_username='******', )
def __init__(self, redis_pool, app, connected_websockets): self.chess_manager = ChessManager(redis_pool) self.user_manager = UserManager(redis_pool, app) self.tournament_manager = TournamentManager(redis_pool, self.chess_manager) self.board_subscribers = {} self.redis_pool = redis_pool self.app = app self.connected_websockets = connected_websockets
def setUp(self): super(TestChessManager, self).setUp() self.manager = ChessManager() self.board_id = self.manager.create_board( white_username='******', black_username='******', )
def __init__(self, redis_pool, app): self.chess_manager = ChessManager(redis_pool) self.user_manager = UserManager(redis_pool, app) self.board_subscribers = {} self.redis_pool = redis_pool self.app = app # self.notify_next_turn() self.pool = Pool(1000)
class TestChessManager(unittest.TestCase): def setUp(self): super(TestChessManager, self).setUp() self.fake_redis = fakeredis.FakeStrictRedis() self.manager = ChessManager(self.fake_redis) self.board_id = self.manager.create_board( white_username='******', black_username='******', move_left=10, ) def test_get_invalid_board(self): with self.assertRaises(InvalidBoardIdException): self.manager.get_board_by_id('hola-mundo') def test_save(self): _board = BoardFactory.size_16() board_id = '1234567890' with freeze_time('2020-10-20 13:15:32'): board = PlayingBoard(_board, 'white player', 'black player', 10) self.manager._save_board(board_id, board) self.assertTrue(self.fake_redis.exists('board:1234567890')) saved_boar_str = self.fake_redis.get('board:1234567890') restored_board = ujson.loads(saved_boar_str) board_str = (('rrhhbbqqkkbbhhrr' * 2) + ('pppppppppppppppp' * 2) + (' ' * 8) + ('PPPPPPPPPPPPPPPP' * 2) + ('RRHHBBQQKKBBHHRR' * 2)) expected_board = { 'board': { 'actual_turn': 'white', 'size': 16, 'board': board_str }, 'white_username': '******', 'black_username': '******', 'turn_token': None, 'board_id': None, 'white_score': 0, 'black_score': 0, 'move_left': 10, 'created': '2020-10-20 13:15:32', } self.assertEqual( restored_board, expected_board, ) def test_create_board(self): board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, WHITE) self.assertEqual(board.white_score, 0) self.assertEqual(board.black_score, 0) self.assertTrue( self.fake_redis.exists('board:{}'.format(self.board_id))) def test_invalid_move(self): with self.assertRaises(InvalidTurnException): self.manager.move(self.board_id, 2, 3, 3, 3) board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, WHITE) self.assertEqual(board.white_score, 0) self.assertEqual(board.black_score, 0) def test_move(self): self.manager.move(self.board_id, 12, 3, 11, 3) board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, BLACK) self.assertEqual(board.black_score, 0) self.assertEqual(board.white_score, 10) def test_start_game(self): turn_token = self.manager.challenge('user1', 'user2', 10) self.assertIsNotNone(turn_token) def test_move_with_turn_token(self): board_id = self.manager.challenge('user1', 'user2', 10) first_turn_token, white_username, actual_turn_color, board, move_left = self.manager.challenge_accepted( board_id) # initial board turn should be WHITE self.assertEqual(actual_turn_color, WHITE) self.assertEqual(move_left, 9) # move WHITE with token second_turn_token, black_username, actual_turn_color, board, move_left = self.manager.move_with_turn_token( first_turn_token, 12, 3, 11, 3) # second board turn should be BLACK self.assertEqual(actual_turn_color, BLACK) self.assertEqual(move_left, 8) # invalid turn token exception with self.assertRaises(InvalidTurnTokenException): self.manager.move_with_turn_token(first_turn_token, 12, 4, 11, 4) # move BLACK with token third_turn_token, white_username, actual_turn_color, board, move_left = self.manager.move_with_turn_token( second_turn_token, 3, 3, 4, 3) self.assertIsNotNone(third_turn_token) self.assertEqual(actual_turn_color, WHITE) self.assertEqual(move_left, 7) def test_save_user_stats(self): board = self.manager.get_board_by_id(self.board_id) self.manager._save_user_stats(board) self.assertTrue( self.fake_redis.exists(self.manager.get_user_stats_key('white'))) self.assertTrue( self.fake_redis.exists(self.manager.get_user_stats_key('black'))) self.assertEqual( self.fake_redis.llen(self.manager.get_user_stats_key('white')), 1, ) self.assertEqual( self.fake_redis.llen(self.manager.get_user_stats_key('black')), 1, )
class Controller: def __init__(self, redis_pool, app, connected_websockets): self.chess_manager = ChessManager(redis_pool) self.user_manager = UserManager(redis_pool, app) self.tournament_manager = TournamentManager(redis_pool, self.chess_manager) self.board_subscribers = {} self.redis_pool = redis_pool self.app = app self.connected_websockets = connected_websockets async def execute_message(self, client, message): self.app.logger.info('process_message: message: {}'.format(message)) await self.process_message(client, message) async def get_current_username(self, client): auth_token = client.args.get('authtoken') if not auth_token: raise NoTokenException() return await self.user_manager.get_username_by_auth_token(auth_token) async def process_message(self, client, message): method_name, data = await self.parse_message(message) current_username = await self.get_current_username(client) self.app.logger.info('process_message from {}: {} {}'.format( current_username, method_name, data)) method = getattr(self, method_name) try: await method(current_username, client, data) # await self.send(client, 'response_ok', data) except Exception as e: tb = traceback.format_exc() self.app.logger.error('exception {} {}'.format(e, tb)) data = {'exception': str(type(e))} # gevent.spawn( await self.send(client, 'response_error', data) raise e async def parse_message(self, message): try: job = ujson.loads(message) except ValueError: raise InvalidActionFormatException() if 'action' not in job: raise InvalidNoActionException() action_name = job['action'] method_name = 'action_' + str(action_name) if not hasattr(self, method_name): raise InvalidActionNameException() if 'data' not in job: raise InvalidNoDataException() data = job['data'] return method_name, data def valid_auth(self, data): return 'username' in data and 'password' in data async def action_register(self, current_username, client, data): if not self.valid_auth(data): raise InvalidRegisterException() return self.user_manager.register(data['username'], data['password']) async def action_get_connected_users(self, current_username, client, data): self.app.logger.info('action_get_connected {} {}'.format( client, current_username)) data = {'users_list': await self.get_active_users()} await self.send(client, 'update_user_list', data) async def action_login(self, current_username, client, data=None): self.app.logger.info('action_login {} {}'.format( client, current_username)) client.username = current_username client.queue.username = current_username data = {'users_list': await self.get_active_users()} self.app.logger.info('connected users: {}'.format(data)) await self.broadcast('update_user_list', data) return True async def get_active_users(self): return { queue.username for queue in self.connected_websockets if hasattr(queue, 'username') } def get_username_by_client(self, client): for queue in self.connected_websockets: if (hasattr(queue, 'webservice') and queue.webservice == client and hasattr(queue, 'username')): return queue.username async def action_challenge(self, current_username, client, data): challenged_username = data['username'] challenger_username = current_username await self._challenge(challenger_username, challenged_username) async def challenge_with_auth_token(self, auth_token, username, message): challenger_username = await self.user_manager.get_username_by_auth_token( auth_token) return await self._challenge(challenger_username, username) async def _challenge(self, challenger_username, challenged_username): self.app.logger.info('action_challenge {} from {}'.format( challenged_username, challenger_username)) if random.choice([True, False]): white_username = challenger_username black_username = challenged_username else: white_username = challenged_username black_username = challenger_username move_left = 200 board_id = self.chess_manager.challenge( white_username=white_username, black_username=black_username, move_left=move_left, ) data = { 'username': challenger_username, 'board_id': board_id, } await self.broadcast('ask_challenge', data, challenged_username) return True async def broadcast(self, event, data, username=None): for queue in self.connected_websockets: if (not username or (hasattr(queue, 'username') and username == queue.username)): message = { 'event': event, 'data': data, } await queue.put(ujson.dumps(message)) async def action_accept_challenge(self, current_username, client, data): board_id = data['board_id'] await self._start_board(board_id) return True async def _start_board(self, board_id): turn_token, username, actual_turn, board, move_left, opponent_username = self.chess_manager.challenge_accepted( board_id) next_turn_data = { 'board_id': board_id, 'turn_token': turn_token, 'username': username, 'actual_turn': actual_turn, 'board': board, 'move_left': move_left, 'opponent_username': opponent_username, } self.app.logger.info('action_accept_challenge ok'.format( board_id, next_turn_data)) await self.set_next_turn(board_id, next_turn_data) async def action_abort(self, current_username, client, data): board_id = data['board_id'] self.chess_manager.abort(board_id, current_username) await self.send_gameover(board_id) async def action_move(self, current_username, client, data): board_id = data['board_id'] turn_token = data['turn_token'] key = self.get_next_turn_key(board_id, turn_token) self.app.logger.info('action_move control timeout {}'.format(key)) if self.redis_pool.exists(key): self.app.logger.info( 'action_move control timeout OK {}'.format(key)) self.redis_pool.delete(key) else: # timeout... self.app.logger.info( 'action_move control timeout ERROR {}'.format(key)) raise TimeoutException() processed = False try: turn_token, username, actual_turn, board, move_left, opponent_username = self.chess_manager.move_with_turn_token( turn_token=data['turn_token'], from_row=data['from_row'], from_col=data['from_col'], to_row=data['to_row'], to_col=data['to_col'], ) processed = True except GameOverException: await self.send_gameover(board_id) return except Exception as e: tb = traceback.format_exc() try: self.app.logger.error('action_move {} exception {} {}'.format( board_id, e, tb)) await self.force_change_turn(data['board_id'], data['turn_token']) return # turn_token, username, actual_turn, board, move_left = self.chess_manager._next_turn_token(board_id) except GameOverException: await self.send_gameover(board_id) return next_turn_data = { 'board_id': board_id, 'turn_token': turn_token, 'username': username, 'actual_turn': actual_turn, 'board': board, 'move_left': move_left, 'opponent_username': opponent_username, } await self.set_next_turn(board_id, next_turn_data) def get_next_turn_key(self, board_id, turn_token): return "next_turn:{}:{}".format(board_id, turn_token) async def set_next_turn(self, board_id, next_turn_data): self.app.logger.info('set_next_turn {} {}'.format( board_id, next_turn_data)) key = self.get_next_turn_key(board_id, next_turn_data['turn_token']) self.redis_pool.set(key, ujson.dumps(next_turn_data)) await self.enqueue_next_turn(key) # if not self._save_turn(next_turn_data): # raise InvalidSaveTurnException() async def enqueue_next_turn(self, key): self.app.logger.info('enqueue_next_turn {}'.format(key)) # self.redis_pool.rpush("next_turn_queue", key) # self.pool.wait_available() # self.pool.spawn(self.process_next_turn, key) await self.process_next_turn(key) def _save_turn(self, data): try: data_json = ujson.dumps(data) self.redis_pool.set("{0}:{1}".format('turn', data['turn_token']), data_json) return True except Exception: return False async def send_gameover(self, board_id): board = self.chess_manager.get_board_by_id(board_id) if self.tournament_manager.get_tournament_key('') in board_id: self.tournament_manager.board_finish(board_id) data = { 'board': board.board.get_simple(), 'white_username': str(board.white_username), 'black_username': str(board.black_username), 'white_score': str(board.white_score), 'black_score': str(board.black_score), 'board_id': board_id, } await self.broadcast('gameover', data, board.white_username) await self.broadcast('gameover', data, board.black_username) async def force_change_turn(self, board_id, turn_token): self.app.logger.info('force_change_turn {} {}'.format( board_id, turn_token)) try: turn_token, username, actual_turn, board, move_left, opponent_username = self.chess_manager.force_change_turn( board_id, turn_token) except GameOverException: await self.send_gameover(board_id) return next_turn_data = { 'board_id': board_id, 'turn_token': turn_token, 'username': username, 'actual_turn': actual_turn, 'board': board, 'move_left': move_left, 'opponent_username': opponent_username, } self.app.logger.info('force_change_turn set_next_turn {} {}'.format( board_id, turn_token)) await self.set_next_turn(board_id, next_turn_data) async def process_next_turn(self, key): self.app.logger.info('process_next_turn {}'.format(key)) try: # key = self.redis_pool.blpop('next_turn_queue') if not key: self.app.logger.info('Nothing pending to process') return data = ujson.loads(self.redis_pool.get(key)) self.app.logger.info('next_turn key: {} data: {}'.format( key, data)) await self.broadcast('your_turn', data, data['username']) # self.notify_to_board_subscribers(data['board_id']) # control timeout await asyncio.sleep(30) self.app.logger.info('Checking timeout {} {}'.format( data['board_id'], data['turn_token'])) if self.redis_pool.exists(key): self.app.logger.info('Forcing timeout {} {}'.format( data['board_id'], data['turn_token'])) self.redis_pool.delete(key) await self.force_change_turn(data['board_id'], data['turn_token']) except Exception as e: tb = traceback.format_exc() self.app.logger.error( 'process_next_turn {} exception {} {}'.format(key, e, tb)) self.app.logger.info('end process_next_turn {}'.format(key)) def notify_to_board_subscribers(self, board_id): board = self.chess_manager.get_board_by_id(board_id) for board_subscriber_client in self.board_subscribers.get( board_id, []): self.notify_board_update(board_subscriber_client, board) async def notify_board_update(self, board_subscriber_client, board): data = { 'board': board.board.get_simple(), 'white_username': board.white_username, 'black_username': board.black_username, 'white_score': board.white_score, 'black_score': board.black_score, } await self.send(board_subscriber_client, 'update_board', data) async def action_subscribe(self, current_username, client, data): board_id = data['board_id'] board = self.chess_manager.get_board_by_id(board_id) if board_id not in self.board_subscribers: self.board_subscribers[board_id] = [] self.board_subscribers[board_id].append(client) self.notify_board_update(client, board) return True async def send(self, client, event, data): """ Send given data to the registered client. Automatically discards invalid connections. """ try: self.app.logger.info( u'send to client: {}, event: {}, data: {}'.format( client, event, data)) message = { 'event': event, 'data': data, } # print 'sent to {0}: {1}'.format(client, message) await client.send(ujson.dumps(message)) except Exception: pass # app.logger.info(u'Exception on sending to client: {}'.format(client)) # self.clients.remove(client) async def action_create_tournament(self, current_username, client, data): tournament = self.tournament_manager.create_tournament() await self.send(client, 'tournament_created', tournament) return True async def action_add_user_to_tournament(self, current_username, client, data): tournament_id = data['tournament_id'] username = data['username'] if username == '*': active_usernames = await self.get_active_users() for username in active_usernames: self.tournament_manager.add_user(tournament_id, username) else: self.tournament_manager.add_user(tournament_id, username) users = self.tournament_manager.get_users(tournament_id) await self.send(client, 'user_added_to_tournament', users) return True async def action_start_tournament(self, current_username, client, data): tournament_id = data['tournament_id'] tournament = self.tournament_manager.get_tournament(tournament_id) # TODO: control and change state... boards = self.tournament_manager.start(tournament_id) for board_id in boards: asyncio.create_task(self._start_board(board_id)) users = self.tournament_manager.get_users(tournament_id) await self.send(client, 'tournament_started', users) return True
class TestChessManager(unittest.TestCase): def setUp(self): super(TestChessManager, self).setUp() self.manager = ChessManager() self.board_id = self.manager.create_board( white_username='******', black_username='******', ) def test_get_invalid_board(self): with self.assertRaises(InvalidBoardIdException): self.manager.get_board_by_id('hola-mundo') def test_create_board(self): board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, WHITE) self.assertEqual(board.white_score, 0) self.assertEqual(board.black_score, 0) def test_invalid_move(self): with self.assertRaises(InvalidTurnException): self.manager.move(self.board_id, 2, 3, 3, 3) board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, WHITE) self.assertEqual(board.white_score, 0) self.assertEqual(board.black_score, 0) def test_move(self): self.manager.move(self.board_id, 12, 3, 11, 3) board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, BLACK) self.assertEqual(board.black_score, 0) self.assertEqual(board.white_score, 10) def test_start_game(self): turn_token = self.manager.challenge('user1', 'user2') self.assertIsNotNone(turn_token) def test_move_with_turn_token(self): board_id = self.manager.challenge('user1', 'user2') first_turn_token, white_username, actual_turn_color, board = self.manager.challenge_accepted(board_id) # initial board turn should be WHITE self.assertEqual(actual_turn_color, WHITE) # move WHITE with token second_turn_token, black_username, actual_turn_color, board = self.manager.move_with_turn_token(first_turn_token, 12, 3, 11, 3) # second board turn should be BLACK self.assertEqual(actual_turn_color, BLACK) # invalid turn token exception with self.assertRaises(InvalidTurnTokenException): self.manager.move_with_turn_token(first_turn_token, 12, 4, 11, 4) # move BLACK with token third_turn_token, white_username, actual_turn_color, board = self.manager.move_with_turn_token(second_turn_token, 3, 3, 4, 3) self.assertIsNotNone(third_turn_token) self.assertEqual(actual_turn_color, WHITE)
def setUp(self): super(TestChessManager, self).setUp() self.manager = ChessManager() self.board_id = self.manager.create_board()
class TestChessManager(unittest.TestCase): def setUp(self): super(TestChessManager, self).setUp() self.manager = ChessManager() self.board_id = self.manager.create_board() def test_get_invalid_board(self): with self.assertRaises(InvalidBoardIdException): self.manager.get_board_by_id('hola-mundo') def test_create_board(self): board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.actual_turn, WHITE) def test_invalid_move(self): with self.assertRaises(InvalidTurnException): self.manager.move(self.board_id, 1, 3, 2, 3) board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.actual_turn, WHITE) def test_move(self): self.manager.move(self.board_id, 6, 3, 5, 3) board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.actual_turn, BLACK) def test_start_game(self): turn_token = self.manager.challenge('user1', 'user2') self.assertIsNotNone(turn_token) def test_move_with_turn_token(self): first_turn_token = self.manager.challenge('user1', 'user2') # initial board turn should be WHITE board_id = self.manager.get_board_id_by_turn_token(first_turn_token) board = self.manager.get_board_by_id(board_id) self.assertEqual(board.actual_turn, WHITE) # move WHITE with token second_turn_token = self.manager.move_with_turn_token(first_turn_token, 6, 3, 5, 3) # second board turn should be BLACK board_id = self.manager.get_board_id_by_turn_token(second_turn_token) board = self.manager.get_board_by_id(board_id) self.assertEqual(board.actual_turn, BLACK) # invalid turn token exception with self.assertRaises(InvalidTurnTokenException): self.manager.move_with_turn_token(first_turn_token, 6, 3, 5, 3) # move BLACK with token third_turn_token = self.manager.move_with_turn_token(second_turn_token, 1, 3, 2, 3) self.assertIsNotNone(third_turn_token) board_id = self.manager.get_board_id_by_turn_token(third_turn_token) board = self.manager.get_board_by_id(board_id) self.assertEqual(board.actual_turn, WHITE)
class TestChessManager(unittest.TestCase): def setUp(self): super(TestChessManager, self).setUp() self.manager = ChessManager(fakeredis.FakeStrictRedis()) self.board_id = self.manager.create_board( white_username='******', black_username='******', ) def test_get_invalid_board(self): with self.assertRaises(InvalidBoardIdException): self.manager.get_board_by_id('hola-mundo') def test_create_board(self): board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, WHITE) self.assertEqual(board.white_score, 0) self.assertEqual(board.black_score, 0) def test_invalid_move(self): with self.assertRaises(InvalidTurnException): self.manager.move(self.board_id, 2, 3, 3, 3) board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, WHITE) self.assertEqual(board.white_score, -1) self.assertEqual(board.black_score, 0) def test_move(self): self.manager.move(self.board_id, 12, 3, 11, 3) board = self.manager.get_board_by_id(self.board_id) self.assertIsNotNone(board) self.assertEqual(board.board.actual_turn, BLACK) self.assertEqual(board.black_score, 0) self.assertEqual(board.white_score, 10) def test_start_game(self): turn_token = self.manager.challenge('user1', 'user2') self.assertIsNotNone(turn_token) def test_move_with_turn_token(self): board_id = self.manager.challenge('user1', 'user2') first_turn_token, white_username, actual_turn_color, board = self.manager.challenge_accepted( board_id) # initial board turn should be WHITE self.assertEqual(actual_turn_color, WHITE) # move WHITE with token second_turn_token, black_username, actual_turn_color, board = self.manager.move_with_turn_token( first_turn_token, 12, 3, 11, 3) # second board turn should be BLACK self.assertEqual(actual_turn_color, BLACK) # invalid turn token exception with self.assertRaises(InvalidTurnTokenException): self.manager.move_with_turn_token(first_turn_token, 12, 4, 11, 4) # move BLACK with token third_turn_token, white_username, actual_turn_color, board = self.manager.move_with_turn_token( second_turn_token, 3, 3, 4, 3) self.assertIsNotNone(third_turn_token) self.assertEqual(actual_turn_color, WHITE)