Files
streamstack/tests/catalogue/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

193 lines
6.3 KiB
Python

from unittest.mock import AsyncMock, patch
import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from streamstack.catalogue.app import app
from streamstack.catalogue.models import Base, MediaItem
from streamstack.core.db import get_db
from streamstack.core.middleware import UserClaims, verify_jwt
@pytest.fixture
async def db_session():
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
session_factory = async_sessionmaker(engine, expire_on_commit=False)
async with session_factory() as session:
yield session
await engine.dispose()
@pytest.fixture
def admin_user():
return UserClaims(sub="admin-uuid", email="admin@test.com", roles=["admin"], jti="test-jti")
@pytest.fixture
def viewer_user():
return UserClaims(sub="viewer-uuid", email="viewer@test.com", roles=["viewer"], jti="test-jti")
@pytest.fixture
def mock_nats():
nc = AsyncMock()
with patch("streamstack.catalogue.service.get_nats", return_value=nc):
yield nc
@pytest.fixture
async def test_client(db_session, mock_nats):
app.dependency_overrides[get_db] = lambda: db_session
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
yield ac
app.dependency_overrides.clear()
@pytest.mark.asyncio
async def test_health(test_client):
resp = await test_client.get("/v1/health")
assert resp.status_code == 200
@pytest.mark.asyncio
async def test_list_empty(test_client):
resp = await test_client.get("/v1/catalogue/")
assert resp.status_code == 200
body = resp.json()
assert body["items"] == []
assert body["total"] == 0
@pytest.mark.asyncio
async def test_create_requires_admin(test_client, viewer_user, db_session):
app.dependency_overrides[get_db] = lambda: db_session
app.dependency_overrides[verify_jwt] = lambda: viewer_user
try:
resp = await test_client.post(
"/v1/catalogue/",
json={"media_type": "media", "title": "Test", "s3_key": "media/test.mp4"},
headers={"Authorization": "Bearer fake"},
)
finally:
app.dependency_overrides.pop(verify_jwt, None)
assert resp.status_code == 403
@pytest.mark.asyncio
async def test_create_movie(test_client, admin_user, db_session):
app.dependency_overrides[get_db] = lambda: db_session
app.dependency_overrides[verify_jwt] = lambda: admin_user
try:
resp = await test_client.post(
"/v1/catalogue/",
json={
"media_type": "movie",
"title": "Inception",
"s3_key": "media/inception.mp4",
"director": "Christopher Nolan",
"release_year": 2010,
"is_published": True,
},
headers={"Authorization": "Bearer fake"},
)
finally:
app.dependency_overrides.pop(verify_jwt, None)
assert resp.status_code == 201
body = resp.json()
assert body["media_type"] == "movie"
assert body["director"] == "Christopher Nolan"
assert body["release_year"] == 2010
@pytest.mark.asyncio
async def test_create_tv_series(test_client, admin_user, db_session):
app.dependency_overrides[get_db] = lambda: db_session
app.dependency_overrides[verify_jwt] = lambda: admin_user
try:
resp = await test_client.post(
"/v1/catalogue/",
json={
"media_type": "tv_series",
"title": "Breaking Bad S01E01",
"s3_key": "media/bb-s01e01.mp4",
"show_name": "Breaking Bad",
"season": 1,
"episode": 1,
"is_published": True,
},
headers={"Authorization": "Bearer fake"},
)
finally:
app.dependency_overrides.pop(verify_jwt, None)
assert resp.status_code == 201
body = resp.json()
assert body["media_type"] == "tv_series"
assert body["show_name"] == "Breaking Bad"
assert body["season"] == 1
@pytest.mark.asyncio
async def test_create_youtube_show(test_client, admin_user, db_session):
app.dependency_overrides[get_db] = lambda: db_session
app.dependency_overrides[verify_jwt] = lambda: admin_user
try:
resp = await test_client.post(
"/v1/catalogue/",
json={
"media_type": "youtube_show",
"title": "Python Tutorial",
"s3_key": "media/yt-python.mp4",
"youtube_video_id": "dQw4w9WgXcQ",
"channel_name": "TechChannel",
"is_published": True,
},
headers={"Authorization": "Bearer fake"},
)
finally:
app.dependency_overrides.pop(verify_jwt, None)
assert resp.status_code == 201
body = resp.json()
assert body["media_type"] == "youtube_show"
assert body["youtube_video_id"] == "dQw4w9WgXcQ"
@pytest.mark.asyncio
async def test_list_filtered_by_media_type(test_client, admin_user, db_session):
app.dependency_overrides[get_db] = lambda: db_session
app.dependency_overrides[verify_jwt] = lambda: admin_user
try:
await test_client.post(
"/v1/catalogue/",
json={"media_type": "movie", "title": "M1", "s3_key": "m/m1.mp4", "is_published": True},
headers={"Authorization": "Bearer fake"},
)
await test_client.post(
"/v1/catalogue/",
json={
"media_type": "tv_series",
"title": "T1",
"s3_key": "m/t1.mp4",
"is_published": True,
},
headers={"Authorization": "Bearer fake"},
)
finally:
app.dependency_overrides.pop(verify_jwt, None)
resp = await test_client.get("/v1/catalogue/?media_type=movie")
assert resp.status_code == 200
body = resp.json()
assert body["total"] == 1
assert body["items"][0]["media_type"] == "movie"
@pytest.mark.asyncio
async def test_get_unpublished_returns_404(test_client, db_session):
item = MediaItem(title="Hidden", s3_key="media/hidden.mp4", is_published=False)
db_session.add(item)
await db_session.commit()
resp = await test_client.get(f"/v1/catalogue/{item.id}")
assert resp.status_code == 404