4a93bd1c44
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
242 lines
9.5 KiB
Python
242 lines
9.5 KiB
Python
import unittest
|
|
|
|
from fastapi import HTTPException
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.api.knowledge_bases import get_audit_log_service, get_knowledge_base_service, require_current_user
|
|
from app.main import app
|
|
|
|
|
|
class StubAuditLogService:
|
|
def __init__(self):
|
|
self.events = []
|
|
|
|
def record_event(self, **kwargs):
|
|
self.events.append(kwargs)
|
|
return kwargs
|
|
|
|
|
|
class StubKnowledgeBaseService:
|
|
def list_knowledge_bases(self, current_user, page, page_size):
|
|
if current_user["role"] != "admin":
|
|
raise HTTPException(status_code=403, detail="forbidden")
|
|
return {
|
|
"items": [
|
|
{
|
|
"id": "kb-123",
|
|
"name": "劳动合同知识库",
|
|
"description": "劳动用工条款",
|
|
"status": "active",
|
|
"document_count": 1,
|
|
"created_at": "2026-04-16T00:00:00+00:00",
|
|
"updated_at": "2026-04-16T00:00:00+00:00",
|
|
}
|
|
],
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"total": 1,
|
|
}
|
|
|
|
def create_knowledge_base(self, current_user, name, description):
|
|
if current_user["role"] != "admin":
|
|
raise HTTPException(status_code=403, detail="forbidden")
|
|
return {
|
|
"id": "kb-123",
|
|
"name": name,
|
|
"description": description,
|
|
"status": "active",
|
|
"document_count": 0,
|
|
"created_at": "2026-04-16T00:00:00+00:00",
|
|
"updated_at": "2026-04-16T00:00:00+00:00",
|
|
}
|
|
|
|
def list_documents(self, current_user, knowledge_base_id, page, page_size):
|
|
if current_user["role"] != "admin":
|
|
raise HTTPException(status_code=403, detail="forbidden")
|
|
return {
|
|
"items": [
|
|
{
|
|
"id": "kbdoc-123",
|
|
"knowledge_base_id": knowledge_base_id,
|
|
"file_name": "rules.txt",
|
|
"mime_type": "text/plain",
|
|
"file_size": 12,
|
|
"status": "indexed",
|
|
"content_excerpt": "合同解除规则",
|
|
"extracted_text_length": 6,
|
|
"preview_truncated": False,
|
|
"created_at": "2026-04-16T00:00:00+00:00",
|
|
"current_stage": "indexed",
|
|
"current_stage_label": "已完成索引",
|
|
"status_detail": "文本提取与索引已完成,可直接用于检索和问答。",
|
|
"parse_status": "completed",
|
|
"index_status": "completed",
|
|
"failure_reason": None,
|
|
"next_action": "可直接进入问答,或继续补充更多制度与条款文档。",
|
|
},
|
|
{
|
|
"id": "kbdoc-456",
|
|
"knowledge_base_id": knowledge_base_id,
|
|
"file_name": "draft.docx",
|
|
"mime_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
"file_size": 18,
|
|
"status": "processing",
|
|
"content_excerpt": None,
|
|
"extracted_text_length": 0,
|
|
"preview_truncated": False,
|
|
"created_at": "2026-04-16T00:10:00+00:00",
|
|
"current_stage": "parse",
|
|
"current_stage_label": "文本提取中",
|
|
"status_detail": "系统正在提取文本并写入检索索引。",
|
|
"parse_status": "processing",
|
|
"index_status": "blocked",
|
|
"failure_reason": "文本提取尚未完成,索引阶段暂时阻塞。",
|
|
"next_action": "请等待文本提取完成;提取成功后系统会继续进入索引阶段。",
|
|
}
|
|
],
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"total": 2,
|
|
}
|
|
|
|
def upload_document(self, current_user, knowledge_base_id, file_name, mime_type, content):
|
|
if current_user["role"] != "admin":
|
|
raise HTTPException(status_code=403, detail="forbidden")
|
|
return {
|
|
"id": "kbdoc-123",
|
|
"knowledge_base_id": knowledge_base_id,
|
|
"file_name": file_name,
|
|
"mime_type": mime_type,
|
|
"file_size": len(content),
|
|
"status": "indexed",
|
|
"content_excerpt": content.decode("utf-8"),
|
|
"extracted_text_length": len(content.decode("utf-8")),
|
|
"preview_truncated": False,
|
|
"created_at": "2026-04-16T00:00:00+00:00",
|
|
"current_stage": "indexed",
|
|
"current_stage_label": "已完成索引",
|
|
"status_detail": "文本提取与索引已完成,可直接用于检索和问答。",
|
|
"parse_status": "completed",
|
|
"index_status": "completed",
|
|
"failure_reason": None,
|
|
"next_action": "可直接进入问答,或继续补充更多制度与条款文档。",
|
|
"error_message": None,
|
|
}
|
|
|
|
|
|
class KnowledgeBasesApiTestCase(unittest.TestCase):
|
|
def setUp(self):
|
|
self.audit_log_service = StubAuditLogService()
|
|
app.dependency_overrides[get_knowledge_base_service] = lambda: StubKnowledgeBaseService()
|
|
app.dependency_overrides[get_audit_log_service] = lambda: self.audit_log_service
|
|
self.client = TestClient(app)
|
|
|
|
def tearDown(self):
|
|
app.dependency_overrides.clear()
|
|
|
|
def test_admin_can_list_knowledge_bases(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {
|
|
"id": "user-admin",
|
|
"tenant_id": "tenant-demo-001",
|
|
"role": "admin",
|
|
}
|
|
|
|
response = self.client.get("/api/v1/knowledge-bases")
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.json()["data"]["items"][0]["name"], "劳动合同知识库")
|
|
|
|
def test_admin_can_list_documents_with_blocked_index_stage(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {
|
|
"id": "user-admin",
|
|
"tenant_id": "tenant-demo-001",
|
|
"role": "admin",
|
|
}
|
|
|
|
response = self.client.get("/api/v1/knowledge-bases/kb-123/documents")
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
items = response.json()["data"]["items"]
|
|
self.assertEqual(len(items), 2)
|
|
blocked_item = items[1]
|
|
self.assertEqual(blocked_item["current_stage"], "parse")
|
|
self.assertEqual(blocked_item["index_status"], "blocked")
|
|
self.assertEqual(blocked_item["failure_reason"], "文本提取尚未完成,索引阶段暂时阻塞。")
|
|
self.assertIn("继续进入索引阶段", blocked_item["next_action"])
|
|
|
|
def test_admin_can_create_knowledge_base(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {
|
|
"id": "user-admin",
|
|
"tenant_id": "tenant-demo-001",
|
|
"role": "admin",
|
|
}
|
|
|
|
response = self.client.post(
|
|
"/api/v1/knowledge-bases",
|
|
data={"name": "采购合同知识库", "description": "采购模板与制度"},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
body = response.json()
|
|
self.assertEqual(body["data"]["name"], "采购合同知识库")
|
|
self.assertEqual(self.audit_log_service.events[0]["action"], "create_knowledge_base")
|
|
self.assertEqual(
|
|
self.audit_log_service.events[0]["detail_json"],
|
|
{
|
|
"name": "采购合同知识库",
|
|
"description": "采购模板与制度",
|
|
"status": "active",
|
|
"document_count": 0,
|
|
},
|
|
)
|
|
|
|
def test_admin_can_upload_document(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {
|
|
"id": "user-admin",
|
|
"tenant_id": "tenant-demo-001",
|
|
"role": "admin",
|
|
}
|
|
|
|
response = self.client.post(
|
|
"/api/v1/knowledge-bases/kb-123/documents",
|
|
data={"note": "首批规则"},
|
|
files={"file": ("rules.txt", "合同解除规则".encode("utf-8"), "text/plain")},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
body = response.json()
|
|
self.assertEqual(body["data"]["file_name"], "rules.txt")
|
|
self.assertEqual(self.audit_log_service.events[0]["action"], "upload_knowledge_document")
|
|
self.assertEqual(
|
|
self.audit_log_service.events[0]["detail_json"],
|
|
{
|
|
"knowledge_base_id": "kb-123",
|
|
"file_name": "rules.txt",
|
|
"mime_type": "text/plain",
|
|
"file_size": len("合同解除规则".encode("utf-8")),
|
|
"status": "indexed",
|
|
"current_stage": "indexed",
|
|
"parse_status": "completed",
|
|
"index_status": "completed",
|
|
"error_message": None,
|
|
"note": "首批规则",
|
|
},
|
|
)
|
|
|
|
def test_member_cannot_access_knowledge_bases(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {
|
|
"id": "user-member",
|
|
"tenant_id": "tenant-demo-001",
|
|
"role": "member",
|
|
}
|
|
|
|
response = self.client.get("/api/v1/knowledge-bases")
|
|
|
|
self.assertEqual(response.status_code, 403)
|
|
self.assertEqual(response.json()["message"], "forbidden")
|
|
self.assertEqual(self.audit_log_service.events, [])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|