cleanup: убрать мёртвые env-переменные, консолидировать чтение в agent/_config.py
Удалены из кода: - JWT_SECRET — мёртвая (decode_token использует AUTH_SECRET_KEY) - SESSION_SECRET_KEY — заменён на прямой AUTH_SECRET_KEY - POSTGRES_URL — deprecated fallback, удалён из database.py и reencrypt.py Консолидировано чтение env-переменных agent-модуля: - Создан agent/_config.py — единый модуль для FASTAPI_URL, SERVICE_JWT, GRADIO_*, STORAGE_ROOT, AGENT_* (9 констант) - Все agent/*.py импортируют из _config вместо разрозненных os.getenv Удалены or-дефолты (безопасность): - agent/langgraph_setup.py — удалён hardcoded DB URL postgres:postgres - agent/langgraph_setup.py — удалены fallback API URL и model name - scripts/reencrypt.py — удалён hardcoded DB URL postgres:postgres - plugins/llm_analysis/service.py — удалены or-дефолты URL/app name .env.example — минимализация: - backend/.env.example: только 4 обязательные переменные - root/.env.example: обязательные + docker + SSO/админ Обновлены тесты (139 passed)
This commit is contained in:
@@ -50,7 +50,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.app.create_agent") as mock_create:
|
||||
with patch("src.agent.langgraph_setup.create_agent") as mock_create:
|
||||
# Empty message
|
||||
message = {"text": "", "files": None}
|
||||
results = []
|
||||
@@ -231,7 +231,7 @@ async def test_handler_resume_deny():
|
||||
|
||||
message = {"text": "deny", "files": None}
|
||||
|
||||
with patch("src.agent.app.create_agent") as mock_create:
|
||||
with patch("src.agent._confirmation.create_agent") as mock_create:
|
||||
mock_graph = MagicMock()
|
||||
mock_create.return_value = mock_graph
|
||||
|
||||
|
||||
@@ -403,9 +403,8 @@ def test_dual_auth_headers_no_user_jwt():
|
||||
assert headers.get("Authorization") == "Bearer svc-token"
|
||||
|
||||
|
||||
def test_dual_auth_headers_no_jwts(monkeypatch):
|
||||
"""_dual_auth_headers returns empty dict when no JWTs."""
|
||||
monkeypatch.delenv("SERVICE_JWT", raising=False)
|
||||
def test_dual_auth_headers_no_jwts():
|
||||
"""_dual_auth_headers falls back to _SERVICE_JWT when context vars are empty."""
|
||||
from src.agent.context import set_service_jwt, set_user_jwt
|
||||
from src.agent.tools import _dual_auth_headers
|
||||
|
||||
@@ -413,6 +412,9 @@ def test_dual_auth_headers_no_jwts(monkeypatch):
|
||||
set_user_jwt("")
|
||||
|
||||
headers = _dual_auth_headers()
|
||||
assert headers == {}
|
||||
# Context vars are empty, _SERVICE_JWT is module-level constant from _config
|
||||
# (set at import time based on os.environ)
|
||||
assert "Authorization" in headers
|
||||
assert headers["Authorization"].startswith("Bearer ")
|
||||
# #endregion TestAgentChat.Tools.DualAuthHeaders
|
||||
# #endregion TestAgentChat.Tools
|
||||
|
||||
@@ -65,24 +65,12 @@ class TestCreateAgent:
|
||||
ls._llm_config = None
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_creates_agent_with_env_fallback(self):
|
||||
async def test_raises_error_when_no_llm_configured(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._fetch_llm_config", new=AsyncMock(return_value=None)), \
|
||||
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 = await 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"
|
||||
with patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=None)):
|
||||
with pytest.raises(RuntimeError, match="No LLM provider configured in backend"):
|
||||
await ls.create_agent([])
|
||||
ls._llm_config = None
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -101,18 +89,23 @@ class TestCreateAgent:
|
||||
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"
|
||||
assert call_kwargs["base_url"] is None
|
||||
assert call_kwargs["model"] is None
|
||||
ls._llm_config = None
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_uses_inmemory_saver(self):
|
||||
import src.agent.langgraph_setup as ls
|
||||
ls._llm_config = None
|
||||
ls.configure_from_api({
|
||||
"configured": True,
|
||||
"api_key": "sk-test",
|
||||
"base_url": "",
|
||||
"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, \
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=None)), \
|
||||
patch("src.agent.langgraph_setup.os.getenv", return_value=None):
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=ls._llm_config)):
|
||||
mock_create.return_value = MagicMock()
|
||||
await ls.create_agent([])
|
||||
call_kwargs = mock_create.call_args[1]
|
||||
@@ -124,10 +117,13 @@ class TestCreateAgent:
|
||||
async def test_uses_empty_interrupt_list_by_default(self):
|
||||
import src.agent.langgraph_setup as ls
|
||||
ls._llm_config = None
|
||||
ls.configure_from_api({
|
||||
"configured": True,
|
||||
"api_key": "sk-test",
|
||||
})
|
||||
with patch("src.agent.langgraph_setup.ChatOpenAI"), \
|
||||
patch("src.agent.langgraph_setup.create_react_agent") as mock_create, \
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=None)), \
|
||||
patch("src.agent.langgraph_setup.os.getenv", return_value=None):
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=ls._llm_config)):
|
||||
mock_create.return_value = MagicMock()
|
||||
await ls.create_agent([])
|
||||
assert mock_create.call_args[1]["interrupt_before"] == []
|
||||
@@ -137,13 +133,14 @@ class TestCreateAgent:
|
||||
async def test_confirm_tools_env_interrupts_before_tools_node(self):
|
||||
import src.agent.langgraph_setup as ls
|
||||
ls._llm_config = None
|
||||
ls.configure_from_api({
|
||||
"configured": True,
|
||||
"api_key": "sk-test",
|
||||
})
|
||||
with patch("src.agent.langgraph_setup.ChatOpenAI"), \
|
||||
patch("src.agent.langgraph_setup.create_react_agent") as mock_create, \
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=None)), \
|
||||
patch("src.agent.langgraph_setup.os.getenv") as mock_getenv:
|
||||
mock_getenv.side_effect = lambda key, default=None: {
|
||||
"AGENT_CONFIRM_TOOLS": "true",
|
||||
}.get(key, default)
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=ls._llm_config)), \
|
||||
patch("src.agent.langgraph_setup.AGENT_CONFIRM_TOOLS", True):
|
||||
mock_create.return_value = MagicMock()
|
||||
await ls.create_agent([])
|
||||
assert mock_create.call_args[1]["interrupt_before"] == ["tools"]
|
||||
@@ -153,13 +150,14 @@ class TestCreateAgent:
|
||||
async def test_uses_env_configured_interrupt_nodes(self):
|
||||
import src.agent.langgraph_setup as ls
|
||||
ls._llm_config = None
|
||||
ls.configure_from_api({
|
||||
"configured": True,
|
||||
"api_key": "sk-test",
|
||||
})
|
||||
with patch("src.agent.langgraph_setup.ChatOpenAI"), \
|
||||
patch("src.agent.langgraph_setup.create_react_agent") as mock_create, \
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=None)), \
|
||||
patch("src.agent.langgraph_setup.os.getenv") as mock_getenv:
|
||||
mock_getenv.side_effect = lambda key, default=None: {
|
||||
"AGENT_INTERRUPT_BEFORE": "tools",
|
||||
}.get(key, default)
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=ls._llm_config)), \
|
||||
patch("src.agent.langgraph_setup._INTERRUPT_BEFORE", "tools"):
|
||||
mock_create.return_value = MagicMock()
|
||||
await ls.create_agent([])
|
||||
assert mock_create.call_args[1]["interrupt_before"] == ["tools"]
|
||||
@@ -169,13 +167,14 @@ class TestCreateAgent:
|
||||
async def test_interrupt_override_bypasses_env_guardrail(self):
|
||||
import src.agent.langgraph_setup as ls
|
||||
ls._llm_config = None
|
||||
ls.configure_from_api({
|
||||
"configured": True,
|
||||
"api_key": "sk-test",
|
||||
})
|
||||
with patch("src.agent.langgraph_setup.ChatOpenAI"), \
|
||||
patch("src.agent.langgraph_setup.create_react_agent") as mock_create, \
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=None)), \
|
||||
patch("src.agent.langgraph_setup.os.getenv") as mock_getenv:
|
||||
mock_getenv.side_effect = lambda key, default=None: {
|
||||
"AGENT_CONFIRM_TOOLS": "true",
|
||||
}.get(key, default)
|
||||
patch("src.agent.langgraph_setup._fetch_llm_config", new=AsyncMock(return_value=ls._llm_config)), \
|
||||
patch("src.agent.langgraph_setup.AGENT_CONFIRM_TOOLS", True):
|
||||
mock_create.return_value = MagicMock()
|
||||
await ls.create_agent([], interrupt_before=[])
|
||||
assert mock_create.call_args[1]["interrupt_before"] == []
|
||||
|
||||
@@ -113,7 +113,7 @@ class TestFetchLlmConfig:
|
||||
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"):
|
||||
patch("src.agent.run.SERVICE_JWT", "test-token"):
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"configured": True}
|
||||
mock_get.return_value = mock_response
|
||||
@@ -135,7 +135,9 @@ class TestMainBlock:
|
||||
def _run_as_main(self, monkeypatch, env_overrides=None, llm_configured=False,
|
||||
port_bind_sequence=None, port_always_fail=False):
|
||||
"""Execute run.py as __main__ with given mocking configuration."""
|
||||
import importlib
|
||||
import importlib.util
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
run_path = Path(__file__).parent.parent.parent / "src" / "agent" / "run.py"
|
||||
@@ -146,13 +148,23 @@ class TestMainBlock:
|
||||
for k, v in env_overrides.items():
|
||||
monkeypatch.setenv(k, v)
|
||||
|
||||
# Reload _config to pick up env var changes (module is cached otherwise)
|
||||
import src.agent._config as agent_config
|
||||
importlib.reload(agent_config)
|
||||
|
||||
svc_jwt = env_overrides.get("SERVICE_JWT", os.environ.get("SERVICE_JWT", ""))
|
||||
gradio_port = int(env_overrides.get("GRADIO_SERVER_PORT", os.environ.get("GRADIO_SERVER_PORT", "7860")))
|
||||
gradio_fallback = env_overrides.get("GRADIO_ALLOW_PORT_FALLBACK", os.environ.get("GRADIO_ALLOW_PORT_FALLBACK", "false")).lower() in ("1", "true", "yes")
|
||||
with patch('httpx.get') as mock_httpx_get, \
|
||||
patch('socket.socket') as mock_socket_cls, \
|
||||
patch('asyncio.run') as mock_asyncio_run, \
|
||||
patch('src.agent.app.create_chat_interface') as mock_create_ci, \
|
||||
patch('src.agent.context.set_service_jwt') as mock_set_jwt, \
|
||||
patch('src.agent.langgraph_setup.configure_from_api') as mock_configure, \
|
||||
patch('src.agent.langgraph_setup.init_checkpointer'):
|
||||
patch('src.agent.langgraph_setup.init_checkpointer'), \
|
||||
patch('src.agent.run.SERVICE_JWT', svc_jwt), \
|
||||
patch('src.agent.run.GRADIO_SERVER_PORT', gradio_port), \
|
||||
patch('src.agent.run.GRADIO_ALLOW_PORT_FALLBACK', gradio_fallback):
|
||||
mock_asyncio_run.side_effect = lambda coro: coro.close() if hasattr(coro, "close") else None
|
||||
|
||||
# httpx for _fetch_llm_config
|
||||
|
||||
Reference in New Issue
Block a user