Пример #1
0
    def test_compute_nps_report(self, unused_mock_sentry_logging, mock_post):
        """Test computing the NPS report on multiple user feedback."""

        self._db.user.insert_many([
            {
                '_id': mongomock.ObjectId('123400000012340000001234'),
                'registeredAt': '2017-11-01T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-01T16:00:00Z',
                    'score': 0,
                    'generalFeedbackComment': 'The app was blocked for me :-(',
                },
            },
            {
                'registeredAt': '2017-11-01T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-01T16:00:00Z',
                    'score': 9,
                },
            },
            {
                'registeredAt': '2017-11-01T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-01T16:00:00Z',
                    'score': 6,
                },
            },
            {
                '_id': mongomock.ObjectId('000056780000005678000000'),
                'registeredAt': '2017-11-01T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-01T16:00:00Z',
                    'score': 9,
                    'generalFeedbackComment': 'You rock!',
                },
            },
            # User registered and answered the NPS after the to_date.
            {
                'registeredAt': '2017-11-11T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-11T16:00:00Z',
                    'score': 0,
                },
            },
        ])
        feedback_report.main([
            'nps', '--from', '2017-10-30', '--to', '2017-11-07', '--no-dry-run'
        ], io.StringIO())
        slack_json = mock_post.call_args[1]['json']
        self.assertEqual(
            '4 users answered the NPS survey for a global NPS of *25.0%*\n'
            '*9*: 2 users\n'
            '*6*: 1 user\n'
            '*0*: 1 user\n'
            'And here are the individual comments:\n'
            '[Score: 9] ObjectId("000056780000005678000000")\n'
            '> You rock!\n'
            '[Score: 0] ObjectId("123400000012340000001234")\n'
            '> The app was blocked for me :-(',
            slack_json['attachments'][0]['text'])
Пример #2
0
    def test_delete_user_token(self):
        """Delete a user without its ID but with an auth token."""

        user_info = {
            'profile': {
                'city': {
                    'name': 'foobar'
                },
                'name': 'Albert',
                'year_of_birth': 1973
            },
            'projects': [{}]
        }
        user_id = self.create_user(data=user_info, email='*****@*****.**')
        token = server.auth.create_token('*****@*****.**', role='unsubscribe')
        response = self.app.delete(
            '/api/user',
            data='{"profile": {"email": "*****@*****.**"}}',
            headers={'Authorization': 'Bearer {}'.format(token)})
        self.assertEqual(200, response.status_code)
        auth_object = self._user_db.user_auth.find_one(
            {'_id': mongomock.ObjectId(user_id)})
        self.assertFalse(auth_object)
        user_data = self._user_db.user.find_one(
            {'_id': mongomock.ObjectId(user_id)})
        self.assertEqual('REDACTED', user_data['profile']['email'])
        self.assertEqual('REDACTED', user_data['profile']['name'])
        self.assertEqual(1973, user_data['profile']['yearOfBirth'])
        self.assertIn('deletedAt', user_data)
        # Pop _id field which is not JSON serializable
        user_data.pop('_id')
        self.assertNotIn('*****@*****.**', json.dumps(user_data))
Пример #3
0
    def test_registered_from_days_ago(
            self, mock_requests: requests_mock.Mocker) -> None:
        """Test update_users_client_metrics."""

        mock_db = mongomock.MongoClient().test
        mock_db.user.insert_many([
            {
                '_id': mongomock.ObjectId('7ed900dbfbebdee97f9e2332'),
                'registeredAt': '2017-11-17T10:57:12Z',
            },
            # User registered just "today" (the day the script is run).
            {
                '_id': mongomock.ObjectId('7ed900dbfbebd00000000004'),
                'registeredAt': '2017-11-19T10:57:12Z',
            },
        ])
        patcher = mock.patch(sync_amplitude.__name__ + '._DB', new=mock_db)
        patcher.start()
        self.addCleanup(patcher.stop)

        mock_requests.get(
            'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2332',
            json={'matches': [{
                'amplitude_id': 42
            }]})
        mock_requests.get(
            'https://amplitude.com/api/2/useractivity?user=42',
            json={
                'events': [
                    {
                        'event_time': '2017-10-24 10:41:00.412000',
                        'event_properties': {
                            'Mobile Version': True,
                        },
                    },
                    # Last event of the session: 25 min, 5.1 sec later.
                    {
                        'event_time': '2017-10-24 11:06:05.512000'
                    },
                ]
            })

        sync_amplitude.main([
            '--registered-from', '2017-11-14', '--disable-sentry',
            '--no-dry-run'
        ])

        user = user_pb2.User()
        proto.parse_from_mongo(
            mock_db.user.find_one({
                '_id':
                mongomock.ObjectId('7ed900dbfbebdee97f9e2332'),
            }), user)
        self.assertEqual('42', user.client_metrics.amplitude_id)
        self.assertEqual(25 * 60 + 5,
                         user.client_metrics.first_session_duration_seconds)
        self.assertEqual(project_pb2.TRUE,
                         user.client_metrics.is_first_session_mobile)
 def setUp(self) -> None:
     super().setUp()
     self._user_db.feedbacks.insert_many([
         # 2022-02-01
         {
             '_id': mongomock.ObjectId('61f878000000000000000000'),
             'userId': 'the first'
         },
         # 2022-02-02
         {
             '_id': mongomock.ObjectId('61f9c9800000000000000000'),
             'userId': 'someone'
         },
     ])
