def test_dynamic_sticky_steps(self): """Check that dynamic fields of sticky steps are populated.""" new_action = action_pb2.Action() database = mongomock.MongoClient().test database.sticky_action_steps.insert_one({ '_id': 'step1', 'title': 'Trouver un job de %masculineJobName', 'content': 'Regarder sur le [ROME](http://go/rome/%romeId).', 'link': 'http://lbb.fr/city/%cityId/rome/%romeId', 'linkName': 'Les bonnes boites de %cityName', 'finishCheckboxCaption': "J'ai trouvé un job de %masculineJobName", }) action.instantiate( new_action, user_pb2.User(), project_pb2.Project( mobility=geo_pb2.Location(city=geo_pb2.FrenchCity( city_id='45123', name='Orléans', departement_id='45', region_id='84')), target_job=job_pb2.Job( masculine_name='Pompier', code_ogr='78910', job_group=job_pb2.JobGroup(rome_id='A1101', name='Combattants')), ), action_pb2.ActionTemplate( action_template_id='my-sticky', step_ids=['step1']), set(), database, None) step = new_action.steps[0] self.assertEqual('http://lbb.fr/city/45123/rome/A1101', step.link) self.assertEqual('Regarder sur le [ROME](http://go/rome/A1101).', step.content)
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_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.local_diagnosis.insert_one({ '_id': '14:A1234', 'imt': {'applicationModes': {'Foo': {'first': 'SPONTANEOUS_APPLICATION'}}}, }) self.database.advice_modules.insert_one({ 'adviceId': 'my-advice', 'triggerScoringModel': 'chantier-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 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 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_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_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_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_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 _pe_connect_authenticate(self, code, nonce): token_data = _get_oauth2_access_token( 'https://authentification-candidat.pole-emploi.fr/connexion/oauth2/access_token?' 'realm=/individu', code=code, client_id=_EMPLOI_STORE_CLIENT_ID, client_secret=_EMPLOI_STORE_CLIENT_SECRET, auth_name='PE Connect', ) if token_data.get('nonce') != nonce: flask.abort(403, 'Mauvais paramètre nonce') authorization_header = '{} {}'.format( token_data.get('token_type', 'Bearer'), token_data.get('access_token', '')) scopes = token_data.get('scope', '').split(' ') user_info_response = requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-individu/v1/userinfo', headers={'Authorization': authorization_header}) if user_info_response.status_code < 200 or user_info_response.status_code >= 400: logging.warning('PE Connect fails (%d): "%s"', user_info_response.status_code, user_info_response.text) flask.abort(403, user_info_response.text) user_info = user_info_response.json() city = None if 'coordonnees' in scopes: coordinates_response = requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-coordonnees/v1/coordonnees', headers={ 'Authorization': authorization_header, 'pe-nom-application': 'Bob Emploi', }) if coordinates_response.status_code >= 200 and coordinates_response.status_code < 400: coordinates = coordinates_response.json() city = geo.get_city_proto(coordinates.get('codeINSEE')) job = None if 'competences' in scopes: competences_response = requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-competences/v1/competences', headers={'Authorization': authorization_header}, ) if competences_response.status_code >= 200 and competences_response.status_code < 400: competences = competences_response.json() job_id, rome_id = next( ((c.get('codeAppellation'), c.get('codeRome')) for c in competences), (None, None)) job = jobs.get_job_proto(self._db, job_id, rome_id) response = user_pb2.AuthResponse() user_dict = self._user_db.user.find_one( {'peConnectId': user_info['sub']}) user_id = str(user_dict.pop('_id')) if user_dict else '' if proto.parse_from_mongo(user_dict, response.authenticated_user): response.authenticated_user.user_id = user_id self._handle_returning_user(response) else: email = user_info.get('email') if email: self._assert_user_not_existing(email) response.authenticated_user.profile.email = email user = response.authenticated_user user.pe_connect_id = user_info['sub'] # TODO(pascal): Handle the case where one of the name is missing. user.profile.name = french.cleanup_firstname( user_info.get('given_name', '')) user.profile.last_name = french.cleanup_firstname( user_info.get('family_name', '')) user.profile.gender = \ _PE_CONNECT_GENDER.get(user_info.get('gender', ''), user_pb2.UNKNOWN_GENDER) if city or job: user.projects.add( is_incomplete=True, mobility=geo_pb2.Location(city=city) if city else None, target_job=job) self._save_new_user(user) response.is_new_user = True response.auth_token = create_token(response.authenticated_user.user_id, 'auth') return response
def test_seasonal_extra_data(self, mock_now): """Test that the advisor computes extra data for the seasonal-relocate advice.""" mock_now.return_value = datetime.datetime(2017, 2, 15) self.user.profile.year_of_birth = datetime.date.today().year - 28 self.user.profile.highest_degree = job_pb2.BAC_BACPRO self.user.profile.family_situation = user_pb2.SINGLE project = project_pb2.Project( mobility=geo_pb2.Location(area_type=geo_pb2.COUNTRY), job_search_length_months=7, employment_types=[ job_pb2.INTERIM, job_pb2.CDD_LESS_EQUAL_3_MONTHS ]) self.database.advice_modules.insert_one({ 'adviceId': 'seasonal-relocate', 'triggerScoringModel': 'advice-seasonal-relocate', 'extraDataFieldName': 'seasonal_data', 'isReadyForProd': True, }) self.database.seasonal_jobbing.insert_one({ '_id': 2, 'departementStats': [ { 'departementId': '06', 'departementSeasonalOffers': 900, 'jobGroups': [ { 'romeId': 'I1202', 'name': 'Professeur de piano', 'offers': 123, }, { 'romeId': 'I1203', 'name': 'Professeur de guitarre', 'offers': 120, }, ], }, { 'departementId': '2A', 'departementSeasonalOffers': 800, 'jobGroups': [ { 'romeId': 'I1202', 'name': 'Professeur de piano', 'offers': 123, }, { 'romeId': 'I1203', 'name': 'Professeur de guitarre', 'offers': 120, }, ], }, ], }) advisor.maybe_advise(self.user, project, self.database) advice = next(a for a in project.advices if a.advice_id == 'seasonal-relocate') self.assertEqual(project_pb2.ADVICE_RECOMMENDED, advice.status) self.assertEqual(2, len(advice.seasonal_data.departement_stats)) first_dep = advice.seasonal_data.departement_stats[0] self.assertEqual('06', first_dep.departement_id) self.assertEqual(900, first_dep.departement_seasonal_offers) self.assertEqual(2, len(first_dep.job_groups)) self.assertEqual('I1202', first_dep.job_groups[0].rome_id) self.assertEqual('Professeur de piano', first_dep.job_groups[0].name) self.assertEqual(123, first_dep.job_groups[0].offers)