tests/test_jsondb.py: add tests that replace a StoredDict element after its parent has been removed.
What changed, and why it matters
This commit only adds new automated tests to Electrum's test suite. It does not change any production code, so it cannot by itself introduce a security vulnerability or fix one. The tests appear to verify that a specific data-handling edge case behaves correctly: when a parent object is removed from the wallet database, modifying a child object that was previously under it should not generate extra database change records. The commit message links this to issue #10000, but no details of that issue are provided.
No action required for this commit. If reviewing the broader issue #10000, examine related commits that modify electrum/json_db.py to determine whether the underlying behavior was a bug or regression.
Security signals we found
No production code changed
Test-only commit
Tests cover data consistency after removal/mutation of nested StoredDict objects
Evidence from the diff
The diff adds a new TestJsonDB class in tests/test_jsondb.py with three async test cases. Two tests construct a JsonDB from a dict, remove a parent (a.pop(‘b’) or db.data.pop(‘a’)), then mutate a still-held reference to the removed child (b[‘c’] = 42). They assert that pending_changes remains length 1 and that the resulting jsonpatch can still be applied to the original data. The first test checks standard jsonpatch behavior: replacing a path after it has been removed raises JsonPatchException. No library or application code is modified.
Changed components
tests/test_jsondb.pyInspect captured patch +49 / −0
diff --git a/tests/test_jsondb.py b/tests/test_jsondb.py
index 16129e1..eeaf410 100644
--- a/tests/test_jsondb.py
+++ b/tests/test_jsondb.py
@@ -1,6 +1,7 @@
import contextlib
import copy
import traceback
+import json
import jsonpatch
from jsonpatch import JsonPatchException
@@ -8,6 +9,7 @@ from jsonpointer import JsonPointerException
from . import ElectrumTestCase
+from electrum.json_db import JsonDB
class TestJsonpatch(ElectrumTestCase):
@@ -84,3 +86,50 @@ class TestJsonpatch(ElectrumTestCase):
with self._customAssertRaises(JsonPointerException) as ctx:
data2 = jpatch.apply(data1)
fail_if_leaking_secret(ctx)
+
+
+class TestJsonDB(ElectrumTestCase):
+
+ async def test_jsonpatch_replace_after_remove(self):
+ data = { 'a':{} }
+ # op "add"
+ patches = [{"op": "add", "path": "/a/b", "value": "42"}]
+ jpatch = jsonpatch.JsonPatch(patches)
+ data = jpatch.apply(data)
+ # remove
+ patches = [{"op": "remove", "path": "/a/b"}]
+ jpatch = jsonpatch.JsonPatch(patches)
+ data = jpatch.apply(data)
+ # replace
+ patches = [{"op": "replace", "path": "/a/b", "value": "43"}]
+ jpatch = jsonpatch.JsonPatch(patches)
+ with self.assertRaises(JsonPatchException):
+ data = jpatch.apply(data)
+
+ async def test_jsondb_replace_after_remove(self):
+ data = { 'a': {'b': {'c': 0}}}
+ db = JsonDB(repr(data))
+ a = db.get_dict('a')
+ # remove
+ b = a.pop('b')
+ self.assertEqual(len(db.pending_changes), 1)
+ # replace item. this must not been written to db
+ b['c'] = 42
+ self.assertEqual(len(db.pending_changes), 1)
+ patches = json.loads('[' + ','.join(db.pending_changes) + ']')
+ jpatch = jsonpatch.JsonPatch(patches)
+ data = jpatch.apply(data)
+
+ async def test_jsondb_replace_after_remove_nested(self):
+ data = { 'a': {'b':{'c':0}}}
+ db = JsonDB(repr(data))
+ # remove
+ a = db.data.pop('a')
+ self.assertEqual(len(db.pending_changes), 1)
+ b = a['b']
+ # replace item. this must not be written to db
+ b['c'] = 42
+ self.assertEqual(len(db.pending_changes), 1)
+ patches = json.loads('[' + ','.join(db.pending_changes) + ']')
+ jpatch = jsonpatch.JsonPatch(patches)
+ data = jpatch.apply(data)
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.