Skip to content

Commit b7cdb0c

Browse files
committed
test: покрытие фильтров и кэша задач
- добавлены unit-тесты кэш-модуля (ключи, отказоустойчивость, удаление по префиксу) - расширены API-тесты: cache hit/miss, инвалидация, фильтры, валидация диапазона дат - добавлены интеграционные тесты репозитория на фильтрацию, сортировку и пагинацию - обновлены тесты сервиса на передачу фильтров - русифицированы docstring и тестовые сообщения
1 parent 947a698 commit b7cdb0c

File tree

5 files changed

+414
-10
lines changed

5 files changed

+414
-10
lines changed

tests/api/conftest.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pytest_asyncio
1111
from fastapi import FastAPI
1212
from httpx import ASGITransport, AsyncClient
13+
from redis.exceptions import ConnectionError as RedisConnectionError
1314

1415
from app.database import get_db
1516
from app.models.user import User
@@ -21,53 +22,90 @@
2122

2223

2324
class FakeRedis:
25+
"""Тестовая реализация Redis-клиента для API-тестов."""
26+
2427
def __init__(self) -> None:
2528
self.store: dict[str, str] = {}
2629
self.eval_result = 1
2730
self.ttl_result = 60
2831
self.forced_delete_result: int | None = None
2932
self.set_calls: list[tuple[str, str, int]] = []
3033
self.delete_calls: list[str] = []
34+
self.raise_on_get = False
35+
self.raise_on_set = False
36+
self.raise_on_delete = False
3137

3238
async def eval(
3339
self, script: str, number_of_keys: int, key: str, window_seconds: int
3440
) -> int:
41+
"""Возвращает заранее заданный результат выполнения Lua-скрипта."""
3542
_ = (script, number_of_keys, key, window_seconds)
3643
return self.eval_result
3744

3845
async def ttl(self, key: str) -> int:
46+
"""Возвращает заранее заданный TTL."""
3947
_ = key
4048
return self.ttl_result
4149

4250
async def set(self, key: str, value: str, ex: int) -> bool:
51+
"""Сохраняет значение по ключу с TTL."""
52+
if self.raise_on_set:
53+
raise RedisConnectionError("Ошибка записи в тестовый Redis")
4354
self.store[key] = value
4455
self.set_calls.append((key, value, ex))
4556
return True
4657

47-
async def delete(self, key: str) -> int:
48-
self.delete_calls.append(key)
58+
async def get(self, key: str) -> str | None:
59+
"""Возвращает значение по ключу."""
60+
if self.raise_on_get:
61+
raise RedisConnectionError("Ошибка чтения из тестового Redis")
62+
return self.store.get(key)
63+
64+
async def scan_iter(self, match: str):
65+
"""Итерирует ключи по паттерну, как Redis SCAN."""
66+
if match.endswith("*"):
67+
prefix = match[:-1]
68+
for key in list(self.store.keys()):
69+
if key.startswith(prefix):
70+
yield key
71+
return
72+
if match in self.store:
73+
yield match
74+
75+
async def delete(self, *keys: str) -> int:
76+
"""Удаляет один или несколько ключей и возвращает число удалённых."""
77+
if self.raise_on_delete:
78+
raise RedisConnectionError("Ошибка удаления из тестового Redis")
79+
self.delete_calls.extend(keys)
4980
if self.forced_delete_result is not None:
5081
if self.forced_delete_result == 1:
51-
self.store.pop(key, None)
82+
for key in keys:
83+
self.store.pop(key, None)
5284
return self.forced_delete_result
5385

54-
existed = key in self.store
55-
self.store.pop(key, None)
56-
return 1 if existed else 0
86+
removed = 0
87+
for key in keys:
88+
existed = key in self.store
89+
self.store.pop(key, None)
90+
removed += 1 if existed else 0
91+
return removed
5792

5893

5994
@pytest.fixture
6095
def fake_session() -> object:
96+
"""Возвращает объект-заглушку сессии для API-тестов."""
6197
return object()
6298

6399

64100
@pytest.fixture
65101
def fake_redis() -> FakeRedis:
102+
"""Создаёт тестовый Redis-клиент."""
66103
return FakeRedis()
67104

68105

69106
@pytest.fixture
70107
def current_user() -> User:
108+
"""Возвращает тестового текущего пользователя."""
71109
return cast(
72110
User,
73111
SimpleNamespace(
@@ -82,6 +120,7 @@ def current_user() -> User:
82120

83121
@pytest.fixture
84122
def api_app(fake_session: object, fake_redis: FakeRedis, current_user: User) -> FastAPI:
123+
"""Собирает тестовое FastAPI-приложение с override зависимостей."""
85124
app = FastAPI()
86125
app.include_router(auth_router, prefix="/api/v1")
87126
app.include_router(tasks_router, prefix="/api/v1")
@@ -104,6 +143,7 @@ async def override_current_user() -> User:
104143

105144
@pytest_asyncio.fixture
106145
async def client(api_app: FastAPI) -> AsyncIterator[AsyncClient]:
146+
"""Предоставляет асинхронный HTTP-клиент для API-тестов."""
107147
transport = ASGITransport(app=api_app)
108148
async with AsyncClient(transport=transport, base_url="http://testserver") as ac:
109149
yield ac

0 commit comments

Comments
 (0)