155 lines
6.3 KiB
Python
155 lines
6.3 KiB
Python
import json
|
|
import unittest
|
|
|
|
from fastapi import HTTPException
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.api.chat import get_shared_ai_platform_client
|
|
from app.api.conversations import get_shared_ai_platform_client as get_conversations_shared_ai_platform_client
|
|
from app.api.reviews import require_current_user
|
|
from app.main import app
|
|
|
|
|
|
class StubChatService:
|
|
def __init__(self):
|
|
self.stream_calls = []
|
|
|
|
def stream_answer(self, current_user, conversation_id, message, review_context=None):
|
|
self.stream_calls.append((current_user, conversation_id, message, review_context))
|
|
if not message.strip():
|
|
yield 'data: {"type":"error","message":"message 不能为空"}\n\n'
|
|
return
|
|
active_id = conversation_id or "conv-123"
|
|
yield 'data: {"type":"text","content":"第一段"}\n\n'
|
|
yield 'data: {"type":"text","content":"重点摘要:\\n- 关注违约金条款"}\n\n'
|
|
yield 'data: {"type":"sources","sources":[{"title":"民法典第585条","content":"...","score":1.2}]}\n\n'
|
|
yield f'data: {{"type":"end","conversation_id":"{active_id}"}}\n\n'
|
|
|
|
def list_conversations(self, current_user, page, page_size):
|
|
return {
|
|
"items": [
|
|
{
|
|
"id": "conv-123",
|
|
"title": "违约金条款咨询",
|
|
"message_count": 2,
|
|
"last_message_at": "2026-04-15T11:00:00+00:00",
|
|
}
|
|
],
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"total": 1,
|
|
}
|
|
|
|
def get_conversation_detail(self, current_user, conversation_id):
|
|
if conversation_id == "missing":
|
|
raise HTTPException(status_code=404, detail="conversation not found")
|
|
return {
|
|
"id": conversation_id,
|
|
"title": "违约金条款咨询",
|
|
"messages": [
|
|
{
|
|
"id": "msg-1",
|
|
"role": "user",
|
|
"content": "这个违约金条款有没有问题?",
|
|
"sources": [],
|
|
"created_at": "2026-04-15T11:00:00+00:00",
|
|
},
|
|
{
|
|
"id": "msg-2",
|
|
"role": "assistant",
|
|
"content": "存在偏高风险。",
|
|
"sources": [{"title": "民法典第585条", "content": "..."}],
|
|
"created_at": "2026-04-15T11:00:03+00:00",
|
|
},
|
|
],
|
|
}
|
|
|
|
|
|
class ChatApiTestCase(unittest.TestCase):
|
|
def setUp(self):
|
|
self.chat_service = StubChatService()
|
|
app.dependency_overrides[get_shared_ai_platform_client] = lambda: self.chat_service
|
|
app.dependency_overrides[get_conversations_shared_ai_platform_client] = lambda: self.chat_service
|
|
self.client = TestClient(app)
|
|
|
|
def tearDown(self):
|
|
app.dependency_overrides.clear()
|
|
|
|
def test_stream_chat_requires_authentication(self):
|
|
response = self.client.post("/api/v1/chat/stream", json={"message": "违约金条款有没有问题"})
|
|
|
|
self.assertEqual(response.status_code, 401)
|
|
|
|
def test_stream_chat_returns_sse_events(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {"id": "user-1", "tenant_id": "tenant-1"}
|
|
|
|
response = self.client.post("/api/v1/chat/stream", json={"message": "违约金条款有没有问题"})
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertIn("text/event-stream", response.headers["content-type"])
|
|
|
|
events = [line.removeprefix("data: ") for line in response.text.split("\n\n") if line.startswith("data: ")]
|
|
payloads = [json.loads(item) for item in events]
|
|
|
|
self.assertEqual(payloads[0]["type"], "text")
|
|
self.assertEqual(payloads[1]["type"], "text")
|
|
self.assertEqual(payloads[2]["type"], "sources")
|
|
self.assertEqual(payloads[2]["sources"][0]["score"], 1.2)
|
|
self.assertEqual(payloads[3]["type"], "end")
|
|
self.assertEqual(payloads[3]["conversation_id"], "conv-123")
|
|
self.assertEqual(self.chat_service.stream_calls[0][0]["id"], "user-1")
|
|
|
|
def test_stream_chat_accepts_review_context(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {"id": "user-1", "tenant_id": "tenant-1"}
|
|
|
|
response = self.client.post(
|
|
"/api/v1/chat/stream",
|
|
json={
|
|
"message": "请继续解释这个风险项",
|
|
"review_context": {
|
|
"reviewId": "review-123",
|
|
"metadata": {
|
|
"reviewTitle": "采购合同审查",
|
|
"focusLabel": "自动续约",
|
|
"actionLabel": "解释风险",
|
|
},
|
|
},
|
|
},
|
|
)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(self.chat_service.stream_calls[0][3]["reviewId"], "review-123")
|
|
self.assertEqual(self.chat_service.stream_calls[0][3]["metadata"]["focusLabel"], "自动续约")
|
|
|
|
def test_list_conversations_returns_paginated_payload(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {"id": "user-1", "tenant_id": "tenant-1"}
|
|
|
|
response = self.client.get("/api/v1/conversations?page=1&page_size=20")
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()["data"]
|
|
self.assertEqual(payload["total"], 1)
|
|
self.assertEqual(payload["items"][0]["id"], "conv-123")
|
|
|
|
def test_get_conversation_detail_returns_messages(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {"id": "user-1", "tenant_id": "tenant-1"}
|
|
|
|
response = self.client.get("/api/v1/conversations/conv-123")
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
payload = response.json()["data"]
|
|
self.assertEqual(payload["id"], "conv-123")
|
|
self.assertEqual(len(payload["messages"]), 2)
|
|
self.assertEqual(payload["messages"][1]["sources"][0]["title"], "民法典第585条")
|
|
|
|
def test_get_conversation_detail_returns_404_when_missing(self):
|
|
app.dependency_overrides[require_current_user] = lambda: {"id": "user-1", "tenant_id": "tenant-1"}
|
|
|
|
response = self.client.get("/api/v1/conversations/missing")
|
|
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|