""" Tests for media management endpoints. """ from __future__ import annotations import uuid from unittest.mock import AsyncMock, patch import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from cpv3.modules.media.models import ArtifactMediaFile, MediaFile from cpv3.modules.users.models import User @pytest.fixture async def test_media_file(test_db_session: AsyncSession, test_user: User) -> MediaFile: """Create a test media file owned by test_user.""" media_file = MediaFile( id=uuid.uuid4(), owner_id=test_user.id, duration_seconds=120.5, frame_rate=30.0, width=1920, height=1080, is_deleted=False, is_active=True, ) test_db_session.add(media_file) await test_db_session.commit() await test_db_session.refresh(media_file) return media_file @pytest.fixture async def other_media_file( test_db_session: AsyncSession, other_user: User ) -> MediaFile: """Create a media file owned by another user.""" media_file = MediaFile( id=uuid.uuid4(), owner_id=other_user.id, duration_seconds=60.0, frame_rate=24.0, width=1280, height=720, is_deleted=False, is_active=True, ) test_db_session.add(media_file) await test_db_session.commit() await test_db_session.refresh(media_file) return media_file @pytest.fixture async def test_artifact( test_db_session: AsyncSession, test_media_file: MediaFile ) -> ArtifactMediaFile: """Create a test artifact linked to test_media_file.""" artifact = ArtifactMediaFile( id=uuid.uuid4(), media_file_id=test_media_file.id, artifact_type="THUMBNAIL", is_deleted=False, is_active=True, ) test_db_session.add(artifact) await test_db_session.commit() await test_db_session.refresh(artifact) return artifact class TestGetMetaEndpoint: """Tests for GET /api/media/get_meta/.""" async def test_get_meta_success(self, auth_client: AsyncClient): """Test getting media metadata.""" with patch( "cpv3.modules.media.router.probe_media", new_callable=AsyncMock, return_value={ "streams": [], "format": {"filename": "test.mp4", "duration": "120.5"}, }, ): response = await auth_client.get( "/api/media/get_meta/", params={"file_path": "uploads/test.mp4"} ) assert response.status_code == 200 async def test_get_meta_unauthenticated(self, async_client: AsyncClient): """Test getting metadata without auth returns 401.""" response = await async_client.get( "/api/media/get_meta/", params={"file_path": "test.mp4"} ) assert response.status_code == 401 class TestSilenceRemoveEndpoint: """Tests for POST /api/media/silence_remove.""" async def test_silence_remove_success(self, auth_client: AsyncClient, mock_storage): """Test silence removal returns file info.""" with patch( "cpv3.modules.media.router.remove_silence", new_callable=AsyncMock, ) as mock_remove: mock_remove.return_value = mock_storage.get_file_info.return_value response = await auth_client.post( "/api/media/silence_remove", json={"file_path": "uploads/test.mp4", "folder": "processed"}, ) assert response.status_code == 200 data = response.json() assert "file_path" in data async def test_silence_remove_unauthenticated(self, async_client: AsyncClient): """Test silence removal without auth returns 401.""" response = await async_client.post( "/api/media/silence_remove", json={"file_path": "test.mp4"}, ) assert response.status_code == 401 class TestConvertEndpoint: """Tests for POST /api/media/convert.""" async def test_convert_success(self, auth_client: AsyncClient, mock_storage): """Test media conversion returns file info.""" with patch( "cpv3.modules.media.router.convert_to_mp4", new_callable=AsyncMock, ) as mock_convert: mock_convert.return_value = mock_storage.get_file_info.return_value response = await auth_client.post( "/api/media/convert", json={"file_path": "uploads/test.mov", "folder": "converted"}, ) assert response.status_code == 200 data = response.json() assert "file_path" in data async def test_convert_unauthenticated(self, async_client: AsyncClient): """Test conversion without auth returns 401.""" response = await async_client.post( "/api/media/convert", json={"file_path": "test.mov"}, ) assert response.status_code == 401 class TestListMediaFilesEndpoint: """Tests for GET /api/media/mediafiles/.""" async def test_list_mediafiles( self, auth_client: AsyncClient, test_media_file: MediaFile ): """Test listing media files.""" response = await auth_client.get("/api/media/mediafiles/") assert response.status_code == 200 data = response.json() assert isinstance(data, list) async def test_list_mediafiles_unauthenticated(self, async_client: AsyncClient): """Test listing media files without auth returns 401.""" response = await async_client.get("/api/media/mediafiles/") assert response.status_code == 401 class TestCreateMediaFileEndpoint: """Tests for POST /api/media/mediafiles/.""" async def test_create_mediafile_success(self, auth_client: AsyncClient): """Test creating a media file entry.""" response = await auth_client.post( "/api/media/mediafiles/", json={ "duration_seconds": 180.0, "frame_rate": 60.0, "width": 3840, "height": 2160, }, ) assert response.status_code == 201 data = response.json() assert data["duration_seconds"] == 180.0 assert data["width"] == 3840 async def test_create_mediafile_unauthenticated(self, async_client: AsyncClient): """Test creating media file without auth returns 401.""" response = await async_client.post( "/api/media/mediafiles/", json={"duration_seconds": 60.0}, ) assert response.status_code == 401 class TestRetrieveMediaFileEndpoint: """Tests for GET /api/media/mediafiles/{media_file_id}/.""" async def test_retrieve_own_mediafile( self, auth_client: AsyncClient, test_media_file: MediaFile ): """Test retrieving own media file.""" response = await auth_client.get(f"/api/media/mediafiles/{test_media_file.id}/") assert response.status_code == 200 data = response.json() assert data["id"] == str(test_media_file.id) async def test_retrieve_other_mediafile_as_staff( self, staff_client: AsyncClient, test_media_file: MediaFile ): """Test staff can retrieve any media file.""" response = await staff_client.get( f"/api/media/mediafiles/{test_media_file.id}/" ) assert response.status_code == 200 async def test_retrieve_nonexistent_mediafile(self, auth_client: AsyncClient): """Test retrieving nonexistent media file returns 404.""" fake_id = uuid.uuid4() response = await auth_client.get(f"/api/media/mediafiles/{fake_id}/") assert response.status_code == 404 async def test_retrieve_other_mediafile_forbidden( self, auth_client: AsyncClient, other_media_file: MediaFile ): """Test regular user cannot retrieve other user's media file.""" response = await auth_client.get( f"/api/media/mediafiles/{other_media_file.id}/" ) assert response.status_code == 403 class TestPatchMediaFileEndpoint: """Tests for PATCH /api/media/mediafiles/{media_file_id}/.""" async def test_patch_own_mediafile( self, auth_client: AsyncClient, test_media_file: MediaFile ): """Test updating own media file.""" response = await auth_client.patch( f"/api/media/mediafiles/{test_media_file.id}/", json={"notes": "Updated notes"}, ) assert response.status_code == 200 data = response.json() assert data["notes"] == "Updated notes" async def test_patch_other_mediafile_as_staff( self, staff_client: AsyncClient, test_media_file: MediaFile ): """Test staff can update any media file.""" response = await staff_client.patch( f"/api/media/mediafiles/{test_media_file.id}/", json={"notes": "Staff updated"}, ) assert response.status_code == 200 async def test_patch_nonexistent_mediafile(self, auth_client: AsyncClient): """Test patching nonexistent media file returns 404.""" fake_id = uuid.uuid4() response = await auth_client.patch( f"/api/media/mediafiles/{fake_id}/", json={"notes": "test"}, ) assert response.status_code == 404 async def test_patch_other_mediafile_forbidden( self, auth_client: AsyncClient, other_media_file: MediaFile ): """Test regular user cannot update other user's media file.""" response = await auth_client.patch( f"/api/media/mediafiles/{other_media_file.id}/", json={"notes": "hacked"}, ) assert response.status_code == 403 class TestDeleteMediaFileEndpoint: """Tests for DELETE /api/media/mediafiles/{media_file_id}/.""" async def test_delete_own_mediafile( self, auth_client: AsyncClient, test_media_file: MediaFile ): """Test deleting own media file.""" response = await auth_client.delete( f"/api/media/mediafiles/{test_media_file.id}/" ) assert response.status_code == 204 async def test_delete_other_mediafile_as_staff( self, staff_client: AsyncClient, test_media_file: MediaFile ): """Test staff can delete any media file.""" response = await staff_client.delete( f"/api/media/mediafiles/{test_media_file.id}/" ) assert response.status_code == 204 async def test_delete_nonexistent_mediafile(self, auth_client: AsyncClient): """Test deleting nonexistent media file returns 404.""" fake_id = uuid.uuid4() response = await auth_client.delete(f"/api/media/mediafiles/{fake_id}/") assert response.status_code == 404 async def test_delete_other_mediafile_forbidden( self, auth_client: AsyncClient, other_media_file: MediaFile ): """Test regular user cannot delete other user's media file.""" response = await auth_client.delete( f"/api/media/mediafiles/{other_media_file.id}/" ) assert response.status_code == 403 class TestListArtifactsEndpoint: """Tests for GET /api/media/artifacts/.""" async def test_list_artifacts( self, auth_client: AsyncClient, test_artifact: ArtifactMediaFile ): """Test listing artifacts.""" response = await auth_client.get("/api/media/artifacts/") assert response.status_code == 200 data = response.json() assert isinstance(data, list) async def test_list_artifacts_unauthenticated(self, async_client: AsyncClient): """Test listing artifacts without auth returns 401.""" response = await async_client.get("/api/media/artifacts/") assert response.status_code == 401 class TestCreateArtifactEndpoint: """Tests for POST /api/media/artifacts/.""" async def test_create_artifact_success( self, auth_client: AsyncClient, test_media_file: MediaFile ): """Test creating an artifact.""" response = await auth_client.post( "/api/media/artifacts/", json={ "media_file_id": str(test_media_file.id), "artifact_type": "AUDIO_PROXY", }, ) assert response.status_code == 201 data = response.json() assert data["artifact_type"] == "AUDIO_PROXY" async def test_create_artifact_unauthenticated(self, async_client: AsyncClient): """Test creating artifact without auth returns 401.""" response = await async_client.post( "/api/media/artifacts/", json={ "media_file_id": str(uuid.uuid4()), "artifact_type": "THUMBNAIL", }, ) assert response.status_code == 401 class TestRetrieveArtifactEndpoint: """Tests for GET /api/media/artifacts/{artifact_id}/.""" async def test_retrieve_artifact( self, auth_client: AsyncClient, test_artifact: ArtifactMediaFile ): """Test retrieving an artifact.""" response = await auth_client.get(f"/api/media/artifacts/{test_artifact.id}/") assert response.status_code == 200 data = response.json() assert data["id"] == str(test_artifact.id) async def test_retrieve_nonexistent_artifact(self, auth_client: AsyncClient): """Test retrieving nonexistent artifact returns 404.""" fake_id = uuid.uuid4() response = await auth_client.get(f"/api/media/artifacts/{fake_id}/") assert response.status_code == 404 class TestPatchArtifactEndpoint: """Tests for PATCH /api/media/artifacts/{artifact_id}/.""" async def test_patch_artifact( self, auth_client: AsyncClient, test_artifact: ArtifactMediaFile ): """Test updating an artifact.""" response = await auth_client.patch( f"/api/media/artifacts/{test_artifact.id}/", json={"is_deleted": True}, ) assert response.status_code == 200 data = response.json() assert data["is_deleted"] is True async def test_patch_nonexistent_artifact(self, auth_client: AsyncClient): """Test patching nonexistent artifact returns 404.""" fake_id = uuid.uuid4() response = await auth_client.patch( f"/api/media/artifacts/{fake_id}/", json={"is_deleted": True}, ) assert response.status_code == 404 class TestDeleteArtifactEndpoint: """Tests for DELETE /api/media/artifacts/{artifact_id}/.""" async def test_delete_artifact( self, auth_client: AsyncClient, test_artifact: ArtifactMediaFile ): """Test deleting an artifact.""" response = await auth_client.delete(f"/api/media/artifacts/{test_artifact.id}/") assert response.status_code == 204 async def test_delete_nonexistent_artifact(self, auth_client: AsyncClient): """Test deleting nonexistent artifact returns 404.""" fake_id = uuid.uuid4() response = await auth_client.delete(f"/api/media/artifacts/{fake_id}/") assert response.status_code == 404