"""Проверки читаемости консольного вывода.""" import logging import sys from unittest.mock import patch from app.console import ConsoleFormatter, enable_ansi, setup_logging def record(level: int, message: str) -> logging.LogRecord: return logging.LogRecord("talkscore-asr", level, __file__, 1, message, None, None) class TestWrapping: def test_short_message_stays_on_one_line(self): out = ConsoleFormatter(colored=False).format(record(logging.INFO, "готово")) assert out.count("\n") == 0 assert out.endswith("готово") def test_long_message_wraps(self): out = ConsoleFormatter(colored=False, width=lambda: 80).format( record(logging.INFO, "слово " * 60)) assert out.count("\n") >= 1 assert all(len(line) <= 80 for line in out.splitlines()) def test_continuation_aligned_under_message(self): """Перенос идёт под текст, а не под начало строки - так видно границу сообщений.""" out = ConsoleFormatter(colored=False, width=lambda: 70).format( record(logging.WARNING, "слово " * 40)) first, second = out.splitlines()[:2] head = len(first) - len(first.lstrip()) + first.index("слово") assert second.startswith(" " * head) assert second.lstrip()[0] != " " def test_existing_newlines_are_kept(self): out = ConsoleFormatter(colored=False).format( record(logging.INFO, "первая\nвторая")) assert "первая" in out.splitlines()[0] assert "вторая" in out.splitlines()[1] class TestColour: def test_no_escape_codes_when_colour_off(self): """Главное требование: без цвета в выводе не должно быть ANSI-мусора.""" for level in (logging.INFO, logging.WARNING, logging.ERROR): out = ConsoleFormatter(colored=False).format(record(level, "текст")) assert "\033" not in out def test_warning_and_error_are_painted(self): for level in (logging.WARNING, logging.ERROR): out = ConsoleFormatter(colored=True).format(record(level, "текст")) assert "\033[" in out def test_info_level_is_not_painted(self): """Обычные сообщения остаются неокрашенными, иначе цвет перестаёт что-то значить.""" out = ConsoleFormatter(colored=True).format(record(logging.INFO, "текст")) assert "\033[3" not in out # ни жёлтого, ни красного class TestAnsiDetection: def test_disabled_when_output_is_redirected(self): """Перенаправленный в файл вывод красить нельзя - в файл попадут коды.""" with patch.object(sys.stdout, "isatty", return_value=False): assert enable_ansi() is False class TestSetup: def test_setup_replaces_handlers(self): root = logging.getLogger() before = root.handlers[:] try: setup_logging(logging.INFO) assert len(root.handlers) == 1 assert isinstance(root.handlers[0].formatter, ConsoleFormatter) finally: root.handlers[:] = before class TestPortCheck: """Занятый порт должен диагностироваться до запуска, а не строкой Errno 10048.""" def test_free_port_is_not_busy(self): import socket as sk from app.main import _port_busy with sk.socket() as probe: probe.bind(("127.0.0.1", 0)) free = probe.getsockname()[1] # Сокет закрыт, порт снова свободен. assert _port_busy("127.0.0.1", free) is False def test_listening_port_is_busy(self): import socket as sk from app.main import _port_busy with sk.socket() as server: server.bind(("127.0.0.1", 0)) server.listen(1) port = server.getsockname()[1] assert _port_busy("127.0.0.1", port) is True def test_wildcard_host_is_probed_via_loopback(self): """При host=0.0.0.0 подключаться к 0.0.0.0 нельзя, проверяем петлю.""" import socket as sk from app.main import _port_busy with sk.socket() as server: server.bind(("127.0.0.1", 0)) server.listen(1) port = server.getsockname()[1] assert _port_busy("0.0.0.0", port) is True