first commit
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled

This commit is contained in:
2026-07-23 02:38:05 +03:00
commit 8757bec8ac
335 changed files with 61527 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
"""Тесты доступности уровней AI (`services.ai_levels.detect_ai_levels`).
Детект без GPU → `max` недоступен с причиной; `medium` доступен или
недоступен по RAM/моделям; ровно три уровня в ответе.
Реальное железо/файлы моделей недетерминированы на машине разработчика —
`HW_*` подставляются через `monkeypatch.setenv` + `get_settings.cache_clear()`
(см. `test_admin_api.py::test_upload_user_avatar_by_admin` за образцом),
факт «модель скачана» — через monkeypatch `services.ai_levels._model_downloaded`.
"""
from collections.abc import Iterator
import pytest
from core.config import Settings, get_settings
from core.plugins.config import ChatConfig, InstanceConfig, SummarizerConfig, TranscriberConfig
from services import ai_levels
from services.ai_levels import detect_ai_levels
def _cfg() -> InstanceConfig:
return InstanceConfig(
transcriber=TranscriberConfig(), summarizer=SummarizerConfig(), chat=ChatConfig()
)
@pytest.fixture(autouse=True)
def _clear_hw_env(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
"""Гарантировать «чистое» железо (без HW_*) на старте каждого теста и сброс кэша
`get_settings` после — тесты этого модуля не должны зависеть друг от друга
или от `.env` окружения разработчика."""
for var in ("HW_CPUS", "HW_RAM_MB", "HW_GPU_NAME", "HW_VRAM_MB"):
monkeypatch.delenv(var, raising=False)
get_settings.cache_clear()
try:
yield
finally:
get_settings.cache_clear()
@pytest.fixture
def _all_models_downloaded(monkeypatch: pytest.MonkeyPatch) -> None:
"""Считать все модели всех уровней скачанными (изолирует тест от реального диска)."""
monkeypatch.setattr(ai_levels, "_model_downloaded", lambda _path: True)
@pytest.fixture
def _no_models_downloaded(monkeypatch: pytest.MonkeyPatch) -> None:
"""Считать, что ни одна модель не скачана."""
monkeypatch.setattr(ai_levels, "_model_downloaded", lambda _path: False)
def test_settings_treats_empty_hw_env_strings_as_none(monkeypatch: pytest.MonkeyPatch) -> None:
"""`docker-compose`/`env_file` подставляют `KEY=` из `.env` как пустую строку, не как
отсутствие переменной: `install.sh` пишет `HW_VRAM_MB=` пустым на любой машине без
NVIDIA GPU (пресеты 14), `.env.example` — все четыре `HW_*` пустыми по умолчанию.
Без нормализации пустой строки в `None` `Settings()` падает `ValidationError` уже на
импорте (`main.py`, `workers/celery_app.py`) — регрессионный тест."""
monkeypatch.setenv("HW_CPUS", "")
monkeypatch.setenv("HW_RAM_MB", "")
monkeypatch.setenv("HW_GPU_NAME", "")
monkeypatch.setenv("HW_VRAM_MB", "")
settings = Settings()
assert settings.hw_cpus is None
assert settings.hw_ram_mb is None
assert settings.hw_gpu_name is None
assert settings.hw_vram_mb is None
def test_detect_ai_levels_returns_exactly_three_levels() -> None:
statuses = detect_ai_levels(_cfg())
assert {status.level for status in statuses} == {"min", "medium", "max"}
def test_min_available_with_enough_ram_and_models(
monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
monkeypatch.setenv("HW_RAM_MB", "16384")
get_settings.cache_clear()
statuses = {status.level: status for status in detect_ai_levels(_cfg())}
assert statuses["min"].available is True
assert statuses["min"].reason is None
def test_min_unavailable_when_model_not_downloaded(_no_models_downloaded: None) -> None:
statuses = {status.level: status for status in detect_ai_levels(_cfg())}
assert statuses["min"].available is False
reason = statuses["min"].reason
assert reason is not None
assert "не скачана" in reason
assert "пресетом 3" in reason
def test_medium_unavailable_with_insufficient_ram(
monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
monkeypatch.setenv("HW_RAM_MB", "16384") # хватает на min (16 ГБ), мало для medium (32 ГБ)
get_settings.cache_clear()
statuses = {status.level: status for status in detect_ai_levels(_cfg())}
assert statuses["medium"].available is False
reason = statuses["medium"].reason
assert reason is not None
assert "недостаточно RAM: нужно 32 ГБ" in reason
def test_medium_available_with_enough_ram_and_models_without_gpu(
monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
"""`medium` не требует GPU (ADR-004: GPU опционален) — доступен на чистом CPU-железе."""
monkeypatch.setenv("HW_RAM_MB", "32768")
get_settings.cache_clear()
statuses = {status.level: status for status in detect_ai_levels(_cfg())}
assert statuses["medium"].available is True
assert statuses["medium"].reason is None
def test_medium_unavailable_when_model_not_downloaded(
monkeypatch: pytest.MonkeyPatch, _no_models_downloaded: None
) -> None:
monkeypatch.setenv("HW_RAM_MB", "32768")
get_settings.cache_clear()
statuses = {status.level: status for status in detect_ai_levels(_cfg())}
assert statuses["medium"].available is False
reason = statuses["medium"].reason
assert reason is not None
assert "не скачана" in reason
assert "пресетом 4" in reason
def test_max_unavailable_without_gpu(
monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
monkeypatch.setenv("HW_RAM_MB", "65536")
get_settings.cache_clear()
statuses = {status.level: status for status in detect_ai_levels(_cfg())}
assert statuses["max"].available is False
reason = statuses["max"].reason
assert reason is not None
assert "требуется GPU NVIDIA ≥16 ГБ VRAM, не обнаружен" in reason
def test_max_unavailable_with_insufficient_vram(
monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
monkeypatch.setenv("HW_RAM_MB", "65536")
monkeypatch.setenv("HW_GPU_NAME", "NVIDIA RTX 3060")
monkeypatch.setenv("HW_VRAM_MB", "12288")
get_settings.cache_clear()
statuses = {status.level: status for status in detect_ai_levels(_cfg())}
assert statuses["max"].available is False
reason = statuses["max"].reason
assert reason is not None
assert "обнаружено только 12 ГБ" in reason
def test_max_available_with_gpu_ram_and_models(
monkeypatch: pytest.MonkeyPatch, _all_models_downloaded: None
) -> None:
monkeypatch.setenv("HW_RAM_MB", "65536")
monkeypatch.setenv("HW_GPU_NAME", "NVIDIA RTX 4090")
monkeypatch.setenv("HW_VRAM_MB", "24576")
get_settings.cache_clear()
statuses = {status.level: status for status in detect_ai_levels(_cfg())}
assert statuses["max"].available is True
assert statuses["max"].reason is None