57 lines
2.6 KiB
Python
57 lines
2.6 KiB
Python
"""Unit coverage for SPEC-001 test-class requirements (ENV-10..12)."""
|
|
|
|
import json
|
|
import time
|
|
import unittest
|
|
|
|
from fjerkroa_bot.ai_responder import AIMessage, AIResponder
|
|
|
|
from .test_bdd_envelope import FakeModelResponder
|
|
|
|
|
|
def entry(channel: str, text: str = "x"):
|
|
return {"role": "user", "content": json.dumps({"message": text, "channel": channel})}
|
|
|
|
|
|
class TestSystemPromptTemplate(unittest.TestCase):
|
|
def test_template_substitution(self):
|
|
"""ENV-10: {date}/{memory} substituted; missing news file leaves {news} literal."""
|
|
config = {"system": "Date {date} memory {memory} news {news}", "history-limit": 5, "news": "/nonexistent/news.txt"}
|
|
responder = AIResponder(config, "chat")
|
|
responder.memory = "MEMSTR"
|
|
messages = responder.message(AIMessage("alice", "hei"))
|
|
system = messages[0]["content"]
|
|
self.assertIn(time.strftime("%Y-%m-%d"), system)
|
|
self.assertIn("MEMSTR", system)
|
|
self.assertIn("{news}", system) # left literal — file does not exist
|
|
|
|
|
|
class TestHistoryShrink(unittest.TestCase):
|
|
def test_shrink_prefers_busy_channel(self):
|
|
"""ENV-11: entry from the channel exceeding history-per-channel is removed first."""
|
|
config = {"system": "s", "history-limit": 4}
|
|
responder = AIResponder(config, "chat")
|
|
responder.history = [entry("a", "a0"), entry("a", "a1"), entry("a", "a2"), entry("a", "a3"), entry("b", "b0")]
|
|
responder.shrink_history_by_one()
|
|
self.assertEqual(len(responder.history), 4)
|
|
self.assertNotIn("a0", responder.history[0]["content"]) # oldest busy-channel entry gone
|
|
|
|
def test_shrink_falls_back_to_oldest(self):
|
|
"""ENV-11: when no channel exceeds the cap, the oldest entry overall is removed."""
|
|
config = {"system": "s", "history-limit": 4}
|
|
responder = AIResponder(config, "chat")
|
|
responder.history = [entry("a", "a0"), entry("b", "b0"), entry("c", "c0")]
|
|
responder.shrink_history_by_one()
|
|
self.assertEqual(len(responder.history), 2)
|
|
self.assertNotIn("a0", responder.history[0]["content"])
|
|
|
|
|
|
class TestRetriesExhausted(unittest.IsolatedAsyncioTestCase):
|
|
async def test_send_raises_after_three_failures(self):
|
|
"""ENV-12: three model failures -> RuntimeError (revision pending in FDB-004/D1)."""
|
|
responder = FakeModelResponder({"system": "s", "history-limit": 5}, "chat")
|
|
# scripted list empty -> chat() returns None every attempt
|
|
with self.assertRaises(RuntimeError):
|
|
await responder.send(AIMessage("alice", "hei", "chat"))
|
|
self.assertEqual(responder.chat_calls, 3)
|