mirror of
https://github.com/snowy-yang/xiaonuan.git
synced 2026-10-20 23:54:48 +00:00
- tests/conftest.py: 内存版 FakeDB 复刻 db.py 全部签名,绕过 lifespan 的测试应用 - test_chat_flow.py: WebSocket 全链路(回声/路由信号落库/档案/慢信/连续天数) - test_delivery.py: 事件/每日任务/关怀投递(在线与离线)、天气工具城市链、system prompt 注入 - test_router.py: 小模型路由清洗(去重/丢过去事件/非法作息/500优雅降级/无模型空判定) - test_tool_loop.py: 原生工具调用循环(喂结果/超轮次强制收尾/坏参数) - test_pure.py: 纯逻辑(连续天数/哈希槽位/时间间隔/模板/向量) - test_db_pg.py: PG 集成测试,无 PG_DSN 自动跳过,配好即真实跑库 - 旧散装脚本移除;tools_live/ws_client/tui_smoke 保留为手动脚本
99 lines
2.8 KiB
Python
99 lines
2.8 KiB
Python
"""纯逻辑测试:连续天数 / 彩蛋时刻 / 时间间隔 / 模板 / 向量"""
|
||
from datetime import datetime
|
||
|
||
from app.connector import ASSISTANT_NAME, hash_embed
|
||
from app.db import cosine
|
||
from app.main import (_daily_slot, absence_text, compute_streak, digest_echo,
|
||
human_gap)
|
||
|
||
NOW = datetime(2026, 9, 21, 19, 30) # 周一晚上
|
||
|
||
|
||
# ---- 连续陪伴天数 ----
|
||
|
||
def test_streak_yesterday_increments():
|
||
assert compute_streak("2026-09-20", 5, NOW) == 6
|
||
|
||
|
||
def test_streak_gap_resets():
|
||
assert compute_streak("2026-09-19", 5, NOW) == 1
|
||
|
||
|
||
def test_streak_first_time():
|
||
assert compute_streak("", 0, NOW) == 1
|
||
|
||
|
||
def test_streak_same_day_unchanged():
|
||
assert compute_streak("2026-09-21", 7, NOW) == 7
|
||
|
||
|
||
# ---- 每日彩蛋时刻(哈希槽位)----
|
||
|
||
def test_daily_slot_deterministic():
|
||
a = _daily_slot("sess-a", "replay", 19 * 60, 22 * 60, "2026-09-21")
|
||
b = _daily_slot("sess-a", "replay", 19 * 60, 22 * 60, "2026-09-21")
|
||
assert a == b
|
||
|
||
|
||
def test_daily_slot_within_window():
|
||
for key in ("replay", "share", "diary", "digest"):
|
||
slot = _daily_slot("sess-a", key, 10 * 60, 21 * 60, "2026-09-21")
|
||
assert 10 * 60 <= slot < 21 * 60
|
||
|
||
|
||
def test_daily_slot_differs_per_session():
|
||
a = _daily_slot("sess-a", "replay", 19 * 60, 22 * 60, "2026-09-21")
|
||
b = _daily_slot("sess-b", "replay", 19 * 60, 22 * 60, "2026-09-21")
|
||
# 不保证必然不同,但同窗口内两者都合法;这里验证可分布
|
||
assert 19 * 60 <= a < 22 * 60 and 19 * 60 <= b < 22 * 60
|
||
|
||
|
||
# ---- 时间间隔说法 ----
|
||
|
||
def test_human_gap_minutes():
|
||
assert human_gap(59) == "1 分钟"
|
||
assert human_gap(5 * 60) == "5 分钟"
|
||
|
||
|
||
def test_human_gap_hours():
|
||
assert human_gap(2 * 3600 + 47 * 60) == "2 小时 47 分钟"
|
||
assert human_gap(2 * 3600) == "2 小时"
|
||
|
||
|
||
def test_human_gap_days():
|
||
assert human_gap(3 * 86400) == "3 天"
|
||
|
||
|
||
# ---- 缺勤迎接(回声模板)----
|
||
|
||
async def test_absence_text_mentions_the_gap():
|
||
line = await absence_text("s1", 3700)
|
||
assert "小时" in line
|
||
assert ("安静" in line) or ("心跳" in line) or ("开了灯" in line)
|
||
|
||
|
||
# ---- 学徒回顾模板 ----
|
||
|
||
def test_digest_echo_signature_and_numbers():
|
||
d = digest_echo({"msgs": 12, "mem": 3}, 45)
|
||
assert "12" in d and "3" in d and "45" in d and ASSISTANT_NAME in d
|
||
|
||
|
||
# ---- 向量(回声模式哈希 embedding)----
|
||
|
||
def test_hash_embed_self_similarity():
|
||
v = hash_embed("我喜欢打篮球")
|
||
assert len(v) == 256
|
||
assert abs(cosine(v, v) - 1.0) < 1e-9
|
||
|
||
|
||
def test_hash_embed_related_beats_unrelated():
|
||
base = hash_embed("我喜欢打篮球")
|
||
near = hash_embed("打篮球的时候我很开心")
|
||
far = hash_embed("明天北京的天气怎么样")
|
||
assert cosine(base, near) > cosine(base, far)
|
||
|
||
|
||
def test_cosine_handles_empty():
|
||
assert cosine(hash_embed("x"), []) == 0.0
|