From 59a6601345747ed000881d65a606b3da9e407966 Mon Sep 17 00:00:00 2001 From: Muhammad Arslan Abdul Rauf Date: Fri, 20 Jun 2025 20:58:56 +0500 Subject: [PATCH 1/2] test(transcoding): utils tests are added --- tests/transcoding/test_utils.py | 141 +++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) diff --git a/tests/transcoding/test_utils.py b/tests/transcoding/test_utils.py index c630e088..2f99d9eb 100644 --- a/tests/transcoding/test_utils.py +++ b/tests/transcoding/test_utils.py @@ -1,6 +1,13 @@ -"""Test for transcoding app""" +"""Test for transcoding app utils""" -from mitol.transcoding.utils import get_subscribe_url +import pytest +from mitol.transcoding.utils import ( + FileConfig, + filter_mp4_groups, + get_output_path, + get_subscribe_url, + is_thumbnail_group, +) def test_get_subscribe_url(mocker): @@ -17,3 +24,133 @@ def test_get_subscribe_url(mocker): f"TopicArn=arn:aws:sns:{fake_region}:{fake_account_id}:" "MediaConvertJobAlert&Token=fake-token" ) + + +def test_file_config(): + """Test FileConfig initialization with various parameters""" + file_config = FileConfig( + "test-video.mp4", + source_prefix="uploads/", + source_bucket="source-bucket", + destination_prefix="transcodes/", + destination_bucket="destination-bucket", + ) + + assert file_config.video_source_key == "test-video.mp4" + assert file_config.source_prefix == "uploads/" + assert file_config.source_bucket == "source-bucket" + assert file_config.destination_prefix == "transcodes/" + assert file_config.destination_bucket == "destination-bucket" + assert file_config.group_settings == {} + + +def test_file_config_init_with_group_settings(): + """Test FileConfig initialization with group settings""" + group_settings = { + "HlsGroupSettings": {"SegmentLength": 6, "Destination": "s3://bucket/path/"} + } + + file_config = FileConfig("test-video.mp4", group_settings=group_settings) + + assert file_config.group_settings == group_settings + assert file_config.video_source_key == "test-video.mp4" + + +def test_file_config_init_with_defaults(): + """Test FileConfig initialization with default parameters""" + file_config = FileConfig("test-video.mp4") + + assert file_config.video_source_key == "test-video.mp4" + assert file_config.source_prefix == "" + assert file_config.source_bucket == "test-bucket" + assert file_config.destination_prefix == "" + assert file_config.destination_bucket == "test-bucket" + assert file_config.group_settings == {} + + +@pytest.mark.parametrize( + ("group", "expected_result"), + [ + ( + { + "Outputs": [ + {"VideoDescription": {"CodecSettings": {"Codec": "FRAME_CAPTURE"}}} + ] + }, + True, + ), + ( + {"Outputs": [{"VideoDescription": {"CodecSettings": {"Codec": "H_264"}}}]}, + False, + ), + ({}, False), + ], +) +def test_is_thumbnail_group(group, expected_result): + """Test is_thumbnail_group correctly identifies thumbnail groups""" + assert is_thumbnail_group(group) is expected_result + + +@pytest.mark.parametrize( + ("output_groups", "expected_count", "expected_container"), + [ + ( + [ + {"Outputs": [{"ContainerSettings": {"Container": "MP4"}}]}, + {"Outputs": [{"ContainerSettings": {"Container": "HLS"}}]}, + ], + 1, + "HLS", + ), + ([], 0, None), + ([{"Outputs": [{"ContainerSettings": {"Container": "MP4"}}]}], 0, None), + ( + [ + {"Outputs": [{"ContainerSettings": {"Container": "HLS"}}]}, + {"Outputs": [{"ContainerSettings": {"Container": "CMAF"}}]}, + ], + 2, + None, + ), + ], +) +def test_filter_mp4_groups(output_groups, expected_count, expected_container): + """Test filter_mp4_groups properly filters out MP4 groups""" + filtered_groups = filter_mp4_groups(output_groups) + + assert len(filtered_groups) == expected_count + + if expected_container and filtered_groups: + assert ( + filtered_groups[0]["Outputs"][0]["ContainerSettings"]["Container"] + == expected_container + ) + + +@pytest.mark.parametrize( + ("is_thumbnail_group", "expected_bucket", "expected_path_prefix"), + [ + (False, "transcode-bucket", "transcodes/test-video"), + (True, "thumbnail-bucket", "thumbnails/test-video"), + ], +) +def test_get_output_path(is_thumbnail_group, expected_bucket, expected_path_prefix): + """Test get_output_path constructs the correct paths for different group types""" + + file_config = FileConfig( + "test-video.mp4", + source_prefix="uploads/", + destination_prefix="transcodes/", + destination_bucket="transcode-bucket", + ) + file_config.destination = "transcodes/test-video" + + output_path = get_output_path( + file_config, + is_thumbnail_group=is_thumbnail_group, + thumbnail_bucket="thumbnail-bucket", + thumbnail_prefix="thumbnails/", + ) + + expected_output_path = f"s3://{expected_bucket}/{expected_path_prefix}" + assert output_path == expected_output_path From 0d2bdfc43cd44b5476392b0799f7229f13d75b37 Mon Sep 17 00:00:00 2001 From: Muhammad Arslan Abdul Rauf Date: Mon, 23 Jun 2025 22:27:33 +0500 Subject: [PATCH 2/2] test: api tests added --- tests/transcoding/test_api.py | 259 ++++++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 tests/transcoding/test_api.py diff --git a/tests/transcoding/test_api.py b/tests/transcoding/test_api.py new file mode 100644 index 00000000..832b385d --- /dev/null +++ b/tests/transcoding/test_api.py @@ -0,0 +1,259 @@ +"""Test for transcoding API""" + +import json +from pathlib import Path + +import boto3 +import pytest +from mitol.transcoding.api import ( + add_group_settings, + get_destination_path, + make_media_convert_job, + media_convert_job, +) +from mitol.transcoding.constants import GroupSettings +from mitol.transcoding.utils import FileConfig + + +@pytest.fixture +def mock_file_config(): + """Create a mock file config instance""" + return FileConfig( + "uploads/test-video.mp4", + source_prefix="uploads/", + source_bucket="source-bucket", + destination_prefix="transcodes/", + destination_bucket="destination-bucket", + ) + + +@pytest.fixture +def mock_job_dict(): + """Create a mock MediaConvert job dictionary""" + return { + "UserMetadata": {"filter": "default"}, + "Queue": "", + "Role": "", + "Settings": { + "Inputs": [{"FileInput": ""}], + "OutputGroups": [ + { + "OutputGroupSettings": { + "Type": GroupSettings.HLS_GROUP_SETTINGS, + GroupSettings.HLS_GROUP_SETTINGS_KEY: { + "SegmentLength": 6, + "AdditionalManifests": [{"ManifestNameModifier": ""}], + }, + "Destination": "", + }, + "Outputs": [ + {"VideoDescription": {"CodecSettings": {"Codec": "H_264"}}} + ], + }, + { + "OutputGroupSettings": { + "Type": GroupSettings.FILE_GROUP_SETTINGS, + GroupSettings.FILE_GROUP_SETTINGS_KEY: {"Destination": ""}, + }, + "Outputs": [ + { + "VideoDescription": { + "CodecSettings": {"Codec": "FRAME_CAPTURE"} + }, + "ContainerSettings": {"Container": "RAW"}, + } + ], + }, + { + "OutputGroupSettings": { + "Type": GroupSettings.FILE_GROUP_SETTINGS, + GroupSettings.FILE_GROUP_SETTINGS_KEY: {"Destination": ""}, + }, + "Outputs": [{"ContainerSettings": {"Container": "MP4"}}], + }, + ], + }, + } + + +@pytest.mark.parametrize( + ("source_prefix", "expected_destination"), + [ + ("uploads/", "transcodes/test-video"), + ("", "transcodes/uploads/test-video"), + ("other-prefix/", "uploads/test-video"), + ], +) +def test_get_destination_path(mock_file_config, source_prefix, expected_destination): + """Test get_destination_path with various source prefixes""" + mock_file_config.video_source_key = "uploads/test-video.mp4" + mock_file_config.source_prefix = source_prefix + mock_file_config.destination_prefix = "transcodes/" + + destination_path = get_destination_path(mock_file_config) + assert destination_path == expected_destination + + +def test_add_group_settings_basic(mock_file_config, mock_job_dict): + """Test add_group_settings with basic settings""" + mock_file_config.destination = "transcodes/test-video" + + add_group_settings(mock_job_dict, mock_file_config) + + output_groups = mock_job_dict["Settings"]["OutputGroups"] + assert len(output_groups) == 3 # noqa: PLR2004 + + hls_settings = output_groups[0]["OutputGroupSettings"][ + GroupSettings.HLS_GROUP_SETTINGS_KEY + ] + assert hls_settings["SegmentLength"] == 10 # noqa: PLR2004 + assert hls_settings["AdditionalManifests"][0]["ManifestNameModifier"] == "__index" + + assert output_groups[0]["OutputGroupSettings"][ + GroupSettings.HLS_GROUP_SETTINGS_KEY + ]["Destination"].startswith("s3://destination-bucket/transcodes/test-video") + assert output_groups[1]["OutputGroupSettings"][ + GroupSettings.FILE_GROUP_SETTINGS_KEY + ]["Destination"].startswith("s3://test-bucket/transcodes/test-video") + assert output_groups[2]["OutputGroupSettings"][ + GroupSettings.FILE_GROUP_SETTINGS_KEY + ]["Destination"].startswith("s3://destination-bucket/transcodes/test-video") + + +def test_add_group_settings_with_filters(mock_file_config, mock_job_dict): + """Test add_group_settings with filters""" + mock_file_config.destination = "transcodes/test-video" + mock_file_config.group_settings = {"exclude_mp4": True, "exclude_thumbnail": True} + + add_group_settings(mock_job_dict, mock_file_config) + + output_groups = mock_job_dict["Settings"]["OutputGroups"] + assert len(output_groups) == 1 + assert ( + output_groups[0]["OutputGroupSettings"]["Type"] + == GroupSettings.HLS_GROUP_SETTINGS + ) + + +def test_add_group_settings_with_custom_settings(mock_file_config, mock_job_dict): + """Test add_group_settings with custom settings""" + mock_file_config.destination = "transcodes/test-video" + mock_file_config.group_settings = { + "SegmentLength": 15, + "ManifestNameModifier": "__custom", + } + + add_group_settings(mock_job_dict, mock_file_config) + + hls_settings = mock_job_dict["Settings"]["OutputGroups"][0]["OutputGroupSettings"][ + GroupSettings.HLS_GROUP_SETTINGS_KEY + ] + assert hls_settings["SegmentLength"] == 15 # noqa: PLR2004 + assert hls_settings["AdditionalManifests"][0]["ManifestNameModifier"] == "__custom" + + +def test_add_group_settings_invalid_type(mock_file_config, mock_job_dict): + """Test add_group_settings with invalid group type""" + mock_file_config.destination = "transcodes/test-video" + + mock_job_dict["Settings"]["OutputGroups"][0]["OutputGroupSettings"]["Type"] = ( + "INVALID_TYPE" + ) + + with pytest.raises(ValueError, match="Unsupported group settings type"): + add_group_settings(mock_job_dict, mock_file_config) + + +def test_make_media_convert_job(mock_file_config, mocker): + """Test make_media_convert_job""" + mocker.patch("django.conf.settings.TRANSCODE_JOB_TEMPLATE", "mock_template.json") + mocker.patch("django.conf.settings.VIDEO_TRANSCODE_QUEUE", "default") + mocker.patch("django.conf.settings.AWS_REGION", "us-east-1") + mocker.patch("django.conf.settings.AWS_ACCOUNT_ID", "123456789012") + mocker.patch("django.conf.settings.AWS_ROLE_NAME", "MediaConvertRole") + + mock_template = { + "UserMetadata": {"filter": ""}, + "Queue": "", + "Role": "", + "Settings": { + "Inputs": [{"FileInput": ""}], + "OutputGroups": [ + { + "OutputGroupSettings": { + "Type": GroupSettings.HLS_GROUP_SETTINGS, + GroupSettings.HLS_GROUP_SETTINGS_KEY: { + "SegmentLength": 6, + "AdditionalManifests": [{"ManifestNameModifier": ""}], + }, + "Destination": "", + }, + "Outputs": [ + {"VideoDescription": {"CodecSettings": {"Codec": "H_264"}}} + ], + }, + ], + }, + } + + mock_open = mocker.mock_open(read_data=json.dumps(mock_template)) + mocker.patch("pathlib.Path.open", mock_open) + mocker.patch("pathlib.Path.cwd", return_value=Path("/")) + + job_dict = make_media_convert_job(mock_file_config) + + assert job_dict["UserMetadata"]["filter"] == "default" + assert ( + job_dict["Queue"] + == "arn:aws:mediaconvert:us-east-1:123456789012:queues/default" + ) + assert job_dict["Role"] == "arn:aws:iam::123456789012:role/MediaConvertRole" + assert ( + job_dict["Settings"]["Inputs"][0]["FileInput"] + == "s3://source-bucket/uploads/test-video.mp4" + ) + + +@pytest.mark.parametrize( + ("exclude_mp4", "exclude_thumbnail"), + [ + (True, True), + (True, False), + (False, True), + (False, False), + ], +) +def test_media_convert_job(mocker, exclude_mp4, exclude_thumbnail): + """Test media_convert_job function with different filter combinations""" + mock_client = mocker.MagicMock() + mocker.patch("boto3.client", return_value=mock_client) + + mock_job_dict = {"job": "config"} + mocker.patch( + "mitol.transcoding.api.make_media_convert_job", return_value=mock_job_dict + ) + + mocker.patch( + "django.conf.settings.VIDEO_S3_TRANSCODE_ENDPOINT", + "https://mediaconvert.us-east-1.amazonaws.com", + ) + mocker.patch("django.conf.settings.AWS_REGION", "us-east-1") + + group_settings = {} + if exclude_mp4: + group_settings["exclude_mp4"] = True + if exclude_thumbnail: + group_settings["exclude_thumbnail"] = True + + media_convert_job( + "uploads/test-video.mp4", + group_settings=group_settings, + ) + + boto3.client.assert_called_once_with( + "mediaconvert", + region_name="us-east-1", + endpoint_url="https://mediaconvert.us-east-1.amazonaws.com", + ) + + mock_client.create_job.assert_called_once_with(**mock_job_dict)