Пример #5
0
 def test_favor_tips_never_given(self):
     """Favor tips that were never sent."""
     self.database.advice_modules.insert_one({
         'adviceId': 'my-advice',
         'tipTemplateIds': ['tip-a', 'tip-b'],
     })
     self.database.tip_templates.insert_many([
         {
             '_id': 'tip-a',
             'actionTemplateId': 'tip-a',
             'title': 'Tip already sent',
             'isReadyForEmail': True,
         },
         {
             '_id': 'tip-b',
             'actionTemplateId': 'tip-b',
             'title': 'Tip never sent',
             'isReadyForEmail': True,
         },
     ])
     user_id = mongomock.ObjectId()
     self.database.email_history.update_one(
         {'_id': user_id},
         {'$set': {'tips.tip-a': now.get().isoformat() + 'Z'}},
         upsert=True,
     )
     tips = advisor.select_tips_for_email(
         user_pb2.User(user_id=str(user_id)),
         project_pb2.Project(),
         advisor_pb2.AdviceModule(advice_id='my-advice'),
         self.database,
         num_tips=1)
     self.assertEqual(['Tip never sent'], sorted(t.title for t in tips))
Пример #6
0
    def test_main(self, mock_post: mock.MagicMock) -> None:
        """Overall test."""

        _db = pymongo.MongoClient('mongodb://my-db/db').db
        _db.user.drop()
        _db.user.insert_one({
            '_id':
            mongomock.ObjectId('5daf2298484ae6c93351b822'),
            'profile': {
                'name': 'Pascal',
                'lastName': 'Corpet',
                'email': '*****@*****.**',
                'locale': 'en',
            },
            'registeredAt':
            datetime.datetime(2018, 1, 22, 10, 0, 0).isoformat() + 'Z',
            'projects': [{
                'title': 'Project Title',
            }],
        })

        mail_nps.main(self._now, '1')

        sent_messages = mailjetmock.get_all_sent_messages()
        self.assertEqual(['*****@*****.**'],
                         [m.recipient['Email'] for m in sent_messages])
        self.assertEqual(100819, sent_messages[0].properties['TemplateID'])
        template_vars = sent_messages[0].properties['Variables']
        nps_form_urlstring = template_vars.pop('npsFormUrl')
        self.assertEqual(
            {
                'baseUrl': 'http://localhost:3000',
                'firstName': 'Pascal',
            }, template_vars)
        nps_form_url = parse.urlparse(nps_form_urlstring)
        self.assertEqual(
            'http://localhost:3000/api/nps',
            parse.urlunparse(nps_form_url[:4] + ('', ) + nps_form_url[5:]))
        nps_form_args = parse.parse_qs(nps_form_url.query)
        self.assertEqual({'user', 'token', 'redirect'}, nps_form_args.keys())
        self.assertEqual(['5daf2298484ae6c93351b822'], nps_form_args['user'])
        auth.check_token('5daf2298484ae6c93351b822',
                         nps_form_args['token'][0],
                         role='nps')
        self.assertEqual(['http://localhost:3000/retours?hl=en'],
                         nps_form_args['redirect'])
        mock_post.assert_called_once_with(
            'https://slack.example.com/webhook',
            json={
                'text':
                "Report for NPS blast: I've sent 1 emails (with 0 errors)."
            },
        )

        modified_user = user_pb2.User()
        proto.parse_from_mongo(_db.user.find_one(), modified_user)
        self.assertEqual(
            [sent_messages[0].message_id],
            [m.mailjet_message_id for m in modified_user.emails_sent])
        self.assertEqual('nps', modified_user.emails_sent[0].campaign_id)
