'advicePageUrl': advice_page_url,
        'hasInterviewFrustration':
        campaign.as_template_boolean(user_profile_pb2.INTERVIEW in user.profile.frustrations),
        'hasSelfConfidenceFrustration':
        campaign.as_template_boolean(user_profile_pb2.SELF_CONFIDENCE in user.profile.frustrations),
    }


_CAMPAIGNS = [
    campaign.Campaign(
        campaign_id='prepare-your-application',
        mongo_filters={
            'projects': {'$elemMatch': {
                'isIncomplete': {'$ne': True},
            }},
        },
        get_vars=_get_prepare_your_application_vars,
        sender_name=i18n.make_translatable_string("Joanna et l'équipe de {{var:productName}}"),
        sender_email='*****@*****.**',
        is_coaching=True,
        is_big_focus=True,
    ),
    campaign.Campaign(
        campaign_id='prepare-your-application-short',
        mongo_filters={
            'projects': {'$elemMatch': {
                'isIncomplete': {'$ne': True},
            }},
        },
        get_vars=_get_prepare_your_application_short_vars,
        sender_name=i18n.make_translatable_string("Pascal et l'équipe de {{var:productName}}"),
Esempio n. 2
0
            'de definir votre projet professionnel')

    return campaign.get_default_coaching_email_vars(user) | {
        'inDepartement': scoring_project.populate_template('%inDepartement'),
        'jobs': [{'name': job.name} for job in reorient_jobs],
        'ofJobName': of_job_name,
    }


campaign.register_campaign(campaign.Campaign(
    campaign_id='jobbing',
    mongo_filters={
        'projects': {'$elemMatch': {
            'isIncomplete': {'$ne': True},
            'openedStrategies.strategyId': 'diploma-free-job',
        }},
    },
    get_vars=_get_jobbing_vars,
    sender_name=i18n.make_translatable_string("Joanna et l'équipe de {{var:productName}}"),
    sender_email='*****@*****.**',
    is_coaching=True,
    is_big_focus=False,
))
campaign.register_campaign(campaign.Campaign(
    campaign_id='jobbing-short',
    mongo_filters={
        'projects': {'$elemMatch': {
            'isIncomplete': {'$ne': True},
            'openedStrategies.strategyId': 'diploma-free-job',
        }},
    },
    get_vars=_get_jobbing_short_vars,
Esempio n. 3
0
) -> dict[str, Any]:
    if not any(email.campaign_id == 'jobflix-first-eval'
               for email in user.emails_sent):
        raise campaign.DoNotSend(
            'Only useful for user that have received the first campaign')
    next_week = now + datetime.timedelta(days=7)
    return campaign.get_default_vars(user) | {
        'closingDate': next_week.strftime('%A %d %B'),
    }


campaign.register_campaign(
    campaign.Campaign(campaign_id='jobflix-first-eval',
                      mongo_filters={'projects.isIncomplete': {
                          '$ne': True
                      }},
                      get_vars=_get_first_eval_vars,
                      sender_name=i18n.make_translatable_string(
                          "{0} et l'équipe de {1}").format('Tabitha', 'Bob'),
                      sender_email='*****@*****.**'))

campaign.register_campaign(
    campaign.Campaign(
        campaign_id='jobflix-first-eval-reminder',
        mongo_filters={'emailsSent.campaignId': 'jobflix-first-eval'},
        get_vars=_get_first_eval_reminder_vars,
        sender_name=i18n.make_translatable_string(
            "{0} et l'équipe de {1}").format('Tabitha', 'Bob'),
        sender_email='*****@*****.**'))

campaign.register_campaign(
    campaign.Campaign(campaign_id='jobflix-welcome',
Esempio n. 4
0
import pymongo
import requests

from bob_emploi.common.python.test import nowmock
from bob_emploi.frontend.api import email_pb2
from bob_emploi.frontend.server import mongo
from bob_emploi.frontend.server import product
from bob_emploi.frontend.server.mail import campaign
from bob_emploi.frontend.server.mail import mail_blast
from bob_emploi.frontend.server.mail.templates import mailjet_templates
from bob_emploi.frontend.server.test import mailjetmock

_FAKE_CAMPAIGNS = {
    'fake-user-campaign':
    campaign.Campaign(typing.cast(mailjet_templates.Id, 'fake-user-campaign'),
                      get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                      sender_name='Sender',
                      sender_email='*****@*****.**')
}


class EmailPolicyTestCase(unittest.TestCase):
    """Tests for the EmailPolicy class."""
    def _make_email(
            self,
            campaign_id: str,
            days_ago: int = 0,
            hours_ago: int = 0,
            status: 'email_pb2.EmailSentStatus.V' = email_pb2.EMAIL_SENT_SENT,
            status_updated_days_after: Optional[int] = 8
    ) -> email_pb2.EmailSent:
        email = email_pb2.EmailSent(campaign_id=campaign_id, status=status)
Esempio n. 5
0
        why_specific_company,
    }


campaign.register_campaign(
    campaign.Campaign(
        campaign_id='focus-spontaneous',
        mongo_filters={
            'projects': {
                '$elemMatch': {
                    'jobSearchHasNotStarted': {
                        '$ne': True
                    },
                    'isIncomplete': {
                        '$ne': True
                    },
                }
            },
        },
        get_vars=_get_spontaneous_vars,
        sender_name=i18n.make_translatable_string(
            "Joanna et l'équipe de {{var:productName}}"),
        sender_email='*****@*****.**',
        is_coaching=True,
        is_big_focus=True,
    ))
campaign.register_campaign(
    campaign.Campaign(
        campaign_id='spontaneous-short',
        mongo_filters={
            'projects': {
Esempio n. 6
0
    }


_NPS_CAMPAIGN = campaign.Campaign(
    # See https://app.mailjet.com/template/100819/build
    campaign_id=_CAMPAIGN_ID,
    mongo_filters={
        'emailsSent': {
            '$not': {
                '$elemMatch': {
                    'campaignId': _CAMPAIGN_ID
                }
            }
        },
        'projects': {
            '$exists': True
        },
        'projects.isIncomplete': {
            '$ne': True
        },
        'registeredAt': {
            '$gt': '2018-01-01'
        },
    },
    get_vars=_get_nps_vars,
    sender_name=product.bob.name,
    sender_email='*****@*****.**',
)

campaign.register_campaign(_NPS_CAMPAIGN)
Esempio n. 7
0
class SendFocusEmailTest(unittest.TestCase):
    """Unit tests for the main function."""
    def setUp(self) -> None:
        super().setUp()
        patcher = mock.patch(focus.mongo.__name__ +
                             '.get_connections_from_env')
        mock_mongo = patcher.start()
        self.addCleanup(patcher.stop)
        self._db = mongomock.MongoClient()
        mock_mongo.return_value = (focus.mongo.NoPiiMongoDatabase(
            self._db.test),
                                   focus.mongo.UsersDatabase.from_database(
                                       self._db.user_test),
                                   focus.mongo.NoPiiMongoDatabase(
                                       self._db.eval_test))

        patcher = nowmock.patch()
        self.mock_now = patcher.start()
        self.addCleanup(patcher.stop)
        self.mock_now.return_value = datetime.datetime(2018, 5, 31, 12, 38)

        self._db.user_test.user.insert_one({
            'profile': {
                'coachingEmailFrequency': 'EMAIL_MAXIMUM',
                'email': '*****@*****.**',
                'frustrations': ['SELF_CONFIDENCE', 'INTERVIEW'],
            },
            'projects': [{
                'kind': 'FIND_A_FIRST_JOB',
                'network_estimate': 1,
                'jobSearchStartedAt': '2017-10-01T09:34:00Z',
                'targetJob': {
                    'jobGroup': {
                        'romeId': 'A1234'
                    }
                },
            }],
            'registeredAt':
            '2018-01-15T15:24:34Z',
        })
        self._db.test.focus_emails.insert_many([{
            'campaignId': c
        } for c in _GOLDEN_FOCUS_CAMPAIGNS])
        self._db.test.job_group_info.insert_one({
            '_id': 'A1234',
            'inDomain': 'dans le domaine',
        })

    @mock.patch('logging.info')
    def test_list_campaigns(self, mock_logging: mock.MagicMock) -> None:
        """List existing focus email campaigns."""

        self._db.user_test.user.drop()

        focus.main(['list'])

        mock_logging.assert_called()
        for logging_call in mock_logging.call_args_list:
            if logging_call[0][0].startswith('Potential focus emails:'):
                self.assertEqual(
                    list(_GOLDEN_FOCUS_CAMPAIGNS),
                    logging_call[0][1],
                    msg=
                    "Update the golden focus campaigns as it's used in other tests."
                )
                break
        else:  # pragma: no-cover
            self.fail('No logging call about potential focus emails.')

    @mock.patch('logging.info')
    def test_list_emails(self, mock_logging: mock.MagicMock) -> None:
        """List uses logging extensively but does not send any email."""

        focus.main(['list'])

        mock_logging.assert_called()

        self.assertFalse(mailjetmock.get_all_sent_messages())

    def test_send_first(self) -> None:
        """Sending a first focus email."""

        focus.main(['send', '--disable-sentry'])

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(1, len(user_data.get('emailsSent')))
        self.assertIn(user_data['emailsSent'][0]['campaignId'],
                      _GOLDEN_FOCUS_CAMPAIGNS)

    def test_send_first_too_early(self) -> None:
        """Sending a first focus email too soon after a registration."""

        self._db.user_test.user.update_one(
            {}, {'$set': {
                'registeredAt': '2018-05-30T14:22:00Z'
            }})

        focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertFalse(user_data.get('emailsSent'))
        self.assertEqual('2018-06-02T14:22:00Z',
                         user_data.get('sendCoachingEmailAfter'))

    def test_send_shortly_after_another(self) -> None:
        """Sending a second focus email shortly after the first one."""

        focus.main(['send', '--disable-sentry'])

        self.mock_now.return_value += datetime.timedelta(hours=1)

        mailjetmock.clear_sent_messages()

        focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(1, len(user_data.get('emailsSent')))

    def test_send_a_week_after_another(self) -> None:
        """Sending a second focus email a week after the first one."""

        focus.main(['send', '--disable-sentry'])

        self.mock_now.return_value += datetime.timedelta(days=9)

        mailjetmock.clear_sent_messages()

        focus.main(['send', '--disable-sentry'])

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(2, len(user_data.get('emailsSent')))
        self.assertIn(user_data['emailsSent'][1]['campaignId'],
                      _GOLDEN_FOCUS_CAMPAIGNS)

    def test_send_only_once_a_month(self) -> None:
        """Sending focus emails to user on "once-a-month" frequency."""

        self._db.user_test.user.update_one(
            {},
            {'$set': {
                'profile.coachingEmailFrequency': 'EMAIL_ONCE_A_MONTH'
            }})

        focus.main(['send', '--disable-sentry'])

        self.mock_now.return_value += datetime.timedelta(days=15)

        mailjetmock.clear_sent_messages()

        focus.main(['send', '--disable-sentry'])

        # No email sent, even 15 days later.
        self.assertFalse(mailjetmock.get_all_sent_messages())

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(1, len(user_data.get('emailsSent')))

        self.mock_now.return_value += datetime.timedelta(days=30)

        mailjetmock.clear_sent_messages()

        focus.main(['send', '--disable-sentry'])

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(2, len(user_data.get('emailsSent')))

    def test_send_after_not_focus(self) -> None:
        """Sending a second focus email shortly after another random email."""

        self._db.user_test.user.update_one({}, {
            '$push': {
                'emailsSent': {
                    'campaignId': 'not-a-focus',
                    'sentAt': '2018-05-30T23:12:00Z',
                }
            }
        })

        focus.main(['send', '--disable-sentry'])

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(2, len(user_data.get('emailsSent')))
        self.assertIn(user_data['emailsSent'][1]['campaignId'],
                      _GOLDEN_FOCUS_CAMPAIGNS)

    @mock.patch('logging.info')
    def test_send_all_focus_emails(
            self, unused_mock_logging: mock.MagicMock) -> None:
        """Sending all focus emails in 6 months."""

        days_without_email = 0
        sent_emails_count = 0

        # Try sending emails until there has been a month without any email sent.
        while days_without_email < 30 and sent_emails_count <= len(
                _GOLDEN_FOCUS_CAMPAIGNS):
            focus.main(['send', '--disable-sentry'])

            emails_sent = mailjetmock.get_all_sent_messages()
            if len(emails_sent) > sent_emails_count:
                sent_emails_count = len(emails_sent)
                days_without_email = 0
            else:
                days_without_email += 1

            self.mock_now.return_value += datetime.timedelta(days=1)

        emails_sent = mailjetmock.get_all_sent_messages()
        self.assertEqual({'*****@*****.**'},
                         {m.recipient['Email']
                          for m in emails_sent})
        self.assertLessEqual(len(emails_sent), len(_GOLDEN_FOCUS_CAMPAIGNS))

        user_data = self._db.user_test.user.find_one()
        assert user_data
        campaigns_sent = [e.get('campaignId') for e in user_data['emailsSent']]
        self.assertCountEqual(set(campaigns_sent),
                              campaigns_sent,
                              msg='No duplicates')
        self.assertLessEqual(set(campaigns_sent), set(_GOLDEN_FOCUS_CAMPAIGNS))

        # Try sending emails until the next check.
        next_date = datetime.datetime.fromisoformat(
            user_data['sendCoachingEmailAfter'][:-1])
        while next_date >= self.mock_now.return_value:
            focus.main(['send', '--disable-sentry'])

            self.mock_now.return_value += datetime.timedelta(days=1)

        self.assertEqual(
            len(emails_sent),
            len(mailjetmock.get_all_sent_messages()),
            msg='No new messages.'
            ' There probably is an issue with time sensitive conditions on some emails'
        )
        user_data = self._db.user_test.user.find_one()
        # Next check should be at least a month from now.
        self.assertLessEqual(
            self.mock_now.return_value + datetime.timedelta(days=30),
            datetime.datetime.fromisoformat(
                user_data['sendCoachingEmailAfter'][:-1]))

    @mock.patch('logging.info')
    @mock.patch('random.random', new=lambda: 0.5)
    def test_change_setting(self, unused_mock_logging: mock.MagicMock) -> None:
        """Changing the settings after the first email has been sent."""

        focus.main(['send', '--disable-sentry'])
        mailjetmock.clear_sent_messages()

        # Change the email frequency setting right after the first email.
        self._db.user_test.user.update_one({}, {
            '$set': {
                'profile.coachingEmailFrequency': 'EMAIL_ONCE_A_MONTH'
            },
            '$unset': {
                'sendCoachingEmailAfter': 1
            },
        })

        # A week later, there should be no email.
        for unused_day in range(7):
            self.mock_now.return_value += datetime.timedelta(days=1)
            focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())
        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(1, len(user_data.get('emailsSent', [])))

        # A month later, there should be another email.
        for unused_data in range(30):
            self.mock_now.return_value += datetime.timedelta(days=1)
            focus.main(['send', '--disable-sentry'])

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])
        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(2, len(user_data.get('emailsSent', [])))
        self.assertIn(user_data['emailsSent'][1]['campaignId'],
                      _GOLDEN_FOCUS_CAMPAIGNS)

    def test_dont_send_to_deleted(self) -> None:
        """Do not send focus emails to deleted users."""

        self._db.user_test.user.update_one({}, {
            '$set': {
                'profile.email': 'REDACTED',
                'deletedAt': '2018-06-01T15:24:34Z',
            }
        })

        focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())

    def test_dont_send_to_mistyped_emails(self) -> None:
        """Do not send focus emails to users with an incorrect email address."""

        self._db.user_test.user.update_one(
            {}, {'$set': {
                'profile.email': 'pascal@ corpet.net',
            }})

        focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())

        self._db.user_test.user.update_one(
            {}, {'$set': {
                'profile.email': 'pascal@corpet',
            }})

        focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())

    @mock.patch('logging.warning')
    def test_dont_send_to_example(self, mock_warning: mock.MagicMock) -> None:
        """Do not send focus emails to users with an example email address."""

        self._db.user_test.user.update_one(
            {}, {'$set': {
                'profile.email': '*****@*****.**',
            }})

        focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())
        mock_warning.assert_not_called()

    def test_restrict_campaign(self) -> None:
        """Restrict to only one campaign."""

        self._db.test.focus_emails.drop()
        self._db.test.focus_emails.insert_one({'campaignId': 'galita-2'})

        focus.main(['send', '--disable-sentry'])
        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual('galita-2', user_data['emailsSent'][0]['campaignId'])

    @mock.patch('bob_emploi.frontend.server.mail.mail_send.send_template')
    @mock.patch('logging.warning')
    def test_error_while_sending(self, mock_warning: mock.MagicMock,
                                 mock_send_template: mock.MagicMock) -> None:
        """Error when sending a focus email get caught and logged as warning."""

        mock_send_template(
        ).raise_for_status.side_effect = requests.exceptions.HTTPError

        focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())
        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertFalse(user_data.get('emailsSent'))

        mock_warning.assert_called_once()
        self.assertEqual('Error while sending an email: %s',
                         mock_warning.call_args[0][0])

    @mock.patch('bob_emploi.frontend.server.mail.mail_send.send_template')
    def test_error_while_dry_run(self,
                                 mock_send_template: mock.MagicMock) -> None:
        """Error when sending a focus email in dry run mode."""

        mock_send_template(
        ).raise_for_status.side_effect = requests.exceptions.HTTPError

        with self.assertRaises(requests.exceptions.HTTPError):
            focus.main(['dry-run', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())
        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertFalse(user_data.get('emailsSent'))

    def test_error_no_secret_salt(self) -> None:
        """Error when trying to send without a secret salt."""

        with mock.patch(focus.auth_token.__name__ + '.SECRET_SALT',
                        new=focus.auth_token.FAKE_SECRET_SALT):
            with self.assertRaises(ValueError):
                focus.main(['send', '--disable-sentry'])

    @mock.patch(focus.report.__name__ + '._setup_sentry_logging')
    @mock.patch.dict(os.environ, {'SENTRY_DSN': 'fake-sentry'})
    def test_setup_report(self, mock_setup_sentry: mock.MagicMock) -> None:
        """Make sure the report is setup."""

        focus.main(['send'])

        mock_setup_sentry.assert_called_once_with('fake-sentry')
        self.assertTrue(mailjetmock.get_all_sent_messages())

    @mock.patch('logging.error')
    def test_failed_setup_report(self, mock_error: mock.MagicMock) -> None:
        """Warn if the report is not correctly setup."""

        focus.main(['send'])

        mock_error.assert_called_once_with(
            'Please set SENTRY_DSN to enable logging to Sentry, or use --disable-sentry option'
        )
        self.assertFalse(mailjetmock.get_all_sent_messages())

    def test_ghost_mode(self) -> None:
        """Test the ghost mode."""

        user = user_pb2.User()
        user.profile.coaching_email_frequency = email_pb2.EMAIL_ONCE_A_MONTH
        user.profile.frustrations.append(user_profile_pb2.SELF_CONFIDENCE)
        user.projects.add()

        campaign_id = focus.send_focus_email_to_user(
            'ghost',
            user,
            database=focus.mongo.NoPiiMongoDatabase(self._db.test),
            instant=datetime.datetime.now())

        self.assertIn(campaign_id, _GOLDEN_FOCUS_CAMPAIGNS)
        self.assertEqual([campaign_id],
                         [e.campaign_id for e in user.emails_sent])
        self.assertGreater(user.send_coaching_email_after.ToDatetime(),
                           datetime.datetime.now())

        self.assertFalse(mailjetmock.get_all_sent_messages())

    def test_ghost_email_none(self) -> None:
        """Test the ghost mode for users that don't want any emails."""

        user = user_pb2.User()
        user.profile.coaching_email_frequency = email_pb2.EMAIL_NONE
        user.profile.frustrations.append(user_profile_pb2.SELF_CONFIDENCE)
        user.projects.add()

        campaign_id = focus.send_focus_email_to_user(
            'ghost',
            user,
            database=focus.mongo.NoPiiMongoDatabase(self._db.test),
            instant=datetime.datetime.now())

        self.assertFalse(campaign_id)
        self.assertFalse(mailjetmock.get_all_sent_messages())

    @mock.patch(
        focus.__name__ + '._FOCUS_CAMPAIGNS', {
            'coaching-campaign':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'coaching-campaign'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_coaching=True,
            ),
        })
    @mock.patch.dict(mailjet_templates.MAP, {
        'coaching-campaign': {
            'mailjetTemplate': 0
        },
    })
    @mock.patch(campaign.__name__ + '.get_campaign_subject',
                lambda campaign_id: {
                    'coaching-campaign': 'Campagne de coaching',
                }[campaign_id])
    def test_focus_with_project_score_zero(self) -> None:
        """Test no email sent if project score is 0"""

        self._db.test.focus_emails.drop()
        self._db.test.focus_emails.insert_many([
            {
                'campaignId': 'post-covid',
                'scoringModel': 'constant(0)'
            },
        ])

        mailjetmock.clear_sent_messages()

        user = user_pb2.User()
        user.profile.coaching_email_frequency = email_pb2.EMAIL_ONCE_A_MONTH
        user.profile.frustrations.append(user_profile_pb2.SELF_CONFIDENCE)
        user.projects.add()

        focus.main(['send', '--disable-sentry'])

        self.assertFalse(mailjetmock.get_all_sent_messages())

    @mock.patch(
        focus.__name__ + '._FOCUS_CAMPAIGNS', {
            'post-covid':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'post-covid'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=False,
            ),
        })
    def test_focus_with_no_scoring_model(self) -> None:
        """Test email sent if there is no scoring model"""

        self._db.test.focus_emails.drop()
        self._db.test.focus_emails.insert_many([
            {
                'campaignId': 'post-covid'
            },
        ])

        mailjetmock.clear_sent_messages()

        user = user_pb2.User()
        user.profile.coaching_email_frequency = email_pb2.EMAIL_ONCE_A_MONTH
        user.profile.frustrations.append(user_profile_pb2.SELF_CONFIDENCE)
        user.projects.add()

        focus.main(['send', '--disable-sentry'])

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(1, len(user_data.get('emailsSent')))
        self.assertEqual(user_data['emailsSent'][0]['campaignId'],
                         'post-covid')

    @mock.patch(
        focus.__name__ + '._FOCUS_CAMPAIGNS', {
            'big-important':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'big-important'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=True,
            ),
            'small-very-important':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'small-very-important'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=False,
            ),
            'big-no-priority':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'big-no-priority'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=True,
            ),
            'big-less-important':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'big-less-important'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=True,
            ),
        })
    @mock.patch.dict(
        mailjet_templates.MAP, {
            'big-important': {
                'mailjetTemplate': 0
            },
            'small-very-important': {
                'mailjetTemplate': 1
            },
            'big-no-priority': {
                'mailjetTemplate': 2
            },
            'big-less-important': {
                'mailjetTemplate': 3
            },
        })
    @mock.patch(
        campaign.__name__ + '.get_campaign_subject', lambda campaign_id: {
            'big-important': 'Un mail gros et important',
            'small-very-important': 'Un mail petit et très important',
            'big-no-priority': 'Un mail gros et pas important',
            'big-less-important': 'Un mail gros et moins important',
        }[campaign_id])
    @mock.patch('random.random')
    def test_send_priority(self, mock_random_random: mock.MagicMock) -> None:
        """Send priority focus emails first."""

        # Avoid random influence in the order calculation
        mock_random_random.return_value = 0
        self._db.test.focus_emails.drop()
        self._db.test.focus_emails.insert_many([
            {
                'campaignId': 'big-important',
                'scoringModel': 'constant(2.5)'
            },
            {
                'campaignId': 'small-very-important',
                'scoringModel': 'constant(3)'
            },
            {
                'campaignId': 'big-no-priority'
            },
            {
                'campaignId': 'big-less-important',
                'scoringModel': 'constant(1)'
            },
        ])

        focus.main([
            'send',
            '--disable-sentry',
            '--restrict-campaigns',
            'big-important',
            'small-very-important',
            'big-no-priority',
            'big-less-important',
        ])

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(1, len(user_data.get('emailsSent')))
        self.assertEqual('big-important',
                         user_data['emailsSent'][0]['campaignId'])

    @mock.patch(
        focus.__name__ + '._FOCUS_CAMPAIGNS', {
            'first-one':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'first-one'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_coaching=True,
            ),
            'second-one':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'second-one'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_coaching=True,
            ),
            'third-one':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'third-one'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_coaching=True,
            ),
        })
    @mock.patch.dict(
        mailjet_templates.MAP, {
            'first-one': {
                'mailjetTemplate': 0
            },
            'second-one': {
                'mailjetTemplate': 0
            },
            'third-one': {
                'mailjetTemplate': 0
            },
        })
    @mock.patch(
        campaign.__name__ + '.get_campaign_subject', lambda campaign_id: {
            'first-one': 'Un premier email',
            'second-one': 'Un deuxième email',
            'third-one': 'Un troisième email',
        }[campaign_id])
    @mock.patch(focus.report.__name__ + '.notify_slack')
    def test_slack(self, mock_notify_slack: mock.MagicMock) -> None:
        """Send message to slack."""

        self._db.test.focus_emails.drop()
        self._db.test.focus_emails.insert_many([
            {
                'campaignId': 'first-one',
                'scoringModel': 'constant(3)'
            },
            {
                'campaignId': 'third-one',
                'scoringModel': 'constant(.1)'
            },
        ])

        # Note that random will not be flaky:
        #  the diff score is (3 - .1) / 3 * _SCORES_WEIGHT = 5 * 2.9 / 3
        #  the max random diff is _RANDOM_WEIGHTS = 4
        # So the difference in score will always be bigger than the random diff and thus first-one
        # will always be selected.
        focus.main([
            'send',
            '--disable-sentry',
        ])

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])

        mock_notify_slack.assert_called_once_with(
            textwrap.dedent('''\
            Focus emails sent today:
             • *first-one*: 1 email
             • *third-one*: 0 email'''))

    @mock.patch(
        focus.__name__ + '._FOCUS_CAMPAIGNS', {
            'just-big':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'just-big'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=True,
            ),
            'small-very-important':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'small-very-important'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=False,
            ),
        })
    @mock.patch.dict(
        mailjet_templates.MAP, {
            'just-big': {
                'mailjetTemplate': 0
            },
            'small-very-important': {
                'mailjetTemplate': 1
            },
        })
    @mock.patch(
        campaign.__name__ + '.get_campaign_subject', lambda campaign_id: {
            'just-big': 'Un mail gros',
            'small-very-important': 'Un mail petit et très important',
        }[campaign_id])
    @mock.patch('random.random')
    def test_send_shuffle_random(self,
                                 mock_random_random: mock.MagicMock) -> None:
        """Test random in shuffle."""

        mock_random_random.side_effect = real_random
        self._db.test.focus_emails.drop()
        self._db.test.focus_emails.insert_many([
            {
                'campaignId': 'just-big',
                'scoringModel': 'constant(0.5)'
            },
            {
                'campaignId': 'small-very-important',
                'scoringModel': 'constant(3)'
            },
        ])

        focus.main([
            'send',
            '--disable-sentry',
            '--restrict-campaigns',
            'just-big',
            'small-very-important',
        ])

        mock_random_random.assert_called()

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(1, len(user_data.get('emailsSent')))
        self.assertIn(user_data['emailsSent'][0]['campaignId'],
                      {'just-big', 'small-very-important'})

    @mock.patch(
        focus.__name__ + '._FOCUS_CAMPAIGNS', {
            'just-big':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'just-big'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=True,
            ),
            'small-very-important':
            campaign.Campaign(
                typing.cast(mailjet_templates.Id, 'small-very-important'),
                get_vars=lambda user, **unused_kwargs: {'key': 'value'},
                sender_name='Sender',
                sender_email='*****@*****.**',
                is_big_focus=False,
            ),
        })
    @mock.patch.dict(
        mailjet_templates.MAP, {
            'just-big': {
                'mailjetTemplate': 0
            },
            'small-very-important': {
                'mailjetTemplate': 1
            },
        })
    @mock.patch(
        campaign.__name__ + '.get_campaign_subject', lambda campaign_id: {
            'big-important': 'Un mail gros',
            'small-very-important': 'Un mail petit et très important',
        }[campaign_id])
    @mock.patch('random.random')
    def test_send_shuffle(self, mock_random_random: mock.MagicMock) -> None:
        """Send the mail with a better score first."""

        mock_random_random.return_value = 0

        self._db.test.focus_emails.drop()
        self._db.test.focus_emails.insert_many([
            {
                'campaignId': 'just-big',
                'scoringModel': 'constant(0.5)'
            },
            {
                'campaignId': 'small-very-important',
                'scoringModel': 'constant(3)'
            },
        ])

        focus.main([
            'send',
            '--disable-sentry',
            '--restrict-campaigns',
            'just-big',
            'small-very-important',
        ])

        self.assertEqual(['*****@*****.**'], [
            m.recipient['Email'] for m in mailjetmock.get_all_sent_messages()
        ])

        user_data = self._db.user_test.user.find_one()
        assert user_data
        self.assertEqual(1, len(user_data.get('emailsSent')))
        self.assertEqual('small-very-important',
                         user_data['emailsSent'][0]['campaignId'])
