示例#1
0
    def test_get_subject(self):
        """
        get_subject properly uses the report instance and count instance variables to return a subject string.
        """
        reporter: Reporter = Reporter(self.potions_report)
        reporter.exams_time_metadata = self.exams_time_metadata
        reporter.prepare_context()

        with patch('pe.reporter.datetime', autospec=True) as mock_datetime:
            mock_datetime.now.return_value = self.fake_finished_at
            subject: str = reporter.get_subject()

        self.assertEqual(subject, self.expected_subject)
示例#2
0
def main(api_util: ApiUtil) -> None:
    """
    Runs the highest-level application process, coordinating the use of ScoresOrchestration and Reporter
    classes and the transfer of data between them.

    :param api_util: Instance of ApiUtil for making API calls
    :type api_util: ApiUtil
    :return: None
    :rtype: None
    """
    start_time: datetime = datetime.now(tz=utc)
    LOGGER.info(f'Starting new run at {start_time}')

    reports: list[Report] = list(Report.objects.all())
    LOGGER.debug(reports)

    exams: list[Exam] = list(Exam.objects.all())
    LOGGER.debug(exams)

    for report in reports:
        reporter: Reporter = Reporter(report)
        for exam in report.exams.all():
            LOGGER.info(f'Processing Exam: {exam.name}')
            exam_start_time = datetime.now(tz=utc)
            exam_orca: ScoresOrchestration = ScoresOrchestration(
                api_util, exam)
            exam_orca.main()
            exam_end_time = datetime.now(tz=utc)
            metadata: dict[str, datetime] = {
                'start_time': exam_start_time,
                'end_time': exam_end_time,
                'sub_time_filter': exam_orca.sub_time_filter
            }
            reporter.exams_time_metadata[exam.id] = metadata

        reporter.prepare_context()
        if reporter.total_successes > 0 or reporter.total_failures > 0:
            LOGGER.info(
                f'Sending {report.name} report email to {report.contact}')
            reporter.send_email()
        else:
            LOGGER.info(
                f'No email will be sent for the {report.name} report as there was no transmission activity.'
            )

    end_time: datetime = datetime.now(tz=utc)
    delta: timedelta = end_time - start_time

    LOGGER.info(f'The run ended at {end_time}')
    LOGGER.info(f'Duration of run: {delta}')
示例#3
0
    def test_send_email(self):
        """
        send_email properly renders plain text and HTML strings (localizing times) using the context and sends an email.
        """
        # Set up snapshots
        with open(os.path.join(SNAPSHOTS_DIR, 'email_snap.txt'), 'r') as email_snap_plain_file:
            email_snap_plain: str = email_snap_plain_file.read()

        with open(os.path.join(SNAPSHOTS_DIR, 'email_snap.html'), 'r') as email_snap_html_file:
            email_snap_html: str = email_snap_html_file.read()

        reporter: Reporter = Reporter(self.potions_report)
        reporter.exams_time_metadata = self.exams_time_metadata

        # Patch os.environ to override environment variables
        with patch.dict(os.environ, {'SUPPORT_EMAIL': '*****@*****.**', 'SMTP_FROM': '*****@*****.**'}):
            reporter.prepare_context()
            with patch('pe.reporter.datetime', autospec=True) as mock_datetime:
                mock_datetime.now.return_value = self.fake_finished_at
                reporter.send_email()

        self.assertEqual(len(mail.outbox), 1)
        email: EmailMultiAlternatives = mail.outbox[0]

        self.assertEqual(len(email.alternatives), 1)
        self.assertEqual(email.alternatives[0][1], 'text/html')
        email_html_msg: str = email.alternatives[0][0]

        # Check subject, to, and from
        self.assertEqual(email.subject, self.expected_subject)
        self.assertEqual(email.to, ['*****@*****.**'])
        self.assertEqual(email.from_email, '*****@*****.**')

        # Check that body matches plain text snapshot
        self.assertEqual(email.body, email_snap_plain)

        # Check that HTML alternative matches HTML snapshot
        self.assertEqual(email_html_msg, email_snap_html)
示例#4
0
    def test_prepare_context(self):
        """
        prepare_context properly uses accumulated time metadata and database records to create a context.
        """
        reporter: Reporter = Reporter(self.potions_report)
        # Check that Reporter is initialized with no data
        self.assertEqual(
            (reporter.total_successes, reporter.total_failures, reporter.total_new, reporter.context),
            (0, 0, 0, dict())
        )

        # I decided to keep Reporter initialization in tests (not setUp), so time metadata is assigned all at once here.
        # This is different from main() in entry.py, where key-value pairs are accumulated.
        reporter.exams_time_metadata = self.exams_time_metadata
        reporter.prepare_context()

        self.assertEqual((reporter.total_successes, reporter.total_failures, reporter.total_new), (4, 1, 2))
        self.assertEqual(sorted(list(reporter.context.keys())), ['exams', 'report', 'support_email'])
        self.assertEqual(reporter.context['report'], {
            'id': 1,
            'name': 'Potions',
            'contact': '*****@*****.**',
            'summary': {'success_count': 4, 'failure_count': 1, 'new_count': 2}
        })
        self.assertEqual(len(reporter.context['exams']), 2)

        first_exam_dict: Dict[str, Any] = reporter.context['exams'][0]
        second_exam_dict: Dict[str, Any] = reporter.context['exams'][1]

        keys_list: List[List[str]] = [
            [
                'assignment_id', 'course_id', 'default_time_filter', 'failures', 'id', 'name', 'report', 'sa_code',
                'successes', 'summary', 'time'
            ],
            sorted(list(first_exam_dict.keys())),
            sorted(list(second_exam_dict.keys()))
        ]
        self.assertEqual(keys_list.count(keys_list[0]), 3)

        # Check whether time metadata correctly incorporated
        self.assertEqual(first_exam_dict['time'], self.exams_time_metadata[1])
        self.assertEqual(second_exam_dict['time'], self.exams_time_metadata[2])

        # Check whether QuerySet-derived lists have correct lengths
        self.assertEqual(len(first_exam_dict['successes']), 0)
        self.assertEqual(len(first_exam_dict['failures']), 1)
        self.assertEqual(len(second_exam_dict['successes']), 4)
        self.assertEqual(len(second_exam_dict['failures']), 0)

        # Check roughly that failures and successes are present
        self.assertEqual(
            first_exam_dict['failures'][0],
            {
                'submission_id': 123458,
                'student_uniqname': 'rweasley',
                'score': 150.0,
                'graded_timestamp': datetime(2020, 6, 12, 16, 0, 0, tzinfo=utc)
            }
        )
        val_success_ids: List[int] = sorted(
            [success_dict['submission_id'] for success_dict in second_exam_dict['successes']]
        )
        self.assertEqual(val_success_ids, [123460, 210000, 444444, 444445])