Files
hunjing/backend/tests/test_admin_auth.py
2026-08-21 16:00:13 +08:00

111 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""管理端点鉴权(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