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