feat(agent-chat): 035-agent-chat-context — контекст, guardrails, tools, database discovery
== User stories == US1: Контекст с дашборда/датасета → /agent с URL params US2: Guardrails card — env badge, 7 risk tones, countdown, permission_denied US3: Tools optimization — retry, timeout, summarise, RBAC + context affinity == Backend == - _context.py (NEW): UIContext validation (7 checks) - _tool_filter.py (NEW): RBAC + context affinity pipeline - _confirmation.py: build_confirmation_contract_v2, permission_denied_payload - tools.py: superset_list_databases, retry/summarise/timeout wrappers - app.py: _inject_uicontext, _inject_env_id_into_tools, database prefetch в runtime context - _persistence.py: prefetch_databases() - agent_superset_explore.py: GET /databases endpoint - _llm_async_http.py, _persistence.py: fix double /v1 в LLM URL (LM Studio Unexpected endpoint) == Frontend == - AgentChatModel.svelte.ts: 5 atoms, 3 actions, countdown, context - AgentChat.svelte: production banner, process steps, debug panel - ConfirmationCard.svelte: 7 risk tones, permission_denied, countdown - ToolCallCard.svelte: retrying/timeout/cancelled states - StreamProcessor.svelte.ts: tool_retry, timeout, permission_denied - TopNavbar: sparkles icon + Ассистент - sidebarNavigation: AI section - DashboardHeader, datasets/+page: contextual AI buttons - Icon: sparkles, brain, cpu icons - tailwind: assistant category colors - i18n: en/ru nav keys == Tests == - 159 backend agent tests (+16 US3: retry, timeout, summarise, contracts) - 2544 frontend tests (+11 model + component tests) - 15 JSON fixtures (10 API + 5 model) == Specs == - specs/035-agent-chat-context/: spec, UX, plan, tasks, research, data-model, contracts, quickstart, traceability, fixtures, checklists Closes #035
This commit is contained in:
@@ -2,14 +2,15 @@
|
||||
# @BRIEF Tests for the Gradio agent handler — streaming, cancel, LLM error, empty message.
|
||||
# @RELATION BINDS_TO -> [AgentChat.GradioApp.Handler]
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent.parent / "src"))
|
||||
|
||||
import jwt
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, patch, MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import jwt
|
||||
|
||||
# Set JWT_SECRET and LLM_API_KEY for tests
|
||||
JWT_SECRET = "test-jwt-secret-key"
|
||||
@@ -35,6 +36,12 @@ def _make_test_jwt(user_id: str = "test-user") -> str:
|
||||
return jwt.encode({"sub": user_id}, JWT_SECRET, algorithm="HS256")
|
||||
|
||||
|
||||
def _empty_agent_state():
|
||||
state = MagicMock(spec_set=["next"])
|
||||
state.next = ()
|
||||
return state
|
||||
|
||||
|
||||
# #region TestAgentChat.Handler.EmptyMessage [C:2] [TYPE Function] [SEMANTICS test,handler,empty]
|
||||
# @BRIEF Empty message returns immediately without calling LangGraph.
|
||||
# @TEST_EDGE empty_text, empty_with_files_none
|
||||
@@ -50,7 +57,7 @@ async def test_handler_empty_message_returns_immediately():
|
||||
mock_request.client.host = "127.0.0.1"
|
||||
|
||||
# Patch create_agent to avoid OpenAI init
|
||||
with patch("src.agent.langgraph_setup.create_agent") as mock_create:
|
||||
with patch("src.agent.langgraph_setup.create_agent"):
|
||||
# Empty message
|
||||
message = {"text": "", "files": None}
|
||||
results = []
|
||||
@@ -85,11 +92,12 @@ async def test_handler_missing_auth_continues_gracefully():
|
||||
# Patch create_agent to prevent LLM call — handler should proceed without JWT
|
||||
with patch("src.agent.app.create_agent") as mock_create:
|
||||
mock_graph = AsyncMock()
|
||||
async def _empty_stream(*args, **kwargs):
|
||||
async def _empty_stream(*_args, **_kwargs):
|
||||
"""Empty async generator — yields nothing."""
|
||||
return
|
||||
yield # make it a generator
|
||||
mock_graph.astream_events = _empty_stream
|
||||
mock_graph.aget_state = AsyncMock(return_value=_empty_agent_state())
|
||||
mock_create.return_value = mock_graph
|
||||
|
||||
results = []
|
||||
@@ -119,10 +127,11 @@ async def test_handler_invalid_jwt_continues_gracefully():
|
||||
|
||||
with patch("src.agent.app.create_agent") as mock_create:
|
||||
mock_graph = AsyncMock()
|
||||
async def _empty_stream(*args, **kwargs):
|
||||
async def _empty_stream(*_args, **_kwargs):
|
||||
return
|
||||
yield
|
||||
mock_graph.astream_events = _empty_stream
|
||||
mock_graph.aget_state = AsyncMock(return_value=_empty_agent_state())
|
||||
mock_create.return_value = mock_graph
|
||||
|
||||
results = []
|
||||
@@ -157,11 +166,12 @@ async def test_handler_yields_stream_tokens():
|
||||
with patch("src.agent.app.create_agent") as mock_create:
|
||||
mock_graph = AsyncMock()
|
||||
|
||||
async def _mock_stream(*args, **kwargs):
|
||||
async def _mock_stream(*_args, **_kwargs):
|
||||
yield {"event": "on_chat_model_stream", "data": {"chunk": MagicMock(content="Hello")}}
|
||||
yield {"event": "on_chat_model_stream", "data": {"chunk": MagicMock(content=" world")}}
|
||||
|
||||
mock_graph.astream_events = _mock_stream
|
||||
mock_graph.aget_state = AsyncMock(return_value=_empty_agent_state())
|
||||
mock_create.return_value = mock_graph
|
||||
|
||||
results = []
|
||||
@@ -197,7 +207,7 @@ async def test_handler_resume_confirm():
|
||||
# imports create_agent directly from langgraph_setup
|
||||
with patch("src.agent._confirmation.create_agent") as mock_create:
|
||||
mock_graph = MagicMock()
|
||||
async def _empty_stream(*args, **kwargs):
|
||||
async def _empty_stream(*_args, **_kwargs):
|
||||
return
|
||||
yield
|
||||
mock_graph.astream_events = _empty_stream
|
||||
|
||||
Reference in New Issue
Block a user