# [DEF:test_clean_release_tui_v2:Module] # @TIER: STANDARD # @PURPOSE: Smoke tests for thin-client TUI action dispatch and blocked transition behavior. # @LAYER: Domain # @RELATION: TESTS -> backend.src.scripts.clean_release_tui """Smoke tests for the redesigned clean release TUI.""" from __future__ import annotations import curses from unittest.mock import MagicMock, patch from backend.src.models.clean_release import CheckFinalStatus from backend.src.scripts.clean_release_tui import CleanReleaseTUI, main def _build_mock_stdscr() -> MagicMock: stdscr = MagicMock() stdscr.getmaxyx.return_value = (40, 120) stdscr.getch.return_value = curses.KEY_F10 return stdscr @patch("backend.src.scripts.clean_release_tui.curses") 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() @patch("backend.src.scripts.clean_release_tui.curses") 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 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 @patch("backend.src.scripts.clean_release_tui.curses") 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 # [/DEF:test_clean_release_tui_v2:Module]