aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_runview.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-09-01 16:42:15 -0400
committerhistoria <historiavg@proton.me>2026-09-01 16:42:15 -0400
commit391f50da7a085bec75155c0eb9b47910266058cc (patch)
treed443ee758c6f6b74aca9d766ef6ed0e7cc7fb4a3 /app/tests/test_runview.py
parent6cfcd564c0684c52618235e6366f4a81c02b9a5b (diff)
downloadtts-audiobook-generator-391f50da7a085bec75155c0eb9b47910266058cc.tar.gz
fix: server status for already-running server
Diffstat (limited to 'app/tests/test_runview.py')
-rw-r--r--app/tests/test_runview.py100
1 files changed, 97 insertions, 3 deletions
diff --git a/app/tests/test_runview.py b/app/tests/test_runview.py
index 6add83f..0d99a6a 100644
--- a/app/tests/test_runview.py
+++ b/app/tests/test_runview.py
@@ -90,6 +90,25 @@ class StateTransitionTests(_FakeTui, unittest.TestCase):
"url": "http://x"})
self.assertEqual(view.server, "ready")
+ def test_running_event_escapes_starting_without_a_boot(self):
+ # Regression for the stuck "Status: starting" run view: when this
+ # run did not boot the server (already running locally, or remote)
+ # the worker's probe reports "running" — and the book events that
+ # follow must still move the panel to "processing".
+ view, _ = self.make_view()
+ view.handle_event({"kind": "running", "name": "audiocpp",
+ "url": "http://x"})
+ self.assertEqual(view.server, "ready")
+ self.assertFalse(view.started_server)
+ view.handle_event({"kind": "book", "index": 1, "total": 1,
+ "name": "b"})
+ self.assertEqual(view.phase, "convert")
+ self.assertEqual(view.server, "processing")
+ view.handle_event({"kind": "book_done", "name": "b", "ok": True})
+ view.handle_event({"kind": "done", "ok": 1, "total": 1})
+ self.assertEqual(view.phase, "done")
+ self.assertNotEqual(view.server, "starting")
+
def test_chunk_progress_updates(self):
view, _ = self.make_view()
view.handle_event({"kind": "book", "index": 1, "total": 2,
@@ -225,6 +244,16 @@ class StateTransitionTests(_FakeTui, unittest.TestCase):
view.handle_event({"kind": "server_down"})
self.assertEqual(view.server, "down")
+ def test_server_down_during_boot_marks_not_responding(self):
+ # The worker's boot-time probe of a server this run did not start:
+ # a target that never answers is reported "down" (not the eternal
+ # "starting"), and never offered for a stop (not started here).
+ view, _ = self.make_view()
+ view.handle_event({"kind": "server_down"})
+ self.assertEqual(view.server, "down")
+ self.assertEqual(view.phase, "boot")
+ self.assertFalse(view.started_server)
+
def test_server_stopped_event_marks_server_stopped(self):
view, _ = self.make_view()
view.server = "stopping"
@@ -308,7 +337,9 @@ class LogAppenderTests(_FakeTui, unittest.TestCase):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
log_path = os.path.join(tmp.name, "audiobook_20260828.log")
- view, _ = self.make_view(log_path=log_path)
+ # server_url=None: no boot and no probe, so the run log test sees
+ # only the worker's own events.
+ view, _ = self.make_view(log_path=log_path, server_url=None)
fake = types.ModuleType("audiobook")
@@ -348,6 +379,24 @@ class RenderTests(_FakeTui, unittest.TestCase):
self.assertIn("starting", text)
self.assertIn("Esc or q: cancel", text)
+ def test_ready_without_autostart_shows_the_not_started_tag(self):
+ # A run that talks to a server it did not start (already running
+ # locally, or remote) tags the ready state so the panel does not
+ # imply this run booted it.
+ view, screen = self.make_view()
+ view.handle_event({"kind": "running", "name": "audiocpp",
+ "url": "http://x"})
+ view.render()
+ self.assertIn("ready (not started by this run)", self._strings(screen))
+
+ def test_not_responding_message_shown_while_down(self):
+ view, screen = self.make_view()
+ view.handle_event({"kind": "server_down"})
+ view.render()
+ text = self._strings(screen)
+ self.assertIn("not responding", text)
+ self.assertIn("the server is not responding", text)
+
def test_summary_screen_after_done(self):
view, screen = self.make_view()
view.handle_event({"kind": "book", "index": 1, "total": 1,
@@ -620,7 +669,9 @@ class WorkerTests(_FakeTui, unittest.TestCase):
kwargs={"voice": "alloy", "book_files": ["b.txt"],
"planned": ["b.txt"]},
book_files=["b.txt"], planned=["b.txt"])
- with patch("audiobook.convert") as mk_convert:
+ with patch("audiobook.convert") as mk_convert, \
+ patch.object(runview.common, "server_running",
+ return_value=True):
view._worker_main()
_, kw = mk_convert.call_args
self.assertEqual(kw["book_files"], ["b.txt"])
@@ -635,7 +686,9 @@ class WorkerTests(_FakeTui, unittest.TestCase):
log_path = os.path.join(tmp, "audiobook_20260825.log")
view = self.make_view(log_path=log_path)
with patch("audiobook.convert",
- side_effect=TypeError("boom")):
+ side_effect=TypeError("boom")), \
+ patch.object(runview.common, "server_running",
+ return_value=False):
view._worker_main()
with open(log_path, encoding="utf-8") as handle:
text = handle.read()
@@ -681,6 +734,47 @@ class WorkerTests(_FakeTui, unittest.TestCase):
view._worker_main()
mk_stop.assert_not_called()
+ def test_worker_probes_the_server_it_does_not_boot(self):
+ # No autostart (the server is already running locally, or remote):
+ # the worker still reports the target as "running" so the status
+ # leaves "starting", then converts against it.
+ with patch("audiobook.convert") as mk_convert, \
+ patch.object(runview.common, "server_running",
+ return_value=True) as mk_running:
+ view = self.make_view()
+ view._worker_main()
+ mk_running.assert_called_once_with("http://127.0.0.1:8080")
+ mk_convert.assert_called_once()
+ events = self._drain(view)
+ self.assertEqual(events[0],
+ {"kind": "running", "name": "audiocpp",
+ "url": "http://127.0.0.1:8080"})
+ self.assertEqual(events[-1]["kind"], "worker_exit")
+
+ def test_worker_reports_down_when_the_probe_fails(self):
+ # A target that never answers (dead remote, server stopped between
+ # the form and the run) is reported "down"; the conversion still
+ # runs and fails through the normal chunk/error path.
+ with patch("audiobook.convert") as mk_convert, \
+ patch.object(runview.common, "server_running",
+ return_value=False):
+ view = self.make_view()
+ view._worker_main()
+ mk_convert.assert_called_once()
+ self.assertEqual(self._drain(view)[0]["kind"], "server_down")
+
+ def test_worker_skips_the_probe_without_a_url(self):
+ # No server URL at all (backend vanished between menu and run):
+ # there is nothing to probe, so no server events are queued.
+ with patch("audiobook.convert"), \
+ patch.object(runview.common, "server_running") as mk_running:
+ view = self.make_view(server_url=None, server_name=None,
+ server_identity=None)
+ view._worker_main()
+ mk_running.assert_not_called()
+ self.assertEqual([e["kind"] for e in self._drain(view)],
+ ["worker_exit"])
+
if __name__ == "__main__":
unittest.main()