Spaces:
Running
Running
| """CLI dispatch tests for obliteratus.cli.main(). | |
| These tests verify argument parsing and subcommand routing without | |
| downloading real models or running any pipeline. They use | |
| ``unittest.mock.patch`` to capture stdout/stderr and | |
| ``pytest.raises(SystemExit)`` for argparse exits. | |
| """ | |
| from __future__ import annotations | |
| from io import StringIO | |
| from unittest.mock import patch | |
| import pytest | |
| from obliteratus.cli import main | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _capture_exit(argv: list[str] | None, *, expect_code: int | None = None): | |
| """Call main(argv), expecting SystemExit; return captured stderr text.""" | |
| buf = StringIO() | |
| with pytest.raises(SystemExit) as exc_info, patch("sys.stderr", buf): | |
| main(argv) | |
| if expect_code is not None: | |
| assert exc_info.value.code == expect_code | |
| return buf.getvalue() | |
| # --------------------------------------------------------------------------- | |
| # Tests | |
| # --------------------------------------------------------------------------- | |
| class TestCLIDispatch: | |
| """Test suite for CLI argument parsing and subcommand dispatch.""" | |
| # 1. No args -> prints help / exits with error | |
| def test_main_no_args_prints_help(self): | |
| """Calling main() with no args should exit (subcommand is required).""" | |
| stderr_text = _capture_exit([], expect_code=2) | |
| # argparse prints usage info to stderr on error | |
| assert "usage" in stderr_text.lower() or "required" in stderr_text.lower() | |
| # 2. models command lists models without error | |
| def test_models_command(self): | |
| """Calling main(['models']) should list models without raising.""" | |
| with patch("obliteratus.cli.console") as mock_console: | |
| main(["models"]) | |
| # console.print is called at least once to render the table | |
| assert mock_console.print.call_count >= 1 | |
| # 3. obliterate without model arg -> error | |
| def test_obliterate_requires_model(self): | |
| """Calling main(['obliterate']) without a model arg should error.""" | |
| stderr_text = _capture_exit(["obliterate"], expect_code=2) | |
| assert "model" in stderr_text.lower() or "required" in stderr_text.lower() | |
| # 4. obliterate --method accepts valid methods | |
| def test_obliterate_valid_methods(self): | |
| """Test that --method accepts basic, advanced, and aggressive.""" | |
| valid_methods = ["basic", "advanced", "aggressive"] | |
| for method in valid_methods: | |
| # Patch the actual pipeline execution so nothing runs | |
| with patch("obliteratus.cli._cmd_abliterate") as mock_cmd: | |
| main(["obliterate", "fake/model", "--method", method]) | |
| mock_cmd.assert_called_once() | |
| args_passed = mock_cmd.call_args[0][0] | |
| assert args_passed.method == method | |
| # 4b. informed is NOT a valid --method choice on the CLI | |
| def test_obliterate_rejects_informed_method(self): | |
| """The CLI --method flag does not accept 'informed' (separate pipeline).""" | |
| stderr_text = _capture_exit( | |
| ["obliterate", "fake/model", "--method", "informed"], | |
| expect_code=2, | |
| ) | |
| assert "invalid choice" in stderr_text.lower() | |
| # 5. run requires config path | |
| def test_run_requires_config(self): | |
| """Calling main(['run']) without a config path should error.""" | |
| stderr_text = _capture_exit(["run"], expect_code=2) | |
| assert "config" in stderr_text.lower() or "required" in stderr_text.lower() | |
| # 6. aggregate with nonexistent dir handles gracefully | |
| def test_aggregate_command_missing_dir(self): | |
| """Calling main(['aggregate']) with nonexistent dir should handle gracefully.""" | |
| with patch("obliteratus.cli.console") as mock_console: | |
| main(["aggregate", "--dir", "/nonexistent/path/to/nowhere"]) | |
| # The command prints a message about no contributions found and returns | |
| printed_text = " ".join( | |
| str(call) for call in mock_console.print.call_args_list | |
| ) | |
| assert "no contributions found" in printed_text.lower() or mock_console.print.called | |
| # 7. --help flag prints help | |
| def test_help_flag(self): | |
| """Calling main(['--help']) should print help and exit 0.""" | |
| buf = StringIO() | |
| with pytest.raises(SystemExit) as exc_info, patch("sys.stdout", buf): | |
| main(["--help"]) | |
| assert exc_info.value.code == 0 | |
| output = buf.getvalue() | |
| assert "obliteratus" in output.lower() or "usage" in output.lower() | |
| # 8. interactive subcommand is registered | |
| def test_interactive_command_exists(self): | |
| """Verify 'interactive' subcommand is registered and dispatches.""" | |
| with patch("obliteratus.cli._cmd_interactive") as mock_cmd: | |
| main(["interactive"]) | |
| mock_cmd.assert_called_once() | |
| # 9. --contribute and --contribute-notes are accepted on obliterate | |
| def test_contribute_flags_on_obliterate(self): | |
| """Verify --contribute and --contribute-notes are accepted args.""" | |
| with patch("obliteratus.cli._cmd_abliterate") as mock_cmd: | |
| main([ | |
| "obliterate", "fake/model", | |
| "--contribute", | |
| "--contribute-notes", "Testing contribution system", | |
| ]) | |
| mock_cmd.assert_called_once() | |
| args_passed = mock_cmd.call_args[0][0] | |
| assert args_passed.contribute is True | |
| assert args_passed.contribute_notes == "Testing contribution system" | |