def test_invalid_record( self, mock_logging: mock.MagicMock, mock_requests: 'requests_mock._RequestObjectProxy') -> None: """Test collecting strings with a record not passing checks.""" mock_requests.post('https://slack.example.com/webhook') bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'contact_lead', { 'card_content': ' Translation needed ', 'email_template': "This is a templated email that doesn't need translation", 'name': 'I need to be translated', }) collect_strings.main(['apiKey', '--collection', 'contact_lead']) self._assert_all_translations([]) self.assertTrue(mock_logging.call_count, msg=mock_logging.call_args_list) self.assertEqual(1, mock_requests.call_count) self.assertEqual( { 'attachments': [{ 'mrkdwn_in': ['text'], 'title': 'Automatic String Collect', 'text': 'Here is the report:\nAll the collections have been collected.' + '\nErrors in collection:\ncontact_lead: 1\n' }], }, mock_requests.request_history[0].json(), )
def test_main(self): """Test collection of strings in various tables and fields.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'explanations (for client)': 'my explanation', 'title': 'First Advice', }) bob_advice_base.create('advice_modules', { 'advice_id': 'second-advice', 'title': 'Second Advice', }) bob_advice_base.create('diagnostic_sentences', { 'sentence_template': 'A sentence template', }) collect_strings.main('apikey') translations = \ list(airtable.Airtable('appkEc8N0Bw4Uok43', '').iterate('translations')) self.assertEqual([ 'A sentence template', 'First Advice', 'Second Advice', 'my explanation' ], sorted(t.get('fields', {}).get('string') for t in translations))
def test_duplicates(self) -> None: """Avoid creating duplicate rows for the same translation.""" airtable.Airtable('appkEc8N0Bw4Uok43', '').create('translations', { 'string': 'Already translated', }) bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'title': 'Already translated', 'explanations': 'New translation', 'trigger_scoring_model': 'constant(2)', }) bob_advice_base.create( 'advice_modules', { 'advice_id': 'second-advice', 'title': 'New translation', 'trigger_scoring_model': 'constant(2)', }) collect_strings.main(['apikey']) self._assert_all_translations([ 'Already translated', 'New translation', 'adviceModules:first-advice:title', 'adviceModules:second-advice:title', ])
def test_client_collection(self) -> None: """Test collecting strings for client-side translation from a specific table.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'contact_lead', { 'card_content': 'Translation needed', 'email_template': "This is a templated email that doesn't need translation", 'name': 'I need to be translated', }) bob_advice_base.create('email_templates', { 'reason': "That's why", 'title': 'Please, translate me', }) bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'title': 'Title to translate', 'static_explanations': 'New translation', 'explanations': 'No translation needed', }) collect_strings.main( ['apiKey', '--collection', 'client-advice_modules']) self._assert_all_translations([ 'adviceModules:first-advice:static_explanations', 'adviceModules:first-advice:title' ])
def test_generator(self) -> None: """Generate several strings to translate from a single Airtable field.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'specific_to_job_advice', { 'expanded_card_items': 'Nous avons réuni quelques idées pour vous aider à réussir ' 'votre approche\u00a0:\n* Se présenter aux boulangers entre 4h et 7h du matin.\n* ' 'Demander au vendeur / à la vendeuse à quelle heure arrive le chef le matin.', 'short_title': 'Un court titre à traduire', 'for-job-group': 'NOT_INTERESTING', 'card_text': 'Un texte de carte à traduire', 'title': 'Un titre à traduire', 'diagnostic_topics': ['PROFILE_DIAGNOSTIC'], }) collect_strings.main(['apikey']) self._assert_all_translations([ 'Demander au vendeur / à la vendeuse à quelle heure arrive le chef le matin.', 'Nous avons réuni quelques idées pour vous aider à réussir votre approche\u00a0:', 'Se présenter aux boulangers entre 4h et 7h du matin.', 'Un court titre à traduire', 'Un texte de carte à traduire', 'Un titre à traduire', ])
def test_collect_all(self, mock_post: mock.MagicMock) -> None: """Test collection of strings in various tables and fields.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'explanations (for client)': 'my explanation', 'title': 'First Advice', }) bob_advice_base.create('advice_modules', { 'advice_id': 'second-advice', 'title': 'Second Advice', }) bob_advice_base.create('diagnostic_sentences', { 'sentence_template': 'A sentence template', 'order': 1, }) collect_strings.main(['apikey']) self._assert_all_translations([ 'A sentence template', 'First Advice', 'Second Advice', 'my explanation' ]) mock_post.assert_not_called()
def test_delete_unused(self) -> None: """Delete unused translations.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create('advice_modules', { 'title': 'Other module', 'advice_id': 'other' }) module = bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'static_explanations': 'Original text', 'title': 'Title to translate', }) collect_strings.main(['apiKey', '--collection', 'client']) bob_advice_base.delete('advice_modules', module['id']) bob_advice_base.create('advice_modules', { 'advice_id': 'second-advice', 'static_explanations': 'New text', }) collect_strings.main( ['apiKey', '--collection', 'client', '--unused', 'delete']) self._assert_all_translations([ 'adviceModules:other:title', 'adviceModules:second-advice:static_explanations', ])
def test_invalid_converter( self, mock_logging: mock.MagicMock, mock_requests: 'requests_mock._RequestObjectProxy') -> None: """Test collecting strings with a broken converter.""" mock_requests.post('https://slack.example.com/webhook') bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create('contact_lead', { 'title': 'Title to translate', }) collect_strings.main(['apiKey', '--collection', 'contact_lead']) self._assert_all_translations([]) mock_logging.assert_called_once() self.assertEqual(1, mock_requests.call_count) self.assertEqual( { 'attachments': [{ 'mrkdwn_in': ['text'], 'title': 'Automatic String Collect', 'text': 'Here is the report:\nAll the collections have been collected.' + '\nErrors in collection:\ncontact_lead: 1\n' }], }, mock_requests.request_history[0].json(), )
def test_delete_unused_with_generator(self) -> None: """Delete unused translations including ones dropped from generators.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') piece_of_advice = bob_advice_base.create( 'specific_to_job_advice', { 'id': 'baker', 'expanded_card_items': 'A list of items to translate in multiple piece:\n' '* first item (will be removed)\n' '* second item (is kept)', 'short_title': 'Un court titre à traduire', 'fr:for-job-group': 'NOT_INTERESTING', 'card_text': 'Un texte de carte à traduire', 'title': 'Un titre à traduire', 'diagnostic_topics': ['PROFILE_DIAGNOSTIC'], }) collect_strings.main(['apikey']) bob_advice_base.delete('specific_to_job_advice', piece_of_advice['id']) bob_advice_base.create( 'specific_to_job_advice', { 'id': 'baker', 'expanded_card_items': 'A list of items to translate in multiple pieces:\n' '* second item (is kept)', 'short_title': 'Un court titre à traduire', 'fr:for-job-group': 'NOT_INTERESTING', 'card_text': 'Un texte de carte à traduire', 'title': 'Un titre à traduire', 'diagnostic_topics': ['PROFILE_DIAGNOSTIC'], }) collect_strings.main(['apiKey', '--unused', 'delete']) self._assert_all_translations([ 'A list of items to translate in multiple pieces:', 'second item (is kept)', 'Un court titre à traduire', 'Un texte de carte à traduire', 'Un titre à traduire', 'specificToJobAdvice:baker:title', 'specificToJobAdvice:baker:card_text', 'specificToJobAdvice:baker:expanded_card_header', 'specificToJobAdvice:baker:short_title', ])
def test_job_group_info(self) -> None: """Test collecting strings for the job_group_info collection from importer.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'skills_for_future', { 'description': 'Translation needed', 'name': 'I need to be translated', 'assets': [1], }) collect_strings.main(['apiKey', '--collection', 'job_group_info']) self._assert_all_translations( ['I need to be translated', 'Translation needed'])
def test_last_used(self) -> None: """Check the behavior of last_used.""" airtable.Airtable('appkEc8N0Bw4Uok43', '').create('translations', { 'string': 'Very old translation', }) airtable.Airtable('appkEc8N0Bw4Uok43', '').create('translations', { 'string': 'Old translation', 'last_used': '2018-09-12T14:35:43.000Z', }) airtable.Airtable('appkEc8N0Bw4Uok43', '').create( 'translations', { 'string': 'Already translated', 'last_used': '2018-09-12T14:35:43.000Z', }) bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create('advice_modules', { 'advice_id': 'first-advice', 'title': 'Already translated', }) bob_advice_base.create('advice_modules', { 'advice_id': 'second-advice', 'title': 'New translation', }) date_before = datetime.datetime.utcnow() - datetime.timedelta( seconds=1) collect_strings.main(['apikey']) translations = \ list(airtable.Airtable('appkEc8N0Bw4Uok43', '').iterate('translations')) actual = { t.get('fields', {}).get('string'): t.get('fields', {}).get('last_used') for t in translations } self.assertEqual(None, actual['Very old translation']) self.assertEqual('2018-09-12T14:35:43.000Z', actual['Old translation']) last_used = actual['New translation'] self.assertTrue(last_used) self.assertEqual(last_used, actual['Already translated']) self.assertRegex(last_used, r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d*)?Z$') last_used_date = datetime.datetime.strptime( re.sub(r'\.\d*Z$', '', last_used), '%Y-%m-%dT%H:%M:%S') self.assertLess(date_before, last_used_date)
def test_list_unused_replaced(self, mock_warning: mock.MagicMock) -> None: """List unused translations including replaced translations.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'advice_modules', { 'title': 'Other module', 'advice_id': 'other', 'trigger_scoring_model': 'constant(2)', }) module = bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'trigger_scoring_model': 'constant(1)', 'title': 'Title to translate', }) collect_strings.main( ['apiKey', '--collection', 'advice_modules', '--unused', 'list']) self.assertFalse(mock_warning.called) bob_advice_base.update('advice_modules', module['id'], {'title': 'Updated title to translate'}) collect_strings.main( ['apiKey', '--collection', 'advice_modules', '--unused', 'list']) self.assertEqual(1, mock_warning.call_count, msg=mock_warning.call_args_list) warning_text = mock_warning.call_args[0][0] % mock_warning.call_args[ 0][1] self.assertIn('advice_modules', warning_text) self.assertIn('title', warning_text) self.assertIn('first-advice', warning_text) self.assertIn('"Title to translate"', warning_text) self.assertIn('"Updated title to translate"', warning_text) self.assertNotIn('"Other module"', warning_text) self._assert_all_translations([ 'Other module', 'Title to translate', 'Updated title to translate', 'adviceModules:first-advice:title', 'adviceModules:other:title', ])
def test_list_unused_replaced(self, mock_warning: mock.MagicMock) -> None: """List unused translations including replaced translations.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create('advice_modules', { 'title': 'Other module', 'advice_id': 'other' }) module = bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'explanations (for client)': 'Original text', 'title': 'Title to translate', }) collect_strings.main( ['apiKey', '--collection', 'client', '--unused', 'list']) self.assertFalse(mock_warning.called) bob_advice_base.delete('advice_modules', module['id']) module = bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'explanations (for client)': 'New text', 'title': 'Title to translate', }) collect_strings.main( ['apiKey', '--collection', 'client', '--unused', 'list']) self.assertEqual(1, mock_warning.call_count, msg=mock_warning.call_args_list) warning_text = mock_warning.call_args[0][0] % mock_warning.call_args[ 0][1] self.assertIn('advice_modules', warning_text) self.assertIn('explanations (for client)', warning_text) self.assertIn('first-advice', warning_text) self.assertIn('"Original text"', warning_text) self.assertIn('"New text"', warning_text) self.assertNotIn('"Title to translate"', warning_text) self._assert_all_translations([ 'New text', 'Original text', 'Other module', 'Title to translate' ])
def test_exsting_duplicates( self, mock_requests: 'requests_mock._RequestObjectProxy') -> None: """Detect existing duplicate rows for the same translation.""" mock_requests.post('https://slack.example.com/webhook') record_1 = airtable.Airtable('appkEc8N0Bw4Uok43', '').create('translations', { 'string': 'Already translated', }) id_1 = record_1['id'] record_2 = airtable.Airtable('appkEc8N0Bw4Uok43', '').create('translations', { 'string': 'Already translated', }) id_2 = record_2['id'] collect_strings.main(['apikey']) self._assert_all_translations([ 'Already translated', 'Already translated', ]) self.assertEqual(1, mock_requests.call_count) self.assertEqual( { 'attachments': [{ 'mrkdwn_in': ['text'], 'title': 'Automatic String Collect', 'text': 'Here is the report:\nDuplicate records found for 1 string:\n' + f' * <https://airtable.com/tblQL7A5EgRJWhQFo/{id_1}|{id_1}>, ' f'<https://airtable.com/tblQL7A5EgRJWhQFo/{id_2}|{id_2}>\n' }], }, mock_requests.request_history[0].json(), )
def test_delete_replaced(self) -> None: """Delete replaced translations.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'advice_modules', { 'title': 'Other module', 'advice_id': 'other', 'trigger_scoring_model': 'constant(1)', }) module = bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'eshort_title': 'Original text', 'title': 'Title to translate', 'trigger_scoring_model': 'constant(1)', }) collect_strings.main(['apiKey', '--collection', 'advice_modules']) module = bob_advice_base.update_all( 'advice_modules', module['id'], { 'advice_id': 'first-advice', 'short_title': 'New text', 'trigger_scoring_model': 'constant(1)', }) collect_strings.main([ 'apiKey', '--collection', 'advice_modules', '--unused', 'delete-replaced' ]) self._assert_all_translations([ 'New text', 'Other module', 'Title to translate', 'adviceModules:first-advice:short_title', 'adviceModules:first-advice:title', 'adviceModules:other:title', ])
def test_collection(self) -> None: """Test collecting strings for a given collection from importer.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'contact_lead', { 'card_content': 'Translation needed', 'email_template': 'This is a templated email', 'filters': ['constant(2)'], 'name': 'I need to be translated', }) bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'title': 'Title to translate', 'static_explanations': 'New translation', 'explanations': 'No translation needed', }) collect_strings.main(['apiKey', '--collection', 'contact_lead']) self._assert_all_translations([ 'I need to be translated', 'This is a templated email', 'Translation needed' ])
def test_list_unused(self, mock_warning: mock.MagicMock) -> None: """List unused translations.""" bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create('advice_modules', { 'title': 'Other module', 'advice_id': 'other' }) module = bob_advice_base.create('advice_modules', { 'advice_id': 'first-advice', 'title': 'Title to translate', }) collect_strings.main( ['apiKey', '--collection', 'client', '--unused', 'list']) self.assertFalse(mock_warning.called) bob_advice_base.delete('advice_modules', module['id']) module = bob_advice_base.create('advice_modules', { 'advice_id': 'second-advice', 'title': 'Title to translate', }) collect_strings.main( ['apiKey', '--collection', 'client', '--unused', 'list']) self.assertEqual(1, mock_warning.call_count, msg=mock_warning.call_args_list) warning_text = mock_warning.call_args[0][0] % mock_warning.call_args[ 0][1] self.assertIn('adviceModules', warning_text) self.assertIn('title', warning_text) self.assertIn('"first-advice"', warning_text) self._assert_all_translations([ 'adviceModules:other:title', 'adviceModules:first-advice:title', 'adviceModules:second-advice:title', ])
def test_collect_all( self, mock_requests: 'requests_mock._RequestObjectProxy') -> None: """Test collection of strings in various tables and fields.""" mock_requests.post('https://slack.example.com/webhook') bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'static_explanations': 'my explanation', 'title': 'First Advice', 'trigger_scoring_model': 'constant(0)', }) bob_advice_base.create( 'advice_modules', { 'advice_id': 'second-advice', 'title': 'Second Advice', 'trigger_scoring_model': 'constant(1)', }) bob_advice_base.create( 'diagnostic_main_challenges', { 'category_id': 'my-category', 'description': 'My category description', 'metric_title': 'A category metric title', 'order': 1, }) diagnostic_overall_record = bob_advice_base.create( 'diagnostic_overall', { 'category_id': 'my-category', 'sentence_template': 'An overall diagnostic sentence', 'text_template': 'An overall diagnostic text', 'score': 4, 'order': 1, }) diagnostic_overall_record_id = diagnostic_overall_record['id'] collect_strings.main(['apikey']) self._assert_all_translations([ 'A category metric title', 'An overall diagnostic sentence', 'An overall diagnostic text', 'First Advice', 'My category description', 'Second Advice', 'adviceModules:first-advice:static_explanations', 'adviceModules:first-advice:title', 'adviceModules:second-advice:title', f'diagnosticOverall:{diagnostic_overall_record_id}:sentence_template', f'diagnosticOverall:{diagnostic_overall_record_id}:text_template', 'diagnosticMainChallenges:my-category:description', 'diagnosticMainChallenges:my-category:metric_title', 'proto:AreaType:CITY', 'proto:AreaType:COUNTRY', 'proto:AreaType:DEPARTEMENT', 'proto:AreaType:REGION', 'proto:AreaType:UNKNOWN_AREA_TYPE', 'proto:AreaType:WORLD', 'proto:EmploymentType:ALTERNANCE', 'proto:EmploymentType:ANY_CONTRACT_LESS_THAN_A_MONTH', 'proto:EmploymentType:CDD', 'proto:EmploymentType:CDD_LESS_EQUAL_3_MONTHS', 'proto:EmploymentType:CDD_OVER_3_MONTHS', 'proto:EmploymentType:CDI', 'proto:EmploymentType:FULL_TIME_EMPLOYMENT', 'proto:EmploymentType:INTERIM', 'proto:EmploymentType:INTERNSHIP', 'proto:EmploymentType:PART_TIME_EMPLOYMENT', 'proto:EmploymentType:UNDEFINED_EMPLOYMENT_TYPE', 'proto:DegreeLevel:BAC_BACPRO', 'proto:DegreeLevel:BTS_DUT_DEUG', 'proto:DegreeLevel:CAP_BEP', 'proto:DegreeLevel:DEA_DESS_MASTER_PHD', 'proto:DegreeLevel:LICENCE_MAITRISE', 'proto:DegreeLevel:NO_DEGREE', 'proto:DegreeLevel:UNKNOWN_DEGREE', ], are_proto_included=True) self.assertEqual(0, mock_requests.call_count)
def test_last_used(self, mock_now: mock.MagicMock) -> None: """Check the behavior of last_used.""" mock_now.return_value = datetime.datetime(2021, 2, 4, 21, 55) airtable.Airtable('appkEc8N0Bw4Uok43', '').create('translations', { 'string': 'Very old translation', }) airtable.Airtable('appkEc8N0Bw4Uok43', '').create('translations', { 'string': 'Old translation', 'last_used': '2018-09-12T14:35:43.000Z', }) airtable.Airtable('appkEc8N0Bw4Uok43', '').create( 'translations', { 'string': 'Already translated', 'last_used': '2018-09-12T14:35:43.000Z', }) bob_advice_base = airtable.Airtable('appXmyc7yYj0pOcae', '') bob_advice_base.create( 'advice_modules', { 'advice_id': 'first-advice', 'trigger_scoring_model': 'constant(0)', 'title': 'Already translated', }) bob_advice_base.create( 'advice_modules', { 'advice_id': 'second-advice', 'trigger_scoring_model': 'constant(0)', 'title': 'New translation', }) collect_strings.main(['apikey']) translations = \ list(airtable.Airtable('appkEc8N0Bw4Uok43', '').iterate('translations')) actual = { t.get('fields', {}).get('string'): t.get('fields', {}).get('last_used') for t in translations } self.assertEqual(None, actual['Very old translation']) self.assertEqual('2018-09-12T14:35:43.000Z', actual['Old translation']) last_used = typing.cast(str, actual['New translation']) self.assertTrue(last_used) self.assertEqual(last_used, actual['Already translated']) self.assertRegex(last_used, r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d*)?Z$') self.assertEqual('2021-02-04T21:55:00Z', last_used) # Wait two hours. mock_now.return_value = datetime.datetime(2021, 2, 4, 23, 55) collect_strings.main(['apikey']) actual = { t.get('fields', {}).get('string'): t.get('fields', {}).get('last_used') for t in translations } self.assertEqual( last_used, actual['Already translated'], msg= "The last_used field should not be updated as it's on the same day" ) # Wait for the next day. mock_now.return_value = datetime.datetime(2021, 2, 5, 6, 55) collect_strings.main(['apikey']) actual = { t.get('fields', {}).get('string'): t.get('fields', {}).get('last_used') for t in translations } self.assertEqual( '2021-02-05T06:55:00Z', actual['Already translated'], msg="The last_used field should be updated as it's on a new day")