Exemplo n.º 1
0
    def test_find_and_respond_to_twitter_events(self, twitter_event_mock):
        db_id = 1
        random_id = random.randint(10000000000000000000, 20000000000000000000)
        event_type = 'status'
        user_handle = 'bdhowald'
        user_id = random.randint(1000000000, 2000000000)
        event_text = '@HowsMyDrivingNY abc1234:ny'
        timestamp = random.randint(1500000000000, 1700000000000)
        in_reply_to_message_id = random.randint(
            10000000000000000000, 20000000000000000000)
        location = 'Queens, NY'
        responded_to = 0
        user_mentions = '@HowsMyDrivingNY'

        twitter_event = TwitterEvent(
            id=db_id,
            created_at=timestamp,
            event_id=random_id,
            event_text=event_text,
            event_type=event_type,
            in_reply_to_message_id=in_reply_to_message_id,
            location=location,
            responded_to=responded_to,
            user_handle=user_handle,
            user_id=user_id,
            user_mentions=user_mentions)

        lookup_request = AccountActivityAPIDirectMessage(
            message=TwitterEvent(
                id=1,
                created_at=timestamp,
                event_id=random_id,
                event_text='@howsmydrivingny ny:hme6483',
                event_type='direct_message',
                user_handle=user_handle,
                user_id=30139847),
            message_source='api')

        twitter_event_mock.get_all_by.return_value = [twitter_event]
        twitter_event_mock.query.filter_by().filter().count.return_value = 0

        initiate_reply_mock = MagicMock(name='initiate_reply')
        self.tweeter.aggregator.initiate_reply = initiate_reply_mock

        build_reply_data_mock = MagicMock(name='build_reply_data')
        build_reply_data_mock.return_value = lookup_request
        self.tweeter.reply_argument_builder.build_reply_data = build_reply_data_mock

        self.tweeter._find_and_respond_to_twitter_events()

        self.tweeter.aggregator.initiate_reply.assert_called_with(
            lookup_request=lookup_request)
    def test_process_response_direct_message(self, mocked_is_production):
        """ Test direct message and new format """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)

        lookup_request = AccountActivityAPIDirectMessage(
            message=TwitterEvent(
                id=1,
                created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
                event_id=message_id,
                event_text='@howsmydrivingny ny:hme6483',
                event_type='direct_message',
                user_handle=username,
                user_id=30139847),
            message_source='direct_message')

        combined_message = "@bdhowald #NY_HME6483 has been queried 1 time.\n\nTotal parking and camera violation tickets: 15\n\n4 | No Standing - Day/Time Limits\n3 | No Parking - Street Cleaning\n1 | Failure To Display Meter Receipt\n1 | No Violation Description Available\n1 | Bus Lane Violation\n\n@bdhowald Parking and camera violation tickets for #NY_HME6483, cont'd:\n\n1 | Failure To Stop At Red Light\n1 | No Standing - Commercial Meter Zone\n1 | Expired Meter\n1 | Double Parking\n1 | No Angle Parking\n\n@bdhowald Violations by year for #NY_HME6483:\n\n10 | 2017\n15 | 2018\n\n@bdhowald Known fines for #NY_HME6483:\n\n$200.00 | Fined\n$125.00 | Outstanding\n$75.00   | Paid\n"

        mocked_is_production.return_value = True

        send_direct_message_mock = MagicMock('send_direct_message_mock')

        application_api_mock = MagicMock(name='application_api')
        application_api_mock.send_direct_message = send_direct_message_mock
        self.tweeter._app_api = application_api_mock

        self.tweeter._process_response(
            request_object=lookup_request,
            response_parts=[[combined_message]],
            successful_lookup=True)

        send_direct_message_mock.assert_called_with(
          recipient_id=30139847,
          text=combined_message)
