"""Tests for pipeline orchestration.""" import pytest from pathlib import Path class TestVizSteps: def test_viz_steps_not_empty(self): from lidar_pipeline.pipeline import VIZ_STEPS assert len(VIZ_STEPS) > 0 def test_viz_steps_have_callable_functions(self): from lidar_pipeline.pipeline import VIZ_STEPS for name, func in VIZ_STEPS: assert callable(func), f"VIZ_STEPS entry '{name}' is not callable" def test_viz_steps_names_unique(self): from lidar_pipeline.pipeline import VIZ_STEPS names = [name for name, _ in VIZ_STEPS] assert len(names) == len(set(names)), "VIZ_STEPS has duplicate names" def test_expected_visualization_count(self): """Should have 16 visualizations (14 terrain + ortho + topo).""" from lidar_pipeline.pipeline import VIZ_STEPS assert len(VIZ_STEPS) == 16 def test_ortho_and_topo_present(self): from lidar_pipeline.pipeline import VIZ_STEPS names = [name for name, _ in VIZ_STEPS] assert "ortho" in names assert "topo" in names class TestLidarArchaeoPipeline: def test_init_creates_dirs(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline input_dir = tmp_path / "input" input_dir.mkdir() output_dir = tmp_path / "output" pipeline = LidarArchaeoPipeline(str(input_dir), str(output_dir)) assert (tmp_path / "output").exists() assert (tmp_path / "output" / "DTM").exists() assert (tmp_path / "output" / "visualisations").exists() assert (tmp_path / "output" / "temp").exists() def test_init_raises_on_missing_input(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline with pytest.raises(ValueError, match="introuvable"): LidarArchaeoPipeline("/nonexistent/path", str(tmp_path / "output")) def test_find_laz_files_empty(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline input_dir = tmp_path / "input" input_dir.mkdir() pipeline = LidarArchaeoPipeline(str(input_dir), str(tmp_path / "output")) files = pipeline.find_laz_files() assert files == [] def test_find_laz_files(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline input_dir = tmp_path / "input" input_dir.mkdir() (input_dir / "test.laz").touch() (input_dir / "other.las").touch() (input_dir / "readme.txt").touch() pipeline = LidarArchaeoPipeline(str(input_dir), str(tmp_path / "output")) files = pipeline.find_laz_files() names = [f.name for f in files] assert "test.laz" in names assert "other.las" in names assert "readme.txt" not in names class TestDtmMethodSidecar: """Méthode de classification enregistrée à côté du DTM (invalidation du cache).""" def test_missing_sidecar_matches(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline input_dir = tmp_path / "input" input_dir.mkdir() pipeline = LidarArchaeoPipeline(str(input_dir), str(tmp_path / "output"), ground_method='csf') # Aucun sidecar écrit → cache conservé (considéré compatible). assert pipeline._dtm_method_matches("tileA", "") is True def test_matching_method(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline input_dir = tmp_path / "input" input_dir.mkdir() pipeline = LidarArchaeoPipeline(str(input_dir), str(tmp_path / "output"), ground_method='csf') pipeline._write_dtm_method("tileA", "") assert pipeline._dtm_method_matches("tileA", "") is True def test_different_method_invalidates_cache(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline input_dir = tmp_path / "input" input_dir.mkdir() out = str(tmp_path / "output") LidarArchaeoPipeline(str(input_dir), out, ground_method='ign')._write_dtm_method("tileA", "") csf = LidarArchaeoPipeline(str(input_dir), out, ground_method='csf') assert csf._dtm_method_matches("tileA", "") is False def test_write_dtm_method(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline input_dir = tmp_path / "input" input_dir.mkdir() pipeline = LidarArchaeoPipeline(str(input_dir), str(tmp_path / "output"), ground_method='smrf') pipeline._write_dtm_method("tileA", "_r0p2") sidecar = tmp_path / "output" / "DTM" / "tileA_dtm_r0p2_method.txt" assert sidecar.exists() assert sidecar.read_text(encoding="utf-8").strip() == "smrf" assert pipeline._dtm_method_name("tileA", "_r0p2") == "smrf" # Le sidecar est un fichier .txt : il ne gêne pas la recherche des DTM .tif. dtm = tmp_path / "output" / "DTM" / "tileA_dtm_r0p2.tif" dtm.touch() assert [p.name for p in (tmp_path / "output" / "DTM").glob("*.tif")] == ["tileA_dtm_r0p2.tif"] def test_force_images_regenerates_existing(self, tmp_path): from lidar_pipeline.pipeline import LidarArchaeoPipeline input_dir = tmp_path / "input" input_dir.mkdir() pipeline = LidarArchaeoPipeline(str(input_dir), str(tmp_path / "output"), output_format='avif') calls = [] def fake_ortho(dem_file, basename, vis_dir, resolution): calls.append(basename) return vis_dir / f"{basename}_ortho.avif" pipeline.viz_steps = [('ortho', fake_ortho)] vis_dir = tmp_path / "output" / "visualisations" / "tileA" vis_dir.mkdir(parents=True) (vis_dir / "tileA_ortho.avif").touch() dtm = tmp_path / "dtm.tif" # Image existante, pas de force → ignorée (pas de régénération). pipeline.generate_all_visualizations(dtm, "tileA", resolution=0.5, vis_dir=vis_dir, force_images=False) assert calls == [] # Image existante, force_images=True → régénérée. calls.clear() pipeline.generate_all_visualizations(dtm, "tileA", resolution=0.5, vis_dir=vis_dir, force_images=True) assert calls == ["tileA"] class TestEffectiveGroundMethod: def test_ign_label_encodes_classes(self): """Les classes IGN sont encodées dans l'étiquette de cache (reclassification).""" from lidar_pipeline.pipeline import LidarArchaeoPipeline import tempfile with tempfile.TemporaryDirectory() as tmpdir: p = LidarArchaeoPipeline(tmpdir, tmpdir, ground_method='ign', ign_classes="sol,unclassified") assert p._effective_ground_method() == "ign_1_2" def test_ign_default_label(self): from lidar_pipeline.pipeline import LidarArchaeoPipeline import tempfile with tempfile.TemporaryDirectory() as tmpdir: p = LidarArchaeoPipeline(tmpdir, tmpdir, ground_method='ign') assert p._effective_ground_method() == "ign" def test_other_methods_unchanged(self): from lidar_pipeline.pipeline import LidarArchaeoPipeline import tempfile with tempfile.TemporaryDirectory() as tmpdir: p = LidarArchaeoPipeline(tmpdir, tmpdir, ground_method='smrf', ign_classes="sol,unclassified") assert p._effective_ground_method() == "smrf"