test(backend): add 55+ test files to push coverage to 98%

Subagents delivered tests across all uncovered backend modules:

Schemas (100%): agent, auth, health, profile, settings, validation
Services (98-100%): auth, profile, health, llm, mapping, resource,
  security, git, superset_lookup, sql_table_extractor, rbac
API routes (new): auth, admin, health, environments, plugins,
  dashboards (helpers, projection, actions, listing),
  git (config, deps, env, helpers)
Clean Release (100%): DTO, facade, policy_engine, stages,
  repos, preparation, source_isolation, compliance
Git services: base, remote_providers
Agent module: app, run, middleware, langgraph_setup
Core: trace, cleanup, ws_log_handler, timezone, auth (config/oauth/security), matching
Reports: normalizer, report_service, type_profiles
Notifications: service, providers

Also:
- .gitignore: add .coverage, *.cover, coverage-* dirs
- src/schemas/auth.py: fix AD group DN regex (comma in CN=...)
- Remove co-located src/services/__tests__/ (caused pytest module collision)
This commit is contained in:
2026-06-15 13:55:57 +03:00
parent 6989bb0bc9
commit ce0369ae5c
67 changed files with 12187 additions and 1993 deletions

View File

@@ -0,0 +1,354 @@
# #region Test.AgentChat.GradioApp [C:3] [TYPE Module] [SEMANTICS test,agent,gradio,chat]
# @BRIEF Tests for agent/app.py — agent_handler, _handle_resume, _extract_user_id, _save_conversation, create_chat_interface, health.
# @RELATION BINDS_TO -> [AgentChat.GradioApp]
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
import json
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
def _make_async_iter(items):
"""Create an async iterator from a list."""
class AsyncIter:
def __init__(self, items):
self._iter = iter(items)
def __aiter__(self):
return self
async def __anext__(self):
try:
return next(self._iter)
except StopIteration:
raise StopAsyncIteration
return AsyncIter(items)
def _make_agent_mock(stream_events=None, raise_on_call=False):
"""Create a properly mocked agent for async iteration."""
agent = MagicMock()
if raise_on_call:
agent.astream_events = MagicMock(side_effect=stream_events if isinstance(stream_events, list) else stream_events)
elif stream_events is not None:
agent.astream_events = MagicMock(return_value=_make_async_iter(stream_events))
else:
agent.astream_events = MagicMock(return_value=_make_async_iter([]))
return agent
@pytest.fixture(autouse=True)
def clear_locks():
"""Clear _user_locks before each test."""
from src.agent.app import _user_locks
_user_locks.clear()
@pytest.fixture
def mock_request():
req = MagicMock()
req.headers = {"authorization": ""}
return req
# #region test_extract_user_id [C:2] [TYPE Function]
# @BRIEF Test _extract_user_id for various JWT payloads.
class TestExtractUserId:
def test_extracts_sub(self):
from src.agent.app import _extract_user_id
with patch("src.agent.app.jwt.decode", return_value={"sub": "user-1"}):
assert _extract_user_id("fake-jwt") == "user-1"
def test_extracts_user_id_fallback(self):
from src.agent.app import _extract_user_id
with patch("src.agent.app.jwt.decode", return_value={"user_id": "user-2"}):
assert _extract_user_id("fake-jwt") == "user-2"
def test_returns_unknown_on_exception(self):
from src.agent.app import _extract_user_id
with patch("src.agent.app.jwt.decode", side_effect=Exception("bad token")):
assert _extract_user_id("bad") == "unknown"
def test_returns_unknown_on_empty(self):
from src.agent.app import _extract_user_id
assert _extract_user_id("") == "unknown"
# #endregion test_extract_user_id
# #region test_agent_handler [C:2] [TYPE Function]
# @BRIEF Test agent_handler for various scenarios.
class TestAgentHandler:
@pytest.mark.asyncio
async def test_handles_concurrent_send(self, mock_request):
from src.agent.app import agent_handler, _user_locks
_user_locks["anon_default"] = True
results = [r async for r in agent_handler("hello", [], mock_request, None, None)]
assert len(results) == 1
data = json.loads(results[0])
assert data["metadata"]["code"] == "CONCURRENT_SEND"
@pytest.mark.asyncio
async def test_handles_file_too_large(self, mock_request):
from src.agent.app import agent_handler, MAX_FILE_SIZE_BYTES
message = {"text": "analyze", "files": ["fake_path"]}
with patch("os.path.exists", return_value=True), \
patch("os.path.getsize", return_value=MAX_FILE_SIZE_BYTES + 1):
results = [r async for r in agent_handler(message, [], mock_request, None, None)]
assert len(results) == 1
data = json.loads(results[0])
assert data["metadata"]["code"] == "FILE_TOO_LARGE"
@pytest.mark.asyncio
async def test_handles_hitl_confirm(self, mock_request):
from src.agent.app import agent_handler
with patch("src.agent.app._handle_resume") as mock_resume:
mock_resume.return_value = _make_async_iter([
json.dumps({"content": "confirmed", "metadata": {"type": "confirm_resolved"}})
])
with patch("src.agent.app._save_conversation", AsyncMock()):
results = [r async for r in agent_handler("confirm", [], mock_request, "conv-1", "confirm")]
assert len(results) == 1
data = json.loads(results[0])
assert data["metadata"]["type"] == "confirm_resolved"
@pytest.mark.asyncio
async def test_handles_hitl_deny(self, mock_request):
from src.agent.app import agent_handler
with patch("src.agent.app._handle_resume") as mock_resume:
mock_resume.return_value = _make_async_iter([
json.dumps({"content": "denied", "metadata": {"type": "confirm_resolved", "result": "denied"}})
])
with patch("src.agent.app._save_conversation", AsyncMock()):
results = [r async for r in agent_handler("deny", [], mock_request, "conv-1", "deny")]
assert len(results) == 1
data = json.loads(results[0])
assert data["metadata"]["result"] == "denied"
@pytest.mark.asyncio
async def test_normal_send_yields_tokens(self, mock_request):
from src.agent.app import agent_handler
mock_chunk = MagicMock()
mock_chunk.content = "Hello"
mock_event_stream = [
{"event": "on_chat_model_stream", "data": {"chunk": mock_chunk}},
{"event": "on_chain_end", "interrupt": {}},
]
agent = _make_agent_mock(mock_event_stream)
with patch("src.agent.app.create_agent", return_value=agent), \
patch("src.agent.app.get_all_tools", return_value=[]), \
patch("src.agent.app._save_conversation", AsyncMock()):
results = [r async for r in agent_handler("hello", [], mock_request, None, None)]
assert len(results) > 0
token_data = json.loads(results[0])
assert token_data["metadata"]["type"] == "stream_token"
@pytest.mark.asyncio
async def test_tool_events_yielded(self, mock_request):
from src.agent.app import agent_handler
mock_event_stream = [
{"event": "on_tool_start", "name": "test_tool", "data": {"input": {}}},
{"event": "on_tool_end", "name": "test_tool", "data": {"output": "done"}},
]
agent = _make_agent_mock(mock_event_stream)
with patch("src.agent.app.create_agent", return_value=agent), \
patch("src.agent.app.get_all_tools", return_value=[]), \
patch("src.agent.app._save_conversation", AsyncMock()), \
patch("src.agent.app.log_tool_event", AsyncMock()):
results = [r async for r in agent_handler("hello", [], mock_request, None, None)]
assert len(results) == 2
assert json.loads(results[0])["metadata"]["type"] == "tool_start"
assert json.loads(results[1])["metadata"]["type"] == "tool_end"
@pytest.mark.asyncio
async def test_tool_error_event(self, mock_request):
from src.agent.app import agent_handler
mock_event_stream = [
{"event": "on_tool_error", "name": "test_tool", "data": {"error": "something failed"}},
]
agent = _make_agent_mock(mock_event_stream)
with patch("src.agent.app.create_agent", return_value=agent), \
patch("src.agent.app.get_all_tools", return_value=[]), \
patch("src.agent.app._save_conversation", AsyncMock()), \
patch("src.agent.app.log_tool_event", AsyncMock()):
results = [r async for r in agent_handler("hello", [], mock_request, None, None)]
assert len(results) == 1
assert json.loads(results[0])["metadata"]["type"] == "tool_error"
@pytest.mark.asyncio
async def test_output_parser_exception_retry(self, mock_request):
from src.agent.app import agent_handler
from langchain_core.exceptions import OutputParserException
mock_stream_after = _make_async_iter([
{"event": "on_chat_model_stream", "data": {"chunk": MagicMock(content="retried")}}
])
call_count = [0]
def mock_astream(*args, **kwargs):
call_count[0] += 1
if call_count[0] == 1:
raise OutputParserException("bad output")
return mock_stream_after
agent = MagicMock()
agent.astream_events = mock_astream
with patch("src.agent.app.create_agent", return_value=agent), \
patch("src.agent.app.get_all_tools", return_value=[]), \
patch("src.agent.app._save_conversation", AsyncMock()):
results = [r async for r in agent_handler("hello", [], mock_request, None, None)]
assert len(results) > 0
@pytest.mark.asyncio
async def test_output_parser_exception_final_failure(self, mock_request):
from src.agent.app import agent_handler
from langchain_core.exceptions import OutputParserException
call_count = [0]
def mock_astream(*args, **kwargs):
call_count[0] += 1
raise OutputParserException(f"bad {call_count[0]}")
agent = MagicMock()
agent.astream_events = mock_astream
with patch("src.agent.app.create_agent", return_value=agent), \
patch("src.agent.app.get_all_tools", return_value=[]), \
patch("src.agent.app._save_conversation", AsyncMock()):
results = [r async for r in agent_handler("hello", [], mock_request, None, None)]
assert len(results) == 1
data = json.loads(results[0])
assert data["metadata"]["code"] == "LLM_MALFORMED_OUTPUT"
@pytest.mark.asyncio
async def test_non_llm_error_saves_conversation(self, mock_request):
from src.agent.app import agent_handler
def mock_astream(*args, **kwargs):
raise RuntimeError("API connection error")
agent = MagicMock()
agent.astream_events = mock_astream
save_mock = AsyncMock()
with patch("src.agent.app.create_agent", return_value=agent), \
patch("src.agent.app.get_all_tools", return_value=[]), \
patch("src.agent.app._save_conversation", save_mock):
with pytest.raises(RuntimeError, match="API connection error"):
await agent_handler("hello", [], mock_request, None, None).__anext__()
save_mock.assert_called_once()
@pytest.mark.asyncio
async def test_invalid_jwt_passes_gracefully(self):
from src.agent.app import agent_handler
import jwt as pyjwt
req = MagicMock()
req.headers = {"authorization": "Bearer invalid.jwt"}
mock_event_stream = [
{"event": "on_chat_model_stream", "data": {"chunk": MagicMock(content="ok")}}
]
agent = _make_agent_mock(mock_event_stream)
with patch("src.agent.app.jwt.decode", side_effect=pyjwt.InvalidTokenError("invalid")), \
patch("src.agent.app.create_agent", return_value=agent), \
patch("src.agent.app.get_all_tools", return_value=[]), \
patch("src.agent.app._save_conversation", AsyncMock()):
results = [r async for r in agent_handler("hi", [], req, None, None)]
assert len(results) == 1
# #endregion test_agent_handler
# #region test_handle_resume [C:2] [TYPE Function]
# @BRIEF Test _handle_resume confirm and deny paths.
class TestHandleResume:
@pytest.mark.asyncio
async def test_confirm(self):
from src.agent.app import _handle_resume
mock_agent = MagicMock()
with patch("src.agent.app.create_agent", return_value=mock_agent), \
patch("src.agent.app.get_all_tools", return_value=[]):
results = [r async for r in _handle_resume("conv-1", "confirm")]
assert len(results) == 1
data = json.loads(results[0])
assert data["metadata"]["result"] == "confirmed"
@pytest.mark.asyncio
async def test_deny(self):
from src.agent.app import _handle_resume
mock_agent = MagicMock()
with patch("src.agent.app.create_agent", return_value=mock_agent), \
patch("src.agent.app.get_all_tools", return_value=[]):
results = [r async for r in _handle_resume("conv-1", "deny")]
assert len(results) == 1
data = json.loads(results[0])
assert data["metadata"]["result"] == "denied"
# #endregion test_handle_resume
# #region test_save_conversation [C:2] [TYPE Function]
# @BRIEF Test _save_conversation with various scenarios.
class TestSaveConversation:
@pytest.mark.asyncio
async def test_save_success(self):
from src.agent.app import _save_conversation
with patch("src.agent.app.httpx.AsyncClient") as mock_client:
mock_client.return_value.__aenter__.return_value.post = AsyncMock()
await _save_conversation("conv-1", "test message", "user-1")
mock_client.return_value.__aenter__.return_value.post.assert_called_once()
@pytest.mark.asyncio
async def test_save_with_service_jwt(self):
from src.agent.app import _save_conversation
with patch("src.agent.app.httpx.AsyncClient") as mock_client, \
patch("src.agent.app.os.getenv", return_value="service-token"):
mock_client.return_value.__aenter__.return_value.post = AsyncMock()
await _save_conversation("conv-1", "hello", "admin")
mock_client.return_value.__aenter__.return_value.post.assert_called_once()
@pytest.mark.asyncio
async def test_save_failure_logged(self):
from src.agent.app import _save_conversation
with patch("src.agent.app.httpx.AsyncClient") as mock_client:
mock_client.return_value.__aenter__.return_value.post.side_effect = Exception("network err")
# Should not raise
await _save_conversation("conv-1", "msg", "u1")
@pytest.mark.asyncio
async def test_save_empty_title(self):
from src.agent.app import _save_conversation
with patch("src.agent.app.httpx.AsyncClient") as mock_client:
client_instance = AsyncMock()
mock_client.return_value.__aenter__.return_value = client_instance
await _save_conversation("conv-1", " ", "user-1")
call_kwargs = client_instance.post.call_args[1]
assert call_kwargs["json"]["title"] == "Agent conversation"
# #endregion test_save_conversation
# #region test_create_chat_interface [C:2] [TYPE Function]
# @BRIEF Test create_chat_interface returns a gr.ChatInterface.
class TestCreateChatInterface:
def test_returns_chat_interface(self):
from src.agent.app import create_chat_interface
with patch("src.agent.app.gr.ChatInterface") as mock_ci:
result = create_chat_interface()
assert result is mock_ci.return_value
# #endregion test_create_chat_interface
# #region test_health [C:2] [TYPE Function]
# @BRIEF Test health endpoint returns status ok.
class TestHealth:
@pytest.mark.asyncio
async def test_health_returns_ok(self):
from src.agent.app import health
result = await health()
assert result["status"] == "ok"
# #endregion test_health
# #endregion Test.AgentChat.GradioApp

