import io import uuid from unittest.mock import AsyncMock, MagicMock, patch import pytest from httpx import ASGITransport, AsyncClient from streamstack.core.middleware import UserClaims, verify_jwt from streamstack.ingest.app import app ADMIN = UserClaims(sub="admin-uuid", email="admin@test.com", roles=["admin"], jti="jti") VIEWER = UserClaims(sub="viewer-uuid", email="viewer@test.com", roles=["viewer"], jti="jti") MOCK_CATALOGUE_ITEM = { "id": str(uuid.uuid4()), "media_type": "movie", "title": "Test Movie", "s3_key": "media/test.mp4", "bucket": "media", "content_type": "video/mp4", "duration_seconds": 10.0, "size_bytes": 1024, "width": 1920, "height": 1080, "fps": 30.0, "codec": "h264", "thumbnail_s3_key": "thumbnails/test.jpg", "is_published": True, "tags": [], "director": None, "release_year": None, "mpaa_rating": None, "imdb_id": None, "description": None, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", } MOCK_META = { "duration": 10.0, "streams": [ {"type": "video", "codec": "h264", "width": 1920, "height": 1080, "fps": 30.0}, {"type": "audio", "codec": "aac", "sample_rate": 44100, "channels": 2}, ], } @pytest.fixture def mock_s3(): s3 = MagicMock() s3.upload_fileobj.return_value = None s3.head_object.return_value = {"ContentLength": 1024} s3.put_object.return_value = None with patch("streamstack.ingest.service.get_s3_client", return_value=s3): yield s3 @pytest.fixture def mock_metadata(): with patch("streamstack.ingest.service.extract_metadata", return_value=MOCK_META): yield @pytest.fixture def mock_thumbnail(): with patch("streamstack.ingest.service._extract_thumbnail", return_value="thumbnails/test.jpg"): yield @pytest.fixture def mock_catalogue(): http_response = MagicMock() http_response.json.return_value = MOCK_CATALOGUE_ITEM http_response.raise_for_status.return_value = None async_client = MagicMock() async_client.__aenter__ = AsyncMock(return_value=async_client) async_client.__aexit__ = AsyncMock(return_value=False) async_client.post = AsyncMock(return_value=http_response) with patch("streamstack.ingest.service.httpx.AsyncClient", return_value=async_client): yield async_client async def _post_upload(ac, user): app.dependency_overrides[verify_jwt] = lambda: user try: return await ac.post( "/v1/ingest/upload", files={"file": ("test.mp4", io.BytesIO(b"fake video"), "video/mp4")}, data={"media_type": "movie", "title": "Test Movie", "is_published": "true"}, headers={"Authorization": "Bearer fake"}, ) finally: app.dependency_overrides.pop(verify_jwt, None) @pytest.mark.asyncio async def test_upload_success(mock_s3, mock_metadata, mock_thumbnail, mock_catalogue): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: resp = await _post_upload(ac, ADMIN) assert resp.status_code == 201 body = resp.json() assert body["media_type"] == "movie" assert body["title"] == "Test Movie" assert body["duration_seconds"] == 10.0 @pytest.mark.asyncio async def test_upload_requires_admin(mock_s3, mock_metadata, mock_thumbnail, mock_catalogue): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: resp = await _post_upload(ac, VIEWER) assert resp.status_code == 403 @pytest.mark.asyncio async def test_upload_calls_catalogue_api(mock_s3, mock_metadata, mock_thumbnail, mock_catalogue): async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac: await _post_upload(ac, ADMIN) mock_catalogue.post.assert_called_once() call_kwargs = mock_catalogue.post.call_args payload = call_kwargs.kwargs["json"] assert payload["s3_key"].startswith("media/") assert payload["s3_key"].endswith(".mp4") assert payload["duration_seconds"] == 10.0 assert payload["width"] == 1920 assert payload["height"] == 1080 assert payload["codec"] == "h264" assert payload["thumbnail_s3_key"] == "thumbnails/test.jpg"