chore: 完成全量功能迭代与部署准备

- 移除冗余依赖包
- 新增账号禁用校验与用户管理能力
- 新增文档下载与管理页面文件展示
- 新增API文档页面与用户管理前端页面
- 重构时区处理与docker-compose部署配置
- 完善测试用例与项目文档
This commit is contained in:
2026-08-01 00:02:42 +08:00
parent f92eff6f65
commit 6c6f690788
19 changed files with 2000 additions and 485 deletions
+168 -2
View File
@@ -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 调业务端点 → 1005session 已清)
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 系列免登录"""