Beispiel #1
0
 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')
     )
Beispiel #2
0
 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)
Beispiel #3
0
    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)
Beispiel #4
0
    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)
Beispiel #5
0
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()
Beispiel #6
0
 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)
Beispiel #10
0
    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)
Beispiel #11
0
            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)