Exemplo n.º 3
0
    def _add_twitter_events_for_missed_statuses(self, messages: List[tweepy.Status]):
        """Creates TwitterEvent objects when the Account Activity API fails to send us
        status events via webhooks to have them created by the HowsMyDrivingNY API.

        :param messages: List[tweepy.Status]: The statuses returned via the Twitter
                                              Search API via Tweepy.
        """

        undetected_messages = 0

        for message in messages:
            existing_event: Optional[TwitterEvent] = TwitterEvent.query.filter(TwitterEvent.event_id == message.id).first()

            if not existing_event and message.user.id != HMDNY_TWITTER_USER_ID:
                undetected_messages += 1

                event: TwitterEvent = TwitterEvent(
                    event_type=TwitterMessageType.STATUS.value,
                    event_id=message.id,
                    user_handle=message.user.screen_name,
                    user_id=message.user.id,
                    event_text=message.full_text,
                    created_at=message.created_at.replace(tzinfo=pytz.timezone('UTC')).timestamp() * MILLISECONDS_PER_SECOND,
                    in_reply_to_message_id=message.in_reply_to_status_id,
                    location=message.place and message.place.full_name,
                    user_mentions=' '.join([user['screen_name'] for user in message.entities['user_mentions']]),
                    detected_via_account_activity_api=False)

                TwitterEvent.query.session.add(event)

        TwitterEvent.query.session.commit()

        LOG.debug(
            f"Found {undetected_messages} status{'' if undetected_messages == 1 else 'es'} that "
            f"{'was' if undetected_messages == 1 else 'were'} previously undetected.")
    def test_process_response_campaign_only_lookup(self,
                                                   recursively_process_status_updates_mock):
        """ Test campaign-only lookup """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)
        campaign_hashtag = '#SaferSkillman'
        campaign_tickets = random.randint(1000, 2000)
        campaign_vehicles = random.randint(100, 200)

        response_parts = [[(f"@{username} {'{:,}'.format(campaign_vehicles)} vehicles with a total of "
            f"{'{:,}'.format(campaign_tickets)} tickets have been tagged with {campaign_hashtag}.\n\n")]]

        lookup_request = AccountActivityAPIStatus(
            message=TwitterEvent(
                id=1,
                created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
                event_id=message_id,
                event_text=f'@howsmydrivingny {campaign_hashtag}',
                event_type='status',
                user_handle=username,
                user_id=30139847,
                user_mention_ids='813286,19834403,1230933768342528001,37687633,66379182',
                user_mentions='@BarackObama @NYCMayor @GoodNYCMayor @NYC_DOT @NYCTSubway'
            ),
            message_source='status')

        reply_event_args = {
            'error_on_lookup': False,
            'request_object': lookup_request,
            'response_parts': response_parts,
            'success': True,
            'successful_lookup': True,
            'username': username
        }

        reply_event_args['username'] = username

        self.tweeter._process_response(
            request_object=lookup_request,
            response_parts=response_parts,
            successful_lookup=True)

        recursively_process_status_updates_mock.assert_called_with(
            response_parts=response_parts,
            message_id=message_id,
            user_mention_ids=[
                '813286',
                '19834403',
                '1230933768342528001',
                '37687633',
                '66379182'
            ])
