mirror of
https://github.com/elder-plinius/OBLITERATUS.git
synced 2026-08-18 00:47:23 +02:00
The README/CONTRIBUTING examples used `obliteratus aggregate --format ...` but the CLI only accepted `--dir`.
This adds `--format {summary,latex}`, `--metric`, and `--min-runs` to the aggregate command, reuses community LaTeX table generation, and adds CLI parsing tests to align behavior with documented usage.
157 lines
6.5 KiB
Python
157 lines
6.5 KiB
Python
"""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 all 9 pipeline methods."""
|
|
valid_methods = [
|
|
"basic", "advanced", "aggressive", "spectral_cascade",
|
|
"informed", "surgical", "optimized", "inverted", "nuclear",
|
|
]
|
|
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. invalid methods are rejected
|
|
def test_obliterate_rejects_invalid_method(self):
|
|
"""The CLI --method flag rejects unknown method names."""
|
|
stderr_text = _capture_exit(
|
|
["obliterate", "fake/model", "--method", "nonexistent"],
|
|
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
|
|
|
|
def test_aggregate_accepts_format_metric_min_runs(self):
|
|
"""aggregate accepts --format, --metric and --min-runs flags."""
|
|
with patch("obliteratus.cli._cmd_aggregate") as mock_cmd:
|
|
main([
|
|
"aggregate",
|
|
"--format", "latex",
|
|
"--metric", "refusal_rate",
|
|
"--min-runs", "3",
|
|
])
|
|
mock_cmd.assert_called_once()
|
|
args_passed = mock_cmd.call_args[0][0]
|
|
assert args_passed.format == "latex"
|
|
assert args_passed.metric == "refusal_rate"
|
|
assert args_passed.min_runs == 3
|
|
|
|
def test_aggregate_rejects_invalid_format(self):
|
|
"""aggregate rejects unknown --format choices."""
|
|
stderr_text = _capture_exit(
|
|
["aggregate", "--format", "invalid"],
|
|
expect_code=2,
|
|
)
|
|
assert "invalid choice" in stderr_text.lower()
|
|
|
|
# 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"
|