示例#1
0
def test_video_subtitle_key():
    """ Tests that the correct subtitle key is returned for a language"""
    video = VideoFactory(key="8494dafc-3665-4960-8e00-9790574ec93a")
    now = datetime.now(tz=pytz.UTC)
    assert (re.fullmatch(
        "subtitles/8494dafc366549608e009790574ec93a/subtitles_8494dafc366549608e009790574ec93a_{}_en.vtt"
        .format(now.strftime("%Y%m%d%H%M%S")),
        video.subtitle_key(now, "en"),
    ) is not None)
示例#2
0
def test_youtube_video_delete_signal(mocker):
    """ Tests that a video's YouTubeVideo object is deleted after changing from public to private"""
    mock_task = mocker.patch("ui.signals.remove_youtube_video.delay")
    video = VideoFactory(is_public=True)
    yt_video = YouTubeVideoFactory(video=video)
    youtube_id = yt_video.id
    video.is_public = False
    video.save()
    mock_task.assert_called_once_with(youtube_id)
示例#3
0
def test_schedule_retranscodes_error(mocker, mocked_celery):
    """
    Test that schedule_retranscodes logs an error if it occurs
    """
    mock_error_log = mocker.patch("cloudsync.tasks.log.exception")
    mocker.patch("cloudsync.tasks.retranscode_video.si",
                 side_effect=ClientError)
    VideoFactory.create_batch(5, schedule_retranscode=True)
    schedule_retranscodes.delay()
    mock_error_log.assert_called_with("schedule_retranscodes threw an error")
示例#4
0
def test_refresh_status_video_job_othererror(mocker, status):
    """
    Verify that refresh_status does not raise ClientError
    """
    video = VideoFactory(status=status)
    EncodeJobFactory(video=video)
    video.status = VideoStatus.TRANSCODING
    mocker.patch("ui.utils.boto3", MockBoto)
    error = Exception("unexpected exception")
    mocker.patch("ui.utils.get_transcoder_client",
                 return_value=MockClientET(error=error))
    with pytest.raises(Exception):
        api.refresh_status(video)
示例#5
0
def test_transcode_job(mocker, status, expected_status):
    """
    Test that video status is updated properly after a transcode job is successfully created
    """
    video = VideoFactory.create(status=status)
    videofile = VideoFileFactory.create(video=video)

    prefix = RETRANSCODE_FOLDER if status == VideoStatus.RETRANSCODE_SCHEDULED else ""
    preset = {
        "Key":
        f"{prefix}transcoded/" + video.hexkey + "/video_1351620000001-000040",
        "PresetId": "1351620000001-000040",
        "SegmentDuration": "10.0",
    }
    if status != VideoStatus.RETRANSCODE_SCHEDULED:
        preset["ThumbnailPattern"] = ("thumbnails/" + video.hexkey +
                                      "/video_thumbnail_{count}")

    mocker.patch.multiple(
        "cloudsync.tasks.settings",
        ET_PRESET_IDS=("1351620000001-000040", "1351620000001-000020"),
        AWS_REGION="us-east-1",
        ET_PIPELINE_ID="foo",
        ENVIRONMENT="test",
    )
    mock_encoder = mocker.patch("cloudsync.api.VideoTranscoder.encode")
    mock_delete_objects = mocker.patch("cloudsync.api.delete_s3_objects")
    mocker.patch("ui.models.tasks")

    api.transcode_video(video, videofile)  # pylint: disable=no-value-for-parameter
    mock_encoder.assert_called_once_with(
        {"Key": videofile.s3_object_key},
        [
            preset,
            {
                "Key": f"{prefix}transcoded/" + video.hexkey +
                "/video_1351620000001-000020",
                "PresetId": "1351620000001-000020",
                "SegmentDuration": "10.0",
            },
        ],
        Playlists=[{
            "Format":
            "HLSv3",
            "Name":
            f"{prefix}transcoded/" + video.hexkey + "/video__index",
            "OutputKeys": [
                f"{prefix}transcoded/" + video.hexkey +
                "/video_1351620000001-000040",
                f"{prefix}transcoded/" + video.hexkey +
                "/video_1351620000001-000020",
            ],
        }],
        UserMetadata={"pipeline": "odl-video-service-test"},
    )
    assert len(video.encode_jobs.all()) == 1
    assert mock_delete_objects.call_count == (
        1 if status == VideoStatus.RETRANSCODE_SCHEDULED else 0)
    assert Video.objects.get(id=video.id).status == expected_status
