def test_put_error_without_transient_result(self): error = self._get_delete_error(3) delete_spy = test_utils.FailingFunction(search.Index.delete, error, 1) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) delete_ctx = self.swap(search.Index, 'delete', delete_spy) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with delete_docs_ctx, delete_ctx, assert_raises_ctx as e: gae_search_services.delete_documents_from_index(['a', 'b', 'c'], 'my_index') # Assert that the method only gets called once, since the error is not # transient. self.assertEqual(delete_docs_counter.times_called, 1) self.assertEqual(e.exception.original_exception, error)
def test_use_custom_number_of_retries(self): exception = self._get_delete_error(1, 0) failing_delete = test_utils.FailingFunction(search.Index.delete, exception, 42) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with delete_ctx, delete_docs_ctx, assert_raises_ctx as cm: gae_search_services.delete_documents_from_index(['id'], 'index', 42) self.assertEqual(delete_docs_counter.times_called, 42)
def test_arguments_are_preserved_in_retries(self): doc = {'id': 'doc', 'prop': 'val'} exception = self._get_put_error(1, 0) failing_put = test_utils.FailingFunction(search.Index.put, exception, 3) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) put_ctx = self.swap(search.Index, 'put', failing_put) add_docs_ctx = self.swap(gae_search_services, 'add_documents_to_index', add_docs_counter) with put_ctx, add_docs_ctx: gae_search_services.add_documents_to_index([doc], 'my_index', 4) self.assertEqual(add_docs_counter.times_called, 4) result = search.Index('my_index').get('doc') self.assertEqual(result.field('prop').value, 'val')
def _is_string_classifier_called(self, answer): string_classifier_predict = ( lda_string_classifier.LDAStringClassifier.predict) predict_counter = test_utils.CallCounter(string_classifier_predict) with self.swap(lda_string_classifier.LDAStringClassifier, 'predict', predict_counter): response = reader.classify(self.exp_state, answer) answer_group_index = response['answer_group_index'] rule_spec_index = response['rule_spec_index'] answer_groups = self.exp_state.interaction.answer_groups if answer_group_index == len(answer_groups): return 'default' answer_group = answer_groups[answer_group_index] return (answer_group.get_classifier_rule_index() == rule_spec_index and predict_counter.times_called == 1)
def test_use_default_num_retries(self): exception = search.TransientError('oops') failing_index_search = test_utils.FailingFunction( search.Index.search, exception, gae_search_services.DEFAULT_NUM_RETRIES) search_counter = test_utils.CallCounter(gae_search_services.search) search_ctx = self.swap(search.Index, 'search', failing_index_search) search_counter_ctx = self.swap(gae_search_services, 'search', search_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with search_ctx, search_counter_ctx, assert_raises_ctx as context_mgr: gae_search_services.search('query', 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual(search_counter.times_called, gae_search_services.DEFAULT_NUM_RETRIES)
def test_use_default_num_retries(self): exception = search.TransientError('oops') failing_index_search = test_utils.FailingFunction( search.Index.search, exception, 1) search_counter = test_utils.CallCounter(gae_search_services.search) search_ctx = self.swap(search.Index, 'search', failing_index_search) search_counter_ctx = self.swap(gae_search_services, 'search', search_counter) assert_raises_ctx = self.assertRaisesRegexp( gae_search_services.SearchFailureError, '<class \'google.appengine.api.search.search.TransientError\'>: ' 'oops') with search_ctx, search_counter_ctx, assert_raises_ctx as context_mgr: gae_search_services.search('query', 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual(search_counter.times_called, 1)
def test_use_custom_number_of_retries(self): exception = search.TransientError('oops') failing_index_search = test_utils.FailingFunction( search.Index.search, exception, 3) search_counter = test_utils.CallCounter(gae_search_services.search) index_ctx = self.swap(search.Index, 'search', failing_index_search) search_counter_ctx = self.swap( gae_search_services, 'search', search_counter) assert_raises_ctx = self.assertRaisesRegexp( gae_search_services.SearchFailureError, '<class \'google.appengine.api.search.search.TransientError\'>:' ' oops') with index_ctx, search_counter_ctx, assert_raises_ctx: gae_search_services.search('query', 'my_index', retries=3) self.assertEqual(search_counter.times_called, 3)
def test_use_custom_number_of_retries(self): doc = {'id': 'doc', 'prop': 'val'} exception = self._get_put_error(1, transient=0) failing_put = test_utils.FailingFunction(search.Index.put, exception, 42) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) put_ctx = self.swap(search.Index, 'put', failing_put) add_docs_ctx = self.swap(gae_search_services, 'add_documents_to_index', add_docs_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with put_ctx, add_docs_ctx, assert_raises_ctx: gae_search_services.add_documents_to_index([doc], 'my_index', retries=42) self.assertEqual(add_docs_counter.times_called, 42)
def test_email_with_bad_content_is_not_sent(self): can_send_emails_ctx = self.swap(feconf, 'CAN_SEND_EMAILS_TO_USERS', True) config_services.set_property( self.ADMIN_ID, email_manager.SIGNUP_EMAIL_CONTENT.name, { 'subject': 'New email subject', 'html_body': 'New HTML body.<script>alert(3);</script>', }) logged_errors = [] def _log_error_for_tests(error_message): logged_errors.append(error_message) log_new_error_counter = test_utils.CallCounter(_log_error_for_tests) log_new_error_ctx = self.swap(email_manager, 'log_new_error', log_new_error_counter) with can_send_emails_ctx, log_new_error_ctx: self.assertEqual(log_new_error_counter.times_called, 0) self.login(self.EDITOR_EMAIL) response = self.testapp.get(feconf.SIGNUP_URL) csrf_token = self.get_csrf_token_from_response(response) # No user-facing error should surface. response_dict = self.post_json(feconf.SIGNUP_DATA_URL, { 'agreed_to_terms': True, 'username': self.EDITOR_USERNAME }, csrf_token=csrf_token) # However, an error should be recorded in the logs. self.assertEqual(log_new_error_counter.times_called, 1) self.assertTrue(logged_errors[0].startswith( 'Original email HTML body does not match cleaned HTML body')) # Check that no email was sent. messages = self.mail_stub.get_sent_messages(to=self.EDITOR_EMAIL) self.assertEqual(0, len(messages))
def test_use_custom_number_of_retries(self): exception = self._get_delete_error(1, transient=0) failing_delete = test_utils.FailingFunction( search.Index.delete, exception, 42) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap( gae_search_services, 'delete_documents_from_index', delete_docs_counter) assert_raises_ctx = self.assertRaisesRegexp( gae_search_services.SearchFailureError, '<class \'google.appengine.api.search.search.DeleteError\'>: lol') with delete_ctx, delete_docs_ctx, assert_raises_ctx: gae_search_services.delete_documents_from_index( ['id'], 'index', retries=42) self.assertEqual(delete_docs_counter.times_called, 42)
def test_fetch_gravatar_failure_exception(self): user_email = '*****@*****.**' error_messages = [] def log_mock(message): error_messages.append(message) gravatar_url = user_services.get_gravatar_url(user_email) expected_error_message = ( 'Failed to fetch Gravatar from %s' % gravatar_url) logging_error_mock = test_utils.CallCounter(log_mock) urlfetch_fail_mock = test_utils.FailingFunction( urlfetch.fetch, urlfetch.InvalidURLError, test_utils.FailingFunction.INFINITY) log_swap_ctx = self.swap(logging, 'error', logging_error_mock) fetch_swap_ctx = self.swap(urlfetch, 'fetch', urlfetch_fail_mock) with log_swap_ctx, fetch_swap_ctx: profile_picture = user_services.fetch_gravatar(user_email) self.assertEqual(logging_error_mock.times_called, 1) self.assertEqual(expected_error_message, error_messages[0]) self.assertEqual( profile_picture, user_services.DEFAULT_IDENTICON_DATA_URL)
def _get_classiying_rule_type(self, answer): string_classifier_predict = ( classifier_services.StringClassifier.predict_label_for_doc) predict_counter = test_utils.CallCounter(string_classifier_predict) with self.swap(classifier_services.StringClassifier, 'predict_label_for_doc', predict_counter): response = reader.classify(self.exp_id, self.exp_state, answer, {'answer': answer}) answer_group_index = response['answer_group_index'] rule_spec_index = response['rule_spec_index'] answer_groups = self.exp_state.interaction.answer_groups if answer_group_index == len(answer_groups): return 'default' answer_group = answer_groups[answer_group_index] if answer_group.get_fuzzy_rule_index() == rule_spec_index: return ('soft' if predict_counter.times_called == 0 else 'classifier') return 'hard'
def test_use_default_num_retries(self): exception = self._get_delete_error(1, transient=0) failing_delete = test_utils.FailingFunction(search.Index.delete, exception, 1) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) assert_raises_ctx = self.assertRaisesRegexp( gae_search_services.SearchFailureError, '<class \'google.appengine.api.search.search.DeleteError\'>: lol') with delete_ctx, delete_docs_ctx, assert_raises_ctx as context_mgr: gae_search_services.delete_documents_from_index(['doc'], 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual(delete_docs_counter.times_called, 1)
def test_email_not_sent_if_content_config_is_not_modified(self): can_send_emails_ctx = self.swap(feconf, 'CAN_SEND_EMAILS_TO_USERS', True) logged_errors = [] def _log_error_for_tests(error_message): logged_errors.append(error_message) log_new_error_counter = test_utils.CallCounter(_log_error_for_tests) log_new_error_ctx = self.swap(email_manager, 'log_new_error', log_new_error_counter) with can_send_emails_ctx, log_new_error_ctx: self.assertEqual(log_new_error_counter.times_called, 0) self.login(self.EDITOR_EMAIL) response = self.testapp.get(feconf.SIGNUP_URL) csrf_token = self.get_csrf_token_from_response(response) # No user-facing error should surface. response_dict = self.post_json(feconf.SIGNUP_DATA_URL, { 'agreed_to_terms': True, 'username': self.EDITOR_USERNAME }, csrf_token=csrf_token) # However, an error should be recorded in the logs. self.assertEqual(log_new_error_counter.times_called, 1) self.assertEqual( logged_errors[0], 'Please ensure that the value for the admin config property ' 'SIGNUP_EMAIL_CONTENT is set, before allowing post-signup ' 'emails to be sent.') # Check that no email was sent. messages = self.mail_stub.get_sent_messages(to=self.EDITOR_EMAIL) self.assertEqual(0, len(messages))
def test_patch_collection_search_document(self): def mock_get_doc(doc_id, index): self.assertEqual(doc_id, self.COLLECTION_ID) self.assertEqual(index, search_services.SEARCH_INDEX_COLLECTIONS) return {'a': 'b', 'c': 'd'} def mock_add_docs(docs, index): self.assertEqual(index, search_services.SEARCH_INDEX_COLLECTIONS) self.assertEqual(docs, [{'a': 'b', 'c': 'e', 'f': 'g'}]) get_doc_swap = self.swap(gae_search_services, 'get_document_from_index', mock_get_doc) add_docs_counter = test_utils.CallCounter(mock_add_docs) add_docs_swap = self.swap(gae_search_services, 'add_documents_to_index', add_docs_counter) with get_doc_swap, add_docs_swap: patch = {'c': 'e', 'f': 'g'} search_services.patch_collection_search_document( self.COLLECTION_ID, patch) self.assertEqual(add_docs_counter.times_called, 1)
def test_use_default_num_retries(self): exception = self._get_delete_error(1, 0) failing_delete = test_utils.FailingFunction( search.Index.delete, exception, gae_search_services.DEFAULT_NUM_RETRIES) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) delete_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with delete_ctx, delete_docs_ctx, assert_raises_ctx as context_mgr: gae_search_services.delete_documents_from_index(['doc'], 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual(delete_docs_counter.times_called, gae_search_services.DEFAULT_NUM_RETRIES)
def test_update_private_exploration_status_in_search(self): def mock_delete_docs(ids, index): self.assertEqual(ids, [self.EXP_ID]) self.assertEqual(index, search_services.SEARCH_INDEX_EXPLORATIONS) def mock_get_rights(unused_exp_id): return rights_manager.ActivityRights( self.EXP_ID, [self.owner_id], [self.editor_id], [self.viewer_id], status=rights_manager.ACTIVITY_STATUS_PRIVATE) delete_docs_counter = test_utils.CallCounter(mock_delete_docs) delete_docs_swap = self.swap(gae_search_services, 'delete_documents_from_index', delete_docs_counter) get_rights_swap = self.swap(rights_manager, 'get_exploration_rights', mock_get_rights) with get_rights_swap, delete_docs_swap: search_services.update_exploration_status_in_search(self.EXP_ID) self.assertEqual(delete_docs_counter.times_called, 1)
def test_put_error_without_transient_result(self): docs = [{'id': 'doc1', 'prop': 'val1'}, {'id': 'doc2', 'prop': 'val2'}, {'id': 'doc3', 'prop': 'val3'}] error = self._get_put_error(3) failing_put = test_utils.FailingFunction(search.Index.put, error, 1) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) add_docs_ctx = self.swap( gae_search_services, 'add_documents_to_index', add_docs_counter) put_ctx = self.swap(search.Index, 'put', failing_put) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with add_docs_ctx, put_ctx, assert_raises_ctx as e: gae_search_services.add_documents_to_index(docs, 'my_index') # assert that the method only gets called once, since the error is not # transient. self.assertEqual(add_docs_counter.times_called, 1) self.assertEqual(e.exception.original_exception, error)
def test_arguments_are_preserved_in_retries(self): index = search.Index('index') index.put([search.Document(doc_id='doc', fields=[ search.TextField(name='prop', value='val') ])]) exception = self._get_delete_error(1, 0) failing_delete = test_utils.FailingFunction( search.Index.delete, exception, 3) delete_docs_counter = test_utils.CallCounter( gae_search_services.delete_documents_from_index) index_ctx = self.swap(search.Index, 'delete', failing_delete) delete_docs_ctx = self.swap( gae_search_services, 'delete_documents_from_index', delete_docs_counter) with index_ctx, delete_docs_ctx: gae_search_services.delete_documents_from_index( ['doc'], 'index', 4) self.assertEqual(delete_docs_counter.times_called, 4) result = search.Index('my_index').get('doc') self.assertIsNone(result)
def test_use_default_num_retries(self): doc = {'id': 'doc', 'prop': 'val'} exception = self._get_put_error(1, 0) failing_put = test_utils.FailingFunction( search.Index.put, exception, gae_search_services.DEFAULT_NUM_RETRIES, ) add_docs_counter = test_utils.CallCounter( gae_search_services.add_documents_to_index) put_ctx = self.swap(search.Index, 'put', failing_put) add_docs_ctx = self.swap(gae_search_services, 'add_documents_to_index', add_docs_counter) assert_raises_ctx = self.assertRaises( gae_search_services.SearchFailureError) with put_ctx, add_docs_ctx, assert_raises_ctx as context_mgr: gae_search_services.add_documents_to_index([doc], 'my_index') self.assertEqual(context_mgr.exception.original_exception, exception) self.assertEqual(add_docs_counter.times_called, gae_search_services.DEFAULT_NUM_RETRIES)