mirror of
https://github.com/langgenius/dify.git
synced 2026-06-25 22:31:10 +08:00
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
40119fef44
commit
e90c7ab8a7
@ -1,4 +1,5 @@
|
||||
import inspect
|
||||
import logging
|
||||
from io import BytesIO
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@ -151,7 +152,9 @@ class TestTenantListApi:
|
||||
get_plan_bulk_mock.assert_called_once_with(["t1", "t2"])
|
||||
get_features_mock.assert_called_once_with("t2", exclude_vector_space=True)
|
||||
|
||||
def test_get_saas_path_falls_back_to_legacy_feature_path_on_bulk_error(self, app: Flask):
|
||||
def test_get_saas_path_falls_back_to_legacy_feature_path_on_bulk_error(
|
||||
self, app: Flask, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
"""Test fallback to FeatureService when bulk billing returns empty result.
|
||||
|
||||
BillingService.get_plan_bulk catches exceptions internally and returns empty dict,
|
||||
@ -170,6 +173,7 @@ class TestTenantListApi:
|
||||
|
||||
with (
|
||||
app.test_request_context("/workspaces"),
|
||||
caplog.at_level(logging.WARNING, logger="controllers.console.workspace.workspace"),
|
||||
patch(
|
||||
"controllers.console.workspace.workspace.TenantService.get_workspaces_for_account",
|
||||
return_value=[(tenant1, make_membership()), (tenant2, make_membership())],
|
||||
@ -185,7 +189,6 @@ class TestTenantListApi:
|
||||
"controllers.console.workspace.workspace.FeatureService.get_features",
|
||||
return_value=features,
|
||||
) as get_features_mock,
|
||||
patch("controllers.console.workspace.workspace.logger.warning") as logger_warning_mock,
|
||||
):
|
||||
result, status = method(api, "t2", user)
|
||||
|
||||
@ -194,7 +197,7 @@ class TestTenantListApi:
|
||||
assert result["workspaces"][1]["plan"] == CloudPlan.TEAM
|
||||
get_plan_bulk_mock.assert_called_once_with(["t1", "t2"])
|
||||
assert get_features_mock.call_count == 2
|
||||
logger_warning_mock.assert_called_once()
|
||||
assert "get_plan_bulk returned empty result, falling back to legacy feature path" in caplog.messages
|
||||
|
||||
def test_get_billing_disabled_community_path(self, app: Flask):
|
||||
api = TenantListApi()
|
||||
@ -365,7 +368,7 @@ class TestTenantApi:
|
||||
with pytest.raises(Unauthorized):
|
||||
method(api, user)
|
||||
|
||||
def test_post_info_path(self, app: Flask):
|
||||
def test_post_info_path(self, app: Flask, caplog: pytest.LogCaptureFixture):
|
||||
api = TenantApi()
|
||||
method = inspect.unwrap(api.post)
|
||||
|
||||
@ -374,15 +377,15 @@ class TestTenantApi:
|
||||
|
||||
with (
|
||||
app.test_request_context("/info"),
|
||||
caplog.at_level(logging.WARNING, logger="controllers.console.workspace.workspace"),
|
||||
patch(
|
||||
"controllers.console.workspace.workspace.WorkspaceService.get_tenant_info",
|
||||
return_value={"id": "t1"},
|
||||
),
|
||||
patch("controllers.console.workspace.workspace.logger.warning") as warn_mock,
|
||||
):
|
||||
result, status = method(api, user)
|
||||
|
||||
warn_mock.assert_called_once()
|
||||
assert "Deprecated URL /info was used." in caplog.messages
|
||||
assert status == 200
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
@ -961,7 +962,9 @@ class TestAdvancedChatAppGeneratorInternals:
|
||||
stream=False,
|
||||
)
|
||||
|
||||
def test_handle_response_re_raises_value_error(self, monkeypatch: pytest.MonkeyPatch):
|
||||
def test_handle_response_re_raises_value_error(
|
||||
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
generator = AdvancedChatAppGenerator()
|
||||
generator._dialogue_count = 1
|
||||
app_config = self._build_app_config()
|
||||
@ -986,29 +989,28 @@ class TestAdvancedChatAppGeneratorInternals:
|
||||
def process(self):
|
||||
raise ValueError("other error")
|
||||
|
||||
logger_exception = MagicMock()
|
||||
monkeypatch.setattr("core.app.apps.advanced_chat.app_generator.logger.exception", logger_exception)
|
||||
monkeypatch.setattr("core.app.apps.advanced_chat.app_generator.AdvancedChatAppGenerateTaskPipeline", _Pipeline)
|
||||
|
||||
with pytest.raises(ValueError, match="other error"):
|
||||
generator._handle_advanced_chat_response(
|
||||
application_generate_entity=application_generate_entity,
|
||||
workflow=WorkflowSnapshot(id="wf", tenant_id="tenant", features_dict={}),
|
||||
queue_manager=SimpleNamespace(),
|
||||
conversation=ConversationSnapshot(id="conv", mode=AppMode.ADVANCED_CHAT),
|
||||
message=MessageSnapshot(
|
||||
id="msg",
|
||||
query="hello",
|
||||
created_at=naive_utc_now(),
|
||||
status=MessageStatus.NORMAL,
|
||||
answer="",
|
||||
),
|
||||
user=SimpleNamespace(),
|
||||
draft_var_saver_factory=lambda **kwargs: None,
|
||||
stream=False,
|
||||
)
|
||||
with caplog.at_level(logging.ERROR, logger="core.app.apps.advanced_chat.app_generator"):
|
||||
with pytest.raises(ValueError, match="other error"):
|
||||
generator._handle_advanced_chat_response(
|
||||
application_generate_entity=application_generate_entity,
|
||||
workflow=WorkflowSnapshot(id="wf", tenant_id="tenant", features_dict={}),
|
||||
queue_manager=SimpleNamespace(),
|
||||
conversation=ConversationSnapshot(id="conv", mode=AppMode.ADVANCED_CHAT),
|
||||
message=MessageSnapshot(
|
||||
id="msg",
|
||||
query="hello",
|
||||
created_at=naive_utc_now(),
|
||||
status=MessageStatus.NORMAL,
|
||||
answer="",
|
||||
),
|
||||
user=SimpleNamespace(),
|
||||
draft_var_saver_factory=lambda **kwargs: None,
|
||||
stream=False,
|
||||
)
|
||||
|
||||
logger_exception.assert_called_once()
|
||||
assert "Failed to process generate task pipeline, conversation_id: conv" in caplog.messages
|
||||
|
||||
def test_generate_worker_handles_invoke_auth_error(self, monkeypatch: pytest.MonkeyPatch):
|
||||
generator = AdvancedChatAppGenerator()
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
@ -274,7 +275,9 @@ class TestAgentChatAppGeneratorWorker:
|
||||
|
||||
assert queue_manager.publish_error.called
|
||||
|
||||
def test_generate_worker_logs_value_error_when_debug(self, generator, mocker: MockerFixture):
|
||||
def test_generate_worker_logs_value_error_when_debug(
|
||||
self, generator, mocker: MockerFixture, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
queue_manager = mocker.MagicMock()
|
||||
generator._get_conversation = mocker.MagicMock(return_value=mocker.MagicMock())
|
||||
generator._get_message = mocker.MagicMock(return_value=mocker.MagicMock())
|
||||
@ -285,15 +288,15 @@ class TestAgentChatAppGeneratorWorker:
|
||||
mocker.patch("core.app.apps.agent_chat.app_generator.db.session.close")
|
||||
|
||||
mocker.patch("core.app.apps.agent_chat.app_generator.dify_config", new=mocker.MagicMock(DEBUG=True))
|
||||
logger = mocker.patch("core.app.apps.agent_chat.app_generator.logger")
|
||||
|
||||
generator._generate_worker(
|
||||
flask_app=mocker.MagicMock(),
|
||||
context=mocker.MagicMock(),
|
||||
application_generate_entity=mocker.MagicMock(),
|
||||
queue_manager=queue_manager,
|
||||
conversation_id="conv",
|
||||
message_id="msg",
|
||||
)
|
||||
with caplog.at_level(logging.ERROR, logger="core.app.apps.agent_chat.app_generator"):
|
||||
generator._generate_worker(
|
||||
flask_app=mocker.MagicMock(),
|
||||
context=mocker.MagicMock(),
|
||||
application_generate_entity=mocker.MagicMock(),
|
||||
queue_manager=queue_manager,
|
||||
conversation_id="conv",
|
||||
message_id="msg",
|
||||
)
|
||||
|
||||
logger.exception.assert_called_once()
|
||||
assert "Error when generating" in caplog.messages
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
@ -263,11 +264,11 @@ class TestAppRunner:
|
||||
files=[],
|
||||
)
|
||||
|
||||
def test_handle_invoke_result_stream_routes_chunks_and_builds_message(self, monkeypatch: pytest.MonkeyPatch):
|
||||
def test_handle_invoke_result_stream_routes_chunks_and_builds_message(
|
||||
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
runner = AppRunner()
|
||||
queue = _QueueRecorder()
|
||||
warning_logger = MagicMock()
|
||||
monkeypatch.setattr("core.app.apps.base_app_runner._logger.warning", warning_logger)
|
||||
|
||||
image_content = ImagePromptMessageContent(
|
||||
url="https://example.com/image.png", format="png", mime_type="image/png"
|
||||
@ -290,23 +291,24 @@ class TestAppRunner:
|
||||
),
|
||||
)
|
||||
|
||||
runner._handle_invoke_result(
|
||||
invoke_result=_stream(),
|
||||
queue_manager=queue,
|
||||
stream=True,
|
||||
agent=False,
|
||||
)
|
||||
with caplog.at_level(logging.WARNING, logger="core.app.apps.base_app_runner"):
|
||||
runner._handle_invoke_result(
|
||||
invoke_result=_stream(),
|
||||
queue_manager=queue,
|
||||
stream=True,
|
||||
agent=False,
|
||||
)
|
||||
|
||||
assert isinstance(queue.events[0], QueueLLMChunkEvent)
|
||||
assert isinstance(queue.events[-1], QueueMessageEndEvent)
|
||||
assert queue.events[-1].llm_result.message.content == "abc"
|
||||
warning_logger.assert_called_once()
|
||||
assert "Received multimodal output but missing required parameters" in caplog.messages
|
||||
|
||||
def test_handle_invoke_result_stream_agent_mode_handles_multimodal_errors(self, monkeypatch: pytest.MonkeyPatch):
|
||||
def test_handle_invoke_result_stream_agent_mode_handles_multimodal_errors(
|
||||
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
runner = AppRunner()
|
||||
queue = _QueueRecorder()
|
||||
exception_logger = MagicMock()
|
||||
monkeypatch.setattr("core.app.apps.base_app_runner._logger.exception", exception_logger)
|
||||
|
||||
monkeypatch.setattr(
|
||||
runner,
|
||||
@ -335,19 +337,20 @@ class TestAppRunner:
|
||||
),
|
||||
)
|
||||
|
||||
runner._handle_invoke_result_stream(
|
||||
invoke_result=_stream(),
|
||||
queue_manager=queue,
|
||||
agent=True,
|
||||
message_id="message-id",
|
||||
user_id="user-id",
|
||||
tenant_id="tenant-id",
|
||||
)
|
||||
with caplog.at_level(logging.ERROR, logger="core.app.apps.base_app_runner"):
|
||||
runner._handle_invoke_result_stream(
|
||||
invoke_result=_stream(),
|
||||
queue_manager=queue,
|
||||
agent=True,
|
||||
message_id="message-id",
|
||||
user_id="user-id",
|
||||
tenant_id="tenant-id",
|
||||
)
|
||||
|
||||
assert isinstance(queue.events[0], QueueAgentMessageEvent)
|
||||
assert isinstance(queue.events[-1], QueueMessageEndEvent)
|
||||
assert queue.events[-1].llm_result.usage == usage
|
||||
exception_logger.assert_called_once()
|
||||
assert "Failed to handle multimodal image output" in caplog.messages
|
||||
|
||||
def test_handle_invoke_result_stream_closes_generator_when_stopped(self):
|
||||
runner = AppRunner()
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock
|
||||
@ -639,7 +640,9 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
assert sleep_spy
|
||||
assert any(isinstance(item, MessageAudioEndStreamResponse) for item in responses)
|
||||
|
||||
def test_wrapper_process_stream_response_handles_audio_exception(self, monkeypatch: pytest.MonkeyPatch):
|
||||
def test_wrapper_process_stream_response_handles_audio_exception(
|
||||
self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
):
|
||||
pipeline = _make_pipeline()
|
||||
pipeline._workflow_features_dict = {
|
||||
"text_to_speech": {"enabled": True, "autoPlay": "enabled", "voice": "v", "language": "en"}
|
||||
@ -659,20 +662,16 @@ class TestWorkflowGenerateTaskPipeline:
|
||||
def publish(self, message):
|
||||
_ = message
|
||||
|
||||
logger_exception = []
|
||||
monkeypatch.setattr("core.app.apps.workflow.generate_task_pipeline.time.time", lambda: 0.0)
|
||||
monkeypatch.setattr(
|
||||
"core.app.apps.workflow.generate_task_pipeline.logger.exception",
|
||||
lambda *args, **kwargs: logger_exception.append((args, kwargs)),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"core.app.apps.workflow.generate_task_pipeline.AppGeneratorTTSPublisher",
|
||||
_Publisher,
|
||||
)
|
||||
|
||||
responses = list(pipeline._wrapper_process_stream_response())
|
||||
with caplog.at_level(logging.ERROR, logger="core.app.apps.workflow.generate_task_pipeline"):
|
||||
responses = list(pipeline._wrapper_process_stream_response())
|
||||
|
||||
assert logger_exception
|
||||
assert "Fails to get audio trunk, task_id: task" in caplog.messages
|
||||
assert any(isinstance(item, MessageAudioEndStreamResponse) for item in responses)
|
||||
|
||||
def test_database_session_rolls_back_on_error(self, monkeypatch: pytest.MonkeyPatch):
|
||||
|
||||
@ -2042,7 +2042,9 @@ def test_get_custom_provider_models_skips_schema_models_with_mismatched_type() -
|
||||
assert all(model.model != "embed-model" for model in models)
|
||||
|
||||
|
||||
def test_get_custom_provider_models_skips_custom_models_on_schema_error_or_none() -> None:
|
||||
def test_get_custom_provider_models_skips_custom_models_on_schema_error_or_none(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
configuration = _build_provider_configuration()
|
||||
configuration.custom_configuration.models = [
|
||||
CustomModelConfiguration(model="error-custom", model_type=ModelType.LLM, credentials={"k": "v"}),
|
||||
@ -2064,7 +2066,7 @@ def test_get_custom_provider_models_skips_custom_models_on_schema_error_or_none(
|
||||
return None
|
||||
return _build_ai_model(model)
|
||||
|
||||
with patch("core.entities.provider_configuration.logger.warning") as mock_warning:
|
||||
with caplog.at_level(logging.WARNING, logger="core.entities.provider_configuration"):
|
||||
with patch.object(ProviderConfiguration, "get_model_schema", side_effect=_schema):
|
||||
models = configuration._get_custom_provider_models(
|
||||
model_types=[ModelType.LLM],
|
||||
@ -2072,6 +2074,6 @@ def test_get_custom_provider_models_skips_custom_models_on_schema_error_or_none(
|
||||
model_setting_map={},
|
||||
)
|
||||
|
||||
assert mock_warning.call_count == 1
|
||||
assert "get custom model schema failed, boom" in caplog.messages
|
||||
assert any(model.model == "ok-custom" for model in models)
|
||||
assert all(model.model != "none-custom" for model in models)
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import logging
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from models.account import TenantPluginAutoUpgradeStrategy
|
||||
|
||||
MODULE = "services.plugin.plugin_auto_upgrade_service"
|
||||
@ -227,7 +230,7 @@ class TestBackfillStrategyCategories:
|
||||
assert default_time % (15 * 60) == 0
|
||||
assert 0 <= default_time < 24 * 60 * 60
|
||||
|
||||
def test_creates_missing_categories_and_splits_known_plugins(self):
|
||||
def test_creates_missing_categories_and_splits_known_plugins(self, caplog: pytest.LogCaptureFixture):
|
||||
p1, session = _patched_session()
|
||||
tool_strategy = SimpleNamespace(
|
||||
category=TenantPluginAutoUpgradeStrategy.PluginCategory.TOOL,
|
||||
@ -260,7 +263,11 @@ class TestBackfillStrategyCategories:
|
||||
installer = MagicMock()
|
||||
installer.list_plugins.return_value = installed_plugins
|
||||
|
||||
with p1, patch(f"{MODULE}.PluginInstaller", return_value=installer), patch(f"{MODULE}.logger") as logger:
|
||||
with (
|
||||
p1,
|
||||
patch(f"{MODULE}.PluginInstaller", return_value=installer),
|
||||
caplog.at_level(logging.WARNING, logger=MODULE),
|
||||
):
|
||||
from services.plugin.plugin_auto_upgrade_service import PluginAutoUpgradeService
|
||||
|
||||
result = PluginAutoUpgradeService.backfill_strategy_categories("t1")
|
||||
@ -272,10 +279,7 @@ class TestBackfillStrategyCategories:
|
||||
assert tool_strategy.include_plugins == ["tool-plugin"]
|
||||
assert model_strategy.exclude_plugins == ["model-plugin"]
|
||||
assert model_strategy.include_plugins == ["model-plugin"]
|
||||
logger.warning.assert_called_once_with(
|
||||
assert (
|
||||
"Skipped unknown plugin IDs while backfilling plugin auto-upgrade strategies: "
|
||||
"tenant_id=%s, field=%s, plugin_ids=%s",
|
||||
"t1",
|
||||
"exclude_plugins",
|
||||
["unknown-plugin"],
|
||||
"tenant_id=t1, field=exclude_plugins, plugin_ids=['unknown-plugin']" in caplog.messages
|
||||
)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import datetime
|
||||
import logging
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
@ -347,7 +348,9 @@ def test_serialize_record_falls_back_to_table_columns() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_process_with_tenant_ids_filters_by_plan_and_logs_errors(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def test_process_with_tenant_ids_filters_by_plan_and_logs_errors(
|
||||
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
monkeypatch.setattr(service_module, "db", SimpleNamespace(engine=object()))
|
||||
|
||||
# Total tenant count query
|
||||
@ -381,14 +384,13 @@ def test_process_with_tenant_ids_filters_by_plan_and_logs_errors(monkeypatch: py
|
||||
process_tenant_mock = MagicMock(side_effect=lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("err")))
|
||||
monkeypatch.setattr(ClearFreePlanTenantExpiredLogs, "process_tenant", process_tenant_mock)
|
||||
|
||||
logger_exc = MagicMock()
|
||||
monkeypatch.setattr(service_module.logger, "exception", logger_exc)
|
||||
|
||||
ClearFreePlanTenantExpiredLogs.process(days=7, batch=10, tenant_ids=["t_sandbox", "t_paid", "t_fail"])
|
||||
with caplog.at_level(logging.ERROR, logger=service_module.logger.name):
|
||||
ClearFreePlanTenantExpiredLogs.process(days=7, batch=10, tenant_ids=["t_sandbox", "t_paid", "t_fail"])
|
||||
|
||||
# Only sandbox tenant should attempt processing, and its failure should be swallowed + logged.
|
||||
assert process_tenant_mock.call_count == 1
|
||||
assert logger_exc.call_count >= 1
|
||||
assert "Failed to process tenant t_sandbox" in caplog.messages
|
||||
assert "Failed to process tenant t_fail" in caplog.messages
|
||||
|
||||
|
||||
def test_process_without_tenant_ids_batches_and_scales_interval(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
@ -9,6 +9,7 @@ This module tests the document indexing task functionality including:
|
||||
- Task cancellation and cleanup
|
||||
"""
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from contextlib import nullcontext
|
||||
from types import SimpleNamespace
|
||||
@ -758,7 +759,15 @@ class TestErrorHandling:
|
||||
assert mock_db_session.close.called
|
||||
|
||||
def test_tenant_queue_error_handling_still_processes_next_task(
|
||||
self, tenant_id, dataset_id, document_ids, mock_redis, mock_db_session, mock_dataset, mock_indexing_runner
|
||||
self,
|
||||
tenant_id,
|
||||
dataset_id,
|
||||
document_ids,
|
||||
mock_redis,
|
||||
mock_db_session,
|
||||
mock_dataset,
|
||||
mock_indexing_runner,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
):
|
||||
"""
|
||||
Test that errors don't prevent processing next task in tenant queue.
|
||||
@ -778,14 +787,17 @@ class TestErrorHandling:
|
||||
with patch("tasks.document_indexing_task._document_indexing") as mock_indexing:
|
||||
mock_indexing.side_effect = Exception("Processing failed")
|
||||
|
||||
# Patch logger to avoid format string issue in actual code
|
||||
with patch("tasks.document_indexing_task.logger"):
|
||||
with caplog.at_level(logging.ERROR, logger="tasks.document_indexing_task"):
|
||||
with patch("tasks.document_indexing_task.normal_document_indexing_task") as mock_task:
|
||||
# Act
|
||||
_document_indexing_with_tenant_queue(tenant_id, dataset_id, document_ids, mock_task)
|
||||
|
||||
# Assert - Next task should still be enqueued despite error
|
||||
mock_task.apply_async.assert_called()
|
||||
assert (
|
||||
f"Error processing document indexing {dataset_id} for tenant {tenant_id}: {document_ids}"
|
||||
in caplog.messages
|
||||
)
|
||||
|
||||
def test_concurrent_task_limit_respected(
|
||||
self, tenant_id, dataset_id, document_ids, mock_redis, mock_db_session, mock_dataset
|
||||
|
||||
Loading…
Reference in New Issue
Block a user