fix defects D1-D12 batch 1, structured envelope, saf gates + ops kill-switches
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user