# #region TestAgentChat.Confirmations [C:3] [TYPE Module] [SEMANTICS test,agent,confirmation,hitl] # @BRIEF Supplementary HITL confirmation flow tests — concurrent send, unknown action, model edge cases. # @RELATION BINDS_TO -> [AgentChat.GradioApp.Handler] # @TEST_EDGE: concurrent_send -> handler yields CONCURRENT_SEND error # @TEST_EDGE: confirm_without_conversation_id -> handler still processes confirm # @NOTE Resume confirm/deny handler tests are in test_agent_handler.py (test_handler_resume_confirm, # test_handler_resume_deny). Model-level state machine tests are in AgentChatModel.test.ts. import os from pathlib import Path import sys from unittest.mock import AsyncMock, MagicMock, patch sys.path.append(str(Path(__file__).parent.parent.parent / "src")) import pytest import jwt os.environ.setdefault("AUTH_SECRET_KEY", "test-secret-key-for-jwt-testing") AUTH_SECRET_KEY = os.environ["AUTH_SECRET_KEY"] os.environ["OPENAI_API_KEY"] = "sk-test-key" os.environ["LLM_API_KEY"] = "sk-test-key" os.environ["LLM_MODEL"] = "gpt-4o" os.environ["LLM_BASE_URL"] = "https://api.openai.com/v1" def _make_test_jwt(user_id: str = "test-user") -> str: return jwt.encode({"sub": user_id}, AUTH_SECRET_KEY, algorithm="HS256") @pytest.fixture(autouse=True) def mock_save_conversation(): with patch("ss_tools.agent.app.save_conversation", new_callable=AsyncMock): yield # #region TestAgentChat.Confirmations.Concurrent [C:2] [TYPE Function] [SEMANTICS test,confirmation,concurrent] # @BRIEF Concurrent send lock prevents multiple simultaneous sends from same user. @pytest.mark.asyncio async def test_concurrent_send_lock(): """Handler rejects concurrent sends from same user with CONCURRENT_SEND error.""" from ss_tools.agent.app import _user_locks, agent_handler # Handler resolves user_id as "admin" when no JWT and no user_id_str are provided _user_locks["admin"] = True history: list = [] mock_request = MagicMock() mock_request.headers = {} mock_request.client.host = "127.0.0.1" message = {"text": "hello", "files": None} results = [] async for chunk in agent_handler(message, history, mock_request, "test-conv", None): results.append(chunk) assert len(results) == 1 import json parsed = json.loads(results[0]) if isinstance(results[0], str) else results[0] assert parsed["metadata"]["code"] == "CONCURRENT_SEND" # Clean up _user_locks.pop("admin", None) # #endregion TestAgentChat.Confirmations.Concurrent # #region TestAgentChat.Confirmations.UnknownAction [C:2] [TYPE Function] [SEMANTICS test,confirmation,unknown] # @BRIEF Non-confirm/deny action values are treated as normal messages. @pytest.mark.asyncio async def test_handler_unknown_action_treated_as_normal(): """Handler with unknown action string proceeds as normal message send.""" from ss_tools.agent.app import agent_handler history: list = [] mock_request = MagicMock() token = _make_test_jwt() mock_request.headers = {"authorization": f"Bearer {token}"} mock_request.client.host = "127.0.0.1" message = {"text": "hello", "files": None} with patch("ss_tools.agent.app.create_agent") as mock_create: mock_graph = MagicMock() async def _mock_stream(*_args, **_kwargs): yield {"event": "on_chat_model_stream", "data": {"chunk": MagicMock(content="Hello")}} mock_graph.astream_events = _mock_stream state = MagicMock(spec_set=["next"]) state.next = () mock_graph.aget_state = AsyncMock(return_value=state) mock_create.return_value = mock_graph results = [] async for chunk in agent_handler(message, history, mock_request, "test-conv", "unknown_action"): results.append(chunk) # Should produce stream tokens, not confirm_resolved assert len(results) > 0 import json meta_types = [json.loads(r)["metadata"]["type"] for r in results] assert "stream_token" in meta_types assert "confirm_resolved" not in meta_types # #endregion TestAgentChat.Confirmations.UnknownAction # #region TestAgentChat.Confirmations.ExpiredState [C:2] [TYPE Function] [SEMANTICS test,confirmation,expired] # @BRIEF Stale confirmations — checkpoint no longer available. @pytest.mark.asyncio async def test_handler_confirm_no_graph(): """Handler with action='confirm' but create_agent raises handles it gracefully.""" from ss_tools.agent.app import agent_handler history: list = [] mock_request = MagicMock() token = _make_test_jwt() mock_request.headers = {"authorization": f"Bearer {token}"} mock_request.client.host = "127.0.0.1" message = {"text": "confirm", "files": None} with patch("ss_tools.agent._confirmation.create_agent") as mock_create: mock_create.side_effect = Exception("Graph creation failed") try: results = [] async for chunk in agent_handler(message, history, mock_request, "test-conv", "confirm"): results.append(chunk) # Exception may propagate or be caught — either is acceptable except Exception: pass # #endregion TestAgentChat.Confirmations.ExpiredState # #endregion TestAgentChat.Confirmations