Exemplo n.º 5
0
    def test_process_response_status_legacy_format(
            self, recursively_process_status_updates_mock):
        """ Test status and old format """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)

        response_parts = [[
            '@BarackObama #PA_GLF7467 has been queried 1 time.\n\nTotal parking and camera violation tickets: 49\n\n17 | No Parking - Street Cleaning\n6   | Expired Meter\n5   | No Violation Description Available\n3   | Fire Hydrant\n3   | No Parking - Day/Time Limits\n',
            "@BarackObama Parking and camera violation tickets for #PA_GLF7467, cont'd:\n\n3   | Failure To Display Meter Receipt\n3   | School Zone Speed Camera Violation\n2   | No Parking - Except Authorized Vehicles\n2   | Bus Lane Violation\n1   | Failure To Stop At Red Light\n",
            "@BarackObama Parking and camera violation tickets for #PA_GLF7467, cont'd:\n\n1   | No Standing - Day/Time Limits\n1   | No Standing - Except Authorized Vehicle\n1   | Obstructing Traffic Or Intersection\n1   | Double Parking\n",
            '@BarackObama Known fines for #PA_GLF7467:\n\n$1,000.00 | Fined\n$225.00     | Outstanding\n$775.00     | Paid\n'
        ]]

        lookup_request = AccountActivityAPIStatus(message=TwitterEvent(
            id=1,
            created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
            event_id=message_id,
            event_text='@howsmydrivingny plate:glf7467 state:pa',
            event_type='status',
            user_handle=username,
            user_id=30139847),
                                                  message_source='status')

        reply_event_args = {
            'error_on_lookup': False,
            'request_object': lookup_request,
            'response_parts': response_parts,
            'success': True,
            'successful_lookup': True,
            'username': username
        }

        is_production_mock = MagicMock(name='is_production')
        is_production_mock.return_value = True

        create_favorite_mock = MagicMock(name='is_production')
        create_favorite_mock.return_value = True

        application_api_mock = MagicMock(name='application_api')
        application_api_mock.create_favorite = create_favorite_mock

        self.tweeter._is_production = is_production_mock
        self.tweeter._app_api = application_api_mock

        reply_event_args['username'] = username

        self.tweeter._process_response(request_object=lookup_request,
                                       response_parts=response_parts,
                                       successful_lookup=True)

        recursively_process_status_updates_mock.assert_called_with(
            response_parts=response_parts, message_id=message_id)
    def test_process_response_with_error(self,
                                         recursively_process_status_updates_mock):
        """ Test error handling """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)

        lookup_request = AccountActivityAPIStatus(
            message=TwitterEvent(
                id=1,
                created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
                event_id=message_id,
                event_text='@howsmydrivingny plate:glf7467 state:pa',
                event_type='status',
                user_handle=username,
                user_id=30139847,
                user_mention_ids='813286,19834403,1230933768342528001,37687633,66379182',
                user_mentions='@BarackObama @NYCMayor @GoodNYCMayor @NYC_DOT @NYCTSubway'
            ),
            message_source='status'
        )

        response_parts = [
            ['@' + username + " Sorry, I encountered an error. Tagging @bdhowald."]]

        reply_event_args = {
            'error_on_lookup': False,
            'request_object': lookup_request,
            'response_parts': response_parts,
            'success': True,
            'successful_lookup': True,
            'username': username
        }

        reply_event_args['username'] = username

        self.tweeter._process_response(
            request_object=lookup_request,
            response_parts=response_parts,
            successful_lookup=True)

        recursively_process_status_updates_mock.assert_called_with(
            response_parts=response_parts,
            message_id=message_id,
            user_mention_ids=[
                '813286',
                '19834403',
                '1230933768342528001',
                '37687633',
                '66379182'
            ])
    def test_process_response_with_direct_message_api_direct_message(self,
                                                                     recursively_process_status_updates_mock):
        """ Test plateless lookup """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)

        response_parts = [
            ['@' + username + " I think you're trying to look up a plate, but can't be sure.\n\nJust a reminder, the format is <state|province|territory>:<plate>, e.g. NY:abc1234"]]

        lookup_request = AccountActivityAPIStatus(
            message=TwitterEvent(
                id=1,
                created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
                event_id=message_id,
                event_text='@howsmydrivingny the state is ny',
                event_type='status',
                user_handle=username,
                user_id=30139847,
                user_mention_ids='813286,19834403,1230933768342528001,37687633,66379182',
                user_mentions='@BarackObama @NYCMayor @GoodNYCMayor @NYC_DOT @NYCTSubway'
            ),
            message_source='status')

        reply_event_args = {
            'error_on_lookup': False,
            'request_object': lookup_request,
            'response_parts': response_parts,
            'success': True,
            'successful_lookup': True,
            'username': username
        }

        reply_event_args['username'] = username

        self.tweeter._process_response(
            request_object=lookup_request,
            response_parts=response_parts,
            successful_lookup=True)

        recursively_process_status_updates_mock.assert_called_with(
            response_parts=response_parts,
            message_id=message_id,
            user_mention_ids=[
                '813286',
                '19834403',
                '1230933768342528001',
                '37687633',
                '66379182'
            ])
