test_session_model_reset.py
1 """Tests that /new (and its /reset alias) clears session-scoped overrides.""" 2 from datetime import datetime 3 from types import SimpleNamespace 4 from unittest.mock import AsyncMock, MagicMock 5 6 import pytest 7 8 from gateway.config import GatewayConfig, Platform, PlatformConfig 9 from gateway.platforms.base import MessageEvent 10 from gateway.session import SessionEntry, SessionSource, build_session_key 11 12 13 def _make_source() -> SessionSource: 14 return SessionSource( 15 platform=Platform.TELEGRAM, 16 user_id="u1", 17 chat_id="c1", 18 user_name="tester", 19 chat_type="dm", 20 ) 21 22 23 def _make_event(text: str) -> MessageEvent: 24 return MessageEvent(text=text, source=_make_source(), message_id="m1") 25 26 27 def _make_runner(): 28 from gateway.run import GatewayRunner 29 30 runner = object.__new__(GatewayRunner) 31 runner.config = GatewayConfig( 32 platforms={Platform.TELEGRAM: PlatformConfig(enabled=True, token="***")} 33 ) 34 adapter = MagicMock() 35 adapter.send = AsyncMock() 36 runner.adapters = {Platform.TELEGRAM: adapter} 37 runner._voice_mode = {} 38 runner.hooks = SimpleNamespace(emit=AsyncMock(), loaded_hooks=False) 39 runner._session_model_overrides = {} 40 runner._session_reasoning_overrides = {} 41 runner._pending_model_notes = {} 42 runner._background_tasks = set() 43 44 session_key = build_session_key(_make_source()) 45 session_entry = SessionEntry( 46 session_key=session_key, 47 session_id="sess-1", 48 created_at=datetime.now(), 49 updated_at=datetime.now(), 50 platform=Platform.TELEGRAM, 51 chat_type="dm", 52 ) 53 runner.session_store = MagicMock() 54 runner.session_store.get_or_create_session.return_value = session_entry 55 runner.session_store.reset_session.return_value = session_entry 56 runner.session_store._entries = {session_key: session_entry} 57 runner.session_store._generate_session_key.return_value = session_key 58 runner._running_agents = {} 59 runner._pending_messages = {} 60 runner._pending_approvals = {} 61 runner._session_db = None 62 runner._agent_cache_lock = None # disables _evict_cached_agent lock path 63 runner._is_user_authorized = lambda _source: True 64 runner._format_session_info = lambda: "" 65 66 return runner 67 68 69 @pytest.mark.asyncio 70 async def test_new_command_clears_session_model_override(): 71 """/new must remove the session-scoped model override for that session.""" 72 runner = _make_runner() 73 session_key = build_session_key(_make_source()) 74 75 # Simulate a prior /model switch stored as a session override 76 runner._session_model_overrides[session_key] = { 77 "model": "gpt-4o", 78 "provider": "openai", 79 "api_key": "***", 80 "base_url": "", 81 "api_mode": "openai", 82 } 83 runner._session_reasoning_overrides[session_key] = {"enabled": True, "effort": "high"} 84 runner._pending_model_notes[session_key] = "[Note: switched to gpt-4o.]" 85 86 await runner._handle_reset_command(_make_event("/new")) 87 88 assert session_key not in runner._session_model_overrides 89 assert session_key not in runner._session_reasoning_overrides 90 assert session_key not in runner._pending_model_notes 91 92 93 @pytest.mark.asyncio 94 async def test_new_command_no_override_is_noop(): 95 """/new with no prior model override must not raise.""" 96 runner = _make_runner() 97 session_key = build_session_key(_make_source()) 98 99 assert session_key not in runner._session_model_overrides 100 assert session_key not in runner._session_reasoning_overrides 101 102 await runner._handle_reset_command(_make_event("/new")) 103 104 assert session_key not in runner._session_model_overrides 105 assert session_key not in runner._session_reasoning_overrides 106 107 108 @pytest.mark.asyncio 109 async def test_new_command_only_clears_own_session(): 110 """/new must only clear the override for the session that triggered it.""" 111 runner = _make_runner() 112 session_key = build_session_key(_make_source()) 113 other_key = "other_session_key" 114 115 runner._session_model_overrides[session_key] = { 116 "model": "gpt-4o", 117 "provider": "openai", 118 "api_key": "sk-test", 119 "base_url": "", 120 "api_mode": "openai", 121 } 122 runner._session_model_overrides[other_key] = { 123 "model": "claude-sonnet-4-6", 124 "provider": "anthropic", 125 "api_key": "***", 126 "base_url": "", 127 "api_mode": "anthropic", 128 } 129 runner._session_reasoning_overrides[session_key] = {"enabled": True, "effort": "high"} 130 runner._session_reasoning_overrides[other_key] = {"enabled": True, "effort": "low"} 131 runner._pending_model_notes[session_key] = "[Note: switched to gpt-4o.]" 132 runner._pending_model_notes[other_key] = "[Note: switched to claude-sonnet-4-6.]" 133 134 await runner._handle_reset_command(_make_event("/new")) 135 136 assert session_key not in runner._session_model_overrides 137 assert other_key in runner._session_model_overrides 138 assert session_key not in runner._session_reasoning_overrides 139 assert other_key in runner._session_reasoning_overrides 140 assert session_key not in runner._pending_model_notes 141 assert other_key in runner._pending_model_notes