From db38085d07ce75f8961eecdc1919e98748254c53 Mon Sep 17 00:00:00 2001 From: historia Date: Fri, 28 Aug 2026 14:57:48 -0400 Subject: refactor: overhaul config.py, remove cli default options --- app/tests/test_audiobook_cli.py | 74 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) (limited to 'app/tests/test_audiobook_cli.py') 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() -- cgit v1.2.3