Пример #7
0
    def user_info_from_db(self, user_id: str) -> Dict[str, Any]:
        """Get user's info directly from DB without calling any endpoint."""

        user_info = self._user_db.user.find_one(
            {'_id': mongomock.ObjectId(user_id)})
        self.assertIn('_server', user_info)
        return {k: v for k, v in user_info.items() if not k.startswith('_')}
Пример #8
0
    def test_signal(self, mock_mail, mock_report_mail):
        """Test that the batch send fails gracefully on SIGTERM."""

        mock_mail.send_template.return_value.status_code = 200
        mock_mail.send_template.return_value.json.return_value = {
            'Sent': [{
                'MessageID': 123456,
            }]
        }
        mock_report_mail.send_template_to_admins.return_value.status_code = 200
        self._db.user.insert_many([
            dict(_USER_PENDING_NPS_DICT,
                 _id=mongomock.ObjectId('580f4a4271cd4a0007672a%dd' % i))
            for i in range(10)
        ])

        users = list(self._db.user.find({}))

        db_user = mock.MagicMock()
        db_user.find.return_value = _SigtermAfterNItems(users, 3)

        mail_nps.main(db_user, 'http://localhost:3000', self._now, '1')

        self.assertEqual(4, mock_mail.send_template.call_count)
        self.assertEqual(4, db_user.update_one.call_count)
        self.assertTrue(mock_report_mail.send_template_to_admins.called)
Пример #9
0
    def test_any_advice_in_the_week(self):
        """Any advice in the middle of the week."""
        user = user_pb2.User(
            user_id=str(mongomock.ObjectId()),
            features_enabled=user_pb2.Features(advisor=user_pb2.ACTIVE),
            projects=[project_pb2.Project(advices=[
                project_pb2.Advice(
                    advice_id='priority-advice',
                    num_stars=3,
                    score=8,
                ),
                project_pb2.Advice(
                    advice_id='easy-advice',
                    num_stars=1,
                    score=2,
                ),
            ])],
        )
        advice_given = set()

        for unused_index in range(101):
            advice = advisor.select_advice_for_email(user, user_pb2.WEDNESDAY, self.database)
            self.assertTrue(advice)
            self.assertIn(advice.advice_id, {'easy-advice', 'priority-advice'})
            advice_given.add(advice.advice_id)
            if len(advice_given) > 2:
                break

        # This could fail with a probability of .8^100 ~= 2e-10.
        self.assertEqual({'easy-advice', 'priority-advice'}, advice_given)
Пример #10
0
    def test_missing_sentry(self, unused_mock_requests: requests_mock.Mocker,
                            mock_logging: mock.MagicMock) -> None:
        """Missing sentry env var."""

        mock_db = mongomock.MongoClient().test
        mock_db.user.insert_one({
            '_id':
            mongomock.ObjectId('7ed900dbfbebdee97f9e2332'),
            'registeredAt':
            '2017-11-17T10:57:12Z',
        })
        patcher = mock.patch(sync_amplitude.__name__ + '._DB', new=mock_db)
        patcher.start()
        self.addCleanup(patcher.stop)

        sync_amplitude.main([
            '--registered-from', '2017-11-14', '--registered-to', '2017-11-18',
            '--no-dry-run'
        ])

        user = user_pb2.User()
        proto.parse_from_mongo(mock_db.user.find_one({}), user)
        self.assertFalse(user.client_metrics.amplitude_id)

        mock_logging.assert_called_once()
Пример #11
0
    def test_filter_after(self,
                          mock_verify_oauth2_token: mock.MagicMock) -> None:
        """Test filtering only recent feedback."""

        mock_verify_oauth2_token.return_value = {
            'iss': 'accounts.google.com',
            'email': '*****@*****.**',
            'sub': '12345',
        }
        self._user_db.feedbacks.insert_many([
            # 2021-01-01
            {
                '_id': mongomock.ObjectId('5fee66000000000000000000'),
                'userId': 'very old feedback'
            },
            {
                '_id':
                mongomock.ObjectId.from_datetime(datetime.datetime(2021, 1,
                                                                   2)),
                'userId':
                'very old feedback',
            },
        ])
        response = self.app.get(
            '/api/eval/feedback/export?token=blabla&data=%7B"ater"%3A"2022-02-01T15:00:00Z"%7D'
        )
        self.assertNotIn('very old feedback', response.get_data(as_text=True))
