def test_default_template_not_include_course_info(
         self, template_mock, update_course,
         get_default_template_for_school, **kwargs):
     get_default_template_for_school.return_value = Mock(
         template_id=template_id)
     template_mock.return_value = Mock(include_course_info=False)
     update_syllabus_body(self.course_job)
     assert not update_course.called
 def test_template_not_include_course_info(self, template_mock,
                                           bulk_job_mock, SDK_CONTEXT,
                                           update_course, **kwargs):
     bulk_job_mock.return_value = Mock(
         template_canvas_course_id=template_id)
     template_mock.return_value = Mock(include_course_info=False)
     update_syllabus_body(self.course_job)
     assert not update_course.called
 def test_template_include_course_info(self, template_mock, bulk_job_mock, SDK_CONTEXT, update_course, **kwargs):
     bulk_job_mock.return_value = Mock(template_canvas_course_id=template_id)
     template_mock.return_value = Mock(include_course_info=True)
     update_syllabus_body(self.course_job)
     update_course.assert_called_with(
         SDK_CONTEXT,
         canvas_course_id,
         course_syllabus_body=course_syllabus_body
     )
 def test_default_template_include_course_info(self, template_mock, get_default_template_for_school, SDK_CONTEXT,
                                               update_course, **kwargs):
     template_mock.return_value = Mock(include_course_info=True)
     get_default_template_for_school.return_value = Mock(template_id=template_id)
     update_syllabus_body(self.course_job)
     update_course.assert_called_with(
         SDK_CONTEXT,
         canvas_course_id,
         course_syllabus_body=course_syllabus_body
     )
 def test_template_include_course_info(self, template_mock, bulk_job_mock,
                                       SDK_CONTEXT, update_course,
                                       **kwargs):
     bulk_job_mock.return_value = Mock(
         template_canvas_course_id=template_id)
     template_mock.return_value = Mock(include_course_info=True)
     update_syllabus_body(self.course_job)
     update_course.assert_called_with(
         SDK_CONTEXT,
         canvas_course_id,
         course_syllabus_body=course_syllabus_body)
 def test_default_template_include_course_info(
         self, template_mock, get_default_template_for_school, SDK_CONTEXT,
         update_course, **kwargs):
     template_mock.return_value = Mock(include_course_info=True)
     get_default_template_for_school.return_value = Mock(
         template_id=template_id)
     update_syllabus_body(self.course_job)
     update_course.assert_called_with(
         SDK_CONTEXT,
         canvas_course_id,
         course_syllabus_body=course_syllabus_body)
    def handle(self, **options):
        """
        select all the active job in the CanvasCourseGenerationJob table and check
        the status using the canvas_sdk.progress method
        """

        # open and lock the file used for determining if another process is running
        _pid_file = getattr(settings, 'PROCESS_ASYNC_JOBS_PID_FILE',
                            'process_async_jobs.pid')
        _pid_file_handle = open(_pid_file, 'w')
        try:
            fcntl.lockf(_pid_file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except IOError as e:
            # another instance is running
            logger.warning(
                f"another instance of the command is already running: {e}")
            return

        start_time = datetime.now()

        jobs = CanvasCourseGenerationJob.objects.filter(
            Q(workflow_state=CanvasCourseGenerationJob.STATUS_QUEUED)
            | Q(workflow_state=CanvasCourseGenerationJob.STATUS_RUNNING)
            | Q(workflow_state=CanvasCourseGenerationJob.
                STATUS_PENDING_FINALIZE))

        for job in jobs:
            try:
                """
                TODO - it turns out we only really need the job_id of the content migration
                no the whole url since we are using the canvas_sdk to check the value. We should
                update this in the database and the setting method. In the meantime just parse out
                the job_id from the url.
                """

                job_start_message = '\nProcessing course with sis_course_id %s' % (
                    job.sis_course_id)
                logger.info(job_start_message)
                user_profile = None

                # Check if the job is flagged for migration or is running the migration
                workflow_state = job.workflow_state

                if workflow_state in (
                        CanvasCourseGenerationJob.STATUS_QUEUED,
                        CanvasCourseGenerationJob.STATUS_RUNNING):
                    response = client.get(SDK_CONTEXT, job.status_url)
                    progress_response = response.json()
                    workflow_state = progress_response['workflow_state']

                    if workflow_state == CanvasCourseGenerationJob.STATUS_COMPLETED:
                        logger.info(
                            'content migration complete for course with sis_course_id %s'
                            % job.sis_course_id)
                        # Update the Job table with the completed state immediately to indicate that the template
                        # migration was successful
                        job.workflow_state = CanvasCourseGenerationJob.STATUS_COMPLETED
                        job.save(update_fields=['workflow_state'])

                if workflow_state in (
                        CanvasCourseGenerationJob.STATUS_COMPLETED,
                        CanvasCourseGenerationJob.STATUS_PENDING_FINALIZE):

                    logger.debug(
                        'Workflow state updated, starting finalization process...'
                    )
                    try:
                        update_syllabus_body(job)
                        canvas_course_url = finalize_new_canvas_course(
                            job.canvas_course_id, job.sis_course_id,
                            'sis_user_id:%s' % job.created_by_user_id,
                            job.bulk_job_id)
                    except Exception:
                        # Catch exceptions from finalize method to set the workflow_state to STATUS_FINALIZE_FAILED
                        # and then re raise it so that generic tasks like tech logger, email generation will continue
                        # to be handled in the larger try block
                        logger.exception(
                            'Exception during finalize method, '
                            'setting state to STATUS_FINALIZE_FAILED '
                            'for sis_course_id id %s' % job.sis_course_id)
                        job.workflow_state = CanvasCourseGenerationJob.STATUS_FINALIZE_FAILED
                        job.save(update_fields=['workflow_state'])

                        raise

                    # Update the Job table with the STATUS_FINALIZED state if finalize is successful
                    job.workflow_state = CanvasCourseGenerationJob.STATUS_FINALIZED
                    job.save(update_fields=['workflow_state'])

                    # if this is not a bulk_job then proceed with email generation to user
                    if not job.bulk_job_id:
                        # Once finalized successfully, only the initiator needs to be emailed
                        user_profile = get_canvas_user_profile(
                            job.created_by_user_id)
                        to_address = [user_profile['primary_email']]
                        success_msg = settings.CANVAS_EMAIL_NOTIFICATION[
                            'course_migration_success_body']
                        logger.debug(
                            "notifying success via email: to_addr=%s and adding course url =%s"
                            % (to_address, canvas_course_url))

                        # add the course url to the  message
                        complete_msg = success_msg.format(canvas_course_url)
                        send_email_helper(
                            settings.CANVAS_EMAIL_NOTIFICATION[
                                'course_migration_success_subject'],
                            complete_msg, to_address)

                elif workflow_state == CanvasCourseGenerationJob.STATUS_FAILED:
                    error_text = 'Content migration failed for course with sis_course_id %s (HUID:%s)' \
                                 % (job.sis_course_id, job.created_by_user_id)
                    logger.info(error_text)
                    tech_logger.error(error_text)

                    # Update the Job table with the new state
                    job.workflow_state = CanvasCourseGenerationJob.STATUS_FAILED
                    job.save(update_fields=['workflow_state'])

                    if not job.bulk_job_id:
                        # send email to notify of failure if it's not a bulk fed course
                        user_profile = get_canvas_user_profile(
                            job.created_by_user_id)
                        send_failure_email(user_profile['primary_email'],
                                           job.sis_course_id)

                else:
                    """
                    if the workflow_state is 'queued' or 'running' the job
                    is not complete and a failure has not occured on Canvas.
                    log that we checked
                    Note: we won't need to update the DB as we will record only the completin or failure in the job table
                    """
                    message = 'content migration state is %s for course with sis_course_id %s' % (
                        workflow_state, job.sis_course_id)
                    logger.info(message)

            except Exception as e:
                error_text = "There was a problem in processing the job for canvas course sis_course_id %s (HUID:%s)" \
                             % (job.sis_course_id, job.created_by_user_id)
                # Note: equivalent to .error(error_text, exc_info=1) -- logs at ERROR level
                logger.exception(error_text)

                # Use the friendly display_text for the subject of the tech_logger email if it's available
                if isinstance(e, RenderableException):
                    error_text = '%s (HUID:%s)' % (e.display_text,
                                                   job.created_by_user_id)
                tech_logger.exception(error_text)

                # send email if it's not a bulk created course
                if not job.bulk_job_id:
                    try:
                        # if failure happened before user profile was fetched, get the user profile
                        # to retrieve email, else reuse the user_profile info
                        if not user_profile:
                            user_profile = get_canvas_user_profile(
                                job.created_by_user_id)

                        send_failure_email(user_profile['primary_email'],
                                           job.sis_course_id)
                    except Exception:
                        # If exception occurs while sending failure email, log it
                        error_text = "There was a problem in sending the failure notification email to initiator " \
                                     "and support staff for sis_course_id %s (HUID:%s)" \
                                     % (job.sis_course_id, job.created_by_user_id)
                        logger.exception(error_text)
                        tech_logger.exception(error_text)

        logger.info('command took %s seconds to run',
                    str(datetime.now() - start_time))

        # unlock and close the file used for determining if another process is running
        try:
            fcntl.lockf(_pid_file_handle, fcntl.LOCK_UN)
            _pid_file_handle.close()
        except IOError:
            logger.error(
                "could not release lock on pid file or close pid file properly"
            )
 def test_no_template(self, bulk_job_mock, update_course, **kwargs):
     bulk_job_mock.return_value = Mock(template_canvas_course_id=None)
     update_syllabus_body(self.course_job)
     assert not update_course.called
 def test_no_default_template(self, update_course,
                              get_default_template_for_school, **kwargs):
     get_default_template_for_school.side_effect = NoTemplateExistsForSchool(
         sis_course_id)
     update_syllabus_body(self.course_job)
     assert not update_course.called
 def test_template_not_include_course_info(self, template_mock, bulk_job_mock, SDK_CONTEXT, update_course, **kwargs):
     bulk_job_mock.return_value = Mock(template_canvas_course_id=template_id)
     template_mock.return_value = Mock(include_course_info=False)
     update_syllabus_body(self.course_job)
     assert not update_course.called
 def test_no_template(self, bulk_job_mock, update_course, **kwargs):
     bulk_job_mock.return_value = Mock(template_canvas_course_id=None)
     update_syllabus_body(self.course_job)
     assert not update_course.called
 def test_default_template_not_include_course_info(self, template_mock, update_course,
                                                   get_default_template_for_school, **kwargs):
     get_default_template_for_school.return_value = Mock(template_id=template_id)
     template_mock.return_value = Mock(include_course_info=False)
     update_syllabus_body(self.course_job)
     assert not update_course.called
 def test_no_default_template(self, update_course, get_default_template_for_school, **kwargs):
     get_default_template_for_school.side_effect = NoTemplateExistsForSchool(sis_course_id)
     update_syllabus_body(self.course_job)
     assert not update_course.called