"""Pure handler contracts for the Watchtower and one-click Gradio tabs.""" from __future__ import annotations import importlib from pathlib import Path from types import SimpleNamespace from unittest.mock import Mock import pytest auto_obliterate = importlib.import_module("obliteratus.auto_obliterate") ui_watchtower = importlib.import_module("obliteratus.ui_watchtower") def test_component_update_delegates_to_gradio_when_available(monkeypatch): update = Mock(return_value={"delegated": True}) monkeypatch.setattr(ui_watchtower, "gr", SimpleNamespace(update=update)) assert ui_watchtower._component_update(interactive=True) == {"delegated": True} update.assert_called_once_with(interactive=True) def test_trending_choices_use_watchtower_and_fall_back_on_empty_or_error(monkeypatch): watchtower = SimpleNamespace(get_model_choices=lambda: ["org/one", "org/two"]) monkeypatch.setattr(ui_watchtower, "_get_watchtower", lambda: watchtower) assert ui_watchtower._get_trending_choices() == ["org/one", "org/two"] watchtower.get_model_choices = lambda: [] assert ui_watchtower._get_trending_choices()[0] == "meta-llama/Llama-3.1-8B-Instruct" monkeypatch.setattr( ui_watchtower, "_get_watchtower", Mock(side_effect=RuntimeError("offline")), ) assert "Qwen/Qwen3-4B" in ui_watchtower._get_trending_choices() @pytest.mark.parametrize("model_id", ["", " ", None]) def test_one_click_rejects_missing_model_without_starting_work(model_id): outputs = list(ui_watchtower._run_one_click(model_id, 3, 5)) assert len(outputs) == 1 assert "Please enter or select" in outputs[0][0] assert outputs[0][3]["interactive"] is False def test_one_click_streams_progress_and_preserves_zero_refusal_rate(monkeypatch, tmp_path): output = tmp_path / "model" output.mkdir() created: list[object] = [] final = SimpleNamespace( success=True, final_output_dir=str(output), final_refusal_rate=0.0, total_time_seconds=12.5, ) class FakeObliterator: def __init__(self, **kwargs): created.append(kwargs) self._result = SimpleNamespace(final_output_dir=str(output)) def run(self): yield "running", "step", "metrics" return final def _format_metrics(self): return "final metrics" monkeypatch.setattr(auto_obliterate, "AutoObliterator", FakeObliterator) outputs = list(ui_watchtower._run_one_click(" org/model ", 3.9, 5)) assert created == [ { "model_id": "org/model", "max_iterations": 3, "target_refusal_rate": 0.05, } ] assert outputs[0][0] == "running" assert outputs[0][3]["interactive"] is True assert outputs[-1][0] == "✅ Complete!" assert "Final refusal rate: 0.0" in outputs[-1][1] assert "unknown" not in outputs[-1][1] def test_one_click_converts_generator_failure_to_noninteractive_error(monkeypatch): class FailingObliterator: def __init__(self, **_kwargs): self._result = SimpleNamespace(final_output_dir=None) def run(self): yield "starting", "", "" raise RuntimeError("device failed") monkeypatch.setattr(auto_obliterate, "AutoObliterator", FailingObliterator) outputs = list(ui_watchtower._run_one_click("org/model", 1, 5)) assert outputs[0][0] == "starting" assert "device failed" in outputs[-1][0] assert outputs[-1][3]["interactive"] is False def test_download_result_selects_latest_numeric_iteration(monkeypatch, tmp_path): monkeypatch.setattr(Path, "home", classmethod(lambda cls: tmp_path)) base = tmp_path / ".obliteratus" / "auto_obliterate" / "org_model" for name in ("iter_invalid", "iter_2", "iter_10"): directory = base / name directory.mkdir(parents=True) (directory / "config.json").write_text("{}", encoding="utf-8") assert ui_watchtower._download_result("org/model") == str(base / "iter_10") def test_download_result_handles_empty_id_and_unreadable_iteration(monkeypatch, tmp_path): monkeypatch.setattr(Path, "home", classmethod(lambda cls: tmp_path)) base = tmp_path / ".obliteratus" / "auto_obliterate" / "org_model" broken = base / "iter_20" broken.mkdir(parents=True) (broken / "config.json").write_text("{}", encoding="utf-8") valid = base / "iter_10" valid.mkdir() (valid / "config.json").write_text("{}", encoding="utf-8") resolve = Path.resolve def controlled_resolve(path, *args, **kwargs): if path == broken: raise OSError("unreadable") return resolve(path, *args, **kwargs) monkeypatch.setattr(Path, "resolve", controlled_resolve) assert ui_watchtower._download_result("") is None assert ui_watchtower._download_result("org/model") == str(valid) def test_download_result_rejects_symlink_that_escapes_managed_root(monkeypatch, tmp_path): monkeypatch.setattr(Path, "home", classmethod(lambda cls: tmp_path)) base = tmp_path / ".obliteratus" / "auto_obliterate" / "org_model" valid = base / "iter_10" valid.mkdir(parents=True) (valid / "config.json").write_text("{}", encoding="utf-8") outside = tmp_path / "outside" outside.mkdir() (outside / "config.json").write_text("{}", encoding="utf-8") (base / "iter_99").symlink_to(outside, target_is_directory=True) assert ui_watchtower._download_result("../../org/model") is None assert ui_watchtower._download_result("org/model") == str(valid) def test_scan_handler_returns_data_and_escaped_failure(monkeypatch): watchtower = SimpleNamespace( scan=lambda on_log: (on_log("scanned") or [SimpleNamespace()]), get_stats=lambda: {"total_tracked": 1}, format_table=lambda: [["org/model"]], ) monkeypatch.setattr(ui_watchtower, "_get_watchtower", lambda: watchtower) status, table, log = ui_watchtower._scan_now() assert "1" in status assert table == [["org/model"]] assert log == "scanned" monkeypatch.setattr( ui_watchtower, "_get_watchtower", Mock(side_effect=RuntimeError("")), ) status, table, log = ui_watchtower._scan_now() assert "