From ca3c78ef577f5d8435dd10db0296c3e2fd8e69e2 Mon Sep 17 00:00:00 2001 From: historia Date: Tue, 25 Aug 2026 20:10:40 -0400 Subject: feat: stop server and exit after generating --- app/tests/test_runview.py | 81 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) (limited to 'app/tests/test_runview.py') diff --git a/app/tests/test_runview.py b/app/tests/test_runview.py index 556cb52..31a8ebe 100644 --- a/app/tests/test_runview.py +++ b/app/tests/test_runview.py @@ -171,6 +171,10 @@ class RenderTests(_FakeTui, unittest.TestCase): text = self._strings(screen) self.assertIn("stopping", text) self.assertIn("(1s)", text) + # The stop wait ignores keys, so the footer must not promise that + # pressing one returns to the menu. + self.assertNotIn("press any key", text) + self.assertIn("stopping the audiocpp server...", text) def test_error_screen_shows_detail_and_log(self): view, screen = self.make_view(log_path="/tmp/audiobook.log") @@ -236,6 +240,83 @@ class RunLoopTests(_FakeTui, unittest.TestCase): view.run() mk_stop.assert_not_called() + def test_finished_run_toggle_off_waits_for_key_then_menu(self): + # Toggle off: a key on the summary screen returns to the menu; no + # prompt is asked and the server keeps running. + with patch.object(runview.tui, "confirm") as mk_confirm, \ + patch.object(runview.servers, "alive", return_value=True), \ + patch.object(runview.servers, "stop") as mk_stop: + view, screen = self.make_view(keys=[ord("x")], + autostart_spec="SPEC") + view.started_server = True + view._queue.put({"kind": "done", "ok": 1, "total": 1}) + result = view.run() + self.assertFalse(result) + mk_confirm.assert_not_called() + mk_stop.assert_not_called() + + def test_stop_and_exit_stops_server_and_quits_without_keys(self): + # Toggle on: the moment the run ends the server is stopped and the + # view reports quit — no key press and no prompt anywhere. + with patch.object(runview.servers, "alive", return_value=True), \ + patch.object(runview.servers, "stop") as mk_stop, \ + patch.object(runview.common, + "record_post_tui_notice") as mk_notice: + view, screen = self.make_view([], autostart_spec="SPEC", + stop_and_exit=True) + view.started_server = True + view._queue.put({"kind": "book", "index": 1, "total": 1, + "name": "book.txt"}) + view._queue.put({"kind": "book_done", "name": "book.txt", + "ok": True, + "files": ["book_test_michael.mp3"]}) + view._queue.put({"kind": "done", "ok": 1, "total": 1}) + result = view.run() + self.assertTrue(result) + mk_stop.assert_called_once_with("audiocpp") + text = mk_notice.call_args[0][0] + self.assertIn("Output directory:", text) + self.assertIn("[OK] book.txt: book_test_michael.mp3", text) + self.assertIn("1 of 1 book(s) generated successfully", text) + self.assertIn("Elapsed time:", text) + + def test_stop_and_exit_leaves_external_servers_alone(self): + # A server this run did not start is never stopped; the TUI still + # quits and the summary is still printed. + with patch.object(runview.servers, "stop") as mk_stop, \ + patch.object(runview.common, + "record_post_tui_notice") as mk_notice: + view, screen = self.make_view([], server_name=None, + stop_and_exit=True) + view.started_server = False + view._queue.put({"kind": "done", "ok": 1, "total": 1}) + result = view.run() + self.assertTrue(result) + mk_stop.assert_not_called() + self.assertIn("No books were converted", + mk_notice.call_args[0][0]) + + def test_stop_and_exit_failure_summary_lists_the_error(self): + # A failed ending also auto-exits; the failing book's detail line + # lands in the post-TUI summary. + with patch.object(runview.servers, "alive", return_value=True), \ + patch.object(runview.servers, "stop"), \ + patch.object(runview.common, + "record_post_tui_notice") as mk_notice: + view, screen = self.make_view([], autostart_spec="SPEC", + stop_and_exit=True) + view.started_server = True + view._queue.put({"kind": "book_failed", "name": "bad.txt", + "error": "chunk 3 failed", + "files": ["bad_x.mp3"]}) + view._queue.put({"kind": "done", "ok": 0, "total": 1}) + result = view.run() + self.assertTrue(result) + text = mk_notice.call_args[0][0] + self.assertIn("[FAIL] bad.txt: bad_x.mp3", text) + self.assertIn("chunk 3 failed", text) + self.assertIn("0 of 1 book(s) generated successfully", text) + class WorkerTests(_FakeTui, unittest.TestCase): """The worker thread's handoff into audiobook.convert.""" -- cgit v1.2.3