Пример #12
0
    def test_signal(self, mock_select_advice, unused_mock_select_tips,
                    mock_mail):
        """Test that the batch send fails gracefully on SIGTERM."""
        mock_select_advice.return_value = project_pb2.Advice(
            advice_id='advice-to-send',
            num_stars=2,
        )

        mock_mail.send_template.return_value.status_code = 200
        mock_mail.send_template_to_admins.return_value.status_code = 200
        mock_mail.count_sent_to.return_value = {
            'DeliveredCount': 0,
            'OpenedCount': 0
        }
        self._db.user.insert_many([
            dict(_USER_READY_FOR_EMAIL,
                 _id=mongomock.ObjectId('580f4a4271cd4a0007672a%dd' % i))
            for i in range(10)
        ])

        users = list(self._db.user.find({}))

        db_user = mock.MagicMock()
        db_user.find.return_value = _SigtermAfterNItems(users, 3)
        self._db.user = db_user

        mail_advice.main(self._db, 'http://localhost:3000', self._now)

        self.assertEqual(4, mock_mail.send_template.call_count)
        self.assertEqual(4, db_user.update_one.call_count)
        self.assertTrue(mock_mail.send_template_to_admins.called)
Пример #13
0
    def create_user_that(self, predicate, num_tries=50, *args, **kwargs):
        """Creates a user that passes a predicate.

        Args:
            predicate: the predicate on the JSON-like dict to pass.
            num_tries: the number of creation attempt that we should try.
            args, kwargs: the parameters to send to the create_user method on
                each attempt.
        Returns:
            a user ID.
        Raises:
            AssertionError: if we could not create a user that passes the
            predicate in the given number of tries.
        """
        for unused_ in range(num_tries):
            user_id = self.create_user(*args, **kwargs)
            user_data = self._db.user.find_one(
                {'_id': mongomock.ObjectId(user_id)})
            try:
                if predicate(user_data):
                    return user_id
            except KeyError:
                pass
        self.fail('Could not create a user that matches the predicate'
                  )  # pragma: no cover
Пример #14
0
 def _assert_user_receives_campaign(self,
                                    mock_mail=None,
                                    should_be_sent=True):
     json_user = json_format.MessageToDict(self.user)
     json_user['_id'] = mongomock.ObjectId(json_user.pop('userId'))
     self._user_database.user.insert_one(json_user)
     mock_mail().status_code = 200
     mock_mail().json.return_value = {
         'Sent': [{
             'MessageID': 18014679230180635
         }]
     }
     mock_mail.reset_mock()
     year = self.user.registered_at.ToDatetime().year
     mail_blast.main([
         self._campaign_id,
         'send',
         '--disable-sentry',
         '--registered-from',
         str(year),
         '--registered-to',
         str(year + 1),
     ])
     if not should_be_sent:
         self.assertFalse(mock_mail.called)
         return
     mock_mail.assert_called_once()
     self._variables = mock_mail.call_args[0][2]
Пример #15
0
    def test_unknown_user(self, mock_requests: requests_mock.Mocker) -> None:
        """Test update_users_client_metrics with an unknown user."""

        mock_db = mongomock.MongoClient().test
        mock_db.user.insert_one({
            '_id':
            mongomock.ObjectId('7ed900dbfbebdee97f9e2332'),
            'registeredAt':
            '2017-11-17T10:57:12Z',
        })
        patcher = mock.patch(sync_amplitude.__name__ + '._DB', new=mock_db)
        patcher.start()
        self.addCleanup(patcher.stop)

        mock_requests.get(
            'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2332',
            json={'matches': []})

        sync_amplitude.main([
            '--registered-from', '2017-11-14', '--registered-to', '2017-11-18',
            '--disable-sentry', '--no-dry-run'
        ])

        user = user_pb2.User()
        proto.parse_from_mongo(mock_db.user.find_one({}), user)

        self.assertEqual('Not Found', user.client_metrics.amplitude_id)
Пример #16
0
 def test_three_tips(self):
     """Only three tips."""
     self.database.advice_modules.insert_one({
         'adviceId': 'has-3-tips',
         'tipTemplateIds': ['tip-a', 'tip-b', 'tip-c'],
     })
     self.database.tip_templates.insert_many([
         {
             '_id': 'tip-a',
             'actionTemplateId': 'tip-a',
             'emailTitle': 'First tip',
             'isReadyForEmail': True,
         },
         {
             '_id': 'tip-b',
             'actionTemplateId': 'tip-b',
             'emailTitle': 'Second tip',
             'isReadyForEmail': True,
         },
         {
             '_id': 'tip-c',
             'actionTemplateId': 'tip-c',
             'emailTitle': 'Third tip',
             'isReadyForEmail': True,
         },
     ])
     tips = advisor.select_tips_for_email(
         user_pb2.User(user_id=str(mongomock.ObjectId())),
         project_pb2.Project(),
         advisor_pb2.AdviceModule(advice_id='has-3-tips'),
         self.database)
     self.assertEqual(
         ['First tip', 'Second tip', 'Third tip'],
         sorted(t.title for t in tips))
