Files
vidconf/backend/tests/test_ai_levels.py

184 lines
7.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Тесты доступности уровней 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