def test_send_email_retried_subtask(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=no-member subtask_id = "subtask-id-value" initialize_subtask_info(entry, "emailed", 100, [subtask_id]) subtask_status = SubtaskStatus.create(subtask_id, state=RETRY, retried_nomax=2) update_subtask_status(entry_id, subtask_id, subtask_status) bogus_email_id = 1001 to_list = ['*****@*****.**'] global_email_context = {'course_title': 'dummy course'} # try running with a clean subtask: new_subtask_status = SubtaskStatus.create(subtask_id) with self.assertRaisesRegexp(DuplicateTaskException, 'already retried'): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, new_subtask_status.to_dict()) # try again, with a retried subtask with lower count: new_subtask_status = SubtaskStatus.create(subtask_id, state=RETRY, retried_nomax=1) with self.assertRaisesRegexp(DuplicateTaskException, 'already retried'): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, new_subtask_status.to_dict())
def _reserve_task(course_id, task_type, task_key, task_input, requester): """ Creates a database entry to indicate that a task is in progress. Throws AlreadyRunningError if the task is already in progress. Includes the creation of an arbitrary value for task_id, to be submitted with the task call to celery. Note that there is a chance of a race condition here, when two users try to run the same task at almost exactly the same time. One user could be after the check and before the create when the second user gets to the check. At that point, both users are able to run their tasks simultaneously. This is deemed a small enough risk to not put in further safeguards. """ if _task_is_running(course_id, task_type, task_key): log.warning("Duplicate task found for task_type %s and task_key %s", task_type, task_key) raise AlreadyRunningError("requested task is already running") try: most_recent_id = InstructorTask.objects.latest('id').id except InstructorTask.DoesNotExist: most_recent_id = "None found" finally: log.warning( "No duplicate tasks found: task_type %s, task_key %s, and most recent task_id = %s", task_type, task_key, most_recent_id ) # Create log entry now, so that future requests will know it's running. return InstructorTask.create(course_id, task_type, task_key, task_input, requester)
def _reserve_task(course_id, task_type, task_key, task_input, requester): """ Creates a database entry to indicate that a task is in progress. Throws AlreadyRunningError if the task is already in progress. Includes the creation of an arbitrary value for task_id, to be submitted with the task call to celery. The InstructorTask.create method makes sure the InstructorTask entry is committed. When called from any view that is wrapped by TransactionMiddleware, and thus in a "commit-on-success" transaction, an autocommit buried within here will cause any pending transaction to be committed by a successful save here. Any future database operations will take place in a separate transaction. Note that there is a chance of a race condition here, when two users try to run the same task at almost exactly the same time. One user could be after the check and before the create when the second user gets to the check. At that point, both users are able to run their tasks simultaneously. This is deemed a small enough risk to not put in further safeguards. """ if _task_is_running(course_id, task_type, task_key): raise AlreadyRunningError("requested task is already running") # Create log entry now, so that future requests will know it's running. return InstructorTask.create(course_id, task_type, task_key, task_input, requester)
def _reserve_task(course_id, task_type, task_key, task_input, requester): """ Creates a database entry to indicate that a task is in progress. Throws AlreadyRunningError if the task is already in progress. Includes the creation of an arbitrary value for task_id, to be submitted with the task call to celery. Note that there is a chance of a race condition here, when two users try to run the same task at almost exactly the same time. One user could be after the check and before the create when the second user gets to the check. At that point, both users are able to run their tasks simultaneously. This is deemed a small enough risk to not put in further safeguards. """ if _task_is_running(course_id, task_type, task_key): log.warning("Duplicate task found for task_type %s and task_key %s", task_type, task_key) raise AlreadyRunningError("requested task is already running") try: most_recent_id = InstructorTask.objects.latest('id').id except InstructorTask.DoesNotExist: most_recent_id = "None found" finally: log.warning( "No duplicate tasks found: task_type %s, task_key %s, and most recent task_id = %s", task_type, task_key, most_recent_id) # Create log entry now, so that future requests will know it's running. return InstructorTask.create(course_id, task_type, task_key, task_input, requester)
def test_nonexistent_to_option(self): """ Tests exception when the to_option in the email doesn't exist """ email = CourseEmail(course_id=self.course.id, to_option="IDONTEXIST") email.save() entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": email.id} # pylint: disable=no-member with self.assertRaisesRegexp(Exception, 'Unexpected bulk email TO_OPTION found: IDONTEXIST'): perform_delegate_email_batches(entry.id, self.course.id, task_input, "action_name") # pylint: disable=no-member
def test_wrong_course_id_in_email(self): """ Tests exception when the course_id in CourseEmail is not the same as one explicitly passed in. """ email = CourseEmail(course_id=SlashSeparatedCourseKey("bogus", "course", "id"), to_option=SEND_TO_ALL) email.save() entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": email.id} # pylint: disable=no-member with self.assertRaisesRegexp(ValueError, 'does not match email value'): perform_delegate_email_batches(entry.id, self.course.id, task_input, "action_name") # pylint: disable=no-member
def test_wrong_course_id_in_task(self): """ Tests exception when the course_id in task is not the same as one explicitly passed in. """ email = CourseEmail(course_id=self.course.id, to_option=SEND_TO_ALL) email.save() entry = InstructorTask.create("bogus/task/id", "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": email.id} # pylint: disable=E1101 with self.assertRaisesRegexp(ValueError, "does not match task value"): perform_delegate_email_batches(entry.id, self.course.id, task_input, "action_name") # pylint: disable=E1101
def test_wrong_course_id_in_task(self): """ Tests exception when the course_id in task is not the same as one explicitly passed in. """ email = CourseEmail(course_id=self.course.id, to_option=SEND_TO_ALL) email.save() entry = InstructorTask.create("bogus/task/id", "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": email.id} with self.assertRaisesRegexp(ValueError, 'does not match task value'): perform_delegate_email_batches(entry.id, self.course.id, task_input, "action_name")
def test_send_email_undefined_subtask(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=no-member to_list = ['*****@*****.**'] global_email_context = {'course_title': 'dummy course'} subtask_id = "subtask-id-value" subtask_status = SubtaskStatus.create(subtask_id) email_id = 1001 with self.assertRaisesRegexp(DuplicateTaskException, 'unable to find subtasks of instructor task'): send_course_email(entry_id, email_id, to_list, global_email_context, subtask_status.to_dict())
def test_nonexistent_course(self): """ Tests exception when the course in the email doesn't exist """ course_id = "I/DONT/EXIST" email = CourseEmail(course_id=course_id) email.save() entry = InstructorTask.create(course_id, "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": email.id} # pylint: disable=E1101 with self.assertRaisesRegexp(ValueError, "Course not found"): perform_delegate_email_batches(entry.id, course_id, task_input, "action_name") # pylint: disable=E1101
def test_nonexistent_course(self): """ Tests exception when the course in the email doesn't exist """ course_id = SlashSeparatedCourseKey("I", "DONT", "EXIST") email = CourseEmail(course_id=course_id) email.save() entry = InstructorTask.create(course_id, "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": email.id} # pylint: disable=no-member # (?i) is a regex for ignore case with self.assertRaisesRegexp(ValueError, r"(?i)course not found"): perform_delegate_email_batches(entry.id, course_id, task_input, "action_name") # pylint: disable=no-member
def test_wrong_course_id_in_email(self): """ Tests exception when the course_id in CourseEmail is not the same as one explicitly passed in. """ email = CourseEmail.create( SlashSeparatedCourseKey("bogus", "course", "id"), self.instructor, [SEND_TO_MYSELF], "re: subject", "dummy body goes here") entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": email.id} with self.assertRaisesRegexp(ValueError, 'does not match email value'): perform_delegate_email_batches(entry.id, self.course.id, task_input, "action_name")
def test_send_email_missing_subtask(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=no-member to_list = ['*****@*****.**'] global_email_context = {'course_title': 'dummy course'} subtask_id = "subtask-id-value" initialize_subtask_info(entry, "emailed", 100, [subtask_id]) different_subtask_id = "bogus-subtask-id-value" subtask_status = SubtaskStatus.create(different_subtask_id) bogus_email_id = 1001 with self.assertRaisesRegexp(DuplicateTaskException, 'unable to find status for subtask of instructor task'): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, subtask_status.to_dict())
def test_send_email_running_subtask(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=no-member subtask_id = "subtask-id-value" initialize_subtask_info(entry, "emailed", 100, [subtask_id]) subtask_status = SubtaskStatus.create(subtask_id) update_subtask_status(entry_id, subtask_id, subtask_status) check_subtask_is_valid(entry_id, subtask_id, subtask_status) bogus_email_id = 1001 to_list = ['*****@*****.**'] global_email_context = {'course_title': 'dummy course'} with self.assertRaisesRegexp(DuplicateTaskException, 'already being executed'): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, subtask_status.to_dict())
def test_send_email_completed_subtask(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=E1101 subtask_id = "subtask-id-value" initialize_subtask_info(entry, "emailed", 100, [subtask_id]) subtask_status = SubtaskStatus.create(subtask_id, state=SUCCESS) update_subtask_status(entry_id, subtask_id, subtask_status) bogus_email_id = 1001 to_list = ["*****@*****.**"] global_email_context = {"course_title": "dummy course"} new_subtask_status = SubtaskStatus.create(subtask_id) with self.assertRaisesRegexp(DuplicateTaskException, "already completed"): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, new_subtask_status.to_dict())
def test_send_email_undefined_email(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=E1101 to_list = ['*****@*****.**'] global_email_context = {'course_title': 'dummy course'} subtask_id = "subtask-id-value" subtask_status = create_subtask_status(subtask_id) bogus_email_id = 1001 with self.assertRaises(CourseEmail.DoesNotExist): # we skip the call that updates subtask status, since we've not set up the InstructorTask # for the subtask, and it's not important to the test. with patch('bulk_email.tasks.update_subtask_status'): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, subtask_status)
def test_nonexistent_email(self, mock_log, result): """ Tests retries when the email doesn't exist """ # create an InstructorTask object to pass through course_id = self.course.id entry = InstructorTask.create(course_id, "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": -1} with self.assertRaises(CourseEmail.DoesNotExist): perform_delegate_email_batches(entry.id, course_id, task_input, "action_name") # pylint: disable=no-member ((log_str, __, email_id), __) = mock_log.warning.call_args self.assertTrue(mock_log.warning.called) self.assertIn('Failed to get CourseEmail with id', log_str) self.assertEqual(email_id, -1) self.assertFalse(result.called)
def test_send_email_undefined_email(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=no-member to_list = ['*****@*****.**'] global_email_context = {'course_title': 'dummy course'} subtask_id = "subtask-id-undefined-email" initialize_subtask_info(entry, "emailed", 100, [subtask_id]) subtask_status = SubtaskStatus.create(subtask_id) bogus_email_id = 1001 with self.assertRaises(CourseEmail.DoesNotExist): # we skip the call that updates subtask status, since we've not set up the InstructorTask # for the subtask, and it's not important to the test. with patch('bulk_email.tasks.update_subtask_status'): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, subtask_status.to_dict())
def test_send_email_with_locked_instructor_task(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=no-member subtask_id = "subtask-id-locked-model" initialize_subtask_info(entry, "emailed", 100, [subtask_id]) subtask_status = SubtaskStatus.create(subtask_id) bogus_email_id = 1001 to_list = ['*****@*****.**'] global_email_context = {'course_title': 'dummy course'} with patch('instructor_task.subtasks.InstructorTask.save') as mock_task_save: mock_task_save.side_effect = DatabaseError with self.assertRaises(DatabaseError): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, subtask_status.to_dict()) self.assertEquals(mock_task_save.call_count, MAX_DATABASE_LOCK_RETRIES)
def test_send_email_undefined_email(self): # test at a lower level, to ensure that the course gets checked down below too. entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) entry_id = entry.id # pylint: disable=E1101 to_list = ["*****@*****.**"] global_email_context = {"course_title": "dummy course"} subtask_id = "subtask-id-undefined-email" initialize_subtask_info(entry, "emailed", 100, [subtask_id]) subtask_status = SubtaskStatus.create(subtask_id) bogus_email_id = 1001 with self.assertRaises(CourseEmail.DoesNotExist): # we skip the call that updates subtask status, since we've not set up the InstructorTask # for the subtask, and it's not important to the test. with patch("bulk_email.tasks.update_subtask_status"): send_course_email(entry_id, bogus_email_id, to_list, global_email_context, subtask_status.to_dict())
def test_wrong_course_id_in_email(self): """ Tests exception when the course_id in CourseEmail is not the same as one explicitly passed in. """ email = CourseEmail.create( SlashSeparatedCourseKey("bogus", "course", "id"), self.instructor, [SEND_TO_MYSELF], "re: subject", "dummy body goes here" ) entry = InstructorTask.create(self.course.id, "task_type", "task_key", "task_input", self.instructor) task_input = {"email_id": email.id} with self.assertRaisesRegexp(ValueError, 'does not match email value'): perform_delegate_email_batches(entry.id, self.course.id, task_input, "action_name")