Files
BossAi/test_api.py
T
selfreleaseandDevin 5c21e8fcd0 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>
2026-08-29 23:57:26 +08:00

145 lines
6.0 KiB
Python

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()