def get_context_data(self, **kwargs): self.presenter = CourseEngagementVideoPresenter(self.access_token, self.course_id) context = super(EngagementVideoContentTemplateView, self).get_context_data(**kwargs) context.update({ 'sections': self.presenter.sections(), 'update_message': self.get_last_updated_message(self.presenter.last_updated), 'no_data_message': self.no_data_message }) return context
class EngagementVideoContentTemplateView(CourseStructureMixin, CourseStructureExceptionMixin, EngagementTemplateView): page_title = _('Engagement Videos') active_secondary_nav_item = 'videos' section_id = None subsection_id = None # Translators: Do not translate UTC. update_message = _( 'Video data was last updated %(update_date)s at %(update_time)s UTC.') no_data_message = _( 'Looks like no one has watched any videos in these sections.') def get_context_data(self, **kwargs): self.presenter = CourseEngagementVideoPresenter( self.access_token, self.course_id) context = super(EngagementVideoContentTemplateView, self).get_context_data(**kwargs) context.update({ 'sections': self.presenter.sections(), 'update_message': self.get_last_updated_message(self.presenter.last_updated), 'no_data_message': self.no_data_message }) return context
def get_context_data(self, **kwargs): self.presenter = CourseEngagementVideoPresenter(self.access_token, self.course_id) context = super(EngagementVideoContentTemplateView, self).get_context_data(**kwargs) context['js_data']['course'].update({ 'showVideoCount': True, # overwrite to hide video count column }) context.update({ 'sections': self.presenter.sections(), 'update_message': self.get_last_updated_message(self.presenter.last_updated), 'no_data_message': self.no_data_message }) return context
class EngagementVideoContentTemplateView(CourseStructureMixin, CourseStructureExceptionMixin, EngagementTemplateView): page_title = _('Engagement Videos') active_secondary_nav_item = 'videos' section_id = None subsection_id = None # Translators: Do not translate UTC. update_message = _('Video data was last updated %(update_date)s at %(update_time)s UTC.') no_data_message = _('Looks like no one has watched any videos in these sections.') def get_context_data(self, **kwargs): self.presenter = CourseEngagementVideoPresenter(self.access_token, self.course_id) context = super(EngagementVideoContentTemplateView, self).get_context_data(**kwargs) context.update({ 'sections': self.presenter.sections(), 'update_message': self.get_last_updated_message(self.presenter.last_updated), 'no_data_message': self.no_data_message }) return context
class EngagementVideoContentTemplateView(CourseStructureMixin, CourseStructureExceptionMixin, EngagementTemplateView): page_title = _('Engagement Videos') active_secondary_nav_item = 'videos' section_id = None subsection_id = None # Translators: Do not translate UTC. update_message = _('Video data was last updated %(update_date)s at %(update_time)s UTC.') no_data_message = _('No videos watched for these exercises.') def get_context_data(self, **kwargs): self.presenter = CourseEngagementVideoPresenter(self.access_token, self.course_id) context = super(EngagementVideoContentTemplateView, self).get_context_data(**kwargs) context['js_data']['course'].update({ 'showVideoCount': True, # overwrite to hide video count column }) context.update({ 'sections': self.presenter.sections(), 'update_message': self.get_last_updated_message(self.presenter.last_updated), 'no_data_message': self.no_data_message }) return context
def setUp(self): super(CourseEngagementVideoPresenterTests, self).setUp() self.course_id = 'this/course/id' self.presenter = CourseEngagementVideoPresenter(None, self.course_id)
class CourseEngagementVideoPresenterTests(SwitchMixin, TestCase): SECTION_ID = 'i4x://edX/DemoX/chapter/9fca584977d04885bc911ea76a9ef29e' SUBSECTION_ID = 'i4x://edX/DemoX/sequential/07bc32474380492cb34f76e5f9d9a135' VIDEO_ID = 'i4x://edX/DemoX/video/0b9e39477cf34507a7a48f74be381fdd' VIDEO_1 = VideoFixture() VIDEO_2 = VideoFixture() VIDEO_3 = VideoFixture() def setUp(self): super(CourseEngagementVideoPresenterTests, self).setUp() self.course_id = 'this/course/id' self.presenter = CourseEngagementVideoPresenter(None, self.course_id) def test_default_block_data(self): self.assertDictEqual(self.presenter.default_block_data, { 'users_at_start': 0, 'users_at_end': 0, 'end_percent': 0, 'start_only_users': 0, 'start_only_percent': 0, }) def _create_graded_and_ungraded_course_structure_fixtures(self): """ Create graded and ungraded video sections. """ chapter_fixture = ChapterFixture() # a dictionary to access the fixtures easily course_structure_fixtures = { 'chapter': chapter_fixture, 'course': CourseFixture(org='this', course='course', run='id') } for grade_status in ['graded', 'ungraded']: sequential_fixture = SequentialFixture(graded=grade_status is 'graded').add_children( VerticalFixture().add_children( VideoFixture() ) ) course_structure_fixtures[grade_status] = { 'sequential': sequential_fixture, } chapter_fixture.add_children(sequential_fixture) course_structure_fixtures['course'].add_children(chapter_fixture) return course_structure_fixtures @data('graded', 'ungraded') @override_settings(CACHES={ 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } }) def test_graded_modes(self, grade_status): """ Ensure that video structure will be retrieved for both graded and ungraded. """ course_structure_fixtures = self._create_graded_and_ungraded_course_structure_fixtures() course_fixture = course_structure_fixtures['course'] chapter_fixture = course_structure_fixtures['chapter'] with mock.patch('slumber.Resource.get', mock.Mock(return_value=course_fixture.course_structure())): with mock.patch('analyticsclient.course.Course.videos', mock.Mock(return_value=utils.get_mock_video_data(course_fixture))): # check that we get results for both graded and ungraded sequential_fixture = course_structure_fixtures[grade_status]['sequential'] video_id = sequential_fixture.children[0].children[0].id actual_videos = self.presenter.subsection_children(chapter_fixture.id, sequential_fixture.id) expected_url = reverse('courses:engagement:video_timeline', kwargs={ 'course_id': self.course_id, 'section_id': chapter_fixture.id, 'subsection_id': sequential_fixture.id, 'video_id': video_id }) expected = [{'id': utils.get_encoded_module_id(video_id), 'url': expected_url}] self.assertEqual(len(actual_videos), len(expected)) for index, actual_video in enumerate(actual_videos): self.assertDictContainsSubset(expected[index], actual_video) def test_module_id_to_data_id(self): opaque_key_id = 'i4x-edX-DemoX-video-0b9e39477cf34507a7a48f74be381fdd' module_id = 'i4x://edX/DemoX/video/0b9e39477cf34507a7a48f74be381fdd' self.assertEqual(self.presenter.module_id_to_data_id({'id': module_id}), opaque_key_id) block_id = 'block-v1:edX+DemoX.1+2014+type@problem+block@466f474fa4d045a8b7bde1b911e095ca' self.assertEqual(self.presenter.module_id_to_data_id({'id': block_id}), '466f474fa4d045a8b7bde1b911e095ca') def test_post_process_adding_data_to_blocks(self): def url_func(parent_block, child_block): return '{}-{}'.format(parent_block, child_block) user_data = {'users_at_start': 10} self.presenter.post_process_adding_data_to_blocks(user_data, 'parent', 'child', url_func) self.assertDictContainsSubset({'url': 'parent-child'}, user_data) empty_data = {} self.presenter.post_process_adding_data_to_blocks(empty_data, 'parent', 'child', None) self.assertDictEqual({}, empty_data) def test_build_module_url_func(self): url_func = self.presenter.build_module_url_func(self.SECTION_ID) actual_url = url_func({'id': self.SUBSECTION_ID}, {'id': self.VIDEO_ID}) expected_url = reverse('courses:engagement:video_timeline', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, 'subsection_id': self.SUBSECTION_ID, 'video_id': self.VIDEO_ID }) self.assertEqual(actual_url, expected_url) def test_build_subsection_url_func(self): url_func = self.presenter.build_subsection_url_func(self.SECTION_ID) actual_url = url_func({'id': self.SUBSECTION_ID}) expected_url = reverse('courses:engagement:video_subsection', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, 'subsection_id': self.SUBSECTION_ID, }) self.assertEqual(actual_url, expected_url) def test_build_section_url_func(self): actual_url = self.presenter.build_section_url({'id': self.SECTION_ID}) expected_url = reverse('courses:engagement:video_section', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, }) self.assertEqual(actual_url, expected_url) def test_attach_computed_data(self): max_users = 15 start_only_users = 10 end_users = max_users - start_only_users end_percent = end_users / max_users module_data = { 'encoded_module_id': self.VIDEO_ID, 'users_at_start': max_users, 'users_at_end': end_users } self.presenter.attach_computed_data(module_data) self.assertDictEqual(module_data, { 'id': self.VIDEO_ID, 'users_at_start': max_users, 'users_at_end': end_users, 'end_percent': end_percent, 'start_only_users': start_only_users, 'start_only_percent': start_only_users / max_users, }) def test_greater_users_at_end(self): module_data = { 'encoded_module_id': self.VIDEO_ID, 'users_at_start': 0, 'users_at_end': 1 } self.presenter.attach_computed_data(module_data) self.assertDictEqual(module_data, { 'id': self.VIDEO_ID, 'users_at_start': 0, 'users_at_end': 1, 'end_percent': 1.0, 'start_only_users': 0, 'start_only_percent': 0.0, }) def test_attach_aggregated_data_to_parent(self): parent = { 'num_modules': 2, 'children': [ { 'users_at_start': 60, 'users_at_end': 40, }, { 'users_at_start': 0, 'users_at_end': 0, }, ] } expected = copy.deepcopy(parent) expected.update({ 'users_at_start': 60, 'users_at_end': 40, 'index': 1, 'average_start_only_users': 10, 'average_users_at_end': 20, 'end_percent': 2/3, 'start_only_users': 20, 'start_only_percent': 1/3, }) self.presenter.attach_aggregated_data_to_parent(0, parent) self.assertDictEqual(parent, expected) @mock.patch('analyticsclient.course.Course.videos') def test_fetch_course_module_data(self, mock_videos): factory = CourseEngagementDataFactory() videos = factory.videos() mock_videos.return_value = videos self.assertListEqual(self.presenter.fetch_course_module_data(), videos) mock_videos.side_effect = NoVideosError(course_id=self.course_id) with self.assertRaises(NoVideosError): self.presenter.fetch_course_module_data() @mock.patch('analyticsclient.module.Module.video_timeline') def test_get_video_timeline(self, mock_timeline): factory = CourseEngagementDataFactory() video_module = { 'pipeline_video_id': 'edX/DemoX/Demo_Course|i4x-edX-DemoX-videoalpha-0b9e39477cf34507a7a48f74be381fdd', 'segment_length': 5, 'duration': None } # duration can be null/None mock_timeline.return_value = factory.get_video_timeline_api_response() actual_timeline = self.presenter.get_video_timeline(video_module) expected_timeline = factory.get_presented_video_timeline(duration=495) self.assertEqual(100, len(actual_timeline)) self.assertTimeline(expected_timeline, actual_timeline) video_module['duration'] = 499 mock_timeline.return_value = factory.get_video_timeline_api_response() actual_timeline = self.presenter.get_video_timeline(video_module) last_segment = expected_timeline[-1].copy() last_segment.update({ 'segment': last_segment['segment'] + 1, 'start_time': video_module['duration'] }) expected_timeline.append(last_segment) self.assertEqual(101, len(actual_timeline)) self.assertTimeline(expected_timeline, actual_timeline) video_module['duration'] = 501 expected_timeline[-1].update({ 'start_time': 500, 'num_users': 0, 'num_views': 0, 'num_replays': 0 }) last_segment = expected_timeline[-1].copy() last_segment.update({ 'segment': last_segment['segment'] + 1, 'start_time': video_module['duration'] }) expected_timeline.append(last_segment) mock_timeline.return_value = factory.get_video_timeline_api_response() actual_timeline = self.presenter.get_video_timeline(video_module) self.assertEqual(102, len(actual_timeline)) self.assertTimeline(expected_timeline, actual_timeline) def assertTimeline(self, expected_timeline, actual_timeline): self.assertEqual(len(expected_timeline), len(actual_timeline)) for expected, actual in zip(expected_timeline, actual_timeline): self.assertDictContainsSubset(actual, expected) def test_build_live_url(self): actual_view_live_url = self.presenter.build_view_live_url('a-url', self.VIDEO_ID) self.assertEqual('a-url/{}/jump_to/{}'.format(self.course_id, self.VIDEO_ID), actual_view_live_url) self.assertEqual(None, self.presenter.build_view_live_url(None, self.VIDEO_ID)) @data( (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1 ) ) ) ), VIDEO_1, 0, VIDEO_1), (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1, VIDEO_2 ) ) ) ), VIDEO_1, 1, VIDEO_2), (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1, VIDEO_2 ) ) ) ), VIDEO_2, -1, VIDEO_1), (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1, ), VerticalFixture().add_children( VIDEO_2, ) ) ) ), VIDEO_1, 1, VIDEO_2), (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1, ), ), SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_2, ), ) ) ), VIDEO_1, 1, VIDEO_2), (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1, ), ), ), ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_2, ), ), ) ), VIDEO_1, 1, VIDEO_2), (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1, ), ), ), ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_2 ), ), ), ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_3 ), ), ) ), VIDEO_1, 2, VIDEO_3), (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1 ) ) ) ), VIDEO_1, -1, None), (CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( VIDEO_1 ) ) ) ), VIDEO_1, 1, None), ) @unpack @override_settings(CACHES={ 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } }) def test_sibling(self, fixture, start_block, offset, expected_sibling_block): """Tests the _sibling method of the `CourseAPIPresenterMixin`.""" with mock.patch( 'analyticsclient.course.Course.videos', mock.Mock(return_value=utils.get_mock_video_data(fixture)) ): with mock.patch('slumber.Resource.get', mock.Mock(return_value=fixture.course_structure())): sibling = self.presenter.sibling_block(utils.get_encoded_module_id(start_block['id']), offset) if expected_sibling_block is None: self.assertIsNone(sibling) else: self.assertEqual(sibling['id'], utils.get_encoded_module_id(expected_sibling_block['id'])) @override_settings(CACHES={ 'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', } }) def test_sibling_no_data(self): """ Verify that _sibling() skips over siblings with no data (no associated URL). """ fixture = CourseFixture().add_children( ChapterFixture().add_children( SequentialFixture().add_children( VerticalFixture().add_children( self.VIDEO_1, self.VIDEO_2, # self.VIDEO_2 will have no data self.VIDEO_3 ) ) ) ) with mock.patch( 'analyticsclient.course.Course.videos', mock.Mock(return_value=utils.get_mock_video_data(fixture, excluded_module_ids=[self.VIDEO_2['id']])) ): with mock.patch('slumber.Resource.get', mock.Mock(return_value=fixture.course_structure())): sibling = self.presenter.sibling_block(utils.get_encoded_module_id(self.VIDEO_1['id']), 1) self.assertEqual(sibling['id'], utils.get_encoded_module_id(self.VIDEO_3['id'])) @data('http://example.com', 'http://example.com/') def test_build_render_xblock_url(self, xblock_render_base): self.assertIsNone(self.presenter.build_render_xblock_url(None, None)) expected_url = '/'.join(str(arg).rstrip('/') for arg in [xblock_render_base, self.VIDEO_ID]) self.assertEqual(expected_url, self.presenter.build_render_xblock_url(xblock_render_base, self.VIDEO_ID))
class CourseEngagementVideoPresenterTests(SwitchMixin, TestCase): SECTION_ID = 'i4x://edX/DemoX/chapter/9fca584977d04885bc911ea76a9ef29e' SUBSECTION_ID = 'i4x://edX/DemoX/sequential/07bc32474380492cb34f76e5f9d9a135' VIDEO_ID = 'i4x://edX/DemoX/video/0b9e39477cf34507a7a48f74be381fdd' def setUp(self): super(CourseEngagementVideoPresenterTests, self).setUp() self.course_id = 'this/course/id' self.presenter = CourseEngagementVideoPresenter(None, self.course_id) def test_default_block_data(self): self.assertDictEqual(self.presenter.default_block_data, { 'start_views': 0, 'end_views': 0, 'end_percent': 0, 'start_only_views': 0, 'start_only_percent': 0, }) def test_module_id_to_data_id(self): opaque_key_id = 'i4x-edX-DemoX-video-0b9e39477cf34507a7a48f74be381fdd' module_id = 'i4x://edX/DemoX/video/0b9e39477cf34507a7a48f74be381fdd' self.assertEqual(self.presenter.module_id_to_data_id({'id': module_id}), opaque_key_id) block_id = 'block-v1:edX+DemoX.1+2014+type@problem+block@466f474fa4d045a8b7bde1b911e095ca' self.assertEqual(self.presenter.module_id_to_data_id({'id': block_id}), '466f474fa4d045a8b7bde1b911e095ca') def test_post_process_adding_data_to_blocks(self): def url_func(parent_block, child_block): return '{}-{}'.format(parent_block, child_block) data = {'start_views': 10} self.presenter.post_process_adding_data_to_blocks(data, 'parent', 'child', url_func) self.assertDictContainsSubset({'url': 'parent-child'}, data) data = {} self.presenter.post_process_adding_data_to_blocks(data, 'parent', 'child', None) self.assertDictEqual({}, data) def test_build_module_url_func(self): url_func = self.presenter.build_module_url_func(self.SECTION_ID) actual_url = url_func({'id': self.SUBSECTION_ID}, {'id': self.VIDEO_ID}) expected_url = reverse('courses:engagement:video_timeline', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, 'subsection_id': self.SUBSECTION_ID, 'video_id': self.VIDEO_ID }) self.assertEqual(actual_url, expected_url) def test_build_subsection_url_func(self): url_func = self.presenter.build_subsection_url_func(self.SECTION_ID) actual_url = url_func({'id': self.SUBSECTION_ID}) expected_url = reverse('courses:engagement:video_subsection', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, 'subsection_id': self.SUBSECTION_ID, }) self.assertEqual(actual_url, expected_url) def test_build_section_url_func(self): actual_url = self.presenter.build_section_url({'id': self.SECTION_ID}) expected_url = reverse('courses:engagement:video_section', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, }) self.assertEqual(actual_url, expected_url) def test_attach_computed_data(self): data = { 'encoded_module_id': self.VIDEO_ID, 'start_views': 15, 'end_views': 5 } self.presenter.attach_computed_data(data) self.assertDictEqual(data, { 'id': self.VIDEO_ID, 'start_views': 15, 'end_views': 5, 'end_percent': 0.25, 'start_only_views': 10, 'start_only_percent': 0.5, }) @mock.patch('analyticsclient.course.Course.videos') def test_fetch_course_module_data(self, mock_videos): videos = [ { "pipeline_video_id": "edX/DemoX/Demo_Course|i4x-edX-DemoX-video-7e9b434e6de3435ab99bd3fb25bde807", "encoded_module_id": "i4x-edX-DemoX-video-7e9b434e6de3435ab99bd3fb25bde807", "duration": 257, "segment_length": 5, "start_views": 10, "end_views": 0, "created": "2015-04-15T214158" }, { "pipeline_video_id": "edX/DemoX/Demo_Course|i4x-edX-DemoX-videoalpha-0b9e39477cf34507a7a48f74be381fdd", "encoded_module_id": "i4x-edX-DemoX-videoalpha-0b9e39477cf34507a7a48f74be381fdd", "duration": 195, "segment_length": 5, "start_views": 55, "end_views": 0, "created": "2015-04-15T214158" } ] mock_videos.return_value = videos self.assertListEqual(self.presenter.fetch_course_module_data(), videos) mock_videos.side_effect = NoVideosError(course_id=self.course_id) with self.assertRaises(NoVideosError): self.presenter.fetch_course_module_data()
class CourseEngagementVideoPresenterTests(SwitchMixin, TestCase): SECTION_ID = 'i4x://edX/DemoX/chapter/9fca584977d04885bc911ea76a9ef29e' SUBSECTION_ID = 'i4x://edX/DemoX/sequential/07bc32474380492cb34f76e5f9d9a135' VIDEO_ID = 'i4x://edX/DemoX/video/0b9e39477cf34507a7a48f74be381fdd' def setUp(self): super(CourseEngagementVideoPresenterTests, self).setUp() self.course_id = 'this/course/id' self.presenter = CourseEngagementVideoPresenter(None, self.course_id) def test_default_block_data(self): self.assertDictEqual(self.presenter.default_block_data, { 'users_at_start': 0, 'users_at_end': 0, 'end_percent': 0, 'start_only_users': 0, 'start_only_percent': 0, }) def test_module_id_to_data_id(self): opaque_key_id = 'i4x-edX-DemoX-video-0b9e39477cf34507a7a48f74be381fdd' module_id = 'i4x://edX/DemoX/video/0b9e39477cf34507a7a48f74be381fdd' self.assertEqual(self.presenter.module_id_to_data_id({'id': module_id}), opaque_key_id) block_id = 'block-v1:edX+DemoX.1+2014+type@problem+block@466f474fa4d045a8b7bde1b911e095ca' self.assertEqual(self.presenter.module_id_to_data_id({'id': block_id}), '466f474fa4d045a8b7bde1b911e095ca') def test_post_process_adding_data_to_blocks(self): def url_func(parent_block, child_block): return '{}-{}'.format(parent_block, child_block) user_data = {'users_at_start': 10} self.presenter.post_process_adding_data_to_blocks(user_data, 'parent', 'child', url_func) self.assertDictContainsSubset({'url': 'parent-child'}, user_data) empty_data = {} self.presenter.post_process_adding_data_to_blocks(empty_data, 'parent', 'child', None) self.assertDictEqual({}, empty_data) def test_build_module_url_func(self): url_func = self.presenter.build_module_url_func(self.SECTION_ID) actual_url = url_func({'id': self.SUBSECTION_ID}, {'id': self.VIDEO_ID}) expected_url = reverse('courses:engagement:video_timeline', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, 'subsection_id': self.SUBSECTION_ID, 'video_id': self.VIDEO_ID }) self.assertEqual(actual_url, expected_url) def test_build_subsection_url_func(self): url_func = self.presenter.build_subsection_url_func(self.SECTION_ID) actual_url = url_func({'id': self.SUBSECTION_ID}) expected_url = reverse('courses:engagement:video_subsection', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, 'subsection_id': self.SUBSECTION_ID, }) self.assertEqual(actual_url, expected_url) def test_build_section_url_func(self): actual_url = self.presenter.build_section_url({'id': self.SECTION_ID}) expected_url = reverse('courses:engagement:video_section', kwargs={ 'course_id': self.course_id, 'section_id': self.SECTION_ID, }) self.assertEqual(actual_url, expected_url) def test_attach_computed_data(self): max_users = 15 start_only_users = 10 end_users = max_users - start_only_users end_percent = end_users / max_users module_data = { 'encoded_module_id': self.VIDEO_ID, 'users_at_start': max_users, 'users_at_end': end_users } self.presenter.attach_computed_data(module_data) self.assertDictEqual(module_data, { 'id': self.VIDEO_ID, 'users_at_start': max_users, 'users_at_end': end_users, 'end_percent': end_percent, 'start_only_users': start_only_users, 'start_only_percent': start_only_users / max_users, }) @mock.patch('analyticsclient.course.Course.videos') def test_fetch_course_module_data(self, mock_videos): videos = [ { "pipeline_video_id": "edX/DemoX/Demo_Course|i4x-edX-DemoX-video-7e9b434e6de3435ab99bd3fb25bde807", "encoded_module_id": "i4x-edX-DemoX-video-7e9b434e6de3435ab99bd3fb25bde807", "duration": 257, "segment_length": 5, "users_at_start": 10, "users_at_end": 0, "created": "2015-04-15T214158" }, { "pipeline_video_id": "edX/DemoX/Demo_Course|i4x-edX-DemoX-videoalpha-0b9e39477cf34507a7a48f74be381fdd", "encoded_module_id": "i4x-edX-DemoX-videoalpha-0b9e39477cf34507a7a48f74be381fdd", "duration": 195, "segment_length": 5, "users_at_start": 55, "users_at_end": 0, "created": "2015-04-15T214158" } ] mock_videos.return_value = videos self.assertListEqual(self.presenter.fetch_course_module_data(), videos) mock_videos.side_effect = NoVideosError(course_id=self.course_id) with self.assertRaises(NoVideosError): self.presenter.fetch_course_module_data() @mock.patch('analyticsclient.module.Module.video_timeline') def test_get_video_timeline(self, mock_timeline): factory = CourseEngagementDataFactory() video_module = { 'pipeline_video_id': 'edX/DemoX/Demo_Course|i4x-edX-DemoX-videoalpha-0b9e39477cf34507a7a48f74be381fdd', 'segment_length': 5, 'duration': None } # duration can be null/None mock_timeline.return_value = factory.get_video_timeline_api_response() actual_timeline = self.presenter.get_video_timeline(video_module) expected_timeline = factory.get_presented_video_timeline(duration=495) self.assertEqual(100, len(actual_timeline)) self.assertTimeline(expected_timeline, actual_timeline) video_module['duration'] = 499 mock_timeline.return_value = factory.get_video_timeline_api_response() actual_timeline = self.presenter.get_video_timeline(video_module) last_segment = expected_timeline[-1].copy() last_segment.update({ 'segment': last_segment['segment'] + 1, 'start_time': video_module['duration'] }) expected_timeline.append(last_segment) self.assertEqual(101, len(actual_timeline)) self.assertTimeline(expected_timeline, actual_timeline) video_module['duration'] = 501 expected_timeline[-1].update({ 'start_time': 500, 'num_users': 0, 'num_views': 0, 'num_replays': 0 }) last_segment = expected_timeline[-1].copy() last_segment.update({ 'segment': last_segment['segment'] + 1, 'start_time': video_module['duration'] }) expected_timeline.append(last_segment) mock_timeline.return_value = factory.get_video_timeline_api_response() actual_timeline = self.presenter.get_video_timeline(video_module) self.assertEqual(102, len(actual_timeline)) self.assertTimeline(expected_timeline, actual_timeline) def assertTimeline(self, expected_timeline, actual_timeline): self.assertEqual(len(expected_timeline), len(actual_timeline)) for expected, actual in zip(expected_timeline, actual_timeline): self.assertDictContainsSubset(actual, expected) def test_build_live_url(self): actual_view_live_url = self.presenter.build_view_live_url('a-url', self.VIDEO_ID) self.assertEqual('a-url/{}/jump_to/{}'.format(self.course_id, self.VIDEO_ID), actual_view_live_url) self.assertEqual(None, self.presenter.build_view_live_url(None, self.VIDEO_ID))
def setUp(self): super(CourseEngagementVideoPresenterTests, self).setUp() self.course_id = 'this/course/id' self.presenter = CourseEngagementVideoPresenter(settings.COURSE_API_KEY, self.course_id)
def setUp(self): super(CourseEngagementVideoPresenterTests, self).setUp() self.course_id = 'this/course/id' self.presenter = CourseEngagementVideoPresenter( settings.COURSE_API_KEY, self.course_id)