API: поиск игроков по нику для автокомплита (GET /api/users/search)

До 10 подсказок по подстроке ника среди активных игроков (role=player).
Регистронезависимо и для кириллицы: SQLite LIKE/lower сворачивают регистр
только у ASCII, поэтому фильтрация/ранжирование в Python (casefold):
префиксные совпадения раньше подстрочных, внутри — по алфавиту.
Пустой q — пустой список. Тесты + регенерированный schema.d.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 02:22:27 +03:00
co-authored by Claude Fable 5
parent fc1b1fd912
commit e5b7420bae
5 changed files with 178 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
"""Поиск игроков для автокомплита по нику: GET /api/users/search."""
from __future__ import annotations
from fastapi.testclient import TestClient
from sqlmodel import Session
from app.models import User
from tests.conftest import login
def _add_user(engine, nickname: str, *, active: bool = True, role: str = "player") -> None:
"""Готовим данные напрямую в БД: нужны выключенные аккаунты и админы."""
with Session(engine) as s:
s.add(
User(
nickname=nickname,
role=role,
auth_provider="stub" if role == "player" else "local",
is_active=active,
# CHECK ck_users_admin_has_password: админ обязан иметь пароль.
password_hash="x" if role == "admin" else None,
)
)
s.commit()
def test_search_prefix_first_cyrillic_case_insensitive(client: TestClient, engine):
login(client, "Искатель")
for nick in ("Барсик", "барс", "Морбарс", "Zed"):
_add_user(engine, nick)
r = client.get("/api/users/search", params={"q": "БАР"})
assert r.status_code == 200, r.text
got = r.json()
# Префиксные раньше подстрочных, внутри — по алфавиту; регистр кириллицы не важен.
assert [u["nickname"] for u in got] == ["барс", "Барсик", "Морбарс"]
assert {"user_id", "nickname", "avatar_url"} <= set(got[0].keys())
def test_search_empty_query_returns_empty_list(client: TestClient):
login(client, "Искатель")
r = client.get("/api/users/search", params={"q": " "})
assert r.status_code == 200, r.text
assert r.json() == []
def test_search_skips_inactive_and_admins(client: TestClient, engine):
login(client, "Искатель")
_add_user(engine, "Спящий барсук", active=False)
_add_user(engine, "адм барс", role="admin")
r = client.get("/api/users/search", params={"q": "барс"})
assert r.status_code == 200, r.text
assert r.json() == []
def test_search_respects_limit(client: TestClient, engine):
login(client, "Искатель")
for i in range(5):
_add_user(engine, f"Игрок {i}")
assert len(client.get("/api/users/search", params={"q": "игрок"}).json()) == 3
assert (
len(client.get("/api/users/search", params={"q": "игрок", "limit": 5}).json()) == 5
)