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

106 lines
3.5 KiB
Python

from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from httpx import ASGITransport, AsyncClient
from streamstack.streaming.app import app
@pytest.fixture
def mock_nats_client():
nc = AsyncMock()
js = AsyncMock()
kv = AsyncMock()
nc.jetstream.return_value = js
js.key_value.return_value = kv
return nc, kv
@pytest.fixture
def mock_resolve_token_valid():
with patch(
"streamstack.streaming.service.resolve_stream_token",
new=AsyncMock(return_value=("media-uuid-abc", "user-1")),
):
yield
@pytest.fixture
def mock_resolve_token_invalid():
with patch(
"streamstack.streaming.service.resolve_stream_token",
new=AsyncMock(return_value=None),
):
yield
@pytest.fixture
def mock_s3_full():
s3 = MagicMock()
s3.head_object.return_value = {
"ContentLength": 1024,
"ContentType": "video/mp4",
}
body = MagicMock()
body.read.side_effect = [b"x" * 512, b"x" * 512, b""]
s3.get_object.return_value = {"Body": body}
with patch("streamstack.streaming.service.get_s3_client", return_value=s3):
yield s3
@pytest.fixture
def mock_nats_global(mock_nats_client):
nc, _ = mock_nats_client
with patch("streamstack.streaming.router.get_nats", return_value=nc):
yield nc
@pytest.mark.asyncio
async def test_health_endpoint():
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
resp = await ac.get("/v1/health")
assert resp.status_code == 200
assert resp.json() == {"status": "ok"}
@pytest.mark.asyncio
async def test_stream_not_found(mock_resolve_token_invalid, mock_nats_global):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
resp = await ac.get("/v1/stream/invalid-token")
assert resp.status_code == 404
@pytest.mark.asyncio
async def test_stream_full_response(mock_resolve_token_valid, mock_nats_global, mock_s3_full):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
resp = await ac.get("/v1/stream/valid-token")
assert resp.status_code == 200
assert resp.headers["accept-ranges"] == "bytes"
assert resp.headers["content-length"] == "1024"
@pytest.mark.asyncio
async def test_stream_range_request(mock_resolve_token_valid, mock_nats_global):
s3 = MagicMock()
s3.head_object.return_value = {"ContentLength": 10000, "ContentType": "video/mp4"}
body = MagicMock()
body.read.side_effect = [b"y" * 500, b""]
s3.get_object.return_value = {"Body": body}
with patch("streamstack.streaming.service.get_s3_client", return_value=s3):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
resp = await ac.get("/v1/stream/valid-token", headers={"Range": "bytes=0-499"})
assert resp.status_code == 206
assert "content-range" in resp.headers
assert resp.headers["content-range"] == "bytes 0-499/10000"
assert resp.headers["content-length"] == "500"
@pytest.mark.asyncio
async def test_stream_invalid_range_header(mock_resolve_token_valid, mock_nats_global):
s3 = MagicMock()
s3.head_object.return_value = {"ContentLength": 10000, "ContentType": "video/mp4"}
with patch("streamstack.streaming.service.get_s3_client", return_value=s3):
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
resp = await ac.get("/v1/stream/valid-token", headers={"Range": "bytes=abc-def"})
assert resp.status_code == 400