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

128 lines
3.9 KiB
Python

import subprocess
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.auth.app import app
from streamstack.auth.models import Base
from streamstack.core.db import get_db
@pytest.fixture(scope="module")
def rsa_key_pair(tmp_path_factory):
tmp = tmp_path_factory.mktemp("keys")
priv = tmp / "private.pem"
pub = tmp / "public.pem"
subprocess.run(
["openssl", "genrsa", "-out", str(priv), "2048"], check=True, capture_output=True
)
subprocess.run(
["openssl", "rsa", "-in", str(priv), "-pubout", "-out", str(pub)],
check=True,
capture_output=True,
)
return str(priv), str(pub)
@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 mock_nats():
nc = AsyncMock()
js = AsyncMock()
kv = AsyncMock()
nc.jetstream.return_value = js
js.key_value.return_value = kv
with patch("streamstack.auth.router.get_nats", return_value=nc):
yield nc
@pytest.fixture
async def test_client(db_session, mock_nats, rsa_key_pair):
priv, pub = rsa_key_pair
app.dependency_overrides[get_db] = lambda: db_session
with (
patch("streamstack.core.auth.settings") as ms,
patch("streamstack.auth.service.settings") as rs,
):
ms.jwt_private_key_path = priv
ms.jwt_public_key_path = pub
ms.jwt_algorithm = "RS256"
ms.jwt_expire_minutes = 30
ms.jwt_refresh_expire_days = 7
rs.jwt_public_key_path = pub
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_register(test_client):
resp = await test_client.post(
"/v1/auth/users/",
json={"email": "new@example.com", "password": "password123"},
)
assert resp.status_code == 201
body = resp.json()
assert body["email"] == "new@example.com"
assert "id" in body
@pytest.mark.asyncio
async def test_register_duplicate_email(test_client):
creds = {"email": "dup@example.com", "password": "pass"}
await test_client.post("/v1/auth/users/", json=creds)
resp = await test_client.post("/v1/auth/users/", json=creds)
assert resp.status_code == 409
@pytest.mark.asyncio
async def test_login_success(test_client):
await test_client.post(
"/v1/auth/users/", json={"email": "login@example.com", "password": "correct"}
)
resp = await test_client.post(
"/v1/auth/token",
data={"username": "login@example.com", "password": "correct"},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert resp.status_code == 200
body = resp.json()
assert "access_token" in body
assert "refresh_token" in body
assert body["token_type"] == "bearer"
@pytest.mark.asyncio
async def test_login_wrong_password(test_client):
await test_client.post(
"/v1/auth/users/", json={"email": "wp@example.com", "password": "correct"}
)
resp = await test_client.post(
"/v1/auth/token",
data={"username": "wp@example.com", "password": "wrong"},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert resp.status_code == 401
@pytest.mark.asyncio
async def test_login_unknown_user(test_client):
resp = await test_client.post(
"/v1/auth/token",
data={"username": "nobody@example.com", "password": "pass"},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
assert resp.status_code == 401