simplify(compat): plugins/platforms+web — drop 7 re-exports + 3 aliases, repoint 1 caller + 11 tests, re-remove credential_summary
dingtalk: drop DINGTALK_TYPE_MAPPING/EXT_MAP re-exports. google_chat: card_spec_to_cards_v2 test -> .cards.
matrix: drop module-level MAX_MESSAGE_LENGTH alias (no importers). teams: drop TeamsSummaryWriter re-export
(teams_pipeline/runtime + tests -> summary_writer). wecom: drop WeComStreamExpiredError/STREAM_EXPIRED_ERRCODE/
MAX_INTERMEDIATE_FRAMES re-exports (tests -> .streaming). parallel: drop _get_parallel_client/_get_async_parallel_client
aliases (tests -> _get_sync_client). email: drop stale 'alias' comment (_esecret_int is the only name).
photon: re-remove credential_summary() (shim-only, cb9b7c36f3); its no-leak test now drives print_credential_summary.
This commit is contained in:
@@ -50,13 +50,7 @@ from gateway.config import Platform, PlatformConfig
|
||||
from gateway.platforms.helpers import MessageDeduplicator, compile_mention_patterns
|
||||
from gateway.platforms.base import BasePlatformAdapter, MessageEvent, SendResult
|
||||
from gateway.platforms._shared import get_scoped_secret as _get_scoped_secret
|
||||
from plugins.platforms.dingtalk.inbound import ( # noqa: F401 — re-exported names
|
||||
DINGTALK_TYPE_MAPPING,
|
||||
EXT_MAP,
|
||||
collect_download_codes,
|
||||
extract_media,
|
||||
extract_text,
|
||||
)
|
||||
from plugins.platforms.dingtalk.inbound import collect_download_codes, extract_media, extract_text
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -54,7 +54,6 @@ _AUTH_METHOD_RE = re.compile(r"\b(dmarc|dkim|spf)\s*=\s*([a-z]+)", re.IGNORECASE
|
||||
_AUTH_PROP_RE = re.compile(r"\b(header\.from|header\.d|smtp\.mailfrom|smtp\.from|envelope-from)\s*=\s*([^\s;]+)", re.IGNORECASE)
|
||||
|
||||
|
||||
# Backwards-compatible alias for the name used by the original #59076 hunks.
|
||||
def _esecret_int(name: str, default: int) -> int:
|
||||
"""Scope-aware integer read."""
|
||||
return coerce_port(str(_get_secret(name, "")).strip() or default, default)
|
||||
|
||||
@@ -26,7 +26,7 @@ from urllib.parse import urlparse
|
||||
from agent.secret_scope import is_multiplex_active
|
||||
from gateway.platforms._shared import get_scoped_secret as _get_scoped_secret
|
||||
|
||||
from .cards import card_spec_to_cards_v2, format_message as _format_message # noqa: F401 (re-exported)
|
||||
from .cards import card_spec_to_cards_v2, format_message as _format_message
|
||||
|
||||
|
||||
def _adc_would_borrow_foreign_credentials() -> bool:
|
||||
|
||||
@@ -352,8 +352,6 @@ def _resolve_max_message_length(config) -> int:
|
||||
return max(500, min(value, MATRIX_MAX_MESSAGE_LENGTH_CEILING))
|
||||
|
||||
|
||||
MAX_MESSAGE_LENGTH = DEFAULT_MAX_MESSAGE_LENGTH # back-compat alias for importers of the module constant
|
||||
|
||||
# E2EE store dir is resolved per adapter in connect() (``_resolve_store_dir``), NOT at module scope:
|
||||
# the multiplex gateway imports this once and a module constant would collide every profile's Olm
|
||||
# identity in one crypto.db.
|
||||
|
||||
@@ -713,36 +713,3 @@ def print_credential_summary(emit: Any = print) -> None:
|
||||
" my number : " + (phone if phone else "✗ missing (run `hermes photon setup --phone ...`)"),
|
||||
" assigned number : " + (assigned if assigned else "✗ missing (run `hermes photon setup`)")]
|
||||
emit("\n".join(rows))
|
||||
|
||||
|
||||
def credential_summary() -> Dict[str, str]:
|
||||
"""Return a fully pre-formatted credential status dict (no raw secrets)."""
|
||||
def _present_token() -> str:
|
||||
return (
|
||||
"✓ stored" if load_photon_token()
|
||||
else "✗ missing (run `hermes photon setup`)"
|
||||
)
|
||||
|
||||
def _present_project_id() -> str:
|
||||
sid, _sec = load_project_credentials()
|
||||
return sid or "✗ missing"
|
||||
|
||||
def _present_secret() -> str:
|
||||
_sid, sec = load_project_credentials()
|
||||
return "✓ stored" if sec else "✗ missing"
|
||||
|
||||
def _present_phone() -> str:
|
||||
phone, _assigned = load_user_numbers()
|
||||
return phone or "✗ missing (run `hermes photon setup --phone ...`)"
|
||||
|
||||
def _present_assigned_phone() -> str:
|
||||
_phone, assigned = load_user_numbers()
|
||||
return assigned or "✗ missing (run `hermes photon setup`)"
|
||||
|
||||
return {
|
||||
"device_token": _present_token(),
|
||||
"project_id": _present_project_id(),
|
||||
"project_key": _present_secret(),
|
||||
"phone_number": _present_phone(),
|
||||
"assigned_phone_number": _present_assigned_phone(),
|
||||
}
|
||||
|
||||
@@ -58,7 +58,6 @@ from gateway.platforms.base import (
|
||||
gateway_trust_env, BasePlatformAdapter, MessageEvent, MessageType, SendResult, cache_image_from_url, cache_media_bytes_async,
|
||||
)
|
||||
from gateway.platforms._shared import coerce_port, get_scoped_secret as _get_scoped_secret
|
||||
from plugins.platforms.teams.summary_writer import TeamsSummaryWriter # noqa: F401 — re-exported for teams_pipeline
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -251,7 +250,7 @@ _SDK_IMPORTS = {
|
||||
"microsoft_teams.cards": ("AdaptiveCard", "ExecuteAction", "TextBlock")}
|
||||
|
||||
|
||||
# Keep the old name as an alias so existing test imports don't break. NOTE: ``check_requirements`` is the
|
||||
# NOTE: ``check_requirements`` is the
|
||||
# PASSIVE probe (registry ``check_fn``, status / unit tests) — it must never trigger a pip install.
|
||||
# ``check_teams_requirements`` is the ACTIVE lazy-installer, registered as ``ensure_deps_fn``: the
|
||||
# registry's ``create_adapter()`` runs it when the passive probe fails, right before the gateway connects
|
||||
|
||||
@@ -34,9 +34,9 @@ from utils import env_float
|
||||
from gateway.platforms._shared import get_scoped_secret as _get_scoped_secret
|
||||
from plugins.platforms.wecom.send_queue import ChatSendQueueMixin
|
||||
from plugins.platforms.wecom.media import WeComMediaMixin, APP_CMD_SEND
|
||||
from plugins.platforms.wecom.streaming import ( # noqa: F401 — re-exported for tests/stream_consumer
|
||||
WeComStreamMixin, WeComStreamExpiredError, ReplyQueue, StreamTurn, APP_CMD_RESPONSE,
|
||||
STREAM_EXPIRED_ERRCODE, STREAM_NOT_SUBSCRIBED_ERRCODE, MAX_STREAM_CONTENT_LENGTH, MAX_INTERMEDIATE_FRAMES,
|
||||
from plugins.platforms.wecom.streaming import (
|
||||
WeComStreamMixin, ReplyQueue, StreamTurn, APP_CMD_RESPONSE,
|
||||
STREAM_NOT_SUBSCRIBED_ERRCODE, MAX_STREAM_CONTENT_LENGTH,
|
||||
STREAM_SAFE_DURATION_SECONDS, STREAM_KEEPALIVE_INTERVAL_SECONDS, STREAM_KEEPALIVE_ENABLED_DEFAULT,
|
||||
)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ def build_pipeline_runtime(gateway: Any) -> TeamsMeetingPipeline:
|
||||
pipeline_config = build_pipeline_runtime_config(gateway.config)
|
||||
if teams_config and teams_config.enabled and (pipeline_config.get("teams_delivery") or {}).get("enabled"):
|
||||
try:
|
||||
from plugins.platforms.teams.adapter import TeamsSummaryWriter
|
||||
from plugins.platforms.teams.summary_writer import TeamsSummaryWriter
|
||||
except ImportError:
|
||||
logger.debug("TeamsSummaryWriter unavailable; Teams outbound delivery remains disabled until the adapter layer is present.")
|
||||
else:
|
||||
|
||||
@@ -37,10 +37,6 @@ def _get_async_client() -> Any:
|
||||
return _client("_async_parallel_client", "AsyncParallel")
|
||||
|
||||
|
||||
# Names re-exported by tools.web_tools for existing tests/callers.
|
||||
_get_parallel_client, _get_async_parallel_client = _get_sync_client, _get_async_client
|
||||
|
||||
|
||||
def _resolve_search_mode() -> str:
|
||||
mode = os.getenv("PARALLEL_SEARCH_MODE", "agentic").lower().strip()
|
||||
return mode if mode in {"fast", "one-shot", "agentic"} else "agentic"
|
||||
|
||||
@@ -135,9 +135,9 @@ from plugins.platforms.google_chat.adapter import ( # noqa: E402
|
||||
_is_google_owned_host,
|
||||
_mime_for_message_type,
|
||||
_redact_sensitive,
|
||||
card_spec_to_cards_v2,
|
||||
check_google_chat_requirements,
|
||||
)
|
||||
from plugins.platforms.google_chat.cards import card_spec_to_cards_v2 # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -1735,7 +1735,7 @@ class TestCronSchedulerRegistry:
|
||||
|
||||
def test_google_chat_is_known_delivery_platform(self):
|
||||
self._ensure_registered()
|
||||
from cron.scheduler import _is_known_delivery_platform
|
||||
from cron.scheduler_delivery import _is_known_delivery_platform
|
||||
|
||||
assert _is_known_delivery_platform("google_chat") is True
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ if _mt and _teams_mod.TypingActivityInput is None:
|
||||
_teams_mod.TypingActivityInput = _mt.TypingActivityInput
|
||||
|
||||
TeamsAdapter = _teams_mod.TeamsAdapter
|
||||
TeamsSummaryWriter = _teams_mod.TeamsSummaryWriter
|
||||
from plugins.platforms.teams.summary_writer import TeamsSummaryWriter # noqa: E402
|
||||
check_requirements = _teams_mod.check_requirements
|
||||
check_teams_requirements = _teams_mod.check_teams_requirements
|
||||
validate_config = _teams_mod.validate_config
|
||||
|
||||
@@ -162,7 +162,7 @@ class TestTeamsAdapterImportDoesNotLeakDotenv:
|
||||
_install_fake_teams_sdk(monkeypatch)
|
||||
_purge_teams_adapter_modules()
|
||||
|
||||
from plugins.platforms.teams.adapter import TeamsSummaryWriter
|
||||
from plugins.platforms.teams.summary_writer import TeamsSummaryWriter
|
||||
|
||||
assert CANARY_KEY not in os.environ
|
||||
assert TeamsSummaryWriter is not None
|
||||
|
||||
@@ -920,7 +920,7 @@ class TestWeComNativeStreamingCapability:
|
||||
assert WeComAdapter.MAX_STREAM_CONTENT_LENGTH == 20480
|
||||
|
||||
def test_stream_expired_errcode_constant(self):
|
||||
from plugins.platforms.wecom.adapter import STREAM_EXPIRED_ERRCODE
|
||||
from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE
|
||||
|
||||
assert STREAM_EXPIRED_ERRCODE == 846608
|
||||
|
||||
@@ -1078,7 +1078,8 @@ class TestSendStreamFrame:
|
||||
@pytest.mark.asyncio
|
||||
async def test_intermediate_frame_cap_drops_excess(self):
|
||||
"""After MAX_INTERMEDIATE_FRAMES, further intermediate frames are dropped."""
|
||||
from plugins.platforms.wecom.adapter import WeComAdapter, MAX_INTERMEDIATE_FRAMES
|
||||
from plugins.platforms.wecom.adapter import WeComAdapter
|
||||
from plugins.platforms.wecom.streaming import MAX_INTERMEDIATE_FRAMES
|
||||
|
||||
adapter = WeComAdapter(PlatformConfig(enabled=True))
|
||||
adapter._last_chat_req_ids["chat-1"] = "req-1"
|
||||
@@ -1179,9 +1180,8 @@ class TestSendStreamFrameFailures:
|
||||
@pytest.mark.asyncio
|
||||
async def test_846608_marks_chat_expired_and_returns_false(self):
|
||||
"""846608 on finalize frame marks the chat expired and returns False."""
|
||||
from plugins.platforms.wecom.adapter import (
|
||||
STREAM_EXPIRED_ERRCODE, WeComAdapter,
|
||||
)
|
||||
from plugins.platforms.wecom.adapter import WeComAdapter
|
||||
from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE
|
||||
|
||||
adapter = WeComAdapter(PlatformConfig(enabled=True))
|
||||
adapter._last_chat_req_ids["chat-1"] = "req-1"
|
||||
@@ -1392,7 +1392,8 @@ class TestSendClosesActiveStream:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_falls_through_when_stream_expired(self):
|
||||
from plugins.platforms.wecom.adapter import STREAM_EXPIRED_ERRCODE, WeComAdapter
|
||||
from plugins.platforms.wecom.adapter import WeComAdapter
|
||||
from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE
|
||||
|
||||
adapter = WeComAdapter(PlatformConfig(enabled=True))
|
||||
adapter._last_chat_req_ids["chat-1"] = "req-1"
|
||||
|
||||
@@ -106,7 +106,8 @@ class TestPerTurnStreamIsolation:
|
||||
@pytest.mark.asyncio
|
||||
async def test_one_user_expired_others_unaffected(self):
|
||||
"""User A hits stream expired; Users B and C continue normally."""
|
||||
from plugins.platforms.wecom.adapter import STREAM_EXPIRED_ERRCODE, WeComAdapter
|
||||
from plugins.platforms.wecom.adapter import WeComAdapter
|
||||
from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE
|
||||
|
||||
adapter = WeComAdapter(PlatformConfig(enabled=True))
|
||||
try:
|
||||
@@ -155,7 +156,8 @@ class TestPerTurnStreamIsolation:
|
||||
@pytest.mark.asyncio
|
||||
async def test_one_turn_expired_other_continues(self):
|
||||
"""When one turn hits stream expired, other concurrent turns can continue."""
|
||||
from plugins.platforms.wecom.adapter import STREAM_EXPIRED_ERRCODE, WeComAdapter
|
||||
from plugins.platforms.wecom.adapter import WeComAdapter
|
||||
from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE
|
||||
|
||||
adapter = WeComAdapter(PlatformConfig(enabled=True))
|
||||
try:
|
||||
|
||||
@@ -33,11 +33,8 @@ from unittest.mock import AsyncMock
|
||||
import pytest
|
||||
|
||||
from gateway.config import PlatformConfig
|
||||
from plugins.platforms.wecom.adapter import (
|
||||
WeComAdapter,
|
||||
WeComStreamExpiredError,
|
||||
STREAM_EXPIRED_ERRCODE,
|
||||
)
|
||||
from plugins.platforms.wecom.adapter import WeComAdapter
|
||||
from plugins.platforms.wecom.streaming import STREAM_EXPIRED_ERRCODE, WeComStreamExpiredError
|
||||
|
||||
|
||||
CHAT_ID = "chat-dup"
|
||||
|
||||
@@ -340,16 +340,17 @@ def test_credential_summary_no_secret_leak(
|
||||
project_secret="secret-bbbbbbbbbbb",
|
||||
dashboard_project_id="dash-uuid",
|
||||
)
|
||||
summary = photon_auth.credential_summary()
|
||||
blob = "\n".join(summary.values())
|
||||
lines: list[str] = []
|
||||
photon_auth.print_credential_summary(lines.append)
|
||||
blob = "\n".join(lines)
|
||||
assert "token-aaaa" not in blob
|
||||
assert "secret-bbbb" not in blob
|
||||
assert summary["device_token"].startswith("✓")
|
||||
assert summary["project_key"].startswith("✓")
|
||||
assert "device token : ✓" in blob
|
||||
assert "project secret : ✓" in blob
|
||||
# Unified id: dashboard id == Spectrum id, surfaced as one project id.
|
||||
assert summary["project_id"] == "sp-uuid"
|
||||
assert summary["phone_number"].startswith("✗ missing")
|
||||
assert summary["assigned_phone_number"].startswith("✗ missing")
|
||||
assert "project id : sp-uuid" in blob
|
||||
assert "my number : ✗ missing" in blob
|
||||
assert "assigned number : ✗ missing" in blob
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -99,7 +99,7 @@ def test_build_pipeline_runtime_reuses_existing_teams_adapter_surface(monkeypatc
|
||||
|
||||
monkeypatch.setattr(runtime_module, "build_graph_client", lambda: object())
|
||||
monkeypatch.setattr(runtime_module, "resolve_teams_pipeline_store_path", lambda: tmp_path / "teams-store.json")
|
||||
monkeypatch.setattr("plugins.platforms.teams.adapter.TeamsSummaryWriter", FakeWriter)
|
||||
monkeypatch.setattr("plugins.platforms.teams.summary_writer.TeamsSummaryWriter", FakeWriter)
|
||||
|
||||
gateway = SimpleNamespace(
|
||||
config=GatewayConfig(
|
||||
|
||||
@@ -4,7 +4,7 @@ Coverage:
|
||||
_get_firecrawl_client() — configuration matrix, singleton caching,
|
||||
constructor failure recovery, return value verification, edge cases.
|
||||
_get_backend() — backend selection logic with env var combinations.
|
||||
_get_parallel_client() — Parallel client configuration, singleton caching.
|
||||
plugins.web.parallel.provider._get_sync_client() — Parallel client configuration, singleton caching.
|
||||
check_web_api_key() — unified availability check across all web backends.
|
||||
"""
|
||||
|
||||
@@ -35,10 +35,10 @@ class TestFirecrawlClientConfig:
|
||||
):
|
||||
os.environ.pop(key, None)
|
||||
# Enable managed tools by default for these tests — patch both the
|
||||
# local web_tools import and the managed_tool_gateway import so the
|
||||
# tool_backend_helpers definition and the managed_tool_gateway import so the
|
||||
# full firecrawl client init path sees True.
|
||||
self._managed_patchers = [
|
||||
patch("tools.web_tools.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.tool_backend_helpers.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True),
|
||||
]
|
||||
for p in self._managed_patchers:
|
||||
@@ -65,18 +65,18 @@ class TestFirecrawlClientConfig:
|
||||
|
||||
def test_no_config_raises_with_helpful_message(self):
|
||||
"""Neither key nor URL → ValueError with guidance."""
|
||||
with patch("tools.web_tools.Firecrawl"):
|
||||
with patch("tools.web_tools._read_nous_access_token", return_value=None):
|
||||
from tools.web_tools import _get_firecrawl_client
|
||||
with patch("plugins.web.firecrawl.provider.Firecrawl"):
|
||||
with patch("tools.managed_tool_gateway.read_nous_access_token", return_value=None):
|
||||
from plugins.web.firecrawl.provider import _get_firecrawl_client
|
||||
with pytest.raises(ValueError, match="FIRECRAWL_API_KEY"):
|
||||
_get_firecrawl_client()
|
||||
|
||||
def test_tool_gateway_domain_builds_firecrawl_gateway_origin(self):
|
||||
"""Shared gateway domain should derive the Firecrawl vendor hostname."""
|
||||
with patch.dict(os.environ, {"TOOL_GATEWAY_DOMAIN": "nousresearch.com"}):
|
||||
with patch("tools.web_tools._read_nous_access_token", return_value="nous-token"):
|
||||
with patch("tools.web_tools.Firecrawl") as mock_fc:
|
||||
from tools.web_tools import _get_firecrawl_client
|
||||
with patch("tools.managed_tool_gateway.read_nous_access_token", return_value="nous-token"):
|
||||
with patch("plugins.web.firecrawl.provider.Firecrawl") as mock_fc:
|
||||
from plugins.web.firecrawl.provider import _get_firecrawl_client
|
||||
result = _get_firecrawl_client()
|
||||
mock_fc.assert_called_once_with(
|
||||
api_key="nous-token",
|
||||
@@ -92,9 +92,9 @@ class TestFirecrawlClientConfig:
|
||||
"""If Firecrawl() raises, next call should retry (not return None)."""
|
||||
import tools.web_tools
|
||||
with patch.dict(os.environ, {"FIRECRAWL_API_KEY": "fc-test"}):
|
||||
with patch("tools.web_tools.Firecrawl") as mock_fc:
|
||||
with patch("plugins.web.firecrawl.provider.Firecrawl") as mock_fc:
|
||||
mock_fc.side_effect = [RuntimeError("init failed"), MagicMock()]
|
||||
from tools.web_tools import _get_firecrawl_client
|
||||
from plugins.web.firecrawl.provider import _get_firecrawl_client
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
_get_firecrawl_client()
|
||||
@@ -109,9 +109,9 @@ class TestFirecrawlClientConfig:
|
||||
def test_empty_string_key_no_url_raises(self):
|
||||
"""FIRECRAWL_API_KEY='' with no URL → should raise."""
|
||||
with patch.dict(os.environ, {"FIRECRAWL_API_KEY": ""}):
|
||||
with patch("tools.web_tools.Firecrawl"):
|
||||
with patch("tools.web_tools._read_nous_access_token", return_value=None):
|
||||
from tools.web_tools import _get_firecrawl_client
|
||||
with patch("plugins.web.firecrawl.provider.Firecrawl"):
|
||||
with patch("tools.managed_tool_gateway.read_nous_access_token", return_value=None):
|
||||
from plugins.web.firecrawl.provider import _get_firecrawl_client
|
||||
with pytest.raises(ValueError):
|
||||
_get_firecrawl_client()
|
||||
|
||||
@@ -120,9 +120,9 @@ class TestFirecrawlClientConfig:
|
||||
from plugins.web.firecrawl import provider as firecrawl_provider
|
||||
|
||||
with patch("tools.web_tools._load_web_config", return_value={"backend": "firecrawl"}):
|
||||
with patch("tools.web_tools._read_nous_access_token", return_value=None):
|
||||
with patch("tools.web_tools.Firecrawl", side_effect=AssertionError("SDK path should not run")):
|
||||
from tools.web_tools import _get_firecrawl_client
|
||||
with patch("tools.managed_tool_gateway.read_nous_access_token", return_value=None):
|
||||
with patch("plugins.web.firecrawl.provider.Firecrawl", side_effect=AssertionError("SDK path should not run")):
|
||||
from plugins.web.firecrawl.provider import _get_firecrawl_client
|
||||
|
||||
result = _get_firecrawl_client()
|
||||
|
||||
@@ -217,7 +217,7 @@ class TestBackendSelection:
|
||||
for key in self._ENV_KEYS:
|
||||
os.environ.pop(key, None)
|
||||
self._managed_patchers = [
|
||||
patch("tools.web_tools.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.tool_backend_helpers.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True),
|
||||
]
|
||||
for p in self._managed_patchers:
|
||||
@@ -412,7 +412,7 @@ class TestParallelClientConfig:
|
||||
def test_creates_client_with_key(self):
|
||||
"""PARALLEL_API_KEY set → creates Parallel client."""
|
||||
with patch.dict(os.environ, {"PARALLEL_API_KEY": "test-key"}):
|
||||
from tools.web_tools import _get_parallel_client
|
||||
from plugins.web.parallel.provider import _get_sync_client as _get_parallel_client
|
||||
from parallel import Parallel
|
||||
client = _get_parallel_client()
|
||||
assert client is not None
|
||||
@@ -420,14 +420,14 @@ class TestParallelClientConfig:
|
||||
|
||||
def test_no_key_raises_with_helpful_message(self):
|
||||
"""No PARALLEL_API_KEY → ValueError with guidance."""
|
||||
from tools.web_tools import _get_parallel_client
|
||||
from plugins.web.parallel.provider import _get_sync_client as _get_parallel_client
|
||||
with pytest.raises(ValueError, match="PARALLEL_API_KEY"):
|
||||
_get_parallel_client()
|
||||
|
||||
def test_singleton_returns_same_instance(self):
|
||||
"""Second call returns cached client."""
|
||||
with patch.dict(os.environ, {"PARALLEL_API_KEY": "test-key"}):
|
||||
from tools.web_tools import _get_parallel_client
|
||||
from plugins.web.parallel.provider import _get_sync_client as _get_parallel_client
|
||||
client1 = _get_parallel_client()
|
||||
client2 = _get_parallel_client()
|
||||
assert client1 is client2
|
||||
@@ -531,7 +531,7 @@ class TestCheckWebApiKey:
|
||||
for key in self._ENV_KEYS:
|
||||
os.environ.pop(key, None)
|
||||
self._managed_patchers = [
|
||||
patch("tools.web_tools.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.tool_backend_helpers.managed_nous_tools_enabled", return_value=True),
|
||||
patch("tools.managed_tool_gateway.managed_nous_tools_enabled", return_value=True),
|
||||
# ddgs availability is package-presence driven and the plugin
|
||||
# registry can hold an available ddgs provider. Neutralize both
|
||||
@@ -571,7 +571,7 @@ class TestCheckWebApiKey:
|
||||
|
||||
def test_configured_firecrawl_backend_accepts_managed_gateway(self):
|
||||
with patch("tools.web_tools._load_web_config", return_value={"backend": "firecrawl"}):
|
||||
with patch("tools.web_tools._peek_nous_access_token", return_value="nous-token"):
|
||||
with patch("tools.managed_tool_gateway.peek_nous_access_token", return_value="nous-token"):
|
||||
with patch.dict(os.environ, {"FIRECRAWL_GATEWAY_URL": "http://127.0.0.1:3002"}, clear=False):
|
||||
from tools.web_tools import check_web_api_key
|
||||
assert check_web_api_key() is True
|
||||
@@ -709,7 +709,7 @@ class TestNonBuiltinProviderAvailability:
|
||||
"""With only a custom provider registered (no built-in creds),
|
||||
check_web_api_key() must return True."""
|
||||
with patch("tools.web_tools._ddgs_package_importable", return_value=False), \
|
||||
patch("tools.web_tools._peek_nous_access_token", return_value=None):
|
||||
patch("tools.managed_tool_gateway.peek_nous_access_token", return_value=None):
|
||||
from tools.web_tools import check_web_api_key
|
||||
assert check_web_api_key() is True
|
||||
|
||||
@@ -717,7 +717,7 @@ class TestNonBuiltinProviderAvailability:
|
||||
"""_get_backend() must return the custom provider name when it's
|
||||
the only available provider."""
|
||||
with patch("tools.web_tools._ddgs_package_importable", return_value=False), \
|
||||
patch("tools.web_tools._peek_nous_access_token", return_value=None):
|
||||
patch("tools.managed_tool_gateway.peek_nous_access_token", return_value=None):
|
||||
from tools.web_tools import _get_backend
|
||||
assert _get_backend() == "fake-plugin-prov"
|
||||
|
||||
@@ -726,7 +726,7 @@ class TestNonBuiltinProviderAvailability:
|
||||
"""Per-capability selection (_get_extract_backend) must resolve the
|
||||
custom provider when configured, instead of dead-ending — issue #32698."""
|
||||
with patch("tools.web_tools._ddgs_package_importable", return_value=False), \
|
||||
patch("tools.web_tools._peek_nous_access_token", return_value=None), \
|
||||
patch("tools.managed_tool_gateway.peek_nous_access_token", return_value=None), \
|
||||
patch("tools.web_tools._load_web_config",
|
||||
return_value={"extract_backend": "fake-plugin-prov"}):
|
||||
from tools.web_tools import _get_extract_backend
|
||||
@@ -736,7 +736,7 @@ class TestNonBuiltinProviderAvailability:
|
||||
"""web_search and web_extract tool entries must remain in the
|
||||
registry when only a custom provider is available."""
|
||||
with patch("tools.web_tools._ddgs_package_importable", return_value=False), \
|
||||
patch("tools.web_tools._peek_nous_access_token", return_value=None):
|
||||
patch("tools.managed_tool_gateway.peek_nous_access_token", return_value=None):
|
||||
import tools.web_tools
|
||||
web_search_entry = tools.web_tools.registry.get_entry("web_search")
|
||||
web_extract_entry = tools.web_tools.registry.get_entry("web_extract")
|
||||
|
||||
Reference in New Issue
Block a user