aboutsummaryrefslogtreecommitdiff
path: root/app/tests/test_audiobook_cli.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-28 14:57:48 -0400
committerhistoria <historiavg@proton.me>2026-08-28 14:57:48 -0400
commitdb38085d07ce75f8961eecdc1919e98748254c53 (patch)
treebeaea2ba84de05bfc2fa90e9b92a24568d3b4cb9 /app/tests/test_audiobook_cli.py
parentafb2c2d5b297c5aa28bcced0e3f90e207d799c2a (diff)
downloadtts-audiobook-generator-db38085d07ce75f8961eecdc1919e98748254c53.tar.gz
refactor: overhaul config.py, remove cli default options
Diffstat (limited to 'app/tests/test_audiobook_cli.py')
-rw-r--r--app/tests/test_audiobook_cli.py74
1 files changed, 72 insertions, 2 deletions
diff --git a/app/tests/test_audiobook_cli.py b/app/tests/test_audiobook_cli.py
index b06a715..d947c5d 100644
--- a/app/tests/test_audiobook_cli.py
+++ b/app/tests/test_audiobook_cli.py
@@ -43,13 +43,17 @@ class MainTestCase(unittest.TestCase):
self.tmp = Path(tempfile.mkdtemp(prefix="audiobook_cli_"))
self.addCleanup(shutil.rmtree, self.tmp, True)
- def run_main(self, argv):
+ def run_main(self, argv, backend="audiocpp"):
"""Run main() with the given argv; returns (code, stderr, convert mock).
The envs bootstrap (which re-execs into the managed venv via
os.execv when active) and convert() are stubbed, so no TTS work
- happens and the process survives.
+ happens and the process survives. BACKEND (a --backend value, or
+ None to omit the required flag) is prepended unless the argv
+ already carries --backend.
"""
+ if backend is not None and "--backend" not in argv:
+ argv = ["--backend", backend, *argv]
err = io.StringIO()
convert = MagicMock(return_value=0)
with patch.object(sys, "argv", ["audiobook.py", *argv]), \
@@ -64,6 +68,71 @@ class MainTestCase(unittest.TestCase):
return code, err.getvalue(), convert
+class MainBackendTests(MainTestCase):
+ """The backend/model/voice are per-run choices with no config defaults."""
+
+ def test_backend_is_required(self):
+ code, err, convert = self.run_main(["--debug"], backend=None)
+ self.assertEqual(code, 2)
+ self.assertIn("--backend", err)
+ self.assertIn("required", err)
+ convert.assert_not_called()
+
+ def test_backend_reaches_convert(self):
+ code, _, convert = self.run_main([])
+ self.assertEqual(code, 0)
+ self.assertEqual(convert.call_args.kwargs["backend"], "audiocpp")
+
+ def test_qwen_accepts_a_builtin_speaker_voice(self):
+ code, _, convert = self.run_main(
+ ["--backend", "qwen", "--voice", "Vivian"], backend=None)
+ self.assertEqual(code, 0)
+ self.assertEqual(convert.call_args.kwargs["backend"], "qwen")
+ self.assertEqual(convert.call_args.kwargs["voice"], "Vivian")
+
+ def test_qwen_rejects_a_non_speaker_voice(self):
+ code, err, convert = self.run_main(
+ ["--backend", "qwen", "--voice", "narrator"], backend=None)
+ self.assertEqual(code, 2)
+ self.assertIn("not a built-in speaker", err)
+ convert.assert_not_called()
+
+ def test_qwen_requires_a_voice_without_clone_or_instructions(self):
+ code, err, convert = self.run_main(["--backend", "qwen"], backend=None)
+ self.assertEqual(code, 2)
+ self.assertIn("--backend qwen needs a voice", err)
+ convert.assert_not_called()
+
+ def test_qwen_clone_run_needs_no_voice(self):
+ code, _, convert = self.run_main(
+ ["--backend", "qwen", "--clone", "ref.wav"], backend=None)
+ self.assertEqual(code, 0)
+ convert.assert_called_once()
+
+ def test_faster_requires_a_voice(self):
+ code, err, convert = self.run_main(["--backend", "faster"],
+ backend=None)
+ self.assertEqual(code, 2)
+ self.assertIn("--backend faster requires --voice", err)
+ convert.assert_not_called()
+
+ def test_noninteractive_no_args_stops_with_guidance(self):
+ # No args in a non-interactive session cannot guess a backend:
+ # point the user at --backend / the TUI instead of converting.
+ out, err = io.StringIO(), io.StringIO()
+ with patch.object(sys, "argv", ["audiobook.py"]), \
+ patch.object(sys, "stdin", io.StringIO()), \
+ patch.object(sys, "stdout", io.StringIO()), \
+ contextlib.redirect_stdout(out), \
+ contextlib.redirect_stderr(err), \
+ patch.object(audiobook._envs, "bootstrap"):
+ with self.assertRaises(SystemExit) as ctx:
+ audiobook.main()
+ self.assertEqual(ctx.exception.code, 2)
+ self.assertIn("No --backend given", out.getvalue())
+ self.assertIn("No --backend given", err.getvalue() + out.getvalue())
+
+
class MainFlagConflictTests(MainTestCase):
"""Mixing the directory and single-book flag pairs stops with an error."""
@@ -200,6 +269,7 @@ class ConvertWiringTests(unittest.TestCase):
self._old_folders
def _convert(self, **kwargs):
+ kwargs.setdefault("backend", "audiocpp")
preflight = MagicMock(
return_value=([self.book], [(self.book, "dune")]))
fake_instance = MagicMock()