65 lines
2.9 KiB
Python
65 lines
2.9 KiB
Python
"""Unit coverage for SPEC-003 injection gates (SAF-01..03)."""
|
|
|
|
import tempfile
|
|
import unittest
|
|
|
|
from fjerkroa_bot.ai_responder import AIMessage, AIResponder, sanitize_external_text
|
|
|
|
from .test_main import TestBotBase
|
|
|
|
|
|
class TestChannelRoutingGate(TestBotBase):
|
|
async def test_default_allowed_set_from_config_names(self):
|
|
"""SAF-01: default allowed set = channels named in config; everything else refused."""
|
|
self.bot.config = dict(self.bot.config)
|
|
self.bot.config.update(
|
|
{"chat-channel": "general", "staff-channel": "staff", "welcome-channel": "welcome", "additional-responders": ["games"]}
|
|
)
|
|
self.assertTrue(self.bot.routing_allowed("general"))
|
|
self.assertTrue(self.bot.routing_allowed("staff"))
|
|
self.assertTrue(self.bot.routing_allowed("games"))
|
|
self.assertFalse(self.bot.routing_allowed("random-channel"))
|
|
self.assertFalse(self.bot.routing_allowed(None))
|
|
|
|
async def test_explicit_allowlist_wins(self):
|
|
"""SAF-01: configured allowed-channels replaces the default set."""
|
|
self.bot.config = dict(self.bot.config)
|
|
self.bot.config.update({"chat-channel": "general", "allowed-channels": ["announcements"]})
|
|
self.assertTrue(self.bot.routing_allowed("announcements"))
|
|
self.assertFalse(self.bot.routing_allowed("general"))
|
|
|
|
|
|
class TestAllowedMentions(TestBotBase):
|
|
async def test_outbound_pings_disabled(self):
|
|
"""SAF-02: the bot is constructed with allowed_mentions = none."""
|
|
mentions = self.bot.allowed_mentions
|
|
self.assertIsNotNone(mentions)
|
|
self.assertFalse(mentions.everyone)
|
|
self.assertFalse(mentions.users)
|
|
self.assertFalse(mentions.roles)
|
|
|
|
|
|
class TestSanitizeExternalText(unittest.TestCase):
|
|
def test_sanitizer_strips_and_caps(self):
|
|
"""SAF-03: control chars stripped, @everyone/@here neutralized, length capped."""
|
|
dirty = "hei\x00\x1b[31m @everyone @here " + "x" * 5000
|
|
clean = sanitize_external_text(dirty, max_len=4000)
|
|
self.assertNotIn("\x00", clean)
|
|
self.assertNotIn("\x1b", clean)
|
|
self.assertNotIn("@everyone", clean)
|
|
self.assertNotIn("@here", clean)
|
|
self.assertLessEqual(len(clean), 4000)
|
|
self.assertIn("hei", clean)
|
|
|
|
def test_news_content_sanitized_into_prompt(self):
|
|
"""SAF-03: news file content passes the sanitizer before prompt injection."""
|
|
with tempfile.NamedTemporaryFile("w", suffix=".txt", delete=False) as fd:
|
|
fd.write("Breaking: @everyone \x00 click here")
|
|
news_path = fd.name
|
|
config = {"system": "News: {news}", "history-limit": 5, "news": news_path}
|
|
responder = AIResponder(config, "chat")
|
|
system = responder.message(AIMessage("alice", "hei"))[0]["content"]
|
|
self.assertNotIn("@everyone", system)
|
|
self.assertNotIn("\x00", system)
|
|
self.assertIn("Breaking:", system)
|