"""Functional tests for the v2 product audio validation endpoint.""" import json from datetime import datetime from typing import Any from unittest import mock import pytest from flask.testing import FlaskClient from flexmock import flexmock from owsrequest import request from assets.constants import asset_types, asset_upload_types, service from tests.unit.models import au_operations # Upload type IDs match the seed in conftest.db_fixture _UPLOAD_TYPE_ID = { asset_upload_types.STEREO: 1, asset_upload_types.ATMOS: 3, } @pytest.fixture def product_id() -> int: return 123 @pytest.fixture def tuid_1() -> int: return 13333333 @pytest.fixture def tuid_2() -> int: return 13333334 @pytest.fixture def v2_audio_url(product_id: int) -> str: """Return the url for validating audio for the fixture product_id.""" return f"/v2/validators/product/{product_id}/audio" def mock_ows_track(product_id: int, tuids: list[int]) -> None: """Stub out ows-track to return the given track tuids for product_id.""" tracks_response = mock.MagicMock( status_code=200, json=mock.MagicMock( return_value={ "items": [{"tuid": tuid} for tuid in tuids], "pagination": {"type": "none", "total_records": len(tuids)}, } ), ) ( flexmock(request) .should_receive("get") .with_args( service.OWS_TRACK, service.OWS_TRACKS_BY_PRODUCT_ID.format(product_id=product_id), ) .and_return(tracks_response) ) def _stereo_upload(upload_id: int, tuid: int, product_id: int) -> dict[str, Any]: return { "id": upload_id, "filename": f"unique_filename_{upload_id}", "user_id": "oa:654321", "token": b"session_token", "original_filename": f"original_{upload_id}.wav", "track_unique_id": tuid, "product_id": product_id, "asset_upload_type_id": _UPLOAD_TYPE_ID[asset_upload_types.STEREO], "deleted": False, "is_correction": False, "updated_timestamp": datetime(2024, 1, 1), "api_version": 2, } def _atmos_upload(upload_id: int, tuid: int, product_id: int) -> dict[str, Any]: return { "id": upload_id, "filename": f"unique_filename_{upload_id}", "user_id": "oa:654321", "token": b"session_token", "original_filename": f"original_{upload_id}.atmos", "track_unique_id": tuid, "product_id": product_id, "asset_upload_type_id": _UPLOAD_TYPE_ID[asset_upload_types.ATMOS], "deleted": False, "is_correction": False, "updated_timestamp": datetime(2024, 1, 1), "api_version": 2, } def _wav_final(upload_id: int) -> dict[str, Any]: return { "asset_upload_id": upload_id, "asset_type": asset_types.TYPE_FILE_WAV, "asset_subtype": asset_types.SUBTYPE_NONE, "bucket": "test-bucket", "filename": f"final_{upload_id}.wav", "duration": 0, } def _atmos_final(upload_id: int) -> dict[str, Any]: return { "asset_upload_id": upload_id, "asset_type": asset_types.TYPE_FILE_ATMOS, "asset_subtype": asset_types.SUBTYPE_NONE, "bucket": "test-bucket", "filename": f"final_{upload_id}.atmos", "duration": 0, } def test_validate_product_audio_valid_no_atmos( db_fixture: None, fixture_client: FlaskClient, product_id: int, tuid_1: int, tuid_2: int, v2_audio_url: str, ) -> None: """Valid product: WAV finals for all tracks, no ATMOS uploads.""" mock_ows_track(product_id, [tuid_1, tuid_2]) au_operations.seed_asset_upload_table( [ _stereo_upload(1, tuid_1, product_id), _stereo_upload(2, tuid_2, product_id), ] ) au_operations.seed_asset_final_table( [ _wav_final(1), _wav_final(2), ] ) response = fixture_client.get(v2_audio_url) assert response.status_code == 200 assert json.loads(response.data.decode()) == {"errorMsgs": []} def test_validate_product_audio_valid_with_atmos( db_fixture: None, fixture_client: FlaskClient, product_id: int, tuid_1: int, tuid_2: int, v2_audio_url: str, ) -> None: """Valid product: WAV finals for all tracks, ATMOS finals for all tracks.""" mock_ows_track(product_id, [tuid_1, tuid_2]) au_operations.seed_asset_upload_table( [ _stereo_upload(1, tuid_1, product_id), _stereo_upload(2, tuid_2, product_id), _atmos_upload(3, tuid_1, product_id), _atmos_upload(4, tuid_2, product_id), ] ) au_operations.seed_asset_final_table( [ _wav_final(1), _wav_final(2), _atmos_final(3), _atmos_final(4), ] ) response = fixture_client.get(v2_audio_url) assert response.status_code == 200 assert json.loads(response.data.decode()) == {"errorMsgs": []} def test_validate_product_audio_missing_stereo( db_fixture: None, fixture_client: FlaskClient, product_id: int, tuid_1: int, tuid_2: int, v2_audio_url: str, ) -> None: """Missing stereo upload for one track produces a missing audio error.""" mock_ows_track(product_id, [tuid_1, tuid_2]) au_operations.seed_asset_upload_table( [ _stereo_upload(1, tuid_1, product_id), # tuid_2 has no stereo upload ] ) au_operations.seed_asset_final_table( [ _wav_final(1), ] ) response = fixture_client.get(v2_audio_url) decoded = json.loads(response.data.decode()) assert response.status_code == 200 assert decoded == { "errorMsgs": [ "This product has missing audio assets, please contact the label user." ] } def test_validate_product_audio_stereo_in_progress( db_fixture: None, fixture_client: FlaskClient, product_id: int, tuid_1: int, tuid_2: int, v2_audio_url: str, ) -> None: """Stereo upload exists but WAV final not yet created produces an in-progress error.""" mock_ows_track(product_id, [tuid_1, tuid_2]) au_operations.seed_asset_upload_table( [ _stereo_upload(1, tuid_1, product_id), _stereo_upload(2, tuid_2, product_id), ] ) # No asset_final records — finals not yet created response = fixture_client.get(v2_audio_url) decoded = json.loads(response.data.decode()) assert response.status_code == 200 assert decoded == { "errorMsgs": [ "This product has in-progress audio asset updates, please contact the label user." ] } def test_validate_product_audio_atmos_in_progress( db_fixture: None, fixture_client: FlaskClient, product_id: int, tuid_1: int, tuid_2: int, v2_audio_url: str, ) -> None: """ATMOS upload exists but ATMOS final not yet created produces an in-progress error.""" mock_ows_track(product_id, [tuid_1, tuid_2]) au_operations.seed_asset_upload_table( [ _stereo_upload(1, tuid_1, product_id), _stereo_upload(2, tuid_2, product_id), _atmos_upload(3, tuid_1, product_id), ] ) au_operations.seed_asset_final_table( [ _wav_final(1), _wav_final(2), # No ATMOS final for upload 3 — still in progress ] ) response = fixture_client.get(v2_audio_url) decoded = json.loads(response.data.decode()) assert response.status_code == 200 assert decoded == { "errorMsgs": [ "This product has in-progress atmos asset updates, please contact the label user." ] }