Exemplo n.º 8
0
    def test_build_account_activity_api_status(self):
        account_activity_api_status_twitter_event = TwitterEvent(
            id=1,
            created_at=random.randint(self.CREATED_AT_MIN,
                                      self.CREATED_AT_MAX),
            event_id=random.randint(self.EVENT_ID_MIN, self.EVENT_ID_MAX),
            event_text='hi there',
            event_type='status',
            user_handle=self.OTHER_TWITTER_HANDLE,
            user_id=random.randint(self.USER_ID_MIN, self.USER_ID_MAX),
            user_mentions=self.HMDNY_TWITTER_HANDLE)

        req = self.reply_argument_builder.build_reply_data(
            account_activity_api_status_twitter_event, LookupSource.STATUS)

        self.assertIsInstance(req, AccountActivityAPIStatus)
Exemplo n.º 9
0
    def _add_twitter_events_for_missed_direct_messages(self, messages: list[tweepy.models.Status]) -> None:
        """Creates TwitterEvent objects when the Account Activity API fails to send us
        direct message events via webhooks to have them created by the HowsMyDrivingNY API.

        :param messages: list[tweepy.DirectMessage]: The direct messages returned via the
                                                     Twitter Search API via Tweepy.
        """

        undetected_messages = 0

        sender_ids = set(int(message.message_create['sender_id']) for message in messages)

        sender_objects = self._get_twitter_client_api().lookup_users(user_id=sender_ids)
        senders = {sender.id_str:sender for sender in sender_objects}

        for message in messages:

            existing_event: Optional[TwitterEvent] = TwitterEvent.query.filter(TwitterEvent.event_id == message.id).first()

            if not existing_event and int(message.message_create['sender_id']) != HMDNY_TWITTER_USER_ID:
                undetected_messages += 1

                sender = senders[message.message_create['sender_id']]

                event: TwitterEvent = TwitterEvent(
                    event_type=TwitterMessageType.DIRECT_MESSAGE.value,
                    event_id=message.id,
                    user_handle=sender.screen_name,
                    user_id=sender.id,
                    event_text=message.message_create['message_data']['text'],
                    created_at=message.created_timestamp,
                    in_reply_to_message_id=None,
                    location=None,
                    user_mention_ids=','.join([user['id_str'] for user in message.message_create['message_data']['entities']['user_mentions']]),
                    user_mentions=' '.join([user['screen_name'] for user in message.message_create['message_data']['entities']['user_mentions']]),
                    detected_via_account_activity_api=False)

                TwitterEvent.query.session.add(event)

        TwitterEvent.query.session.commit()

        LOG.debug(
            f"Found {undetected_messages} direct message{'' if undetected_messages == 1 else 's'} that "
            f"{'was' if undetected_messages == 1 else 'were'} previously undetected.")
Exemplo n.º 10
0
    def test_process_response_with_search_status(
            self, recursively_process_status_updates_mock):
        """ Test plateless lookup """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)

        response_parts = [[
            '@' + username +
            ' I’d be happy to look that up for you!\n\nJust a reminder, the format is <state|province|territory>:<plate>, e.g. NY:abc1234'
        ]]

        lookup_request = AccountActivityAPIStatus(message=TwitterEvent(
            id=1,
            created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
            event_id=message_id,
            event_text='@howsmydrivingny plate dkr9364 state ny',
            event_type='status',
            user_handle=username,
            user_id=30139847),
                                                  message_source='status')

        reply_event_args = {
            'error_on_lookup': False,
            'request_object': lookup_request,
            'response_parts': response_parts,
            'success': True,
            'successful_lookup': True,
            'username': username
        }

        reply_event_args['username'] = username

        self.tweeter._process_response(request_object=lookup_request,
                                       response_parts=response_parts,
                                       successful_lookup=True)

        recursively_process_status_updates_mock.assert_called_with(
            response_parts=response_parts, message_id=message_id)
Exemplo n.º 11
0
    def test_process_response_with_error(
            self, recursively_process_status_updates_mock):
        """ Test error handling """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)

        lookup_request = AccountActivityAPIStatus(message=TwitterEvent(
            id=1,
            created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
            event_id=message_id,
            event_text='@howsmydrivingny plate:glf7467 state:pa',
            event_type='status',
            user_handle=username,
            user_id=30139847),
                                                  message_source='status')

        response_parts = [[
            '@' + username +
            " Sorry, I encountered an error. Tagging @bdhowald."
        ]]

        reply_event_args = {
            'error_on_lookup': False,
            'request_object': lookup_request,
            'response_parts': response_parts,
            'success': True,
            'successful_lookup': True,
            'username': username
        }

        reply_event_args['username'] = username

        self.tweeter._process_response(request_object=lookup_request,
                                       response_parts=response_parts,
                                       successful_lookup=True)

        recursively_process_status_updates_mock.assert_called_with(
            response_parts=response_parts, message_id=message_id)
