feat: BossAi 老板AI助手初始版本
包含功能: - 法务参谋/经营决策/生活助理/情绪中心四大AI对话模块 - 专家咨询系统(用户选定→专家确认→咨询→回复→评价) - 角色登录(用户/专家/管理员)+ ProtectedRoute 权限控制 - 管理后台(用户管理/专家管理/AI使用统计/专家活跃度) - 专家工作台(待确认服务/待回复咨询/已回复咨询/服务中用户) - 服务关系取消(用户和专家均可取消,需自己确认) - 全页面移动端响应式适配 - 知识库RAG检索 - 对话历史(普通/保密模式) Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
+144
@@ -0,0 +1,144 @@
|
||||
from fastapi.testclient import TestClient
|
||||
import unittest
|
||||
from api import app
|
||||
import api as api_module
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
class ApiTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""每个测试前清理限流桶,避免跨测试影响。"""
|
||||
api_module._rate_buckets.clear()
|
||||
|
||||
def test_health_and_ephemeral_flow(self):
|
||||
self.assertEqual(client.get('/health').status_code, 200)
|
||||
sid = client.post('/sessions', json={'mode': 'ephemeral'}).json()['session_id']
|
||||
r = client.post(f'/sessions/{sid}/ask', json={'question': '员工工伤后能否开除'})
|
||||
self.assertEqual(r.json()['risk']['level'], 'orange')
|
||||
self.assertTrue(client.post(f'/sessions/{sid}/destroy').json()['destroyed'])
|
||||
self.assertEqual(client.post(f'/sessions/{sid}/ask', json={'question': 'test'}).status_code, 410)
|
||||
|
||||
def test_sensitive_file_limit(self):
|
||||
sid = client.post('/sessions', json={'mode': 'confidential'}).json()['session_id']
|
||||
r = client.post(f'/sessions/{sid}/file', files={'file': ('a.txt', b'hello')})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
def test_text_file_and_knowledge_search(self):
|
||||
sid = client.post('/sessions', json={'mode': 'normal'}).json()['session_id']
|
||||
r = client.post(f'/sessions/{sid}/file', files={'file': ('a.txt', '付款违约条款'.encode())})
|
||||
self.assertIn('付款违约', r.json()['text_preview'])
|
||||
|
||||
def test_knowledge_rag_search(self):
|
||||
"""知识库 RAG 检索:中文 2-gram 分词"""
|
||||
results = client.get('/knowledge/search?q=工伤解除').json()['results']
|
||||
self.assertGreater(len(results), 0)
|
||||
self.assertIn('score', results[0])
|
||||
|
||||
def test_knowledge_add_and_list(self):
|
||||
"""知识库新增和列表"""
|
||||
r = client.post('/knowledge', json={
|
||||
'title': '测试法规', 'content': '测试内容', 'doc_type': 'law', 'region': '全国'
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIn('id', r.json())
|
||||
docs = client.get('/knowledge').json()['results']
|
||||
self.assertGreater(len(docs), 0)
|
||||
|
||||
def test_settings_get_and_update(self):
|
||||
"""设置持久化:读取和更新"""
|
||||
s = client.get('/settings').json()['settings']
|
||||
self.assertIn('cloud_call_allowed', s)
|
||||
r = client.put('/settings', json={'settings': {'long_term_memory': 'true'}})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.json()['settings']['long_term_memory'], 'true')
|
||||
|
||||
def test_expert_list_and_select(self):
|
||||
"""专家列表和选定"""
|
||||
# 列出法务专家
|
||||
r = client.get('/experts?category=legal')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertGreater(len(r.json()['results']), 0)
|
||||
expert_id = r.json()['results'][0]['id']
|
||||
# 专家详情
|
||||
detail = client.get(f'/experts/{expert_id}')
|
||||
self.assertEqual(detail.status_code, 200)
|
||||
self.assertIn('ratings', detail.json())
|
||||
|
||||
def test_ask_with_rag_context(self):
|
||||
"""法务场景 ask 接口注入 RAG 上下文"""
|
||||
sid = client.post('/sessions', json={'mode': 'confidential'}).json()['session_id']
|
||||
r = client.post(f'/sessions/{sid}/ask', json={
|
||||
'question': '员工工伤后能否开除', 'scene': 'legal'
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIn('rag_context', r.json())
|
||||
self.assertIn('inference_location', r.json())
|
||||
|
||||
def test_file_parse_docx(self):
|
||||
"""DOCX 文件解析"""
|
||||
import docx
|
||||
import io
|
||||
doc = docx.Document()
|
||||
doc.add_paragraph('测试合同条款')
|
||||
buf = io.BytesIO()
|
||||
doc.save(buf)
|
||||
sid = client.post('/sessions', json={'mode': 'confidential'}).json()['session_id']
|
||||
r = client.post(f'/sessions/{sid}/file', files={'file': ('test.docx', buf.getvalue())})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIn('测试合同', r.json()['text_preview'])
|
||||
self.assertGreater(r.json()['text_length'], 0)
|
||||
|
||||
def test_vector_search(self):
|
||||
"""向量检索:TF-IDF 余弦相似度"""
|
||||
r = client.get('/knowledge/vector-search?q=工伤解除')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertGreater(len(r.json()['results']), 0)
|
||||
self.assertIn('score', r.json()['results'][0])
|
||||
|
||||
def test_destroy_status(self):
|
||||
"""销毁状态查询"""
|
||||
sid = client.post('/sessions', json={'mode': 'ephemeral'}).json()['session_id']
|
||||
client.post(f'/sessions/{sid}/destroy')
|
||||
r = client.get(f'/sessions/{sid}/destroy-status')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertTrue(r.json()['destroyed'])
|
||||
self.assertGreater(len(r.json()['destroy_log']), 0)
|
||||
|
||||
def test_action_summary_save(self):
|
||||
"""脱敏行动摘要保存"""
|
||||
r = client.post('/summaries', json={
|
||||
'session_id': 'test',
|
||||
'summary': {'risk_level': 'orange', 'conclusion': '测试结论', 'actions': ['行动1']}
|
||||
})
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIn('id', r.json())
|
||||
self.assertEqual(r.json()['summary']['risk_level'], 'orange')
|
||||
|
||||
def test_metrics(self):
|
||||
"""可观测性指标"""
|
||||
r = client.get('/metrics')
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertIn('request_count', r.json())
|
||||
self.assertIn('model_calls', r.json())
|
||||
|
||||
def test_rate_limit(self):
|
||||
"""限流:设置低阈值后快速请求应触发 429"""
|
||||
import importlib
|
||||
import api as api_module
|
||||
original = api_module.RATE_LIMIT
|
||||
api_module.RATE_LIMIT = 2 # 临时设为 2/秒
|
||||
try:
|
||||
codes = []
|
||||
for i in range(10):
|
||||
r = client.get('/settings')
|
||||
codes.append(r.status_code)
|
||||
self.assertIn(429, codes)
|
||||
finally:
|
||||
api_module.RATE_LIMIT = original
|
||||
# 清理限流桶,避免影响后续测试
|
||||
api_module._rate_buckets.clear()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user