fix(pairing): treat null approved/pending maps as empty

This commit is contained in:
santhreal
2026-07-27 00:46:40 +08:00
committed by Xubin Ren
parent cf6ca13b6d
commit f7bf4c972e
2 changed files with 54 additions and 5 deletions
+30 -5
View File
@@ -44,8 +44,18 @@ def _load() -> dict[str, Any]:
logger.warning("Corrupted pairing store, resetting") logger.warning("Corrupted pairing store, resetting")
return {"approved": {}, "pending": {}} return {"approved": {}, "pending": {}}
# JSON stores may contain null maps after partial edits; treat like {}.
approved = data.get("approved") or {}
if not isinstance(approved, dict):
approved = {}
data["approved"] = approved
pending = data.get("pending") or {}
if not isinstance(pending, dict):
pending = {}
data["pending"] = pending
# Convert approved lists to str sets for O(1) lookup. # Convert approved lists to str sets for O(1) lookup.
for channel, users in data.get("approved", {}).items(): for channel, users in approved.items():
if not isinstance(users, list): if not isinstance(users, list):
users = [] users = []
data["approved"][channel] = {str(u) for u in users} data["approved"][channel] = {str(u) for u in users}
@@ -56,9 +66,15 @@ def _save(data: dict[str, Any]) -> None:
path = _store_path() path = _store_path()
path.parent.mkdir(parents=True, exist_ok=True) path.parent.mkdir(parents=True, exist_ok=True)
# Convert sets back to lists for JSON serialization # Convert sets back to lists for JSON serialization
approved = data.get("approved") or {}
pending = data.get("pending") or {}
if not isinstance(approved, dict):
approved = {}
if not isinstance(pending, dict):
pending = {}
payload = { payload = {
"approved": {ch: sorted(list(users)) for ch, users in data.get("approved", {}).items()}, "approved": {ch: sorted(list(users)) for ch, users in approved.items()},
"pending": dict(data.get("pending", {})), "pending": dict(pending),
} }
_write_text_atomic(path, json.dumps(payload, indent=2, ensure_ascii=False)) _write_text_atomic(path, json.dumps(payload, indent=2, ensure_ascii=False))
@@ -66,10 +82,18 @@ def _save(data: dict[str, Any]) -> None:
def _gc_pending(data: dict[str, Any]) -> None: def _gc_pending(data: dict[str, Any]) -> None:
"""Remove expired pending entries in-place.""" """Remove expired pending entries in-place."""
now = time.time() now = time.time()
pending: dict[str, Any] = data.get("pending", {}) pending: dict[str, Any] = data.get("pending") or {}
expired = [code for code, info in pending.items() if info.get("expires_at", 0) < now] if not isinstance(pending, dict):
data["pending"] = {}
return
expired = [
code
for code, info in pending.items()
if not isinstance(info, dict) or info.get("expires_at", 0) < now
]
for code in expired: for code in expired:
del pending[code] del pending[code]
data["pending"] = pending
def generate_code( def generate_code(
@@ -152,6 +176,7 @@ def list_pending() -> list[dict[str, Any]]:
return [ return [
{"code": code, **info} {"code": code, **info}
for code, info in data.get("pending", {}).items() for code, info in data.get("pending", {}).items()
if isinstance(info, dict)
] ]
+24
View File
@@ -257,3 +257,27 @@ def test_load_treats_null_approved_channel_list_as_empty(tmp_path, monkeypatch):
assert store.is_approved("telegram", "123") is False assert store.is_approved("telegram", "123") is False
assert store.is_approved("discord", "456") is True assert store.is_approved("discord", "456") is True
assert store.get_approved("telegram") == [] assert store.get_approved("telegram") == []
def test_load_treats_null_approved_and_pending_maps_as_empty(tmp_path, monkeypatch):
"""Top-level approved/pending null must not crash pairing load or list_pending."""
path = tmp_path / "pairing.json"
path.write_text(
'{"approved": null, "pending": null}',
encoding="utf-8",
)
monkeypatch.setattr(store, "_store_path", lambda: path)
assert store.is_approved("telegram", "123") is False
assert store.list_pending() == []
assert store.get_approved("telegram") == []
def test_list_pending_skips_null_pending_entries(tmp_path, monkeypatch):
"""Null pending entry values must be dropped instead of crashing list_pending."""
path = tmp_path / "pairing.json"
path.write_text(
'{"approved": {}, "pending": {"ABCD-EFGH": null}}',
encoding="utf-8",
)
monkeypatch.setattr(store, "_store_path", lambda: path)
assert store.list_pending() == []