Files
edx-platform/cms/djangoapps/contentstore/tests/test_video_utils.py
2019-07-09 13:09:08 +05:00

371 lines
13 KiB
Python

#-*- coding: utf-8 -*-
"""
Unit tests for video utils.
"""
from __future__ import absolute_import
from datetime import datetime
from unittest import TestCase
import ddt
import pytz
import requests
import six
from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.test.utils import override_settings
from edxval.api import create_profile, create_video, get_course_video_image_url, update_video_image
from mock import patch
from contentstore.tests.utils import CourseTestCase
from contentstore.video_utils import (
YOUTUBE_THUMBNAIL_SIZES,
download_youtube_video_thumbnail,
scrape_youtube_thumbnail,
validate_video_image
)
from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file
class ValidateVideoImageTestCase(TestCase):
"""
Tests for `validate_video_image` method.
"""
def test_invalid_image_file_info(self):
"""
Test that when no file information is provided to validate_video_image, it gives proper error message.
"""
error = validate_video_image({})
self.assertEquals(error, 'The image must have name, content type, and size information.')
def test_corrupt_image_file(self):
"""
Test that when corrupt file is provided to validate_video_image, it gives proper error message.
"""
with open(settings.MEDIA_ROOT + '/test-corrupt-image.png', 'w+') as image_file:
uploaded_image_file = UploadedFile(
image_file,
content_type='image/png',
size=settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES']
)
error = validate_video_image(uploaded_image_file)
self.assertEquals(error, 'There is a problem with this image file. Try to upload a different file.')
@ddt.ddt
class ScrapeVideoThumbnailsTestCase(CourseTestCase):
"""
Test cases for scraping video thumbnails from youtube.
"""
def setUp(self):
super(ScrapeVideoThumbnailsTestCase, self).setUp()
course_ids = [six.text_type(self.course.id)]
profiles = ['youtube']
created = datetime.now(pytz.utc)
previous_uploads = [
{
'edx_video_id': 'test1',
'client_video_id': 'test1.mp4',
'duration': 42.0,
'status': 'upload',
'courses': course_ids,
'encoded_videos': [],
'created': created
},
{
'edx_video_id': 'test-youtube-video-1',
'client_video_id': 'test-youtube-id.mp4',
'duration': 128.0,
'status': 'file_complete',
'courses': course_ids,
'created': created,
'encoded_videos': [
{
'profile': 'youtube',
'url': '3_yD_cEKoCk',
'file_size': 1600,
'bitrate': 100,
}
],
},
{
'edx_video_id': 'test-youtube-video-2',
'client_video_id': 'test-youtube-id.mp4',
'image': 'image2.jpg',
'duration': 128.0,
'status': 'file_complete',
'courses': course_ids,
'created': created,
'encoded_videos': [
{
'profile': 'youtube',
'url': '3_yD_cEKoCk',
'file_size': 1600,
'bitrate': 100,
}
],
},
]
for profile in profiles:
create_profile(profile)
for video in previous_uploads:
create_video(video)
# Create video images.
with make_image_file() as image_file:
update_video_image(
'test-youtube-video-2', six.text_type(self.course.id), image_file, 'image.jpg'
)
def mocked_youtube_thumbnail_response(
self,
mocked_content=None,
error_response=False,
image_width=settings.VIDEO_IMAGE_MIN_WIDTH,
image_height=settings.VIDEO_IMAGE_MIN_HEIGHT
):
"""
Returns a mocked youtube thumbnail response.
"""
image_content = ''
with make_image_file(dimensions=(image_width, image_height), ) as image_file:
image_content = image_file.read()
if mocked_content or error_response:
image_content = mocked_content
mocked_response = requests.Response()
mocked_response.status_code = requests.codes.ok if image_content else requests.codes.not_found # pylint: disable=no-member
mocked_response._content = image_content # pylint: disable=protected-access
mocked_response.headers = {'content-type': 'image/jpeg'}
return mocked_response
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret')
@patch('requests.get')
@ddt.data(
(
{
'maxresdefault': 'maxresdefault-result-image-content',
'sddefault': 'sddefault-result-image-content',
'hqdefault': 'hqdefault-result-image-content',
'mqdefault': 'mqdefault-result-image-content',
'default': 'default-result-image-content'
},
'maxresdefault-result-image-content'
),
(
{
'maxresdefault': '',
'sddefault': 'sddefault-result-image-content',
'hqdefault': 'hqdefault-result-image-content',
'mqdefault': 'mqdefault-result-image-content',
'default': 'default-result-image-content'
},
'sddefault-result-image-content'
),
(
{
'maxresdefault': '',
'sddefault': '',
'hqdefault': 'hqdefault-result-image-content',
'mqdefault': 'mqdefault-result-image-content',
'default': 'default-result-image-content'
},
'hqdefault-result-image-content'
),
(
{
'maxresdefault': '',
'sddefault': '',
'hqdefault': '',
'mqdefault': 'mqdefault-result-image-content',
'default': 'default-result-image-content'
},
'mqdefault-result-image-content'
),
(
{
'maxresdefault': '',
'sddefault': '',
'hqdefault': '',
'mqdefault': '',
'default': 'default-result-image-content'
},
'default-result-image-content'
),
)
@ddt.unpack
def test_youtube_video_thumbnail_download(
self,
thumbnail_content_data,
expected_thumbnail_content,
mocked_request
):
"""
Test that we get highest resolution video thumbnail available from youtube.
"""
# Mock get youtube thumbnail responses.
def mocked_youtube_thumbnail_responses(resolutions):
"""
Returns a list of mocked responses containing youtube thumbnails.
"""
mocked_responses = []
for resolution in YOUTUBE_THUMBNAIL_SIZES:
mocked_content = resolutions.get(resolution, '')
error_response = False if mocked_content else True
mocked_responses.append(self.mocked_youtube_thumbnail_response(mocked_content, error_response))
return mocked_responses
mocked_request.side_effect = mocked_youtube_thumbnail_responses(thumbnail_content_data)
thumbnail_content, thumbnail_content_type = download_youtube_video_thumbnail('test-yt-id')
# Verify that we get the expected thumbnail content.
self.assertEqual(thumbnail_content, expected_thumbnail_content)
self.assertEqual(thumbnail_content_type, 'image/jpeg')
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret')
@patch('requests.get')
def test_scrape_youtube_thumbnail(self, mocked_request):
"""
Test that youtube thumbnails are correctly scrapped.
"""
course_id = six.text_type(self.course.id)
video1_edx_video_id = 'test-youtube-video-1'
video2_edx_video_id = 'test-youtube-video-2'
# Mock get youtube thumbnail responses.
mocked_request.side_effect = [self.mocked_youtube_thumbnail_response()]
# Verify that video1 has no image attached.
video1_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video1_edx_video_id)
self.assertIsNone(video1_image_url)
# Verify that video2 has already image attached.
video2_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video2_edx_video_id)
self.assertIsNotNone(video2_image_url)
# Scrape video thumbnails.
scrape_youtube_thumbnail(course_id, video1_edx_video_id, 'test-yt-id')
scrape_youtube_thumbnail(course_id, video2_edx_video_id, 'test-yt-id2')
# Verify that now video1 image is attached.
video1_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video1_edx_video_id)
self.assertIsNotNone(video1_image_url)
# Also verify that video2's image is not updated.
video2_image_url_latest = get_course_video_image_url(course_id=course_id, edx_video_id=video2_edx_video_id)
self.assertEqual(video2_image_url, video2_image_url_latest)
@ddt.data(
(
100,
100,
False
),
(
640,
360,
True
)
)
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret')
@patch('contentstore.video_utils.LOGGER')
@patch('requests.get')
@ddt.unpack
def test_scrape_youtube_thumbnail_logging(
self,
image_width,
image_height,
is_success,
mocked_request,
mock_logger
):
"""
Test that we get correct logs in case of failure as well as success.
"""
course_id = six.text_type(self.course.id)
video1_edx_video_id = 'test-youtube-video-1'
mocked_request.side_effect = [
self.mocked_youtube_thumbnail_response(
image_width=image_width,
image_height=image_height
)
]
scrape_youtube_thumbnail(course_id, video1_edx_video_id, 'test-yt-id')
if is_success:
mock_logger.info.assert_called_with(
u'VIDEOS: Scraping youtube video thumbnail for edx_video_id [%s] in course [%s]',
video1_edx_video_id,
course_id
)
else:
mock_logger.info.assert_called_with(
u'VIDEOS: Scraping youtube video thumbnail failed for edx_video_id [%s] in course [%s] with error: %s',
video1_edx_video_id,
course_id,
'This image file must be larger than 2 KB.'
)
@ddt.data(
(
None,
'image/jpeg',
u'This image file must be larger than {image_min_size}.'.format(
image_min_size=settings.VIDEO_IMAGE_MIN_FILE_SIZE_KB
)
),
(
'dummy-content',
None,
u'This image file type is not supported. Supported file types are {supported_file_formats}.'.format(
supported_file_formats=list(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys())
)
),
(
None,
None,
u'This image file type is not supported. Supported file types are {supported_file_formats}.'.format(
supported_file_formats=list(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys())
)
),
)
@patch('contentstore.video_utils.LOGGER')
@patch('contentstore.video_utils.download_youtube_video_thumbnail')
@ddt.unpack
def test_no_video_thumbnail_downloaded(
self,
image_content,
image_content_type,
error_message,
mock_download_youtube_thumbnail,
mock_logger
):
"""
Test that when no thumbnail is downloaded, video image is not updated.
"""
mock_download_youtube_thumbnail.return_value = image_content, image_content_type
course_id = six.text_type(self.course.id)
video1_edx_video_id = 'test-youtube-video-1'
# Verify that video1 has no image attached.
video1_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video1_edx_video_id)
self.assertIsNone(video1_image_url)
# Scrape video thumbnail.
scrape_youtube_thumbnail(course_id, video1_edx_video_id, 'test-yt-id')
mock_logger.info.assert_called_with(
u'VIDEOS: Scraping youtube video thumbnail failed for edx_video_id [%s] in course [%s] with error: %s',
video1_edx_video_id,
course_id,
error_message
)
# Verify that no image is attached to video1.
video1_image_url = get_course_video_image_url(course_id=course_id, edx_video_id=video1_edx_video_id)
self.assertIsNone(video1_image_url)