mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 23:06:40 +08:00
refactor(api): enable strict typing for content moderation tests (#43650)
This commit is contained in:
1 parent
0e78aa5829
commit
6cdbdbd962
2 files changed
+83
-74
No files matched your search
@@ -65,12 +65,12 @@ class TestKeywordsModeration:
|
||||
config=keywords_config,
|
||||
)
|
||||
|
||||
def test_validate_config_success(self, keywords_config: dict[str, Any]):
|
||||
def test_validate_config_success(self, keywords_config: dict[str, Any]) -> None:
|
||||
"""Test successful validation of keywords moderation configuration."""
|
||||
# Should not raise any exception
|
||||
KeywordsModeration.validate_config("test-tenant", keywords_config)
|
||||
|
||||
def test_validate_config_missing_keywords(self):
|
||||
def test_validate_config_missing_keywords(self) -> None:
|
||||
"""Test validation fails when keywords are missing."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": True, "preset_response": "Blocked"},
|
||||
@@ -80,7 +80,7 @@ class TestKeywordsModeration:
|
||||
with pytest.raises(ValueError, match="keywords is required"):
|
||||
KeywordsModeration.validate_config("test-tenant", config)
|
||||
|
||||
def test_validate_config_keywords_too_long(self):
|
||||
def test_validate_config_keywords_too_long(self) -> None:
|
||||
"""Test validation fails when keywords exceed length limit."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": True, "preset_response": "Blocked"},
|
||||
@@ -91,7 +91,7 @@ class TestKeywordsModeration:
|
||||
with pytest.raises(ValueError, match="keywords length must be less than 10000"):
|
||||
KeywordsModeration.validate_config("test-tenant", config)
|
||||
|
||||
def test_validate_config_too_many_rows(self):
|
||||
def test_validate_config_too_many_rows(self) -> None:
|
||||
"""Test validation fails when keyword rows exceed limit."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": True, "preset_response": "Blocked"},
|
||||
@@ -102,7 +102,7 @@ class TestKeywordsModeration:
|
||||
with pytest.raises(ValueError, match="the number of rows for the keywords must be less than 100"):
|
||||
KeywordsModeration.validate_config("test-tenant", config)
|
||||
|
||||
def test_validate_config_missing_preset_response(self):
|
||||
def test_validate_config_missing_preset_response(self) -> None:
|
||||
"""Test validation fails when preset response is missing for enabled config."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": True}, # Missing preset_response
|
||||
@@ -113,7 +113,7 @@ class TestKeywordsModeration:
|
||||
with pytest.raises(ValueError, match="inputs_config.preset_response is required"):
|
||||
KeywordsModeration.validate_config("test-tenant", config)
|
||||
|
||||
def test_validate_config_preset_response_too_long(self):
|
||||
def test_validate_config_preset_response_too_long(self) -> None:
|
||||
"""Test validation fails when preset response exceeds character limit."""
|
||||
config = {
|
||||
"inputs_config": {
|
||||
@@ -127,7 +127,7 @@ class TestKeywordsModeration:
|
||||
with pytest.raises(ValueError, match="inputs_config.preset_response must be less than 100 characters"):
|
||||
KeywordsModeration.validate_config("test-tenant", config)
|
||||
|
||||
def test_moderation_for_inputs_crlf_keywords_match(self, keywords_config: dict[str, Any]):
|
||||
def test_moderation_for_inputs_crlf_keywords_match(self, keywords_config: dict[str, Any]) -> None:
|
||||
"""Keywords pasted with Windows line endings still match."""
|
||||
keywords_config["keywords"] = "spam\r\nfree money"
|
||||
moderation = KeywordsModeration(app_id="app", tenant_id="tenant", config=keywords_config)
|
||||
@@ -136,7 +136,7 @@ class TestKeywordsModeration:
|
||||
|
||||
assert result.flagged is True
|
||||
|
||||
def test_moderation_whitespace_only_keyword_row_is_ignored(self, keywords_config: dict[str, Any]):
|
||||
def test_moderation_whitespace_only_keyword_row_is_ignored(self, keywords_config: dict[str, Any]) -> None:
|
||||
"""A whitespace-only keyword row must not flag every message."""
|
||||
keywords_config["keywords"] = "spam\n \n "
|
||||
moderation = KeywordsModeration(app_id="app", tenant_id="tenant", config=keywords_config)
|
||||
@@ -145,7 +145,7 @@ class TestKeywordsModeration:
|
||||
|
||||
assert result.flagged is False
|
||||
|
||||
def test_moderation_for_inputs_no_violation(self, keywords_moderation: KeywordsModeration):
|
||||
def test_moderation_for_inputs_no_violation(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test input moderation when no keywords are matched."""
|
||||
inputs = {"user_input": "This is a clean message"}
|
||||
query = "What is the weather?"
|
||||
@@ -156,7 +156,7 @@ class TestKeywordsModeration:
|
||||
assert result.action == ModerationAction.DIRECT_OUTPUT
|
||||
assert result.preset_response == "Your input contains inappropriate content."
|
||||
|
||||
def test_moderation_for_inputs_with_violation_in_query(self, keywords_moderation: KeywordsModeration):
|
||||
def test_moderation_for_inputs_with_violation_in_query(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test input moderation detects keywords in query string."""
|
||||
inputs = {"user_input": "Hello"}
|
||||
query = "Tell me about badword"
|
||||
@@ -167,7 +167,7 @@ class TestKeywordsModeration:
|
||||
assert result.action == ModerationAction.DIRECT_OUTPUT
|
||||
assert result.preset_response == "Your input contains inappropriate content."
|
||||
|
||||
def test_moderation_for_inputs_with_violation_in_inputs(self, keywords_moderation: KeywordsModeration):
|
||||
def test_moderation_for_inputs_with_violation_in_inputs(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test input moderation detects keywords in input fields."""
|
||||
inputs = {"user_input": "This contains offensive content"}
|
||||
query = ""
|
||||
@@ -177,7 +177,7 @@ class TestKeywordsModeration:
|
||||
assert result.flagged is True
|
||||
assert result.action == ModerationAction.DIRECT_OUTPUT
|
||||
|
||||
def test_moderation_for_inputs_case_insensitive(self, keywords_moderation: KeywordsModeration):
|
||||
def test_moderation_for_inputs_case_insensitive(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test keyword matching is case-insensitive."""
|
||||
inputs = {"user_input": "This has BADWORD in caps"}
|
||||
query = ""
|
||||
@@ -186,7 +186,7 @@ class TestKeywordsModeration:
|
||||
|
||||
assert result.flagged is True
|
||||
|
||||
def test_moderation_for_inputs_partial_match(self, keywords_moderation: KeywordsModeration):
|
||||
def test_moderation_for_inputs_partial_match(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test keywords are matched as substrings."""
|
||||
inputs = {"user_input": "This has badwords (plural)"}
|
||||
query = ""
|
||||
@@ -195,7 +195,7 @@ class TestKeywordsModeration:
|
||||
|
||||
assert result.flagged is True
|
||||
|
||||
def test_moderation_for_inputs_disabled(self):
|
||||
def test_moderation_for_inputs_disabled(self) -> None:
|
||||
"""Test input moderation when inputs_config is disabled."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": False},
|
||||
@@ -209,7 +209,7 @@ class TestKeywordsModeration:
|
||||
|
||||
assert result.flagged is False
|
||||
|
||||
def test_moderation_for_outputs_no_violation(self, keywords_moderation: KeywordsModeration):
|
||||
def test_moderation_for_outputs_no_violation(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test output moderation when no keywords are matched."""
|
||||
text = "This is a clean response from the AI"
|
||||
|
||||
@@ -219,7 +219,7 @@ class TestKeywordsModeration:
|
||||
assert result.action == ModerationAction.DIRECT_OUTPUT
|
||||
assert result.preset_response == "The response was blocked due to policy."
|
||||
|
||||
def test_moderation_for_outputs_with_violation(self, keywords_moderation: KeywordsModeration):
|
||||
def test_moderation_for_outputs_with_violation(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test output moderation detects keywords in output text."""
|
||||
text = "This response contains spam content"
|
||||
|
||||
@@ -229,7 +229,7 @@ class TestKeywordsModeration:
|
||||
assert result.action == ModerationAction.DIRECT_OUTPUT
|
||||
assert result.preset_response == "The response was blocked due to policy."
|
||||
|
||||
def test_moderation_for_outputs_case_insensitive(self, keywords_moderation: KeywordsModeration):
|
||||
def test_moderation_for_outputs_case_insensitive(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test output keyword matching is case-insensitive."""
|
||||
text = "This has OFFENSIVE in uppercase"
|
||||
|
||||
@@ -237,7 +237,7 @@ class TestKeywordsModeration:
|
||||
|
||||
assert result.flagged is True
|
||||
|
||||
def test_moderation_for_outputs_disabled(self):
|
||||
def test_moderation_for_outputs_disabled(self) -> None:
|
||||
"""Test output moderation when outputs_config is disabled."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": True, "preset_response": "Blocked"},
|
||||
@@ -250,7 +250,7 @@ class TestKeywordsModeration:
|
||||
|
||||
assert result.flagged is False
|
||||
|
||||
def test_empty_keywords_filtered(self):
|
||||
def test_empty_keywords_filtered(self) -> None:
|
||||
"""Test that empty lines in keywords are properly filtered out."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": True, "preset_response": "Blocked"},
|
||||
@@ -266,7 +266,7 @@ class TestKeywordsModeration:
|
||||
result = moderation.moderation_for_inputs({"input": "clean"}, "")
|
||||
assert result.flagged is False
|
||||
|
||||
def test_multiple_inputs_any_violation(self, keywords_moderation: KeywordsModeration):
|
||||
def test_multiple_inputs_any_violation(self, keywords_moderation: KeywordsModeration) -> None:
|
||||
"""Test that violation in any input field triggers flagging."""
|
||||
inputs = {
|
||||
"field1": "clean text",
|
||||
@@ -278,7 +278,7 @@ class TestKeywordsModeration:
|
||||
|
||||
assert result.flagged is True
|
||||
|
||||
def test_config_not_set_raises_error(self):
|
||||
def test_config_not_set_raises_error(self) -> None:
|
||||
"""Test that moderation fails gracefully when config is None."""
|
||||
moderation = KeywordsModeration("app-id", "tenant-id", None)
|
||||
|
||||
@@ -328,12 +328,12 @@ class TestOpenAIModeration:
|
||||
config=openai_config,
|
||||
)
|
||||
|
||||
def test_validate_config_success(self, openai_config: dict[str, Any]):
|
||||
def test_validate_config_success(self, openai_config: dict[str, Any]) -> None:
|
||||
"""Test successful validation of OpenAI moderation configuration."""
|
||||
# Should not raise any exception
|
||||
OpenAIModeration.validate_config("test-tenant", openai_config)
|
||||
|
||||
def test_validate_config_both_disabled_fails(self):
|
||||
def test_validate_config_both_disabled_fails(self) -> None:
|
||||
"""Test validation fails when both inputs and outputs are disabled."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": False},
|
||||
@@ -344,7 +344,9 @@ class TestOpenAIModeration:
|
||||
OpenAIModeration.validate_config("test-tenant", config)
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_moderation_for_inputs_no_violation(self, mock_model_manager: Mock, openai_moderation: OpenAIModeration):
|
||||
def test_moderation_for_inputs_no_violation(
|
||||
self, mock_model_manager: Mock, openai_moderation: OpenAIModeration
|
||||
) -> None:
|
||||
"""Test input moderation when OpenAI API returns no violations."""
|
||||
# Mock the model manager and instance
|
||||
mock_instance = MagicMock()
|
||||
@@ -361,7 +363,9 @@ class TestOpenAIModeration:
|
||||
assert result.preset_response == "Content flagged by OpenAI moderation."
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_moderation_for_inputs_with_violation(self, mock_model_manager: Mock, openai_moderation: OpenAIModeration):
|
||||
def test_moderation_for_inputs_with_violation(
|
||||
self, mock_model_manager: Mock, openai_moderation: OpenAIModeration
|
||||
) -> None:
|
||||
"""Test input moderation when OpenAI API detects violations."""
|
||||
# Mock the model manager to return violation
|
||||
mock_instance = MagicMock()
|
||||
@@ -378,7 +382,9 @@ class TestOpenAIModeration:
|
||||
assert result.preset_response == "Content flagged by OpenAI moderation."
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_moderation_for_inputs_query_included(self, mock_model_manager: Mock, openai_moderation: OpenAIModeration):
|
||||
def test_moderation_for_inputs_query_included(
|
||||
self, mock_model_manager: Mock, openai_moderation: OpenAIModeration
|
||||
) -> None:
|
||||
"""Test that query is included in moderation check with special key."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.invoke_moderation.return_value = False
|
||||
@@ -405,7 +411,7 @@ class TestOpenAIModeration:
|
||||
assert "e" in moderated_text
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_moderation_for_inputs_disabled(self, mock_model_manager: Mock):
|
||||
def test_moderation_for_inputs_disabled(self, mock_model_manager: Mock) -> None:
|
||||
"""Test input moderation when inputs_config is disabled."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": False},
|
||||
@@ -420,7 +426,9 @@ class TestOpenAIModeration:
|
||||
mock_model_manager.assert_not_called()
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_moderation_for_outputs_no_violation(self, mock_model_manager: Mock, openai_moderation: OpenAIModeration):
|
||||
def test_moderation_for_outputs_no_violation(
|
||||
self, mock_model_manager: Mock, openai_moderation: OpenAIModeration
|
||||
) -> None:
|
||||
"""Test output moderation when OpenAI API returns no violations."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.invoke_moderation.return_value = False
|
||||
@@ -434,7 +442,9 @@ class TestOpenAIModeration:
|
||||
assert result.preset_response == "Response blocked by moderation."
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_moderation_for_outputs_with_violation(self, mock_model_manager: Mock, openai_moderation: OpenAIModeration):
|
||||
def test_moderation_for_outputs_with_violation(
|
||||
self, mock_model_manager: Mock, openai_moderation: OpenAIModeration
|
||||
) -> None:
|
||||
"""Test output moderation when OpenAI API detects violations."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.invoke_moderation.return_value = True
|
||||
@@ -447,7 +457,7 @@ class TestOpenAIModeration:
|
||||
assert result.action == ModerationAction.DIRECT_OUTPUT
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_moderation_for_outputs_disabled(self, mock_model_manager: Mock):
|
||||
def test_moderation_for_outputs_disabled(self, mock_model_manager: Mock) -> None:
|
||||
"""Test output moderation when outputs_config is disabled."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": True, "preset_response": "Blocked"},
|
||||
@@ -463,7 +473,7 @@ class TestOpenAIModeration:
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_model_manager_called_with_correct_params(
|
||||
self, mock_model_manager: Mock, openai_moderation: OpenAIModeration
|
||||
):
|
||||
) -> None:
|
||||
"""Test that ModelManager is called with correct parameters."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.invoke_moderation.return_value = False
|
||||
@@ -478,7 +488,7 @@ class TestOpenAIModeration:
|
||||
assert call_kwargs["provider"] == "openai"
|
||||
assert call_kwargs["model"] == "omni-moderation-latest"
|
||||
|
||||
def test_config_not_set_raises_error(self):
|
||||
def test_config_not_set_raises_error(self) -> None:
|
||||
"""Test that moderation fails when config is None."""
|
||||
moderation = OpenAIModeration("app-id", "tenant-id", None)
|
||||
|
||||
@@ -492,7 +502,7 @@ class TestOpenAIModeration:
|
||||
class TestModerationRuleStructure:
|
||||
"""Test suite for ModerationRule data structure."""
|
||||
|
||||
def test_moderation_rule_structure(self):
|
||||
def test_moderation_rule_structure(self) -> None:
|
||||
"""Test ModerationRule structure for output moderation."""
|
||||
from core.moderation.output_moderation import ModerationRule
|
||||
|
||||
@@ -514,7 +524,7 @@ class TestModerationFactoryIntegration:
|
||||
"""Test suite for ModerationFactory integration."""
|
||||
|
||||
@patch("core.moderation.factory.code_based_extension", autospec=True)
|
||||
def test_factory_delegates_to_extension(self, mock_extension: Mock):
|
||||
def test_factory_delegates_to_extension(self, mock_extension: Mock) -> None:
|
||||
"""Test ModerationFactory delegates to extension system."""
|
||||
from core.moderation.factory import ModerationFactory
|
||||
|
||||
@@ -538,7 +548,7 @@ class TestModerationFactoryIntegration:
|
||||
mock_instance.moderation_for_inputs.assert_called_once()
|
||||
|
||||
@patch("core.moderation.factory.code_based_extension", autospec=True)
|
||||
def test_factory_validate_config_delegates(self, mock_extension: Mock):
|
||||
def test_factory_validate_config_delegates(self, mock_extension: Mock) -> None:
|
||||
"""Test ModerationFactory.validate_config delegates to extension."""
|
||||
from core.moderation.factory import ModerationFactory
|
||||
|
||||
@@ -553,12 +563,12 @@ class TestModerationFactoryIntegration:
|
||||
class TestModerationBase:
|
||||
"""Test suite for base moderation classes and enums."""
|
||||
|
||||
def test_moderation_action_enum_values(self):
|
||||
def test_moderation_action_enum_values(self) -> None:
|
||||
"""Test ModerationAction enum has expected values."""
|
||||
assert ModerationAction.DIRECT_OUTPUT == "direct_output"
|
||||
assert ModerationAction.OVERRIDDEN == "overridden"
|
||||
|
||||
def test_moderation_inputs_result_defaults(self):
|
||||
def test_moderation_inputs_result_defaults(self) -> None:
|
||||
"""Test ModerationInputsResult default values."""
|
||||
result = ModerationInputsResult(action=ModerationAction.DIRECT_OUTPUT)
|
||||
|
||||
@@ -567,7 +577,7 @@ class TestModerationBase:
|
||||
assert result.inputs == {}
|
||||
assert result.query == ""
|
||||
|
||||
def test_moderation_outputs_result_defaults(self):
|
||||
def test_moderation_outputs_result_defaults(self) -> None:
|
||||
"""Test ModerationOutputsResult default values."""
|
||||
result = ModerationOutputsResult(action=ModerationAction.DIRECT_OUTPUT)
|
||||
|
||||
@@ -575,12 +585,12 @@ class TestModerationBase:
|
||||
assert result.preset_response == ""
|
||||
assert result.text == ""
|
||||
|
||||
def test_moderation_error_exception(self):
|
||||
def test_moderation_error_exception(self) -> None:
|
||||
"""Test ModerationError can be raised and caught."""
|
||||
with pytest.raises(ModerationError, match="Test error message"):
|
||||
raise ModerationError("Test error message")
|
||||
|
||||
def test_moderation_inputs_result_with_values(self):
|
||||
def test_moderation_inputs_result_with_values(self) -> None:
|
||||
"""Test ModerationInputsResult with custom values."""
|
||||
result = ModerationInputsResult(
|
||||
flagged=True,
|
||||
@@ -596,7 +606,7 @@ class TestModerationBase:
|
||||
assert result.inputs == {"field": "sanitized"}
|
||||
assert result.query == "sanitized query"
|
||||
|
||||
def test_moderation_outputs_result_with_values(self):
|
||||
def test_moderation_outputs_result_with_values(self) -> None:
|
||||
"""Test ModerationOutputsResult with custom values."""
|
||||
result = ModerationOutputsResult(
|
||||
flagged=True,
|
||||
@@ -614,7 +624,7 @@ class TestModerationBase:
|
||||
class TestPresetManagement:
|
||||
"""Test suite for preset response management across moderation types."""
|
||||
|
||||
def test_keywords_preset_response_in_inputs(self):
|
||||
def test_keywords_preset_response_in_inputs(self) -> None:
|
||||
"""Test preset response is properly returned for keyword input violations."""
|
||||
config = {
|
||||
"inputs_config": {
|
||||
@@ -631,7 +641,7 @@ class TestPresetManagement:
|
||||
assert result.flagged is True
|
||||
assert result.preset_response == "Custom input blocked message"
|
||||
|
||||
def test_keywords_preset_response_in_outputs(self):
|
||||
def test_keywords_preset_response_in_outputs(self) -> None:
|
||||
"""Test preset response is properly returned for keyword output violations."""
|
||||
config = {
|
||||
"inputs_config": {"enabled": False},
|
||||
@@ -649,7 +659,7 @@ class TestPresetManagement:
|
||||
assert result.preset_response == "Custom output blocked message"
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_openai_preset_response_in_inputs(self, mock_model_manager: Mock):
|
||||
def test_openai_preset_response_in_inputs(self, mock_model_manager: Mock) -> None:
|
||||
"""Test preset response is properly returned for OpenAI input violations."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.invoke_moderation.return_value = True
|
||||
@@ -670,7 +680,7 @@ class TestPresetManagement:
|
||||
assert result.preset_response == "OpenAI input blocked"
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_openai_preset_response_in_outputs(self, mock_model_manager: Mock):
|
||||
def test_openai_preset_response_in_outputs(self, mock_model_manager: Mock) -> None:
|
||||
"""Test preset response is properly returned for OpenAI output violations."""
|
||||
mock_instance = MagicMock()
|
||||
mock_instance.invoke_moderation.return_value = True
|
||||
@@ -690,7 +700,7 @@ class TestPresetManagement:
|
||||
assert result.flagged is True
|
||||
assert result.preset_response == "OpenAI output blocked"
|
||||
|
||||
def test_preset_response_length_validation(self):
|
||||
def test_preset_response_length_validation(self) -> None:
|
||||
"""Test that preset responses exceeding 100 characters are rejected."""
|
||||
config = {
|
||||
"inputs_config": {
|
||||
@@ -704,7 +714,7 @@ class TestPresetManagement:
|
||||
with pytest.raises(ValueError, match="must be less than 100 characters"):
|
||||
KeywordsModeration.validate_config("tenant-id", config)
|
||||
|
||||
def test_different_preset_responses_for_inputs_and_outputs(self):
|
||||
def test_different_preset_responses_for_inputs_and_outputs(self) -> None:
|
||||
"""Test that inputs and outputs can have different preset responses."""
|
||||
config = {
|
||||
"inputs_config": {
|
||||
@@ -737,7 +747,7 @@ class TestKeywordsModerationAdvanced:
|
||||
- Complex input structures
|
||||
"""
|
||||
|
||||
def test_unicode_keywords_matching(self):
|
||||
def test_unicode_keywords_matching(self) -> None:
|
||||
"""
|
||||
Test that keyword moderation correctly handles Unicode characters.
|
||||
|
||||
@@ -763,7 +773,7 @@ class TestKeywordsModerationAdvanced:
|
||||
result = moderation.moderation_for_outputs("This is 🚫 content")
|
||||
assert result.flagged is True
|
||||
|
||||
def test_special_regex_characters_in_keywords(self):
|
||||
def test_special_regex_characters_in_keywords(self) -> None:
|
||||
"""
|
||||
Test that special regex characters in keywords are treated as literals.
|
||||
|
||||
@@ -793,7 +803,7 @@ class TestKeywordsModerationAdvanced:
|
||||
result = moderation.moderation_for_inputs({"text": "Get $money fast"}, "")
|
||||
assert result.flagged is True
|
||||
|
||||
def test_whitespace_variations_in_keywords(self):
|
||||
def test_whitespace_variations_in_keywords(self) -> None:
|
||||
"""
|
||||
Test keyword matching with various whitespace characters.
|
||||
|
||||
@@ -815,7 +825,7 @@ class TestKeywordsModerationAdvanced:
|
||||
result = moderation.moderation_for_inputs({"text": "tab\there"}, "")
|
||||
assert result.flagged is True
|
||||
|
||||
def test_maximum_keyword_length_boundary(self):
|
||||
def test_maximum_keyword_length_boundary(self) -> None:
|
||||
"""
|
||||
Test behavior at the maximum allowed keyword list length (10000 chars).
|
||||
|
||||
@@ -848,7 +858,7 @@ class TestKeywordsModerationAdvanced:
|
||||
with pytest.raises(ValueError, match="keywords length must be less than 10000"):
|
||||
KeywordsModeration.validate_config("tenant-id", config_over)
|
||||
|
||||
def test_maximum_keyword_rows_boundary(self):
|
||||
def test_maximum_keyword_rows_boundary(self) -> None:
|
||||
"""
|
||||
Test behavior at the maximum allowed keyword rows (100 rows).
|
||||
|
||||
@@ -878,7 +888,7 @@ class TestKeywordsModerationAdvanced:
|
||||
with pytest.raises(ValueError, match="the number of rows for the keywords must be less than 100"):
|
||||
KeywordsModeration.validate_config("tenant-id", config_over)
|
||||
|
||||
def test_nested_dict_input_values(self):
|
||||
def test_nested_dict_input_values(self) -> None:
|
||||
"""
|
||||
Test moderation with nested dictionary structures in inputs.
|
||||
|
||||
@@ -902,7 +912,7 @@ class TestKeywordsModerationAdvanced:
|
||||
result = moderation.moderation_for_inputs(nested_input, "")
|
||||
assert result.flagged is True
|
||||
|
||||
def test_numeric_input_values(self):
|
||||
def test_numeric_input_values(self) -> None:
|
||||
"""
|
||||
Test moderation with numeric input values.
|
||||
|
||||
@@ -928,7 +938,7 @@ class TestKeywordsModerationAdvanced:
|
||||
result = moderation.moderation_for_inputs({"text": "Room 666"}, "")
|
||||
assert result.flagged is True
|
||||
|
||||
def test_boolean_input_values(self):
|
||||
def test_boolean_input_values(self) -> None:
|
||||
"""
|
||||
Test moderation with boolean input values.
|
||||
|
||||
@@ -950,7 +960,7 @@ class TestKeywordsModerationAdvanced:
|
||||
result = moderation.moderation_for_inputs({"flag": False}, "")
|
||||
assert result.flagged is True
|
||||
|
||||
def test_empty_string_inputs(self):
|
||||
def test_empty_string_inputs(self) -> None:
|
||||
"""
|
||||
Test moderation with empty string inputs.
|
||||
|
||||
@@ -972,7 +982,7 @@ class TestKeywordsModerationAdvanced:
|
||||
result = moderation.moderation_for_inputs({"text": "clean"}, "")
|
||||
assert result.flagged is False
|
||||
|
||||
def test_very_long_input_text(self):
|
||||
def test_very_long_input_text(self) -> None:
|
||||
"""
|
||||
Test moderation performance with very long input text.
|
||||
|
||||
@@ -1009,7 +1019,7 @@ class TestOpenAIModerationAdvanced:
|
||||
"""
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_openai_api_timeout_handling(self, mock_model_manager: Mock):
|
||||
def test_openai_api_timeout_handling(self, mock_model_manager: Mock) -> None:
|
||||
"""
|
||||
Test graceful handling of OpenAI API timeouts.
|
||||
|
||||
@@ -1032,7 +1042,7 @@ class TestOpenAIModerationAdvanced:
|
||||
moderation.moderation_for_inputs({"text": "test"}, "")
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_openai_api_rate_limit_handling(self, mock_model_manager: Mock):
|
||||
def test_openai_api_rate_limit_handling(self, mock_model_manager: Mock) -> None:
|
||||
"""
|
||||
Test handling of OpenAI API rate limit errors.
|
||||
|
||||
@@ -1055,7 +1065,7 @@ class TestOpenAIModerationAdvanced:
|
||||
moderation.moderation_for_inputs({"text": "test"}, "")
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_openai_with_multiple_input_fields(self, mock_model_manager: Mock):
|
||||
def test_openai_with_multiple_input_fields(self, mock_model_manager: Mock) -> None:
|
||||
"""
|
||||
Test OpenAI moderation with multiple input fields.
|
||||
|
||||
@@ -1099,7 +1109,7 @@ class TestOpenAIModerationAdvanced:
|
||||
assert "e" in moderated_text
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_openai_empty_text_handling(self, mock_model_manager: Mock):
|
||||
def test_openai_empty_text_handling(self, mock_model_manager: Mock) -> None:
|
||||
"""
|
||||
Test OpenAI moderation with empty text inputs.
|
||||
|
||||
@@ -1123,7 +1133,7 @@ class TestOpenAIModerationAdvanced:
|
||||
mock_instance.invoke_moderation.assert_called_once()
|
||||
|
||||
@patch("core.moderation.openai_moderation.openai_moderation.ModelManager.for_tenant", autospec=True)
|
||||
def test_openai_model_instance_fetched_on_each_call(self, mock_model_manager: Mock):
|
||||
def test_openai_model_instance_fetched_on_each_call(self, mock_model_manager: Mock) -> None:
|
||||
"""
|
||||
Test that ModelManager fetches a fresh model instance on each call.
|
||||
|
||||
@@ -1158,7 +1168,7 @@ class TestModerationActionBehavior:
|
||||
- OVERRIDDEN: Returns sanitized/modified content
|
||||
"""
|
||||
|
||||
def test_direct_output_action_blocks_completely(self):
|
||||
def test_direct_output_action_blocks_completely(self) -> None:
|
||||
"""
|
||||
Test that DIRECT_OUTPUT action completely blocks content.
|
||||
|
||||
@@ -1179,7 +1189,7 @@ class TestModerationActionBehavior:
|
||||
assert result.inputs == {}
|
||||
assert result.query == ""
|
||||
|
||||
def test_overridden_action_sanitizes_content(self):
|
||||
def test_overridden_action_sanitizes_content(self) -> None:
|
||||
"""
|
||||
Test that OVERRIDDEN action provides sanitized content.
|
||||
|
||||
@@ -1200,7 +1210,7 @@ class TestModerationActionBehavior:
|
||||
assert result.query == "Tell me about ***"
|
||||
assert result.preset_response == ""
|
||||
|
||||
def test_action_enum_string_values(self):
|
||||
def test_action_enum_string_values(self) -> None:
|
||||
"""
|
||||
Test that ModerationAction enum has correct string values.
|
||||
|
||||
@@ -1222,7 +1232,7 @@ class TestConfigurationEdgeCases:
|
||||
proper validation and error messages.
|
||||
"""
|
||||
|
||||
def test_missing_inputs_config_dict(self):
|
||||
def test_missing_inputs_config_dict(self) -> None:
|
||||
"""
|
||||
Test validation fails when inputs_config is not a dict.
|
||||
|
||||
@@ -1238,7 +1248,7 @@ class TestConfigurationEdgeCases:
|
||||
with pytest.raises(ValueError, match="inputs_config must be a dict"):
|
||||
KeywordsModeration.validate_config("tenant-id", config)
|
||||
|
||||
def test_missing_outputs_config_dict(self):
|
||||
def test_missing_outputs_config_dict(self) -> None:
|
||||
"""
|
||||
Test validation fails when outputs_config is not a dict.
|
||||
|
||||
@@ -1254,7 +1264,7 @@ class TestConfigurationEdgeCases:
|
||||
with pytest.raises(ValueError, match="outputs_config must be a dict"):
|
||||
KeywordsModeration.validate_config("tenant-id", config)
|
||||
|
||||
def test_both_inputs_and_outputs_disabled(self):
|
||||
def test_both_inputs_and_outputs_disabled(self) -> None:
|
||||
"""
|
||||
Test validation fails when both inputs and outputs are disabled.
|
||||
|
||||
@@ -1270,7 +1280,7 @@ class TestConfigurationEdgeCases:
|
||||
with pytest.raises(ValueError, match="At least one of inputs_config or outputs_config must be enabled"):
|
||||
KeywordsModeration.validate_config("tenant-id", config)
|
||||
|
||||
def test_preset_response_exactly_100_characters(self):
|
||||
def test_preset_response_exactly_100_characters(self) -> None:
|
||||
"""
|
||||
Test that preset response length validation works correctly.
|
||||
|
||||
@@ -1304,7 +1314,7 @@ class TestConfigurationEdgeCases:
|
||||
with pytest.raises(ValueError, match="must be less than 100 characters"):
|
||||
KeywordsModeration.validate_config("tenant-id", config_101)
|
||||
|
||||
def test_empty_preset_response_when_enabled(self):
|
||||
def test_empty_preset_response_when_enabled(self) -> None:
|
||||
"""
|
||||
Test validation fails when preset_response is empty but config is enabled.
|
||||
|
||||
@@ -1332,7 +1342,7 @@ class TestConcurrentModerationScenarios:
|
||||
multiple requests or checking multiple fields simultaneously.
|
||||
"""
|
||||
|
||||
def test_multiple_keywords_in_single_input(self):
|
||||
def test_multiple_keywords_in_single_input(self) -> None:
|
||||
"""
|
||||
Test detection when multiple keywords appear in one input.
|
||||
|
||||
@@ -1351,7 +1361,7 @@ class TestConcurrentModerationScenarios:
|
||||
|
||||
assert result.flagged is True
|
||||
|
||||
def test_keyword_at_start_middle_end_of_text(self):
|
||||
def test_keyword_at_start_middle_end_of_text(self) -> None:
|
||||
"""
|
||||
Test keyword detection at different positions in text.
|
||||
|
||||
@@ -1377,7 +1387,7 @@ class TestConcurrentModerationScenarios:
|
||||
result = moderation.moderation_for_inputs({"text": "this is a flag"}, "")
|
||||
assert result.flagged is True
|
||||
|
||||
def test_case_variations_of_same_keyword(self):
|
||||
def test_case_variations_of_same_keyword(self) -> None:
|
||||
"""
|
||||
Test that different case variations of keywords are all detected.
|
||||
|
||||
|
||||
@@ -386,7 +386,6 @@ project-excludes = [
|
||||
"core/memory/test_token_buffer_memory.py",
|
||||
"core/model_runtime/test_model_provider_factory.py",
|
||||
"core/moderation/api/test_api.py",
|
||||
"core/moderation/test_content_moderation.py",
|
||||
"core/moderation/test_input_moderation.py",
|
||||
"core/moderation/test_output_moderation.py",
|
||||
"core/moderation/test_sensitive_word_filter.py",
|
||||
|
||||
Reference in new issue
Block a user