Пример #17
0
    def test_too_many_requests(self, mock_requests):
        """Test too many requests."""

        mock_db = mongomock.MongoClient().test
        mock_db.user.insert_one({
            '_id':
            mongomock.ObjectId('7ed900dbfbebdee97f9e2332'),
            'registeredAt':
            '2017-11-17T10:57:12Z',
        })
        patcher = mock.patch(sync_amplitude.__name__ + '._DB', new=mock_db)
        patcher.start()
        self.addCleanup(patcher.stop)

        mock_requests.get(
            'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2332',
            status_code=429,
            reason='429 Client Error: Too many requests for url',
        )

        with self.assertRaises(sync_amplitude.TooManyRequestsException):
            sync_amplitude.main([
                '--registered-from', '2017-11-14', '--registered-to',
                '2017-11-18', '--disable-sentry', '--no-dry-run'
            ])
Пример #18
0
    def test_compute_nps_report_no_comments(self, unused_mock_sentry_logging,
                                            mock_post):
        """Test computing the NPS report on multiple user feedback."""

        self._db.user.insert_many([
            {
                '_id': mongomock.ObjectId('123400000012340000001234'),
                'registeredAt': '2017-11-01T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-01T16:00:00Z',
                    'score': 0,
                },
            },
            {
                'registeredAt': '2017-11-01T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-01T16:00:00Z',
                    'score': 9,
                },
            },
            {
                'registeredAt': '2017-11-01T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-01T16:00:00Z',
                    'score': 6,
                },
            },
            {
                '_id': mongomock.ObjectId('000056780000005678000000'),
                'registeredAt': '2017-11-01T12:00:00Z',
                'netPromoterScoreSurveyResponse': {
                    'respondedAt': '2017-11-01T16:00:00Z',
                    'score': 9,
                },
            },
        ])
        feedback_report.main([
            'nps', '--from', '2017-10-30', '--to', '2017-11-07', '--no-dry-run'
        ], io.StringIO())
        slack_json = mock_post.call_args[1]['json']
        self.assertEqual(
            '4 users answered the NPS survey for a global NPS of *25.0%*\n'
            '*9*: 2 users\n'
            '*6*: 1 user\n'
            '*0*: 1 user\n'
            'There are no individual comments.',
            slack_json['attachments'][0]['text'])
Пример #19
0
 def test_not_in_advisor(self):
     """User does not use the advisor yet."""
     user_not_in_advisor = user_pb2.User(
         user_id=str(mongomock.ObjectId()),
         projects=[project_pb2.Project()],
     )
     self.assertFalse(advisor.select_advice_for_email(
         user_not_in_advisor, user_pb2.TUESDAY, self.database))
Пример #20
0
    def test_long_continuous_session(
            self, mock_requests: requests_mock.Mocker) -> None:
        """Test update_users_client_metrics with a user using Bob continuously for an hour."""

        mock_db = mongomock.MongoClient().test
        mock_db.user.insert_one({
            '_id':
            mongomock.ObjectId('7ed900dbfbebdee97f9e2332'),
            'registeredAt':
            '2017-11-17T10:57:12Z',
        })
        patcher = mock.patch(sync_amplitude.__name__ + '._DB', new=mock_db)
        patcher.start()
        self.addCleanup(patcher.stop)

        mock_requests.get(
            'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2332',
            json={'matches': [{
                'amplitude_id': 42
            }]})
        mock_requests.get('https://amplitude.com/api/2/useractivity?user=42',
                          json={
                              'events': [
                                  {
                                      'event_time': '2017-10-24 10:40:00'
                                  },
                                  {
                                      'event_time': '2017-10-24 10:45:08'
                                  },
                                  {
                                      'event_time': '2017-10-24 11:06:05'
                                  },
                                  {
                                      'event_time': '2017-10-24 11:26:05'
                                  },
                                  {
                                      'event_time': '2017-10-24 11:46:05'
                                  },
                                  {
                                      'event_time': '2017-10-24 12:05:05'
                                  },
                              ]
                          })

        sync_amplitude.main([
            '--registered-from', '2017-11-14', '--registered-to', '2017-11-18',
            '--disable-sentry', '--no-dry-run'
        ])

        user = user_pb2.User()
        proto.parse_from_mongo(mock_db.user.find_one({}), user)

        self.assertEqual('42', user.client_metrics.amplitude_id)
        self.assertEqual(85 * 60 + 5,
                         user.client_metrics.first_session_duration_seconds)
        self.assertEqual(project_pb2.FALSE,
                         user.client_metrics.is_first_session_mobile)
