refactor(tests): replace logger mocks with caplog (#37468) (#37922)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Mr_xie 2026-06-25 17:48:43 +08:00 committed by GitHub
parent 40119fef44
commit e90c7ab8a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 117 additions and 87 deletions

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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):

View File

@ -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)

View File

@ -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
)

View File

@ -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:

View File

@ -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