"""管理端点鉴权(require_admin)测试。 覆盖四种情形:ADMIN_TOKEN 未配置 → 503(fail-closed,避免裸奔);已配置但缺凭证 / 凭证错误 → 401;正确凭证放行。探针端点用 ``/api/v1/admin/traces``(不依赖沙箱运行时, 测试环境可正常返回,便于判定鉴权层是否放行)。 """ from collections.abc import AsyncGenerator import pytest from httpx import ASGITransport, AsyncClient from app.main import app _PROBE = "/api/v1/admin/traces" @pytest.fixture async def client() -> AsyncGenerator[AsyncClient, None]: transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as http_client: yield http_client @pytest.mark.asyncio async def test_admin_unconfigured_returns_503( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: """ADMIN_TOKEN 与 JWT_SECRET 均未配置时管理端点 fail-closed(503)。""" monkeypatch.setattr("app.api.v1.deps.settings.admin_token", "") monkeypatch.setattr("app.api.v1.deps.settings.jwt_secret", "") resp = await client.get(_PROBE) assert resp.status_code == 503 @pytest.mark.asyncio async def test_admin_missing_credential_returns_401( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: """已配置 ADMIN_TOKEN 但请求未带 Authorization 头 → 401。""" monkeypatch.setattr("app.api.v1.deps.settings.admin_token", "secret-token") monkeypatch.setattr("app.api.v1.deps.settings.jwt_secret", "") resp = await client.get(_PROBE) assert resp.status_code == 401 @pytest.mark.asyncio async def test_admin_wrong_credential_returns_401( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: """Authorization 凭证错误 → 401。""" monkeypatch.setattr("app.api.v1.deps.settings.admin_token", "secret-token") monkeypatch.setattr("app.api.v1.deps.settings.jwt_secret", "") resp = await client.get(_PROBE, headers={"Authorization": "Bearer wrong"}) assert resp.status_code == 401 @pytest.mark.asyncio async def test_admin_correct_credential_passes( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: """正确凭证放行:鉴权层不再拦截。""" monkeypatch.setattr("app.api.v1.deps.settings.admin_token", "secret-token") monkeypatch.setattr("app.api.v1.deps.settings.jwt_secret", "") resp = await client.get(_PROBE, headers={"Authorization": "Bearer secret-token"}) assert resp.status_code != 401 # ═══════════════════════════════════════════════════════════════ # JWT 模式测试 # ═══════════════════════════════════════════════════════════════ @pytest.mark.asyncio async def test_admin_jwt_admin_user_passes( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: """拥有 is_admin 标记的用户 JWT 放行 /admin/*。""" monkeypatch.setattr("app.api.v1.deps.settings.jwt_secret", "test-jwt-secret") from app.db.session import AsyncSessionLocal from app.services.auth import create_access_token, create_user async with AsyncSessionLocal() as session: user = await create_user( session, username="adminuser", email="admin@test.com", password="pass", is_admin=True ) await session.commit() token = create_access_token(subject=user.id, extra={"owner_scope": user.id}) resp = await client.get(_PROBE, headers={"Authorization": f"Bearer {token}"}) assert resp.status_code != 401 assert resp.status_code != 403 @pytest.mark.asyncio async def test_admin_jwt_non_admin_returns_403( client: AsyncClient, monkeypatch: pytest.MonkeyPatch ) -> None: """普通用户 JWT 访问 /admin/* → 403。""" monkeypatch.setattr("app.api.v1.deps.settings.jwt_secret", "test-jwt-secret") from app.db.session import AsyncSessionLocal from app.services.auth import create_access_token, create_user async with AsyncSessionLocal() as session: user = await create_user( session, username="regular", email="regular@test.com", password="pass", is_admin=False ) await session.commit() token = create_access_token(subject=user.id, extra={"owner_scope": user.id}) resp = await client.get(_PROBE, headers={"Authorization": f"Bearer {token}"}) assert resp.status_code == 403