diff --git a/api/tests/unit_tests/core/app/features/hosting_moderation/test_hosting_moderation.py b/api/tests/unit_tests/core/app/features/hosting_moderation/test_hosting_moderation.py index 01194c16f50..a7086e8cafc 100644 --- a/api/tests/unit_tests/core/app/features/hosting_moderation/test_hosting_moderation.py +++ b/api/tests/unit_tests/core/app/features/hosting_moderation/test_hosting_moderation.py @@ -1,19 +1,24 @@ from types import SimpleNamespace from unittest.mock import Mock, patch -from core.app.features.hosting_moderation.hosting_moderation import HostingModerationFeature +from core.app.features.hosting_moderation.hosting_moderation import ( + HostingModerationFeature, +) +from graphon.model_runtime.entities.message_entities import PromptMessage, UserPromptMessage class TestHostingModerationFeature: - def test_check_aggregates_text_and_calls_moderation(self): + def test_check_aggregates_text_and_calls_moderation(self) -> None: application_generate_entity = Mock() application_generate_entity.model_conf = {"model": "mock"} application_generate_entity.app_config = SimpleNamespace(tenant_id="tenant-1") - prompt_messages = [ - SimpleNamespace(content="hello"), - SimpleNamespace(content=123), - SimpleNamespace(content="world"), + # The middle message bypasses validation on purpose: `content` is not + # always a string at runtime, and the feature must skip those entries. + prompt_messages: list[PromptMessage] = [ + UserPromptMessage(role="user", content="hello"), + UserPromptMessage.model_construct(role="user", content=123), + UserPromptMessage(role="user", content="world"), ] with patch("core.app.features.hosting_moderation.hosting_moderation.moderation.check_moderation") as mock_check: diff --git a/api/tests/unit_tests/core/external_data_tool/test_factory.py b/api/tests/unit_tests/core/external_data_tool/test_factory.py index 273a4e75d39..80c5f9f1e49 100644 --- a/api/tests/unit_tests/core/external_data_tool/test_factory.py +++ b/api/tests/unit_tests/core/external_data_tool/test_factory.py @@ -4,7 +4,7 @@ from core.extension.extensible import ExtensionModule from core.external_data_tool.factory import ExternalDataToolFactory -def test_external_data_tool_factory_init(): +def test_external_data_tool_factory_init() -> None: with patch("core.external_data_tool.factory.code_based_extension") as mock_code_based_extension: mock_extension_class = MagicMock() mock_code_based_extension.extension_class.return_value = mock_extension_class @@ -23,7 +23,7 @@ def test_external_data_tool_factory_init(): ) -def test_external_data_tool_factory_validate_config(): +def test_external_data_tool_factory_validate_config() -> None: with patch("core.external_data_tool.factory.code_based_extension") as mock_code_based_extension: mock_extension_class = MagicMock() mock_code_based_extension.extension_class.return_value = mock_extension_class @@ -38,7 +38,7 @@ def test_external_data_tool_factory_validate_config(): mock_extension_class.validate_config.assert_called_once_with(tenant_id, config) -def test_external_data_tool_factory_query(): +def test_external_data_tool_factory_query() -> None: with patch("core.external_data_tool.factory.code_based_extension") as mock_code_based_extension: mock_extension_class = MagicMock() mock_extension_instance = MagicMock() diff --git a/api/tests/unit_tests/core/plugin/impl/test_asset.py b/api/tests/unit_tests/core/plugin/impl/test_asset.py index 994b00c2e6a..46a09df963f 100644 --- a/api/tests/unit_tests/core/plugin/impl/test_asset.py +++ b/api/tests/unit_tests/core/plugin/impl/test_asset.py @@ -6,7 +6,7 @@ from core.plugin.impl.asset import PluginAssetManager class TestPluginAssetManager: - def test_fetch_asset_success(self, mocker: MockerFixture): + def test_fetch_asset_success(self, mocker: MockerFixture) -> None: manager = PluginAssetManager() response = httpx.Response(status_code=200, content=b"asset-bytes") request_mock = mocker.patch.object(manager, "_request", return_value=response) @@ -16,14 +16,18 @@ class TestPluginAssetManager: assert result == b"asset-bytes" request_mock.assert_called_once_with(method="GET", path="plugin/tenant-1/asset/asset-1") - def test_fetch_asset_not_found_raises(self, mocker: MockerFixture): + def test_fetch_asset_not_found_raises(self, mocker: MockerFixture) -> None: manager = PluginAssetManager() - mocker.patch.object(manager, "_request", return_value=httpx.Response(status_code=404, content=b"")) + mocker.patch.object( + manager, + "_request", + return_value=httpx.Response(status_code=404, content=b""), + ) with pytest.raises(ValueError, match="can not found asset asset-1"): manager.fetch_asset("tenant-1", "asset-1") - def test_extract_asset_success(self, mocker: MockerFixture): + def test_extract_asset_success(self, mocker: MockerFixture) -> None: manager = PluginAssetManager() response = httpx.Response(status_code=200, content=b"file-content") request_mock = mocker.patch.object(manager, "_request", return_value=response) @@ -34,12 +38,19 @@ class TestPluginAssetManager: request_mock.assert_called_once_with( method="GET", path="plugin/tenant-1/extract-asset/", - params={"plugin_unique_identifier": "org/plugin:1", "file_path": "README.md"}, + params={ + "plugin_unique_identifier": "org/plugin:1", + "file_path": "README.md", + }, ) - def test_extract_asset_not_found_raises(self, mocker: MockerFixture): + def test_extract_asset_not_found_raises(self, mocker: MockerFixture) -> None: manager = PluginAssetManager() - mocker.patch.object(manager, "_request", return_value=httpx.Response(status_code=404, content=b"")) + mocker.patch.object( + manager, + "_request", + return_value=httpx.Response(status_code=404, content=b""), + ) with pytest.raises(ValueError, match="can not found asset org/plugin:1, 404"): manager.extract_asset("tenant-1", "org/plugin:1", "README.md") diff --git a/api/tests/unit_tests/core/plugin/impl/test_exc.py b/api/tests/unit_tests/core/plugin/impl/test_exc.py index d99a8c114fc..65576456bf2 100644 --- a/api/tests/unit_tests/core/plugin/impl/test_exc.py +++ b/api/tests/unit_tests/core/plugin/impl/test_exc.py @@ -7,13 +7,13 @@ from core.plugin.impl.exc import PluginDaemonError, PluginInvokeError class TestPluginImplExceptions: - def test_plugin_daemon_error_str_contains_request_id(self, mocker: MockerFixture): + def test_plugin_daemon_error_str_contains_request_id(self, mocker: MockerFixture) -> None: mocker.patch("core.plugin.impl.exc.get_request_id", return_value="req-123") error = PluginDaemonError("bad") assert str(error) == "req_id: req-123 PluginDaemonError: bad" - def test_plugin_invoke_error_with_json_payload(self): + def test_plugin_invoke_error_with_json_payload(self) -> None: err = PluginInvokeError(json.dumps({"error_type": "RateLimit", "message": "too many"})) assert err.get_error_type() == "RateLimit" @@ -23,7 +23,7 @@ class TestPluginImplExceptions: assert "RateLimit" in friendly assert "too many" in friendly - def test_plugin_invoke_error_invalid_json_and_fallback(self, mocker: MockerFixture): + def test_plugin_invoke_error_invalid_json_and_fallback(self, mocker: MockerFixture) -> None: err = PluginInvokeError("plain text") assert err._get_error_object() == {} @@ -34,7 +34,7 @@ class TestPluginImplExceptions: err2 = PluginInvokeError("plain text") assert err2.get_error_message() == "plain text" - def test_plugin_invoke_error_get_error_object_handles_adapter_exception(self, mocker: MockerFixture): + def test_plugin_invoke_error_get_error_object_handles_adapter_exception(self, mocker: MockerFixture) -> None: adapter = mocker.patch.object(exc_module, "TypeAdapter") adapter.return_value.validate_json.side_effect = RuntimeError("invalid") diff --git a/api/tests/unit_tests/core/trigger/utils/test_encryption.py b/api/tests/unit_tests/core/trigger/utils/test_encryption.py index 8804526e2ec..7202d27270d 100644 --- a/api/tests/unit_tests/core/trigger/utils/test_encryption.py +++ b/api/tests/unit_tests/core/trigger/utils/test_encryption.py @@ -21,41 +21,41 @@ def _make_schema(name: str, field_type: str = "secret-input") -> ProviderConfig: class TestMaskedCredentials: - def test_short_secret_fully_masked(self): + def test_short_secret_fully_masked(self) -> None: schema = [_make_schema("key", "secret-input")] result = masked_credentials(schema, {"key": "ab"}) assert result["key"] == "**" - def test_long_secret_partially_masked(self): + def test_long_secret_partially_masked(self) -> None: schema = [_make_schema("key", "secret-input")] result = masked_credentials(schema, {"key": "abcdef"}) assert result["key"].startswith("ab") assert result["key"].endswith("ef") assert "**" in result["key"] - def test_non_secret_field_unchanged(self): + def test_non_secret_field_unchanged(self) -> None: schema = [_make_schema("host", "text-input")] result = masked_credentials(schema, {"host": "example.com"}) assert result["host"] == "example.com" - def test_unknown_key_passes_through(self): + def test_unknown_key_passes_through(self) -> None: result = masked_credentials([], {"unknown": "value"}) assert result["unknown"] == "value" class TestCacheKeyGeneration: - def test_credentials_cache_key_contains_ids(self): + def test_credentials_cache_key_contains_ids(self) -> None: cache = TriggerProviderCredentialsCache(tenant_id="t1", provider_id="p1", credential_id="c1") assert "t1" in cache.cache_key assert "p1" in cache.cache_key assert "c1" in cache.cache_key - def test_oauth_client_cache_key_contains_ids(self): + def test_oauth_client_cache_key_contains_ids(self) -> None: cache = TriggerProviderOAuthClientParamsCache(tenant_id="t1", provider_id="p1") assert "t1" in cache.cache_key assert "p1" in cache.cache_key - def test_properties_cache_key_contains_ids(self): + def test_properties_cache_key_contains_ids(self) -> None: cache = TriggerProviderPropertiesCache(tenant_id="t1", provider_id="p1", subscription_id="s1") assert "t1" in cache.cache_key assert "p1" in cache.cache_key diff --git a/api/tests/unit_tests/core/workflow/test_graph_topology.py b/api/tests/unit_tests/core/workflow/test_graph_topology.py index d84780e6a11..35743ec9fe2 100644 --- a/api/tests/unit_tests/core/workflow/test_graph_topology.py +++ b/api/tests/unit_tests/core/workflow/test_graph_topology.py @@ -24,25 +24,25 @@ _GRAPH = { } -def test_upstream_node_ids_collects_all_ancestors_excluding_ghosts(): +def test_upstream_node_ids_collects_all_ancestors_excluding_ghosts() -> None: topology = WorkflowGraphTopology.from_graph(_GRAPH) assert topology.upstream_node_ids("agent") == {"start", "llm-1", "llm-2"} -def test_upstream_node_ids_differ_per_target_node(): +def test_upstream_node_ids_differ_per_target_node() -> None: topology = WorkflowGraphTopology.from_graph(_GRAPH) assert topology.upstream_node_ids("llm-1") == {"start"} assert topology.upstream_node_ids("end") == {"start", "llm-1", "llm-2", "agent"} assert topology.upstream_node_ids("start") == set() -def test_is_upstream_kept_for_publish_validation(): +def test_is_upstream_kept_for_publish_validation() -> None: topology = WorkflowGraphTopology.from_graph(_GRAPH) assert topology.is_upstream(source_node_id="start", target_node_id="end") assert not topology.is_upstream(source_node_id="end", target_node_id="start") -def test_cycle_safe(): +def test_cycle_safe() -> None: graph = { "nodes": [{"id": "a"}, {"id": "b"}], "edges": [{"source": "a", "target": "b"}, {"source": "b", "target": "a"}], diff --git a/api/tests/unit_tests/libs/test_custom_inputs.py b/api/tests/unit_tests/libs/test_custom_inputs.py index 7e4c3b4ff0e..24a16815b26 100644 --- a/api/tests/unit_tests/libs/test_custom_inputs.py +++ b/api/tests/unit_tests/libs/test_custom_inputs.py @@ -8,27 +8,27 @@ from libs.custom_inputs import time_duration class TestTimeDuration: """Test time_duration input validator.""" - def test_valid_days(self): + def test_valid_days(self) -> None: """Test valid days format.""" result = time_duration("7d") assert result == "7d" - def test_valid_hours(self): + def test_valid_hours(self) -> None: """Test valid hours format.""" result = time_duration("4h") assert result == "4h" - def test_valid_minutes(self): + def test_valid_minutes(self) -> None: """Test valid minutes format.""" result = time_duration("30m") assert result == "30m" - def test_valid_seconds(self): + def test_valid_seconds(self) -> None: """Test valid seconds format.""" result = time_duration("30s") assert result == "30s" - def test_uppercase_conversion(self): + def test_uppercase_conversion(self) -> None: """Test uppercase units are converted to lowercase.""" result = time_duration("7D") assert result == "7d" @@ -36,12 +36,12 @@ class TestTimeDuration: result = time_duration("4H") assert result == "4h" - def test_invalid_format_no_unit(self): + def test_invalid_format_no_unit(self) -> None: """Test invalid format without unit.""" with pytest.raises(ValueError, match="Invalid time duration format"): time_duration("7") - def test_invalid_format_wrong_unit(self): + def test_invalid_format_wrong_unit(self) -> None: """Test invalid format with wrong unit.""" with pytest.raises(ValueError, match="Invalid time duration format"): time_duration("7days") @@ -49,7 +49,7 @@ class TestTimeDuration: with pytest.raises(ValueError, match="Invalid time duration format"): time_duration("7x") - def test_invalid_format_no_number(self): + def test_invalid_format_no_number(self) -> None: """Test invalid format without number.""" with pytest.raises(ValueError, match="Invalid time duration format"): time_duration("d") @@ -57,12 +57,12 @@ class TestTimeDuration: with pytest.raises(ValueError, match="Invalid time duration format"): time_duration("abc") - def test_empty_string(self): + def test_empty_string(self) -> None: """Test empty string.""" with pytest.raises(ValueError, match="Time duration cannot be empty"): time_duration("") - def test_none(self): + def test_none(self) -> None: """Test None value.""" with pytest.raises(ValueError, match="Time duration cannot be empty"): - time_duration(None) + time_duration(None) # type: ignore[arg-type] diff --git a/api/tests/unit_tests/pyrefly.toml b/api/tests/unit_tests/pyrefly.toml index e662dc5b691..67cc25fa6e2 100644 --- a/api/tests/unit_tests/pyrefly.toml +++ b/api/tests/unit_tests/pyrefly.toml @@ -186,7 +186,6 @@ project-excludes = [ "core/app/entities/test_app_invoke_entities.py", "core/app/features/rate_limiting/test_rate_limit.py", "core/app/features/annotation_reply/test_annotation_reply.py", - "core/app/features/hosting_moderation/test_hosting_moderation.py", "core/app/layers/test_conversation_variable_persist_layer.py", "core/app/layers/test_pause_state_persist_layer.py", "core/app/layers/test_timeslice_layer.py", @@ -208,7 +207,6 @@ project-excludes = [ "core/entities/test_mcp_provider.py", "core/entities/test_provider_configuration.py", "core/extension/test_api_based_extension_requestor.py", - "core/external_data_tool/test_factory.py", "core/helper/code_executor/test_code_executor.py", "core/helper/test_ssrf_proxy.py", "core/llm_generator/output_parser/test_structured_output.py", @@ -231,11 +229,9 @@ project-excludes = [ "core/ops/entities/test_trace_entity.py", "core/ops/test_utils.py", "core/plugin/impl/test_agent.py", - "core/plugin/impl/test_asset.py", "core/plugin/impl/test_base.py", "core/plugin/impl/test_datasource.py", "core/plugin/impl/test_endpoint.py", - "core/plugin/impl/test_exc.py", "core/plugin/impl/test_model.py", "core/plugin/impl/test_oauth.py", "core/plugin/impl/test_trigger.py", @@ -300,7 +296,6 @@ project-excludes = [ "core/tools/utils/test_web_reader_tool.py", "core/tools/workflow_as_tool/test_provider.py", "core/tools/workflow_as_tool/test_tool.py", - "core/trigger/utils/test_encryption.py", "context/test_execution_context.py", "context/test_flask_app_context.py", "../e2e_tests/contracts/core/workflow/entities/test_private_workflow_pause.py", @@ -348,7 +343,6 @@ project-excludes = [ "core/workflow/nodes/trigger_webhook/test_entities.py", "core/workflow/nodes/trigger_webhook/test_node.py", "core/workflow/nodes/human_input/test_pause_reason.py", - "core/workflow/test_graph_topology.py", "core/workflow/nodes/human_input/test_callback.py", "core/workflow/test_human_input_forms.py", "../e2e_tests/contracts/core/workflow/test_node_mapping_bootstrap.py", @@ -384,7 +378,6 @@ project-excludes = [ "libs/broadcast_channel/redis/test_streams_channel.py", "libs/test_archive_storage.py", "libs/test_schedule_utils.py", - "libs/test_custom_inputs.py", "libs/test_datetime_utils.py", "libs/test_email_i18n.py", "libs/test_external_api.py", @@ -420,7 +413,6 @@ project-excludes = [ "../helpers/unit/services/hit_service.py", "services/plugin/test_plugin_parameter_service.py", "services/errors/test_plugin.py", - "services/rag_pipeline/pipeline_template/test_pipeline_template_base.py", "services/rag_pipeline/test_pipeline_generate_service.py", "services/rag_pipeline/test_rag_pipeline_dsl_service.py", "services/rag_pipeline/test_rag_pipeline.py", @@ -469,7 +461,6 @@ project-excludes = [ "services/test_workflow_node_execution_trace_service.py", "services/test_workflow_run_service.py", "services/test_workflow_service.py", - "services/tools/test_api_tools_manage_service.py", "services/tools/test_builtin_tools_manage_service.py", "services/tools/test_tools_transform_service.py", "services/tools/test_tools_manage_service.py", diff --git a/api/tests/unit_tests/services/rag_pipeline/pipeline_template/test_pipeline_template_base.py b/api/tests/unit_tests/services/rag_pipeline/pipeline_template/test_pipeline_template_base.py index ad19b411a43..198e2ee70b1 100644 --- a/api/tests/unit_tests/services/rag_pipeline/pipeline_template/test_pipeline_template_base.py +++ b/api/tests/unit_tests/services/rag_pipeline/pipeline_template/test_pipeline_template_base.py @@ -1,15 +1,21 @@ import pytest from sqlalchemy.orm import Session -from services.rag_pipeline.pipeline_template.pipeline_template_base import PipelineTemplateRetrievalBase +from services.rag_pipeline.pipeline_template.pipeline_template_base import ( + PipelineTemplateRetrievalBase, +) class DummyRetrieval(PipelineTemplateRetrievalBase): - def get_pipeline_templates(self, language: str, *, session) -> dict: - del session + def get_pipeline_templates( + self, language: str, current_tenant_id: str | None = None, *, session: Session + ) -> dict[str, object]: + del current_tenant_id, session return {"language": language} - def get_pipeline_template_detail(self, template_id: str, current_tenant_id: str, *, session) -> dict | None: + def get_pipeline_template_detail( + self, template_id: str, current_tenant_id: str, *, session: Session + ) -> dict[str, object] | None: del current_tenant_id, session return {"id": template_id} @@ -18,7 +24,9 @@ class DummyRetrieval(PipelineTemplateRetrievalBase): @pytest.mark.parametrize("sqlite_session", [()], indirect=True) -def test_pipeline_template_retrieval_base_concrete_implementation(sqlite_session: Session) -> None: +def test_pipeline_template_retrieval_base_concrete_implementation( + sqlite_session: Session, +) -> None: retrieval = DummyRetrieval() assert retrieval.get_pipeline_templates("en-US", session=sqlite_session) == {"language": "en-US"} diff --git a/api/tests/unit_tests/services/tools/test_api_tools_manage_service.py b/api/tests/unit_tests/services/tools/test_api_tools_manage_service.py index 5c4825019f7..db1933927fb 100644 --- a/api/tests/unit_tests/services/tools/test_api_tools_manage_service.py +++ b/api/tests/unit_tests/services/tools/test_api_tools_manage_service.py @@ -5,7 +5,9 @@ import pytest from services.tools.api_tools_manage_service import ApiToolManageService -def test_get_api_tool_provider_remote_schema_uses_ssrf_proxy_get(monkeypatch: pytest.MonkeyPatch) -> None: +def test_get_api_tool_provider_remote_schema_uses_ssrf_proxy_get( + monkeypatch: pytest.MonkeyPatch, +) -> None: schema = """ { "openapi": "3.0.0",