Files
ss-tools/backend/tests/scripts/test_clean_release_tui_v2.py
2026-05-26 09:30:41 +03:00

125 lines
4.5 KiB
Python

# #region test_clean_release_tui_v2 [C:2] [TYPE Module]
# @RELATION BINDS_TO -> SrcRoot
# @PURPOSE: Smoke tests for thin-client TUI action dispatch and blocked transition behavior.
# @LAYER Domain
"""Smoke tests for the redesigned clean release TUI."""
from __future__ import annotations
import curses
from unittest.mock import MagicMock, patch
from src.models.clean_release import CheckFinalStatus
from src.scripts.clean_release_tui import CleanReleaseTUI, main
# #region _build_mock_stdscr [C:2] [TYPE Function]
# @RELATION BINDS_TO -> test_clean_release_tui_v2
# @PURPOSE: Build deterministic curses screen mock with default terminal geometry and exit key.
def _build_mock_stdscr() -> MagicMock:
stdscr = MagicMock()
stdscr.getmaxyx.return_value = (40, 120)
stdscr.getch.return_value = curses.KEY_F10
return stdscr
# #endregion _build_mock_stdscr
@patch("src.scripts.clean_release_tui.curses")
# #region test_tui_f5_dispatches_run_action [C:2] [TYPE Function]
# @RELATION BINDS_TO -> test_clean_release_tui_v2
# @PURPOSE: Verify F5 key dispatch invokes run_checks exactly once before graceful exit.
def test_tui_f5_dispatches_run_action(mock_curses_module: MagicMock) -> None:
"""F5 should dispatch run action from TUI loop."""
mock_curses_module.KEY_F10 = curses.KEY_F10
mock_curses_module.KEY_F5 = curses.KEY_F5
mock_curses_module.color_pair.side_effect = lambda value: value
mock_curses_module.A_BOLD = 0
stdscr = _build_mock_stdscr()
app = CleanReleaseTUI(stdscr)
stdscr.getch.side_effect = [curses.KEY_F5, curses.KEY_F10]
with patch.object(app, "run_checks", autospec=True) as run_checks_mock:
app.loop()
run_checks_mock.assert_called_once_with()
# #endregion test_tui_f5_dispatches_run_action
@patch("src.scripts.clean_release_tui.curses")
# #region test_tui_f5_run_smoke_reports_blocked_state [C:2] [TYPE Function]
# @RELATION BINDS_TO -> test_clean_release_tui_v2
# @PURPOSE: Verify blocked compliance state is surfaced after F5-triggered run action.
def test_tui_f5_run_smoke_reports_blocked_state(mock_curses_module: MagicMock) -> None:
"""F5 smoke test should expose blocked outcome state after run action."""
mock_curses_module.KEY_F10 = curses.KEY_F10
mock_curses_module.KEY_F5 = curses.KEY_F5
mock_curses_module.color_pair.side_effect = lambda value: value
mock_curses_module.A_BOLD = 0
stdscr = _build_mock_stdscr()
app = CleanReleaseTUI(stdscr)
stdscr.getch.side_effect = [curses.KEY_F5, curses.KEY_F10]
def _set_blocked_state() -> None:
app.status = CheckFinalStatus.BLOCKED
app.report_id = "CCR-smoke-blocked"
app.violations_list = [object()]
with patch.object(app, "run_checks", side_effect=_set_blocked_state, autospec=True):
app.loop()
assert app.status == CheckFinalStatus.BLOCKED
assert app.report_id == "CCR-smoke-blocked"
assert app.violations_list
# #endregion test_tui_f5_run_smoke_reports_blocked_state
# #region test_tui_non_tty_refuses_startup [C:2] [TYPE Function]
# @RELATION BINDS_TO -> test_clean_release_tui_v2
# @PURPOSE: Verify non-TTY execution returns exit code 2 with actionable stderr guidance.
def test_tui_non_tty_refuses_startup(capsys) -> None:
"""Non-TTY startup must refuse TUI mode and redirect operator to CLI/API flow."""
with patch("sys.stdout.isatty", return_value=False):
exit_code = main()
captured = capsys.readouterr()
assert exit_code == 2
assert "TTY is required for TUI mode" in captured.err
assert "Use CLI/API workflow instead" in captured.err
# #endregion test_tui_non_tty_refuses_startup
@patch("src.scripts.clean_release_tui.curses")
# #region test_tui_f8_blocked_without_facade_binding [C:2] [TYPE Function]
# @RELATION BINDS_TO -> test_clean_release_tui_v2
# @PURPOSE: Verify F8 path reports disabled action instead of mutating hidden facade state.
def test_tui_f8_blocked_without_facade_binding(mock_curses_module: MagicMock) -> None:
"""F8 should not perform hidden state mutation when facade action is not bound."""
mock_curses_module.KEY_F10 = curses.KEY_F10
mock_curses_module.KEY_F8 = curses.KEY_F8
mock_curses_module.color_pair.side_effect = lambda value: value
mock_curses_module.A_BOLD = 0
stdscr = _build_mock_stdscr()
app = CleanReleaseTUI(stdscr)
stdscr.getch.side_effect = [curses.KEY_F8, curses.KEY_F10]
app.loop()
assert app.last_error is not None
assert "F8 disabled" in app.last_error
# #endregion test_tui_f8_blocked_without_facade_binding
# #endregion test_clean_release_tui_v2