示例#6
0
def test_video_ordering():
    """
    Tests that videos are sorted by reverse creation date or forward custom order
    """
    collection = CollectionFactory.create()
    VideoFactory.create_batch(10, collection=collection)
    # Should be sorted by reverse creation date
    videos = collection.videos.all()
    for (idx, video) in enumerate(videos):
        if idx > len(videos) - 1:
            assert video.created_at >= videos[idx + 1].created_at
        videos[idx].custom_order = len(videos) - idx - 1
        videos[idx].save()
    # Should be sorted by custom_order
    resorted_videos = Collection.objects.get(id=collection.id).videos.all()
    for (idx, video) in enumerate(resorted_videos):
        assert video.custom_order == idx
示例#7
0
def test_async_send_notification_email_no_video(mocker):
    """
    Tests async_send_notification_email for a video_id that does not exist
    """
    mocked_send_email = mocker.patch("mail.tasks.send_notification_email",
                                     autospec=True)
    video = VideoFactory(status=VideoStatus.COMPLETE)
    tasks.async_send_notification_email.delay(video.id + 10000)
    assert mocked_send_email.call_count == 0
示例#8
0
def test_schedule_retranscodes(mocker, mock_transcode,
                               mock_successful_encode_job, mocked_celery):
    """
    Test that schedule_retranscodes triggers retranscode_video tasks for each scheduled video
    """
    retranscode_video_mock = mocker.patch("cloudsync.tasks.retranscode_video",
                                          autospec=True)
    collection = CollectionFactory.create(schedule_retranscode=True)
    scheduled_videos = VideoFactory.create_batch(5, schedule_retranscode=True)
    VideoFactory.create_batch(3, schedule_retranscode=False)
    with pytest.raises(mocked_celery.replace_exception_class):
        schedule_retranscodes.delay()
    assert mocked_celery.group.call_count == 1
    assert retranscode_video_mock.si.call_count == len(scheduled_videos)
    for video in scheduled_videos:
        retranscode_video_mock.si.assert_any_call(video.id)
    assert Collection.objects.get(
        id=collection.id).schedule_retranscode is False
示例#9
0
def test_async_send_notification_email_happy_path(mocker):
    """
    Tests async_send_notification_email with happy path
    """
    mocked_send_email = mocker.patch("mail.tasks.send_notification_email",
                                     autospec=True)
    video = VideoFactory(status=VideoStatus.COMPLETE)
    tasks.async_send_notification_email.delay(video.id)
    mocked_send_email.assert_called_once_with(video)
示例#10
0
def test_video_sources_hls():
    """ Tests that the video sources property returns the expected result for HLS """
    video = VideoFactory(key="8494dafc-3665-4960-8e00-9790574ec93a")
    videofile = VideoFileFactory(video=video, encoding=EncodingNames.HLS)
    assert video.sources == [{
        "src": videofile.cloudfront_url,
        "label": EncodingNames.HLS,
        "type": "application/x-mpegURL",
    }]
示例#11
0
def test_download_mp4(encodings, download):
    """ Tests that video.download returns the most appropriate file for download """
    video = VideoFactory()
    for encoding in encodings:
        VideoFileFactory(video=video,
                         s3_object_key="{}.mp4".format(encoding),
                         encoding=encoding)
    if not download:
        assert video.download is None
    else:
        assert video.download.encoding == download
