107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
from __future__ import annotations
|
|||
|
|
|
||
|
|
from django.core.cache import cache
|
||
|
|
from django.core.exceptions import ValidationError
|
||
|
|
from django.test import TestCase, override_settings
|
||
|
|
|
||
|
|
from .models import SensitiveWord
|
||
|
|
from .normalization import normalize_text
|
||
|
|
from .providers.keyword import reset_keyword_matcher_cache
|
||
|
|
from .services import moderate_prompt
|
||
|
|
from .versioning import get_sensitive_words_version
|
||
|
|
|
||
|
|
|
||
|
|
@override_settings(MODERATION_CACHE_VERSION_KEY="test:moderation:sensitive_words:version")
|
||
|
|
class SensitiveWordNormalizationTests(TestCase):
|
||
|
|
def setUp(self):
|
||
|
|
cache.clear()
|
||
|
|
reset_keyword_matcher_cache()
|
||
|
|
|
||
|
|
def tearDown(self):
|
||
|
|
reset_keyword_matcher_cache()
|
||
|
|
cache.clear()
|
||
|
|
|
||
|
|
def test_normalize_text_removes_separators_zero_width_and_case(self):
|
||
|
|
self.assertEqual(normalize_text("Bad-\u200b Word"), "badword")
|
||
|
|
self.assertEqual(normalize_text("敏 感-词"), normalize_text("敏感词"))
|
||
|
|
|
||
|
|
def test_sensitive_word_saves_normalized_word_and_rejects_empty_normalized_word(self):
|
||
|
|
word = SensitiveWord.objects.create(word=" 敏 感-词 ", category="")
|
||
|
|
|
||
|
|
self.assertEqual(word.category, "custom")
|
||
|
|
self.assertEqual(word.normalized_word, "敏感词")
|
||
|
|
|
||
|
|
with self.assertRaises(ValidationError):
|
||
|
|
SensitiveWord.objects.create(word=" - \u200b ")
|
||
|
|
|
||
|
|
def test_word_save_bumps_shared_cache_version(self):
|
||
|
|
before = get_sensitive_words_version()
|
||
|
|
|
||
|
|
SensitiveWord.objects.create(word="敏感词")
|
||
|
|
|
||
|
|
self.assertNotEqual(get_sensitive_words_version(), before)
|
||
|
|
|
||
|
|
|
||
|
|
@override_settings(
|
||
|
|
MODERATION_ENABLED=True,
|
||
|
|
MODERATION_PROVIDER="keyword",
|
||
|
|
MODERATION_CACHE_VERSION_KEY="test:moderation:sensitive_words:version",
|
||
|
|
)
|
||
|
|
class KeywordModerationTests(TestCase):
|
||
|
|
def setUp(self):
|
||
|
|
cache.clear()
|
||
|
|
reset_keyword_matcher_cache()
|
||
|
|
|
||
|
|
def tearDown(self):
|
||
|
|
reset_keyword_matcher_cache()
|
||
|
|
cache.clear()
|
||
|
|
|
||
|
|
def test_prompt_blocks_normalized_keyword_without_storing_prompt(self):
|
||
|
|
word = SensitiveWord.objects.create(word="敏感词", category="policy")
|
||
|
|
prompt = "请处理敏-感\u200b 词内容"
|
||
|
|
|
||
|
|
with self.assertLogs("apps.moderation.services", level="INFO") as logs:
|
||
|
|
outcome = moderate_prompt(prompt=prompt)
|
||
|
|
|
||
|
|
self.assertTrue(outcome.blocked)
|
||
|
|
self.assertEqual(outcome.labels, ("policy",))
|
||
|
|
self.assertEqual(outcome.keywords, ("敏感词",))
|
||
|
|
self.assertEqual(outcome.matched_word_ids, (word.id,))
|
||
|
|
self.assertNotIn(prompt, "\n".join(logs.output))
|
||
|
|
|
||
|
|
def test_non_matching_prompt_passes(self):
|
||
|
|
SensitiveWord.objects.create(word="敏感词")
|
||
|
|
|
||
|
|
outcome = moderate_prompt(prompt="正常业务标题")
|
||
|
|
|
||
|
|
self.assertFalse(outcome.blocked)
|
||
|
|
|
||
|
|
def test_matcher_rebuilds_after_shared_version_changes(self):
|
||
|
|
self.assertFalse(moderate_prompt(prompt="新增词").blocked)
|
||
|
|
|
||
|
|
SensitiveWord.objects.create(word="新增词")
|
||
|
|
|
||
|
|
self.assertTrue(moderate_prompt(prompt="这个提示包含新增词").blocked)
|
||
|
|
|
||
|
|
|
||
|
|
@override_settings(
|
||
|
|
MODERATION_ENABLED=False,
|
||
|
|
MODERATION_PROVIDER="keyword",
|
||
|
|
MODERATION_CACHE_VERSION_KEY="test:moderation:sensitive_words:version",
|
||
|
|
)
|
||
|
|
class DisabledModerationTests(TestCase):
|
||
|
|
def setUp(self):
|
||
|
|
cache.clear()
|
||
|
|
reset_keyword_matcher_cache()
|
||
|
|
|
||
|
|
def tearDown(self):
|
||
|
|
reset_keyword_matcher_cache()
|
||
|
|
cache.clear()
|
||
|
|
|
||
|
|
def test_disabled_moderation_is_noop(self):
|
||
|
|
SensitiveWord.objects.create(word="敏感词")
|
||
|
|
|
||
|
|
outcome = moderate_prompt(prompt="敏感词")
|
||
|
|
|
||
|
|
self.assertFalse(outcome.blocked)
|