View File

@@ -0,0 +1,110 @@
# #region Test.AgentChat.LangGraph.Setup [C:3] [TYPE Module] [SEMANTICS test,agent,langgraph,setup]
# @BRIEF Tests for agent/langgraph_setup.py — configure_from_api, create_agent.
# @RELATION BINDS_TO -> [AgentChat.LangGraph.Setup]
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
from unittest.mock import MagicMock, patch
import pytest
# #region test_configure_from_api [C:2] [TYPE Function]
# @BRIEF Test configure_from_api updates global config.
class TestConfigureFromApi:
def test_sets_llm_config(self):
import src.agent.langgraph_setup as ls
ls._llm_config = None # Reset
ls.configure_from_api({"configured": True, "api_key": "sk-test", "default_model": "gpt-4o"})
assert ls._llm_config is not None
assert ls._llm_config["configured"] is True
# Reset for other tests
ls.configure_from_api(None)
ls._llm_config = None
def test_overwrites_previous_config(self):
import src.agent.langgraph_setup as ls
ls._llm_config = None # Reset
ls.configure_from_api({"configured": True, "api_key": "sk-1"})
ls.configure_from_api({"configured": False})
assert ls._llm_config["configured"] is False
ls._llm_config = None
# #endregion test_configure_from_api
# #region test_create_agent [C:2] [TYPE Function]
# @BRIEF Test create_agent with various LLM config states.
class TestCreateAgent:
def test_creates_agent_with_api_config(self):
import src.agent.langgraph_setup as ls
ls._llm_config = None # Reset
ls.configure_from_api({
"configured": True,
"api_key": "sk-api-config",
"base_url": "https://custom.api.com/v1",
"default_model": "gpt-4o-mini",
})
with patch("src.agent.langgraph_setup.ChatOpenAI") as mock_llm, \
patch("src.agent.langgraph_setup.create_react_agent") as mock_create:
mock_create.return_value = MagicMock()
result = ls.create_agent([MagicMock()])
assert result is mock_create.return_value
call_kwargs = mock_llm.call_args[1]
assert call_kwargs["api_key"] == "sk-api-config"
assert call_kwargs["base_url"] == "https://custom.api.com/v1"
assert call_kwargs["model"] == "gpt-4o-mini"
ls._llm_config = None
def test_creates_agent_with_env_fallback(self):
import src.agent.langgraph_setup as ls
ls._llm_config = None # Reset
with patch("src.agent.langgraph_setup.ChatOpenAI") as mock_llm, \
patch("src.agent.langgraph_setup.create_react_agent") as mock_create, \
patch("src.agent.langgraph_setup.os.getenv") as mock_getenv:
mock_getenv.side_effect = lambda key, default=None: {
"LLM_API_KEY": "sk-env-key",
"LLM_BASE_URL": "https://env.api.com",
"LLM_MODEL": "gpt-4",
}.get(key, default)
mock_create.return_value = MagicMock()
result = ls.create_agent([])
assert result is mock_create.return_value
call_kwargs = mock_llm.call_args[1]
assert call_kwargs["api_key"] == "sk-env-key"
assert call_kwargs["model"] == "gpt-4"
ls._llm_config = None
def test_creates_agent_with_partial_api_config(self):
import src.agent.langgraph_setup as ls
ls._llm_config = None
ls.configure_from_api({
"configured": True,
"api_key": "sk-key-only",
})
with patch("src.agent.langgraph_setup.ChatOpenAI") as mock_llm, \
patch("src.agent.langgraph_setup.create_react_agent") as mock_create:
mock_create.return_value = MagicMock()
result = ls.create_agent([])
assert result is mock_create.return_value
call_kwargs = mock_llm.call_args[1]
assert call_kwargs["api_key"] == "sk-key-only"
assert call_kwargs["base_url"] == "https://api.openai.com/v1"
assert call_kwargs["model"] == "gpt-4o-mini"
ls._llm_config = None
def test_uses_inmemory_saver(self):
import src.agent.langgraph_setup as ls
ls._llm_config = None
with patch("src.agent.langgraph_setup.ChatOpenAI") as mock_llm, \
patch("src.agent.langgraph_setup.create_react_agent") as mock_create, \
patch("src.agent.langgraph_setup.os.getenv", return_value=None):
mock_create.return_value = MagicMock()
ls.create_agent([])
call_kwargs = mock_create.call_args[1]
from langgraph.checkpoint.memory import InMemorySaver
assert isinstance(call_kwargs["checkpointer"], InMemorySaver)
ls._llm_config = None
# #endregion test_create_agent
# #endregion Test.AgentChat.LangGraph.Setup

