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
def test_post_hls_to_edx_wrong_type(mocker): """ post_hls_to_edx should raise an exception if the given video file is not configured correctly for posting to edX """ video_file = VideoFileFactory.create(encoding=EncodingNames.ORIGINAL) with pytest.raises(Exception): api.post_hls_to_edx(video_file)
def test_post_hls_to_edx_no_endpoints(mocker): """post_hls_to_edx should log an error if no endpoints are configured for some video's collection""" patched_log_error = mocker.patch("ui.api.log.error") video_file = VideoFileFactory.create( encoding=EncodingNames.HLS, video__collection__edx_course_id="some-course-id", ) responses = api.post_hls_to_edx(video_file) patched_log_error.assert_called_once() assert responses == {}
def edx_api_scenario(): """Fixture that provides a VideoFile with the correct properties to post to edX""" course_id = "course-v1:abc" video_file = VideoFileFactory.create( encoding=EncodingNames.HLS, video__title="My Video", video__collection__edx_course_id=course_id, ) collection_edx_endpoint = CollectionEdxEndpointFactory( collection=video_file.video.collection ) return SimpleNamespace( video_file=video_file, course_id=course_id, collection_endpoint=collection_edx_endpoint.edx_endpoint, )
def test_video_file_can_add_to_edx(encoding, edx_course_id, expected): """Test that VideoFile.can_add_to_edx returns True under the right conditions""" video_files = VideoFileFactory.create( encoding=encoding, video__collection__edx_course_id=edx_course_id) assert video_files.can_add_to_edx is expected
def test_transcode_job_failure(mocker, status, error_status): """ Test that video status is updated properly after a transcode or retranscode job creation fails """ mocker.patch("cloudsync.api.delete_s3_objects") video = VideoFactory.create(status=status) videofile = VideoFileFactory.create(video=video) job_result = { "Job": { "Id": "1498220566931-qtmtcu", "Status": "Error" }, "Error": { "Code": 200, "Message": "FAIL" }, } mocker.patch.multiple( "cloudsync.tasks.settings", ET_PRESET_IDS=("1351620000001-000020", ), AWS_REGION="us-east-1", ET_PIPELINE_ID="foo", ENVIRONMENT="test", ) mocker.patch("ui.models.tasks") mock_encoder = mocker.patch( "cloudsync.api.VideoTranscoder.encode", side_effect=ClientError(error_response=job_result, operation_name="ReadJob"), ) with pytest.raises(ClientError): api.transcode_video(video, videofile) prefix = "" if status == VideoStatus.TRANSCODING else RETRANSCODE_FOLDER preset = { "Key": f"{prefix}transcoded/" + video.hexkey + "/video_1351620000001-000020", "PresetId": "1351620000001-000020", "SegmentDuration": "10.0", } if status == VideoStatus.TRANSCODING: preset["ThumbnailPattern"] = ("thumbnails/" + video.hexkey + "/video_thumbnail_{count}") mock_encoder.assert_called_once_with( {"Key": videofile.s3_object_key}, [preset], Playlists=[{ "Format": "HLSv3", "Name": f"{prefix}transcoded/" + video.hexkey + "/video__index", "OutputKeys": [ f"{prefix}transcoded/" + video.hexkey + "/video_1351620000001-000020" ], }], UserMetadata={"pipeline": "odl-video-service-test"}, ) assert len(video.encode_jobs.all()) == 1 assert Video.objects.get(id=video.id).status == error_status
def test_process_transcode_results(mocker, status): """ Verify that a videofile object is created for each output in the job JSON, and a thumbnail is created for each S3 object in the appropriate bucket virtual subfolder. """ mock_move_s3_objects = mocker.patch("cloudsync.api.move_s3_objects") video = VideoFactory.create(status=status) VideoFileFactory.create(video=video) # We need to create the thumbnail bucket since this is all in the Moto virtual AWS account conn = boto3.resource("s3", region_name="us-east-1") bucket = conn.create_bucket(Bucket=settings.VIDEO_S3_THUMBNAIL_BUCKET) p = RETRANSCODE_FOLDER if status == VideoStatus.RETRANSCODING else "" # Throw a fake thumbnail in the bucket: data = io.BytesIO(b"00000001111111") bucket.upload_fileobj( data, "thumbnails/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_00001.jpg") job = { "Id": "1498765896748-e0p0qr", "Input": { "Key": "1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi.mp4" }, "Inputs": [{ "Key": "1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi.mp4" }], "Output": { "Id": "1", "Key": f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700489769-iyi2t4", "PresetId": "1498700489769-iyi2t4", "SegmentDuration": "10.0", "Status": "Complete", }, "Outputs": [ { "Id": "1", "Key": f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700489769-iyi2t4", "PresetId": "1498700489769-iyi2t4", "SegmentDuration": "10.0", "Status": "Complete", "ThumbnailPattern": "thumbnails/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_{count}", "Watermarks": [], "Width": 1280, }, { "Id": "2", "Key": f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700403561-zc5oo5", "PresetId": "1498700403561-zc5oo5", "SegmentDuration": "10.0", "Status": "Complete", "Watermarks": [], "Width": 1280, }, { "Id": "3", "Key": f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700578799-qvvjor", "PresetId": "1498700578799-qvvjor", "SegmentDuration": "10.0", "Status": "Complete", "Watermarks": [], "Width": 854, }, { "Id": "4", "Key": f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700649488-6t9m3h", "PresetId": "1498700649488-6t9m3h", "SegmentDuration": "10.0", "Status": "Complete", "Watermarks": [], "Width": 640, }, ], "PipelineId": "1497455687488-evsuze", "Playlists": [{ "Format": "HLSv4", "Name": f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi__index", "OutputKeys": [ f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700489769-iyi2t4", f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700403561-zc5oo5", f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700578799-qvvjor", f"{p}transcoded/1/05a06f21-7625-4c20-b416-ae161f31722a/lastjedi_1498700649488-6t9m3h", ], "Status": "Complete", }], "Status": "Complete", } MockClientET.preset = { "Preset": { "Thumbnails": { "MaxHeight": 190, "MaxWidth": 100 } } } mocker.patch("ui.utils.get_transcoder_client", return_value=MockClientET()) api.process_transcode_results(video, job) assert len(video.videofile_set.all()) == 2 assert len(video.videothumbnail_set.all()) == 1 assert mock_move_s3_objects.call_count == ( 1 if status == VideoStatus.RETRANSCODING else 0)
def test_post_hls_to_edx(mocker): """post_hls_to_edx task should load a VideoFile and call an internal API function to post to edX""" patched_api_method = mocker.patch("ui.tasks.ovs_api.post_hls_to_edx") video_file = VideoFileFactory.create() tasks.post_hls_to_edx.delay(video_file.id) patched_api_method.assert_called_once_with(video_file)