aboutsummaryrefslogtreecommitdiff
path: root/app/tests
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-24 03:41:57 -0400
committerhistoria <historiavg@proton.me>2026-08-24 03:41:57 -0400
commit73b466fbc054e80b50e318b49643aee8a03c784b (patch)
treedf3d78efa84956bcf47586af736479f319a80abe /app/tests
parent471798cf5e967b2d1bceb02d12a47fe9ad1cbed1 (diff)
downloadtts-audiobook-generator-73b466fbc054e80b50e318b49643aee8a03c784b.tar.gz
feat: settings for ports in TUI
Diffstat (limited to 'app/tests')
-rw-r--r--app/tests/test_hub.py157
-rw-r--r--app/tests/test_tui.py25
2 files changed, 157 insertions, 25 deletions
diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py
index 749d2e6..4b394e8 100644
--- a/app/tests/test_hub.py
+++ b/app/tests/test_hub.py
@@ -92,8 +92,7 @@ class HubMenuTests(unittest.TestCase):
patch.object(hub, "detect_all", return_value=[]):
hub._hub_menu(screen)
labels = [label for label, _ in captured["options"]]
- self.assertEqual(labels, ["Set up a backend...", "Settings...",
- "Quit"])
+ self.assertEqual(labels, ["Set up a backend", "Settings", "Quit"])
def test_menu_has_all_six_when_one_installed(self):
captured = {}
@@ -112,8 +111,9 @@ class HubMenuTests(unittest.TestCase):
labels = [label for label, _ in captured["options"]]
self.assertEqual(
labels,
- ["Convert books...", "Set up a backend...",
- "Configure a backend...", "Server...", "Settings...", "Quit"])
+ ["Convert books", "Set up a backend",
+ "Configure a backend", "Start/Stop Backend Servers",
+ "Settings", "Quit"])
# The status table is passed through, one row per backend.
self.assertEqual(captured["rows"],
[("qwen-tts", "installed", "warn", "body")])
@@ -157,8 +157,9 @@ class HubMenuTests(unittest.TestCase):
labels = [label for label, _ in captured["options"]]
self.assertEqual(
labels,
- ["Convert books...", "Set up a backend...",
- "Configure a backend...", "Server...", "Settings...", "Quit"])
+ ["Convert books", "Set up a backend",
+ "Configure a backend", "Start/Stop Backend Servers",
+ "Settings", "Quit"])
def test_ffmpeg_warning_shown_when_missing(self):
# ffmpeg not on PATH → a red notice is passed above the table.
@@ -194,7 +195,7 @@ class HubMenuTests(unittest.TestCase):
def test_convert_with_no_available_backend_offers_setup(self):
# One installed-but-not-ready backend → Convert is offered. The
# convert menu lists no available backend, so only "Set up a
- # backend..." is shown; Enter selects it → setup menu lists 3
+ # backend" is shown; Enter selects it → setup menu lists 3
# backends; Esc goes back → convert returns None → main menu loops.
# Then quit: main menu now has 5 options, Quit is the 5th (Down x4).
from backends import BackendInfo, BackendStatus
@@ -396,41 +397,53 @@ class SettingsTests(unittest.TestCase):
original = {name: getattr(hub.config, name) for name in
("AUDIO_FORMAT", "AUDIO_BITRATE", "LANGUAGE",
- "CHUNK_SIZE")}
+ "CHUNK_SIZE", "QWEN_API_URL", "CLONE_API_URL",
+ "FASTER_API_URL", "AUDIOCPP_API_URL")}
self.addCleanup(lambda: [setattr(hub.config, name, value)
for name, value in original.items()])
values = {"audio_format": "ogg", "audio_bitrate": " 192k ",
- "language": "en", "chunk_size": "300"}
- with patch.object(hub, "_write_config", fake_write):
+ "language": "en", "chunk_size": "300",
+ "qwen_custom_port": "7862", "qwen_clone_port": "7863",
+ "faster_port": "8001", "audiocpp_port": "8081"}
+ with patch.object(hub, "_write_config", fake_write), \
+ patch.object(hub, "_sync_audiocpp_server_port"):
hub._apply_settings(values)
# Values are trimmed and language normalized to a display name.
self.assertEqual(written, {"AUDIO_FORMAT": "ogg",
"AUDIO_BITRATE": "192k",
"LANGUAGE": "English",
- "CHUNK_SIZE": 300})
+ "CHUNK_SIZE": 300,
+ "QWEN_API_URL": "http://127.0.0.1:7862",
+ "CLONE_API_URL": "http://127.0.0.1:7863",
+ "FASTER_API_URL": "http://127.0.0.1:8001",
+ "AUDIOCPP_API_URL":
+ "http://127.0.0.1:8081"})
# In-memory config is reloaded so this session sees the change.
self.assertEqual(hub.config.AUDIO_FORMAT, "ogg")
self.assertEqual(hub.config.AUDIO_BITRATE, "192k")
self.assertEqual(hub.config.LANGUAGE, "English")
self.assertEqual(hub.config.CHUNK_SIZE, 300)
+ self.assertEqual(hub.config.QWEN_API_URL, "http://127.0.0.1:7862")
+ self.assertEqual(hub.config.FASTER_API_URL, "http://127.0.0.1:8001")
def test_apply_settings_rejects_bad_values(self):
original = {name: getattr(hub.config, name) for name in
("AUDIO_FORMAT", "AUDIO_BITRATE", "LANGUAGE",
- "CHUNK_SIZE")}
+ "CHUNK_SIZE", "QWEN_API_URL", "CLONE_API_URL",
+ "FASTER_API_URL", "AUDIOCPP_API_URL")}
self.addCleanup(lambda: [setattr(hub.config, name, value)
for name, value in original.items()])
+ base = {"audio_format": "m4b", "audio_bitrate": "128k",
+ "language": "English", "chunk_size": "250",
+ "qwen_custom_port": "7860", "qwen_clone_port": "7861",
+ "faster_port": "8000", "audiocpp_port": "8080"}
with patch.object(hub, "_write_config") as mk_write:
with self.assertRaises(ValueError):
- hub._apply_settings({"audio_format": "m4b",
- "audio_bitrate": "128k",
- "language": "Klingon",
- "chunk_size": "250"})
+ hub._apply_settings({**base, "language": "Klingon"})
+ with self.assertRaises(ValueError):
+ hub._apply_settings({**base, "chunk_size": "0"})
with self.assertRaises(ValueError):
- hub._apply_settings({"audio_format": "m4b",
- "audio_bitrate": "128k",
- "language": "English",
- "chunk_size": "0"})
+ hub._apply_settings({**base, "audiocpp_port": "70000"})
mk_write.assert_not_called()
def test_field_validators(self):
@@ -442,6 +455,12 @@ class SettingsTests(unittest.TestCase):
self.assertIsNone(hub._validate_chunk_size("250"))
self.assertIsNotNone(hub._validate_chunk_size("abc"))
self.assertIsNotNone(hub._validate_chunk_size("0"))
+ self.assertIsNone(hub._validate_port("8080"))
+ self.assertIsNone(hub._validate_port("1"))
+ self.assertIsNone(hub._validate_port("65535"))
+ self.assertIsNotNone(hub._validate_port("0"))
+ self.assertIsNotNone(hub._validate_port("70000"))
+ self.assertIsNotNone(hub._validate_port("abc"))
def test_settings_menu_builds_form_and_saves(self):
captured = {}
@@ -449,7 +468,9 @@ class SettingsTests(unittest.TestCase):
def fake_form(stdscr, title, fields, back_value=None):
captured["fields"] = fields
return {"audio_format": "ogg", "audio_bitrate": "192k",
- "language": "English", "chunk_size": "300"}
+ "language": "English", "chunk_size": "300",
+ "qwen_custom_port": "7860", "qwen_clone_port": "7861",
+ "faster_port": "8000", "audiocpp_port": "8080"}
applied = []
@@ -465,14 +486,20 @@ class SettingsTests(unittest.TestCase):
hub._settings_menu(None)
self.assertEqual([f["key"] for f in captured["fields"]],
["audio_format", "audio_bitrate", "language",
- "chunk_size"])
+ "chunk_size", "qwen_custom_port", "qwen_clone_port",
+ "faster_port", "audiocpp_port"])
kinds = {f["key"]: f["kind"] for f in captured["fields"]}
self.assertEqual(kinds["audio_format"], "choice")
self.assertEqual(kinds["audio_bitrate"], "text")
+ self.assertEqual(kinds["audiocpp_port"], "text")
self.assertEqual(applied, [{"audio_format": "ogg",
"audio_bitrate": "192k",
"language": "English",
- "chunk_size": "300"}])
+ "chunk_size": "300",
+ "qwen_custom_port": "7860",
+ "qwen_clone_port": "7861",
+ "faster_port": "8000",
+ "audiocpp_port": "8080"}])
self.assertEqual(captured["flash"], ("Settings saved.", "ok"))
def test_settings_menu_cancel_does_not_apply(self):
@@ -500,7 +527,8 @@ class SettingsTests(unittest.TestCase):
original = {name: getattr(hub.config, name) for name in
("AUDIO_FORMAT", "AUDIO_BITRATE", "LANGUAGE",
- "CHUNK_SIZE")}
+ "CHUNK_SIZE", "QWEN_API_URL", "CLONE_API_URL",
+ "FASTER_API_URL", "AUDIOCPP_API_URL")}
self.addCleanup(lambda: [setattr(hub.config, name, value)
for name, value in original.items()])
@@ -512,7 +540,11 @@ class SettingsTests(unittest.TestCase):
'AUDIO_BITRATE = "128k"\n'
'LANGUAGE = "English"\n'
"\n"
- "CHUNK_SIZE = 250\n",
+ "CHUNK_SIZE = 250\n"
+ 'QWEN_API_URL = "http://127.0.0.1:7860"\n'
+ 'CLONE_API_URL = "http://127.0.0.1:7861"\n'
+ 'FASTER_API_URL = "http://127.0.0.1:8000"\n'
+ 'AUDIOCPP_API_URL = "http://127.0.0.1:8080"\n',
encoding="utf-8")
with patch.object(hub.config, "__file__", str(path)):
# Down to Chunk size, Enter -> editor, Ctrl-U + '300',
@@ -528,6 +560,81 @@ class SettingsTests(unittest.TestCase):
# The running session also picked up the change in-memory.
self.assertEqual(hub.config.CHUNK_SIZE, 300)
+ def test_settings_menu_updates_backend_ports(self):
+ import tempfile
+ original = {name: getattr(hub.config, name) for name in
+ ("QWEN_API_URL", "CLONE_API_URL",
+ "FASTER_API_URL", "AUDIOCPP_API_URL")}
+ self.addCleanup(lambda: [setattr(hub.config, name, value)
+ for name, value in original.items()])
+ with tempfile.TemporaryDirectory() as td:
+ path = Path(td) / "config.py"
+ path.write_text(
+ 'QWEN_API_URL = "http://127.0.0.1:7860"\n'
+ 'CLONE_API_URL = "http://127.0.0.1:7861"\n'
+ 'FASTER_API_URL = "http://127.0.0.1:8000"\n'
+ 'AUDIOCPP_API_URL = "http://127.0.0.1:8080"\n',
+ encoding="utf-8")
+ with patch.object(hub.config, "__file__", str(path)), \
+ patch.object(hub, "_sync_audiocpp_server_port"):
+ hub._write_config({
+ "QWEN_API_URL": "http://127.0.0.1:7862",
+ "CLONE_API_URL": "http://127.0.0.1:7863",
+ "FASTER_API_URL": "http://127.0.0.1:8001",
+ "AUDIOCPP_API_URL": "http://127.0.0.1:8081",
+ })
+ text = path.read_text(encoding="utf-8")
+ self.assertIn('QWEN_API_URL = "http://127.0.0.1:7862"', text)
+ self.assertIn('CLONE_API_URL = "http://127.0.0.1:7863"', text)
+ self.assertIn('FASTER_API_URL = "http://127.0.0.1:8001"', text)
+ self.assertIn('AUDIOCPP_API_URL = "http://127.0.0.1:8081"', text)
+
+
+class AudiocppServerConfigTests(unittest.TestCase):
+ """update_server_config_port: rewriting the checkout's server.json."""
+
+ def _make_checkout(self, td, port=8080):
+ from backends import audiocpp as audiocpp_backend
+ import json
+ checkout = Path(td) / "audio.cpp"
+ checkout.mkdir()
+ server_json = checkout / "server.json"
+ server_json.write_text(
+ json.dumps({"host": "127.0.0.1", "port": port,
+ "models": [{"id": "qwen"}]}, indent=2),
+ encoding="utf-8")
+ return audiocpp_backend, checkout, server_json
+
+ def test_rewrites_existing_server_json_port(self):
+ import tempfile
+ import json
+ with tempfile.TemporaryDirectory() as td:
+ mod, checkout, server_json = self._make_checkout(td, port=8080)
+ with patch.object(mod, "find_local_checkout",
+ return_value=checkout):
+ self.assertTrue(mod.update_server_config_port(9090))
+ data = json.loads(server_json.read_text(encoding="utf-8"))
+ self.assertEqual(data["port"], 9090)
+ # Other keys are preserved.
+ self.assertEqual(data["host"], "127.0.0.1")
+ self.assertEqual(data["models"], [{"id": "qwen"}])
+
+ def test_noop_when_port_unchanged(self):
+ import tempfile
+ with tempfile.TemporaryDirectory() as td:
+ mod, checkout, server_json = self._make_checkout(td, port=8080)
+ before = server_json.read_text(encoding="utf-8")
+ with patch.object(mod, "find_local_checkout",
+ return_value=checkout):
+ self.assertTrue(mod.update_server_config_port(8080))
+ self.assertEqual(server_json.read_text(encoding="utf-8"), before)
+
+ def test_false_when_no_checkout(self):
+ from backends import audiocpp as audiocpp_backend
+ with patch.object(audiocpp_backend, "find_local_checkout",
+ return_value=None):
+ self.assertFalse(audiocpp_backend.update_server_config_port(9090))
+
if __name__ == "__main__":
unittest.main()
diff --git a/app/tests/test_tui.py b/app/tests/test_tui.py
index c87374f..5862a54 100644
--- a/app/tests/test_tui.py
+++ b/app/tests/test_tui.py
@@ -408,6 +408,31 @@ class FormTests(TuiTestCase):
back_value=marker)
self.assertIs(result, marker)
+ def test_down_on_last_field_moves_to_save(self):
+ # Down moves cursor to the last field, Down again steps onto the
+ # Save button, Enter saves.
+ screen = FakeScreen(keys=[FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
+ 10])
+ result = tui.form(screen, "Settings", self._fields())
+ self.assertEqual(result, {"fmt": "m4b", "chunk": "250"})
+
+ def test_up_on_first_field_moves_to_cancel(self):
+ marker = object()
+ # Up from the first field steps onto the Cancel button, Enter.
+ screen = FakeScreen(keys=[FakeCurses.KEY_UP, 10])
+ result = tui.form(screen, "Settings", self._fields(),
+ back_value=marker)
+ self.assertIs(result, marker)
+
+ def test_up_down_on_buttons_returns_to_fields(self):
+ # Down (last field -> Save), Up returns to the last field, Enter
+ # opens its text editor, then Tab -> Save, Enter.
+ screen = FakeScreen(keys=[FakeCurses.KEY_DOWN, FakeCurses.KEY_DOWN,
+ FakeCurses.KEY_UP, 10, ord("x"), 10,
+ 9, 10])
+ result = tui.form(screen, "Settings", self._fields())
+ self.assertEqual(result, {"fmt": "m4b", "chunk": "250x"})
+
def test_esc_returns_back_value(self):
marker = object()
screen = FakeScreen(keys=[27])