View File

@@ -0,0 +1,88 @@
# #region Test.AgentChat.Middleware [C:3] [TYPE Module] [SEMANTICS test,agent,middleware,audit]
# @BRIEF Tests for agent/middleware.py — log_tool_event.
# @RELATION BINDS_TO -> [AgentChat.Middleware]
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
from unittest.mock import MagicMock, patch
import pytest
# #region test_log_tool_event [C:2] [TYPE Function]
# @BRIEF Test log_tool_event for various event types.
class TestLogToolEvent:
@pytest.mark.asyncio
async def test_logs_tool_start(self):
from src.agent.middleware import log_tool_event
event = {
"event": "on_tool_start",
"name": "migrate",
"data": {"input": {"dashboard_id": "42"}},
}
with patch("src.agent.middleware.get_user_jwt", return_value="user-token"):
await log_tool_event(event, "conv-1")
# No exception = success
@pytest.mark.asyncio
async def test_logs_tool_end(self):
from src.agent.middleware import log_tool_event
event = {
"event": "on_tool_end",
"name": "migrate",
"data": {"output": "success"},
}
with patch("src.agent.middleware.get_user_jwt", return_value="user-token"):
await log_tool_event(event, "conv-1")
@pytest.mark.asyncio
async def test_logs_tool_error(self):
from src.agent.middleware import log_tool_event
event = {
"event": "on_tool_error",
"name": "migrate",
"data": {"error": "Connection failed"},
}
with patch("src.agent.middleware.get_user_jwt", return_value="user-token"):
await log_tool_event(event, "conv-1")
@pytest.mark.asyncio
async def test_logs_without_user_jwt(self):
from src.agent.middleware import log_tool_event
event = {
"event": "on_tool_start",
"name": "test_tool",
"data": {"input": {}},
}
with patch("src.agent.middleware.get_user_jwt", return_value=None):
await log_tool_event(event, "conv-1")
@pytest.mark.asyncio
async def test_handles_missing_data_key(self):
from src.agent.middleware import log_tool_event
event = {"event": "on_tool_start", "name": "test_tool"}
with patch("src.agent.middleware.get_user_jwt", return_value=None):
await log_tool_event(event, "conv-1")
@pytest.mark.asyncio
async def test_handles_unknown_event_kind(self):
from src.agent.middleware import log_tool_event
event = {"event": "on_custom_event", "name": "custom"}
with patch("src.agent.middleware.get_user_jwt", return_value=None):
await log_tool_event(event, "conv-1")
@pytest.mark.asyncio
async def test_truncates_long_input(self):
from src.agent.middleware import log_tool_event
long_input = "x" * 1000
event = {
"event": "on_tool_start",
"name": "big_tool",
"data": {"input": long_input},
}
with patch("src.agent.middleware.get_user_jwt", return_value="token"):
await log_tool_event(event, "conv-1")
# #endregion test_log_tool_event
# #endregion Test.AgentChat.Middleware

