Compare commits

..

3 Commits

Author SHA1 Message Date
GareArc
2f12505386 fix: export telemetry routing constants for tests
Some checks are pending
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Waiting to run
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Waiting to run
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Waiting to run
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Blocked by required conditions
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Blocked by required conditions
Export CASE_ROUTING and CASE_TO_TRACE_TASK as module-level constants
to fix import errors in enterprise telemetry tests.

Tests were failing with:
- ImportError: cannot import name 'CASE_ROUTING' from 'core.telemetry.gateway'
- ImportError: cannot import name 'CASE_TO_TRACE_TASK' from 'core.telemetry.gateway'

This fix allows tests to access the routing configuration without breaking
the lazy-loading pattern used internally.
2026-03-11 23:40:01 -07:00
Yunlu Wen
5c018184e7 feat: sync pull 33158 (#33343) 2026-03-12 12:57:51 +08:00
GareArc
b4227b195e Squash merge fix/enterprise-api-error-handling into release/e-1.12.1
Some checks failed
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Has been cancelled
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Has been cancelled
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Has been cancelled
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Has been cancelled
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Has been cancelled
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Has been cancelled
2026-03-11 19:01:45 -07:00
5 changed files with 71 additions and 50 deletions

View File

@@ -19,6 +19,10 @@ class EnterpriseFeatureConfig(BaseSettings):
default=False,
)
ENTERPRISE_REQUEST_TIMEOUT: int = Field(
ge=1, description="Maximum timeout in seconds for enterprise requests", default=5
)
class EnterpriseTelemetryConfig(BaseSettings):
"""

View File

@@ -60,7 +60,6 @@ def get_trace_task_to_case() -> dict:
"""Return TraceTaskName → TelemetryCase (inverse of _get_case_to_trace_task)."""
return {v: k for k, v in _get_case_to_trace_task().items()}
def _get_case_routing() -> dict:
global _case_routing
if _case_routing is None:
@@ -91,7 +90,6 @@ def _get_case_routing() -> dict:
CASE_TO_TRACE_TASK = _get_case_to_trace_task()
CASE_ROUTING = _get_case_routing()
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

View File

@@ -3,6 +3,7 @@ import logging
from pydantic import BaseModel
from configs import dify_config
from services.enterprise.base import EnterprisePluginManagerRequest
from services.errors.base import BaseServiceError
@@ -69,6 +70,7 @@ class PluginManagerService:
"POST",
"/pre-uninstall-plugin",
json=body.model_dump(),
timeout=dify_config.ENTERPRISE_REQUEST_TIMEOUT,
)
except Exception:
logger.exception(

View File

@@ -30,6 +30,7 @@ from extensions.ext_database import db
from extensions.ext_redis import redis_client
from models.provider import ProviderCredential
from models.provider_ids import GenericProviderID
from services.enterprise.plugin_manager_service import PluginManagerService, PreUninstallPluginRequest
from services.errors.plugin import PluginInstallationForbiddenError
from services.feature_service import FeatureService, PluginInstallationScope
@@ -518,6 +519,13 @@ class PluginService:
if plugin:
plugin_id = plugin.plugin_id
logger.info("Deleting credentials for plugin: %s", plugin_id)
if dify_config.ENTERPRISE_ENABLED:
PluginManagerService.try_pre_uninstall_plugin(
PreUninstallPluginRequest(
tenant_id=tenant_id,
plugin_unique_identifier=plugin.plugin_unique_identifier,
)
)
# Delete provider credentials that match this plugin
credentials = db.session.scalars(

View File

@@ -14,73 +14,82 @@ from services.enterprise.plugin_manager_service import (
PreUninstallPluginRequest,
)
_FAKE_TIMEOUT = 30
_SEND_REQUEST_PATH = (
"services.enterprise.plugin_manager_service.EnterprisePluginManagerRequest.send_request"
)
_DIFY_CONFIG_PATH = "services.enterprise.plugin_manager_service.dify_config"
_LOGGER_PATH = "services.enterprise.plugin_manager_service.logger"
class TestTryPreUninstallPlugin:
def test_try_pre_uninstall_plugin_success(self):
@patch(_DIFY_CONFIG_PATH)
@patch(_SEND_REQUEST_PATH)
def test_try_pre_uninstall_plugin_success(self, mock_send_request, mock_config):
body = PreUninstallPluginRequest(
tenant_id="tenant-123",
plugin_unique_identifier="com.example.my_plugin",
)
mock_config.ENTERPRISE_REQUEST_TIMEOUT = _FAKE_TIMEOUT
mock_send_request.return_value = {}
with patch(
"services.enterprise.plugin_manager_service.EnterprisePluginManagerRequest.send_request"
) as mock_send_request:
mock_send_request.return_value = {}
PluginManagerService.try_pre_uninstall_plugin(body)
PluginManagerService.try_pre_uninstall_plugin(body)
mock_send_request.assert_called_once_with(
"POST",
"/pre-uninstall-plugin",
json={"tenant_id": "tenant-123", "plugin_unique_identifier": "com.example.my_plugin"},
timeout=_FAKE_TIMEOUT,
)
mock_send_request.assert_called_once_with(
"POST",
"/pre-uninstall-plugin",
json={"tenant_id": "tenant-123", "plugin_unique_identifier": "com.example.my_plugin"},
)
def test_try_pre_uninstall_plugin_http_error_soft_fails(self):
@patch(_DIFY_CONFIG_PATH)
@patch(_LOGGER_PATH)
@patch(_SEND_REQUEST_PATH)
def test_try_pre_uninstall_plugin_http_error_soft_fails(
self, mock_send_request, mock_logger, mock_config
):
body = PreUninstallPluginRequest(
tenant_id="tenant-456",
plugin_unique_identifier="com.example.other_plugin",
)
mock_config.ENTERPRISE_REQUEST_TIMEOUT = _FAKE_TIMEOUT
mock_send_request.side_effect = HTTPStatusError(
"502 Bad Gateway",
request=None,
response=None,
)
with (
patch(
"services.enterprise.plugin_manager_service.EnterprisePluginManagerRequest.send_request"
) as mock_send_request,
patch("services.enterprise.plugin_manager_service.logger") as mock_logger,
):
mock_send_request.side_effect = HTTPStatusError(
"502 Bad Gateway",
request=None,
response=None,
)
PluginManagerService.try_pre_uninstall_plugin(body)
PluginManagerService.try_pre_uninstall_plugin(body)
mock_send_request.assert_called_once_with(
"POST",
"/pre-uninstall-plugin",
json={"tenant_id": "tenant-456", "plugin_unique_identifier": "com.example.other_plugin"},
timeout=_FAKE_TIMEOUT,
)
mock_logger.exception.assert_called_once()
mock_send_request.assert_called_once_with(
"POST",
"/pre-uninstall-plugin",
json={"tenant_id": "tenant-456", "plugin_unique_identifier": "com.example.other_plugin"},
)
mock_logger.exception.assert_called_once()
def test_try_pre_uninstall_plugin_generic_exception_soft_fails(self):
@patch(_DIFY_CONFIG_PATH)
@patch(_LOGGER_PATH)
@patch(_SEND_REQUEST_PATH)
def test_try_pre_uninstall_plugin_generic_exception_soft_fails(
self, mock_send_request, mock_logger, mock_config
):
body = PreUninstallPluginRequest(
tenant_id="tenant-789",
plugin_unique_identifier="com.example.failing_plugin",
)
mock_config.ENTERPRISE_REQUEST_TIMEOUT = _FAKE_TIMEOUT
mock_send_request.side_effect = ConnectionError("network unreachable")
with (
patch(
"services.enterprise.plugin_manager_service.EnterprisePluginManagerRequest.send_request"
) as mock_send_request,
patch("services.enterprise.plugin_manager_service.logger") as mock_logger,
):
mock_send_request.side_effect = ConnectionError("network unreachable")
PluginManagerService.try_pre_uninstall_plugin(body)
PluginManagerService.try_pre_uninstall_plugin(body)
mock_send_request.assert_called_once_with(
"POST",
"/pre-uninstall-plugin",
json={"tenant_id": "tenant-789", "plugin_unique_identifier": "com.example.failing_plugin"},
)
mock_logger.exception.assert_called_once()
mock_send_request.assert_called_once_with(
"POST",
"/pre-uninstall-plugin",
json={"tenant_id": "tenant-789", "plugin_unique_identifier": "com.example.failing_plugin"},
timeout=_FAKE_TIMEOUT,
)
mock_logger.exception.assert_called_once()