Spaces:
Sleeping
Sleeping
Claude
fix: résoudre les 64 erreurs ruff pré-existantes révélées par le lint actif
6362212 unverified | """Tests unitaires pour les adaptateurs moteurs OCR. | |
| Les tests vérifient la structure et le comportement des adaptateurs | |
| sans requérir que Tesseract ou Pero OCR soient réellement installés. | |
| """ | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from unittest.mock import MagicMock, patch | |
| from picarones.engines.base import BaseOCREngine, EngineResult | |
| from picarones.engines.tesseract import TesseractEngine | |
| from picarones.engines.pero_ocr import PeroOCREngine | |
| # --------------------------------------------------------------------------- | |
| # Tests BaseOCREngine | |
| # --------------------------------------------------------------------------- | |
| class ConcreteEngine(BaseOCREngine): | |
| """Implémentation minimale pour tester la classe de base.""" | |
| def name(self) -> str: | |
| return "test_engine" | |
| def version(self) -> str: | |
| return "1.0.0" | |
| def _run_ocr(self, image_path: Path) -> str: | |
| return "Texte extrait par le moteur de test." | |
| class FailingEngine(BaseOCREngine): | |
| """Moteur qui lève toujours une exception.""" | |
| def name(self) -> str: | |
| return "failing_engine" | |
| def version(self) -> str: | |
| return "0.0.0" | |
| def _run_ocr(self, image_path: Path) -> str: | |
| raise RuntimeError("OCR échoué intentionnellement.") | |
| class TestBaseOCREngine: | |
| def test_run_returns_engine_result(self, tmp_path): | |
| (tmp_path / "image.png").write_bytes(b"fake_image") | |
| engine = ConcreteEngine() | |
| result = engine.run(tmp_path / "image.png") | |
| assert isinstance(result, EngineResult) | |
| def test_run_success(self, tmp_path): | |
| (tmp_path / "image.png").write_bytes(b"fake_image") | |
| engine = ConcreteEngine() | |
| result = engine.run(tmp_path / "image.png") | |
| assert result.success is True | |
| assert result.error is None | |
| assert result.text == "Texte extrait par le moteur de test." | |
| def test_run_captures_exception(self, tmp_path): | |
| (tmp_path / "image.png").write_bytes(b"fake_image") | |
| engine = FailingEngine() | |
| result = engine.run(tmp_path / "image.png") | |
| assert result.success is False | |
| assert result.error is not None | |
| assert "OCR échoué" in result.error | |
| def test_run_measures_duration(self, tmp_path): | |
| (tmp_path / "image.png").write_bytes(b"fake_image") | |
| engine = ConcreteEngine() | |
| result = engine.run(tmp_path / "image.png") | |
| assert result.duration_seconds >= 0.0 | |
| def test_engine_result_engine_name(self, tmp_path): | |
| (tmp_path / "image.png").write_bytes(b"fake_image") | |
| engine = ConcreteEngine() | |
| result = engine.run(tmp_path / "image.png") | |
| assert result.engine_name == "test_engine" | |
| def test_repr(self): | |
| engine = ConcreteEngine() | |
| assert "ConcreteEngine" in repr(engine) | |
| assert "test_engine" in repr(engine) | |
| def test_image_path_stored(self, tmp_path): | |
| img = tmp_path / "image.png" | |
| img.write_bytes(b"fake_image") | |
| engine = ConcreteEngine() | |
| result = engine.run(img) | |
| assert result.image_path == str(img) | |
| # --------------------------------------------------------------------------- | |
| # Tests TesseractEngine | |
| # --------------------------------------------------------------------------- | |
| class TestTesseractEngine: | |
| def test_name_default(self): | |
| engine = TesseractEngine() | |
| assert engine.name == "tesseract" | |
| def test_name_from_config(self): | |
| engine = TesseractEngine(config={"name": "tesseract_fra"}) | |
| assert engine.name == "tesseract_fra" | |
| def test_from_config_factory(self): | |
| engine = TesseractEngine.from_config({"lang": "lat", "psm": 7}) | |
| assert engine.config["lang"] == "lat" | |
| assert engine.config["psm"] == 7 | |
| def test_run_with_pytesseract_mocked(self, tmp_path): | |
| """Vérifie que le moteur appelle pytesseract correctement.""" | |
| img = tmp_path / "page.png" | |
| img.write_bytes(b"fake") | |
| with ( | |
| patch("picarones.engines.tesseract._PYTESSERACT_AVAILABLE", True), | |
| patch("picarones.engines.tesseract.pytesseract") as mock_tess, | |
| patch("picarones.engines.tesseract.Image") as mock_pil, | |
| ): | |
| mock_tess.image_to_string.return_value = "Résultat OCR mock" | |
| mock_pil.open.return_value = MagicMock() | |
| engine = TesseractEngine(config={"lang": "fra", "psm": 6}) | |
| result = engine.run(img) | |
| assert result.success is True | |
| assert result.text == "Résultat OCR mock" | |
| mock_tess.image_to_string.assert_called_once() | |
| def test_run_without_pytesseract_raises(self, tmp_path): | |
| """Sans pytesseract, le moteur doit retourner un EngineResult avec erreur.""" | |
| img = tmp_path / "page.png" | |
| img.write_bytes(b"fake") | |
| with patch("picarones.engines.tesseract._PYTESSERACT_AVAILABLE", False): | |
| engine = TesseractEngine() | |
| result = engine.run(img) | |
| assert result.success is False | |
| assert "pytesseract" in result.error.lower() | |
| # --------------------------------------------------------------------------- | |
| # Tests PeroOCREngine | |
| # --------------------------------------------------------------------------- | |
| class TestPeroOCREngine: | |
| def test_name_default(self): | |
| engine = PeroOCREngine() | |
| assert engine.name == "pero_ocr" | |
| def test_name_from_config(self): | |
| engine = PeroOCREngine(config={"name": "pero_historique"}) | |
| assert engine.name == "pero_historique" | |
| def test_from_config_factory(self): | |
| engine = PeroOCREngine.from_config({"config": "/path/to/pero.ini"}) | |
| assert engine.config["config"] == "/path/to/pero.ini" | |
| def test_run_without_pero_raises(self, tmp_path): | |
| """Sans pero-ocr, le moteur doit retourner un EngineResult avec erreur.""" | |
| img = tmp_path / "page.png" | |
| img.write_bytes(b"fake") | |
| with patch("picarones.engines.pero_ocr._PERO_AVAILABLE", False): | |
| engine = PeroOCREngine(config={"config": "/fake/config.ini"}) | |
| result = engine.run(img) | |
| assert result.success is False | |
| def test_run_without_config_raises(self, tmp_path): | |
| """Sans paramètre 'config', le moteur doit signaler une erreur claire.""" | |
| img = tmp_path / "page.png" | |
| img.write_bytes(b"fake") | |
| with patch("picarones.engines.pero_ocr._PERO_AVAILABLE", True): | |
| engine = PeroOCREngine() | |
| result = engine.run(img) | |
| assert result.success is False | |
| assert "config" in result.error.lower() | |
| # --------------------------------------------------------------------------- | |
| # Tests EngineResult | |
| # --------------------------------------------------------------------------- | |
| class TestEngineResult: | |
| def test_success_true_when_no_error(self): | |
| r = EngineResult( | |
| engine_name="test", image_path="/img.png", | |
| text="texte", duration_seconds=0.1 | |
| ) | |
| assert r.success is True | |
| def test_success_false_when_error(self): | |
| r = EngineResult( | |
| engine_name="test", image_path="/img.png", | |
| text="", duration_seconds=0.1, error="Erreur" | |
| ) | |
| assert r.success is False | |
| def test_metadata_default_empty(self): | |
| r = EngineResult( | |
| engine_name="test", image_path="/img.png", | |
| text="", duration_seconds=0.0 | |
| ) | |
| assert r.metadata == {} | |