feat: 实现密码重置功能与用户搜索API,优化注册登录流程

- 新增忘记密码页面与重置密码确认流程(前端+后端)
- 修复注册验证码页登录跳转路由
- 新增用户搜索API(按邮箱查询)
- 简化infra脚本,统一为app.sh
- 补充密码重置与用户API测试覆盖
- 更新runtime文档与AGENTS配置
This commit is contained in:
qzl
2026-02-27 15:22:42 +08:00
parent 0d4811fee5
commit e4e995854d
37 changed files with 2101 additions and 222 deletions
+77 -45
View File
@@ -9,7 +9,7 @@ from fastapi.testclient import TestClient
from app import app
from core.auth.models import CurrentUser
from v1.users.dependencies import get_current_user, get_user_service
from v1.users.schemas import UserResponse, UserUpdateRequest
from v1.users.schemas import UserResponse, UserSearchRequest, UserUpdateRequest
from v1.users.service import UserService
@@ -18,6 +18,10 @@ class FakeUserService:
def __init__(self, user: UserResponse) -> None:
self._user = user
self._search_results: list[UserResponse] = []
def set_search_results(self, results: list[UserResponse]) -> None:
self._search_results = results
async def get_me(self) -> UserResponse:
if self._user.id is None:
@@ -45,6 +49,11 @@ class FakeUserService:
raise HTTPException(status_code=404, detail="User not found")
return self._user
async def search_users(self, request: UserSearchRequest) -> list[UserResponse]:
if request.query:
return self._search_results if self._search_results else [self._user]
return []
def _override_user_service(
service: FakeUserService,
@@ -111,50 +120,6 @@ def test_patch_me_updates_user() -> None:
app.dependency_overrides = {}
def test_get_user_by_username() -> None:
user = UserResponse(
id="00000000-0000-0000-0000-000000000001",
username="demo",
avatar_url=None,
bio=None,
)
app.dependency_overrides[get_user_service] = _override_user_service(
FakeUserService(user)
)
client = TestClient(app)
try:
response = client.get("/api/v1/users/demo")
assert response.status_code == 200
body = response.json()
assert body["username"] == "demo"
finally:
app.dependency_overrides = {}
def test_user_not_found_returns_problem_details() -> None:
user = UserResponse(
id="00000000-0000-0000-0000-000000000001",
username="demo",
avatar_url=None,
bio=None,
)
app.dependency_overrides[get_user_service] = _override_user_service(
FakeUserService(user)
)
client = TestClient(app)
try:
response = client.get("/api/v1/users/unknown")
assert response.status_code == 404
assert response.headers["content-type"].startswith("application/problem+json")
body = response.json()
assert body["title"] == "Not Found"
assert body["status"] == 404
finally:
app.dependency_overrides = {}
def test_patch_me_validation_error_returns_problem_details() -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
user = UserResponse(
@@ -178,3 +143,70 @@ def test_patch_me_validation_error_returns_problem_details() -> None:
assert body["status"] == 422
finally:
app.dependency_overrides = {}
def test_search_users_returns_list() -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
user = UserResponse(
id=str(user_id),
username="demo",
avatar_url=None,
bio=None,
)
app.dependency_overrides[get_user_service] = _override_user_service(
FakeUserService(user)
)
client = TestClient(app)
try:
response = client.post(
"/api/v1/users/search",
json={"query": "demo"},
)
assert response.status_code == 200
body = response.json()
assert isinstance(body, list)
finally:
app.dependency_overrides = {}
def test_search_users_empty_query_returns_422() -> None:
user_id = UUID("00000000-0000-0000-0000-000000000001")
user = UserResponse(
id=str(user_id),
username="demo",
avatar_url=None,
bio=None,
)
app.dependency_overrides[get_user_service] = _override_user_service(
FakeUserService(user)
)
client = TestClient(app)
try:
response = client.post(
"/api/v1/users/search",
json={"query": ""},
)
assert response.status_code == 422
finally:
app.dependency_overrides = {}
def test_get_user_by_username_returns_404() -> None:
user = UserResponse(
id="00000000-0000-0000-0000-000000000001",
username="demo",
avatar_url=None,
bio=None,
)
app.dependency_overrides[get_user_service] = _override_user_service(
FakeUserService(user)
)
client = TestClient(app)
try:
response = client.get("/api/v1/users/demo")
assert response.status_code == 404
finally:
app.dependency_overrides = {}