示例#12
0
def test_send_notification_email_wrong_status(mocker):
    """
    Tests send_notification_email with a status that does not require sending an email
    """
    mocked_mailgun = mocker.patch("mail.api.MailgunClient", autospec=True)
    mocked__get_recipients_for_video = mocker.patch(
        "mail.tasks._get_recipients_for_video", autospec=True)
    assert VideoStatus.UPLOADING not in tasks.STATUS_TO_NOTIFICATION
    video = VideoFactory(status=VideoStatus.UPLOADING)
    tasks.send_notification_email(video)
    assert mocked_mailgun.send_individual_email.call_count == 0
    assert mocked__get_recipients_for_video.call_count == 0
示例#13
0
def test_original_video():
    """ Tests that the original_video property returns the VideoFile with 'original' encoding """
    video = VideoFactory(key="8494dafc-3665-4960-8e00-9790574ec93a")
    videofiles = [
        VideoFileFactory(video=video,
                         s3_object_key="original.mp4",
                         encoding=EncodingNames.ORIGINAL),
        VideoFileFactory(video=video,
                         s3_object_key="transcoded.hls",
                         encoding=EncodingNames.HLS),
    ]
    assert video.original_video == videofiles[0]
示例#14
0
def test_collection_serializer_private_video(mocker, is_admin, is_superuser):
    """
    Test that a private video is not included in the serializer unless the user is super/admin
    """
    has_permission = is_superuser or is_admin
    mocker.patch("ui.permissions.has_admin_permission",
                 return_value=has_permission)
    mocked_request = mocker.MagicMock()
    mocked_request.user = UserFactory.create(is_superuser=is_superuser)

    mocker.patch("ui.serializers.has_common_lists", return_value=is_admin)

    collection = factories.CollectionFactory(
        admin_lists=[MoiraListFactory.create()])
    VideoFactory.create(collection=collection)
    VideoFactory.create(is_private=True, collection=collection)

    serialized_data = serializers.CollectionSerializer(
        collection, context=dict(request=mocked_request)).data

    assert len(serialized_data["videos"]) == (2 if has_permission else 1)
示例#15
0
def test_send_notification_email_no_recipients(mocker):
    """
    Tests send_notification_email for a video that has no recipients
    """
    mocked_mailgun = mocker.patch("mail.api.MailgunClient", autospec=True)
    mocked__get_recipients_for_video = mocker.patch(
        "mail.tasks._get_recipients_for_video", autospec=True, return_value=[])
    assert VideoStatus.COMPLETE in tasks.STATUS_TO_NOTIFICATION
    video = VideoFactory(status=VideoStatus.COMPLETE)
    tasks.send_notification_email(video)
    assert mocked_mailgun.send_individual_email.call_count == 0
    mocked__get_recipients_for_video.assert_called_once_with(video)
示例#16
0
def test_sends_debug_emails(mocker, status):
    """
    Tests send_notification_email with statuses that should trigger sending a
    separate email to support.
    """
    mocked_mailgun = mocker.patch("mail.api.MailgunClient", autospec=True)
    mocked_send_debug_email = mocker.patch("mail.tasks._send_debug_email",
                                           autospec=True)
    video = VideoFactory(status=status)
    tasks.send_notification_email(video)
    mocked_send_debug_email.assert_called_once_with(
        video=video, email_kwargs=mocked_mailgun.send_batch.call_args[1])
示例#17
0
def test_transcoded_hls_video():
    """ Tests that Video.transcoded_videos returns transcoded HLS videofile"""
    video = VideoFactory()
    videofiles = [
        VideoFileFactory(video=video,
                         s3_object_key="original.mp4",
                         encoding=EncodingNames.ORIGINAL),
        VideoFileFactory(video=video,
                         s3_object_key="video.m3u8",
                         encoding=EncodingNames.HLS),
    ]
    assert len(video.transcoded_videos) == 1
    assert video.transcoded_videos[0] == videofiles[1]
