refactor(backend): update API routes and service layer

- Update agent router/service/repository with new endpoints
- Update auth routes with phone-based authentication
- Update users service with new phone lookup
- Update schedule_items with new schemas
- Update message schemas with visibility support
- Update settings with new automation scheduler config
- Update CLI with new commands
- Update tests to match new API contracts
This commit is contained in:
qzl
2026-03-19 18:42:59 +08:00
parent 641d847008
commit f0af44d840
36 changed files with 1083 additions and 1853 deletions
@@ -6,7 +6,7 @@ from uuid import uuid4
import pytest
from core.auth.models import CurrentUser
from v1.users.schemas import UserUpdateRequest
from v1.users.schemas import UserSearchRequest, UserUpdateRequest
from v1.users.service import UserService
@@ -16,6 +16,7 @@ class _FakeProfile:
username: str
avatar_url: str | None
bio: str | None
settings: dict | None = None
class _FakeRepository:
@@ -51,6 +52,37 @@ class _FakeSession:
self.rollback_called += 1
class _FakeSearchRepository:
def __init__(self, profiles: list[_FakeProfile]) -> None:
self._profiles_by_id = {profile.id: profile for profile in profiles}
async def get_by_user_ids(
self, user_ids: list[object]
) -> dict[object, _FakeProfile]:
return {
user_id: self._profiles_by_id[user_id]
for user_id in user_ids
if user_id in self._profiles_by_id
}
async def search_users(self, query: str, limit: int = 20) -> list[_FakeProfile]:
_ = limit
return [
profile
for profile in self._profiles_by_id.values()
if query.lower() in profile.username.lower()
]
class _FakeAuthLookup:
def __init__(self, mapping: dict[str, list[str]]) -> None:
self.mapping = mapping
async def search_user_ids_by_phone(self, query: str, limit: int = 20) -> list[str]:
_ = limit
return self.mapping.get(query, [])
class _FakeUserContextCache:
def __init__(self, *, should_fail: bool = False) -> None:
self.should_fail = should_fail
@@ -72,7 +104,7 @@ async def test_update_me_invalidates_user_context_cache() -> None:
session = _FakeSession()
cache = _FakeUserContextCache()
service = UserService(
repository=repo,
repository=repo, # type: ignore[arg-type]
session=session, # type: ignore[arg-type]
current_user=CurrentUser(id=user_id),
user_context_cache=cache, # type: ignore[arg-type]
@@ -94,7 +126,7 @@ async def test_update_me_succeeds_when_cache_invalidation_fails() -> None:
session = _FakeSession()
cache = _FakeUserContextCache(should_fail=True)
service = UserService(
repository=repo,
repository=repo, # type: ignore[arg-type]
session=session, # type: ignore[arg-type]
current_user=CurrentUser(id=user_id),
user_context_cache=cache, # type: ignore[arg-type]
@@ -105,3 +137,59 @@ async def test_update_me_succeeds_when_cache_invalidation_fails() -> None:
assert result.username == "new-name"
assert session.commit_called == 1
assert cache.invalidated_user_ids == [user_id]
@pytest.mark.asyncio
async def test_search_users_supports_phone_without_country_code() -> None:
user_id = uuid4()
repo = _FakeSearchRepository(
[
_FakeProfile(
id=user_id,
username="alice",
avatar_url=None,
bio=None,
)
]
)
session = _FakeSession()
auth_lookup = _FakeAuthLookup({"13812345678": [str(user_id)]})
service = UserService(
repository=repo, # type: ignore[arg-type]
session=session, # type: ignore[arg-type]
current_user=CurrentUser(id=user_id),
auth_gateway=auth_lookup, # type: ignore[arg-type]
)
results = await service.search_users(UserSearchRequest(query="13812345678"))
assert len(results) == 1
assert results[0].id == str(user_id)
@pytest.mark.asyncio
async def test_search_users_preserves_numeric_username_lookup() -> None:
user_id = uuid4()
repo = _FakeSearchRepository(
[
_FakeProfile(
id=user_id,
username="20260319",
avatar_url=None,
bio=None,
)
]
)
session = _FakeSession()
auth_lookup = _FakeAuthLookup({})
service = UserService(
repository=repo, # type: ignore[arg-type]
session=session, # type: ignore[arg-type]
current_user=CurrentUser(id=user_id),
auth_gateway=auth_lookup, # type: ignore[arg-type]
)
results = await service.search_users(UserSearchRequest(query="20260319"))
assert len(results) == 1
assert results[0].username == "20260319"