Files
streamstack/tests/ingest/test_router.py
T
unkinben 2309e9f43a Initial commit — StreamStack v1
Five-service streaming platform: auth, catalogue, streaming, ingest, thumbnailer.
Includes React frontend served by nginx, NATS JetStream event bus, aiobotocore
async S3, PyAV video metadata + thumbnail extraction, service-to-service JWT auth,
and a full unit + e2e test suite.
2026-05-04 22:16:39 +10:00

130 lines
4.2 KiB
Python

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"