示例#18
0
def test_upload_youtube_videos_error(mocker):
    """
    Test that the YoutubeVideo object is deleted if an error occurs during upload, and all videos are processed
    """
    videos = VideoFactory.create_batch(3,
                                       is_public=True,
                                       status=VideoStatus.COMPLETE)
    mock_uploader = mocker.patch("cloudsync.tasks.YouTubeApi.upload_video",
                                 side_effect=OSError)
    upload_youtube_videos()
    assert mock_uploader.call_count == 3
    for video in videos:
        assert YouTubeVideo.objects.filter(video=video).first() is None
示例#19
0
def test_send_notification_email_no_mail_template(mocker):
    """
    Tests send_notification_email for a video with a status not correspondent to a email template
    """
    mocked_mailgun = mocker.patch("mail.api.MailgunClient", autospec=True)
    mock_log = mocker.patch("mail.tasks.log.error")
    video = VideoFactory(status=VideoStatus.RETRANSCODING)
    tasks.send_notification_email(video)
    assert mocked_mailgun.send_individual_email.call_count == 0
    mock_log.assert_called_once_with(
        "Unexpected video status",
        video_hexkey=video.hexkey,
        video_status="Retranscoding",
    )
示例#20
0
def test_upload_youtube_videos(mocker, source, max_uploads):
    """
    Test that the upload_youtube_videos task calls YouTubeApi.upload_video
    & creates a YoutubeVideo object for each public video, up to the max daily limit
    """
    settings.YT_UPLOAD_LIMIT = max_uploads
    private_videos = VideoFactory.create_batch(2,
                                               is_public=False,
                                               status=VideoStatus.COMPLETE)
    VideoFactory.create_batch(
        3,
        collection=CollectionFactory(stream_source=source),
        is_public=True,
        status=VideoStatus.COMPLETE,
    )
    mock_uploader = mocker.patch(
        "cloudsync.tasks.YouTubeApi.upload_video",
        return_value={
            "id":
            "".join([random.choice(string.ascii_lowercase) for n in range(8)]),
            "status": {
                "uploadStatus": "uploaded"
            },
        },
    )
    upload_youtube_videos()
    assert mock_uploader.call_count == (min(
        3, max_uploads) if source != StreamSource.CLOUDFRONT else 0)
    for video in Video.objects.filter(
            is_public=True).order_by("-created_at")[:settings.YT_UPLOAD_LIMIT]:
        if video.collection.stream_source != StreamSource.CLOUDFRONT:
            assert YouTubeVideo.objects.filter(video=video).first() is not None
        else:
            assert YouTubeVideo.objects.filter(video=video).first() is None
    for video in private_videos:
        assert YouTubeVideo.objects.filter(video=video).first() is None
示例#21
0
 def test_render_email_templates(self, _):
     """Test render_email_templates"""
     video = VideoFactory.create()
     context = context_for_video(video)
     subject, text_body, html_body = render_email_templates("sample", context)
     assert subject == f'Your video "{video.title}" is ready'
     assert text_body == f"html link ({context['video_url']})"
     assert html_body == (
         '<style type="text/css">\n'
         "a {\n"
         "  color: red;\n"
         "}\n"
         "</style>\n"
         f'<a href="{context["video_url"]}">html link</a>\n'
     )
示例#22
0
def test_video_sources_mp4():
    """ Tests that the video sources property returns the expected sorted results for MP4 """
    video = VideoFactory(key="8494dafc-3665-4960-8e00-9790574ec93a")
    videofiles = [
        VideoFileFactory(video=video,
                         s3_object_key="medium.mp4",
                         encoding=EncodingNames.MEDIUM),
        VideoFileFactory(video=video,
                         s3_object_key="small.mp4",
                         encoding=EncodingNames.SMALL),
        VideoFileFactory(video=video,
                         s3_object_key="large.mp4",
                         encoding=EncodingNames.LARGE),
        VideoFileFactory(video=video,
                         s3_object_key="basic.mp4",
                         encoding=EncodingNames.BASIC),
        VideoFileFactory(video=video,
                         s3_object_key="hd.mp4",
                         encoding=EncodingNames.HD),
    ]
    assert video.sources == [
        {
            "src": videofiles[4].cloudfront_url,
            "label": EncodingNames.HD,
            "type": "video/mp4",
        },
        {
            "src": videofiles[2].cloudfront_url,
            "label": EncodingNames.LARGE,
            "type": "video/mp4",
        },
        {
            "src": videofiles[0].cloudfront_url,
            "label": EncodingNames.MEDIUM,
            "type": "video/mp4",
        },
        {
            "src": videofiles[3].cloudfront_url,
            "label": EncodingNames.BASIC,
            "type": "video/mp4",
        },
        {
            "src": videofiles[1].cloudfront_url,
            "label": EncodingNames.SMALL,
            "type": "video/mp4",
        },
    ]
