Files
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

510 lines
16 KiB
Python

"""Tests for the assistant-ui-sync-server-api client."""
import pytest
import httpx
import warnings
from unittest.mock import AsyncMock, Mock, patch
from assistant_ui import AssistantClient
from assistant_ui.types import Message, AssistantTransportCommand
@pytest.mark.asyncio
async def test_basic_chat():
"""Test basic async chat functionality."""
client = AssistantClient(
base_url="https://api.example.com",
headers={"Authorization": "Bearer test"}
)
messages: list[Message] = [
{
"role": "user",
"content": [{"type": "text", "text": "Hello"}],
}
]
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
mock_response.json.return_value = {"response": "Hi there!"}
mock_response.text = "Success"
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-123")
# Suppress deprecation warning for this test
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
response = await thread.chat(
messages=messages,
system="You are helpful"
)
assert response.status_code == 200
# Check the request was made correctly
call_args = mock_client.request.call_args
payload = call_args[1]["json"]
assert payload["threadId"] == "thread-123"
assert payload["messages"] == messages
assert payload["system"] == "You are helpful"
# Commands should also be present due to conversion
assert "commands" in payload
await client.close()
@pytest.mark.asyncio
async def test_chat_with_tools():
"""Test chat with tools."""
async with AssistantClient(base_url="https://api.example.com") as client:
messages: list[Message] = []
tools = {"search": {"description": "Search the web"}}
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-456")
await thread.chat(messages=messages, tools=tools)
# Check tools were included
call_args = mock_client.request.call_args
assert call_args[1]["json"]["tools"] == tools
@pytest.mark.asyncio
async def test_cancel():
"""Test cancel functionality."""
client = AssistantClient(base_url="https://api.example.com")
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-789")
await thread.cancel()
# Check cancel request was made
mock_client.request.assert_called_once_with(
"POST",
"/api/cancel",
headers={"Content-Type": "application/json"},
json={"threadId": "thread-789"}
)
await client.close()
def test_sync_chat():
"""Test synchronous chat."""
with AssistantClient(base_url="https://api.example.com") as client:
messages: list[Message] = [
{
"role": "assistant",
"content": [
{"type": "text", "text": "I can help."},
{
"type": "tool-call",
"toolCallId": "call-123",
"toolName": "search",
"args": {"query": "weather"}
}
],
}
]
mock_response = Mock()
mock_response.is_success = True
mock_response.status_code = 200
mock_response.text = "Success"
with patch.object(client, "_ensure_sync_client") as mock_ensure:
mock_client = Mock()
mock_client.request = Mock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-sync")
response = thread.chat_sync(messages=messages)
assert response.status_code == 200
@pytest.mark.asyncio
async def test_async_headers():
"""Test with async header function."""
async def get_headers():
return {"Authorization": "Bearer dynamic-token"}
client = AssistantClient(
base_url="https://api.example.com",
headers=get_headers
)
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-async-headers")
await thread.chat(messages=[])
# Check dynamic headers were used
call_args = mock_client.request.call_args
assert call_args[1]["headers"]["Authorization"] == "Bearer dynamic-token"
await client.close()
def test_sync_with_async_headers_raises():
"""Test that sync methods raise error with async headers."""
async def get_headers():
return {"Authorization": "Bearer token"}
client = AssistantClient(
base_url="https://api.example.com",
headers=get_headers
)
thread = client.threads("thread-123")
with pytest.raises(ValueError) as exc_info:
thread.chat_sync(messages=[])
assert "async header functions" in str(exc_info.value)
@pytest.mark.asyncio
async def test_error_handling():
"""Test error handling."""
client = AssistantClient(base_url="https://api.example.com")
mock_response = AsyncMock()
mock_response.is_success = False
mock_response.status_code = 500
mock_response.text = "Internal Server Error"
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-error")
with pytest.raises(Exception) as exc_info:
await thread.chat(messages=[])
assert "Request failed with status 500" in str(exc_info.value)
await client.close()
@pytest.mark.asyncio
async def test_context_manager():
"""Test async context manager."""
async with AssistantClient(base_url="https://api.example.com") as client:
assert client._async_client is None # Not created until first use
# Force client creation
mock_response = AsyncMock()
mock_response.is_success = True
with patch("httpx.AsyncClient") as mock_async_client_class:
mock_instance = AsyncMock()
mock_instance.request = AsyncMock(return_value=mock_response)
mock_async_client_class.return_value = mock_instance
thread = client.threads("test")
await thread.chat(messages=[])
assert client._async_client is not None
# After context exit, client should be closed
assert client._async_client is None
def test_sync_context_manager():
"""Test sync context manager."""
with AssistantClient(base_url="https://api.example.com") as client:
assert client._sync_client is None # Not created until first use
# Force client creation
mock_response = Mock()
mock_response.is_success = True
with patch("httpx.Client") as mock_client_class:
mock_instance = Mock()
mock_instance.request = Mock(return_value=mock_response)
mock_client_class.return_value = mock_instance
thread = client.threads("test")
thread.chat_sync(messages=[])
assert client._sync_client is not None
# After context exit, client should be closed
assert client._sync_client is None
@pytest.mark.asyncio
async def test_all_parameters():
"""Test chat with all possible parameters."""
client = AssistantClient(
base_url="https://api.example.com",
headers={"X-API-Key": "test-key"},
timeout=30.0
)
messages: list[Message] = [
{
"role": "user",
"content": [
{"type": "text", "text": "Analyze this:"},
{"type": "image", "image": "https://example.com/img.jpg"},
{"type": "file", "data": "https://example.com/doc.pdf", "mimeType": "application/pdf"}
],
"unstable_id": "msg-123",
}
]
tools = {
"analyze": {
"description": "Analyze content",
"parameters": {
"type": "object",
"properties": {"content": {"type": "string"}},
"required": ["content"]
}
}
}
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-all-params")
await thread.chat(
messages=messages,
tools=tools,
system="You are an analyzer",
unstable_assistantMessageId="asst-456",
runConfig={"model": "gpt-5.4-nano"},
state={"session": "abc"},
custom_field="custom_value"
)
# Check all parameters were included
call_args = mock_client.request.call_args
payload = call_args[1]["json"]
assert payload["threadId"] == "thread-all-params"
assert payload["system"] == "You are an analyzer"
assert payload["messages"] == messages
assert payload["tools"] == tools
assert payload["unstable_assistantMessageId"] == "asst-456"
assert payload["runConfig"] == {"model": "gpt-5.4-nano"}
assert payload["state"] == {"session": "abc"}
assert payload["custom_field"] == "custom_value"
await client.close()
@pytest.mark.asyncio
async def test_chat_with_commands():
"""Test chat with commands instead of messages."""
client = AssistantClient(
base_url="https://api.example.com",
headers={"Authorization": "Bearer test"}
)
commands: list[AssistantTransportCommand] = [
{
"type": "add-message",
"message": {
"role": "user",
"parts": [{"type": "text", "text": "Hello with commands"}]
}
},
{
"type": "add-tool-result",
"toolCallId": "call-789",
"toolName": "search",
"result": {"data": "search results"},
"isError": False,
"artifact": None
}
]
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-cmd")
response = await thread.chat(commands=commands)
assert response.status_code == 200
# Check the request was made with commands
call_args = mock_client.request.call_args
payload = call_args[1]["json"]
assert payload["threadId"] == "thread-cmd"
assert payload["commands"] == commands
assert "messages" not in payload
await client.close()
@pytest.mark.asyncio
async def test_chat_with_deprecated_messages():
"""Test that messages parameter shows deprecation warning."""
client = AssistantClient(base_url="https://api.example.com")
messages: list[Message] = [
{
"role": "user",
"content": [{"type": "text", "text": "Using deprecated API"}],
}
]
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-deprecated")
# Check deprecation warning
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
await thread.chat(messages=messages)
assert len(w) == 1
assert issubclass(w[0].category, DeprecationWarning)
assert "messages' parameter is deprecated" in str(w[0].message)
# Check both messages and commands were sent
call_args = mock_client.request.call_args
payload = call_args[1]["json"]
assert payload["messages"] == messages
assert "commands" in payload
assert len(payload["commands"]) > 0
await client.close()
@pytest.mark.asyncio
async def test_chat_messages_and_commands_together():
"""Test using both messages and commands together."""
client = AssistantClient(base_url="https://api.example.com")
messages: list[Message] = [
{
"role": "assistant",
"content": [{"type": "text", "text": "Previous message"}],
}
]
commands: list[AssistantTransportCommand] = [
{
"type": "add-message",
"message": {
"role": "user",
"parts": [{"type": "text", "text": "New command"}]
}
}
]
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
with patch.object(client, "_ensure_async_client") as mock_ensure:
mock_client = AsyncMock()
mock_client.request = AsyncMock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-both")
with warnings.catch_warnings():
warnings.simplefilter("ignore")
await thread.chat(messages=messages, commands=commands)
# Check both were merged
call_args = mock_client.request.call_args
payload = call_args[1]["json"]
assert payload["messages"] == messages
assert "commands" in payload
# Should have original command plus converted message
assert len(payload["commands"]) >= 2
await client.close()
def test_sync_chat_with_commands():
"""Test synchronous chat with commands."""
with AssistantClient(base_url="https://api.example.com") as client:
commands: list[AssistantTransportCommand] = [
{
"type": "add-message",
"message": {
"role": "assistant",
"parts": [{"type": "text", "text": "Sync command test"}]
}
}
]
mock_response = Mock()
mock_response.is_success = True
mock_response.status_code = 200
with patch.object(client, "_ensure_sync_client") as mock_ensure:
mock_client = Mock()
mock_client.request = Mock(return_value=mock_response)
mock_ensure.return_value = mock_client
thread = client.threads("thread-sync-cmd")
response = thread.chat_sync(commands=commands)
assert response.status_code == 200
# Check commands were sent
call_args = mock_client.request.call_args
payload = call_args[1]["json"]
assert payload["commands"] == commands