Пример #21
0
    def test_update_users_client_metrics(
            self, mock_requests: requests_mock.Mocker) -> None:
        """Test update_users_client_metrics."""

        mock_db = mongomock.MongoClient().test
        mock_db.user.insert_one({
            '_id':
            mongomock.ObjectId('7ed900dbfbebdee97f9e2332'),
            'registeredAt':
            '2017-11-17T10:57:12Z',
        })
        patcher = mock.patch(sync_amplitude.__name__ + '._DB', new=mock_db)
        patcher.start()
        self.addCleanup(patcher.stop)

        mock_requests.get(
            'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2332',
            json={'matches': [{
                'amplitude_id': 42
            }]})
        mock_requests.get(
            'https://amplitude.com/api/2/useractivity?user=42',
            json={
                'events': [
                    {
                        'event_time': '2017-10-24 10:41:08.396000'
                    },
                    # Event out of order, this one is actually the first of the session.
                    {
                        'event_time': '2017-10-24 10:41:00.412000'
                    },
                    # Last event of the session: 25 min, 5.1 sec later.
                    {
                        'event_time': '2017-10-24 11:06:05.512000'
                    },
                    # Event really later: next session.
                    {
                        'event_time': '2017-10-24 13:06:05'
                    },
                ]
            })

        sync_amplitude.main([
            '--registered-from', '2017-11-14', '--registered-to', '2017-11-18',
            '--disable-sentry', '--no-dry-run'
        ])

        user = user_pb2.User()
        proto.parse_from_mongo(mock_db.user.find_one({}), user)

        self.assertEqual('42', user.client_metrics.amplitude_id)
        self.assertEqual(25 * 60 + 5,
                         user.client_metrics.first_session_duration_seconds)
        self.assertEqual(project_pb2.FALSE,
                         user.client_metrics.is_first_session_mobile)
Пример #22
0
 def test_no_tips(self):
     """No tips."""
     self.database.advice_modules.insert_one({
         'adviceId': 'has-no-tips',
     })
     tips = advisor.select_tips_for_email(
         user_pb2.User(user_id=str(mongomock.ObjectId())),
         project_pb2.Project(),
         advisor_pb2.AdviceModule(advice_id='has-no-tips'),
         self.database)
     self.assertFalse(tips)
Пример #23
0
 def test_one_advice_module_only(self):
     """Only one advice module was recommended."""
     user = user_pb2.User(
         user_id=str(mongomock.ObjectId()),
         features_enabled=user_pb2.Features(advisor=user_pb2.ACTIVE),
         projects=[project_pb2.Project(advices=[project_pb2.Advice(
             advice_id='only-advice')])],
     )
     advice = advisor.select_advice_for_email(user, user_pb2.TUESDAY, self.database)
     self.assertTrue(advice)
     self.assertEqual('only-advice', advice.advice_id)
Пример #24
0
    def _assert_user_receives_campaign(
            self,
            should_be_sent: bool = True,
            blast_from: Optional[str] = None,
            blast_to: Optional[str] = None,
            extra_args: Optional[list[str]] = None) -> None:
        json_user = json_format.MessageToDict(self.user)
        json_user['_id'] = mongomock.ObjectId(json_user.pop('userId'))
        self._user_database.user.insert_one(json_user)
        year = self.user.registered_at.ToDatetime().year
        if self.now:
            now_patcher = nowmock.patch(new=mock.MagicMock(
                return_value=self.now))
            now_patcher.start()
            self.addCleanup(now_patcher.stop)
        mail_blast.main([
            self.campaign_id,
            'send',
            '--disable-sentry',
            '--registered-from',
            blast_from or str(year),
            '--registered-to',
            blast_to or str(year + 1),
            '--log-reason-on-error',
        ] + (extra_args or []))
        all_sent_messages = mailjetmock.get_all_sent_messages()
        if not should_be_sent:
            self.assertFalse(all_sent_messages)
            return
        self.assertEqual(1, len(all_sent_messages), msg=all_sent_messages)
        self.assertEqual(self.campaign_id,
                         all_sent_messages[0].properties['CustomCampaign'])
        self._variables = all_sent_messages[0].properties['Variables']
        self._from = all_sent_messages[0].properties['From']
        self.assertEqual(self._from['Name'], self._variables.pop('senderName'))

        # Test that variables used in the template are populated.
        template_id = str(all_sent_messages[0].properties['TemplateID'])
        template_path = campaign.get_campaign_folder(
            typing.cast(mailjet_templates.Id, self.campaign_id))
        self.assertTrue(template_path,
                        msg=f'No template for campaign "{self.campaign_id}"')
        assert template_path
        vars_filename = os.path.join(template_path, 'vars-example.json')
        with open(vars_filename, 'r', encoding='utf-8') as vars_file:
            template_vars = json.load(vars_file).keys()
        for template_var in template_vars:
            self.assertIn(
                template_var,
                self._variables,
                msg=f'Template error for campaign {self.campaign_id}, see '
                f'https://app.mailjet.com/template/{template_id}/build')