示例#23
0
def test_refresh_status_video_job_status_complete(mocker, status):
    """
    Verify that Video.job_status property returns the status of its encoding job
    """
    video = VideoFactory(status=status)
    encodejob = EncodeJobFactory(video=video)
    MockClientET.job = {
        "Job": {
            "Id": "1498220566931-qtmtcu",
            "Status": "Complete"
        }
    }
    mocker.patch("ui.utils.boto3", MockBoto)
    mocker.patch("cloudsync.api.process_transcode_results")
    mocker.patch("ui.models.tasks")
    api.refresh_status(video, encodejob)
    assert video.status == VideoStatus.COMPLETE
示例#24
0
def test_upload_youtube_quota_exceeded(mocker, msg):
    """
    Test that the YoutubeVideo object is deleted if an error occurs during upload,
    and the loop is halted if the quota is exceeded.
    """
    videos = VideoFactory.create_batch(3,
                                       is_public=True,
                                       status=VideoStatus.COMPLETE)
    mock_uploader = mocker.patch(
        "cloudsync.tasks.YouTubeApi.upload_video",
        side_effect=ResumableUploadError(MockHttpErrorResponse(403),
                                         str.encode(msg, "utf-8")),
    )
    upload_youtube_videos()
    assert mock_uploader.call_count == (1 if msg == API_QUOTA_ERROR_MSG else 3)
    for video in videos:
        assert YouTubeVideo.objects.filter(video=video).first() is None
示例#25
0
def test_upload_video_long_fields(mocker):
    """
    Test that the upload_youtube_video task truncates title and description if too long
    """
    title = "".join(random.choice(string.ascii_lowercase) for c in range(105))
    desc = "".join(random.choice(string.ascii_lowercase) for c in range(5005))
    video = VideoFactory.create(title=title,
                                description=desc,
                                is_public=True,
                                status=VideoStatus.COMPLETE)
    VideoFileFactory(video=video)
    mocker.patch("cloudsync.youtube.resumable_upload")
    youtube_mocker = mocker.patch("cloudsync.youtube.build")
    mock_upload = youtube_mocker().videos.return_value.insert
    YouTubeApi().upload_video(video)
    called_args, called_kwargs = mock_upload.call_args
    assert called_kwargs["body"]["snippet"]["title"] == title[:100]
    assert called_kwargs["body"]["snippet"]["description"] == desc[:5000]
示例#26
0
def test_get_recipients_for_video(mocker):
    """
    Tests the _get_recipients_for_video api
    """
    mock_client = mocker.patch("mail.tasks.get_moira_client")
    lists = MoiraListFactory.create_batch(3)
    video = VideoFactory(collection__admin_lists=lists)
    list_attributes = [[{"mailList": False}], [{"mailList": True}], None]
    list_emails = ["{}@mit.edu".format(lists[1].name)]
    mocker.patch("mail.tasks.has_common_lists", return_value=False)
    mock_client(
    ).client.service.getListAttributes.side_effect = list_attributes
    assert tasks._get_recipients_for_video(
        video) == list_emails + [video.collection.owner.email]
    mocker.patch("mail.tasks.has_common_lists", return_value=True)
    mock_client(
    ).client.service.getListAttributes.side_effect = list_attributes
    assert tasks._get_recipients_for_video(video) == list_emails
