test_session_reset_fix.py
1 """Tests for session reset completeness (fixes #2635). 2 3 /clear and /new must not carry stale state into the next session. 4 Two fields were added after reset_session_state() was written and were 5 therefore never cleared: 6 - ContextCompressor._previous_summary 7 - AIAgent._user_turn_count 8 """ 9 import sys 10 import types 11 from pathlib import Path 12 13 import pytest 14 15 # Ensure repo root is importable 16 sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent)) 17 18 # Stub out optional heavy dependencies not installed in the test environment 19 sys.modules.setdefault("fire", types.SimpleNamespace(Fire=lambda *a, **k: None)) 20 sys.modules.setdefault("firecrawl", types.SimpleNamespace(Firecrawl=object)) 21 sys.modules.setdefault("fal_client", types.SimpleNamespace()) 22 23 from run_agent import AIAgent 24 from agent.context_compressor import ContextCompressor 25 26 27 def _make_minimal_agent() -> AIAgent: 28 """Return an AIAgent constructed with the absolute minimum args. 29 30 We pass dummy values that bypass network calls and filesystem access. 31 The object is never used to make API calls — only its attributes and 32 reset_session_state() are exercised. 33 """ 34 agent = AIAgent.__new__(AIAgent) # skip __init__ entirely 35 36 # Seed the exact attributes that reset_session_state() writes 37 agent.session_total_tokens = 0 38 agent.session_input_tokens = 0 39 agent.session_output_tokens = 0 40 agent.session_prompt_tokens = 0 41 agent.session_completion_tokens = 0 42 agent.session_cache_read_tokens = 0 43 agent.session_cache_write_tokens = 0 44 agent.session_reasoning_tokens = 0 45 agent.session_api_calls = 0 46 agent.session_estimated_cost_usd = 0.0 47 agent.session_cost_status = "unknown" 48 agent.session_cost_source = "none" 49 50 # The two fields under test 51 agent._user_turn_count = 0 52 agent.context_compressor = None # will be set per-test as needed 53 54 return agent 55 56 57 class TestResetSessionState: 58 """reset_session_state() must clear ALL session-scoped state.""" 59 60 def test_previous_summary_cleared_on_reset(self): 61 """Compression summary from old session must not leak into new session.""" 62 agent = _make_minimal_agent() 63 compressor = ContextCompressor.__new__(ContextCompressor) 64 compressor._previous_summary = "Old session summary about unrelated topic" 65 # Seed counter attributes that reset_session_state touches 66 compressor.last_prompt_tokens = 100 67 compressor.last_completion_tokens = 50 68 compressor.last_total_tokens = 150 69 compressor.compression_count = 3 70 compressor._context_probed = True 71 72 agent.context_compressor = compressor 73 74 agent.reset_session_state() 75 76 assert compressor._previous_summary is None, ( 77 "_previous_summary must be None after reset; got: " 78 f"{compressor._previous_summary!r}" 79 ) 80 81 def test_user_turn_count_cleared_on_reset(self): 82 """Turn counter must reset to 0 on new session.""" 83 agent = _make_minimal_agent() 84 agent._user_turn_count = 7 # simulates turns accumulated in previous session 85 agent.context_compressor = None 86 87 agent.reset_session_state() 88 89 assert agent._user_turn_count == 0, ( 90 f"_user_turn_count must be 0 after reset; got: {agent._user_turn_count}" 91 ) 92 93 def test_both_fields_cleared_together(self): 94 """Both stale fields are cleared in a single reset_session_state() call.""" 95 agent = _make_minimal_agent() 96 agent._user_turn_count = 3 97 98 compressor = ContextCompressor.__new__(ContextCompressor) 99 compressor._previous_summary = "Stale summary" 100 compressor.last_prompt_tokens = 0 101 compressor.last_completion_tokens = 0 102 compressor.last_total_tokens = 0 103 compressor.compression_count = 0 104 compressor._context_probed = False 105 agent.context_compressor = compressor 106 107 agent.reset_session_state() 108 109 assert agent._user_turn_count == 0 110 assert compressor._previous_summary is None 111 112 def test_reset_without_compressor_does_not_raise(self): 113 """reset_session_state() must not raise when context_compressor is None.""" 114 agent = _make_minimal_agent() 115 agent._user_turn_count = 2 116 agent.context_compressor = None 117 118 # Must not raise 119 agent.reset_session_state() 120 121 assert agent._user_turn_count == 0