chore: import upstream snapshot with attribution
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# Tests for assistant-ui package
|
||||
@@ -0,0 +1,510 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user