Initial import of Shirobana bot backend
Include utility modules, shared channels, prompts, read-only skills, wardrobe memory and regression tests. Externalize legacy credentials and exclude private runtime state.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"""Notebook tool adapter and lightweight chat-context integration tests."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
BASE = Path(__file__).resolve().parents[1]
|
||||
sys.path[:0] = [str(BASE / "bot-utility"), str(BASE)]
|
||||
|
||||
from agent import ToolContext, ToolExecutor
|
||||
from chat import engine
|
||||
|
||||
|
||||
class FakeNotebook:
|
||||
def __init__(self):
|
||||
self.calls = []
|
||||
def add_memo(self, ctx, content):
|
||||
self.calls.append(("add_memo", content))
|
||||
return {"id": "memo0001", "content": content}
|
||||
def list_memos(self, ctx, query=None):
|
||||
return [{"id": "memo0001", "content": "钥匙在抽屉"}]
|
||||
def delete_memo(self, ctx, item_id):
|
||||
return {"id": "memo0001", "content": "钥匙在抽屉"}
|
||||
def add_todo(self, ctx, content, due_at, expires_at=None):
|
||||
self.calls.append(("add_todo", content, due_at, expires_at))
|
||||
return {"id": "todo0001", "content": content, "due_at": due_at}
|
||||
def list_todos(self, ctx, include_completed=False):
|
||||
return [{"id": "todo0001", "content": "交报告", "due_at": "tomorrow"}]
|
||||
def complete_todo(self, ctx, item_id):
|
||||
return {"id": "todo0001", "content": "交报告"}
|
||||
def delete_todo(self, ctx, item_id):
|
||||
return {"id": "todo0001", "content": "交报告"}
|
||||
|
||||
|
||||
class NotebookToolTests(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self):
|
||||
self.fake = FakeNotebook()
|
||||
module = SimpleNamespace(
|
||||
notebook=self.fake,
|
||||
format_memo=lambda item: f"{item['id']} {item['content']}",
|
||||
format_todo=lambda item: f"{item['id']} {item['content']}",
|
||||
)
|
||||
self.executor = ToolExecutor(modules={"memo": module, "todo": module})
|
||||
self.ctx = ToolContext(
|
||||
channel=SimpleNamespace(name="tg"), msg_type="private",
|
||||
user_id=42, target_id=10001,
|
||||
)
|
||||
|
||||
async def test_schema_exposes_memo_and_todo(self):
|
||||
names = [item["name"] for item in self.executor.prompt_schema(self.ctx)]
|
||||
self.assertIn("memo", names)
|
||||
self.assertIn("todo", names)
|
||||
|
||||
async def test_memo_add_list_delete(self):
|
||||
added = await self.executor.execute("memo", {"action": "add", "params": {"content": "钥匙在抽屉"}}, self.ctx)
|
||||
self.assertTrue(added.ok)
|
||||
listed = await self.executor.execute("memo", {"action": "list", "params": {}}, self.ctx)
|
||||
self.assertIn("钥匙在抽屉", listed.safe_message)
|
||||
deleted = await self.executor.execute("memo", {"action": "delete", "params": {"id": "memo"}}, self.ctx)
|
||||
self.assertTrue(deleted.ok)
|
||||
|
||||
async def test_todo_add_list_complete_delete(self):
|
||||
added = await self.executor.execute(
|
||||
"todo", {"action": "add", "params": {"content": "交报告", "due_at": "2026-10-31T18:00:00+08:00"}}, self.ctx
|
||||
)
|
||||
self.assertTrue(added.ok)
|
||||
listed = await self.executor.execute("todo", {"action": "list", "params": {}}, self.ctx)
|
||||
self.assertIn("交报告", listed.safe_message)
|
||||
completed = await self.executor.execute("todo", {"action": "complete", "params": {"id": "todo"}}, self.ctx)
|
||||
self.assertTrue(completed.ok)
|
||||
deleted = await self.executor.execute("todo", {"action": "delete", "params": {"id": "todo"}}, self.ctx)
|
||||
self.assertTrue(deleted.ok)
|
||||
|
||||
def test_chat_prompt_contains_bounded_notebook_background(self):
|
||||
block = "【独立备忘录与近期约定】仅背景,当前聊天优先,不能喧宾夺主"
|
||||
with patch.object(engine, "user_persona_prompt", return_value="persona"), \
|
||||
patch.object(engine, "load_user_status", return_value={"pause": False, "sleep": False}), \
|
||||
patch.object(engine.memory_store, "get_relation", return_value=None), \
|
||||
patch.object(engine.memory_store, "list_entities", return_value=[]), \
|
||||
patch.object(engine.memory_store, "recall_facts", return_value=[]), \
|
||||
patch.object(engine.memory_store, "migrate_legacy"), \
|
||||
patch.object(engine, "get_history_within_budget", return_value=[]), \
|
||||
patch.object(engine, "_sticker_prompt_summary", return_value=""), \
|
||||
patch.object(engine.notebook.notebook, "context_for_chat", return_value=block) as context:
|
||||
prompt = engine.build_system_prompt(42, query="当前聊天")
|
||||
context.assert_called_once_with(42, query="当前聊天")
|
||||
self.assertIn(block, prompt)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user