import asyncio from app.modules.chat.direct_service import CodeExplainChatService from app.modules.chat.session_resolver import ChatSessionResolver from app.modules.chat.task_store import TaskStore from app.modules.agent.runtime.steps.explain.models import ExplainIntent, ExplainPack from app.schemas.chat import ChatFileContext, ChatMessageRequest class _FakeRetriever: def build_pack(self, rag_session_id: str, user_query: str, *, file_candidates: list[dict] | None = None) -> ExplainPack: return ExplainPack( intent=ExplainIntent(raw_query=user_query, normalized_query=user_query), missing=["code_excerpts"], ) class _FakeLlm: def __init__(self) -> None: self.calls = 0 def generate(self, prompt_name: str, user_input: str, *, log_context: str | None = None) -> str: self.calls += 1 return "should not be called" class _FakeDialogs: def get(self, dialog_session_id: str): return None def test_direct_service_skips_llm_when_evidence_is_insufficient() -> None: messages: list[tuple[str, str, str, str | None]] = [] llm = _FakeLlm() task_store = TaskStore() service = CodeExplainChatService( retriever=_FakeRetriever(), llm=llm, session_resolver=ChatSessionResolver(_FakeDialogs(), lambda rag_session_id: rag_session_id == "rag-1"), task_store=task_store, message_sink=lambda dialog_session_id, role, content, task_id=None: messages.append((dialog_session_id, role, content, task_id)), ) result = asyncio.run( service.handle_message( ChatMessageRequest( session_id="dialog-1", project_id="rag-1", message="Explain get_user", files=[ChatFileContext(path="app/api/users.py", content="", content_hash="x")], ) ) ) task = task_store.get(result.task_id) assert task is not None assert task.answer is not None assert "Недостаточно опоры в коде" in task.answer assert result.status == "done" assert llm.calls == 0 assert [item[1] for item in messages] == ["user", "assistant"]