Esempio n. 8
0
        campaign.get_deep_link_advice(user.user_id, project, 'fresh-resume')

    return campaign.get_default_coaching_email_vars(user) | {
        'deepLinkAdviceUrl': deep_link_advice_url,
        'hasExperience': has_experience,
        'isSeptember': campaign.as_template_boolean(now.month == 9),
        'loginUrl': campaign.create_logged_url(user.user_id)
    }


campaign.register_campaign(
    campaign.Campaign(
        campaign_id='improve-cv',
        mongo_filters={
            'profile.frustrations': 'RESUME',
            'projects': {
                '$elemMatch': {
                    'isIncomplete': {
                        '$ne': True
                    },
                }
            },
        },
        get_vars=_get_improve_cv_vars,
        sender_name=i18n.make_translatable_string(
            "Joanna et l'équipe de {{var:productName}}"),
        sender_email='*****@*****.**',
        is_coaching=True,
        is_big_focus=True,
    ))
Esempio n. 9
0
        'productUrl':
        f'{login_url}?utm_source=bob-emploi&amp;amp;utm_medium=email',
    }


campaign.register_campaign(
    campaign.Campaign(
        campaign_id='get-diploma',
        mongo_filters={
            'projects': {
                '$elemMatch': {
                    'isIncomplete': {
                        '$ne': True
                    },
                    'openedStrategies.strategyId': 'get-diploma',
                }
            },
        },
        get_vars=_get_find_diploma_vars,
        sender_name=i18n.make_translatable_string(
            "Joanna et l'équipe de {{var:productName}}"),
        sender_email='*****@*****.**',
        is_coaching=True,
        is_big_focus=True,
    ))