Exemplo n.º 12
0
    def test_process_response_campaign_only_lookup(self,
                                                   recursively_process_status_updates_mock):
        """ Test campaign-only lookup """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)
        campaign_hashtag = '#SaferSkillman'
        campaign_tickets = random.randint(1000, 2000)
        campaign_vehicles = random.randint(100, 200)

        response_parts = [[(f"@{username} {'{:,}'.format(campaign_vehicles)} vehicles with a total of "
            f"{'{:,}'.format(campaign_tickets)} tickets have been tagged with {campaign_hashtag}.\n\n")]]

        lookup_request = AccountActivityAPIStatus(
            message=TwitterEvent(
                id=1,
                created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
                event_id=message_id,
                event_text=f'@howsmydrivingny {campaign_hashtag}',
                event_type='status',
                user_handle=username,
                user_id=30139847),
            message_source='status')

        reply_event_args = {
            'error_on_lookup': False,
            'request_object': lookup_request,
            'response_parts': response_parts,
            'success': True,
            'successful_lookup': True,
            'username': username
        }

        reply_event_args['username'] = username

        self.tweeter._process_response(reply_event_args)

        recursively_process_status_updates_mock.assert_called_with(
            response_parts, message_id, username)
Exemplo n.º 13
0
    def test_process_response_with_direct_message_api_direct_message(self,
                                                                     recursively_process_status_updates_mock):
        """ Test plateless lookup """

        username = '******'
        message_id = random.randint(1000000000000000000, 2000000000000000000)

        response_parts = [
            ['@' + username + " I think you're trying to look up a plate, but can't be sure.\n\nJust a reminder, the format is <state|province|territory>:<plate>, e.g. NY:abc1234"]]

        lookup_request = AccountActivityAPIStatus(
            message=TwitterEvent(
                id=1,
                created_at=random.randint(1_000_000_000_000, 2_000_000_000_000),
                event_id=message_id,
                event_text='@howsmydrivingny the state is ny',
                event_type='status',
                user_handle=username,
                user_id=30139847),
            message_source='status')

        reply_event_args = {
            'error_on_lookup': False,
            'request_object': lookup_request,
            'response_parts': response_parts,
            'success': True,
            'successful_lookup': True,
            'username': username
        }

        reply_event_args['username'] = username

        self.tweeter._process_response(reply_event_args)

        recursively_process_status_updates_mock.assert_called_with(
            response_parts, message_id, username)
    def test_find_and_respond_to_missed_direct_messages(self, twitter_event_mock):
        db_id = 1
        event_id = random.randint(10000000000000000000, 20000000000000000000)
        event_text = 'abc1234:ny'
        event_type = 'direct_message'
        timestamp = random.randint(1500000000000, 1700000000000)
        user_handle = 'bdhowald'
        user_id = random.randint(1000000000, 2000000000)

        new_twitter_event = TwitterEvent(
            created_at=timestamp + 1,
            detected_via_account_activity_api=False,
            event_id=event_id + 1,
            event_text=event_text + '!',
            event_type=event_type,
            in_reply_to_message_id=None,
            location=None,
            responded_to=False,
            user_handle=user_handle,
            user_id=user_id,
            user_mentions=[])

        message_needing_event = MagicMock(
            id=event_id + 1,
            created_timestamp = timestamp + 1,
            message_create={
              'message_data': {
                'entities': {
                  'user_mentions': [
                    {
                      'id': 123,
                      'id_str': '123',
                      'screen_name': user_handle
                    },
                    {
                      'id': 456,
                      'id_str': '456',
                      'screen_name': 'OtherUser'
                    },
                    {
                      'id': 789,
                      'id_str': '456',
                      'screen_name': 'SomeOtherUser'
                    }
                  ]
                },
                'text': event_text + '!'
              },
              'sender_id': f'{user_id}'
            },
            name='message_needing_event')

        message_not_needing_event = MagicMock(
            id=event_id - 1,
            created_timestamp = timestamp - 1,
            message_create={
                'message_data': {
                    'entities': {
                        'user_mentions': [
                            {
                                'id': 123,
                                'id_str': '123',
                                'screen_name': user_handle
                            },
                            {
                                'id': 456,
                                'id_str': '456',
                                'screen_name': 'OtherUser'
                            },
                            {
                                'id': 789,
                                'id_str': '456',
                                'screen_name': 'SomeOtherUser'
                            }
                        ]
                    },
                  'text': event_text,
                },
                'sender_id': f'{user_id}'
            },
            name='message_not_needing_event')

        sender = MagicMock(
            id=user_id,
            id_str=f'{user_id}',
            name='sender',
            screen_name=user_handle)

        twitter_event_mock.return_value = new_twitter_event
        twitter_event_mock.query.filter.return_value.first.side_effect = [
            None, message_not_needing_event]

        client_api_mock = MagicMock(name='client_api')
        client_api_mock.get_direct_messages.return_value = [
          message_needing_event, message_not_needing_event]
        client_api_mock.lookup_users.return_value = [sender]
        self.tweeter._client_api = client_api_mock

        self.tweeter._find_and_respond_to_missed_direct_messages()

        twitter_event_mock.query.session.add.assert_called_once_with(new_twitter_event)
        twitter_event_mock.query.session.commit.assert_called_once_with()

        self.mocked_log.debug.assert_called_with('Found 1 direct message that was previously undetected.')

        self.tweeter.terminate_lookups()
    def test_find_and_respond_to_failed_twitter_events(self,
                                                       twitter_event_mock: MagicMock,
                                                       event_type: str,
                                                       expect_called: bool,
                                                       num_times_failed: int,
                                                       last_failed_time: timedelta = timedelta(minutes=0),
                                                       tweet_exists: bool = True):
        db_id = 1
        random_id = random.randint(1000000000000000000, 2000000000000000000)
        user_handle = 'bdhowald'
        user_id = random.randint(1000000000, 2000000000)
        event_text = '@HowsMyDrivingNY abc1234:ny'
        timestamp = random.randint(1500000000000, 1700000000000)
        in_reply_to_message_id = random.randint(
            10000000000000000000, 20000000000000000000)
        location = 'Queens, NY'
        responded_to = 0
        user_mentions = [
            {
                'id': 123,
                'id_str': '123',
                'screen_name': 'HowsMyDrivingNY'
            },
            {
                'id': 456,
                'id_str': '456',
                'screen_name': 'OtherUser'
            },
            {
                'id': 789,
                'id_str': '456',
                'screen_name': 'SomeOtherUser'
            }
        ]


        twitter_event = TwitterEvent(
            id=db_id,
            created_at=timestamp,
            event_id=random_id,
            event_text=event_text,
            event_type=event_type,
            in_reply_to_message_id=in_reply_to_message_id,
            last_failed_at_time=(datetime.utcnow() - last_failed_time),
            location=location,
            num_times_failed=num_times_failed,
            responded_to=responded_to,
            user_handle=user_handle,
            user_id=user_id,
            user_mentions=user_mentions)

        direct_message_lookup_request = AccountActivityAPIDirectMessage(
            message=TwitterEvent(
                id=1,
                created_at=timestamp,
                event_id=random_id,
                event_text=event_text,
                event_type=event_type,
                user_handle=user_handle,
                user_id=user_id,
                user_favorited_non_follower_reply=False),
            message_source=event_type)

        status_lookup_request = AccountActivityAPIStatus(
            message=TwitterEvent(
                id=1,
                created_at=timestamp,
                event_id=random_id,
                event_text=event_text,
                event_type=event_type,
                user_handle=user_handle,
                user_id=user_id,
                user_favorited_non_follower_reply=False),
            message_source=event_type)

        lookup_request = (direct_message_lookup_request if
            event_type == 'direct_message' else status_lookup_request)

        twitter_event_mock.get_all_by.side_effect = [[], [twitter_event]]
        twitter_event_mock.query.filter_by().filter().count.return_value = 0

        tweet_exists_mock = MagicMock(name='tweet_exists')
        tweet_exists_mock.return_value = True if tweet_exists else False
        self.tweeter.tweet_detection_service.tweet_exists = tweet_exists_mock

        initiate_reply_mock = MagicMock(name='initiate_reply')
        self.tweeter.aggregator.initiate_reply = initiate_reply_mock

        build_reply_data_mock = MagicMock(name='build_reply_data')
        build_reply_data_mock.return_value = lookup_request
        self.tweeter.reply_argument_builder.build_reply_data = build_reply_data_mock

        application_api_mock = MagicMock(name='application_api')
        application_api_mock.get_follower_ids.return_value = ([user_id], (123, 0))
        self.tweeter._app_api = application_api_mock

        process_response_mock = MagicMock(name='process_response')
        process_response_mock.return_value = random_id
        self.tweeter._process_response = process_response_mock

        self.tweeter._find_and_respond_to_twitter_events()

        if expect_called:
            self.tweeter.aggregator.initiate_reply.assert_called_with(
                lookup_request=lookup_request)
        else:
            self.tweeter.aggregator.initiate_reply.assert_not_called()

        self.tweeter.terminate_lookups()
    def test_find_and_respond_to_twitter_events(self,
                                                twitter_event_mock: MagicMock,
                                                event_type: str,
                                                is_follower: bool,
                                                response_parts: Optional[list[str]] = None):
        db_id = 1
        random_id = random.randint(1000000000000000000, 2000000000000000000)
        user_handle = 'bdhowald'
        user_id = random.randint(1000000000, 2000000000)
        event_text = '@HowsMyDrivingNY abc1234:ny'
        timestamp = random.randint(1500000000000, 1700000000000)
        in_reply_to_message_id = random.randint(
            10000000000000000000, 20000000000000000000)
        location = 'Queens, NY'
        responded_to = 0
        user_mentions = [
            {
                'id': 123,
                'id_str': '123',
                'screen_name': 'HowsMyDrivingNY'
            },
            {
                'id': 456,
                'id_str': '456',
                'screen_name': 'OtherUser'
            },
            {
                'id': 789,
                'id_str': '456',
                'screen_name': 'SomeOtherUser'
            }
        ]

        twitter_event = TwitterEvent(
            id=db_id,
            created_at=timestamp,
            event_id=random_id,
            event_text=event_text,
            event_type=event_type,
            in_reply_to_message_id=in_reply_to_message_id,
            last_failed_at_time=None,
            location=location,
            num_times_failed=0,
            responded_to=responded_to,
            user_handle=user_handle,
            user_id=user_id,
            user_mentions=user_mentions)

        lookup_request = AccountActivityAPIDirectMessage(
            message=TwitterEvent(
                id=1,
                created_at=timestamp,
                event_id=random_id,
                event_text=event_text,
                event_type=event_type,
                user_handle=user_handle,
                user_id=user_id,
                user_favorited_non_follower_reply=False),
            message_source=event_type)

        twitter_event_mock.get_all_by.return_value = [twitter_event]
        twitter_event_mock.query.filter_by().filter().count.return_value = 0

        initiate_reply_mock = MagicMock(name='initiate_reply')
        self.tweeter.aggregator.initiate_reply = initiate_reply_mock

        build_reply_data_mock = MagicMock(name='build_reply_data')
        build_reply_data_mock.return_value = lookup_request
        self.tweeter.reply_argument_builder.build_reply_data = build_reply_data_mock

        application_api_mock = MagicMock(name='application_api')
        application_api_mock.get_follower_ids.return_value = ([
            user_id if is_follower else (user_id + 1)], (123, 0))
        self.tweeter._app_api = application_api_mock

        process_response_mock = MagicMock(name='process_response')
        process_response_mock.return_value = random_id
        self.tweeter._process_response = process_response_mock

        self.tweeter._find_and_respond_to_twitter_events()

        if is_follower:
            self.tweeter.aggregator.initiate_reply.assert_called_with(
                lookup_request=lookup_request)
        else:
            self.tweeter.aggregator.initiate_reply.assert_not_called()
            self.tweeter._process_response.assert_called_with(
                request_object=lookup_request,
                response_parts=response_parts)

        self.tweeter.terminate_lookups()
    def test_find_and_respond_to_missed_statuses(self, twitter_event_mock):
        db_id = 1
        event_id = random.randint(10000000000000000000, 20000000000000000000)
        event_text = '@HowsMyDrivingNY @bdhowald abc1234:ny'
        event_type = 'status'
        in_reply_to_message_id = random.randint(
            10000000000000000000, 20000000000000000000)
        location = 'Queens, NY'
        now = datetime.utcnow()
        place = MagicMock(
            full_name=location,
            name='place')

        user_id = random.randint(1000000000, 2000000000)
        user = MagicMock(
            id=user_id,
            name='user')
        user_handle = 'bdhowald'

        older_twitter_event = TwitterEvent(
            id=db_id,
            created_at=now.replace(tzinfo=pytz.timezone('UTC')).timestamp() * 1000,
            detected_via_account_activity_api=False,
            event_id=event_id,
            event_text=event_text,
            event_type=event_type,
            in_reply_to_message_id=in_reply_to_message_id,
            location=location,
            responded_to=True,
            user_handle=user_handle,
            user_id=user_id,
            user_mention_ids=[user_id],
            user_mentions=[
                {
                    'id': 123,
                    'id_str': '123',
                    'screen_name': user_handle
                },
                {
                    'id': 456,
                    'id_str': '456',
                    'screen_name': 'OtherUser'
                },
                {
                    'id': 789,
                    'id_str': '456',
                    'screen_name': 'SomeOtherUser'
                }
            ])

        new_twitter_event = TwitterEvent(
            created_at=now.replace(tzinfo=pytz.timezone('UTC')).timestamp() * 1000,
            detected_via_account_activity_api=False,
            event_id=event_id + 1,
            event_text=event_text + '!',
            event_type=event_type,
            in_reply_to_message_id=in_reply_to_message_id,
            location=location,
            responded_to=False,
            user_handle=user_handle,
            user_id=user_id,
            user_mention_ids=[user_id],
            user_mentions=user_handle)

        status_needing_event = MagicMock(
            id=event_id + 1,
            created_at = now,
            entities={
                'user_mentions': [
                    {
                        'id': 123,
                        'id_str': '123',
                        'screen_name': user_handle
                    },
                    {
                        'id': 456,
                        'id_str': '456',
                        'screen_name': 'OtherUser'
                    },
                    {
                        'id': 789,
                        'id_str': '456',
                        'screen_name': 'SomeOtherUser'
                    }
                ]
            },
            full_text=event_text + '!',
            in_reply_to_message_id=in_reply_to_message_id,
            name='status_needing_event',
            place=place,
            user=user)

        status_not_needing_event = MagicMock(
            id=event_id - 1,
            created_at = now - relativedelta(minutes=1),
            entities={
                'user_mentions': [
                    {
                        'id': 123,
                        'id_str': '123',
                        'screen_name': user_handle
                    },
                    {
                        'id': 456,
                        'id_str': '456',
                        'screen_name': 'OtherUser'
                    },
                    {
                        'id': 789,
                        'id_str': '456',
                        'screen_name': 'SomeOtherUser'
                    }
                ]
            },
            full_text=event_text,
            in_reply_to_message_id=in_reply_to_message_id,
            name='status_not_needing_event',
            place=place,
            user=user)

        twitter_event_mock.return_value = new_twitter_event
        twitter_event_mock.query.filter().order_by().first.return_value = older_twitter_event
        twitter_event_mock.query.filter().first.side_effect = [
            None, status_not_needing_event]

        client_api_mock = MagicMock(name='client_api')
        client_api_mock.mentions_timeline.side_effect = [[
            status_needing_event, status_not_needing_event], []]
        self.tweeter._client_api = client_api_mock

        self.tweeter._find_and_respond_to_missed_statuses()

        twitter_event_mock.query.session.add.assert_called_once_with(new_twitter_event)
        twitter_event_mock.query.session.commit.assert_called_once_with()

        self.mocked_log.debug.assert_called_with('Found 1 status that was previously undetected.')

        self.tweeter.terminate_lookups()