Files
fastgpt-python-sdk/tests/test_chat_client.py
2026-06-01 13:39:22 +08:00

749 lines
28 KiB
Python

"""Tests for ChatClient."""
from unittest.mock import Mock, patch
import pytest
import httpx
from fastgpt_client.client import ChatClient
from fastgpt_client.exceptions import ValidationError
class TestChatClientCreateChatCompletion:
"""Test suite for ChatClient.create_chat_completion method."""
def test_base_url_trailing_api_is_normalized(self, api_key):
"""A base URL ending with /api should not produce /api/api/... requests."""
client = ChatClient(api_key, base_url="https://cloud.fastgpt.cn/api")
assert client.base_url == "https://cloud.fastgpt.cn"
assert str(client._client.base_url) == "https://cloud.fastgpt.cn"
def test_create_chat_completion_basic(self, api_key, sample_chat_response):
"""Test basic chat completion creation."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json = Mock(return_value=sample_chat_response)
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.create_chat_completion(
messages=[{"role": "user", "content": "Hello"}],
stream=False
)
assert response.status_code == 200
mock_send.assert_called_once()
call_args = mock_send.call_args
assert call_args[0][0] == "POST"
assert call_args[0][1] == "/api/v1/chat/completions"
assert call_args[1]['json']['messages'] == [{"role": "user", "content": "Hello"}]
assert call_args[1]['stream'] is False
def test_create_chat_completion_with_chat_id(self, api_key):
"""Test chat completion with chatId parameter."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.create_chat_completion(
messages=[{"role": "user", "content": "Hello"}],
chatId="chat-123"
)
assert response.status_code == 200
assert mock_send.call_args[1]['json']['chatId'] == "chat-123"
def test_create_chat_completion_with_variables(self, api_key):
"""Test chat completion with variables parameter."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.create_chat_completion(
messages=[{"role": "user", "content": "Hello"}],
variables={"name": "John", "city": "NYC"}
)
assert response.status_code == 200
assert mock_send.call_args[1]['json']['variables'] == {"name": "John", "city": "NYC"}
def test_create_chat_completion_with_detail(self, api_key):
"""Test chat completion with detail enabled."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.create_chat_completion(
messages=[{"role": "user", "content": "Hello"}],
detail=True
)
assert response.status_code == 200
assert mock_send.call_args[1]['json']['detail'] is True
def test_create_chat_completion_with_response_chat_item_id(self, api_key):
"""Test chat completion with custom responseChatItemId."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.create_chat_completion(
messages=[{"role": "user", "content": "Hello"}],
responseChatItemId="custom-id-123"
)
assert response.status_code == 200
assert mock_send.call_args[1]['json']['responseChatItemId'] == "custom-id-123"
def test_create_chat_completion_streaming(self, api_key, mock_stream_response):
"""Test streaming chat completion."""
client = ChatClient(api_key)
with patch.object(client, '_send_request', return_value=mock_stream_response) as mock_send:
response = client.create_chat_completion(
messages=[{"role": "user", "content": "Hello"}],
stream=True
)
assert response.status_code == 200
assert mock_send.call_args[1]['stream'] is True
def test_create_chat_completion_all_parameters(self, api_key):
"""Test chat completion with all parameters."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.create_chat_completion(
messages=[{"role": "user", "content": "Hello"}],
stream=True,
chatId="chat-123",
detail=True,
variables={"key": "value"},
responseChatItemId="custom-id"
)
assert response.status_code == 200
json_data = mock_send.call_args[1]['json']
assert json_data['chatId'] == "chat-123"
assert json_data['detail'] is True
assert json_data['variables'] == {"key": "value"}
assert json_data['responseChatItemId'] == "custom-id"
def test_create_chat_completion_parameter_validation(self, api_key):
"""Test that parameter validation works for known fields."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
# Test with valid chatId (should pass validation)
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.create_chat_completion(
messages=[{"role": "user", "content": "Hello"}],
chatId="valid-chat-id"
)
# Verify the request was sent successfully
assert response.status_code == 200
mock_send.assert_called_once()
class TestChatClientFileUpload:
"""Test suite for ChatClient chat file upload helpers."""
def test_presign_chat_file_post_url_uses_local_path(self, api_key):
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json = Mock(return_value={
"code": 200,
"message": "",
"data": {"url": "https://s3.example/upload", "fields": {"key": "chat/file.png"}},
})
with patch.object(client, "_send_request", return_value=mock_response) as mock_send:
result = client.presign_chat_file_post_url(
appId="app-123",
chatId="chat-123",
filename="file.png",
fileSelectConfig={"canSelectImg": True},
)
assert result["fields"]["key"] == "chat/file.png"
assert mock_send.call_args[0][1] == "/api/core/chat/presignChatFilePostUrl"
assert mock_send.call_args[1]["json"]["fileSelectConfig"] == {"canSelectImg": True}
def test_presign_chat_file_get_url_unwraps_string(self, api_key):
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json = Mock(return_value={
"code": 200,
"message": "",
"data": "https://s3.example/preview.png",
})
with patch.object(client, "_send_request", return_value=mock_response) as mock_send:
result = client.presign_chat_file_get_url(appId="app-123", key="chat/file.png")
assert result == "https://s3.example/preview.png"
assert mock_send.call_args[0][1] == "/api/core/chat/presignChatFileGetUrl"
def test_upload_to_presigned_url_with_post_fields(self, api_key, tmp_path):
client = ChatClient(api_key)
file_path = tmp_path / "file.png"
file_path.write_bytes(b"png")
mock_response = Mock(spec=httpx.Response)
mock_response.raise_for_status = Mock()
with patch("fastgpt_client.client.httpx.post", return_value=mock_response) as mock_post:
response = client.upload_to_presigned_url(
upload_url="https://s3.example/upload",
file_path=file_path,
fields={"key": "chat/file.png", "policy": "abc"},
)
assert response is mock_response
mock_post.assert_called_once()
assert mock_post.call_args[1]["data"] == {"key": "chat/file.png", "policy": "abc"}
assert "file" in mock_post.call_args[1]["files"]
def test_upload_to_presigned_url_with_put_headers(self, api_key, tmp_path):
client = ChatClient(api_key)
file_path = tmp_path / "file.png"
file_path.write_bytes(b"png")
mock_response = Mock(spec=httpx.Response)
mock_response.raise_for_status = Mock()
with patch("fastgpt_client.client.httpx.put", return_value=mock_response) as mock_put:
response = client.upload_to_presigned_url(
upload_url="https://s3.example/upload",
file_path=file_path,
headers={"x-amz-meta-test": "1"},
)
assert response is mock_response
mock_put.assert_called_once()
assert mock_put.call_args[1]["headers"]["x-amz-meta-test"] == "1"
assert mock_put.call_args[1]["headers"]["Content-Type"] == "image/png"
def test_upload_chat_image_with_fields_gets_preview_url(self, api_key, tmp_path):
client = ChatClient(api_key)
file_path = tmp_path / "file.png"
file_path.write_bytes(b"png")
with patch.object(client, "_get_chat_file_select_config", return_value={"canSelectImg": True}), \
patch.object(client, "presign_chat_file_post_url", return_value={
"url": "https://s3.example/upload",
"fields": {"key": "chat/file.png"},
"maxSize": 100,
}) as mock_presign, \
patch.object(client, "upload_to_presigned_url") as mock_upload, \
patch.object(client, "presign_chat_file_get_url", return_value="https://s3.example/preview.png") as mock_get:
result = client.upload_chat_image(
appId="app-123",
chatId="chat-123",
file_path=file_path,
)
assert result == {
"type": "img",
"name": "file.png",
"key": "chat/file.png",
"url": "https://s3.example/preview.png",
"previewUrl": "https://s3.example/preview.png",
}
assert mock_presign.call_args[1]["fileSelectConfig"] == {"canSelectImg": True}
mock_upload.assert_called_once()
mock_get.assert_called_once_with(appId="app-123", key="chat/file.png", outLinkAuthData=None)
class TestChatClientGetChatHistories:
"""Test suite for ChatClient.get_chat_histories method."""
def test_get_chat_histories_basic(self, api_key, sample_chat_histories_response):
"""Test getting chat histories with basic parameters."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json = Mock(return_value=sample_chat_histories_response)
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_histories(appId="app-123")
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "POST"
assert call_args[0][1] == "/api/core/chat/getHistories"
assert call_args[1]['json']['appId'] == "app-123"
def test_get_chat_histories_with_pagination(self, api_key):
"""Test getting chat histories with pagination."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_histories(
appId="app-123",
offset=10,
pageSize=50
)
json_data = mock_send.call_args[1]['json']
assert json_data['offset'] == 10
assert json_data['pageSize'] == 50
def test_get_chat_histories_with_source(self, api_key):
"""Test getting chat histories with source filter."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_histories(
appId="app-123",
source="online"
)
assert mock_send.call_args[1]['json']['source'] == "online"
def test_get_chat_histories_all_parameters(self, api_key):
"""Test getting chat histories with all parameters."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_histories(
appId="app-123",
offset=5,
pageSize=25,
source="share"
)
json_data = mock_send.call_args[1]['json']
assert json_data == {
"appId": "app-123",
"offset": 5,
"pageSize": 25,
"source": "share"
}
class TestChatClientGetChatInit:
"""Test suite for ChatClient.get_chat_init method."""
def test_get_chat_init(self, api_key):
"""Test getting chat initialization."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_init(appId="app-123", chatId="chat-123")
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "GET"
assert call_args[0][1] == "/api/core/chat/init"
assert call_args[1]['params'] == {"appId": "app-123", "chatId": "chat-123"}
class TestChatClientGetChatRecords:
"""Test suite for ChatClient.get_chat_records method."""
def test_get_chat_records_basic(self, api_key, sample_chat_records_response):
"""Test getting chat records with basic parameters."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
mock_response.json = Mock(return_value=sample_chat_records_response)
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_records(appId="app-123", chatId="chat-123")
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "POST"
assert call_args[0][1] == "/api/core/chat/getPaginationRecords"
json_data = call_args[1]['json']
assert json_data['appId'] == "app-123"
assert json_data['chatId'] == "chat-123"
def test_get_chat_records_with_pagination(self, api_key):
"""Test getting chat records with pagination."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_records(
appId="app-123",
chatId="chat-123",
offset=20,
pageSize=30
)
json_data = mock_send.call_args[1]['json']
assert json_data['offset'] == 20
assert json_data['pageSize'] == 30
def test_get_chat_records_with_custom_feedbacks(self, api_key):
"""Test getting chat records with custom feedbacks loaded."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_records(
appId="app-123",
chatId="chat-123",
loadCustomFeedbacks=True
)
assert mock_send.call_args[1]['json']['loadCustomFeedbacks'] is True
def test_get_chat_records_all_parameters(self, api_key):
"""Test getting chat records with all parameters."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_chat_records(
appId="app-123",
chatId="chat-123",
offset=10,
pageSize=20,
loadCustomFeedbacks=True
)
json_data = mock_send.call_args[1]['json']
assert json_data == {
"appId": "app-123",
"chatId": "chat-123",
"offset": 10,
"pageSize": 20,
"loadCustomFeedbacks": True
}
class TestChatClientGetRecordDetail:
"""Test suite for ChatClient.get_record_detail method."""
def test_get_record_detail(self, api_key):
"""Test getting record detail."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_record_detail(
appId="app-123",
chatId="chat-123",
dataId="data-123"
)
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "GET"
assert call_args[0][1] == "/api/core/chat/getResData"
assert call_args[1]['params'] == {
"appId": "app-123",
"chatId": "chat-123",
"dataId": "data-123"
}
class TestChatClientUpdateChatHistory:
"""Test suite for ChatClient.update_chat_history method."""
def test_update_chat_history_title(self, api_key):
"""Test updating chat history title."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.update_chat_history(
appId="app-123",
chatId="chat-123",
customTitle="New Title"
)
assert response.status_code == 200
json_data = mock_send.call_args[1]['json']
assert json_data['appId'] == "app-123"
assert json_data['chatId'] == "chat-123"
assert json_data['customTitle'] == "New Title"
assert 'top' not in json_data
def test_update_chat_history_pin(self, api_key):
"""Test pinning chat history."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.update_chat_history(
appId="app-123",
chatId="chat-123",
top=True
)
json_data = mock_send.call_args[1]['json']
assert json_data['top'] is True
assert 'customTitle' not in json_data
def test_update_chat_history_both_parameters(self, api_key):
"""Test updating both title and pin status."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.update_chat_history(
appId="app-123",
chatId="chat-123",
customTitle="Important Chat",
top=True
)
json_data = mock_send.call_args[1]['json']
assert json_data['customTitle'] == "Important Chat"
assert json_data['top'] is True
class TestChatClientDeleteChatHistory:
"""Test suite for ChatClient.delete_chat_history method."""
def test_delete_chat_history(self, api_key):
"""Test deleting a chat history."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.delete_chat_history(appId="app-123", chatId="chat-123")
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "DELETE"
assert call_args[0][1] == "/api/core/chat/delHistory"
assert call_args[1]['params'] == {"appId": "app-123", "chatId": "chat-123"}
class TestChatClientClearChatHistories:
"""Test suite for ChatClient.clear_chat_histories method."""
def test_clear_chat_histories(self, api_key):
"""Test clearing all chat histories."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.clear_chat_histories(appId="app-123")
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "DELETE"
assert call_args[0][1] == "/api/core/chat/clearHistories"
assert call_args[1]['params'] == {"appId": "app-123"}
class TestChatClientDeleteChatRecord:
"""Test suite for ChatClient.delete_chat_record method."""
def test_delete_chat_record(self, api_key):
"""Test deleting a single chat record."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.delete_chat_record(
appId="app-123",
chatId="chat-123",
contentId="content-123"
)
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "DELETE"
assert call_args[0][1] == "/api/core/chat/item/delete"
assert call_args[1]['json'] == {
"appId": "app-123",
"chatId": "chat-123",
"contentId": "content-123"
}
class TestChatClientSendFeedback:
"""Test suite for ChatClient.send_feedback method."""
def test_send_feedback_good(self, api_key):
"""Test sending positive feedback."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.send_feedback(
appId="app-123",
chatId="chat-123",
dataId="data-123",
userGoodFeedback="Great response!"
)
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "POST"
assert call_args[0][1] == "/api/core/chat/feedback/updateUserFeedback"
json_data = call_args[1]['json']
assert json_data['userGoodFeedback'] == "Great response!"
assert 'userBadFeedback' not in json_data
def test_send_feedback_bad(self, api_key):
"""Test sending negative feedback."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.send_feedback(
appId="app-123",
chatId="chat-123",
dataId="data-123",
userBadFeedback="Not helpful"
)
json_data = mock_send.call_args[1]['json']
assert json_data['userBadFeedback'] == "Not helpful"
assert 'userGoodFeedback' not in json_data
def test_send_feedback_cancel_good(self, api_key):
"""Test canceling positive feedback by passing empty string."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.send_feedback(
appId="app-123",
chatId="chat-123",
dataId="data-123",
userGoodFeedback=""
)
# Empty string is still sent (to cancel the feedback)
json_data = mock_send.call_args[1]['json']
assert 'userGoodFeedback' in json_data
def test_send_feedback_only_required_params(self, api_key):
"""Test sending feedback with only required parameters."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.send_feedback(
appId="app-123",
chatId="chat-123",
dataId="data-123"
)
json_data = mock_send.call_args[1]['json']
assert json_data == {
"appId": "app-123",
"chatId": "chat-123",
"dataId": "data-123"
}
class TestChatClientGetSuggestedQuestions:
"""Test suite for ChatClient.get_suggested_questions method."""
def test_get_suggested_questions_basic(self, api_key):
"""Test getting suggested questions."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_suggested_questions(
appId="app-123",
chatId="chat-123"
)
assert response.status_code == 200
call_args = mock_send.call_args
assert call_args[0][0] == "POST"
assert call_args[0][1] == "/api/core/ai/agent/v2/createQuestionGuide"
json_data = call_args[1]['json']
assert json_data['appId'] == "app-123"
assert json_data['chatId'] == "chat-123"
assert 'questionGuide' not in json_data
def test_get_suggested_questions_with_guide(self, api_key):
"""Test getting suggested questions with custom question guide."""
client = ChatClient(api_key)
mock_response = Mock(spec=httpx.Response)
mock_response.status_code = 200
question_guide = {
"maxQuestions": 5,
"contextWindow": 10
}
with patch.object(client, '_send_request', return_value=mock_response) as mock_send:
response = client.get_suggested_questions(
appId="app-123",
chatId="chat-123",
questionGuide=question_guide
)
json_data = mock_send.call_args[1]['json']
assert json_data['questionGuide'] == question_guide