def create_task(self, interval=None, interval_type=None):
     """Allow arguments to be passed to the task constructor."""
     if not interval:
         interval = self.DEFAULT_DATE
     self.task = StudentEngagementTask(
         interval=luigi.DateIntervalParameter().parse(interval),
         output_root='/fake/output',
         interval_type=interval_type,
     )
     self.task.init_local()
 def setUp(self):
     fake_param = luigi.DateIntervalParameter()
     self.task = StudentEngagementTask(
         interval=fake_param.parse(self.DATE),
         output_root='/fake/output'
     )
     self.task.init_local()
 def create_task(self, interval=None, interval_type=None):
     """Allow arguments to be passed to the task constructor."""
     if not interval:
         interval = self.DEFAULT_DATE
     self.task = StudentEngagementTask(
         interval=luigi.DateIntervalParameter().parse(interval),
         output_root='/fake/output',
         interval_type=interval_type,
     )
     self.task.init_local()
class BaseStudentEngagementTaskMapTest(InitializeOpaqueKeysMixin, MapperTestMixin, unittest.TestCase):
    """Base class for test analysis of detailed student engagement"""

    DEFAULT_USER_ID = 10
    DEFAULT_TIMESTAMP = "2013-12-17T15:38:32.805444"
    DEFAULT_DATE = "2013-12-17"

    def setUp(self):
        super(BaseStudentEngagementTaskMapTest, self).setUp()

        self.initialize_ids()
        self.video_id = 'i4x-foo-bar-baz'
        self.event_templates = {
            'play_video': {
                "username": "******",
                "host": "test_host",
                "event_source": "browser",
                "event_type": "play_video",
                "context": {
                    "course_id": self.course_id,
                    "org_id": self.org_id,
                    "user_id": self.DEFAULT_USER_ID,
                },
                "time": "{0}+00:00".format(self.DEFAULT_TIMESTAMP),
                "ip": "127.0.0.1",
                "event": '{"id": "%s", "currentTime": "23.4398", "code": "87389iouhdfh"}' % self.video_id,
                "agent": "blah, blah, blah",
                "page": None
            },
            'problem_check': {
                "username": "******",
                "host": "test_host",
                "event_source": "server",
                "event_type": "problem_check",
                "context": {
                    "course_id": self.course_id,
                    "org_id": self.org_id,
                    "user_id": self.DEFAULT_USER_ID,
                },
                "time": "{0}+00:00".format(self.DEFAULT_TIMESTAMP),
                "ip": "127.0.0.1",
                "event": {
                    "problem_id": self.problem_id,
                    "success": "incorrect",
                },
                "agent": "blah, blah, blah",
                "page": None
            }
        }
        self.default_event_template = 'problem_check'
        self.default_key = (self.DEFAULT_DATE, self.course_id, 'test_user')

    def create_task(self, interval=None, interval_type=None):
        """Allow arguments to be passed to the task constructor."""
        if not interval:
            interval = self.DEFAULT_DATE
        self.task = StudentEngagementTask(
            interval=luigi.DateIntervalParameter().parse(interval),
            output_root='/fake/output',
            interval_type=interval_type,
        )
        self.task.init_local()

    def assert_date_mappings(self, expected_end_date, actual_event_date):
        """Asserts that an event_date is mapped to the expected date in the key."""
        self.assert_single_map_output(
            self.create_event_log_line(time="{}T15:38:32.805444".format(actual_event_date)),
            (expected_end_date, self.course_id, 'test_user'),
            (self.problem_id, 'problem_check', '{}', actual_event_date)
        )
