92b062c048
- 新增 app/api/deps.py、app/core/users.py、app/core/sessions.py:会话鉴权依赖、 用户存储(PBKDF2-HMAC-SHA256 + 随机 salt,Redis/内存降级)、会话签发与校验(TTL 12h) - auth.py 新增用户管理端点(列表/创建/重置密码/删除)与 admin/user 角色权限边界, user 访问用户管理返回 1006,禁删自己与最后一个 admin - admin.html 新增用户管理面板(仅 admin 挂载)与 API 指南在线测试台 - Dockerfile 将 uv 放入 PATH;docker-compose 调整 qdrant 依赖为 service_started 并移除依赖 curl 的 healthcheck(官方镜像不含 curl) - 新增用户管理测试(users/sessions/auth_api/auth_integration),全量 461 项测试通过 Co-Authored-By: WorkBuddy <workbuddy@tencent.com>
79 lines
2.7 KiB
Python
79 lines
2.7 KiB
Python
"""旧 JWT 认证核心(app.core.auth)的单元测试
|
||
|
||
API 层(/api/v1/auth/*)已切换为会话制认证,端点契约测试见 tests/test_auth_api.py;
|
||
本文件仅保留 app.core.auth 的函数级覆盖:
|
||
- require_admin:非 admin 抛 FORBIDDEN(直接测函数)
|
||
- create_access_token + decode_token 往返一致 / 非法 token 拒绝
|
||
- hash_password + verify_password 正确 / 错误 / 非法哈希
|
||
"""
|
||
|
||
from datetime import UTC, datetime
|
||
|
||
import pytest
|
||
|
||
from app.api.response import ApiError
|
||
from app.config import settings
|
||
from app.core.auth import (
|
||
ERR_FORBIDDEN,
|
||
ERR_TOKEN_INVALID,
|
||
create_access_token,
|
||
decode_token,
|
||
hash_password,
|
||
require_admin,
|
||
verify_password,
|
||
)
|
||
from app.models.auth import AuthUser
|
||
|
||
|
||
def _now() -> datetime:
|
||
return datetime.now(UTC)
|
||
|
||
|
||
class TestRequireAdmin:
|
||
"""require_admin:admin 通过、非 admin 抛 FORBIDDEN(直接测函数,不经 API)"""
|
||
|
||
async def test_admin_passes(self):
|
||
admin = AuthUser(username="alice", role="admin", created_at=_now())
|
||
result = await require_admin(user=admin)
|
||
assert result.role == "admin"
|
||
|
||
async def test_non_admin_raises_forbidden(self):
|
||
normal = AuthUser(username="bob", role="user", created_at=_now())
|
||
with pytest.raises(ApiError) as exc:
|
||
await require_admin(user=normal)
|
||
assert exc.value.code == ERR_FORBIDDEN
|
||
|
||
|
||
class TestAuthCoreFunctions:
|
||
"""核心函数:token 往返 / 密码哈希"""
|
||
|
||
def test_create_and_decode_token_roundtrip(self):
|
||
token, expires_in = create_access_token("alice", "admin")
|
||
assert expires_in == settings.jwt_expire_minutes * 60
|
||
payload = decode_token(token)
|
||
assert payload["sub"] == "alice"
|
||
assert payload["role"] == "admin"
|
||
assert "iat" in payload and "exp" in payload
|
||
|
||
def test_decode_invalid_token_raises(self):
|
||
with pytest.raises(ApiError) as exc:
|
||
decode_token("not-a-jwt")
|
||
assert exc.value.code == ERR_TOKEN_INVALID
|
||
|
||
def test_hash_password_not_plaintext(self):
|
||
hashed = hash_password("my-secret")
|
||
assert hashed != "my-secret"
|
||
assert hashed.startswith("$2") # bcrypt 哈希前缀
|
||
|
||
def test_verify_password_correct(self):
|
||
hashed = hash_password("my-secret")
|
||
assert verify_password("my-secret", hashed) is True
|
||
|
||
def test_verify_password_wrong(self):
|
||
hashed = hash_password("my-secret")
|
||
assert verify_password("wrong-password", hashed) is False
|
||
|
||
def test_verify_password_garbage_hash_returns_false(self):
|
||
# 非法 hash 会被 bcrypt 拒绝,verify_password 捕获异常返回 False
|
||
assert verify_password("any", "not-a-valid-hash") is False
|