def test_advice_job_boards_extra_data(self): """Test that the advisor computes extra data for the "Find a Job Board" advice.""" project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup( rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.jobboards.insert_one({ 'title': 'Indeed', 'filters': ['for-departement(14)'] }) self.database.advice_modules.insert_one({ 'adviceId': 'job-boards', 'triggerScoringModel': 'advice-job-boards', 'extraDataFieldName': 'job_boards_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'job-boards') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('Indeed', advice.job_boards_data.job_board_title) self.assertFalse(advice.job_boards_data.is_specific_to_job_group) self.assertTrue(advice.job_boards_data.is_specific_to_region)
def test_categorize_advice_missing(self): """Test that the advisor does not choke on an ancient advice that does not exist anymore.""" project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), advices=[ project_pb2.Advice(advice_id='spontaneous-application', status=project_pb2.ADVICE_RECOMMENDED, num_stars=2), project_pb2.Advice(advice_id='advice-does-no-exist-anymore', status=project_pb2.ADVICE_RECOMMENDED, num_stars=2), ]) self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'categories': ['first', 'second'], 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, ]) advisor.maybe_categorize_advice(self.user, project, self.database) # Point check that some categorization happened. self.assertEqual(['spontaneous-application'], project.advice_categories[0].advice_ids)
def test_advice_other_work_env_extra_data(self): """Test that the advisor computes extra data for the work environment advice.""" project = project_pb2.Project(target_job=job_pb2.Job( job_group=job_pb2.JobGroup(rome_id='A1234')), ) self.user.features_enabled.alpha = True self.database.job_group_info.insert_one({ '_id': 'A1234', 'workEnvironmentKeywords': { 'structures': ['A', 'B'], 'sectors': ['sector Toise', 'sector Gal'], }, }) self.database.advice_modules.insert_one({ 'adviceId': 'other-work-env', 'triggerScoringModel': 'advice-other-work-env', 'extraDataFieldName': 'other_work_env_advice_data', 'isReadyForProd': True, }) advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'other-work-env') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual(['A', 'B'], advice.other_work_env_advice_data. work_environment_keywords.structures) self.assertEqual([ 'sector Toise', 'sector Gal' ], advice.other_work_env_advice_data.work_environment_keywords.sectors)
def test_advice_spontaneous_application_extra_data(self, mock_get_lbb_companies): """Test that the advisor computes extra data for the "Spontaneous Application" advice.""" project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup( rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.job_group_info.insert_one({ '_id': 'A1234', 'applicationModes': { 'R4Z92': { 'modes': [{ 'percentage': 36.38, 'mode': 'SPONTANEOUS_APPLICATION' }, { 'percentage': 29.46, 'mode': 'PERSONAL_OR_PROFESSIONAL_CONTACTS' }, { 'percentage': 18.38, 'mode': 'PLACEMENT_AGENCY' }, { 'percentage': 15.78, 'mode': 'UNDEFINED_APPLICATION_MODE' }], } }, }) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'categories': ['first'], 'triggerScoringModel': 'advice-spontaneous-application', 'extraDataFieldName': 'spontaneous_application_data', 'isReadyForProd': True, }) mock_get_lbb_companies.return_value = iter([ { 'name': 'EX NIHILO' }, { 'name': 'M.F.P MULTIMEDIA FRANCE PRODUCTIONS' }, ]) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual( ['EX NIHILO', 'M.F.P MULTIMEDIA FRANCE PRODUCTIONS'], [c.name for c in advice.spontaneous_application_data.companies])
def compute_extra_data(self, project): """Compute extra data for this module to render a card in the client.""" # The number of job names is restricted to 2 because some job names # are very long e.g. Conducteur / Conductrice d'engins d'exploitation. recommended_jobs = self.get_local_jobbing( project).reorient_jobbing_jobs[:2] return project_pb2.ReorientData( jobs=[job_pb2.Job(name=job.name) for job in recommended_jobs])
def test_advice_better_job_in_group_extra_data(self): """Test that the advisor computes extra data for the "Better Job in Group" advice.""" project = project_pb2.Project( target_job=job_pb2.Job( code_ogr='1234', job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.job_group_info.insert_one({ '_id': 'A1234', 'jobs': [ { 'codeOgr': '1234', 'name': 'Pilote' }, { 'codeOgr': '5678', 'name': 'Pompier' }, { 'codeOgr': '9012', 'name': 'Facteur' }, ], 'requirements': { 'specificJobs': [ { 'codeOgr': '5678', 'percentSuggested': 55, }, { 'codeOgr': '1234', 'percentSuggested': 45, }, ], }, }) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-better-job-in-group', 'extraDataFieldName': 'better_job_in_group_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('Pompier', advice.better_job_in_group_data.better_job.name) self.assertEqual(1, advice.better_job_in_group_data.num_better_jobs)
def test_advice_specific_to_job_override(self) -> None: """Test that the advisor overrides some advice data with the "Specific to Job" module.""" project = project_pb2.Project(target_job=job_pb2.Job( job_group=job_pb2.JobGroup(rome_id='D1102')), ) self.database.advice_modules.insert_one({ 'adviceId': 'custom-advice-id', 'categories': ['first'], 'triggerScoringModel': 'advice-specific-to-job', 'isReadyForProd': True, }) self.database.specific_to_job_advice.insert_one({ 'title': 'Présentez-vous au chef boulanger dès son arrivée tôt le matin', 'shortTitle': 'Astuces de boulanger', 'goal': 'impressionner le patron', 'diagnosticTopics': [ diagnostic_pb2.MARKET_DIAGNOSTIC, diagnostic_pb2.PROJECT_DIAGNOSTIC ], 'filters': ['for-job-group(D1102)', 'not-for-job(12006)'], 'cardText': 'Allez à la boulangerie la veille pour savoir à quelle ' 'heure arrive le chef boulanger.', 'expandedCardHeader': "Voilà ce qu'il faut faire", 'expandedCardItems': [ 'Se présenter aux boulangers entre 4h et 7h du matin.', 'Demander au vendeur / à la vendeuse à quelle heure arrive le chef le matin', 'Contacter les fournisseurs de farine locaux : ils connaissent ' 'tous les boulangers du coin et sauront où il y a des ' 'embauches.', ], }) advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'custom-advice-id') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual( 'Présentez-vous au chef boulanger dès son arrivée tôt le matin', advice.title) self.assertEqual("Voilà ce qu'il faut faire", advice.expanded_card_header) self.assertTrue(advice.card_text) self.assertTrue(advice.expanded_card_items) self.assertEqual('Astuces de boulanger', advice.short_title) self.assertEqual([ diagnostic_pb2.MARKET_DIAGNOSTIC, diagnostic_pb2.PROJECT_DIAGNOSTIC ], advice.diagnostic_topics) self.assertEqual('impressionner le patron', advice.goal)
def test_advice_spontaneous_application_extra_data( self, mock_get_lbb_companies: mock.MagicMock) -> None: """Test that the advisor computes extra data for the "Spontaneous Application" advice.""" persona = self._random_persona().clone() persona.project.CopyFrom( project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup( rome_id='A1234')), weekly_applications_estimate=project_pb2.A_LOT, employment_types=[job_pb2.CDI], total_interview_count=1, )) persona.project.job_search_started_at.FromDatetime( persona.project.created_at.ToDatetime() - datetime.timedelta(days=210)) persona.project.city.departement_id = '14' self.database.job_group_info.insert_one({ '_id': 'A1234', 'applicationModes': { 'R4Z92': { 'modes': [{ 'percentage': 36.38, 'mode': 'SPONTANEOUS_APPLICATION' }, { 'percentage': 29.46, 'mode': 'PERSONAL_OR_PROFESSIONAL_CONTACTS' }, { 'percentage': 18.38, 'mode': 'PLACEMENT_AGENCY' }, { 'percentage': 15.78, 'mode': 'UNDEFINED_APPLICATION_MODE' }], } }, }) mock_get_lbb_companies.return_value = iter([ { 'name': 'EX NIHILO' }, { 'name': 'M.F.P MULTIMEDIA FRANCE PRODUCTIONS' }, ]) extra_data = typing.cast(project_pb2.SpontaneousApplicationData, self._compute_expanded_card_data(persona)) self.assertEqual(['EX NIHILO', 'M.F.P MULTIMEDIA FRANCE PRODUCTIONS'], [c.name for c in extra_data.companies]) self.assertEqual(10, extra_data.max_distance_to_companies_km)
def test_categorize_all_pieces_of_advice(self): """Test that the advisor categorize all advice modules.""" project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), advices=[ project_pb2.Advice(advice_id='spontaneous-application', status=project_pb2.ADVICE_RECOMMENDED, num_stars=3), project_pb2.Advice(advice_id='other-work-env', status=project_pb2.ADVICE_RECOMMENDED, num_stars=2), project_pb2.Advice(advice_id='one-star', status=project_pb2.ADVICE_RECOMMENDED, num_stars=1), ]) self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'categories': ['first', 'second'], 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'categories': ['second'], 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, { 'adviceId': 'one-star', 'categories': ['second'], 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) advisor.maybe_categorize_advice(self.user, project, self.database) # First category is the category with all the 3-stars advice. self.assertEqual(['spontaneous-application'], project.advice_categories[0].advice_ids) self.assertEqual('three-stars', project.advice_categories[0].category_id) self.assertEqual( ['other-work-env', 'spontaneous-application', 'one-star'], project.advice_categories[1].advice_ids) self.assertEqual('second', project.advice_categories[1].category_id)
def compute_extra_data(self, project): """Compute extra data for this module to render a card in the client.""" # The number of job names is restricted to 2. all_close_jobs = self.get_close_jobs(project) recommended_jobs = itertools.islice( itertools.chain(all_close_jobs.close_jobs, all_close_jobs.evolution_jobs), 2) return project_pb2.ReorientData(jobs=[ job_pb2.Job( name=french.genderize_job(job, project.user_profile.gender)) for job in recommended_jobs ])
def find_job(self, job_title: str) -> Optional[job_pb2.Job]: """Find a job in Algolia index by its name.""" try: return job_pb2.Job( name=job_title, job_group=job_pb2.JobGroup(rome_id=self._mapping[job_title])) except KeyError: logging.exception('Could not find job "%s" in Excel file', job_title) self._missed.add(job_title) return None
def test_generate_actions(self) -> None: """Test that the advisor generates actions if the action_plan feature is active.""" self.database.action_templates.insert_many([ { 'actionTemplateId': 'review-me', 'triggerScoringModel': 'constant(1)', 'tags': ['chrome-tool'], 'duration': 'FIFTEEN_TO_30_MIN', 'resourceUrl': 'https://bar', }, { 'actionTemplateId': 'not-for-you', 'triggerScoringModel': 'constant(0)', }, { 'actionTemplateId': 'finish-the-sprint', 'triggerScoringModel': 'constant(2)', }, ]) self.database.translations.insert_many([ {'string': 'actionTemplates:review-me:title', 'en': 'Review me!'}, {'string': 'actionTemplates:review-me:short_description', 'en': 'Please review me now'}, { 'string': 'actionTemplates:review-me:short_description_FEMININE', 'en': 'Please review me now, Madam', }, {'string': 'actionTemplates:tags:chrome-tool', 'en': 'Chrome Tool'}, {'string': 'actionTemplates:review-me:resource_url_usa', 'en': 'https://foo'}, ]) self.user.features_enabled.action_plan = features_pb2.ACTIVE self.user.profile.locale = 'en' project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), ) advisor.maybe_advise(self.user, project, self.database) self.assertEqual(['finish-the-sprint', 'review-me'], [a.action_id for a in project.actions]) review_me = project.actions[1] self.assertEqual('Review me!', review_me.title) self.assertEqual('Please review me now, Madam', review_me.short_description) self.assertEqual(['Chrome Tool'], review_me.tags) self.assertEqual(action_pb2.FIFTEEN_TO_30_MIN, review_me.duration) self.assertEqual('https://foo', review_me.resource_url)
def test_advice_improve_success_rate_extra_data(self): """Test that the advisor computes extra data for the "Improve Success Rate" advice.""" project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup( rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=6, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.local_diagnosis.insert_one({ '_id': '14:A1234', 'imt': { 'yearlyAvgOffersDenominator': 10, 'yearlyAvgOffersPer10Candidates': 2, }, }) self.database.job_group_info.insert_one({ '_id': 'A1234', 'requirements': { 'skills': [{ 'name': 'Humour' }, { 'name': 'Empathie' }], 'skillsShortText': '**Humour** et **empathie**', }, }) self.database.advice_modules.insert_one({ 'adviceId': 'improve-success', 'triggerScoringModel': 'advice-improve-resume', 'extraDataFieldName': 'improve_success_rate_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'improve-success') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertGreater( advice.improve_success_rate_data.num_interviews_increase, 50) self.assertFalse(advice.improve_success_rate_data.requirements.skills) self.assertEqual( '**Humour** et **empathie**', advice.improve_success_rate_data.requirements.skills_short_text)
def test_find_all_pieces_of_advice(self, mock_now: mock.MagicMock) -> None: """Test that the advisor scores all advice modules.""" mock_now.return_value = datetime.datetime(2018, 6, 10) project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), actions=[ action_pb2.Action( action_id='be-happy', title='Be happy', status=action_pb2.ACTION_UNREAD, ), action_pb2.Action( action_id='dont-worry', title="Don't worry", status=action_pb2.ACTION_CURRENT, ), action_pb2.Action( action_id='sing-along', title='Sing along', status=action_pb2.ACTION_UNREAD, ), ], ) self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'categories': ['first'], 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'categories': ['first'], 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) advisor.maybe_advise(self.user, project, self.database) self.assertEqual(['spontaneous-application'], [a.advice_id for a in project.advices]) self.assertEqual(project_pb2.ADVICE_RECOMMENDED, project.advices[0].status)
def get_job_proto(database, job_id, rome_id): """Get a Job proto corresponding to the job ID if it is found in the ROME job group.""" job_group = get_group_proto(database, rome_id) if not job_group or not job_id: return None for job_proto in job_group.jobs: if job_proto.code_ogr == job_id: job = job_pb2.Job() job.CopyFrom(job_proto) job.job_group.rome_id = job_group.rome_id job.job_group.name = job_group.name return job return None
def test_advice_events_extra_data(self, mock_now): """Test that the advisor computes extra data for the "Events" advice.""" mock_now.return_value = datetime.datetime(2017, 8, 15) project = project_pb2.Project( target_job=job_pb2.Job( code_ogr='1234', job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='75')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-event', 'extraDataFieldName': 'events_data', 'isReadyForProd': True, }) self.database.events.insert_many([ { 'title': 'AP HEROS CANDIDATS MADIRCOM - BORDEAUX', 'link': 'https://www.workuper.com/events/ap-heros-candidats-madircom-bordeaux', 'organiser': 'MADIRCOM', 'startDate': '2017-08-29', }, { 'title': 'Le Salon du Travail et de la Mobilité Professionnelle', 'link': 'https://www.workuper.com/events/le-salon-du-travail-et-de-la-mobilite-' 'professionnelle', 'organiser': 'Altice Media Events', 'startDate': '2018-01-19', }, ]) advisor.clear_cache() self.user.features_enabled.alpha = True advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('AP HEROS CANDIDATS MADIRCOM - BORDEAUX', advice.events_data.event_name)
def test_find_all_pieces_of_advice(self, mock_send_template): """Test that the advisor scores all advice modules.""" mock_send_template().status_code = 200 mock_send_template.reset_mock() project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), ) self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'categories': ['first'], 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'categories': ['first'], 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) advisor.maybe_advise(self.user, project, self.database, 'http://base.example.com') self.assertEqual(['spontaneous-application'], [a.advice_id for a in project.advices]) self.assertEqual(project_pb2.ADVICE_RECOMMENDED, project.advices[0].status) mock_send_template.assert_called_once() data = mock_send_template.call_args[0][2] self.assertEqual( ['advices', 'baseUrl', 'firstName', 'ofProjectTitle', 'projectId'], sorted(data.keys())) self.assertEqual('http://base.example.com', data['baseUrl']) self.assertEqual('Margaux', data['firstName']) self.assertEqual("d'hôtesse", data['ofProjectTitle']) self.assertEqual('1234', data['projectId'])
def test_jobgroup_info_fr_locale(self) -> None: """A scoring project can be represented as a meaningful string.""" user = user_pb2.User() user.profile.locale = '' database = mongomock.MongoClient().test database.job_group_info.insert_many([{ '_id': 'A1234', 'name': 'french' }, { '_id': 'nl:A1234', 'name': 'dutch' }]) project = project_pb2.Project(target_job=job_pb2.Job( job_group=job_pb2.JobGroup(rome_id='A1234'))) scoring_project = scoring.ScoringProject(project, user, database) job_group_info = scoring_project.job_group_info() self.assertEqual('french', job_group_info.name)
def test_avoid_double_first_advice(self): """The advisor avoids having the same first advice for two neighbor categories.""" project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), advices=[ project_pb2.Advice(advice_id='spontaneous-application', status=project_pb2.ADVICE_RECOMMENDED, num_stars=2), project_pb2.Advice(advice_id='other-work-env', status=project_pb2.ADVICE_RECOMMENDED, num_stars=1) ]) self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'categories': ['first', 'second', 'third'], 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'categories': ['first', 'second', 'third'], 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) advisor.maybe_categorize_advice(self.user, project, self.database) # First category is the category with all the 3-stars advice. self.assertEqual(3, len(project.advice_categories), msg=project.advice_categories) self.assertEqual(['spontaneous-application', 'other-work-env'], project.advice_categories[0].advice_ids) self.assertEqual(['other-work-env', 'spontaneous-application'], project.advice_categories[1].advice_ids) self.assertEqual(['spontaneous-application', 'other-work-env'], project.advice_categories[2].advice_ids)
def test_advice_specific_to_job_override_i18n(self) -> None: """Test that the advisor translate overrides with the "Specific to Job" module.""" self.user.profile.locale = 'en' self.database.translations.insert_many([ {'string': 'specificToJobAdvice:baker:title', 'en': 'Get there early'}, {'string': 'Astuces de boulanger', 'en': 'Baker tips'}, ]) project = project_pb2.Project( target_job=job_pb2.Job(job_group=job_pb2.JobGroup(rome_id='D1102')), ) self.database.advice_modules.insert_one({ 'adviceId': 'custom-advice-id', 'categories': ['first'], 'triggerScoringModel': 'advice-specific-to-job', 'isReadyForProd': True, }) self.database.specific_to_job_advice.insert_one({ 'id': 'baker', 'title': 'Présentez-vous au chef boulanger dès son arrivée tôt le matin', 'shortTitle': 'Astuces de boulanger', 'goal': 'impressionner le patron', 'filters': ['for-job-group(D1102)', 'not-for-job(12006)'], 'cardText': 'Allez à la boulangerie la veille pour savoir à quelle ' 'heure arrive le chef boulanger.', 'expandedCardHeader': "Voilà ce qu'il faut faire", 'expandedCardItems': [ 'Se présenter aux boulangers entre 4h et 7h du matin.', 'Demander au vendeur / à la vendeuse à quelle heure arrive le chef le matin', 'Contacter les fournisseurs de farine locaux : ils connaissent ' 'tous les boulangers du coin et sauront où il y a des ' 'embauches.', ], }) advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'custom-advice-id') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('Get there early', advice.title) self.assertEqual('Baker tips', advice.short_title)
def test_advice_volunteer_extra_data(self): """Test that the advisor computes extra data for the "Volunteer" advice.""" project = project_pb2.Project( target_job=job_pb2.Job( code_ogr='1234', job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='75')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.volunteering_missions.insert_one({ '_id': '75', 'missions': [ { 'associationName': 'BackUp Rural' }, { 'associationName': 'Construisons Ensemble Comment Faire' }, ], }) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-volunteer', 'extraDataFieldName': 'volunteer_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual( ['BackUp Rural', 'Construisons Ensemble Comment Faire'], sorted(advice.volunteer_data.association_names))
def test_redacted_email_address(self) -> None: """Test that we do not send any email if the email address is already redacted.""" project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), ) self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'categories': ['first'], 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'categories': ['first'], 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) self.user.profile.email = 'REDACTED' advisor.maybe_advise(self.user, project, self.database, 'http://base.example.com') self.assertEqual(['spontaneous-application'], [a.advice_id for a in project.advices]) self.assertEqual(project_pb2.ADVICE_RECOMMENDED, project.advices[0].status) mails_sent = mailjetmock.get_all_sent_messages() self.assertFalse(mails_sent)
def test_advice_association_help_extra_data(self): """Test that the advisor computes extra data for the "Find an association" advice.""" project = project_pb2.Project( target_job=job_pb2.Job( code_ogr='1234', job_group=job_pb2.JobGroup(rome_id='A1234')), mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( departement_id='14')), job_search_length_months=7, weekly_applications_estimate=project_pb2.A_LOT, total_interview_count=1, ) self.database.associations.insert_many([ { 'name': 'Pôle emploi' }, { 'name': 'SNC', 'filters': ['for-departement(14,15,16)'] }, { 'name': 'Ressort', 'filters': ['for-departement(69)'] }, ]) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'advice-association-help', 'extraDataFieldName': 'associations_data', 'isReadyForProd': True, }) advisor.clear_cache() advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'my-advice') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual('SNC', advice.associations_data.association_name)
def _find_job(job_title: str) -> Optional[job_pb2.Job]: if job_group := _JOB_MAP.get(job_title): return job_pb2.Job(name=job_title, job_group=job_group)
job_pb2.JobGroup( name='Health Informatics Specialists', rome_id='15-1211', ), } @functools.lru_cache() def _find_job(job_title: str) -> Optional[job_pb2.Job]: if job_group := _JOB_MAP.get(job_title): return job_pb2.Job(name=job_title, job_group=job_group) for finder in _JOB_FINDERS: job = finder(job_title) if job: return job return job_pb2.Job(name=job_title) @functools.lru_cache() def _find_state(state_name: str) -> geo_pb2.FrenchCity: return geo_pb2.FrenchCity(region_id=STATE_MAP[state_name], region_name=state_name) class _AlgoliaJobFinder: def __init__(self) -> None: client = search_client.SearchClient.create('K6ACI9BKKT') self._index = client.init_index('jobs_en') self._missed: set[str] = set() def find_job(self, job_title: str) -> Optional[job_pb2.Job]:
def test_find_all_pieces_of_advice(self, mock_now: mock.MagicMock) -> None: """Test that the advisor scores all advice modules.""" mock_now.return_value = datetime.datetime(2018, 6, 10) project = project_pb2.Project( project_id='1234', target_job=job_pb2.Job( name='Steward/ Hôtesse', feminine_name='Hôtesse', masculine_name='Steward', ), ) self.database.advice_modules.insert_many([ { 'adviceId': 'spontaneous-application', 'categories': ['first'], 'triggerScoringModel': 'constant(2)', 'isReadyForProd': True, }, { 'adviceId': 'other-work-env', 'categories': ['first'], 'triggerScoringModel': 'constant(0)', 'isReadyForProd': True, }, ]) advisor.maybe_advise(self.user, project, self.database, 'http://base.example.com') self.assertEqual(['spontaneous-application'], [a.advice_id for a in project.advices]) self.assertEqual(project_pb2.ADVICE_RECOMMENDED, project.advices[0].status) mails_sent = mailjetmock.get_all_sent_messages() self.assertEqual(1, len(mails_sent), msg=mails_sent) data = mails_sent[0].properties['Variables'] with open(path.join(_TEMPLATE_PATH, 'activation-email', 'vars.txt'), 'r') as vars_file: template_vars = {v.strip() for v in vars_file} self.assertEqual(template_vars, set(data.keys())) self.assertEqual('10 juin 2018', data['date']) self.assertEqual('Margaux', data['firstName']) self.assertEqual('FEMININE', data['gender']) self.assertEqual("d'hôtesse", data['ofJob']) login_url = data.pop('loginUrl') base_url = re.escape( f'http://base.example.com?userId={self.user.user_id}') self.assertRegex(login_url, rf'^{base_url}&authToken=\d+\.[a-f0-9]+$') email_settings_url = data.pop('changeEmailSettingsUrl') base_url = re.escape( f'http://base.example.com/unsubscribe.html?user={self.user.user_id}' ) self.assertRegex( email_settings_url, rf'^{base_url}&auth=\d+\.[a-f0-9]+&coachingEmailFrequency=UNKNOWN_EMAIL_FREQUENCY&' r'hl=fr%40tu$') self.assertEqual('', data['isCoachingEnabled'])
def test_new_user( self, mock_requests: requests_mock.Mocker, mock_get_job_proto: mock.MagicMock, mock_get_city_proto: mock.MagicMock) -> None: """Auth request with PE Connect for a new user.""" def _match_correct_code(request: 'requests_mock._RequestObjectProxy') -> bool: return 'code=correct-code' in (request.text or '') mock_requests.post( 'https://authentification-candidat.pole-emploi.fr/connexion/oauth2/access_token?' 'realm=/individu', json={ 'access_token': '123456', 'nonce': 'correct-nonce', 'scope': 'api_peconnect-individuv1 openid profile email api_peconnect-coordonneesv1 ' 'coordonnees competences', 'token_type': 'Bearer', }, additional_matcher=_match_correct_code) mock_requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-competences/v1/competences', headers={'Authorization': 'Bearer 123456'}, json=[ { 'codeAppellation': '86420', 'codeRome': 'A1234', }, { 'codeAppellation': '86421', 'codeRome': 'A1235', }, ], ) mock_requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-individu/v1/userinfo', headers={'Authorization': 'Bearer 123456'}, json={ 'email': '*****@*****.**', 'family_name': 'CORPET', 'gender': 'male', 'given_name': 'PASCAL', 'sub': 'pe-connect-user-id-1', }) mock_requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-coordonnees/v1/coordonnees', headers={'Authorization': 'Bearer 123456'}, json={ 'codeINSEE': '69386', 'address1': '55 rue du lac', }) mock_get_city_proto.return_value = geo_pb2.FrenchCity(name='Lyon', city_id='69123') mock_get_job_proto.return_value = job_pb2.Job(name='Plombier') response = self.app.post( '/api/user/authenticate', data='{"peConnectCode": "correct-code", "peConnectNonce": "correct-nonce"}', content_type='application/json') auth_response = self.json_from_response(response) self.assertTrue(auth_response['isNewUser']) user = auth_response['authenticatedUser'] self.assertEqual('pe-connect-user-id-1', user.get('peConnectId')) self.assertTrue(user.get('hasAccount')) self.assertEqual('Pascal', user.get('profile', {}).get('name')) self.assertEqual('Corpet', user.get('profile', {}).get('lastName')) self.assertEqual('MASCULINE', user.get('profile', {}).get('gender')) self.assertEqual([True], [p.get('isIncomplete') for p in user.get('projects', [])]) self.assertEqual('Lyon', user['projects'][0].get('city', {}).get('name')) self.assertEqual('69123', user['projects'][0].get('city', {}).get('cityId')) self.assertEqual('Plombier', user['projects'][0].get('targetJob', {}).get('name')) user_id = user['userId'] self.assertEqual([user_id], [str(u['_id']) for u in self._user_db.user.find()]) mock_get_city_proto.assert_called_once_with('69123') mock_get_job_proto.assert_called_once() self.assertEqual(('86420', 'A1234'), mock_get_job_proto.call_args[0][1:])