campaign.register_campaign(
    campaign.Campaign(
        campaign_id='get-diploma-short',
        mongo_filters={},
        get_vars=_get_short_diploma_vars,
Esempio n. 10
0
        'inCity': in_city,
        'inTargetDomain': in_target_domain,
        'isAbleBodied': campaign.as_template_boolean(not user.profile.has_handicap),
        'isYoung': campaign.as_template_boolean(age <= max_young),
        'jobGroupInDepartement': f'{job_group_name} {in_departement}',
        'networkApplicationPercentage': network_application_importance,
    }


campaign.register_campaign(campaign.Campaign(
    campaign_id='focus-network',
    mongo_filters={
        'projects': {'$elemMatch': {
            'networkEstimate': 1,
            'isIncomplete': {'$ne': True},
        }},
    },
    get_vars=_get_network_vars,
    sender_name=i18n.make_translatable_string("Joanna et l'équipe de {{var:productName}}"),
    sender_email='*****@*****.**',
    is_coaching=True,
    is_big_focus=True,
))

campaign.register_campaign(campaign.Campaign(
    campaign_id='network-plus',
    mongo_filters={
        'projects': {'$elemMatch': {
            'networkEstimate': {'$gte': 2},
            'isIncomplete': {'$ne': True},
        }},
    },
Esempio n. 11
0
        goal = 'décrocher votre prochain emploi'

    return campaign.get_default_coaching_email_vars(user) | {
        'goal': goal,
        'numberUsers': '250\u00A0000',
        'lastYear': str(now.year - 1),
        'year': str(now.year),
    }


campaign.register_campaign(
    campaign.Campaign(
        campaign_id='christmas',
        mongo_filters={},
        get_vars=_christmas_vars,
        is_coaching=True,
        is_big_focus=True,
        sender_name=i18n.make_translatable_string(
            'Joanna de {{var:productName}}'),
        sender_email='*****@*****.**',
    ))
campaign.register_campaign(
    campaign.Campaign(
        campaign_id='new-year',
        mongo_filters={},
        get_vars=_new_year_vars,
        sender_name=i18n.make_translatable_string(
            'Joanna de {{var:productName}}'),
        sender_email='*****@*****.**',
    ))
Esempio n. 12
0
"""

import json
import os
import typing
from typing import Any

from bob_emploi.frontend.server import i18n
from bob_emploi.frontend.server.mail import campaign


def _get_mongo_filters() -> dict[str, Any]:
    """Get the mongo filters from env vars."""

    filters_as_string = os.getenv('RESEARCH_TARGET_USERS', '')
    if not filters_as_string:
        return {}

    return typing.cast(dict[str, Any], json.loads(filters_as_string))


campaign.register_campaign(
    campaign.Campaign(
        campaign_id='bob-research-recruit',
        get_mongo_filters=_get_mongo_filters,
        get_vars=campaign.get_default_vars,
        sender_name=i18n.make_translatable_string(
            "Tabitha et l'équipe de {{var:productName}}"),
        sender_email='*****@*****.**',
    ))
Esempio n. 13
0
    return campaign.get_default_coaching_email_vars(user)


# TODO(cyrille): Modularize.
_CAMPAIGNS = [
    campaign.Campaign(
        campaign_id='focus-self-develop',
        mongo_filters={
            'projects': {
                '$elemMatch': {
                    'jobSearchHasNotStarted': {
                        '$ne': True
                    },
                    'isIncomplete': {
                        '$ne': True
                    },
                }
            }
        },
        get_vars=_get_self_development_vars,
        sender_name=i18n.make_translatable_string(
            "Joanna et l'équipe de {{var:productName}}"),
        sender_email='*****@*****.**',
        is_coaching=True,
        is_big_focus=True,
    ),
    campaign.Campaign(
        campaign_id='focus-body-language',
        mongo_filters={
            'projects': {
                '$elemMatch': {
Esempio n. 14
0
def _get_switch_vars(user: user_pb2.User, *, now: datetime.datetime,
                     **unused_kwargs: Any) -> dict[str, str]:
    """Compute all variables required for the Switch campaign."""

    if now.year - user.profile.year_of_birth < 22:
        raise campaign.DoNotSend('User is too young')

    project = next((p for p in user.projects), project_pb2.Project())

    if project.seniority <= project_pb2.INTERMEDIARY:
        raise campaign.DoNotSend("User doesn't have enough experience")

    return campaign.get_default_coaching_email_vars(user) | {
        'isConverting':
        campaign.as_template_boolean(
            project.kind == project_pb2.REORIENTATION),
    }


campaign.register_campaign(
    campaign.Campaign(
        campaign_id='switch-grant',
        mongo_filters={},
        is_coaching=True,
        get_vars=_get_switch_vars,
        sender_name=i18n.make_translatable_string(
            'Joanna de {{var:productName}}'),
        sender_email='*****@*****.**',
    ))
Esempio n. 15
0
                        'gender': user_profile_pb2.Gender.Name(
                            user.profile.gender),
                        'mainChallenge': main_challenge_id,
                    }),
            }),
    }


_FFS_CAMPAIGN = campaign.Campaign(
    campaign_id=_CAMPAIGN_ID,
    mongo_filters={
        'emailsSent': {
            '$not': {
                '$elemMatch': {
                    'campaignId': _CAMPAIGN_ID
                }
            }
        },
        # Don't bug very old users, they wouldn't pass the date check anyway.
        'registeredAt': {
            '$gt': '2022-02-01'
        },
    },
    get_vars=_get_ffs_vars,
    sender_name=i18n.make_translatable_string(
        'Tabitha de {{var:productName}}'),
    sender_email='*****@*****.**',
)

campaign.register_campaign(_FFS_CAMPAIGN)
Esempio n. 16
0
campaign.register_campaign(
    campaign.Campaign(
        campaign_id=_CAMPAIGN_ID,
        mongo_filters={
            'projects': {
                '$elemMatch': {
                    'jobSearchHasNotStarted': {
                        '$ne': True
                    },
                    'isIncomplete': {
                        '$ne': True
                    },
                    'actions.0': {
                        '$exists': True
                    },
                }
            },
            'emailsSent': {
                '$not': {
                    '$elemMatch': {
                        'campaignId': _CAMPAIGN_ID
                    }
                }
            },
        },
        get_vars=_get_vars,
        sender_name=i18n.make_translatable_string(
            "{{var:firstTeamMember}} et l'équipe de {{var:productName}}"),
        sender_email='*****@*****.**',
    ))
Esempio n. 17
0
from bob_emploi.frontend.server import proto
from bob_emploi.frontend.server.mail import campaign
from bob_emploi.frontend.server.mail import mail_send

_TWO_YEARS_AGO_STRING = \
    proto.datetime_to_json_string(datetime.datetime.now() - datetime.timedelta(730))


def _account_deletion_notice_vars(user: user_pb2.User, **unused_kwargs: Any) -> dict[str, str]:
    return dict(
        campaign.get_default_vars(user),
        loginUrl=campaign.create_logged_url(user.user_id))


campaign.register_campaign(campaign.Campaign(
    campaign_id='account-deletion-notice',
    mongo_filters={
        # User hasn't been on Bob for two years.
        'registeredAt': {'$lt': _TWO_YEARS_AGO_STRING},
        'requestedByUserAtDate': {'$not': {'$gt': _TWO_YEARS_AGO_STRING}},
        # User hasn't read any email we sent to them in the last two years.
        'emailsSent': {'$not': {'$elemMatch': {
            'sentAt': {'$gt': _TWO_YEARS_AGO_STRING},
            'status': {'$in': list(mail_send.READ_EMAIL_STATUS_STRINGS)},
        }}},
    },
    get_vars=_account_deletion_notice_vars,
    sender_name=i18n.make_translatable_string("Joanna et l'équipe de {{var:productName}}"),
    sender_email='*****@*****.**',
))
Esempio n. 18
0
    return campaign.get_default_coaching_email_vars(user) | {
        'applicationModes': _make_section(application_modes_section),
        'departements': _make_section(departements_section),
        'employmentType': _make_section(employment_types_section),
        'imtLink': imt_link,
        'inCity': scoring_project.populate_template('%inCity'),
        'jobNameInDepartement': job_name_in_departement,
        'loginUrl': campaign.create_logged_url(user.user_id),
        'marketStress': _make_section(market_stress_section),
        'months': _make_section(months_section),
        'ofJobNameInDepartement': of_job_name_in_departement,
        'ofJobName': of_job_name,
    }


campaign.register_campaign(campaign.Campaign(
    campaign_id='imt',
    mongo_filters={
        'projects': {
            '$elemMatch': {
                'isIncomplete': {'$ne': True},
            },
        },
    },
    get_vars=_get_imt_vars,
    sender_name=i18n.make_translatable_string("Pascal et l'équipe de {{var:productName}}"),
    sender_email='*****@*****.**',
    is_coaching=True,
    is_big_focus=True,
))