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)
129 lines
5.5 KiB
Python
129 lines
5.5 KiB
Python
# #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
|