"""Tests for asset_status logic module.""" import json from datetime import datetime from typing import Any import pytest import pytest_mock from flexmock import flexmock from freezegun import freeze_time from sqlalchemy.exc import SQLAlchemyError from assets.constants import ( api, asset_status as asset_status_const, asset_types, asset_upload_types, error as error_constants, field_const, ) from assets.exceptions import AssetUploadNotFound, NoAssetsFound from assets.logic import asset_status from assets.models import ( asset_final as asset_final_model, asset_status as asset_status_model, asset_upload as asset_upload_model, asset_upload_type as asset_upload_type_model, ) @pytest.fixture def asset_upload_data() -> dict[str, Any]: """Fixture for asset_upload model data.""" return { "id": 1, "product_id": 123, "upc": 54321, "track_unique_id": 10, "asset_type": "WAV", "asset_upload_type_id": 1, "is_correction": False, "original_filename": "audio/original_filename_5.wav", } @pytest.fixture def asset_final_data() -> dict[str, Any]: """Fixture for asset_final model data.""" return { "id": 1, "asset_upload_id": 1, "asset_type": "jpg", "asset_subtype": "cover", "bucket": "bucket", "filename": "qwe_123.jpg", } @pytest.fixture def asset_final_status() -> dict[str, Any]: """Fixture for create_status response.""" return { "id": 1, "asset_upload_id": 1, "asset_type": "jpg", "asset_subtype": "cover", "bucket": "bucket", "filename": "qwe_123.jpg", } @pytest.fixture def final_status_data() -> dict[str, Any]: """Fixture for final status data.""" return { "filename": "filename.ext", "status": "success", "description": "", "message": { "function": "some_function", "status": "success", "input": {"key": "filename.ext"}, }, "timestamp": "2017-05-05T22:23:24.333Z", "final_assets": [{"key": "filename.ext", "bucket": "bucket1"}], } def test_create_status_by_filename( asset_upload_data: dict[str, Any], status_data: dict[str, Any] ) -> None: """Test for creating asset status by filename.""" ( flexmock(asset_upload_model) .should_receive("get_asset_upload") .with_args("filename", api_version=api.API_VERSION_V2) .and_return(asset_upload_data) ) ( flexmock(asset_status_model) .should_receive("create_asset_status") .with_args( asset_upload_data["id"], status_data["status"], datetime(2017, 5, 5, 22, 23, 24, 333000), status_data["description"], json.dumps(status_data["message"]), ) .and_return(None) ) result = asset_status.create_status_by_filename(**status_data) assert result def test_create_status_with_nonexistent_asset(status_data: dict[str, Any]) -> None: """Test for creating asset status for nonexistent asset.""" ( flexmock(asset_upload_model) .should_receive("get_asset_upload") .with_args("filename", api_version=api.API_VERSION_V2) .and_raise(AssetUploadNotFound(error_constants.ERROR_ASSET_UPLOAD_NOT_FOUND)) ) with pytest.raises(AssetUploadNotFound) as exc: asset_status.create_status_by_filename(**status_data) assert exc.value.description == error_constants.ERROR_ASSET_UPLOAD_NOT_FOUND def test_create_status_with_asset_upload_error(status_data: dict[str, Any]) -> None: """Test for creating asset status with error in asset_upload model.""" error_message = "Query error" ( flexmock(asset_upload_model) .should_receive("get_asset_upload") .with_args("filename", api_version=api.API_VERSION_V2) .and_raise(SQLAlchemyError(error_message)) ) with pytest.raises(SQLAlchemyError) as exc: asset_status.create_status_by_filename(**status_data) assert str(exc.value) == error_message def test_create_status_with_asset_status_error( asset_upload_data: dict[str, Any], status_data: dict[str, Any] ) -> None: """Test for creating asset status with error in asset_status model.""" ( flexmock(asset_upload_model) .should_receive("get_asset_upload") .with_args("filename", api_version=api.API_VERSION_V2) .and_return(asset_upload_data) ) error_message = "Query error" ( flexmock(asset_status_model) .should_receive("create_asset_status") .with_args( asset_upload_data["id"], status_data["status"], datetime(2017, 5, 5, 22, 23, 24, 333000), status_data["description"], json.dumps(status_data["message"]), ) .and_raise(SQLAlchemyError(error_message)) ) with pytest.raises(SQLAlchemyError) as exc: asset_status.create_status_by_filename(**status_data) assert str(exc.value) == error_message def test_create_status_by_asset_upload_id( asset_upload_data: dict[str, Any], status_data: dict[str, Any] ) -> None: """Test for creating asset status directly by asset_upload_id.""" ( flexmock(asset_status_model) .should_receive("create_asset_status") .with_args( asset_upload_id=asset_upload_data["id"], status=status_data["status"], status_time=datetime(2017, 5, 5, 22, 23, 24, 333000), description=status_data["description"], message=json.dumps(status_data["message"]), ) .and_return(None) .once() ) result = asset_status.create_status_by_asset_upload_id( asset_upload_id=asset_upload_data["id"], status=status_data["status"], description=status_data["description"], message=status_data["message"], timestamp=status_data["timestamp"], ) assert result == str(asset_upload_data["id"]) def test_create_status_by_asset_upload_id_with_error( asset_upload_data: dict[str, Any], status_data: dict[str, Any] ) -> None: """Test for creating asset status by asset_upload_id when model raises an error.""" error_message = "Query error" ( flexmock(asset_status_model) .should_receive("create_asset_status") .and_raise(SQLAlchemyError(error_message)) ) with pytest.raises(SQLAlchemyError) as exc: asset_status.create_status_by_asset_upload_id( asset_upload_id=asset_upload_data["id"], status=status_data["status"], description=status_data["description"], message=status_data["message"], timestamp=status_data["timestamp"], ) assert str(exc.value) == error_message def test_get_current_status( asset_upload_data: dict[str, Any], status_data: dict[str, Any] ) -> None: """Test for getting asset status.""" ( flexmock(asset_status_model) .should_receive("get_last_asset_status") .with_args(asset_upload_data["id"]) .and_return(status_data) ) assert asset_status.get_current_status(asset_upload_data["id"]) == status_data def test_get_current_status_with_asset_status_error( asset_upload_data: dict[str, Any], status_data: dict[str, Any] ) -> None: """Test for getting asset status with error in asset_status model.""" error_message = "Query error" ( flexmock(asset_status_model) .should_receive("get_last_asset_status") .with_args(asset_upload_data["id"]) .and_raise(SQLAlchemyError(error_message)) ) with pytest.raises(SQLAlchemyError) as exc: asset_status.get_current_status(asset_upload_data["id"]) assert str(exc.value) == error_message @pytest.mark.parametrize( "state,status", [ ( asset_status_const.STATE_ENCODING_PROCESSING, asset_status_const.STATUS_ENCODING_PROGRESSING, ), ( asset_status_const.STATE_ENCODING_COMPLETED, asset_status_const.STATUS_ENCODING_COMPLETED, ), ( asset_status_const.STATE_ENCODING_WARNING, asset_status_const.STATUS_ENCODING_WARNING, ), ( asset_status_const.STATE_ENCODING_ERROR, asset_status_const.STATUS_ENCODING_ERROR, ), ("Something", asset_status_const.STATUS_ENCODING_UNKNOWN), ], ) def test_map_encoding_state_to_status(state: str, status: str) -> None: """Test mapping encoding state to status.""" result = asset_status.map_encoding_state_to_status(state) assert result == status @pytest.fixture def fixture_final_assets_tif() -> list[Any]: """Return final assets data for tif.""" return [ { "asset_subtype": "cover", "asset_type": "JPG", "bucket": "qa-asset-storage", "key": "images/v2/product/cover/test_image_2000_2000.jpeg", }, { "asset_subtype": "large_cover", "asset_type": "JPG", "bucket": "qa-asset-storage", "key": "images/v2/product/large_cover/test_image_2000_2000.jpeg", }, { "asset_subtype": "xlarge_cover", "asset_type": "JPG", "bucket": "qa-asset-storage", "key": "images/v2/product/xlarge_cover/test_image_2000_2000.jpeg", }, { "asset_subtype": None, "asset_type": "TIF", "bucket": "qa-orcd-mezzanine-assets", "key": "images/tif/test_image_2000_2000.tif", }, ] @pytest.fixture def fixture_final_assets() -> list[Any]: """Return final assets data.""" return [ { "asset_type": "JPEG", "key": "img/jpeg/test_image_2000_2000.jpeg", "bucket": "some_bucket", }, { "container": "flac", "key": "test_flac.flac", "bucket": "some_bucket", "duration": 10, "channels": 2, "codec": "flac", "sample_rate": 44100, "bit_rate": None, "bit_depth": 16, }, {"container": "wave", "key": "test_wav.wav", "bucket": "some_bucket"}, {"container": "mpeg audio", "key": "test_mp3.mp3", "bucket": "some_bucket"}, ] @pytest.fixture def fixture_final_assets_no_asset_type() -> list[Any]: """Return final assets data without asset_type.""" return [ {"key": "img/jpeg/test_image_2000_2000.jpeg", "bucket": "some_bucket"}, {"key": "img/jpg/test_image_110_110.jpeg", "bucket": "some_bucket"}, {"key": "test_wav.wav", "bucket": "some_bucket"}, ] @pytest.fixture def create_status_mocks( asset_final_status: dict[str, Any], asset_upload_data: dict[str, Any] ) -> dict[str, Any]: """Return create_status_and_final_assets subcall mocks.""" return { "create_status": asset_final_status["asset_upload_id"], "create_asset_final": None, "get_asset": asset_upload_data, } @pytest.fixture def final_assets_fixtures( fixture_final_assets: list[Any], fixture_final_assets_no_asset_type: list[Any] ) -> dict[str, Any]: """Final assets fixtures.""" return { "empty": [], "final": fixture_final_assets, "final_no_asset_type": fixture_final_assets_no_asset_type, } @pytest.mark.parametrize("test_type", ["empty", "final", "final_no_asset_type"]) def test_create_status_and_final_assets( final_status_data: dict[str, Any], test_type: str, create_status_mocks: dict[str, Any], asset_upload_data: dict[str, Any], final_assets_fixtures: dict[str, Any], ) -> None: """Test for test_create_status_and_final_assets status.""" final_assets = final_assets_fixtures[test_type] final_status_data["final_assets"] = final_assets ( flexmock(asset_upload_model) .should_receive("get_asset_upload") .with_args("filename", api_version=api.API_VERSION_V2) .and_return(asset_upload_data) ) ( flexmock(asset_status) .should_receive("create_status_by_asset_upload_id") .and_return(str(create_status_mocks["create_status"])) ) (flexmock(asset_status).should_receive("update_track_duration_in_ar_db")) ( flexmock(asset_upload_type_model) .should_receive("resolve_asset_upload_type") .and_return(asset_upload_types.STEREO) ) if test_type == "empty": ( flexmock(asset_final_model) .should_receive("create_asset_final") .and_return(create_status_mocks["create_asset_final"]) .times(0) ) elif test_type == "final": ( flexmock(asset_final_model) .should_receive("create_asset_final") .with_args( asset_upload_id=1, asset_type="JPEG", asset_subtype="none", filename="img/jpeg/test_image_2000_2000.jpeg", bucket="some_bucket", duration=0, channels=None, codec=None, sample_rate=None, bit_rate=None, bit_depth=None, ) .and_return(create_status_mocks["create_asset_final"]) .times(1) ) ( flexmock(asset_final_model) .should_receive("create_asset_final") .with_args( asset_upload_id=1, asset_type="FLAC", asset_subtype="none", filename="test_flac.flac", bucket="some_bucket", duration=10, channels=2, codec="flac", sample_rate=44100, bit_rate=None, bit_depth=16, ) .and_return(create_status_mocks["create_asset_final"]) .times(1) ) ( flexmock(asset_final_model) .should_receive("create_asset_final") .with_args( asset_upload_id=1, asset_type="WAV", asset_subtype="none", filename="test_wav.wav", bucket="some_bucket", duration=0, channels=None, codec=None, sample_rate=None, bit_rate=None, bit_depth=None, ) .and_return(create_status_mocks["create_asset_final"]) .times(1) ) ( flexmock(asset_final_model) .should_receive("create_asset_final") .with_args( asset_upload_id=1, asset_type="MP3_192", asset_subtype="none", filename="test_mp3.mp3", bucket="some_bucket", duration=0, channels=None, codec=None, sample_rate=None, bit_rate=None, bit_depth=None, ) .and_return(create_status_mocks["create_asset_final"]) .times(1) ) elif test_type == "final_no_asset_type": ( flexmock(asset_final_model) .should_receive("create_asset_final") .with_args( asset_upload_id=1, asset_type=None, asset_subtype="none", filename="img/jpeg/test_image_2000_2000.jpeg", bucket="some_bucket", duration=0, channels=None, codec=None, sample_rate=None, bit_rate=None, bit_depth=None, ) .and_return(create_status_mocks["create_asset_final"]) ) ( flexmock(asset_final_model) .should_receive("create_asset_final") .with_args( asset_upload_id=1, asset_type=None, asset_subtype="none", filename="img/jpg/test_image_110_110.jpeg", bucket="some_bucket", duration=0, channels=None, codec=None, sample_rate=None, bit_rate=None, bit_depth=None, ) .and_return(create_status_mocks["create_asset_final"]) .times(1) ) ( flexmock(asset_final_model) .should_receive("create_asset_final") .with_args( asset_upload_id=1, asset_type=None, asset_subtype="none", filename="test_wav.wav", bucket="some_bucket", duration=0, channels=None, codec=None, sample_rate=None, bit_rate=None, bit_depth=None, ) .and_return(create_status_mocks["create_asset_final"]) .times(1) ) asset_status.create_status_and_final_assets(**final_status_data) def test_create_status_and_final_assets_frontend_distro_v2_ff_on( mocker: pytest_mock.MockerFixture, final_status_data: dict[str, Any], fixture_final_assets: list[Any], create_status_mocks: dict[str, Any], asset_upload_data: dict[str, Any], ) -> None: """Test create_status_and_final_assets with frontend distro v2 ff on.""" final_status_data["final_assets"] = fixture_final_assets mocker.patch.object( asset_upload_model, "get_asset_upload", return_value=asset_upload_data ) mocker.patch.object( asset_status, "create_status_by_asset_upload_id", return_value=str(create_status_mocks["create_status"]), ) mocker.patch.object( asset_upload_type_model, "resolve_asset_upload_type", return_value=asset_upload_types.STEREO, ) mocker.patch.object(asset_final_model, "create_asset_final", return_value=True) mock_update_track_duration_in_ar_db = mocker.patch.object( asset_status, "update_track_duration_in_ar_db" ) asset_status.create_status_and_final_assets(**final_status_data) mock_update_track_duration_in_ar_db.assert_called_once() def test_create_status_and_final_assets_create_status_failure( final_status_data: dict[str, Any], create_status_mocks: dict[str, Any], asset_upload_data: dict[str, Any], ) -> None: """Test test_create_status_and_final_assets with create_status error.""" error_message = "fatal_error_create_status" ( flexmock(asset_upload_model) .should_receive("get_asset_upload") .and_return(asset_upload_data) ) ( flexmock(asset_status) .should_receive("create_status_by_asset_upload_id") .and_raise(SQLAlchemyError(error_message)) ) (flexmock(asset_final_model).should_receive("create_asset_final").never()) with pytest.raises(SQLAlchemyError) as exc: asset_status.create_status_and_final_assets(**final_status_data) assert str(exc.value) == error_message def test_create_status_and_final_assets_create_asset_final_failure( final_status_data: dict[str, Any], create_status_mocks: dict[str, Any], asset_upload_data: dict[str, Any], ) -> None: """Test test_create_status_and_final_assets with create_asset_final error.""" error_message = "fatal_error_create_asset_final" ( flexmock(asset_upload_model) .should_receive("get_asset_upload") .and_return(asset_upload_data) ) ( flexmock(asset_status) .should_receive("create_status_by_asset_upload_id") .and_return(str(create_status_mocks["create_status"])) ) ( flexmock(asset_upload_type_model) .should_receive("resolve_asset_upload_type") .and_return(asset_upload_types.STEREO) ) ( flexmock(asset_final_model) .should_receive("create_asset_final") .and_raise(SQLAlchemyError(error_message)) ) with pytest.raises(SQLAlchemyError) as exc: asset_status.create_status_and_final_assets(**final_status_data) assert str(exc.value) == error_message def test_update_track_duration_in_ar_db_with_deleted() -> None: """Test update_track_duration_in_ar_db when asset upload deleted.""" asset_upload_id = 100 ( flexmock(asset_status) .should_receive("get_asset_info_for_track_duration_update") .and_raise(NoAssetsFound(error_constants.ERROR_ASSET_NOT_FOUND)) ) with pytest.raises(NoAssetsFound) as exc: asset_status.update_track_duration_in_ar_db(asset_upload_id) assert exc.value.description == error_constants.ERROR_ASSET_NOT_FOUND def test_update_track_duration_in_ar_db_without_correction() -> None: """Test update_track_duration_in_ar_db without correction.""" asset_upload_id = 100 ( flexmock(asset_status) .should_receive("get_asset_info_for_track_duration_update") .and_return( { "upc": 1234567890, "product_id": 500, "track_unique_id": 1, "duration": 61000, "is_correction": False, } ) ) ( flexmock(asset_status) .should_receive("update_track_duration") .with_args(1, 1, 1) .and_return( { "id": 600, "upc": 1234567890, "track_id": 1, "cd": 1, "length_minute": 1, "lenth_seconds": 1, "last_updated": datetime(2017, 5, 5, 22, 23, 24, 333000), } ) ) asset_status.update_track_duration_in_ar_db(asset_upload_id) @freeze_time("2021-05-01") def test_update_track_duration_in_ar_db_with_correction() -> None: """Test update_track_duration_in_ar_db with correction.""" asset_upload_id = 100 ( flexmock(asset_status) .should_receive("get_asset_info_for_track_duration_update") .and_return( { "upc": 1234567890, "product_id": 500, "track_unique_id": 7, "duration": 61000, "is_correction": True, } ) ) ( flexmock(asset_status) .should_receive("get_release_correction_by_release_id") .with_args(500) .and_return({"release_correction_id": 10}) ) ( flexmock(asset_status) .should_receive("set_release_correction_detail_by_track_unique_id") .with_args(10, 7, "length_minute", 1) ) ( flexmock(asset_status) .should_receive("set_release_correction_detail_by_track_unique_id") .with_args(10, 7, "length_seconds", 1) ) ( flexmock(asset_status) .should_receive("set_release_correction_detail_by_track_unique_id") .with_args(10, 7, "last_updated", datetime.now().strftime("%Y-%m-%d %H:%M:%S")) ) ( flexmock(asset_status) .should_receive("set_release_correction_detail_by_track_unique_id") .times(3) ) asset_status.update_track_duration_in_ar_db(asset_upload_id) def test_create_status_and_final_assets_frontend_distro_v2_ff_on_only_tif( mocker: pytest_mock.MockerFixture, final_status_data: dict[str, Any], fixture_final_assets_tif: list[Any], create_status_mocks: dict[str, Any], asset_upload_data: dict[str, Any], ) -> None: """Test create_status_and_final_assets with frontend distro v2 ff on.""" final_status_data["final_assets"] = fixture_final_assets_tif mocker.patch.object( asset_upload_model, "get_asset_upload", return_value=asset_upload_data ) mocker.patch.object( asset_status, "create_status_by_asset_upload_id", return_value=str(create_status_mocks["create_status"]), ) mocker.patch.object( asset_upload_type_model, "resolve_asset_upload_type", return_value=asset_upload_types.STEREO, ) mocker.patch.object(asset_final_model, "create_asset_final", return_value=True) mock_update_track_duration_in_ar_db = mocker.patch.object( asset_status, "update_track_duration_in_ar_db" ) asset_status.create_status_and_final_assets(**final_status_data) mock_update_track_duration_in_ar_db.assert_not_called() def test_select_final_asset_type_returns_atmos_when_upload_type_is_atmos( asset_upload_data: dict[str, Any], ) -> None: """_select_final_asset_type returns TYPE_FILE_ATMOS when asset_upload_type is ATMOS.""" ( flexmock(asset_upload_type_model) .should_receive("resolve_asset_upload_type") .with_args(asset_upload_data[field_const.ASSET_UPLOAD_TYPE_ID]) .and_return(asset_upload_types.ATMOS) ) final_asset = {"asset_type": "WAV", "key": "test.wav", "bucket": "some_bucket"} result = asset_status._select_final_asset_type(final_asset, asset_upload_data) assert result == asset_types.TYPE_FILE_ATMOS def test_select_final_asset_type_uses_explicit_asset_type_for_non_atmos( asset_upload_data: dict[str, Any], ) -> None: """_select_final_asset_type returns asset_type from final_asset for non-ATMOS uploads.""" ( flexmock(asset_upload_type_model) .should_receive("resolve_asset_upload_type") .and_return(asset_upload_types.STEREO) ) final_asset = {"asset_type": "JPEG", "key": "image.jpeg", "bucket": "some_bucket"} result = asset_status._select_final_asset_type(final_asset, asset_upload_data) assert result == "JPEG" @pytest.mark.parametrize( "container, expected_type", [ ("flac", asset_types.TYPE_FILE_FLAC), ("wave", asset_types.TYPE_FILE_WAV), ("mpeg audio", asset_types.TYPE_FILE_MP3_192), ], ) def test_select_final_asset_type_maps_container_to_type( container: str, expected_type: str, asset_upload_data: dict[str, Any] ) -> None: """_select_final_asset_type maps known audio containers to asset types.""" ( flexmock(asset_upload_type_model) .should_receive("resolve_asset_upload_type") .and_return(asset_upload_types.STEREO) ) final_asset = {"container": container, "key": "audio.ext", "bucket": "some_bucket"} result = asset_status._select_final_asset_type(final_asset, asset_upload_data) assert result == expected_type