diff --git a/CHANGELOG.md b/CHANGELOG.md index 10f50e8..3780f63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/). ### Behoben / Fixed - Sichere Literalersetzung in `SearchReplaceBar.replace_all`: Case-insensitive Textersetzung verwendet jetzt einen Callable-Ersatz (`lambda _m: replace_text`) in `re.sub()`. Dies verhindert Abstürze mit `re.error: bad escape \u` bei Pfadangaben mit Backslashes (z. B. Windows-Pfade wie `C:\Users\...`) sowie Fehlinterpretationen von Regex-Gruppenreferenzen und stellt identisches Literal-Verhalten wie bei case-sensitiver Ersetzung sicher. +- Snippet-Import prüft `locked` vor dem Schreiben: `LibraryManager.import_payload` hat bei einem ungültigen `locked`-Wert das Snippet zuerst gespeichert und danach wieder gelöscht. Ein bereits vorhandenes Snippet gleichen Namens ging dabei verloren. Solche Einträge werden jetzt übersprungen, ohne die Bibliothek anzufassen. ## [1.0.1] - 2026-09-18 diff --git a/PythonBox_v8.py b/PythonBox_v8.py index 7095b52..f646bb8 100644 --- a/PythonBox_v8.py +++ b/PythonBox_v8.py @@ -3029,18 +3029,20 @@ def import_payload(self, payload: dict) -> dict: continue snippet_name = snippet_name.strip() + # Erst prüfen, dann schreiben: ein ungültiger "locked"-Wert darf + # kein bereits vorhandenes Snippet überschreiben oder löschen. + try: + is_locked = _coerce_portable_bool(locked) + except ValueError: + skipped += 1 + continue + success, _message = self.save_snippet(topic_name, snippet_name, content) if not success: skipped += 1 continue key = f"{topic_name}/{snippet_name}" - try: - is_locked = _coerce_portable_bool(locked) - except ValueError: - skipped += 1 - self.delete_item(topic_name, snippet_name) - continue if is_locked: self.locked_items.add(key) diff --git a/tests/test_pythonbox_regressions.py b/tests/test_pythonbox_regressions.py index 1b00cfb..48ba599 100644 --- a/tests/test_pythonbox_regressions.py +++ b/tests/test_pythonbox_regressions.py @@ -430,6 +430,31 @@ def test_save_file_as_cancel_keeps_original_path(self): app.processEvents() +class LibraryImportValidationRegressionTests(unittest.TestCase): + def test_import_with_invalid_locked_value_keeps_existing_snippet(self): + module = load_pythonbox_module() + + with tempfile.TemporaryDirectory() as temp_dir: + manager = module.LibraryManager(root=Path(temp_dir) / "library") + manager.save_snippet("Tests", "Alpha", "print('original')\n") + + result = manager.import_payload({ + "schema": module.SNIPPET_EXPORT_SCHEMA, + "topics": [{ + "name": "Tests", + "snippets": [ + {"name": "Alpha", "content": "print('neu')\n", "locked": "vielleicht"}, + {"name": "Beta", "content": "print('beta')\n", "locked": "vielleicht"}, + ], + }], + }) + + self.assertEqual(0, result["imported"]) + self.assertEqual(2, result["skipped"]) + self.assertEqual("print('original')\n", manager.get_content("Tests", "Alpha")) + self.assertEqual("", manager.get_content("Tests", "Beta")) + + class LibraryExportRegressionTests(unittest.TestCase): def test_snippet_library_json_roundtrip_preserves_content_and_locks(self): module = load_pythonbox_module()