Windows-консоль не разбирала ANSI и печатала их как мусор, поэтому цвет был выключен целиком. Теперь разбор включается через WinAPI, а при неудаче цвет молча отключается. Длинные сообщения переносятся по словам с отступом под текст. Вывод Caddy уведён в файл - он забивал консоль сертификатами. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
76 lines
3.3 KiB
Python
76 lines
3.3 KiB
Python
"""Проверки читаемости консольного вывода."""
|
|
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
|