chore: 完成全量功能迭代与部署准备
- 移除冗余依赖包 - 新增账号禁用校验与用户管理能力 - 新增文档下载与管理页面文件展示 - 新增API文档页面与用户管理前端页面 - 重构时区处理与docker-compose部署配置 - 完善测试用例与项目文档
This commit is contained in:
+168
-2
@@ -245,8 +245,9 @@ class TestUsersCrud:
|
||||
users = body["data"]
|
||||
assert len(users) == 1
|
||||
admin = users[0]
|
||||
assert set(admin.keys()) == {"username", "role", "must_change_password", "created_at"}
|
||||
assert set(admin.keys()) == {"username", "role", "must_change_password", "enabled", "created_at"}
|
||||
assert admin["username"] == "admin"
|
||||
assert admin["enabled"] is True
|
||||
assert "password_hash" not in admin
|
||||
assert "salt" not in admin
|
||||
|
||||
@@ -271,10 +272,11 @@ class TestUsersCrud:
|
||||
|
||||
assert body["code"] == 0
|
||||
data = body["data"]
|
||||
assert set(data.keys()) == {"username", "role", "must_change_password", "created_at"}
|
||||
assert set(data.keys()) == {"username", "role", "must_change_password", "enabled", "created_at"}
|
||||
assert data["username"] == "carol"
|
||||
assert data["role"] == "user"
|
||||
assert data["must_change_password"] is False
|
||||
assert data["enabled"] is True
|
||||
# 新用户可登录
|
||||
assert _login(client, "carol", "carol-pass-123")["code"] == 0
|
||||
|
||||
@@ -386,6 +388,170 @@ class TestUsersCrud:
|
||||
assert body["code"] == 1001
|
||||
|
||||
|
||||
class TestUpdateUser:
|
||||
"""PATCH /api/v1/auth/users/{username}:更新角色/启用状态"""
|
||||
|
||||
def test_update_role_success(self, client: TestClient, admin_headers: dict[str, str]) -> None:
|
||||
client.post(
|
||||
"/api/v1/auth/users",
|
||||
json={"username": "carol", "password": "carol-pass-123"},
|
||||
headers=admin_headers,
|
||||
)
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/carol", json={"role": "admin"}, headers=admin_headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 0
|
||||
assert body["data"]["role"] == "admin"
|
||||
assert body["data"]["enabled"] is True
|
||||
|
||||
def test_update_enabled_success(
|
||||
self, client: TestClient, auth_stores, admin_headers: dict[str, str]
|
||||
) -> None:
|
||||
user_store, _ = auth_stores
|
||||
_create_user(user_store, "dave", "dave-pass-123", role="user")
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/dave", json={"enabled": False}, headers=admin_headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 0
|
||||
assert body["data"]["enabled"] is False
|
||||
assert body["data"]["role"] == "user"
|
||||
|
||||
def test_update_both_role_and_enabled(
|
||||
self, client: TestClient, auth_stores, admin_headers: dict[str, str]
|
||||
) -> None:
|
||||
user_store, _ = auth_stores
|
||||
_create_user(user_store, "erin", "erin-pass-123", role="user")
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/erin",
|
||||
json={"role": "admin", "enabled": False},
|
||||
headers=admin_headers,
|
||||
).json()
|
||||
|
||||
assert body["code"] == 0
|
||||
assert body["data"]["role"] == "admin"
|
||||
assert body["data"]["enabled"] is False
|
||||
|
||||
def test_at_least_one_field_required(self, client: TestClient, admin_headers: dict[str, str]) -> None:
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/admin", json={}, headers=admin_headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 1001
|
||||
assert body["data"] is None
|
||||
|
||||
def test_user_not_found(self, client: TestClient, admin_headers: dict[str, str]) -> None:
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/ghost", json={"role": "user"}, headers=admin_headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 1004
|
||||
assert body["data"] is None
|
||||
|
||||
def test_invalid_role(self, client: TestClient, admin_headers: dict[str, str]) -> None:
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/admin", json={"role": "superuser"}, headers=admin_headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 1001
|
||||
|
||||
def test_last_admin_demote_forbidden(self, client: TestClient, admin_headers: dict[str, str]) -> None:
|
||||
# 唯一 admin 降级为 user → 1001
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/admin", json={"role": "user"}, headers=admin_headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 1001
|
||||
# 未生效
|
||||
assert client.get("/api/v1/auth/users", headers=admin_headers).json()["code"] == 0
|
||||
|
||||
def test_last_admin_disable_forbidden(self, client: TestClient, admin_headers: dict[str, str]) -> None:
|
||||
# 唯一 admin 禁用 → 1001
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/admin", json={"enabled": False}, headers=admin_headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 1001
|
||||
|
||||
def test_non_admin_forbidden(self, client: TestClient, auth_stores) -> None:
|
||||
user_store, session_store = auth_stores
|
||||
_create_user(user_store, "bob", "bob-pass-123", role="user")
|
||||
headers = _headers(session_store, "bob", "user")
|
||||
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/bob", json={"role": "admin"}, headers=headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 1006
|
||||
|
||||
def test_demote_when_multiple_admins_ok(
|
||||
self, client: TestClient, auth_stores, admin_headers: dict[str, str]
|
||||
) -> None:
|
||||
# 存在第二个 admin 时,可降级其中一个
|
||||
user_store, _ = auth_stores
|
||||
_create_user(user_store, "admin2", "admin2-pass-123", role="admin")
|
||||
body = client.patch(
|
||||
"/api/v1/auth/users/admin2", json={"role": "user"}, headers=admin_headers
|
||||
).json()
|
||||
|
||||
assert body["code"] == 0
|
||||
assert body["data"]["role"] == "user"
|
||||
|
||||
|
||||
class TestDisabledUser:
|
||||
"""禁用用户:登录拦截 + 旧 token 失效 + 业务端点拦截"""
|
||||
|
||||
def test_disabled_user_login_blocked(
|
||||
self, client: TestClient, auth_stores, admin_headers: dict[str, str]
|
||||
) -> None:
|
||||
user_store, _ = auth_stores
|
||||
_create_user(user_store, "bob", "bob-pass-123", role="user")
|
||||
# 禁用 bob
|
||||
client.patch(
|
||||
"/api/v1/auth/users/bob", json={"enabled": False}, headers=admin_headers
|
||||
)
|
||||
# 登录 → 1005 账号已禁用
|
||||
body = _login(client, "bob", "bob-pass-123")
|
||||
|
||||
assert body["code"] == 1005
|
||||
assert body["data"] is None
|
||||
|
||||
def test_disabled_user_old_token_invalidated(
|
||||
self, client: TestClient, auth_stores, admin_headers: dict[str, str]
|
||||
) -> None:
|
||||
user_store, _ = auth_stores
|
||||
_create_user(user_store, "bob", "bob-pass-123", role="user")
|
||||
# bob 登录拿到 token
|
||||
login_body = _login(client, "bob", "bob-pass-123")
|
||||
assert login_body["code"] == 0
|
||||
bob_headers = {"Authorization": f"Bearer {login_body['data']['token']}"}
|
||||
# 禁用 bob → session 清除
|
||||
client.patch(
|
||||
"/api/v1/auth/users/bob", json={"enabled": False}, headers=admin_headers
|
||||
)
|
||||
# 旧 token 调业务端点 → 1005(session 已清)
|
||||
body = client.get("/api/v1/auth/me", headers=bob_headers).json()
|
||||
|
||||
assert body["code"] == 1005
|
||||
|
||||
def test_disabled_user_blocked_even_with_valid_session(
|
||||
self, client: TestClient, auth_stores, admin_headers: dict[str, str]
|
||||
) -> None:
|
||||
"""禁用用户即使持有效 session(直接签发绕过登录),业务端点仍拦截 1005"""
|
||||
user_store, session_store = auth_stores
|
||||
_create_user(user_store, "bob", "bob-pass-123", role="user")
|
||||
# 禁用 bob → session 清除
|
||||
client.patch(
|
||||
"/api/v1/auth/users/bob", json={"enabled": False}, headers=admin_headers
|
||||
)
|
||||
# 直接为 bob 签发新 session 绕过登录与清理,验证 get_current_user 的 enabled 拦截
|
||||
bob_headers = _headers(session_store, "bob", "user")
|
||||
body = client.get("/api/v1/auth/me", headers=bob_headers).json()
|
||||
|
||||
assert body["code"] == 1005
|
||||
|
||||
|
||||
class TestDocumentAuth:
|
||||
"""文档端点鉴权:变更类需登录,GET 系列免登录"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user