b7f52be4c9
CI / Run CI (push) Has been cancelled
CI / check-backend (push) Has been cancelled
CI / check-frontend (push) Has been cancelled
CI / tests (push) Has been cancelled
CI / e2e-tests (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
758 lines
28 KiB
Python
758 lines
28 KiB
Python
import asyncio
|
|
import json
|
|
from contextlib import contextmanager
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
|
|
import pytest
|
|
|
|
from chainlit.action import Action
|
|
from chainlit.context import ChainlitContext, context_var
|
|
from chainlit.message import (
|
|
AskActionMessage,
|
|
AskElementMessage,
|
|
AskFileMessage,
|
|
AskUserMessage,
|
|
ErrorMessage,
|
|
Message,
|
|
MessageBase,
|
|
)
|
|
|
|
|
|
@contextmanager
|
|
def mock_chainlit_context(session=None):
|
|
"""Context manager to set up and tear down Chainlit context."""
|
|
mock_loop = Mock(spec=asyncio.AbstractEventLoop)
|
|
mock_session = session or Mock()
|
|
mock_session.thread_id = "thread_123"
|
|
|
|
with patch("asyncio.get_running_loop", return_value=mock_loop):
|
|
mock_emitter = AsyncMock()
|
|
mock_context = ChainlitContext(session=mock_session, emitter=mock_emitter)
|
|
token = context_var.set(mock_context)
|
|
try:
|
|
yield mock_context
|
|
finally:
|
|
context_var.reset(token)
|
|
|
|
|
|
class TestMessageBase:
|
|
"""Test suite for MessageBase class."""
|
|
|
|
def test_post_init_sets_thread_id(self):
|
|
"""Test that __post_init__ sets thread_id from session."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content="test")
|
|
assert msg.thread_id == "thread_123"
|
|
|
|
def test_post_init_generates_id_if_not_provided(self):
|
|
"""Test that __post_init__ generates UUID if id not provided."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content="test")
|
|
assert msg.id is not None
|
|
assert len(msg.id) == 36
|
|
|
|
def test_post_init_uses_provided_id(self):
|
|
"""Test that __post_init__ uses provided id."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content="test", id="custom_id")
|
|
assert msg.id == "custom_id"
|
|
|
|
def test_from_dict_creates_message(self):
|
|
"""Test creating message from dictionary."""
|
|
step_dict = {
|
|
"id": "msg_123",
|
|
"parentId": "parent_123",
|
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
"output": "Hello world",
|
|
"name": "Assistant",
|
|
"command": "/test",
|
|
"type": "user_message",
|
|
"language": "python",
|
|
"metadata": {"key": "value"},
|
|
}
|
|
|
|
with mock_chainlit_context():
|
|
msg = MessageBase.from_dict(step_dict)
|
|
|
|
assert msg.id == "msg_123"
|
|
assert msg.parent_id == "parent_123"
|
|
assert msg.created_at == "2024-01-01T00:00:00Z"
|
|
assert msg.content == "Hello world"
|
|
assert msg.author == "Assistant"
|
|
assert msg.command == "/test"
|
|
assert msg.type == "user_message"
|
|
assert msg.language == "python"
|
|
assert msg.metadata == {"key": "value"}
|
|
|
|
def test_from_dict_with_minimal_data(self):
|
|
"""Test from_dict with minimal required fields."""
|
|
step_dict = {
|
|
"id": "msg_123",
|
|
"createdAt": "2024-01-01T00:00:00Z",
|
|
"output": "Hello",
|
|
}
|
|
|
|
with mock_chainlit_context():
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.ui.name = "DefaultBot"
|
|
msg = MessageBase.from_dict(step_dict)
|
|
|
|
assert msg.id == "msg_123"
|
|
assert msg.content == "Hello"
|
|
assert msg.author == "DefaultBot"
|
|
assert msg.type == "assistant_message"
|
|
|
|
def test_to_dict_returns_step_dict(self):
|
|
"""Test converting message to dictionary."""
|
|
with mock_chainlit_context():
|
|
msg = Message(
|
|
content="Test content",
|
|
author="TestBot",
|
|
language="python",
|
|
type="user_message",
|
|
metadata={"key": "value"},
|
|
tags=["tag1", "tag2"],
|
|
id="msg_123",
|
|
parent_id="parent_123",
|
|
command="/test",
|
|
)
|
|
msg.created_at = "2024-01-01T00:00:00Z"
|
|
|
|
result = msg.to_dict()
|
|
|
|
assert result["id"] == "msg_123"
|
|
assert result["threadId"] == "thread_123"
|
|
assert result["parentId"] == "parent_123"
|
|
assert result["createdAt"] == "2024-01-01T00:00:00Z"
|
|
assert result["command"] == "/test"
|
|
assert result["output"] == "Test content"
|
|
assert result["name"] == "TestBot"
|
|
assert result["type"] == "user_message"
|
|
assert result["language"] == "python"
|
|
assert result["streaming"] is False
|
|
assert result["isError"] is False
|
|
assert result["waitForAnswer"] is False
|
|
assert result["metadata"] == {"key": "value"}
|
|
assert result["tags"] == ["tag1", "tag2"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_stops_streaming(self):
|
|
"""Test that update stops streaming."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = Message(content="test")
|
|
msg.streaming = True
|
|
|
|
with patch("chainlit.message.chat_context") as mock_chat_ctx:
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
result = await msg.update()
|
|
|
|
assert msg.streaming is False
|
|
assert result is True
|
|
mock_chat_ctx.add.assert_called_once_with(msg)
|
|
ctx.emitter.update_step.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_with_data_layer(self):
|
|
"""Test update with data layer."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = Message(content="test")
|
|
mock_data_layer = AsyncMock()
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
with patch(
|
|
"chainlit.message.get_data_layer", return_value=mock_data_layer
|
|
):
|
|
with patch("asyncio.create_task") as mock_create_task:
|
|
await msg.update()
|
|
|
|
mock_create_task.assert_called_once()
|
|
ctx.emitter.update_step.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remove_from_chat_context(self):
|
|
"""Test removing message from chat context."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = Message(content="test", id="msg_123")
|
|
|
|
with patch("chainlit.message.chat_context") as mock_chat_ctx:
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
result = await msg.remove()
|
|
|
|
assert result is True
|
|
mock_chat_ctx.remove.assert_called_once_with(msg)
|
|
ctx.emitter.delete_step.assert_called_once()
|
|
|
|
|
|
class TestMessage:
|
|
"""Test suite for Message class."""
|
|
|
|
def test_message_with_string_content(self):
|
|
"""Test creating message with string content."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content="Hello world")
|
|
|
|
assert msg.content == "Hello world"
|
|
assert msg.language is None
|
|
|
|
def test_message_with_dict_content(self):
|
|
"""Test creating message with dict content."""
|
|
with mock_chainlit_context():
|
|
content_dict = {"key": "value", "number": 42}
|
|
msg = Message(content=content_dict)
|
|
|
|
expected = json.dumps(content_dict, indent=4, ensure_ascii=False)
|
|
assert msg.content == expected
|
|
assert msg.language == "json"
|
|
|
|
def test_message_with_non_serializable_dict(self):
|
|
"""Test message with non-JSON-serializable dict."""
|
|
with mock_chainlit_context():
|
|
|
|
class NonSerializable:
|
|
pass
|
|
|
|
content_dict = {"obj": NonSerializable()}
|
|
msg = Message(content=content_dict)
|
|
|
|
assert msg.language == "text"
|
|
assert "NonSerializable" in msg.content
|
|
|
|
def test_message_with_non_string_content(self):
|
|
"""Test message with non-string, non-dict content."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content=12345)
|
|
|
|
assert msg.content == "12345"
|
|
assert msg.language == "text"
|
|
|
|
def test_message_with_custom_author(self):
|
|
"""Test message with custom author."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content="test", author="CustomBot")
|
|
|
|
assert msg.author == "CustomBot"
|
|
|
|
def test_message_with_default_author(self):
|
|
"""Test message uses default author from config."""
|
|
with mock_chainlit_context():
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.ui.name = "DefaultBot"
|
|
msg = Message(content="test")
|
|
|
|
assert msg.author == "DefaultBot"
|
|
|
|
def test_message_with_actions(self):
|
|
"""Test message with actions."""
|
|
with mock_chainlit_context():
|
|
action1 = Mock(spec=Action)
|
|
action2 = Mock(spec=Action)
|
|
msg = Message(content="test", actions=[action1, action2])
|
|
|
|
assert len(msg.actions) == 2
|
|
assert action1 in msg.actions
|
|
assert action2 in msg.actions
|
|
|
|
def test_message_with_elements(self):
|
|
"""Test message with elements."""
|
|
with mock_chainlit_context():
|
|
element1 = Mock()
|
|
element2 = Mock()
|
|
msg = Message(content="test", elements=[element1, element2])
|
|
|
|
assert len(msg.elements) == 2
|
|
assert element1 in msg.elements
|
|
assert element2 in msg.elements
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_message_send(self):
|
|
"""Test sending a message."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = Message(content="test")
|
|
|
|
with patch("chainlit.message.chat_context") as mock_chat_ctx:
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
result = await msg.send()
|
|
|
|
assert result == msg
|
|
assert msg.created_at is not None
|
|
assert msg.streaming is False
|
|
mock_chat_ctx.add.assert_called_once_with(msg)
|
|
ctx.emitter.send_step.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_message_send_with_author_rename(self):
|
|
"""Test sending message with author rename."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content="test", author="OldName")
|
|
|
|
async def rename_author(name):
|
|
return "NewName"
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = rename_author
|
|
|
|
await msg.send()
|
|
|
|
assert msg.author == "NewName"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_message_send_with_actions_and_elements(self):
|
|
"""Test sending message with actions and elements."""
|
|
with mock_chainlit_context():
|
|
action = AsyncMock(spec=Action)
|
|
element = AsyncMock()
|
|
msg = Message(content="test", actions=[action], elements=[element])
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
await msg.send()
|
|
|
|
action.send.assert_called_once()
|
|
element.send.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_message_update_with_actions(self):
|
|
"""Test updating message with new actions."""
|
|
with mock_chainlit_context():
|
|
action1 = AsyncMock(spec=Action)
|
|
action1.forId = None
|
|
action2 = AsyncMock(spec=Action)
|
|
action2.forId = "existing_id"
|
|
|
|
msg = Message(content="test", actions=[action1, action2])
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
result = await msg.update()
|
|
|
|
assert result is True
|
|
action1.send.assert_called_once()
|
|
action2.send.assert_not_called()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_message_remove_actions(self):
|
|
"""Test removing all actions from message."""
|
|
with mock_chainlit_context():
|
|
action1 = AsyncMock(spec=Action)
|
|
action2 = AsyncMock(spec=Action)
|
|
msg = Message(content="test", actions=[action1, action2])
|
|
|
|
await msg.remove_actions()
|
|
|
|
action1.remove.assert_called_once()
|
|
action2.remove.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stream_token_starts_streaming(self):
|
|
"""Test that stream_token starts streaming."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = Message(content="")
|
|
|
|
await msg.stream_token("Hello")
|
|
|
|
assert msg.streaming is True
|
|
assert msg.content == "Hello"
|
|
ctx.emitter.stream_start.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stream_token_appends_content(self):
|
|
"""Test that stream_token appends to content."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = Message(content="Hello")
|
|
msg.streaming = True
|
|
|
|
await msg.stream_token(" world")
|
|
|
|
assert msg.content == "Hello world"
|
|
ctx.emitter.send_token.assert_called_once_with(
|
|
id=msg.id, token=" world", is_sequence=False
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stream_token_with_sequence(self):
|
|
"""Test stream_token with is_sequence=True."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = Message(content="Old content")
|
|
msg.streaming = True
|
|
|
|
await msg.stream_token("New content", is_sequence=True)
|
|
|
|
assert msg.content == "New content"
|
|
ctx.emitter.send_token.assert_called_once_with(
|
|
id=msg.id, token="New content", is_sequence=True
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stream_token_ignores_empty_token(self):
|
|
"""Test that empty tokens are ignored."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = Message(content="test")
|
|
|
|
await msg.stream_token("")
|
|
|
|
assert msg.content == "test"
|
|
ctx.emitter.stream_start.assert_not_called()
|
|
|
|
|
|
class TestErrorMessage:
|
|
"""Test suite for ErrorMessage class."""
|
|
|
|
def test_error_message_initialization(self):
|
|
"""Test ErrorMessage initialization."""
|
|
with mock_chainlit_context():
|
|
msg = ErrorMessage(content="An error occurred")
|
|
|
|
assert msg.content == "An error occurred"
|
|
assert msg.author is not None
|
|
assert msg.type == "assistant_message"
|
|
assert msg.is_error is True
|
|
assert msg.fail_on_persist_error is False
|
|
|
|
def test_error_message_with_custom_author(self):
|
|
"""Test ErrorMessage with custom author."""
|
|
with mock_chainlit_context():
|
|
msg = ErrorMessage(content="Error", author="ErrorBot")
|
|
|
|
assert msg.author == "ErrorBot"
|
|
|
|
def test_error_message_with_fail_on_persist(self):
|
|
"""Test ErrorMessage with fail_on_persist_error=True."""
|
|
with mock_chainlit_context():
|
|
msg = ErrorMessage(content="Error", fail_on_persist_error=True)
|
|
|
|
assert msg.fail_on_persist_error is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_error_message_send(self):
|
|
"""Test sending error message."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = ErrorMessage(content="Error occurred")
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
result = await msg.send()
|
|
|
|
assert result == msg
|
|
ctx.emitter.send_step.assert_called_once()
|
|
|
|
|
|
class TestAskUserMessage:
|
|
"""Test suite for AskUserMessage class."""
|
|
|
|
def test_ask_user_message_initialization(self):
|
|
"""Test AskUserMessage initialization."""
|
|
with mock_chainlit_context():
|
|
msg = AskUserMessage(content="What is your name?")
|
|
|
|
assert msg.content == "What is your name?"
|
|
assert msg.author is not None
|
|
assert msg.timeout == 60
|
|
assert msg.raise_on_timeout is False
|
|
|
|
def test_ask_user_message_with_custom_timeout(self):
|
|
"""Test AskUserMessage with custom timeout."""
|
|
with mock_chainlit_context():
|
|
msg = AskUserMessage(content="Question?", timeout=120)
|
|
|
|
assert msg.timeout == 120
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_user_message_send(self):
|
|
"""Test sending AskUserMessage."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = AskUserMessage(content="Question?")
|
|
ctx.emitter.send_ask_user = AsyncMock(return_value={"output": "Answer"})
|
|
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
result = await msg.send()
|
|
|
|
assert result == {"output": "Answer"}
|
|
assert msg.wait_for_answer is False
|
|
ctx.emitter.send_ask_user.assert_called_once()
|
|
|
|
|
|
class TestAskFileMessage:
|
|
"""Test suite for AskFileMessage class."""
|
|
|
|
def test_ask_file_message_initialization(self):
|
|
"""Test AskFileMessage initialization."""
|
|
with mock_chainlit_context():
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.ui.name = "Bot"
|
|
msg = AskFileMessage(
|
|
content="Upload a file", accept=["text/plain", "application/pdf"]
|
|
)
|
|
|
|
assert msg.content == "Upload a file"
|
|
assert msg.accept == ["text/plain", "application/pdf"]
|
|
assert msg.max_size_mb == 2
|
|
assert msg.max_files == 1
|
|
|
|
def test_ask_file_message_with_custom_limits(self):
|
|
"""Test AskFileMessage with custom limits."""
|
|
with mock_chainlit_context():
|
|
msg = AskFileMessage(
|
|
content="Upload", accept=["image/*"], max_size_mb=10, max_files=5
|
|
)
|
|
|
|
assert msg.max_size_mb == 10
|
|
assert msg.max_files == 5
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_file_message_send_with_response(self):
|
|
"""Test AskFileMessage send with file response."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = AskFileMessage(content="Upload", accept=["text/plain"])
|
|
file_response = [
|
|
{
|
|
"id": "file_123",
|
|
"name": "test.txt",
|
|
"path": "/path/to/test.txt",
|
|
"size": 1024,
|
|
"type": "text/plain",
|
|
}
|
|
]
|
|
ctx.emitter.send_ask_user = AsyncMock(return_value=file_response)
|
|
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
result = await msg.send()
|
|
|
|
assert result is not None
|
|
assert len(result) == 1
|
|
assert result[0].id == "file_123"
|
|
assert result[0].name == "test.txt"
|
|
assert result[0].path == "/path/to/test.txt"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_file_message_send_with_no_response(self):
|
|
"""Test AskFileMessage send with no response."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = AskFileMessage(content="Upload", accept=["text/plain"])
|
|
ctx.emitter.send_ask_user = AsyncMock(return_value=None)
|
|
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
result = await msg.send()
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestAskActionMessage:
|
|
"""Test suite for AskActionMessage class."""
|
|
|
|
def test_ask_action_message_initialization(self):
|
|
"""Test AskActionMessage initialization."""
|
|
with mock_chainlit_context():
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.ui.name = "Bot"
|
|
action1 = Mock(spec=Action)
|
|
action2 = Mock(spec=Action)
|
|
msg = AskActionMessage(
|
|
content="Choose an action", actions=[action1, action2]
|
|
)
|
|
|
|
assert msg.content == "Choose an action"
|
|
assert len(msg.actions) == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_action_message_send_with_response(self):
|
|
"""Test AskActionMessage send with action response."""
|
|
with mock_chainlit_context() as ctx:
|
|
action = AsyncMock(spec=Action)
|
|
action.id = "action_123"
|
|
msg = AskActionMessage(content="Choose", actions=[action])
|
|
ctx.emitter.send_ask_user = AsyncMock(
|
|
return_value={"id": "action_123", "label": "Confirm"}
|
|
)
|
|
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
result = await msg.send()
|
|
|
|
assert result == {"id": "action_123", "label": "Confirm"}
|
|
assert msg.content == "**Selected:** Confirm"
|
|
action.send.assert_called_once()
|
|
action.remove.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_action_message_send_timeout(self):
|
|
"""Test AskActionMessage send with timeout."""
|
|
with mock_chainlit_context() as ctx:
|
|
action = AsyncMock(spec=Action)
|
|
action.id = "action_123"
|
|
msg = AskActionMessage(content="Choose", actions=[action])
|
|
ctx.emitter.send_ask_user = AsyncMock(return_value=None)
|
|
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
result = await msg.send()
|
|
|
|
assert result is None
|
|
assert msg.content == "Timed out: no action was taken"
|
|
|
|
|
|
class TestAskElementMessage:
|
|
"""Test suite for AskElementMessage class."""
|
|
|
|
def test_ask_element_message_initialization(self):
|
|
"""Test AskElementMessage initialization."""
|
|
with mock_chainlit_context():
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.ui.name = "Bot"
|
|
element = Mock()
|
|
msg = AskElementMessage(content="Submit form", element=element)
|
|
|
|
assert msg.content == "Submit form"
|
|
assert msg.element == element
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_element_message_send_submitted(self):
|
|
"""Test AskElementMessage send with submitted response."""
|
|
with mock_chainlit_context() as ctx:
|
|
element = AsyncMock()
|
|
element.id = "element_123"
|
|
msg = AskElementMessage(content="Submit", element=element)
|
|
ctx.emitter.send_ask_user = AsyncMock(
|
|
return_value={"submitted": True, "data": {"field": "value"}}
|
|
)
|
|
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
result = await msg.send()
|
|
|
|
assert result == {"submitted": True, "data": {"field": "value"}}
|
|
assert msg.content == "Thanks for submitting"
|
|
element.send.assert_called_once()
|
|
element.remove.assert_called_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_element_message_send_cancelled(self):
|
|
"""Test AskElementMessage send with cancelled response."""
|
|
with mock_chainlit_context() as ctx:
|
|
element = AsyncMock()
|
|
element.id = "element_123"
|
|
msg = AskElementMessage(content="Submit", element=element)
|
|
ctx.emitter.send_ask_user = AsyncMock(return_value={"submitted": False})
|
|
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
result = await msg.send()
|
|
|
|
assert result == {"submitted": False}
|
|
assert msg.content == "Cancelled"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_element_message_send_timeout(self):
|
|
"""Test AskElementMessage send with timeout."""
|
|
with mock_chainlit_context() as ctx:
|
|
element = AsyncMock()
|
|
element.id = "element_123"
|
|
msg = AskElementMessage(content="Submit", element=element)
|
|
ctx.emitter.send_ask_user = AsyncMock(return_value=None)
|
|
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
result = await msg.send()
|
|
|
|
assert result is None
|
|
assert msg.content == "Timed out"
|
|
|
|
|
|
class TestMessageEdgeCases:
|
|
"""Test suite for message edge cases."""
|
|
|
|
def test_message_with_none_content(self):
|
|
"""Test message handles None content."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content=None)
|
|
assert msg.content == "None"
|
|
|
|
def test_message_language_override(self):
|
|
"""Test that dict content sets language to json."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content={"key": "value"}, language="python")
|
|
# Dict content always sets language to json, overriding the parameter
|
|
assert msg.language == "json"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_message_send_with_none_content(self):
|
|
"""Test sending message with None content."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content="test")
|
|
msg.content = None
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
with patch("chainlit.message.config") as mock_config:
|
|
mock_config.code.author_rename = None
|
|
|
|
await msg.send()
|
|
|
|
assert msg.content == ""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ask_message_remove_clears_ask(self):
|
|
"""Test that AskMessage remove clears ask state."""
|
|
with mock_chainlit_context() as ctx:
|
|
msg = AskUserMessage(content="Question?")
|
|
|
|
with patch("chainlit.message.chat_context"):
|
|
with patch("chainlit.message.get_data_layer", return_value=None):
|
|
await msg.remove()
|
|
|
|
ctx.emitter.clear.assert_called_once_with("clear_ask")
|
|
|
|
def test_message_metadata_and_tags(self):
|
|
"""Test message with metadata and tags."""
|
|
with mock_chainlit_context():
|
|
metadata = {"key1": "value1", "key2": 123}
|
|
tags = ["important", "user-query"]
|
|
msg = Message(content="test", metadata=metadata, tags=tags)
|
|
|
|
assert msg.metadata == metadata
|
|
assert msg.tags == tags
|
|
|
|
def test_message_to_dict_with_none_metadata(self):
|
|
"""Test to_dict with None metadata."""
|
|
with mock_chainlit_context():
|
|
msg = Message(content="test")
|
|
msg.metadata = None
|
|
|
|
result = msg.to_dict()
|
|
|
|
assert result["metadata"] == {}
|