# -*- coding: utf-8 -*- """ Tests for video outline API """ # pylint: disable=no-member import ddt import itertools from uuid import uuid4 from collections import namedtuple from edxval import api from mobile_api.models import MobileApiConfig from xmodule.modulestore.tests.factories import ItemFactory from xmodule.video_module import transcripts_utils from xmodule.modulestore.django import modulestore from xmodule.partitions.partitions import Group, UserPartition from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory from openedx.core.djangoapps.course_groups.models import CourseUserGroupPartitionGroup from ..testutils import MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin class TestVideoAPITestCase(MobileAPITestCase): """ Base test class for video related mobile APIs """ def setUp(self): super(TestVideoAPITestCase, self).setUp() self.section = ItemFactory.create( parent=self.course, category="chapter", display_name=u"test factory section omega \u03a9", ) self.sub_section = ItemFactory.create( parent=self.section, category="sequential", display_name=u"test subsection omega \u03a9", ) self.unit = ItemFactory.create( parent=self.sub_section, category="vertical", metadata={'graded': True, 'format': 'Homework'}, display_name=u"test unit omega \u03a9", ) self.other_unit = ItemFactory.create( parent=self.sub_section, category="vertical", metadata={'graded': True, 'format': 'Homework'}, display_name=u"test unit omega 2 \u03a9", ) self.nameless_unit = ItemFactory.create( parent=self.sub_section, category="vertical", metadata={'graded': True, 'format': 'Homework'}, display_name=None, ) self.edx_video_id = 'testing-123' self.video_url = 'http://val.edx.org/val/video.mp4' self.video_url_high = 'http://val.edx.org/val/video_high.mp4' self.youtube_url = 'http://val.edx.org/val/youtube.mp4' self.html5_video_url = 'http://video.edx.org/html5/video.mp4' api.create_profile('youtube') api.create_profile('mobile_high') api.create_profile('mobile_low') # create the video in VAL api.create_video({ 'edx_video_id': self.edx_video_id, 'status': 'test', 'client_video_id': u"test video omega \u03a9", 'duration': 12, 'courses': [unicode(self.course.id)], 'encoded_videos': [ { 'profile': 'youtube', 'url': 'xyz123', 'file_size': 0, 'bitrate': 1500 }, { 'profile': 'mobile_low', 'url': self.video_url, 'file_size': 12345, 'bitrate': 250 }, { 'profile': 'mobile_high', 'url': self.video_url_high, 'file_size': 99999, 'bitrate': 250 }, ]}) # Set requested profiles MobileApiConfig(video_profiles="mobile_low,mobile_high,youtube").save() class TestVideoAPIMixin(object): """ Mixin class that provides helpers for testing video related mobile APIs """ def _create_video_with_subs(self, custom_subid=None): """ Creates and returns a video with stored subtitles. """ subid = custom_subid or uuid4().hex transcripts_utils.save_subs_to_store( { 'start': [100, 200, 240, 390, 1000], 'end': [200, 240, 380, 1000, 1500], 'text': [ 'subs #1', 'subs #2', 'subs #3', 'subs #4', 'subs #5' ] }, subid, self.course) return ItemFactory.create( parent=self.unit, category="video", edx_video_id=self.edx_video_id, display_name=u"test video omega \u03a9", sub=subid ) def _verify_paths(self, course_outline, path_list, outline_index=0): """ Takes a path_list and compares it against the course_outline Attributes: course_outline (list): A list of dictionaries that includes a 'path' and 'named_path' field which we will be comparing path_list to path_list (list): A list of the expected strings outline_index (int): Index into the course_outline list for which the path is being tested. """ path = course_outline[outline_index]['path'] self.assertEqual(len(path), len(path_list)) for i in range(len(path_list)): self.assertEqual(path_list[i], path[i]['name']) #named_path will be deprecated eventually named_path = course_outline[outline_index]['named_path'] self.assertEqual(len(named_path), len(path_list)) for i in range(len(path_list)): self.assertEqual(path_list[i], named_path[i]) def _setup_course_partitions(self, scheme_id='random', is_cohorted=False): """Helper method to configure the user partitions in the course.""" self.partition_id = 0 # pylint: disable=attribute-defined-outside-init self.course.user_partitions = [ UserPartition( self.partition_id, 'first_partition', 'First Partition', [Group(0, 'alpha'), Group(1, 'beta')], scheme=None, scheme_id=scheme_id ), ] self.course.cohort_config = {'cohorted': is_cohorted} self.store.update_item(self.course, self.user.id) def _setup_group_access(self, xblock, partition_id, group_ids): """Helper method to configure the partition and group mapping for the given xblock.""" xblock.group_access = {partition_id: group_ids} self.store.update_item(xblock, self.user.id) def _setup_split_module(self, sub_block_category): """Helper method to configure a split_test unit with children of type sub_block_category.""" self._setup_course_partitions() self.split_test = ItemFactory.create( # pylint: disable=attribute-defined-outside-init parent=self.unit, category="split_test", display_name=u"split test unit", user_partition_id=0, ) sub_block_a = ItemFactory.create( parent=self.split_test, category=sub_block_category, display_name=u"split test block a", ) sub_block_b = ItemFactory.create( parent=self.split_test, category=sub_block_category, display_name=u"split test block b", ) self.split_test.group_id_to_child = { str(index): url for index, url in enumerate([sub_block_a.location, sub_block_b.location]) } self.store.update_item(self.split_test, self.user.id) return sub_block_a, sub_block_b class TestNonStandardCourseStructure(MobileAPITestCase, TestVideoAPIMixin): """ Tests /api/mobile/v0.5/video_outlines/courses/{course_id} with no course set """ REVERSE_INFO = {'name': 'video-summary-list', 'params': ['course_id']} def setUp(self): super(TestNonStandardCourseStructure, self).setUp() self.chapter_under_course = ItemFactory.create( parent=self.course, category="chapter", display_name=u"test factory chapter under course omega \u03a9", ) self.section_under_course = ItemFactory.create( parent=self.course, category="sequential", display_name=u"test factory section under course omega \u03a9", ) self.section_under_chapter = ItemFactory.create( parent=self.chapter_under_course, category="sequential", display_name=u"test factory section under chapter omega \u03a9", ) self.vertical_under_course = ItemFactory.create( parent=self.course, category="vertical", display_name=u"test factory vertical under course omega \u03a9", ) self.vertical_under_section = ItemFactory.create( parent=self.section_under_chapter, category="vertical", display_name=u"test factory vertical under section omega \u03a9", ) def test_structure_course_video(self): """ Tests when there is a video without a vertical directly under course """ self.login_and_enroll() ItemFactory.create( parent=self.course, category="video", display_name=u"test factory video omega \u03a9", ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) section_url = course_outline[0]["section_url"] unit_url = course_outline[0]["unit_url"] self.assertRegexpMatches(section_url, r'courseware$') self.assertEqual(section_url, unit_url) self._verify_paths(course_outline, []) def test_structure_course_vert_video(self): """ Tests when there is a video under vertical directly under course """ self.login_and_enroll() ItemFactory.create( parent=self.vertical_under_course, category="video", display_name=u"test factory video omega \u03a9", ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) section_url = course_outline[0]["section_url"] unit_url = course_outline[0]["unit_url"] self.assertRegexpMatches( section_url, r'courseware/test_factory_vertical_under_course_omega_%CE%A9/$' ) self.assertEqual(section_url, unit_url) self._verify_paths( course_outline, [ u'test factory vertical under course omega \u03a9' ] ) def test_structure_course_chap_video(self): """ Tests when there is a video directly under chapter """ self.login_and_enroll() ItemFactory.create( parent=self.chapter_under_course, category="video", display_name=u"test factory video omega \u03a9", ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) section_url = course_outline[0]["section_url"] unit_url = course_outline[0]["unit_url"] self.assertRegexpMatches( section_url, r'courseware/test_factory_chapter_under_course_omega_%CE%A9/$' ) self.assertEqual(section_url, unit_url) self._verify_paths( course_outline, [ u'test factory chapter under course omega \u03a9', ] ) def test_structure_course_section_video(self): """ Tests when chapter is none, and video under section under course """ self.login_and_enroll() ItemFactory.create( parent=self.section_under_course, category="video", display_name=u"test factory video omega \u03a9", ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) section_url = course_outline[0]["section_url"] unit_url = course_outline[0]["unit_url"] self.assertRegexpMatches( section_url, r'courseware/test_factory_section_under_course_omega_%CE%A9/$' ) self.assertEqual(section_url, unit_url) self._verify_paths( course_outline, [ u'test factory section under course omega \u03a9', ] ) def test_structure_course_chap_section_video(self): """ Tests when chapter and sequential exists, with a video with no vertical. """ self.login_and_enroll() ItemFactory.create( parent=self.section_under_chapter, category="video", display_name=u"meow factory video omega \u03a9", ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) section_url = course_outline[0]["section_url"] unit_url = course_outline[0]["unit_url"] self.assertRegexpMatches( section_url, ( r'courseware/test_factory_chapter_under_course_omega_%CE%A9/' + 'test_factory_section_under_chapter_omega_%CE%A9/$' ) ) self.assertEqual(section_url, unit_url) self._verify_paths( course_outline, [ u'test factory chapter under course omega \u03a9', u'test factory section under chapter omega \u03a9', ] ) def test_structure_course_section_vert_video(self): """ Tests chapter->section->vertical->unit """ self.login_and_enroll() ItemFactory.create( parent=self.vertical_under_section, category="video", display_name=u"test factory video omega \u03a9", ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) section_url = course_outline[0]["section_url"] unit_url = course_outline[0]["unit_url"] self.assertRegexpMatches( section_url, ( r'courseware/test_factory_chapter_under_course_omega_%CE%A9/' + 'test_factory_section_under_chapter_omega_%CE%A9/$' ) ) self.assertRegexpMatches( unit_url, ( r'courseware/test_factory_chapter_under_course_omega_%CE%A9/' + 'test_factory_section_under_chapter_omega_%CE%A9/1$' ) ) self._verify_paths( course_outline, [ u'test factory chapter under course omega \u03a9', u'test factory section under chapter omega \u03a9', u'test factory vertical under section omega \u03a9' ] ) @ddt.ddt class TestVideoSummaryList( TestVideoAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin, TestVideoAPIMixin # pylint: disable=bad-continuation ): """ Tests for /api/mobile/v0.5/video_outlines/courses/{course_id}.. """ REVERSE_INFO = {'name': 'video-summary-list', 'params': ['course_id']} def test_only_on_web(self): self.login_and_enroll() course_outline = self.api_response().data self.assertEqual(len(course_outline), 0) subid = uuid4().hex transcripts_utils.save_subs_to_store( { 'start': [100], 'end': [200], 'text': [ 'subs #1', ] }, subid, self.course) ItemFactory.create( parent=self.unit, category="video", display_name=u"test video", only_on_web=True, subid=subid ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) self.assertIsNone(course_outline[0]["summary"]["video_url"]) self.assertIsNone(course_outline[0]["summary"]["video_thumbnail_url"]) self.assertEqual(course_outline[0]["summary"]["duration"], 0) self.assertEqual(course_outline[0]["summary"]["size"], 0) self.assertEqual(course_outline[0]["summary"]["name"], "test video") self.assertEqual(course_outline[0]["summary"]["transcripts"], {}) self.assertIsNone(course_outline[0]["summary"]["language"]) self.assertEqual(course_outline[0]["summary"]["category"], "video") self.assertTrue(course_outline[0]["summary"]["only_on_web"]) def test_mobile_api_config(self): """ Tests VideoSummaryList with different MobileApiConfig video_profiles """ self.login_and_enroll() edx_video_id = "testing_mobile_high" api.create_video({ 'edx_video_id': edx_video_id, 'status': 'test', 'client_video_id': u"test video omega \u03a9", 'duration': 12, 'courses': [unicode(self.course.id)], 'encoded_videos': [ { 'profile': 'youtube', 'url': self.youtube_url, 'file_size': 2222, 'bitrate': 4444 }, { 'profile': 'mobile_high', 'url': self.video_url_high, 'file_size': 111, 'bitrate': 333 }, ]}) ItemFactory.create( parent=self.other_unit, category="video", display_name=u"testing mobile high video", edx_video_id=edx_video_id, ) expected_output = { 'category': u'video', 'video_thumbnail_url': None, 'language': u'en', 'name': u'testing mobile high video', 'video_url': self.video_url_high, 'duration': 12.0, 'transcripts': { 'en': 'http://testserver/api/mobile/v0.5/video_outlines/transcripts/{}/testing_mobile_high_video/en'.format(self.course.id) # pylint: disable=line-too-long }, 'only_on_web': False, 'encoded_videos': { u'mobile_high': { 'url': self.video_url_high, 'file_size': 111 }, u'youtube': { 'url': self.youtube_url, 'file_size': 2222 } }, 'size': 111 } # Testing when video_profiles='mobile_low,mobile_high,youtube' course_outline = self.api_response().data course_outline[0]['summary'].pop("id") self.assertEqual(course_outline[0]['summary'], expected_output) # Testing when there is no mobile_low, and that mobile_high doesn't show MobileApiConfig(video_profiles="mobile_low,youtube").save() course_outline = self.api_response().data expected_output['encoded_videos'].pop('mobile_high') expected_output['video_url'] = self.youtube_url expected_output['size'] = 2222 course_outline[0]['summary'].pop("id") self.assertEqual(course_outline[0]['summary'], expected_output) # Testing where youtube is the default video over mobile_high MobileApiConfig(video_profiles="youtube,mobile_high").save() course_outline = self.api_response().data expected_output['encoded_videos']['mobile_high'] = { 'url': self.video_url_high, 'file_size': 111 } course_outline[0]['summary'].pop("id") self.assertEqual(course_outline[0]['summary'], expected_output) def test_video_not_in_val(self): self.login_and_enroll() self._create_video_with_subs() ItemFactory.create( parent=self.other_unit, category="video", edx_video_id="some_non_existent_id_in_val", display_name=u"some non existent video in val", html5_sources=[self.html5_video_url] ) summary = self.api_response().data[1]['summary'] self.assertEqual(summary['name'], "some non existent video in val") self.assertIsNone(summary['encoded_videos']) self.assertIsNone(summary['duration']) self.assertEqual(summary['size'], 0) self.assertEqual(summary['video_url'], self.html5_video_url) def test_course_list(self): self.login_and_enroll() self._create_video_with_subs() ItemFactory.create( parent=self.other_unit, category="video", display_name=u"test video omega 2 \u03a9", html5_sources=[self.html5_video_url] ) ItemFactory.create( parent=self.other_unit, category="video", display_name=u"test video omega 3 \u03a9", source=self.html5_video_url ) ItemFactory.create( parent=self.unit, category="video", edx_video_id=self.edx_video_id, display_name=u"test draft video omega \u03a9", visible_to_staff_only=True, ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 3) vid = course_outline[0] self.assertTrue('test_subsection_omega_%CE%A9' in vid['section_url']) self.assertTrue('test_subsection_omega_%CE%A9/1' in vid['unit_url']) self.assertTrue(u'test_video_omega_\u03a9' in vid['summary']['id']) self.assertEqual(vid['summary']['video_url'], self.video_url) self.assertEqual(vid['summary']['size'], 12345) self.assertTrue('en' in vid['summary']['transcripts']) self.assertFalse(vid['summary']['only_on_web']) self.assertEqual(course_outline[1]['summary']['video_url'], self.html5_video_url) self.assertEqual(course_outline[1]['summary']['size'], 0) self.assertFalse(course_outline[1]['summary']['only_on_web']) self.assertEqual(course_outline[1]['path'][2]['name'], self.other_unit.display_name) self.assertEqual(course_outline[1]['path'][2]['id'], unicode(self.other_unit.location)) self.assertEqual(course_outline[2]['summary']['video_url'], self.html5_video_url) self.assertEqual(course_outline[2]['summary']['size'], 0) self.assertFalse(course_outline[2]['summary']['only_on_web']) def test_with_nameless_unit(self): self.login_and_enroll() ItemFactory.create( parent=self.nameless_unit, category="video", edx_video_id=self.edx_video_id, display_name=u"test draft video omega 2 \u03a9" ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) self.assertEqual(course_outline[0]['path'][2]['name'], self.nameless_unit.location.block_id) def test_with_video_in_sub_section(self): """ Tests a non standard xml format where a video is underneath a sequential We are expecting to return the same unit and section url since there is no unit vertical. """ self.login_and_enroll() ItemFactory.create( parent=self.sub_section, category="video", edx_video_id=self.edx_video_id, display_name=u"video in the sub section" ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) self.assertEqual(len(course_outline[0]['path']), 2) section_url = course_outline[0]["section_url"] unit_url = course_outline[0]["unit_url"] self.assertIn( u'courseware/test_factory_section_omega_%CE%A9/test_subsection_omega_%CE%A9', section_url ) self.assertTrue(section_url) self.assertTrue(unit_url) self.assertEqual(section_url, unit_url) @ddt.data( *itertools.product([True, False], ["video", "problem"]) ) @ddt.unpack def test_with_split_block(self, is_user_staff, sub_block_category): """Test with split_module->sub_block_category and for both staff and non-staff users.""" self.login_and_enroll() self.user.is_staff = is_user_staff self.user.save() self._setup_split_module(sub_block_category) video_outline = self.api_response().data num_video_blocks = 1 if sub_block_category == "video" else 0 self.assertEqual(len(video_outline), num_video_blocks) for block_index in range(num_video_blocks): self._verify_paths( video_outline, [ self.section.display_name, self.sub_section.display_name, self.unit.display_name, self.split_test.display_name ], block_index ) self.assertIn(u"split test block", video_outline[block_index]["summary"]["name"]) def test_with_split_vertical(self): """Test with split_module->vertical->video structure.""" self.login_and_enroll() split_vertical_a, split_vertical_b = self._setup_split_module("vertical") ItemFactory.create( parent=split_vertical_a, category="video", display_name=u"video in vertical a", ) ItemFactory.create( parent=split_vertical_b, category="video", display_name=u"video in vertical b", ) video_outline = self.api_response().data # user should see only one of the videos (a or b). self.assertEqual(len(video_outline), 1) self.assertIn(u"video in vertical", video_outline[0]["summary"]["name"]) a_or_b = video_outline[0]["summary"]["name"][-1:] self._verify_paths( video_outline, [ self.section.display_name, self.sub_section.display_name, self.unit.display_name, self.split_test.display_name, u"split test block " + a_or_b ], ) def _create_cohorted_video(self, group_id): """Creates a cohorted video block, giving access to only the given group_id.""" video_block = ItemFactory.create( parent=self.unit, category="video", display_name=u"video for group " + unicode(group_id), ) self._setup_group_access(video_block, self.partition_id, [group_id]) def _create_cohorted_vertical_with_video(self, group_id): """Creates a cohorted vertical with a child video block, giving access to only the given group_id.""" vertical_block = ItemFactory.create( parent=self.sub_section, category="vertical", display_name=u"vertical for group " + unicode(group_id), ) self._setup_group_access(vertical_block, self.partition_id, [group_id]) ItemFactory.create( parent=vertical_block, category="video", display_name=u"video for group " + unicode(group_id), ) @ddt.data("_create_cohorted_video", "_create_cohorted_vertical_with_video") def test_with_cohorted_content(self, content_creator_method_name): self.login_and_enroll() self._setup_course_partitions(scheme_id='cohort', is_cohorted=True) cohorts = [] for group_id in [0, 1]: getattr(self, content_creator_method_name)(group_id) cohorts.append(CohortFactory(course_id=self.course.id, name=u"Cohort " + unicode(group_id))) link = CourseUserGroupPartitionGroup( course_user_group=cohorts[group_id], partition_id=self.partition_id, group_id=group_id, ) link.save() for cohort_index in range(len(cohorts)): # add user to this cohort cohorts[cohort_index].users.add(self.user) # should only see video for this cohort video_outline = self.api_response().data self.assertEqual(len(video_outline), 1) self.assertEquals( u"video for group " + unicode(cohort_index), video_outline[0]["summary"]["name"] ) # remove user from this cohort cohorts[cohort_index].users.remove(self.user) # un-cohorted user should see no videos video_outline = self.api_response().data self.assertEqual(len(video_outline), 0) # staff user sees all videos self.user.is_staff = True self.user.save() video_outline = self.api_response().data self.assertEqual(len(video_outline), 2) def test_with_hidden_blocks(self): self.login_and_enroll() hidden_subsection = ItemFactory.create( parent=self.section, category="sequential", hide_from_toc=True, ) unit_within_hidden_subsection = ItemFactory.create( parent=hidden_subsection, category="vertical", ) hidden_unit = ItemFactory.create( parent=self.sub_section, category="vertical", hide_from_toc=True, ) ItemFactory.create( parent=unit_within_hidden_subsection, category="video", edx_video_id=self.edx_video_id, ) ItemFactory.create( parent=hidden_unit, category="video", edx_video_id=self.edx_video_id, ) course_outline = self.api_response().data self.assertEqual(len(course_outline), 0) def test_language(self): self.login_and_enroll() video = ItemFactory.create( parent=self.nameless_unit, category="video", edx_video_id=self.edx_video_id, display_name=u"test draft video omega 2 \u03a9" ) language_case = namedtuple('language_case', ['transcripts', 'expected_language']) language_cases = [ # defaults to english language_case({}, "en"), # supports english language_case({"en": 1}, "en"), # supports another language language_case({"lang1": 1}, "lang1"), # returns first alphabetically-sorted language language_case({"lang1": 1, "en": 2}, "en"), language_case({"lang1": 1, "lang2": 2}, "lang1"), ] for case in language_cases: video.transcripts = case.transcripts modulestore().update_item(video, self.user.id) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) self.assertEqual(course_outline[0]['summary']['language'], case.expected_language) def test_transcripts(self): self.login_and_enroll() video = ItemFactory.create( parent=self.nameless_unit, category="video", edx_video_id=self.edx_video_id, display_name=u"test draft video omega 2 \u03a9" ) transcript_case = namedtuple('transcript_case', ['transcripts', 'english_subtitle', 'expected_transcripts']) transcript_cases = [ # defaults to english transcript_case({}, "", ["en"]), transcript_case({}, "en-sub", ["en"]), # supports english transcript_case({"en": 1}, "", ["en"]), transcript_case({"en": 1}, "en-sub", ["en"]), # keeps both english and other languages transcript_case({"lang1": 1, "en": 2}, "", ["lang1", "en"]), transcript_case({"lang1": 1, "en": 2}, "en-sub", ["lang1", "en"]), # adds english to list of languages only if english_subtitle is specified transcript_case({"lang1": 1, "lang2": 2}, "", ["lang1", "lang2"]), transcript_case({"lang1": 1, "lang2": 2}, "en-sub", ["lang1", "lang2", "en"]), ] for case in transcript_cases: video.transcripts = case.transcripts video.sub = case.english_subtitle modulestore().update_item(video, self.user.id) course_outline = self.api_response().data self.assertEqual(len(course_outline), 1) self.assertSetEqual( set(course_outline[0]['summary']['transcripts'].keys()), set(case.expected_transcripts) ) class TestTranscriptsDetail( TestVideoAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin, TestVideoAPIMixin # pylint: disable=bad-continuation ): """ Tests for /api/mobile/v0.5/video_outlines/transcripts/{course_id}.. """ REVERSE_INFO = {'name': 'video-transcripts-detail', 'params': ['course_id']} def setUp(self): super(TestTranscriptsDetail, self).setUp() self.video = self._create_video_with_subs() def reverse_url(self, reverse_args=None, **kwargs): reverse_args = reverse_args or {} reverse_args.update({ 'block_id': self.video.location.block_id, 'lang': kwargs.get('lang', 'en'), }) return super(TestTranscriptsDetail, self).reverse_url(reverse_args, **kwargs) def test_incorrect_language(self): self.login_and_enroll() self.api_response(expected_response_code=404, lang='pl') def test_transcript_with_unicode_file_name(self): self.video = self._create_video_with_subs(custom_subid=u'你好') self.login_and_enroll() self.api_response(expected_response_code=200, lang='en')