from __future__ import annotations import subprocess import sys from pathlib import Path from typer.testing import CliRunner from ocr_pipeline import config as config_module from ocr_pipeline.cli import app runner = CliRunner() SRC_DIR = Path(__file__).parent.parent / "src" def _isolate_config(tmp_path: Path, monkeypatch) -> None: """Pretend no config exists anywhere.""" monkeypatch.delenv(config_module.CONFIG_ENV_VAR, raising=False) monkeypatch.chdir(tmp_path) monkeypatch.setattr(config_module, "user_config_dir", lambda: tmp_path / "no-such-dir") def test_find_config_file_prefers_env_var(tmp_path: Path, monkeypatch) -> None: env_cfg = tmp_path / "env.yaml" env_cfg.write_text("ocr:\n engine: tesseract\n", encoding="utf-8") cwd_dir = tmp_path / "cwd" cwd_dir.mkdir() (cwd_dir / "config.yaml").write_text("{}", encoding="utf-8") monkeypatch.setenv(config_module.CONFIG_ENV_VAR, str(env_cfg)) monkeypatch.chdir(cwd_dir) assert config_module.find_config_file() == env_cfg def test_find_config_file_uses_cwd(tmp_path: Path, monkeypatch) -> None: monkeypatch.delenv(config_module.CONFIG_ENV_VAR, raising=False) (tmp_path / "config.yaml").write_text("{}", encoding="utf-8") monkeypatch.chdir(tmp_path) assert config_module.find_config_file() == tmp_path / "config.yaml" def test_find_config_file_none_when_unconfigured(tmp_path: Path, monkeypatch) -> None: _isolate_config(tmp_path, monkeypatch) assert config_module.find_config_file() is None def test_import_does_not_read_cwd_config(tmp_path: Path) -> None: (tmp_path / "config.yaml").write_text("{invalid yaml: [", encoding="utf-8") result = subprocess.run( [sys.executable, "-c", "import ocr_pipeline.config; print('ok')"], cwd=tmp_path, capture_output=True, text=True, env={"PYTHONPATH": str(SRC_DIR), "PATH": "/usr/bin:/bin"}, ) assert result.returncode == 0 assert "ok" in result.stdout def test_settings_proxy_delegates() -> None: from ocr_pipeline.config import settings assert settings.ocr.engine in {"tesseract", "paddleocr", "auto"} assert repr(settings) def test_init_writes_local_config(tmp_path: Path, monkeypatch) -> None: _isolate_config(tmp_path, monkeypatch) result = runner.invoke(app, ["init", "--local"]) assert result.exit_code == 0 content = (tmp_path / "config.yaml").read_text(encoding="utf-8") assert "paths:" in content assert "tesseract" in content def test_init_refuses_to_overwrite(tmp_path: Path, monkeypatch) -> None: _isolate_config(tmp_path, monkeypatch) (tmp_path / "config.yaml").write_text("existing: true\n", encoding="utf-8") result = runner.invoke(app, ["init", "--local"]) assert result.exit_code == 1 assert (tmp_path / "config.yaml").read_text(encoding="utf-8") == "existing: true\n" def test_run_guard_without_config_or_input_dir(tmp_path: Path, monkeypatch) -> None: _isolate_config(tmp_path, monkeypatch) result = runner.invoke(app, ["run"]) assert result.exit_code == 2 assert "ocr-pipeline init" in result.output assert "ocr-pipeline demo" in result.output def test_watch_guard_without_config(tmp_path: Path, monkeypatch) -> None: _isolate_config(tmp_path, monkeypatch) result = runner.invoke(app, ["watch"]) assert result.exit_code == 2 def test_setup_dry_run_prints_steps_without_executing() -> None: result = runner.invoke(app, ["setup", "full", "--dry-run"]) assert result.exit_code == 0 assert "uv sync" in result.output or "pip install" in result.output def test_setup_rejects_unknown_target() -> None: result = runner.invoke(app, ["setup", "everything", "--dry-run"]) assert result.exit_code == 2 def test_demo_image_bundled_and_valid() -> None: from PIL import Image from ocr_pipeline.demo import demo_image_path with Image.open(demo_image_path()) as img: assert img.size[0] >= 800 assert img.format == "PNG"