trace/services/class-monitor/tests/test_core.py

157 lines
6.9 KiB
Python

import asyncio
import json
import tempfile
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo
from class_monitor.app import daily_summary_period, immediate_notification, sender_is_excluded
from class_monitor.llm import Analyzer, _json, is_pure_acknowledgement
from class_monitor.storage import Storage
class FakeAnalyzer:
def __init__(self, analyzer):
self.analyzer = analyzer
async def call(self, _system, _user):
return {"importance": 0, "category": "其他", "need_action": False, "deadline": None, "summary": "普通消息", "reason": "测试"}
def test_json():
assert _json('```json\n{"importance":2}\n```')["importance"] == 2
def test_pure_acknowledgement_filter_is_conservative():
assert is_pure_acknowledgement("收到")
assert is_pure_acknowledgement("收到,谢谢老师!")
assert is_pure_acknowledgement("[表情][表情]")
assert is_pure_acknowledgement("👍")
assert not is_pure_acknowledgement("收到,请问几点?")
assert not is_pure_acknowledgement("收到,孩子明天请假")
assert not is_pure_acknowledgement("我没有收到通知")
assert not is_pure_acknowledgement("收到", '[{"type":"image"}]')
def test_sender_exclusion_is_scoped_to_group_and_sender():
cfg = {"excluded_sender_ids_by_group": {"kindergarten": ["robot"]}}
assert sender_is_excluded(cfg, {"group_id": "kindergarten", "user_id": "robot"})
assert not sender_is_excluded(cfg, {"group_id": "primary", "user_id": "robot"})
assert not sender_is_excluded(cfg, {"group_id": "kindergarten", "user_id": "parent"})
def test_immediate_notification_uses_original_text_and_optional_items():
row = {
"sender_name": "王老师",
"text": "明天请带水彩笔和剪刀。",
"summary": "老师通知准备绘画工具",
"media_json": "[]",
"result_json": json.dumps({"items_to_prepare": ["水彩笔", "剪刀"]}),
}
title, text = immediate_notification("小学群", row)
assert title == ""
assert text == "【小学群】 发送人:王老师\n消息:明天请带水彩笔和剪刀。\n准备物品:水彩笔、剪刀"
assert "班级消息" not in title
def test_immediate_notification_uses_summary_for_non_text_message():
row = {
"sender_name": "老师",
"text": "[图片]",
"summary": "老师发送了一张课程安排图片",
"media_json": '[{"type":"image"}]',
"result_json": "{}",
}
title, text = immediate_notification("幼儿园群", row)
assert title == ""
assert text == "【幼儿园群】 发送人:老师\n摘要:老师发送了一张课程安排图片"
assert "消息:" not in text
assert "准备物品" not in text
def test_daily_summary_period_regular_day():
now = datetime(2026, 9, 5, 19, 0, tzinfo=ZoneInfo("Asia/Singapore"))
start, period, title_kind, empty_overview = daily_summary_period(now)
assert start == datetime(2026, 9, 5, 0, 0, tzinfo=now.tzinfo)
assert period == "2026-09-05"
assert title_kind == "日报"
assert empty_overview == "今日群内无消息"
def test_daily_summary_period_sunday_includes_friday_to_sunday():
now = datetime(2026, 9, 6, 19, 0, tzinfo=ZoneInfo("Asia/Singapore"))
start, period, title_kind, empty_overview = daily_summary_period(now)
assert start == datetime(2026, 9, 4, 0, 0, tzinfo=now.tzinfo)
assert period == "2026-09-04 至 2026-09-06"
assert title_kind == "周末总结"
assert empty_overview == "周五至周日群内无消息"
def test_storage():
async def run():
with tempfile.TemporaryDirectory() as d:
s = Storage(str(Path(d) / "x.db"))
await s.init()
item = {"group_id": "1", "message_id": "2", "user_id": "3", "sender": {"nickname": "老师"}, "time": 1, "raw_message": "明天带材料"}
pk, inserted = await s.save_message(item, "明天带材料", [])
assert inserted and pk
_, inserted2 = await s.save_message(item, "明天带材料", [])
assert not inserted2
claimed = await s.claim_pending()
assert claimed["text"] == "明天带材料"
asyncio.run(run())
def test_receipts_are_excluded_from_context_and_daily_summary():
async def run():
with tempfile.TemporaryDirectory() as d:
s = Storage(str(Path(d) / "x.db"))
await s.init()
receipt = {"group_id": "1", "message_id": "1", "user_id": "p1", "sender": {"nickname": "家长甲"}, "time": 1}
useful = {"group_id": "1", "message_id": "2", "user_id": "t1", "sender": {"nickname": "老师"}, "time": 2}
current = {"group_id": "1", "message_id": "3", "user_id": "p2", "sender": {"nickname": "家长乙"}, "time": 3}
receipt_pk, _ = await s.save_message(receipt, "收到", [])
useful_pk, _ = await s.save_message(useful, "明天带材料", [])
current_pk, _ = await s.save_message(current, "请问带什么?", [])
await s.save_analysis(receipt_pk, {"importance": 0, "category": "回执", "need_action": False, "deadline": None, "summary": "纯确认回复", "reason": "测试"})
await s.save_analysis(useful_pk, {"importance": 2, "category": "材料", "need_action": True, "deadline": None, "summary": "带材料", "reason": "测试"})
context = await s.context({"id": current_pk, "group_id": "1"}, 10)
assert [row["text"] for row in context] == ["明天带材料"]
rows = await s.messages_for_day("1970-01-01T00:00:00+00:00", "1970-01-02T00:00:00+00:00", "1")
assert [row["text"] for row in rows] == ["明天带材料", "请问带什么?"]
asyncio.run(run())
def test_parent_question_is_exactly_importance_one():
async def run():
analyzer = Analyzer.__new__(Analyzer)
analyzer.cfg = {"teacher_user_ids": [], "teacher_names": []}
async def high_importance(_system, _user):
return {"importance": 3, "category": "其他", "need_action": False, "deadline": None, "summary": "问题", "reason": "测试"}
analyzer.call = high_importance
row = {"sender_id": "parent", "sender_name": "家长", "text": "请问明天几点到校?", "raw_json": json.dumps({"sender": {"role": "member"}})}
result = await analyzer.analyze(row, [])
assert result["importance"] == 1
asyncio.run(run())
def test_group_admin_is_weighted():
async def run():
analyzer = Analyzer.__new__(Analyzer)
analyzer.cfg = {"teacher_user_ids": [], "teacher_names": []}
analyzer.call = FakeAnalyzer(analyzer).call
row = {
"sender_id": "3",
"sender_name": "群管理员",
"text": "大家看一下",
"raw_json": json.dumps({"sender": {"role": "admin"}}),
}
result = await analyzer.analyze(row, [])
assert result["importance"] == 1
asyncio.run(run())