Пример #25
0
    def test_delete_user(self):
        """Test deleting a user and all their data."""

        user_info = {
            'profile': {
                'city': {
                    'name': 'foobar'
                },
                'name': 'Albert',
                'year_of_birth': 1973
            },
            'projects': [{}],
            'emailsSent': [{
                'mailjetMessageId': 1234
            }],
        }
        user_id, auth_token = self.create_user_with_token(data=user_info,
                                                          email='*****@*****.**')
        response = self.app.delete(
            '/api/user',
            data='{{"userId": "{}", "profile": {{"email": "*****@*****.**"}}}}'.
            format(user_id),
            headers={'Authorization': 'Bearer ' + auth_token})
        self.assertEqual(200, response.status_code)
        auth_object = self._user_db.user_auth.find_one(
            {'_id': mongomock.ObjectId(user_id)})
        self.assertFalse(auth_object)
        user_data = self._user_db.user.find_one(
            {'_id': mongomock.ObjectId(user_id)})
        self.assertEqual('REDACTED', user_data['profile']['email'])
        self.assertEqual('REDACTED', user_data['profile']['name'])
        self.assertEqual(1973, user_data['profile']['yearOfBirth'])
        self.assertIn('deletedAt', user_data)
        self.assertEqual([{}], user_data.get('emailsSent'))
        # Pop _id field which is not JSON serializable
        user_data.pop('_id')
        self.assertNotIn('*****@*****.**', json.dumps(user_data))
Пример #26
0
    def test_different_advice_during_the_week(self, mock_now):
        """Different advice during the week."""
        user_id = mongomock.ObjectId()
        user = user_pb2.User(
            user_id=str(user_id),
            features_enabled=user_pb2.Features(advisor=user_pb2.ACTIVE),
            projects=[project_pb2.Project(advices=[
                project_pb2.Advice(
                    advice_id='priority-advice',
                    num_stars=3,
                    score=8,
                ),
                project_pb2.Advice(
                    advice_id='other-advice',
                    num_stars=2,
                    score=9,
                ),
                project_pb2.Advice(
                    advice_id='easy-advice',
                    num_stars=1,
                    score=2,
                ),
            ])],
        )
        advice_given = set()

        mock_now.return_value = datetime.datetime(2017, 4, 3, 13, 00)
        advice = advisor.select_advice_for_email(user, user_pb2.MONDAY, self.database)
        advice_given.add(advice.advice_id)
        self.database.email_history.update_one(
            {'_id': user_id},
            {'$set': {'advice_modules.%s' % advice.advice_id: mock_now().isoformat() + 'Z'}},
            upsert=True,
        )

        mock_now.return_value = datetime.datetime(2017, 4, 5, 13, 00)
        advice = advisor.select_advice_for_email(user, user_pb2.WEDNESDAY, self.database)
        advice_given.add(advice.advice_id)
        self.database.email_history.update_one(
            {'_id': user_id},
            {'$set': {'advice_modules.%s' % advice.advice_id: mock_now().isoformat() + 'Z'}},
            upsert=True,
        )

        mock_now.return_value = datetime.datetime(2017, 4, 7, 13, 00)
        advice = advisor.select_advice_for_email(user, user_pb2.FRIDAY, self.database)
        advice_given.add(advice.advice_id)

        self.assertEqual(3, len(advice_given), msg=advice_given)
