feature: create multitasking
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user