def test_add_user(self): # assert that the user ID is equivalent across both calls. self.assertEqual( FirestoreDB.add_user(json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, name='user/foo', tiktok='tiktok/bar', phone_number='+10000000000'), FirestoreDB.add_user(json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, name='user/bar', tiktok='tiktok/bar', phone_number='+10000000000') )
def start_game(self, tiktok: Text, phone_number: Text, game_hashtag: Text): game_id = FirestoreDB.add_game( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, hashtag=game_hashtag) self._gamedb = FirestoreDB( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, game_id=game_id) self.join_game(tiktok=tiktok, phone_number=phone_number, game_id=game_id)
def test_generate_tribes_count_initialization( self, algorithm_type, number_of_joined_players, game_options, expected_game_count_tribes, expected_game_count_teams, expected_game_count_players, expected_tribe_count_teams, expected_tribe_count_players, expected_team_count_players): # create a game game_id = FirestoreDB.add_game( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, hashtag='hashtag/foo') _gamedb._game_id = game_id # generate mock players with an id attribute and add the players to the game players = list() for i in range(0, number_of_joined_players): name = 'name/foo' tiktok = 'tiktok/bar' phone_number = f'+1000000000{i}' player = _gamedb.player(name=name, tiktok=tiktok, phone_number=phone_number) FirestoreDB.add_user( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, name=name, tiktok=tiktok, phone_number=phone_number) players.append(player) # read counts for game, all tribes, all teams and verify that they are correct game_info_dict = algorithm_type.generate_tribes( game_id=game_id, players=players, game_options=game_options, gamedb=_gamedb) game = _gamedb.game_from_id(game_id) self.assertEqual(game.count_tribes, expected_game_count_tribes) self.assertEqual(game.count_teams, expected_game_count_teams) self.assertEqual(game.count_players, expected_game_count_players) for tribe in game_info_dict['tribes']: tribe_ref = _gamedb.tribe_from_id(tribe.id) self.assertEqual(tribe_ref.count_teams, expected_tribe_count_teams) self.assertEqual(tribe_ref.count_players, expected_tribe_count_players) for team in game_info_dict['teams']: self.assertEqual( _gamedb.team_from_id(team.id).count_players, expected_team_count_players)
def _play_game(self, game: Game, game_snap: DocumentSnapshot, players: list, game_dict: dict, is_test: bool = False): log_message("Starting a game", game_id=game_dict.get("id"), additional_tags=game_dict) if is_test: database = MockDatabase() engine = MockPlayEngine().CreateEngine(database) else: # NOTE(brandon): the game DB instance used by the matchmaker is for searching over all games. when we create # a game instance, we also supply new game DB and engine objects that have the specific game ID. database = FirestoreDB(json_config_path=json_config_path, game_id=game._game_id) engine = Engine(options=game._options, game_id=game._game_id, sqs_config_path=_TEST_AMAZON_SQS_CONFIG_PATH, twilio_config_path=_TEST_TWILIO_SMS_CONFIG_PATH, gamedb=database) try: game_data = self._matchmaker.generate_tribes( game_id=game._game_id, players=players, game_options=game._options, gamedb=database) tribes = game_data['tribes'] message = messages.NOTIFY_GAME_STARTED_EVENT_MSG_FMT.format( header=messages.game_sms_header( hashtag=game_dict.get('hashtag')), game=game_dict.get('hashtag')) self._notify_players(game_id=game._game_id, players=players, message=message) if self._is_mvp: # NOTE(brandon): changing to thread for now. can't pickle non-primitive engine object. game_thread = threading.Thread(target=game.play, args=(tribes[0], tribes[1], database, engine)) game_thread.start() else: # start on new GCP instance pass except MatchMakerError as e: # Catches error from matchmaker algorithm message = "Matchmaker Error: {}".format(e) log_message(message=message, game_id=game._game_id) self._set_game_has_started(game_snap=game_snap, game=game, value=False) self._notify_players(game_id=game._game_id, players=players, message=message) self._reschedule_or_cancel_game(game_snap=game_snap, game_dict=game_dict, players=players)
class MockFrontend(Frontend): def __init__(self): self._gamedb = None def start_game(self, tiktok: Text, phone_number: Text, game_hashtag: Text): game_id = FirestoreDB.add_game( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, hashtag=game_hashtag) self._gamedb = FirestoreDB( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, game_id=game_id) self.join_game(tiktok=tiktok, phone_number=phone_number, game_id=game_id) def _verify_game_started(self): if not self._gamedb: raise TestError('Must call start_game to create a game_id.') def join_game(self, tiktok: Text, phone_number: Text, game_id: Text) -> None: name = tiktok self._verify_game_started() self._gamedb.player(name=name, tiktok=tiktok, phone_number=phone_number) def submit_entry(self, likes: int, views: int, player_id: Text, tribe_id: Text, challenge_id: Text, team_id: Text, url: Text) -> None: self._verify_game_started() self._gamedb.add_challenge_entry( database.Entry(likes=likes, views=views, player_id=player_id, tribe_id=tribe_id, challenge_id=challenge_id, team_id=team_id, url=url)) def submit_challenge(self, from_player_id: Text, challenge_name: Text, challenge_message: Text) -> None: self._verify_game_started()
def test_matchmaker_daemon_happy(self, use_mock=False, *_): # Test happy path if use_mock: gamedb = MockDatabase() else: gamedb = FirestoreDB(json_config_path=json_config_path) service = MatchmakerService(matchmaker=MatchMakerRoundRobin(), gamedb=gamedb) service.start_matchmaker_daemon(sleep_seconds=1, is_test=True) time.sleep(2) service.set_stop() service.clear_stop()
def test_integration(self): test_id = str(uuid.uuid4()) # Inject a game ID (test uuid) into the gamedb. use a globally unique country code to ensure # that only this game gets scheduled. game_id = FirestoreDB.add_game( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, hashtag=_TEST_GAME_HASHTAG, country_code=f'US-{test_id}') gamedb = FirestoreDB( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, game_id=game_id) log_message(f'Running integration test for game {game_id}') # create an integration test subsystem log stream that specifically captures # game inputs and outputs at the SMS boundary. test_log_stream = GameIntegrationTestLogStream(game_id=game_id, test_id=test_id) # add test challenges for challenge in _TEST_CHALLENGES: gamedb.add_challenge(challenge=challenge) # if non-existent, create users associated with players. NOTE: critical for FE code # to create users when players sign up. for player_info in [*_EMULATED_PLAYERS, *_REAL_PLAYERS]: FirestoreDB.add_user( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, name=player_info[0], tiktok=player_info[1], phone_number=player_info[2]) # Inject players into gamedb. set the phone number of the players to a known, or emulated device. emulated_players = [] for player_info in _EMULATED_PLAYERS: player = gamedb.player(name=player_info[0], tiktok=player_info[1], phone_number=player_info[2]) emulated_players.append( EmulatedPlayer(id=player.id, name=player.name, tiktok=player.tiktok, phone_number=player.phone_number, test_stream=test_log_stream, gamedb=gamedb)) for player_info in _REAL_PLAYERS: player = gamedb.player(name=player_info[0], tiktok=player_info[1], phone_number=player_info[2]) # mock the scheduling method in MM service so that we can force schedule # after initialization. save_check_start_time_fn = MatchmakerService._check_start_time check_start_time_fn = mock.MagicMock() check_start_time_fn.return_value = False MatchmakerService._check_start_time = check_start_time_fn # create device emulator for Twilio SMS events. patch this # into the game engine class for the test. notification_emulator = FakeTwilioSMSNotifier( json_config_path=_TEST_TWILIO_SMS_CONFIG_PATH, game_id=game_id, emulated_devices=emulated_players) build_notifier_fn = mock.MagicMock() build_notifier_fn.return_value = notification_emulator Engine._get_sms_notifier = build_notifier_fn # Instantiate a local matchmaker service. make sure all services have the right configs, # e.g. twilio should be using the prod service phone number so that it can actually send messages. # set the clock mode on (2) to async so that game events happen immediately during testing. service = MatchmakerService( matchmaker=MatchMakerRoundRobin(), region=f'US-{test_id}', gamedb=gamedb, game_options=GameOptions( game_clock_mode=GameClockMode.ASYNC, game_wait_sleep_interval_sec=_TEST_SLEEP_INTERVAL, multi_tribe_min_tribe_size=2, engine_worker_thread_count=5, tribe_council_time_sec=_TEST_SLEEP_INTERVAL)) # mock the MM Twilio client service._get_sms_notifier = mock.MagicMock( return_value=notification_emulator) try: service.start_matchmaker_daemon(sleep_seconds=1) # force schedule the game in MM (1). check_start_time_fn.return_value = True MatchmakerService._check_start_time = check_start_time_fn while gamedb.game_from_id(game_id).count_players > 1: time.sleep(_TEST_SLEEP_INTERVAL) finally: MatchmakerService._check_start_time = save_check_start_time_fn service.set_stop() test_dict = test_log_stream.to_dict() if _SAVE_TEST_LOGS: persisted_test_logs = test_log_stream.persist() log_message(persisted_test_logs) with open(f'game_logs/stream.{test_id}.json', 'w+') as f: f.write(persisted_test_logs) active_players = list() for player in gamedb.stream_players( active_player_predicate_value=True): active_players.append(player) winner = active_players[0] found_winner_message = False for output in test_dict['outputs']: if 'You are the last survivor and WINNER' in output[ 'message'] and output['name'] == winner.name: found_winner_message = True self.assertTrue(found_winner_message)
_TEST_TRIBE_TIGRAWAY_ID = '77TMV9omdLeW7ORvuheX' _TEST_TRIBE_SIDAMA_ID = 'cbTgYdPh97K6rRTDdEPL' _TEST_GAME_ID = '7rPwCJaiSkxYgDocGDw1' _TEST_TEAM_BLUE_ID = 'GQnxhYXnV86oJXLklbGB' _TEST_TEAM_YELLOW_ID = 'Q09FeEtoIgjNI57Bnl1E' _TEST_CHALLENGE_KARAOKE_ID = 'PTifdegtPAtUAgxtNoBK' _TEST_CHALLENGE_KARAOKE_URL = 'https://www.youtube.com/watch?v=irVIUvDTTB0' _TEST_YELLOW_TEAM_ACTIVE_PLAYER_ID = '2ZPmDfX9q82KY5PVf1LH' _TEST_BOSTON_ROB_PLAYER_ID = '2ZPmDfX9q82KY5PVf1LH' _TEST_COLLECTION_PATHS = [ 'games', 'games/7rPwCJaiSkxYgDocGDw1/players', 'games/7rPwCJaiSkxYgDocGDw1/teams', 'games/7rPwCJaiSkxYgDocGDw1/tribes', 'games/7rPwCJaiSkxYgDocGDw1/votes' ] _gamedb = FirestoreDB(json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, game_id=_TEST_GAME_ID) _TEST_DATA_JSON = """ { "games":{ "7rPwCJaiSkxYgDocGDw1":{ "count_teams":6, "count_players":2, "name":"test_game1" } }, "games/7rPwCJaiSkxYgDocGDw1/players":{ "2ZPmDfX9q82KY5PVf1LH":{ "team_id":"Q09FeEtoIgjNI57Bnl1E", "active":true, "name":"Boston Rob",
"id":"59939070-34e1-4bfd-9a92-5415e2fb4277", "class":"Tribe", "name":"tribe/MANESSEH" }, "c4630003-d361-4ff7-a03f-ab6f92afbb81":{ "id":"c4630003-d361-4ff7-a03f-ab6f92afbb81", "class":"Tribe", "name":"tribe/ASHER", "count_players":10, "active":true } } } """ _gamedb = FirestoreDB( json_config_path=_TEST_FIRESTORE_INSTANCE_JSON_PATH, game_id=_TEST_GAME_ID) _gamedb.import_collections(collections_json=_TEST_GAME_ENVIRONMENT) def _event_messages_as_json(messages): return json.dumps([m.to_dict() for m in messages]) class SMSMessageUXTest(unittest.TestCase): def setUp(self): self.game_options = GameOptions() self.game_options.game_schedule = STV_I18N_TABLE['US'] self.twilio = TwilioSMSNotifier( json_config_path=_TEST_TWILIO_SMS_CONFIG_PATH)
def test_check_start_time(self, *_): gamedb = FirestoreDB(json_config_path=json_config_path) service = MatchmakerService(matchmaker=MatchMakerRoundRobin(), gamedb=gamedb, min_players=9000) mock_schedule = GameSchedule( country='United States', country_code='US', game_time_zone=pytz.timezone('America/New_York'), game_start_day_of_week=ISODayOfWeek.Friday, game_start_time=datetime.time(hour=12), daily_challenge_start_time=datetime.time(hour=12), daily_challenge_end_time=datetime.time(hour=18), daily_tribal_council_start_time=datetime.time(hour=19), daily_tribal_council_end_time=datetime.time(hour=21), ) # Case 1: before start_time mock_time = datetime.datetime(year=2020, month=7, day=10, hour=mock_schedule.game_start_time.hour - 1) result = service._check_start_time( schedule=mock_schedule, now_dt_with_tz=mock_schedule.game_time_zone.localize(mock_time)) self.assertEqual(result, False) # Case 1.5: after start_time, but on a previous day mock_time = datetime.datetime(year=2020, month=7, day=9, hour=mock_schedule.game_start_time.hour) result = service._check_start_time( schedule=mock_schedule, now_dt_with_tz=mock_schedule.game_time_zone.localize(mock_time)) self.assertEqual(result, False) # Case 2: On same day as start_day and after start_time mock_time = datetime.datetime(year=2020, month=7, day=10, hour=mock_schedule.game_start_time.hour + 5) result = service._check_start_time( schedule=mock_schedule, now_dt_with_tz=mock_schedule.game_time_zone.localize(mock_time)) self.assertEqual(result, True) # Case 2.5: On same day as start_day and after start_time, but in different timezone mock_time = datetime.datetime(year=2020, month=7, day=10, hour=mock_schedule.game_start_time.hour + 5) result = service._check_start_time( schedule=mock_schedule, now_dt_with_tz=pytz.timezone("Asia/Tokyo").localize(mock_time)) self.assertEqual(result, False) # Case 3: Day following start_day and after start_time mock_time = datetime.datetime(year=2020, month=7, day=11, hour=mock_schedule.game_start_time.hour) result = service._check_start_time( schedule=mock_schedule, now_dt_with_tz=mock_schedule.game_time_zone.localize(mock_time)) self.assertEqual(result, False) mock_time = datetime.datetime(year=2020, month=7, day=13, hour=mock_schedule.game_start_time.hour) result = service._check_start_time( schedule=mock_schedule, now_dt_with_tz=mock_schedule.game_time_zone.localize(mock_time)) self.assertEqual(result, False) # Case 4: 1 Week after, on the right time/date mock_time = datetime.datetime(year=2020, month=7, day=17, hour=mock_schedule.game_start_time.hour) result = service._check_start_time( schedule=mock_schedule, now_dt_with_tz=mock_schedule.game_time_zone.localize(mock_time)) self.assertEqual(result, True)
for rank in range(num_scores): score, player_id = heapq.heappop(top_scores) log_message("Player {} rank {} with score {}.".format( player_id, rank, score)) # all but the highest scorer lose if rank < (num_scores - 1): losing_players.append(gamedb.player_from_id(player_id)) return losing_players def _merge_tribes(self, tribe1: Tribe, tribe2: Tribe, new_tribe_name: Text, gamedb: Database) -> Tribe: new_tribe = gamedb.tribe(name=new_tribe_name) gamedb.batch_update_tribe(from_tribe=tribe1, to_tribe=new_tribe) gamedb.batch_update_tribe(from_tribe=tribe2, to_tribe=new_tribe) return new_tribe if __name__ == '__main__': options = GameOptions() game = Game(game_id=str(uuid.uuid4), options=options) # TODO(brandon) for production each game should instantiate a separate AWS FIFO # on the fly. engine = Engine(options=options, sqs_config_path=_AMAZON_SQS_PROD_CONF_JSON_PATH) database = FirestoreDB(json_config_path=_FIRESTORE_PROD_CONF_JSON_PATH) game.play(tribe1=database.tribe_from_id(_TRIBE_1_ID), tribe2=database.tribe_from_id(_TRIBE_2_ID), gamedb=database, engine=engine)