from __future__ import annotations from typer.testing import CliRunner from ocr_pipeline.cli import app from ocr_pipeline.config import settings from ocr_pipeline.doctor import ( check_config, check_db, check_output_dir, check_paddle, check_python, check_spacy, check_tables, check_tesseract, check_torch, exit_code_for, render, run_checks, ) runner = CliRunner() def test_check_python_ok() -> None: r = check_python() assert r.status == "ok" assert "Python" in r.name def test_check_tesseract_ok_when_on_path(monkeypatch) -> None: monkeypatch.setattr("shutil.which", lambda _: "/usr/bin/tesseract") r = check_tesseract() assert r.status == "ok" assert "/usr/bin/tesseract" in r.detail def test_check_tesseract_fails_when_missing(monkeypatch) -> None: monkeypatch.setattr("shutil.which", lambda _: None) r = check_tesseract() assert r.status == "fail" assert r.hint is not None def test_check_paddle_warns_when_missing(monkeypatch) -> None: monkeypatch.setattr( "ocr_pipeline.doctor._has_module", lambda name: False if name == "paddleocr" else True ) r = check_paddle() assert r.status == "warn" assert "paddle" in r.hint.lower() if r.hint else True def test_check_torch_reports_backend(monkeypatch) -> None: monkeypatch.setattr("ocr_pipeline.doctor._has_module", lambda name: True) class FakeMPS: @staticmethod def is_available(): # type: ignore[no-untyped-def] return False class FakeBackends: mps = FakeMPS class FakeCuda: @staticmethod def is_available(): # type: ignore[no-untyped-def] return False fake_torch = type("T", (), {"cuda": FakeCuda, "backends": FakeBackends}) monkeypatch.setitem(__import__("sys").modules, "torch", fake_torch) r = check_torch() assert r.status == "ok" assert "backend=" in r.detail def test_check_tables_warns_when_missing(monkeypatch) -> None: monkeypatch.setattr( "ocr_pipeline.doctor._has_module", lambda name: False if name in ("torch", "transformers") else True, ) r = check_tables() assert r.status == "warn" def test_check_spacy_warns_when_model_missing(monkeypatch, tmp_path) -> None: monkeypatch.setattr("ocr_pipeline.doctor._has_module", lambda name: True) class FakeSpacy: @staticmethod def load(_model): raise OSError("model not found") monkeypatch.setitem(__import__("sys").modules, "spacy", FakeSpacy) r = check_spacy() assert r.status == "warn" assert "en_core_sci_lg" in (r.hint or "") def test_check_config_warns_when_no_file(monkeypatch, tmp_path) -> None: monkeypatch.chdir(tmp_path) monkeypatch.setattr("ocr_pipeline.config.user_config_dir", lambda: tmp_path / "missing") r = check_config() assert r.status == "warn" assert "init" in (r.hint or "") def test_check_db_writes_temp_file(tmp_path, monkeypatch) -> None: db = tmp_path / "sub" / "test.db" monkeypatch.setattr(settings.watch, "db_path", str(db)) r = check_db() assert r.status == "ok" assert db.exists() def test_check_output_dir_creates_path(tmp_path, monkeypatch) -> None: out = tmp_path / "deep" / "output" monkeypatch.setattr(settings.output, "base_directory", str(out)) r = check_output_dir() assert r.status == "ok" assert out.is_dir() def test_exit_code_for() -> None: from ocr_pipeline.doctor import CheckResult assert exit_code_for([]) == 0 assert exit_code_for([CheckResult("a", "ok", "x")]) == 0 assert exit_code_for([CheckResult("a", "warn", "x")]) == 0 assert exit_code_for([CheckResult("a", "fail", "x")]) == 1 def test_run_checks_returns_all_categories() -> None: results = run_checks() names = {r.name for r in results} assert {"Tesseract", "PaddleOCR", "Python", "Config"}.issubset(names) def test_render_writes_table(capsys) -> None: from rich.console import Console from ocr_pipeline.doctor import CheckResult console = Console(file=__import__("sys").stdout) render( [CheckResult("A", "ok", "fine"), CheckResult("B", "fail", "bad", hint="fix me")], console ) out = capsys.readouterr().out assert "A" in out and "B" in out def test_doctor_cli_runs(monkeypatch) -> None: from ocr_pipeline.doctor import CheckResult monkeypatch.setattr("ocr_pipeline.cli.run_checks", lambda: [CheckResult("X", "ok", "ok")]) result = runner.invoke(app, ["doctor"]) assert result.exit_code == 0 assert "X" in result.output def test_doctor_cli_exit_code_on_fail(monkeypatch) -> None: from ocr_pipeline.doctor import CheckResult monkeypatch.setattr("ocr_pipeline.cli.run_checks", lambda: [CheckResult("X", "fail", "bad")]) result = runner.invoke(app, ["doctor"]) assert result.exit_code == 1