class BaseStudentEngagementTaskMapTest(InitializeOpaqueKeysMixin,
                                       MapperTestMixin, unittest.TestCase):
    """Base class for test analysis of detailed student engagement"""

    DEFAULT_USER_ID = 10
    DEFAULT_TIMESTAMP = "2013-12-17T15:38:32.805444"
    DEFAULT_DATE = "2013-12-17"

    def setUp(self):
        super(BaseStudentEngagementTaskMapTest, self).setUp()

        self.initialize_ids()
        self.video_id = 'i4x-foo-bar-baz'
        self.event_templates = {
            'play_video': {
                "username":
                "******",
                "host":
                "test_host",
                "event_source":
                "browser",
                "event_type":
                "play_video",
                "context": {
                    "course_id": self.course_id,
                    "org_id": self.org_id,
                    "user_id": self.DEFAULT_USER_ID,
                },
                "time":
                "{0}+00:00".format(self.DEFAULT_TIMESTAMP),
                "ip":
                "127.0.0.1",
                "event":
                '{"id": "%s", "currentTime": "23.4398", "code": "87389iouhdfh"}'
                % self.video_id,
                "agent":
                "blah, blah, blah",
                "page":
                None
            },
            'problem_check': {
                "username": "******",
                "host": "test_host",
                "event_source": "server",
                "event_type": "problem_check",
                "context": {
                    "course_id": self.course_id,
                    "org_id": self.org_id,
                    "user_id": self.DEFAULT_USER_ID,
                },
                "time": "{0}+00:00".format(self.DEFAULT_TIMESTAMP),
                "ip": "127.0.0.1",
                "event": {
                    "problem_id": self.problem_id,
                    "success": "incorrect",
                },
                "agent": "blah, blah, blah",
                "page": None
            }
        }
        self.default_event_template = 'problem_check'
        self.default_key = (self.DEFAULT_DATE, self.course_id, 'test_user')

    def create_task(self, interval=None, interval_type=None):
        """Allow arguments to be passed to the task constructor."""
        if not interval:
            interval = self.DEFAULT_DATE
        self.task = StudentEngagementTask(
            interval=luigi.DateIntervalParameter().parse(interval),
            output_root='/fake/output',
            interval_type=interval_type,
        )
        self.task.init_local()

    def assert_date_mappings(self, expected_end_date, actual_event_date):
        """Asserts that an event_date is mapped to the expected date in the key."""
        self.assert_single_map_output(
            self.create_event_log_line(
                time="{}T15:38:32.805444".format(actual_event_date)),
            (expected_end_date, self.course_id, 'test_user'),
            (self.problem_id, 'problem_check', '{}', actual_event_date))