View File

@@ -0,0 +1,128 @@
# #region Test.AgentChat.Run [C:3] [TYPE Module] [SEMANTICS test,agent,run,entrypoint]
# @BRIEF Tests for agent/run.py — _find_free_port and _fetch_llm_config.
# @RELATION BINDS_TO -> [AgentChat.Run]
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
import socket
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
# #region test_find_free_port [C:2] [TYPE Function]
# @BRIEF Test _find_free_port for port scanning behavior.
class TestFindFreePort:
def test_returns_free_port(self):
from src.agent.run import _find_free_port
with patch("socket.socket") as mock_socket:
mock_instance = MagicMock()
mock_socket.return_value.__enter__.return_value = mock_instance
result = _find_free_port(8000, 10)
assert result == 8000
mock_instance.bind.assert_called_once_with(("", 8000))
def test_skips_busy_ports(self):
from src.agent.run import _find_free_port
with patch("socket.socket") as mock_socket:
mock_instance = MagicMock()
mock_socket.return_value.__enter__.return_value = mock_instance
# Ports 8000-8002 busy, 8003 free
mock_instance.bind.side_effect = [
OSError("Address in use"), # 8000
OSError("Address in use"), # 8001
OSError("Address in use"), # 8002
None, # 8003 — success
]
result = _find_free_port(8000, 10)
assert result == 8003
assert mock_instance.bind.call_count == 4
def test_raises_when_all_busy(self):
from src.agent.run import _find_free_port
with patch("socket.socket") as mock_socket:
mock_instance = MagicMock()
mock_socket.return_value.__enter__.return_value = mock_instance
mock_instance.bind.side_effect = OSError("Address in use")
with pytest.raises(OSError, match="No free port found"):
_find_free_port(8000, 3)
assert mock_instance.bind.call_count == 3
# #endregion test_find_free_port
# #region test_fetch_llm_config [C:2] [TYPE Function]
# @BRIEF Test _fetch_llm_config with retry and fallback behavior.
class TestFetchLlmConfig:
def test_returns_config_on_success(self):
from src.agent.run import _fetch_llm_config
with patch("src.agent.run.httpx.get") as mock_get:
mock_response = MagicMock()
mock_response.json.return_value = {"configured": True, "provider_type": "openai", "default_model": "gpt-4o"}
mock_get.return_value = mock_response
result = _fetch_llm_config()
assert result is not None
assert result["configured"] is True
def test_returns_none_when_not_configured(self):
from src.agent.run import _fetch_llm_config
with patch("src.agent.run.httpx.get") as mock_get:
mock_response = MagicMock()
mock_response.json.return_value = {"configured": False, "reason": "no provider"}
mock_get.return_value = mock_response
result = _fetch_llm_config()
assert result is None
def test_retries_on_failure(self):
from src.agent.run import _fetch_llm_config
import time as time_module
with patch("src.agent.run.httpx.get") as mock_get, \
patch.object(time_module, "sleep") as mock_sleep:
mock_get.side_effect = Exception("Connection refused")
result = _fetch_llm_config()
assert result is None
assert mock_get.call_count == 6
def test_retries_then_returns_config(self):
from src.agent.run import _fetch_llm_config
import time as time_module
with patch("src.agent.run.httpx.get") as mock_get, \
patch.object(time_module, "sleep") as mock_sleep:
mock_get.side_effect = [
Exception("Timeout"), # Attempt 1
Exception("Timeout"), # Attempt 2
MagicMock(json=lambda: {"configured": True, "provider_type": "openai"}), # Attempt 3
]
result = _fetch_llm_config()
assert result is not None
assert result["configured"] is True
def test_returns_none_after_max_retries_with_http_error(self):
from src.agent.run import _fetch_llm_config
import time as time_module
with patch("src.agent.run.httpx.get") as mock_get, \
patch.object(time_module, "sleep") as mock_sleep:
mock_response = MagicMock()
mock_response.raise_for_status.side_effect = Exception("HTTP 500")
mock_get.return_value = mock_response
result = _fetch_llm_config()
assert result is None
assert mock_get.call_count == 6
def test_uses_service_token_header(self):
from src.agent.run import _fetch_llm_config
with patch("src.agent.run.httpx.get") as mock_get, \
patch("src.agent.run.os.getenv", return_value="test-token"):
mock_response = MagicMock()
mock_response.json.return_value = {"configured": True}
mock_get.return_value = mock_response
result = _fetch_llm_config()
assert result is not None
# Verify Authorization header was sent
call_kwargs = mock_get.call_args[1]
assert call_kwargs["headers"].get("Authorization") == "Bearer test-token"
# #endregion test_fetch_llm_config
# #endregion Test.AgentChat.Run