diff --git a/docs/tests/testkatalog.html b/docs/tests/testkatalog.html
index 910af04..1ade333 100644
--- a/docs/tests/testkatalog.html
+++ b/docs/tests/testkatalog.html
@@ -125,22 +125,22 @@
Testkatalog der sn_*-Plugins — was die Suite heute prüft, auf einen Blick.
Prüft, dass _aggregate_results ohne Zeilen ok=False mit Aktion 'keine_validen_eintraege' liefert.
grün
nie geprüft
— (Docstring fehlt)
TestDateipruefer 17
flowchart TD
root(["test_dateipruefer.py"])
class root rootnode
- root --> g1["TestDateipruefer (12)"]
+ root --> g1["TestDateipruefer (17)"]
class g1 groupnode
g1 --> t1("leereingabe_erlaubt")
g1 --> t2("leereingabe_nicht_erlaubt")
@@ -225,6 +225,11 @@
g1 --> t10("ist_gueltiger_gpkg_pfad_akzeptiert_vorhandenes_verzeichnis")
g1 --> t11("ist_gueltiger_gpkg_pfad_lehnt_falsche_endung_ab")
g1 --> t12("ist_gueltiger_gpkg_pfad_lehnt_fehlendes_verzeichnis_ab")
+ g1 --> t13("ist_leer_erkennt_none_und_nicht_string_als_leer")
+ g1 --> t14("ist_leer_erkennt_reinen_whitespace_als_leer")
+ g1 --> t15("ist_leer_meldet_pfad_mit_sichtbaren_zeichen_nicht_als_leer")
+ g1 --> t16("leere_eingabe_bevorzugt_leereingabe_erlaubt_vor_standarddatei")
+ g1 --> t17("leere_eingabe_bevorzugt_standarddatei_vor_temporaer_erlaubt")
class t1 ok
class t2 ok
class t3 ok
@@ -237,11 +242,16 @@
class t10 ok
class t11 ok
class t12 ok
+ class t13 ok
+ class t14 ok
+ class t15 ok
+ class t16 ok
+ class t17 ok
classDef rootnode fill:#1f4038,stroke:#16302a,stroke-width:1px,color:#f4f1e8,font-weight:600;
classDef groupnode fill:#cfe0da,stroke:#5c8a7d,stroke-width:1px,color:#152420;
classDef ok fill:#dcefdc,stroke:#3f7d4a,stroke-width:1px,color:#1c3a20;
classDef warn fill:#f6ead9,stroke:#b5722a,stroke-width:1px,color:#4a2c08;
- classDef skip fill:#e6e6ee,stroke:#6f7285,stroke-width:1px,color:#2b2d38;
Testfall
prüft
Status
geprüft
TestDateipruefer test_leereingabe_erlaubt
Prüft, dass eine leere Pfadangabe bei leereingabe_erlaubt=True als 'leereingabe_erlaubt' ohne Kontext gemeldet wird.
grün
geprüft am 2026-09-17
TestDateipruefer test_leereingabe_nicht_erlaubt
Prüft, dass eine leere Pfadangabe ohne leereingabe_erlaubt als 'leereingabe_nicht_erlaubt' abgelehnt wird.
grün
geprüft am 2026-09-17
TestDateipruefer test_standarddatei_vorschlagen
Prüft, dass bei leerer Pfadangabe mit gesetzter Standarddatei 'standarddatei_vorschlagen' mit dem Standardpfad als Kontext geliefert wird.
grün
nie geprüft
TestDateipruefer test_temporaer_erlaubt
Prüft, dass eine leere Pfadangabe bei temporaer_erlaubt=True als 'temporaer_erlaubt' ohne Kontext gemeldet wird.
grün
geprüft am 2026-09-17
TestDateipruefer test_datei_nicht_gefunden
Prüft, dass ein nicht existierender Dateipfad als 'datei_nicht_gefunden' mit dem Pfad als Kontext abgelehnt wird.
grün
geprüft am 2026-09-17
TestDateipruefer test_datei_ok
Prüft, dass ein existierender Dateipfad als 'ok' mit dem Pfad als Kontext bestätigt wird.
flowchart TD
root(["test_datenschreiber.py"])
class root rootnode
root --> g1["WriteLayerToGpkgTests (2)"]
@@ -331,6 +341,12 @@
class g5 groupnode
g5 --> t13("ohne_speicherort_wirft_valueerror")
g5 --> t14("schreibt_json_datei_mit_ergebnissen_und_gibt_pfad_zurueck")
+ root --> g6["StoreStyleInGpkgTests (4)"]
+ class g6 groupnode
+ g6 --> t15("legt_tabelle_an_und_speichert_stil_als_default")
+ g6 --> t16("setzt_bisherigen_default_stil_desselben_layers_zurueck")
+ g6 --> t17("liest_geometriespalte_ueber_geometrycolumn_aus")
+ g6 --> t18("fehlender_stilpfad_meldet_fehler_ueber_pruefmanager_statt_absturz")
class t1 ok
class t2 ok
class t3 ok
@@ -345,11 +361,15 @@
class t12 ok
class t13 ok
class t14 ok
+ class t15 ok
+ class t16 ok
+ class t17 ok
+ class t18 ok
classDef rootnode fill:#1f4038,stroke:#16302a,stroke-width:1px,color:#f4f1e8,font-weight:600;
classDef groupnode fill:#cfe0da,stroke:#5c8a7d,stroke-width:1px,color:#152420;
classDef ok fill:#dcefdc,stroke:#3f7d4a,stroke-width:1px,color:#1c3a20;
classDef warn fill:#f6ead9,stroke:#b5722a,stroke-width:1px,color:#4a2c08;
- classDef skip fill:#e6e6ee,stroke:#6f7285,stroke-width:1px,color:#2b2d38;
Prüft, dass teardown_verfahrensgebiet_listener alle Signale trennt und mehrfacher Aufruf nicht fehlschlägt.
grün
nie geprüft
-
Heuristik: Name kommt in keiner Testdatei des Pakets vor (Details siehe docs/tests/testkatalog.md). beabsichtigt liest den Docstring der Funktion – ein Vermerk wie „von Test abgesehen, weil …“ oder „Test ausgesetzt, zunächst Refactor“ gilt als bewusste Entscheidung. ungeklärt hat weder Test noch Vermerk.
Heuristik: Name kommt in keiner Testdatei des Pakets vor (Details siehe docs/tests/testkatalog.md). beabsichtigt liest den Docstring der Funktion – ein Vermerk wie „von Test abgesehen, weil …“ oder „Test ausgesetzt, zunächst Refactor“ gilt als bewusste Entscheidung. ungeklärt hat weder Test noch Vermerk.
diff --git a/docs/tests/testkatalog.md b/docs/tests/testkatalog.md
index eeaf1f6..e451fb4 100644
--- a/docs/tests/testkatalog.md
+++ b/docs/tests/testkatalog.md
@@ -10,19 +10,19 @@ selbst (Docstrings) und aus der Testbaseline.
Stand der Baseline: `docs/status/testbaseline.json` (SHA-256 `aeabc946a7f3`)
Stand des Prüf-Audits: `docs/status/testpruefungen.json` (SHA-256 `68b590c8feef`)
-Gesamt: 3 Pakete, 49 Testdateien, 569 Testfälle – 526 grün, 35 rot (Baseline), 8 übersprungen.
-Mit Beschreibung (Docstring): 424 von 569 (75 %).
-Fachlich geprüft: 55 von 569 (10 %) aktuell, 0 veraltet seit Codeänderung, 514 nie geprüft.
-Strukturelle Lücken (heuristisch, Namensabgleich): 149 von 413 (36 %) Funktionen/Methoden ohne erkennbaren Testbezug – Abschnitt „Ungetestete Funktionen“.
+Gesamt: 3 Pakete, 49 Testdateien, 580 Testfälle – 537 grün, 35 rot (Baseline), 8 übersprungen.
+Mit Beschreibung (Docstring): 435 von 580 (75 %).
+Fachlich geprüft: 55 von 580 (9 %) aktuell, 0 veraltet seit Codeänderung, 525 nie geprüft.
+Strukturelle Lücken (heuristisch, Namensabgleich): 145 von 413 (35 %) Funktionen/Methoden ohne erkennbaren Testbezug – Abschnitt „Ungetestete Funktionen“.
## Zusammenfassung
| Paket | Testdateien | Testfälle | grün | rot (Baseline) | übersprungen | mit Beschreibung | fachlich geprüft | ohne Testbezug |
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
-| `sn_basis` | 32 | 317 | 317 | 0 | 0 | 317 von 317 (100 %) | 55 von 317 (17 %) | 76 von 229 (33 %) |
+| `sn_basis` | 32 | 328 | 328 | 0 | 0 | 328 von 328 (100 %) | 55 von 328 (17 %) | 72 von 229 (31 %) |
| `sn_verfahrensgebiet` | 12 | 208 | 166 | 34 | 8 | 106 von 208 (51 %) | 0 von 208 (0 %) | 34 von 104 (33 %) |
| `sn_plan41` | 5 | 44 | 43 | 1 | 0 | 1 von 44 (2 %) | 0 von 44 (0 %) | 39 von 80 (49 %) |
-| **Summe** | **49** | **569** | **526** | **35** | **8** | **424 von 569 (75 %)** | **55 von 569 (10 %)** | **149 von 413 (36 %)** |
+| **Summe** | **49** | **580** | **537** | **35** | **8** | **435 von 580 (75 %)** | **55 von 580 (9 %)** | **145 von 413 (35 %)** |
### Legende
@@ -36,7 +36,7 @@ Strukturelle Lücken (heuristisch, Namensabgleich): 149 von 413 (36 %) Funktione
## Paket `sn_basis`
-Plattform-Plugin, Ordner `Plugin_SN_Basis` – 32 Testdateien mit 317 Testfällen (317 grün, 0 rot, 0 übersprungen).
+Plattform-Plugin, Ordner `Plugin_SN_Basis` – 32 Testdateien mit 328 Testfällen (328 grün, 0 rot, 0 übersprungen).
### `Plugin_SN_Basis/tests/test_base_dockwidget.py`
@@ -85,7 +85,7 @@ Worum es in der Datei geht: — (Docstring fehlt)
Testgruppen:
-- `TestDateipruefer` (12 Testfälle): — (Docstring fehlt)
+- `TestDateipruefer` (17 Testfälle): — (Docstring fehlt)
| Testfall | prüft | Status | geprüft |
| --- | --- | --- | --- |
@@ -101,6 +101,11 @@ Testgruppen:
| `TestDateipruefer.test_ist_gueltiger_gpkg_pfad_akzeptiert_vorhandenes_verzeichnis` | Prüft, dass ein .gpkg-Pfad mit vorhandenem Zielverzeichnis als gültig gilt. | grün | nie geprüft |
| `TestDateipruefer.test_ist_gueltiger_gpkg_pfad_lehnt_falsche_endung_ab` | Prüft, dass ein Pfad ohne .gpkg-Endung als ungültig gilt, auch bei vorhandenem Verzeichnis. | grün | nie geprüft |
| `TestDateipruefer.test_ist_gueltiger_gpkg_pfad_lehnt_fehlendes_verzeichnis_ab` | Prüft, dass ein .gpkg-Pfad in einem nicht vorhandenen Verzeichnis als ungültig gilt. | grün | nie geprüft |
+| `TestDateipruefer.test_ist_leer_erkennt_none_und_nicht_string_als_leer` | Prüft, dass _ist_leer sowohl None als auch einen Nicht-String-Wert als leer meldet. | grün | nie geprüft |
+| `TestDateipruefer.test_ist_leer_erkennt_reinen_whitespace_als_leer` | Prüft, dass _ist_leer eine Eingabe aus reinen Leerzeichen als leer meldet. | grün | nie geprüft |
+| `TestDateipruefer.test_ist_leer_meldet_pfad_mit_sichtbaren_zeichen_nicht_als_leer` | Prüft, dass _ist_leer einen Pfad mit sichtbaren Zeichen nicht als leer meldet. | grün | nie geprüft |
+| `TestDateipruefer.test_leere_eingabe_bevorzugt_leereingabe_erlaubt_vor_standarddatei` | Prüft, dass bei leerer Eingabe leereingabe_erlaubt Vorrang vor einer gesetzten Standarddatei hat. | grün | nie geprüft |
+| `TestDateipruefer.test_leere_eingabe_bevorzugt_standarddatei_vor_temporaer_erlaubt` | Prüft, dass bei leerer Eingabe eine gesetzte Standarddatei Vorrang vor temporaer_erlaubt hat. | grün | nie geprüft |
### `Plugin_SN_Basis/tests/test_datenabruf.py`
@@ -151,6 +156,7 @@ Testgruppen:
- `SchreibeDatenTests` (4 Testfälle): — (Docstring fehlt)
- `LadeLayerTests` (3 Testfälle): — (Docstring fehlt)
- `SchreibeLogTests` (2 Testfälle): — (Docstring fehlt)
+- `StoreStyleInGpkgTests` (4 Testfälle): Tests für Datenschreiber._store_style_in_gpkg (Issue #70, nachgezogen aus #63).
| Testfall | prüft | Status | geprüft |
| --- | --- | --- | --- |
@@ -168,6 +174,10 @@ Testgruppen:
| `LadeLayerTests.test_ungueltiger_layer_meldet_layer_nicht_gefunden` | Prüft, dass ein ungültig ladbarer Layer als 'layer_nicht_gefunden' gemeldet wird. | grün | nie geprüft |
| `SchreibeLogTests.test_ohne_speicherort_wirft_valueerror` | Prüft, dass schreibe_log ohne Speicherort einen ValueError wirft. | grün | nie geprüft |
| `SchreibeLogTests.test_schreibt_json_datei_mit_ergebnissen_und_gibt_pfad_zurueck` | Prüft, dass schreibe_log eine lesbare JSON-Log-Datei erzeugt und deren Pfad zurückgibt. | grün | nie geprüft |
+| `StoreStyleInGpkgTests.test_legt_tabelle_an_und_speichert_stil_als_default` | Prüft, dass _store_style_in_gpkg die layer_styles-Tabelle anlegt und den Stil als Default speichert. | grün | nie geprüft |
+| `StoreStyleInGpkgTests.test_setzt_bisherigen_default_stil_desselben_layers_zurueck` | Prüft, dass _store_style_in_gpkg den useAsDefault-Wert vorhandener Stile desselben Layers zurücksetzt. | grün | nie geprüft |
+| `StoreStyleInGpkgTests.test_liest_geometriespalte_ueber_geometrycolumn_aus` | Prüft, dass _store_style_in_gpkg die Geometriespalte eines Layers mit geometryColumn() übernimmt. | grün | nie geprüft |
+| `StoreStyleInGpkgTests.test_fehlender_stilpfad_meldet_fehler_ueber_pruefmanager_statt_absturz` | Prüft, dass ein nicht lesbarer Stilpfad über den Pruefmanager gemeldet wird statt eine Exception zu werfen. | grün | nie geprüft |
### `Plugin_SN_Basis/tests/test_dockmanager.py`
@@ -536,7 +546,7 @@ Worum es in der Datei geht: — (Docstring fehlt)
Testgruppen:
-- `TestPruefmanager` (24 Testfälle): — (Docstring fehlt)
+- `TestPruefmanager` (26 Testfälle): — (Docstring fehlt)
| Testfall | prüft | Status | geprüft |
| --- | --- | --- | --- |
@@ -556,6 +566,8 @@ Testgruppen:
| `TestPruefmanager.test_datei_existiert_in_non_qgis_mode_returns_input` | Prüft, dass außerhalb des QGIS-Modus bei bestehender Datei keine Rückfrage erfolgt, sondern die ursprüngliche Aktion unentschieden zurückgegeben wird. | grün | geprüft am 2026-09-17 |
| `TestPruefmanager.test_verfahrensgebiet_layer_existiert_ergaenzen` | Prüft, dass die Nutzerwahl 'ergänzen' bei bestehendem Verfahrensgebiet zur Aktion 'verfahrensgebiet_ergaenzen' führt. | grün | nie geprüft |
| `TestPruefmanager.test_verfahrensgebiet_layer_existiert_abbrechen` | Prüft, dass die Nutzerwahl 'abbrechen' bei bestehendem Verfahrensgebiet ok=False mit Aktion 'verfahrensgebiet_abbrechen' liefert. | grün | nie geprüft |
+| `TestPruefmanager.test_verfahrensgebiet_layer_existiert_entfernen` | Prüft, dass die Nutzerwahl 'entfernen' bei bestehendem Verfahrensgebiet zur Aktion 'verfahrensgebiet_entfernen' führt. | grün | nie geprüft |
+| `TestPruefmanager.test_verfahrensgebiet_layer_existiert_ersetzen` | Prüft, dass die Nutzerwahl 'ersetzen' bei bestehendem Verfahrensgebiet zur Aktion 'verfahrensgebiet_ersetzen' führt. | grün | nie geprüft |
| `TestPruefmanager.test_verfahrensgebiet_layer_existiert_in_non_qgis_mode_returns_input` | Prüft, dass außerhalb des QGIS-Modus bei bestehendem Verfahrensgebiet keine Rückfrage erfolgt. | grün | nie geprüft |
| `TestPruefmanager.test_report_error_kritische_aktion_ruft_error_auf` | Prüft, dass eine kritische Aktion (z. B. 'netzwerkfehler') über error() gemeldet wird, nicht über warning(). | grün | nie geprüft |
| `TestPruefmanager.test_report_error_warn_aktion_ruft_warning_auf` | Prüft, dass eine Warn-Aktion (z. B. 'datei_nicht_gefunden') über warning() gemeldet wird, nicht über error(). | grün | nie geprüft |
@@ -1236,7 +1248,7 @@ Arbeitsliste: Diese Testfälle haben keinen Docstring; was sie fachlich prüfen,
aus dem Quelltext erschließen. Jeder Eintrag braucht eine einzeilige Beschreibung im Testfall,
dann verschwindet er hier von selbst.
-### `sn_basis` – 0 von 317 Testfällen ohne Beschreibung
+### `sn_basis` – 0 von 328 Testfällen ohne Beschreibung
Keine Lücken.
@@ -1442,7 +1454,7 @@ Keine Lücken.
Heuristik, kein Beweis: Diese Funktionen und Methoden kommen unter ihrem Namen in keiner Testdatei des Pakets vor. Das schließt nicht aus, dass sie indirekt über eine andere, getestete Funktion mitlaufen – umgekehrt gilt: Taucht der Name irgendwo in einer Testdatei auf, gilt die Funktion schon als referenziert, auch bei zufälliger Namensgleichheit mit etwas anderem. `main.py`/`__init__.py` (QGIS-Einstiegspunkte wie `classFactory`, `initGui`, `unload`) und Dunder-Methoden sind ausgenommen. Diese Liste ersetzt keine fachliche Prüfung, ob ein Test sinnvoll wäre – dafür braucht es einen Menschen, der die Funktion kennt.
„beabsichtigt“ liest den Docstring der Funktion: Enthält er einen Vermerk wie „von Test abgesehen, weil …“ oder „Test ausgesetzt, zunächst Refactor“, gilt der fehlende Test als bewusste Entscheidung. Alles andere ist „ungeklärt“ – die eigentliche Arbeitsliste.
-### `sn_basis` – 76 von 229 (33 %) ohne Testbezug (9 ungeklärt, 67 beabsichtigt)
+### `sn_basis` – 72 von 229 (31 %) ohne Testbezug (0 ungeklärt, 72 beabsichtigt)
#### `functions/dialog_wrapper.py` (9)
@@ -1484,15 +1496,6 @@ Heuristik, kein Beweis: Diese Funktionen und Methoden kommen unter ihrem Namen i
- `DataGrabber._process_database_source` (Zeile 312) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `DataGrabber._process_service_source` (Zeile 324) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
-#### `modules/Dateipruefer.py` (2)
-
-- `Dateipruefer._ist_leer` (Zeile 98) — ungeklärt
-- `Dateipruefer._handle_leere_eingabe` (Zeile 227) — ungeklärt
-
-#### `modules/Datenschreiber.py` (1)
-
-- `Datenschreiber._store_style_in_gpkg` (Zeile 77) — ungeklärt
-
#### `modules/LayerLoader.py` (2)
- `LayerLoader._was_canceled` (Zeile 42) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
@@ -1500,9 +1503,9 @@ Heuristik, kein Beweis: Diese Funktionen und Methoden kommen unter ihrem Namen i
#### `modules/linkpruefer.py` (3)
-- `Linkpruefer._ist_url` (Zeile 41) — ungeklärt
-- `Linkpruefer._pruefe_url` (Zeile 81) — ungeklärt
-- `Linkpruefer._pruefe_dateipfad` (Zeile 115) — ungeklärt
+- `Linkpruefer._ist_url` (Zeile 41) — **beabsichtigt:** von Test abgesehen, weil bereits über pruefe() in test_linkpruefer.py indirekt
+- `Linkpruefer._pruefe_url` (Zeile 84) — **beabsichtigt:** von Test abgesehen, weil bereits über pruefe() in test_linkpruefer.py vollständig
+- `Linkpruefer._pruefe_dateipfad` (Zeile 121) — **beabsichtigt:** von Test abgesehen, weil bereits über pruefe() in test_linkpruefer.py vollständig
#### `modules/print_layout.py` (11)
@@ -1547,13 +1550,12 @@ Heuristik, kein Beweis: Diese Funktionen und Methoden kommen unter ihrem Namen i
- `PrintLogic._get_plot_dimensions_mm` (Zeile 1078) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
- `PrintLogic._get_required_map_dimensions_mm` (Zeile 1088) — **beabsichtigt:** Test ausgesetzt, zunächst Refactor
-#### `modules/Pruefmanager.py` (5)
+#### `modules/Pruefmanager.py` (4)
- `Pruefmanager.report_summary` (Zeile 45) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
- `Pruefmanager.zeige_hinweis` (Zeile 63) — **beabsichtigt:** von Test abgesehen, weil vernachlässigbar; HD 18.09.2026
-- `Pruefmanager._handle_datei_existiert` (Zeile 87) — ungeklärt
-- `Pruefmanager._handle_verfahrensgebiet_layer_existiert` (Zeile 234) — ungeklärt
-- `Pruefmanager._ask_use_or_replace_pufferlayer` (Zeile 277) — ungeklärt
+- `Pruefmanager._handle_datei_existiert` (Zeile 87) — **beabsichtigt:** von Test abgesehen, weil bereits über verarbeite() in test_pruefmanager.py vollständig
+- `Pruefmanager._ask_use_or_replace_pufferlayer` (Zeile 280) — **beabsichtigt:** von Test abgesehen, weil toter Code: referenziert self.pruefmanager, das auf
#### `ui/tabs/print_tab.py` (7)
diff --git a/docs/wissen/fallstricke.md b/docs/wissen/fallstricke.md
index 6f69896..a859a69 100644
--- a/docs/wissen/fallstricke.md
+++ b/docs/wissen/fallstricke.md
@@ -8,6 +8,13 @@ Kurz, konkret, mit Datum und Quelle. Neue Einträge oben in der passenden Rubrik
- **QGIS 3.40 ist seit Januar 2026 End of Life.** Aktueller LTR ist 3.44, Stable 4.2 (wird 25.09.2026 LTR). `workspace.toml` und alle `metadata.template` stehen noch auf `3.40` – offene Produktentscheidung, siehe `docs/konzept/07_qgis_tests_plan.md` Abschnitt 2. (04.09.2026, QGIS-Release-Fahrplan)
- **Zwei Qt-Welten.** QGIS 3.40/3.44 laufen mit Qt5/PyQt5, QGIS 4 mit Qt6/PyQt6. `qt_wrapper` lädt PyQt6 zuerst, dann PyQt5, sonst Mock. Enum-Zugriffe unterscheiden sich (`Qt.AlignmentFlag.AlignTop` statt `Qt.AlignTop`), deshalb nur Wrapper-Aliase verwenden. (09/2026, Code)
- **Mock-Modus ist kein QGIS.** `python scripts/run_tests.py` läuft in Sekunden, prüft aber nur Logik gegen Mock-Klassen. Geometrieoperationen, WFS, Layouts und Signale müssen in QGIS geprüft werden (`tests/test_qgis.bat` unter Windows). Manuelle QGIS-Tests im PR dokumentieren. (09/2026)
+- **`sqlite3.connect()` in einem `with`-Block schließt die Verbindung nicht.** Das `with`-Statement committet
+ bzw. rollt nur zurück, `conn` bleibt offen (siehe `Datenschreiber._store_style_in_gpkg`). Unter Windows
+ sperrt das die Datei, bis die Verbindung per Garbage Collection oder explizitem `close()` freigegeben wird –
+ ein Test, der danach dasselbe `tempfile.TemporaryDirectory()` verlassen will, bricht mit
+ `PermissionError: [WinError 32]` ab. Betroffene Produktionsfunktion nicht nebenbei umschreiben (kein
+ Refactoring im Testticket); stattdessen im Test `tempfile.mkdtemp()` + `try/finally` mit `gc.collect()`
+ vor `shutil.rmtree(..., ignore_errors=True)` verwenden. (21.09.2026, Issue Basis #70)
- **`_MockQgsProject` war unvollständig:** `transformContext()` und `addMapLayer()` fehlten, wodurch
`Datenschreiber._write_layer_to_gpkg` und `LayerLoader.add_to_project` im Mock-Modus für **jeden** gültigen
Layer eine Ausnahme geworfen bzw. eine Fehlermeldung erzeugt hätten – nicht durch einen Fehler in diesen