class StudentEngagementTaskReducerTest(unittest.TestCase):
    """
    Tests to verify that engagement data is reduced properly
    """

    DATE = '2013-12-17'
    COURSE_ID = 'foo/bar/baz'
    USERNAME = '******'
    REDUCE_KEY = (DATE, COURSE_ID, USERNAME)

    WAS_ACTIVE_COLUMN = 3
    PROBLEMS_ATTEMPTED_COLUMN = 4
    PROBLEM_ATTEMPTS_COLUMN = 5
    PROBLEMS_CORRECT_COLUMN = 6
    VIDEOS_PLAYED_COLUMN = 7
    FORUM_POSTS_COLUMN = 8
    FORUM_REPLIES_COLUMN = 9
    FORUM_COMMENTS_COLUMN = 10
    TEXTBOOK_PAGES_COLUMN = 11
    LAST_SUBSECTION_COLUMN = 12

    def setUp(self):
        fake_param = luigi.DateIntervalParameter()
        self.task = StudentEngagementTask(
            interval=fake_param.parse(self.DATE),
            output_root='/fake/output'
        )
        self.task.init_local()

    def test_no_events(self):
        output = self._get_reducer_output([])
        self.assertEquals(len(output), 0)

    def _get_reducer_output(self, inputs):
        """Run the reducer and return the ouput"""
        return tuple(self.task.reducer(self.REDUCE_KEY, inputs))

    def test_any_activity(self):
        inputs = [
            ('', '/foo', '{}', self.DATE)
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            self.PROBLEMS_ATTEMPTED_COLUMN: 0,
            self.PROBLEM_ATTEMPTS_COLUMN: 0,
            self.PROBLEMS_CORRECT_COLUMN: 0,
            self.VIDEOS_PLAYED_COLUMN: 0,
            self.FORUM_POSTS_COLUMN: 0,
            self.FORUM_REPLIES_COLUMN: 0,
            self.FORUM_COMMENTS_COLUMN: 0,
            self.TEXTBOOK_PAGES_COLUMN: 0,
            self.LAST_SUBSECTION_COLUMN: '',
        })

    def _check_output(self, inputs, column_values):
        """Compare generated with expected output."""
        output = self._get_reducer_output(inputs)
        self.assertEquals(len(output), 1)
        for column_num, expected_value in column_values.iteritems():
            self.assertEquals(output[0][column_num], expected_value)

    def test_single_problem_attempted(self):
        inputs = [
            ('i4x://foo/bar/baz', 'problem_check', json.dumps({'correct': True}), self.DATE)
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            self.PROBLEMS_ATTEMPTED_COLUMN: 1,
            self.PROBLEM_ATTEMPTS_COLUMN: 1,
            self.PROBLEMS_CORRECT_COLUMN: 1,
        })

    def test_single_problem_attempted_incorrect(self):
        inputs = [
            ('i4x://foo/bar/baz', 'problem_check', '{}', self.DATE)
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            self.PROBLEMS_ATTEMPTED_COLUMN: 1,
            self.PROBLEM_ATTEMPTS_COLUMN: 1,
            self.PROBLEMS_CORRECT_COLUMN: 0,
        })

    def test_single_problem_attempted_multiple_events(self):
        inputs = [
            ('i4x://foo/bar/baz', 'problem_check', json.dumps({'correct': True}), self.DATE),
            ('i4x://foo/bar/baz', 'problem_check', json.dumps({'correct': True}), self.DATE),
            ('i4x://foo/bar/baz', 'problem_check', '{}', self.DATE)
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            self.PROBLEMS_ATTEMPTED_COLUMN: 1,
            self.PROBLEM_ATTEMPTS_COLUMN: 3,
            self.PROBLEMS_CORRECT_COLUMN: 1,
        })

    def test_multiple_problems_attempted(self):
        inputs = [
            ('i4x://foo/bar/baz', 'problem_check', json.dumps({'correct': True}), self.DATE),
            ('i4x://foo/bar/baz2', 'problem_check', json.dumps({'correct': True}), self.DATE),
            ('i4x://foo/bar/baz', 'problem_check', '{}', self.DATE)
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            self.PROBLEMS_ATTEMPTED_COLUMN: 2,
            self.PROBLEM_ATTEMPTS_COLUMN: 3,
            self.PROBLEMS_CORRECT_COLUMN: 2,
        })

    def test_single_video_played(self):
        inputs = [
            ('foobarbaz', 'play_video', '{}', self.DATE),
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            self.VIDEOS_PLAYED_COLUMN: 1,
        })

    def test_multiple_video_plays_same_video(self):
        inputs = [
            ('foobarbaz', 'play_video', '{}', self.DATE),
            ('foobarbaz', 'play_video', '{}', self.DATE),
            ('foobarbaz', 'play_video', '{}', self.DATE),
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            self.VIDEOS_PLAYED_COLUMN: 1,
        })

    def test_other_video_events(self):
        inputs = [
            ('foobarbaz', 'pause_video', '{}', self.DATE),
            ('foobarbaz2', 'seek_video', '{}', self.DATE),
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            self.VIDEOS_PLAYED_COLUMN: 0,
        })

    @data(
        ('edx.forum.thread.created', FORUM_POSTS_COLUMN),
        ('edx.forum.response.created', FORUM_REPLIES_COLUMN),
        ('edx.forum.comment.created', FORUM_COMMENTS_COLUMN),
        ('book', TEXTBOOK_PAGES_COLUMN),
    )
    @unpack
    def test_count_events(self, event_type, column_num):
        inputs = [
            ('', event_type, '{}', self.DATE),
        ]
        self._check_output(inputs, {
            self.WAS_ACTIVE_COLUMN: 1,
            column_num: 1,
        })

    @data(
        ('edx.forum.thread.created', FORUM_POSTS_COLUMN),
        ('edx.forum.response.created', FORUM_REPLIES_COLUMN),
        ('edx.forum.comment.created', FORUM_COMMENTS_COLUMN),
        ('book', TEXTBOOK_PAGES_COLUMN),
    )
    @unpack
    def test_multiple_counted_events(self, event_type, column_num):
        inputs = [
            ('', event_type, '{}', self.DATE),
            ('', event_type, '{}', self.DATE),
        ]
        self._check_output(inputs, {
            column_num: 2,
        })

    def test_last_subsection(self):
        inputs = [
            ('', SUBSECTION_VIEWED_MARKER, json.dumps({
                'path': 'foobar',
                'timestamp': '2014-12-01T00:00:00.000000',
            }), self.DATE),
        ]
        self._check_output(inputs, {
            self.LAST_SUBSECTION_COLUMN: 'foobar',
        })

    def test_multiple_subsection_views(self):
        inputs = [
            ('', SUBSECTION_VIEWED_MARKER, json.dumps({
                'path': 'finalpath',
                'timestamp': '2014-12-01T00:00:04.000000',
            }), self.DATE),
            ('', SUBSECTION_VIEWED_MARKER, json.dumps({
                'path': 'foobar',
                'timestamp': '2014-12-01T00:00:00.000000',
            }), self.DATE),
            ('', SUBSECTION_VIEWED_MARKER, json.dumps({
                'path': 'foobar1',
                'timestamp': '2014-12-01T00:00:03.000000',
            }), self.DATE),
        ]
        self._check_output(inputs, {
            self.LAST_SUBSECTION_COLUMN: 'finalpath',
        })
