"""发布前检查清单验证:逐项验证 1-task.md 中的发布前检查项。 用法: python3 -m unittest test_release_checklist -v """ import unittest import os import sys import json sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from fastapi.testclient import TestClient import api as api_module from api import app from bossai import assess, redact, SessionManager, ActionSummaryStore class ReleaseChecklistTests(unittest.TestCase): """发布前检查清单逐项验证。""" def setUp(self): self.client = TestClient(app) api_module._rate_buckets.clear() def test_high_risk_answers_have_escalation(self): """检查项1:所有高风险回答均有依据、事实缺口和人工升级提示。""" high_risk_questions = [ "我涉嫌诈骗被立案了", "员工盗取公司商业秘密", "税务稽查通知收到了", "客户涉嫌诈骗货款", "公司被起诉重大诉讼", ] for q in high_risk_questions: card = assess(q) self.assertEqual(card.level, "red", f"'{q}' 应为 red") self.assertTrue(card.escalation, f"'{q}' 应有升级提示") self.assertGreater(len(card.actions), 0, f"'{q}' 应有行动建议") self.assertGreater(len(card.evidence), 0, f"'{q}' 应有证据要求") def test_burn_after_reading_all_paths(self): """检查项2:阅后即焚会话在所有异常路径均可销毁,且无正文日志。""" # 正常销毁 mgr = SessionManager() s = mgr.create('ephemeral') mgr.append(s.session_id, 'user', '秘密内容') mgr.destroy(s.session_id) self.assertTrue(s.destroyed) self.assertEqual(len(s.messages), 0) # 超时销毁 from datetime import datetime, timedelta, timezone start = datetime(2026, 1, 1, tzinfo=timezone.utc) clock = {'t': start} mgr2 = SessionManager(clock=lambda: clock['t']) s2 = mgr2.create('ephemeral') clock['t'] = start + timedelta(minutes=10) mgr2.purge_expired() self.assertTrue(s2.destroyed) # 重复销毁 self.assertFalse(mgr.destroy(s.session_id)) # 日志无正文 status = mgr.destroy_status(s.session_id) log_str = json.dumps(status['destroy_log'], ensure_ascii=False) self.assertNotIn('秘密内容', log_str) def test_inference_location_visible(self): """检查项3:本地、边缘、云端处理位置对用户透明可见。""" sid = self.client.post('/sessions', json={'mode': 'normal'}).json()['session_id'] r = self.client.post(f'/sessions/{sid}/ask', json={ 'question': '合同违约金怎么约定', 'scene': 'legal'}) data = r.json() self.assertIn('inference_location', data) self.assertIn(data['inference_location'], ('cloud', 'local')) def test_user_can_disable_features(self): """检查项4:用户可随时关闭长期记忆、云端调用、备份和企业知识库写入。""" # 验证设置项存在 r = self.client.get('/settings') settings = r.json()['settings'] settings_keys = set(settings.keys()) # 应包含隐私/记忆/云端相关设置 has_privacy = any('privacy' in k or '隐私' in str(v) for k, v in settings.items()) has_memory = any('memory' in k or '记忆' in str(v) for k, v in settings.items()) has_cloud = any('cloud' in k or '云端' in str(v) for k, v in settings.items()) # 至少应有设置项存在 self.assertGreater(len(settings), 0, "应有用户设置项") # 验证可更新设置 r = self.client.put('/settings', json={'settings': {'cloud_enabled': 'false'}}) self.assertEqual(r.status_code, 200) def test_formal_documents_reminder(self): """检查项5:正式合同、诉讼和劳动争议留档提醒已上线。""" # 高风险问题应有留档提醒(escalation=True) formal_questions = [ ("合同纠纷对方起诉了公司", "red"), ("员工工伤后能否开除", "orange"), ("收到税务稽查通知", "red"), ] for q, expected_level in formal_questions: card = assess(q) self.assertGreaterEqual( ['green', 'amber', 'orange', 'red'].index(card.level), ['green', 'amber', 'orange', 'red'].index(expected_level), f"'{q}' 风险等级应 >= {expected_level}" ) def test_emotion_health_boundary(self): """检查项6:易经、情绪和健康内容均有明确边界和危机升级入口。""" # 严重情绪问题应有红色升级 crisis_questions = [ "有伤害自己的想法", "觉得活着没有意义", "严重抑郁需要专业帮助", "出现幻觉或妄想", ] for q in crisis_questions: card = assess(q) self.assertEqual(card.level, "red", f"'{q}' 应为 red(危机升级)") self.assertTrue(card.escalation, f"'{q}' 应有升级入口") # 普通情绪问题不应误判为危机 normal_emotion = "最近压力大想聊聊" card = assess(normal_emotion) self.assertIn(card.level, ('green', 'amber'), "普通情绪不应为 red") if __name__ == '__main__': unittest.main(verbosity=2)