first commit
This commit is contained in:
183
backend/tests/test_ai_levels.py
Normal file
183
backend/tests/test_ai_levels.py
Normal 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 (пресеты 1–4), `.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
|
||||
Reference in New Issue
Block a user