class BaseStudentEngagementTaskMapTest(InitializeOpaqueKeysMixin, unittest.TestCase):
    """Base class for test analysis of detailed student engagement"""

    DEFAULT_USER_ID = 10
    DEFAULT_TIMESTAMP = "2013-12-17T15:38:32.805444"
    DEFAULT_DATE = "2013-12-17"

    def setUp(self):
        self.initialize_ids()
        self.video_id = 'i4x-foo-bar-baz'
        self.event_templates = {
            'play_video': {
                "username": "******",
                "host": "test_host",
                "event_source": "browser",
                "event_type": "play_video",
                "context": {
                    "course_id": self.course_id,
                    "org_id": self.org_id,
                    "user_id": self.DEFAULT_USER_ID,
                },
                "time": "{0}+00:00".format(self.DEFAULT_TIMESTAMP),
                "ip": "127.0.0.1",
                "event": '{"id": "%s", "currentTime": "23.4398", "code": "87389iouhdfh"}' % self.video_id,
                "agent": "blah, blah, blah",
                "page": None
            },
            'problem_check': {
                "username": "******",
                "host": "test_host",
                "event_source": "server",
                "event_type": "problem_check",
                "context": {
                    "course_id": self.course_id,
                    "org_id": self.org_id,
                    "user_id": self.DEFAULT_USER_ID,
                },
                "time": "{0}+00:00".format(self.DEFAULT_TIMESTAMP),
                "ip": "127.0.0.1",
                "event": {
                    "problem_id": self.problem_id,
                    "success": "incorrect",
                },
                "agent": "blah, blah, blah",
                "page": None
            }
        }
        self.default_key = (self.DEFAULT_DATE, self.course_id, 'test_user')

    def create_task(self, interval=None, interval_type=None):
        """Allow arguments to be passed to the task constructor."""
        if not interval:
            interval = self.DEFAULT_DATE
        self.task = StudentEngagementTask(
            interval=luigi.DateIntervalParameter().parse(interval),
            output_root='/fake/output',
            interval_type=interval_type,
        )
        self.task.init_local()

    def create_event_log_line(self, **kwargs):
        """Create an event log with test values, as a JSON string."""
        return json.dumps(self._create_event_dict(**kwargs))

    def _create_event_dict(self, **kwargs):
        """Create an event log with test values, as a dict."""
        # Define default values for event log entry.
        # event_dict = kwargs.pop('template', self.event_templates['play_video']).copy()
        event_dict = kwargs.pop('template', self.event_templates['problem_check']).copy()
        event_dict.update(**kwargs)
        return event_dict

    def assert_single_map_output(self, line, expected_key, expected_value):
        """Assert that an input line generates exactly one output record with the expected key and value"""
        mapper_output = tuple(self.task.mapper(line))
        self.assertEquals(len(mapper_output), 1)
        row = mapper_output[0]
        self.assertEquals(len(row), 2)
        actual_key, actual_value = row
        self.assertEquals(expected_key, actual_key)
        self.assertEquals(expected_value, actual_value)

    def assert_no_map_output_for(self, line):
        """Assert that an input line generates no output."""
        self.assertEquals(
            tuple(self.task.mapper(line)),
            tuple()
        )

    def assert_date_mappings(self, expected_end_date, actual_event_date):
        """Asserts that an event_date is mapped to the expected date in the key."""
        self.assert_single_map_output(
            self.create_event_log_line(time="{}T15:38:32.805444".format(actual_event_date)),
            (expected_end_date, self.course_id, 'test_user'),
            (self.problem_id, 'problem_check', '{}', actual_event_date)
        )