Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gateway/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ def _cleanup_expired(self, platform: str) -> None:
def _all_platforms(self, suffix: str) -> list:
"""List all platforms that have data files of a given suffix."""
platforms = []
for f in PAIRING_DIR.iterdir():
for f in self._dir.iterdir():
if f.name.endswith(f"-{suffix}.json"):
platform = f.name.replace(f"-{suffix}.json", "")
if not platform.startswith("_"):
Expand Down
36 changes: 36 additions & 0 deletions tests/gateway/test_pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,42 @@ def test_active_entries_win_when_merging_split_dirs(self, tmp_path):
assert migrated["ou_other"]["user_name"] == "Other"


class TestProfileScopedDiscovery:
def test_list_approved_scopes_platform_discovery_to_profile_dir(self, tmp_path):
# A profile-scoped store must enumerate platforms from its own
# per-profile directory (self._dir), not the module-global PAIRING_DIR.
# Regression: _all_platforms iterated PAIRING_DIR while every per-file
# path helper routed through self._dir, so a profile store confirmed a
# user via is_approved() (reads self._dir) yet returned [] from
# list_approved() (scanned the empty global dir).
home = tmp_path / "home"
global_dir = tmp_path / "global-pairing"
global_dir.mkdir(parents=True)

# PairingStore.__init__ resolves the profile dir via a *function-local*
# ``from hermes_constants import get_hermes_home``, so the patch must
# target ``hermes_constants.get_hermes_home`` (the source module) — not
# ``gateway.pairing.get_hermes_home`` (the module-global binding, which
# the local re-import bypasses). Patching the wrong target would leave
# ``self._dir`` rooted at the real HERMES_HOME.
with patch("gateway.pairing.PAIRING_DIR", global_dir), patch(
"hermes_constants.get_hermes_home", return_value=home
):
store = PairingStore(profile="alice")
# Store lives under the mocked home's profile dir — provably scoped
# there, and distinct from the module-global PAIRING_DIR.
assert store._dir == home / "profiles" / "alice" / "pairing"
assert store._dir != global_dir
with store._lock:
store._approve_user("telegram", "tg-456", "Bob")

assert store.is_approved("telegram", "tg-456") is True
approved = store.list_approved()

assert [r["user_id"] for r in approved] == ["tg-456"]
assert approved[0]["platform"] == "telegram"


# ---------------------------------------------------------------------------
# _secure_write
# ---------------------------------------------------------------------------
Expand Down
Loading