fix(pairing): normalize sender IDs to str in the pairing store
This commit is contained in:
@@ -44,9 +44,12 @@ def _load() -> dict[str, Any]:
|
||||
logger.warning("Corrupted pairing store, resetting")
|
||||
return {"approved": {}, "pending": {}}
|
||||
|
||||
# Convert approved lists to sets for O(1) lookup
|
||||
# Convert approved lists to sets for O(1) lookup. Sender IDs are normalized
|
||||
# to str so lookups match is_approved()/revoke(), which coerce with str():
|
||||
# IDs may be numeric (e.g. Telegram/QQ) in code or in a hand-edited
|
||||
# pairing.json, and an int entry would never match the str() lookup.
|
||||
for channel, users in data.get("approved", {}).items():
|
||||
data["approved"][channel] = set(users)
|
||||
data["approved"][channel] = {str(u) for u in users}
|
||||
return data
|
||||
|
||||
|
||||
@@ -87,7 +90,7 @@ def generate_code(
|
||||
|
||||
data.setdefault("pending", {})[code] = {
|
||||
"channel": channel,
|
||||
"sender_id": sender_id,
|
||||
"sender_id": str(sender_id),
|
||||
"created_at": time.time(),
|
||||
"expires_at": time.time() + ttl,
|
||||
}
|
||||
@@ -162,12 +165,13 @@ def revoke(channel: str, sender_id: str) -> bool:
|
||||
data = _load()
|
||||
approved: dict[str, set[str]] = data.get("approved", {})
|
||||
users = approved.get(channel, set())
|
||||
if sender_id in users:
|
||||
users.discard(sender_id)
|
||||
sid = str(sender_id)
|
||||
if sid in users:
|
||||
users.discard(sid)
|
||||
if not users:
|
||||
del approved[channel]
|
||||
_save(data)
|
||||
logger.info("Revoked {} from {}", sender_id, channel)
|
||||
logger.info("Revoked {} from {}", sid, channel)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -174,6 +174,34 @@ class TestHandlePairingCommand:
|
||||
assert "Pending pairing requests:" in reply
|
||||
|
||||
|
||||
class TestNonStringSenderId:
|
||||
"""Sender IDs may be numeric (e.g. Telegram/QQ). The store normalizes them
|
||||
to str so writes/reads/removals stay consistent with is_approved()."""
|
||||
|
||||
def test_numeric_sender_id_round_trip(self) -> None:
|
||||
code = store.generate_code("telegram", 12345)
|
||||
assert store.approve_code(code) == ("telegram", "12345")
|
||||
# Approved regardless of whether the caller passes int or str.
|
||||
assert store.is_approved("telegram", 12345) is True
|
||||
assert store.is_approved("telegram", "12345") is True
|
||||
assert store.get_approved("telegram") == ["12345"]
|
||||
# Revoke also works with a numeric id.
|
||||
assert store.revoke("telegram", 12345) is True
|
||||
assert store.is_approved("telegram", "12345") is False
|
||||
|
||||
def test_numeric_id_in_hand_edited_store(self) -> None:
|
||||
# Operators may edit pairing.json directly; a numeric entry must still
|
||||
# match the str() lookup that is_approved() performs.
|
||||
store._store_path().write_text(
|
||||
'{"approved": {"telegram": [12345]}, "pending": {}}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert store.is_approved("telegram", "12345") is True
|
||||
assert store.is_approved("telegram", 12345) is True
|
||||
assert store.revoke("telegram", 12345) is True
|
||||
assert store.is_approved("telegram", "12345") is False
|
||||
|
||||
|
||||
class TestStoreDurability:
|
||||
def test_corruption_recovery(self, tmp_path, monkeypatch) -> None:
|
||||
path = tmp_path / "pairing.json"
|
||||
|
||||
Reference in New Issue
Block a user