arm-scan-gateway/api/tests/unit/test_health.py

139 lines
5.0 KiB
Python
Raw 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.

"""Состояние для `/healthcheck` (`src.core.health`): поля `nats` и `hotfolder` (spec «Старт в режиме локальных папок»,
«Горячая папка недоступна», «Соединение с NATS без JetStream»; D-8; T8.4 — проверка не зависает, T2.3 — сбой SMB)."""
import threading
from pathlib import Path
import pytest
from src.core.health import HealthProvider
from src.gateway.hotfolder import AnswerFile, LocalHotFolder
pytestmark = pytest.mark.usefixtures("log_lines")
SUBJECTS = {"image_request": "files.img_uploaded", "result": "files.tabular_parsed"}
class Connection:
def __init__(self, connected: bool) -> None:
self.is_connected = connected
class CountingHotFolder:
"""`healthy()` считает вызовы; может зависнуть до события или бросить исключение."""
def __init__(self, result: bool = True) -> None:
self.result = result
self.calls = 0
self.release: threading.Event | None = None
self.error: BaseException | None = None
def put(self, name: str, data: bytes) -> None:
raise NotImplementedError
def poll(self, job_id: str) -> list[AnswerFile] | None:
raise NotImplementedError
def healthy(self) -> bool:
self.calls += 1
if self.release is not None:
self.release.wait(5)
if self.error is not None:
raise self.error
return self.result
class Clock:
def __init__(self) -> None:
self.now = 1000.0
def __call__(self) -> float:
return self.now
async def test_local_root_exists_is_ok(tmp_path: Path) -> None:
health = HealthProvider(nats_enabled=False, hotfolder=LocalHotFolder(tmp_path), subjects=SUBJECTS)
assert await health.snapshot() == {"nats": "disabled", "hotfolder": "ok", "subjects": SUBJECTS}
async def test_missing_root_is_down(tmp_path: Path) -> None:
health = HealthProvider(nats_enabled=False, hotfolder=LocalHotFolder(tmp_path / "missing"), subjects=SUBJECTS)
assert await health.hotfolder_status() == "down"
async def test_no_hotfolder_is_down() -> None:
assert await HealthProvider(nats_enabled=False, hotfolder=None, subjects=SUBJECTS).hotfolder_status() == "down"
@pytest.mark.parametrize(
("enabled", "connection", "expected"),
[
(False, None, "disabled"),
(False, Connection(True), "disabled"),
(True, None, "down"),
(True, Connection(False), "down"),
(True, Connection(True), "ok"),
],
)
def test_nats_status(enabled: bool, connection: Connection | None, expected: str) -> None:
health = HealthProvider(nats_enabled=enabled, hotfolder=None, subjects=SUBJECTS)
health.nats = connection
assert health.nats_status() == expected
async def test_hotfolder_result_is_cached_for_ttl() -> None:
hotfolder = CountingHotFolder(result=True)
clock = Clock()
health = HealthProvider(nats_enabled=False, hotfolder=hotfolder, cache_ttl=5, clock=clock, subjects=SUBJECTS)
assert await health.hotfolder_status() == "ok"
hotfolder.result = False
clock.now += 4.9
assert await health.hotfolder_status() == "ok"
assert hotfolder.calls == 1
clock.now += 0.2
assert await health.hotfolder_status() == "down"
assert hotfolder.calls == 2
async def test_hanging_check_is_down_within_timeout_and_not_repeated() -> None:
hotfolder = CountingHotFolder()
hotfolder.release = threading.Event()
clock = Clock()
health = HealthProvider(
nats_enabled=False, hotfolder=hotfolder, check_timeout=0.05, cache_ttl=5, clock=clock, subjects=SUBJECTS
)
try:
assert await health.hotfolder_status() == "down"
clock.now += 10 # кэш истёк, но прежняя проверка ещё висит — новый поток не запускается
assert await health.hotfolder_status() == "down"
assert hotfolder.calls == 1
finally:
hotfolder.release.set()
async def test_check_raising_is_down() -> None:
hotfolder = CountingHotFolder()
hotfolder.error = OSError("smb")
health = HealthProvider(nats_enabled=False, hotfolder=hotfolder, subjects=SUBJECTS)
assert await health.hotfolder_status() == "down"
async def test_snapshot_with_connected_nats(tmp_path: Path) -> None:
health = HealthProvider(nats_enabled=True, hotfolder=LocalHotFolder(tmp_path), subjects=SUBJECTS)
health.nats = Connection(True)
assert await health.snapshot() == {"nats": "ok", "hotfolder": "ok", "subjects": SUBJECTS}
@pytest.mark.parametrize(
("enabled", "connection", "expected"),
[(True, Connection(True), "ok"), (True, Connection(False), "down"), (False, None, "disabled")],
)
async def test_snapshot_contains_subjects(enabled: bool, connection: Connection | None, expected: str) -> None:
health = HealthProvider(nats_enabled=enabled, hotfolder=None, subjects=SUBJECTS)
health.nats = connection
snapshot = await health.snapshot()
assert snapshot["nats"] == expected
assert snapshot["subjects"] == SUBJECTS