class BaseCourseEnrollmentValidationTaskReducerTest(unittest.TestCase):
    """Provide common methods for testing CourseEnrollmentValidationTask reducer."""

    def setUp(self):
        self.mode = 'honor'

    @property
    def key(self):
        """Returns key value to simulate output from mapper to pass to reducer."""
        user_id = 0
        course_id = 'foo/bar/baz'
        return (course_id, user_id)

    def create_task(self, generate_before=True, tuple_output=True, include_nonstate_changes=True,
                    earliest_timestamp=None, expected_validation=None):
        """Create a task for testing purposes."""
        interval = '2013-01-01-2014-10-10'

        interval_value = luigi.DateIntervalParameter().parse(interval)
        earliest_timestamp_value = luigi.DateHourParameter().parse(earliest_timestamp) if earliest_timestamp else None
        expected_validation_value = (
            luigi.DateHourParameter().parse(expected_validation) if expected_validation else None
        )

        self.task = CourseEnrollmentValidationTask(
            interval=interval_value,
            output_root="/fake/output",
            generate_before=generate_before,
            tuple_output=tuple_output,
            include_nonstate_changes=include_nonstate_changes,
            earliest_timestamp=earliest_timestamp_value,
            expected_validation=expected_validation_value,
        )
        self.task.init_local()

    def _activated(self, timestamp, mode=None):
        """Creates an ACTIVATED event."""
        return (timestamp, ACTIVATED, mode or self.mode, None)

    def _deactivated(self, timestamp, mode=None):
        """Creates a DEACTIVATED event."""
        return (timestamp, DEACTIVATED, mode or self.mode, None)

    def _mode_changed(self, timestamp, mode=None):
        """Creates a MODE_CHANGED event."""
        return (timestamp, MODE_CHANGED, mode or self.mode, None)

    def _validated(self, timestamp, is_active, created, mode=None, dump_duration_in_secs=300):
        """Creates a VALIDATED event."""
        dump_end = timestamp
        dump_start = add_microseconds(timestamp, int(dump_duration_in_secs) * -100000)
        validation_info = {
            'is_active': is_active,
            'created': created,
            'dump_start': dump_start,
            'dump_end': dump_end,
        }
        return (timestamp, VALIDATED, mode or self.mode, validation_info)

    def _get_reducer_output(self, values):
        """Run reducer with provided values hardcoded key."""
        return tuple(self.task.reducer(self.key, values))

    def check_output(self, inputs, expected):
        """Compare generated with expected output."""
        expected_with_key = tuple([(key, self.key + value) for key, value in expected])
        self.assertEquals(self._get_reducer_output(inputs), expected_with_key)
class BaseCourseEnrollmentValidationTaskReducerTest(unittest.TestCase):
    """Provide common methods for testing CourseEnrollmentValidationTask reducer."""
    def setUp(self):
        self.mode = 'honor'

    @property
    def key(self):
        """Returns key value to simulate output from mapper to pass to reducer."""
        user_id = 0
        course_id = 'foo/bar/baz'
        return (course_id, user_id)

    def create_task(self,
                    generate_before=True,
                    tuple_output=True,
                    include_nonstate_changes=True,
                    earliest_timestamp=None,
                    expected_validation=None):
        """Create a task for testing purposes."""
        interval = '2013-01-01-2014-10-10'

        interval_value = luigi.DateIntervalParameter().parse(interval)
        earliest_timestamp_value = luigi.DateHourParameter().parse(
            earliest_timestamp) if earliest_timestamp else None
        expected_validation_value = (
            luigi.DateHourParameter().parse(expected_validation)
            if expected_validation else None)

        self.task = CourseEnrollmentValidationTask(
            interval=interval_value,
            output_root="/fake/output",
            generate_before=generate_before,
            tuple_output=tuple_output,
            include_nonstate_changes=include_nonstate_changes,
            earliest_timestamp=earliest_timestamp_value,
            expected_validation=expected_validation_value,
        )
        self.task.init_local()

    def _activated(self, timestamp, mode=None):
        """Creates an ACTIVATED event."""
        return (timestamp, ACTIVATED, mode or self.mode, None)

    def _deactivated(self, timestamp, mode=None):
        """Creates a DEACTIVATED event."""
        return (timestamp, DEACTIVATED, mode or self.mode, None)

    def _mode_changed(self, timestamp, mode=None):
        """Creates a MODE_CHANGED event."""
        return (timestamp, MODE_CHANGED, mode or self.mode, None)

    def _validated(self,
                   timestamp,
                   is_active,
                   created,
                   mode=None,
                   dump_duration_in_secs=300):
        """Creates a VALIDATED event."""
        dump_end = timestamp
        dump_start = add_microseconds(timestamp,
                                      int(dump_duration_in_secs) * -100000)
        validation_info = {
            'is_active': is_active,
            'created': created,
            'dump_start': dump_start,
            'dump_end': dump_end,
        }
        return (timestamp, VALIDATED, mode or self.mode, validation_info)

    def _get_reducer_output(self, values):
        """Run reducer with provided values hardcoded key."""
        return tuple(self.task.reducer(self.key, values))

    def check_output(self, inputs, expected):
        """Compare generated with expected output."""
        expected_with_key = tuple([(key, self.key + value)
                                   for key, value in expected])
        self.assertEquals(self._get_reducer_output(inputs), expected_with_key)