diff --git a/changelog.txt b/changelog.txt index 1011734..09cdbf8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,4 @@ +- Bugfix: `get_plugin_root()` löst keine Symlinks/Junctions mehr auf – im per `link_qgis_profile.py` verknüpften Dev-Setup zeigte die Funktion sonst auf den Workspace-Root statt auf den QGIS-Plugin-Ordner, wodurch Assets von Fachplugins (z. B. QML-Stile in sn_verfahrensgebiet/sn_plan41) nicht gefunden wurden (Verfahrensgebiet #50) - Bezeichner-Bereinigung (Layernamen) um Bindestrich ergänzt und in eine zentrale Funktion `sanitize_bezeichner` (`functions/text_wrapper.py`) ausgelagert, damit Umlaute/Sonderzeichen nicht mehr pro Aufrufstelle vergessen werden (#42) - Landkreis-Dropdown (statt Freitext) in den Projekteigenschaften ergänzt, inkl. Landkreiskennzahl als eigene Projektvariable - Landkreisliste liegt jetzt als JSON (sn_basis/data/landkreise.json) statt hartcodiert vor, damit auch andere Module und Nicht-Entwickler sie pflegen können diff --git a/functions/sys_wrapper.py b/functions/sys_wrapper.py index 047f563..587e0ca 100644 --- a/functions/sys_wrapper.py +++ b/functions/sys_wrapper.py @@ -18,8 +18,15 @@ _PathLike = Union[str, Path] def get_plugin_root() -> Path: """ Liefert das Basisverzeichnis des Plugins. + + Löst bewusst keine Symlinks/Junctions auf (``.absolute()`` statt + ``.resolve()``): ``link_qgis_profile.py`` verknüpft die Klone im + QGIS-Profil per Junction unter ihrem Paketnamen. Würde hier aufgelöst, + landete man beim Workspace-Root (wo die Klone unter ihrem Repo-Namen + liegen) statt beim QGIS-Plugin-Ordner – Assets von Fachplugins wären dann + nicht mehr auffindbar (Verfahrensgebiet #50). """ - return Path(__file__).resolve().parents[2] + return Path(__file__).absolute().parents[2] # --------------------------------------------------------- diff --git a/tests/test_sys_wrapper.py b/tests/test_sys_wrapper.py new file mode 100644 index 0000000..3edd962 --- /dev/null +++ b/tests/test_sys_wrapper.py @@ -0,0 +1,56 @@ +# sn_basis/test/test_sys_wrapper.py +"""sn_basis/tests/test_sys_wrapper.py + +Unit-Tests für sn_basis.functions.sys_wrapper.get_plugin_root(). +""" + +import os +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch + +from sn_basis.functions import sys_wrapper + + +def _verknuepfung_anlegen(ziel: Path, quelle: Path) -> None: + """Legt eine Verzeichnisverknüpfung an (Symlink, unter Windows notfalls Junction). + + Entspricht exakt der Logik in scripts/link_qgis_profile.py, mit der Klone + ins QGIS-Profil verknüpft werden (dort ohne Admin-Rechte per Junction). + """ + try: + os.symlink(quelle, ziel, target_is_directory=True) + except (OSError, NotImplementedError): + if os.name == "nt": + import _winapi + + _winapi.CreateJunction(str(quelle), str(ziel)) + else: + raise + + +class TestGetPluginRoot(unittest.TestCase): + + def test_get_plugin_root_folgt_keiner_verzeichnisverknuepfung(self): + """Prüft, dass get_plugin_root() bei einem über eine Verzeichnisverknüpfung (wie von link_qgis_profile.py angelegt) erreichten Modul den verlinkten Pfad liefert, statt zum echten Zielverzeichnis aufzulösen (Verfahrensgebiet #50).""" + with TemporaryDirectory() as tmp: + tmp_path = Path(tmp) + echtes_ziel = tmp_path / "echt" / "sn_basis" / "functions" + echtes_ziel.mkdir(parents=True) + + verlinkter_ordner = tmp_path / "verlinkt" + verlinkter_ordner.mkdir() + verlinkte_sn_basis = verlinkter_ordner / "sn_basis" + _verknuepfung_anlegen(verlinkte_sn_basis, echtes_ziel.parent) + + fake_file = verlinkte_sn_basis / "functions" / "sys_wrapper.py" + + with patch.object(sys_wrapper, "__file__", str(fake_file)): + ergebnis = sys_wrapper.get_plugin_root() + + self.assertEqual(ergebnis, verlinkter_ordner) + + +if __name__ == "__main__": + unittest.main()