"""Tests for PluginRegistry user-scoped session isolation (remote-hosted mode)."""
import pytest
from core.config import config
from transport.plugin_registry import PluginRegistry
class TestRegistryUserIsolation:
@pytest.mark.asyncio
async def test_register_with_user_id_stores_composite_key(self):
registry = PluginRegistry()
session = await registry.register(
"sess-1", "MyProject", "hash1", "2022.3", user_id="user-A"
)
assert session.user_id == "user-A"
assert ("user-A", "hash1") in registry._user_hash_to_session
assert registry._user_hash_to_session[("user-A", "hash1")] == "sess-1"
@pytest.mark.asyncio
async def test_get_session_id_by_hash(self):
registry = PluginRegistry()
await registry.register("sess-1", "Proj", "h1", "2022", user_id="uA")
found = await registry.get_session_id_by_hash("h1", "uA")
assert found == "sess-1"
# Different user, same hash -> not found
not_found = await registry.get_session_id_by_hash("h1", "uB")
assert not_found is None
@pytest.mark.asyncio
async def test_cross_user_isolation_same_hash(self):
"""Two users registering with the same project_hash get independent sessions."""
registry = PluginRegistry()
sess_a = await registry.register("sA", "Proj", "hash1", "2022", user_id="userA")
sess_b = await registry.register("sB", "Proj", "hash1", "2022", user_id="userB")
assert sess_a.session_id == "sA"
assert sess_b.session_id == "sB"
# Each user resolves to their own session
assert await registry.get_session_id_by_hash("hash1", "userA") == "sA"
assert await registry.get_session_id_by_hash("hash1", "userB") == "sB"
# Both sessions exist
all_sessions = await registry.list_sessions()
assert len(all_sessions) == 2
@pytest.mark.asyncio
async def test_list_sessions_filtered_by_user(self):
registry = PluginRegistry()
await registry.register("s1", "ProjA", "hA", "2022", user_id="userA")
await registry.register("s2", "ProjB", "hB", "2022", user_id="userB")
await registry.register("s3", "ProjC", "hC", "2022", user_id="userA")
user_a_sessions = await registry.list_sessions(user_id="userA")
assert len(user_a_sessions) == 2
assert "s1" in user_a_sessions
assert "s3" in user_a_sessions
user_b_sessions = await registry.list_sessions(user_id="userB")
assert len(user_b_sessions) == 1
assert "s2" in user_b_sessions
@pytest.mark.asyncio
async def test_list_sessions_no_filter_returns_all_in_local_mode(self):
"""In local mode (not remote-hosted), list_sessions(user_id=None) returns all."""
registry = PluginRegistry()
await registry.register("s1", "P1", "h1", "2022", user_id="uA")
await registry.register("s2", "P2", "h2", "2022", user_id="uB")
await registry.register("s3", "P3", "h3", "2022") # local mode, no user_id
all_sessions = await registry.list_sessions(user_id=None)
assert len(all_sessions) == 3
@pytest.mark.asyncio
async def test_list_sessions_no_filter_raises_in_remote_hosted(self, monkeypatch):
"""In remote-hosted mode, list_sessions(user_id=None) raises ValueError."""
monkeypatch.setattr(config, "http_remote_hosted", True)
registry = PluginRegistry()
await registry.register("s1", "P1", "h1", "2022", user_id="uA")
with pytest.raises(ValueError, match="requires user_id"):
await registry.list_sessions(user_id=None)
@pytest.mark.asyncio
async def test_unregister_cleans_user_scoped_mapping(self):
registry = PluginRegistry()
await registry.register("s1", "Proj", "h1", "2022", user_id="uA")
assert ("uA", "h1") in registry._user_hash_to_session
await registry.unregister("s1")
assert ("uA", "h1") not in registry._user_hash_to_session
assert "s1" not in (await registry.list_sessions())
@pytest.mark.asyncio
async def test_reconnect_replaces_previous_session(self):
"""Same (user_id, hash) re-registered evicts old session, stores new one."""
registry = PluginRegistry()
await registry.register("old-sess", "Proj", "h1", "2022", user_id="uA")
assert await registry.get_session_id_by_hash("h1", "uA") == "old-sess"
await registry.register("new-sess", "Proj", "h1", "2022", user_id="uA")
assert await registry.get_session_id_by_hash("h1", "uA") == "new-sess"
# Old session should be evicted
all_sessions = await registry.list_sessions()
assert "old-sess" not in all_sessions
assert "new-sess" in all_sessions