From 2ce7919e6d406bb5ebed83341e4478caa2cc7a39 Mon Sep 17 00:00:00 2001 From: Novice Date: Thu, 19 Mar 2026 17:19:56 +0800 Subject: [PATCH] test: update unit tests for system message handling and workflow collaboration serices --- .../unit_tests/core/agent/test_agent_app_runner.py | 7 +++---- .../core/app/apps/test_workflow_app_generator.py | 14 ++++++++++++-- .../unit_tests/core/variables/test_segment_type.py | 1 + .../core/variables/test_segment_type_validation.py | 5 +++-- .../core/workflow/nodes/llm/test_llm_utils.py | 6 ++++-- .../test_workflow_collaboration_repository.py | 2 ++ .../test_workflow_collaboration_service.py | 9 +++++++++ .../services/test_workflow_comment_service.py | 4 ++++ .../utils/encryption/test_system_encryption.py | 14 +++++++------- 9 files changed, 45 insertions(+), 17 deletions(-) diff --git a/api/tests/unit_tests/core/agent/test_agent_app_runner.py b/api/tests/unit_tests/core/agent/test_agent_app_runner.py index d9301ccfe0..df78c8b79e 100644 --- a/api/tests/unit_tests/core/agent/test_agent_app_runner.py +++ b/api/tests/unit_tests/core/agent/test_agent_app_runner.py @@ -134,8 +134,8 @@ class TestInitSystemMessage: assert result == [] - def test_existing_system_message_not_duplicated(self, mock_runner): - """Test that system message is not duplicated if already present.""" + def test_existing_system_message_replaced_with_template(self, mock_runner): + """Test that existing system message is replaced with the new template.""" existing_messages = [ SystemPromptMessage(content="Existing system"), UserPromptMessage(content="User message"), @@ -143,9 +143,8 @@ class TestInitSystemMessage: result = mock_runner._init_system_message("New template", existing_messages) - # Should not insert new system message assert len(result) == 2 - assert result[0].content == "Existing system" + assert result[0].content == "New template" def test_system_message_inserted_when_missing(self, mock_runner): """Test that system message is inserted when first message is not system.""" diff --git a/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py b/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py index 7e8367c6c4..b1d1df6f09 100644 --- a/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py +++ b/api/tests/unit_tests/core/app/apps/test_workflow_app_generator.py @@ -105,9 +105,12 @@ def test_generate_appends_pause_layer_and_forwards_state(mocker): graph_runtime_state = MagicMock() + workflow_mock = MagicMock() + workflow_mock.get_feature.return_value.enabled = False + result = generator._generate( app_model=app_model, - workflow=MagicMock(), + workflow=workflow_mock, user=MagicMock(), application_generate_entity=application_generate_entity, invoke_from="service-api", @@ -143,8 +146,15 @@ def test_resume_path_runs_worker_with_runtime_state(mocker): fake_db = SimpleNamespace(session=MagicMock(), engine=MagicMock()) mocker.patch("core.app.apps.workflow.app_generator.db", fake_db) + sandbox_feature = SimpleNamespace(enabled=False) workflow = SimpleNamespace( - id="workflow", tenant_id="tenant", app_id="app", graph_dict={}, type="workflow", version="1" + id="workflow", + tenant_id="tenant", + app_id="app", + graph_dict={}, + type="workflow", + version="1", + get_feature=lambda _feature: sandbox_feature, ) end_user = SimpleNamespace(session_id="end-user-session") app_record = SimpleNamespace(id="app") diff --git a/api/tests/unit_tests/core/variables/test_segment_type.py b/api/tests/unit_tests/core/variables/test_segment_type.py index 3bfc5a957f..c848fc8828 100644 --- a/api/tests/unit_tests/core/variables/test_segment_type.py +++ b/api/tests/unit_tests/core/variables/test_segment_type.py @@ -38,6 +38,7 @@ class TestSegmentTypeIsArrayType: SegmentType.NONE, SegmentType.GROUP, SegmentType.BOOLEAN, + SegmentType.ARRAY_PROMPT_MESSAGE, ] for seg_type in expected_array_types: diff --git a/api/tests/unit_tests/core/variables/test_segment_type_validation.py b/api/tests/unit_tests/core/variables/test_segment_type_validation.py index 3a0054cd46..f79b18d09d 100644 --- a/api/tests/unit_tests/core/variables/test_segment_type_validation.py +++ b/api/tests/unit_tests/core/variables/test_segment_type_validation.py @@ -581,11 +581,11 @@ class TestSegmentTypeIsValid: test_value = None elif segment_type == SegmentType.GROUP: test_value = SegmentGroup(value=[StringSegment(value="test")]) + elif segment_type == SegmentType.ARRAY_PROMPT_MESSAGE: + continue # Internal type, not validated via is_valid elif segment_type.is_array_type(): test_value = [] # Empty array is valid for all array types else: - # If we get here, there's a segment type we don't know how to test - # This should prompt us to add validation logic pytest.fail(f"Unknown segment type {segment_type} needs validation logic and test case") # This should NOT raise AssertionError @@ -788,6 +788,7 @@ class TestSegmentTypeValidationIntegration: unhandled_types = { SegmentType.INTEGER, # Handled by NUMBER validation logic SegmentType.FLOAT, # Handled by NUMBER validation logic + SegmentType.ARRAY_PROMPT_MESSAGE, # Internal type, not user-facing } # Verify all types are accounted for diff --git a/api/tests/unit_tests/core/workflow/nodes/llm/test_llm_utils.py b/api/tests/unit_tests/core/workflow/nodes/llm/test_llm_utils.py index 477c108aeb..b97729f36f 100644 --- a/api/tests/unit_tests/core/workflow/nodes/llm/test_llm_utils.py +++ b/api/tests/unit_tests/core/workflow/nodes/llm/test_llm_utils.py @@ -180,7 +180,8 @@ class TestBuildContext: ], ) - context = build_context(messages, "The weather in Beijing is sunny, 25°C.", generation_data) + accumulated_response = "Let me check the weather.The weather in Beijing is sunny, 25°C." + context = build_context(messages, accumulated_response, generation_data) # Should have: user message + assistant with tool_call + tool result + final assistant assert len(context) == 4 @@ -263,7 +264,8 @@ class TestBuildContext: ], ) - context = build_context(messages, "Beijing is sunny at 25°C, Shanghai is cloudy at 22°C.", generation_data) + accumulated_response = "I'll check both cities.Beijing is sunny at 25°C, Shanghai is cloudy at 22°C." + context = build_context(messages, accumulated_response, generation_data) # Should have: user + assistant with 2 tool_calls + 2 tool results + final assistant assert len(context) == 5 diff --git a/api/tests/unit_tests/repositories/test_workflow_collaboration_repository.py b/api/tests/unit_tests/repositories/test_workflow_collaboration_repository.py index 1f47e8b692..14ef21dfc9 100644 --- a/api/tests/unit_tests/repositories/test_workflow_collaboration_repository.py +++ b/api/tests/unit_tests/repositories/test_workflow_collaboration_repository.py @@ -45,6 +45,8 @@ class TestWorkflowCollaborationRepository: "avatar": None, "sid": "sid-1", "connected_at": 2, + "graph_active": False, + "active_skill_file_id": None, } ] diff --git a/api/tests/unit_tests/services/test_workflow_collaboration_service.py b/api/tests/unit_tests/services/test_workflow_collaboration_service.py index f1484f2822..1c15a3d01e 100644 --- a/api/tests/unit_tests/services/test_workflow_collaboration_service.py +++ b/api/tests/unit_tests/services/test_workflow_collaboration_service.py @@ -10,6 +10,13 @@ class TestWorkflowCollaborationService: @pytest.fixture def service(self) -> tuple[WorkflowCollaborationService, Mock, Mock]: repository = Mock(spec=WorkflowCollaborationRepository) + repository.get_current_leader.return_value = None + repository.get_session_sids.return_value = [] + repository.get_active_skill_file_id.return_value = None + repository.get_active_skill_session_sids.return_value = [] + repository.is_graph_active.return_value = False + repository.get_skill_leader.return_value = None + repository.list_sessions.return_value = [] socketio = Mock() return WorkflowCollaborationService(repository, socketio), repository, socketio @@ -124,6 +131,7 @@ class TestWorkflowCollaborationService: # Arrange collaboration_service, repository, _socketio = service repository.get_current_leader.return_value = "sid-1" + repository.is_graph_active.return_value = True with patch.object(collaboration_service, "is_session_active", return_value=True): # Act @@ -265,6 +273,7 @@ class TestWorkflowCollaborationService: # Arrange collaboration_service, repository, _socketio = service repository.get_current_leader.return_value = "sid-1" + repository.is_graph_active.return_value = True with patch.object(collaboration_service, "is_session_active", return_value=True): # Act diff --git a/api/tests/unit_tests/services/test_workflow_comment_service.py b/api/tests/unit_tests/services/test_workflow_comment_service.py index dfb1c9452f..32c8e5f2a6 100644 --- a/api/tests/unit_tests/services/test_workflow_comment_service.py +++ b/api/tests/unit_tests/services/test_workflow_comment_service.py @@ -17,6 +17,10 @@ def mock_session(monkeypatch: pytest.MonkeyPatch) -> Mock: mock_db.engine = Mock() monkeypatch.setattr(service_module, "Session", Mock(return_value=context_manager)) monkeypatch.setattr(service_module, "db", mock_db) + monkeypatch.setattr(service_module, "send_workflow_comment_mention_email_task", Mock()) + scalars_default = Mock() + scalars_default.all.return_value = [] + session.scalars.return_value = scalars_default return session diff --git a/api/tests/unit_tests/utils/encryption/test_system_encryption.py b/api/tests/unit_tests/utils/encryption/test_system_encryption.py index cfa381eb21..dfdeca39ed 100644 --- a/api/tests/unit_tests/utils/encryption/test_system_encryption.py +++ b/api/tests/unit_tests/utils/encryption/test_system_encryption.py @@ -29,7 +29,7 @@ class TestSystemOAuthEncrypter: def test_init_with_none_secret_key(self): """Test initialization with None secret key falls back to config""" - with patch("core.tools.utils.system_oauth_encryption.dify_config") as mock_config: + with patch("core.tools.utils.system_encryption.dify_config") as mock_config: mock_config.SECRET_KEY = "config_secret" encrypter = SystemEncrypter(secret_key=None) expected_key = hashlib.sha256(b"config_secret").digest() @@ -43,7 +43,7 @@ class TestSystemOAuthEncrypter: def test_init_without_secret_key_uses_config(self): """Test initialization without secret key uses config""" - with patch("core.tools.utils.system_oauth_encryption.dify_config") as mock_config: + with patch("core.tools.utils.system_encryption.dify_config") as mock_config: mock_config.SECRET_KEY = "default_secret" encrypter = SystemEncrypter() expected_key = hashlib.sha256(b"default_secret").digest() @@ -302,7 +302,7 @@ class TestSystemOAuthEncrypter: decrypted2 = encrypter2.decrypt_params(encrypted2) assert decrypted1 == decrypted2 == oauth_params - @patch("core.tools.utils.system_oauth_encryption.get_random_bytes") + @patch("core.tools.utils.system_encryption.get_random_bytes") def test_encrypt_oauth_params_crypto_error(self, mock_get_random_bytes): """Test encryption when crypto operation fails""" mock_get_random_bytes.side_effect = Exception("Crypto error") @@ -315,7 +315,7 @@ class TestSystemOAuthEncrypter: assert "Encryption failed" in str(exc_info.value) - @patch("core.tools.utils.system_oauth_encryption.TypeAdapter") + @patch("core.tools.utils.system_encryption.TypeAdapter") def test_encrypt_oauth_params_serialization_error(self, mock_type_adapter): """Test encryption when JSON serialization fails""" mock_type_adapter.return_value.dump_json.side_effect = Exception("Serialization error") @@ -370,7 +370,7 @@ class TestFactoryFunctions: def test_create_system_oauth_encrypter_without_secret(self): """Test factory function without secret key""" - with patch("core.tools.utils.system_oauth_encryption.dify_config") as mock_config: + with patch("core.tools.utils.system_encryption.dify_config") as mock_config: mock_config.SECRET_KEY = "config_secret" encrypter = create_system_encrypter() @@ -380,7 +380,7 @@ class TestFactoryFunctions: def test_create_system_oauth_encrypter_with_none_secret(self): """Test factory function with None secret key""" - with patch("core.tools.utils.system_oauth_encryption.dify_config") as mock_config: + with patch("core.tools.utils.system_encryption.dify_config") as mock_config: mock_config.SECRET_KEY = "config_secret" encrypter = create_system_encrypter(None) @@ -412,7 +412,7 @@ class TestGlobalEncrypterInstance: core.tools.utils.system_encryption._encrypter = None - with patch("core.tools.utils.system_oauth_encryption.dify_config") as mock_config: + with patch("core.tools.utils.system_encryption.dify_config") as mock_config: mock_config.SECRET_KEY = "global_secret" encrypter = get_system_encrypter()