示例#27
0
def test_send_notification_email_happy_path(mocker):
    """
    Tests send_notification_email with happy path
    """
    mocked_mailgun = mocker.patch("mail.api.MailgunClient", autospec=True)
    assert VideoStatus.COMPLETE in tasks.STATUS_TO_NOTIFICATION
    video = VideoFactory(status=VideoStatus.COMPLETE)
    subject, text, html = render_email_templates(
        STATUS_TO_NOTIFICATION[VideoStatus.COMPLETE], context_for_video(video))
    tasks.send_notification_email(video)
    mocked_mailgun.send_batch.assert_called_once_with(
        **{
            "subject": subject,
            "html_body": html,
            "text_body": text,
            "recipients": [(video.collection.owner.email, {})],
            "sender_address": settings.EMAIL_SUPPORT,
            "raise_for_status": True,
        })
示例#28
0
def test_send_debug_email(mocker):
    """
    Tests sends debug email to support.
    """
    mocked_mailgun = mocker.patch("mail.api.MailgunClient", autospec=True)
    mocked_generate_debug_email_body = mocker.patch(
        "mail.tasks._generate_debug_email_body")
    mock_email_kwargs = defaultdict(mocker.MagicMock)
    video = VideoFactory()
    tasks._send_debug_email(video=video, email_kwargs=mock_email_kwargs)
    mocked_generate_debug_email_body.assert_called_once_with(
        video=video, email_kwargs=mock_email_kwargs)
    mocked_mailgun.send_individual_email.assert_called_once_with(
        **{
            "subject": "DEBUG:{}".format(mock_email_kwargs["subject"]),
            "html_body": None,
            "text_body": mocked_generate_debug_email_body.return_value,
            "recipient": settings.EMAIL_SUPPORT,
        })
示例#29
0
def test_transcoded_mp4_video():
    """ Tests that Video.transcoded_videos returns transcoded MP4 videos in the correct order"""
    video = VideoFactory()
    videofiles = [
        VideoFileFactory(video=video,
                         s3_object_key="original.mp4",
                         encoding=EncodingNames.ORIGINAL),
        VideoFileFactory(video=video,
                         s3_object_key="small.mp4",
                         encoding=EncodingNames.SMALL),
        VideoFileFactory(video=video,
                         s3_object_key="basic.mp4",
                         encoding=EncodingNames.BASIC),
        VideoFileFactory(video=video,
                         s3_object_key="HD.mp4",
                         encoding=EncodingNames.HD),
    ]
    assert len(video.transcoded_videos) == 3
    assert video.transcoded_videos[0] == videofiles[3]
    assert video.transcoded_videos[1] == videofiles[2]
    assert video.transcoded_videos[2] == videofiles[1]
示例#30
0
def test_youtube_video_permissions_signal(mocker):
    """ Tests that a video's public permissions are removed if it's subtitle is deleted """
    mock_delete_video = mocker.patch("ui.signals.remove_youtube_video.delay")
    mock_delete_caption = mocker.patch("ui.signals.remove_youtube_caption.delay")
    mocker.patch("ui.models.VideoSubtitle.delete_from_s3")
    video = VideoFactory(is_public=True)
    YouTubeVideoFactory(video=video)
    VideoSubtitleFactory(video=video)
    VideoSubtitleFactory(video=video, language="fr")
    video.videosubtitle_set.get(language="fr").delete()
    # video's public status should not be changed as long as 1 subtitle still exists
    assert video.is_public is True
    assert mock_delete_caption.call_count == 1
    video.videosubtitle_set.first().delete()
    # If no subtitles exists, video should be made non-public and deleted from youtube
    assert mock_delete_video.call_count == 1
    assert not video.is_public
    caption = VideoSubtitleFactory(video=video)
    mock_video_save = mocker.patch("ui.models.Video.save")
    caption.delete()
    # If video is not public, no change to it should be saved after a caption is deleted.
    assert mock_video_save.call_count == 0