182 lines
5.4 KiB
Python
182 lines
5.4 KiB
Python
import importlib
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
BACKEND_DIR = Path(__file__).resolve().parents[3]
|
|
|
|
sys.path.insert(0, str(BACKEND_DIR))
|
|
sys.modules.pop('cli.output', None)
|
|
sys.modules.pop('cli', None)
|
|
|
|
cli_output = importlib.import_module('cli.output')
|
|
OUTPUT_RENDERER = cli_output.OutputRenderer()
|
|
|
|
_ANSI_ESCAPE_PATTERN = re.compile(r'\x1b\[[0-9;]*m')
|
|
|
|
|
|
class _FakeStream:
|
|
"""
|
|
模拟终端输出流。
|
|
|
|
:param is_tty: 是否模拟为 TTY
|
|
"""
|
|
|
|
def __init__(self, *, is_tty: bool) -> None:
|
|
self._is_tty = is_tty
|
|
|
|
def isatty(self) -> bool:
|
|
"""
|
|
返回是否为 TTY。
|
|
|
|
:return: 是否为 TTY
|
|
"""
|
|
return self._is_tty
|
|
|
|
|
|
def _strip_ansi(text: str) -> str:
|
|
"""
|
|
移除 ANSI 转义序列,便于断言文本可视结果。
|
|
|
|
:param text: 原始带样式文本
|
|
:return: 去除 ANSI 后的文本
|
|
"""
|
|
return _ANSI_ESCAPE_PATTERN.sub('', text)
|
|
|
|
|
|
def test_decorate_text_output_promotes_success_header_with_emoji_icon() -> None:
|
|
"""
|
|
校验成功结果头在 emoji 模式下会被提升为统一状态头。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('emoji')
|
|
|
|
rendered = OUTPUT_RENDERER.decorate_text_output('ok: true\nenv: dev')
|
|
|
|
assert rendered == '✅ OK SUCCESS\nenv: dev'
|
|
|
|
|
|
def test_decorate_text_output_promotes_failure_header_with_emoji_icon() -> None:
|
|
"""
|
|
校验失败结果头在 emoji 模式下会被提升为统一状态头。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('emoji')
|
|
|
|
rendered = OUTPUT_RENDERER.decorate_text_output('ok: false\nerror: boom')
|
|
|
|
assert rendered == '❌ FAIL FAILED\nerror: boom'
|
|
|
|
|
|
def test_colorize_text_output_renders_inline_status_segments_with_emoji_icon() -> None:
|
|
"""
|
|
校验文本着色后会为 message、hint、error 等字段附加状态标签。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('emoji')
|
|
OUTPUT_RENDERER.set_color_mode('always')
|
|
decorated = OUTPUT_RENDERER.decorate_text_output('ok: false\nmessage: retry later\nhint: use --yes\nerror: boom')
|
|
|
|
rendered = OUTPUT_RENDERER.colorize_text_output(decorated, _FakeStream(is_tty=True))
|
|
|
|
assert '\x1b[' in rendered
|
|
assert _strip_ansi(rendered) == (
|
|
'❌ FAIL FAILED\nmessage: 💬 INFO retry later\nhint: 💡 HINT use --yes\nerror: 🚨 ERROR boom'
|
|
)
|
|
|
|
|
|
def test_colorize_text_output_supports_warning_and_info_field_aliases() -> None:
|
|
"""
|
|
校验 `warning` 与 `info` 字段也会使用统一状态图标渲染。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('emoji')
|
|
OUTPUT_RENDERER.set_color_mode('always')
|
|
decorated = OUTPUT_RENDERER.decorate_text_output('ok: true\nwarning: rotate key soon\ninfo: cache refreshed')
|
|
|
|
rendered = OUTPUT_RENDERER.colorize_text_output(decorated, _FakeStream(is_tty=True))
|
|
|
|
assert _strip_ansi(rendered) == ('✅ OK SUCCESS\nwarning: 💡 HINT rotate key soon\ninfo: 💬 INFO cache refreshed')
|
|
|
|
|
|
def test_colorize_text_output_styles_status_segments_inside_list_items() -> None:
|
|
"""
|
|
校验列表项中的带标签状态片段也会应用统一视觉规则。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('emoji')
|
|
OUTPUT_RENDERER.set_color_mode('always')
|
|
rendered = OUTPUT_RENDERER.colorize_text_output(
|
|
'results:\n - users: true | exported\n - roles: false | error: lock timeout',
|
|
_FakeStream(is_tty=True),
|
|
)
|
|
|
|
assert _strip_ansi(rendered) == (
|
|
'results:\n - users: ✅ OK | exported\n - roles: ❌ FAIL | 🚨 ERROR lock timeout'
|
|
)
|
|
|
|
|
|
def test_colorize_text_output_supports_ascii_icon_mode() -> None:
|
|
"""
|
|
校验 ascii 图标模式会渲染为纯 ASCII 状态标签。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('ascii')
|
|
OUTPUT_RENDERER.set_color_mode('always')
|
|
decorated = OUTPUT_RENDERER.decorate_text_output('ok: true\nhint: use --yes')
|
|
|
|
rendered = OUTPUT_RENDERER.colorize_text_output(decorated, _FakeStream(is_tty=True))
|
|
|
|
assert _strip_ansi(rendered) == '[OK] SUCCESS\nhint: [HINT] use --yes'
|
|
|
|
|
|
def test_colorize_text_output_skips_ansi_when_color_mode_is_never() -> None:
|
|
"""
|
|
校验关闭颜色后不会输出 ANSI 转义序列。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('emoji')
|
|
OUTPUT_RENDERER.set_color_mode('never')
|
|
decorated = OUTPUT_RENDERER.decorate_text_output('ok: false\nhint: use --yes')
|
|
|
|
rendered = OUTPUT_RENDERER.colorize_text_output(decorated, _FakeStream(is_tty=True))
|
|
|
|
assert rendered == '❌ FAIL FAILED\nhint: use --yes'
|
|
assert '\x1b[' not in rendered
|
|
|
|
|
|
def test_render_error_text_uses_status_token_in_emoji_mode() -> None:
|
|
"""
|
|
校验标准错误输出在 emoji 模式下使用统一错误图标。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('emoji')
|
|
OUTPUT_RENDERER.set_color_mode('never')
|
|
|
|
rendered = OUTPUT_RENDERER.render_error_text('boom', 23, _FakeStream(is_tty=True))
|
|
|
|
assert rendered == '🚨 ERROR[23]: boom'
|
|
|
|
|
|
def test_render_error_text_supports_ascii_mode_with_color() -> None:
|
|
"""
|
|
校验标准错误输出在 ascii 模式下仍保持统一错误前缀与颜色。
|
|
|
|
:return: None
|
|
"""
|
|
OUTPUT_RENDERER.set_icon_mode('ascii')
|
|
OUTPUT_RENDERER.set_color_mode('always')
|
|
|
|
rendered = OUTPUT_RENDERER.render_error_text('boom', 23, _FakeStream(is_tty=True))
|
|
|
|
assert '\x1b[' in rendered
|
|
assert _strip_ansi(rendered) == '[ERROR][23]: boom'
|