Пример #27
0
    def test_too_many_requests_but_still_enough(self, mock_requests):
        """Test too many requests but already done more than 200."""

        mock_db = mongomock.MongoClient().test
        mock_db.user.insert_many([{
            '_id':
            mongomock.ObjectId('7ed900dbfbebdee97f9e2{:03d}'.format(i)),
            'registeredAt':
            '2017-11-17T10:57:12Z',
        } for i in range(400)])
        patcher = mock.patch(sync_amplitude.__name__ + '._DB', new=mock_db)
        patcher.start()
        self.addCleanup(patcher.stop)

        # Reply politely to the 300 first.
        for i in range(300):
            mock_requests.get(
                'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2{:03d}'
                .format(i),
                json={'matches': [{
                    'amplitude_id': i
                }]},
            )
            mock_requests.get(
                'https://amplitude.com/api/2/useractivity?user={}'.format(i),
                json={'events': []})
        # Then reply with an error 429.
        for i in range(100):
            mock_requests.get(
                'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2{:03d}'
                .format(i + 300),
                status_code=429,
                reason='429 Client Error: Too many requests for url',
            )

        sync_amplitude.main([
            '--registered-from', '2017-11-14', '--registered-to', '2017-11-18',
            '--disable-sentry', '--no-dry-run'
        ])

        self.assertEqual(
            300,
            len(
                list(
                    mock_db.user.find(
                        {'clientMetrics.amplitudeId': {
                            '$exists': True
                        }}))))
Пример #28
0
    def test_dry_run(self, mock_requests: requests_mock.Mocker,
                     mock_logging: mock.MagicMock) -> None:
        """Test update_users_client_metrics."""

        mock_db = mongomock.MongoClient().test
        mock_db.user.insert_one({
            '_id':
            mongomock.ObjectId('7ed900dbfbebdee97f9e2332'),
            'registeredAt':
            '2017-11-17T10:57:12Z',
        })
        patcher = mock.patch(sync_amplitude.__name__ + '._DB', new=mock_db)
        patcher.start()
        self.addCleanup(patcher.stop)

        mock_requests.get(
            'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2332',
            json={'matches': [{
                'amplitude_id': 42
            }]})
        mock_requests.get('https://amplitude.com/api/2/useractivity?user=42',
                          json={
                              'events': [
                                  {
                                      'event_time':
                                      '2017-10-24 10:41:00.412000'
                                  },
                                  {
                                      'event_time':
                                      '2017-10-24 11:06:05.512000'
                                  },
                              ]
                          })

        sync_amplitude.main([
            '--registered-from', '2017-11-14', '--registered-to', '2017-11-18',
            '--disable-sentry'
        ])

        user = user_pb2.User()
        proto.parse_from_mongo(mock_db.user.find_one({}), user)

        self.assertFalse(user.client_metrics.amplitude_id)
        self.assertFalse(user.client_metrics.first_session_duration_seconds)
        self.assertFalse(user.client_metrics.is_first_session_mobile)

        mock_logging.assert_called_once()
Пример #29
0
    def test_too_many_requests_but_still_enough(
            self, mock_requests: requests_mock.Mocker) -> None:
        """Test too many requests but already done more than 200."""

        self._user_db.user.insert_many([{
            '_id':
            mongomock.ObjectId(f'7ed900dbfbebdee97f9e2{i:03d}'),
            'registeredAt':
            '2017-11-17T10:57:12Z',
        } for i in range(400)])

        # Reply politely to the 300 first.
        for i in range(300):
            mock_requests.get(
                f'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2{i:03d}',
                json={'matches': [{
                    'amplitude_id': i
                }]},
            )
            mock_requests.get(
                f'https://amplitude.com/api/2/useractivity?user={i}',
                json={'events': []})
        # Then reply with an error 429.
        for i in range(100):
            mock_requests.get(
                f'https://amplitude.com/api/2/usersearch?user=7ed900dbfbebdee97f9e2{i + 300:03d}',
                status_code=429,
                reason='429 Client Error: Too many requests for url',
            )

        sync_amplitude.main([
            '--registered-from', '2017-11-14', '--registered-to', '2017-11-18',
            '--disable-sentry', '--no-dry-run'
        ])

        self.assertEqual(
            300,
            len(
                list(
                    self._user_db.user.find(
                        {'clientMetrics.amplitudeId': {
                            '$exists': True
                        }}))))
Пример #30
0
 def _assert_user_receives_focus(self, should_be_sent: bool = True) -> None:
     json_user = json_format.MessageToDict(self.user)
     json_user['_id'] = mongomock.ObjectId(json_user.pop('userId'))
     self._user_database.get_collection(
         self.mongo_collection).insert_one(json_user)
     with mock.patch(focus.__name__ + '._POTENTIAL_CAMPAIGNS',
                     {self.campaign_id}):
         focus.main([
             'send',
             '--disable-sentry',
         ])
     all_sent_messages = mailjetmock.get_all_sent_messages()
     if not should_be_sent:
         self.assertFalse(all_sent_messages)
         return
     self.assertEqual(1, len(all_sent_messages), msg=all_sent_messages)
     self.assertEqual(self.campaign_id,
                      all_sent_messages[0].properties['CustomCampaign'])
     self._variables = all_sent_messages[0].properties['Variables']