- Полный набор тестов для всех модулей API - test_auth.py: аутентификация и JWT токены - test_members.py: CRUD участников, агенты, токены - test_projects.py: CRUD проектов, участники проектов - test_tasks.py: CRUD задач, этапы, назначения, зависимости - test_chat.py: сообщения, комментарии, mentions - test_files.py: upload/download файлов проектов - test_labels.py: CRUD лейблов, привязка к задачам - test_websocket.py: WebSocket подключения и события - test_streaming.py: агентный стриминг через WebSocket - conftest.py: фикстуры для подключения к API - requirements.txt: зависимости pytest, httpx, websockets - pytest.ini: настройки asyncio для pytest
315 lines
11 KiB
Python
315 lines
11 KiB
Python
"""
|
||
Тесты работы с чатами и сообщениями - отправка, mentions, история
|
||
"""
|
||
import pytest
|
||
import httpx
|
||
import uuid
|
||
from conftest import assert_uuid, assert_timestamp
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_project_messages(http_client: httpx.AsyncClient, test_project: dict):
|
||
"""Test getting messages from project chat"""
|
||
chat_id = test_project["chat_id"]
|
||
|
||
response = await http_client.get(f"/messages?chat_id={chat_id}")
|
||
assert response.status_code == 200
|
||
|
||
messages = response.json()
|
||
assert isinstance(messages, list)
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_task_comments(http_client: httpx.AsyncClient, test_task: dict):
|
||
"""Test getting comments for specific task"""
|
||
response = await http_client.get(f"/messages?task_id={test_task['id']}")
|
||
assert response.status_code == 200
|
||
|
||
messages = response.json()
|
||
assert isinstance(messages, list)
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_send_message_to_project_chat(http_client: httpx.AsyncClient, test_project: dict):
|
||
"""Test sending message to project chat"""
|
||
chat_id = test_project["chat_id"]
|
||
message_data = {
|
||
"chat_id": chat_id,
|
||
"content": "Hello from test! This is a test message."
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=message_data)
|
||
assert response.status_code == 201
|
||
|
||
message = response.json()
|
||
assert message["content"] == message_data["content"]
|
||
assert message["chat_id"] == chat_id
|
||
assert message["task_id"] is None
|
||
assert message["parent_id"] is None
|
||
assert message["author_type"] in ["human", "agent"]
|
||
assert message["author"] is not None
|
||
assert_uuid(message["id"])
|
||
assert_uuid(message["author_id"])
|
||
assert_timestamp(message["created_at"])
|
||
|
||
# Проверяем структуру автора
|
||
assert "id" in message["author"]
|
||
assert "slug" in message["author"]
|
||
assert "name" in message["author"]
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_send_comment_to_task(http_client: httpx.AsyncClient, test_task: dict):
|
||
"""Test sending comment to task"""
|
||
message_data = {
|
||
"task_id": test_task["id"],
|
||
"content": "This is a comment on the task."
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=message_data)
|
||
assert response.status_code == 201
|
||
|
||
message = response.json()
|
||
assert message["content"] == message_data["content"]
|
||
assert message["chat_id"] is None
|
||
assert message["task_id"] == test_task["id"]
|
||
assert message["parent_id"] is None
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_send_message_with_mentions(http_client: httpx.AsyncClient, test_project: dict, test_user: dict):
|
||
"""Test sending message with user mentions"""
|
||
chat_id = test_project["chat_id"]
|
||
message_data = {
|
||
"chat_id": chat_id,
|
||
"content": f"Hey @{test_user['slug']}, check this out!",
|
||
"mentions": [test_user["id"]]
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=message_data)
|
||
assert response.status_code == 201
|
||
|
||
message = response.json()
|
||
assert len(message["mentions"]) == 1
|
||
assert message["mentions"][0]["id"] == test_user["id"]
|
||
assert message["mentions"][0]["slug"] == test_user["slug"]
|
||
assert message["mentions"][0]["name"] == test_user["name"]
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_send_agent_message_with_thinking(agent_client: httpx.AsyncClient, test_project: dict):
|
||
"""Test agent sending message with thinking content"""
|
||
chat_id = test_project["chat_id"]
|
||
message_data = {
|
||
"chat_id": chat_id,
|
||
"content": "I think this is the best approach.",
|
||
"thinking": "Let me analyze this problem step by step. First, I need to consider..."
|
||
}
|
||
|
||
response = await agent_client.post("/messages", json=message_data)
|
||
assert response.status_code == 201
|
||
|
||
message = response.json()
|
||
assert message["content"] == message_data["content"]
|
||
assert message["thinking"] == message_data["thinking"]
|
||
assert message["author_type"] == "agent"
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_send_reply_in_thread(http_client: httpx.AsyncClient, test_project: dict):
|
||
"""Test sending reply to existing message (thread)"""
|
||
chat_id = test_project["chat_id"]
|
||
|
||
# Отправляем основное сообщение
|
||
original_data = {
|
||
"chat_id": chat_id,
|
||
"content": "Original message"
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=original_data)
|
||
assert response.status_code == 201
|
||
original_message = response.json()
|
||
|
||
# Отправляем ответ в тред
|
||
reply_data = {
|
||
"chat_id": chat_id,
|
||
"parent_id": original_message["id"],
|
||
"content": "This is a reply in thread"
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=reply_data)
|
||
assert response.status_code == 201
|
||
|
||
reply = response.json()
|
||
assert reply["parent_id"] == original_message["id"]
|
||
assert reply["content"] == reply_data["content"]
|
||
assert reply["chat_id"] == chat_id
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_thread_replies(http_client: httpx.AsyncClient, test_project: dict):
|
||
"""Test getting replies in thread"""
|
||
chat_id = test_project["chat_id"]
|
||
|
||
# Отправляем основное сообщение
|
||
original_data = {
|
||
"chat_id": chat_id,
|
||
"content": "Message with replies"
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=original_data)
|
||
assert response.status_code == 201
|
||
original_message = response.json()
|
||
|
||
# Отправляем несколько ответов
|
||
for i in range(3):
|
||
reply_data = {
|
||
"chat_id": chat_id,
|
||
"parent_id": original_message["id"],
|
||
"content": f"Reply {i+1}"
|
||
}
|
||
await http_client.post("/messages", json=reply_data)
|
||
|
||
# Получаем ответы в треде
|
||
response = await http_client.get(f"/messages/{original_message['id']}/replies")
|
||
assert response.status_code == 200
|
||
|
||
replies = response.json()
|
||
assert len(replies) == 3
|
||
for reply in replies:
|
||
assert reply["parent_id"] == original_message["id"]
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_send_message_without_chat_or_task_fails(http_client: httpx.AsyncClient):
|
||
"""Test that message without chat_id or task_id returns 400"""
|
||
message_data = {
|
||
"content": "Message without destination"
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=message_data)
|
||
assert response.status_code == 400
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_send_message_to_nonexistent_chat_fails(http_client: httpx.AsyncClient):
|
||
"""Test sending message to non-existent chat"""
|
||
fake_chat_id = str(uuid.uuid4())
|
||
message_data = {
|
||
"chat_id": fake_chat_id,
|
||
"content": "Message to nowhere"
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=message_data)
|
||
assert response.status_code == 404
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_send_message_to_nonexistent_task_fails(http_client: httpx.AsyncClient):
|
||
"""Test sending message to non-existent task"""
|
||
fake_task_id = str(uuid.uuid4())
|
||
message_data = {
|
||
"task_id": fake_task_id,
|
||
"content": "Comment on nowhere"
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=message_data)
|
||
assert response.status_code == 404
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_messages_with_pagination(http_client: httpx.AsyncClient, test_project: dict):
|
||
"""Test getting messages with limit and offset"""
|
||
chat_id = test_project["chat_id"]
|
||
|
||
# Отправляем несколько сообщений
|
||
for i in range(5):
|
||
message_data = {
|
||
"chat_id": chat_id,
|
||
"content": f"Test message {i+1}"
|
||
}
|
||
await http_client.post("/messages", json=message_data)
|
||
|
||
# Получаем с лимитом
|
||
response = await http_client.get(f"/messages?chat_id={chat_id}&limit=2")
|
||
assert response.status_code == 200
|
||
|
||
messages = response.json()
|
||
assert len(messages) <= 2
|
||
|
||
# Получаем с отступом
|
||
response = await http_client.get(f"/messages?chat_id={chat_id}&limit=2&offset=2")
|
||
assert response.status_code == 200
|
||
|
||
offset_messages = response.json()
|
||
assert len(offset_messages) <= 2
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_get_messages_with_parent_filter(http_client: httpx.AsyncClient, test_project: dict):
|
||
"""Test getting messages filtered by parent_id"""
|
||
chat_id = test_project["chat_id"]
|
||
|
||
# Отправляем основное сообщение
|
||
original_data = {
|
||
"chat_id": chat_id,
|
||
"content": "Parent message for filter test"
|
||
}
|
||
|
||
response = await http_client.post("/messages", json=original_data)
|
||
assert response.status_code == 201
|
||
parent_message = response.json()
|
||
|
||
# Отправляем ответ
|
||
reply_data = {
|
||
"chat_id": chat_id,
|
||
"parent_id": parent_message["id"],
|
||
"content": "Child reply"
|
||
}
|
||
|
||
await http_client.post("/messages", json=reply_data)
|
||
|
||
# Фильтруем по parent_id
|
||
response = await http_client.get(f"/messages?parent_id={parent_message['id']}")
|
||
assert response.status_code == 200
|
||
|
||
messages = response.json()
|
||
for message in messages:
|
||
assert message["parent_id"] == parent_message["id"]
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_message_order_chronological(http_client: httpx.AsyncClient, test_project: dict):
|
||
"""Test that messages are returned in chronological order"""
|
||
chat_id = test_project["chat_id"]
|
||
|
||
# Отправляем сообщения с задержкой
|
||
import asyncio
|
||
|
||
messages_sent = []
|
||
for i in range(3):
|
||
message_data = {
|
||
"chat_id": chat_id,
|
||
"content": f"Ordered message {i+1}"
|
||
}
|
||
response = await http_client.post("/messages", json=message_data)
|
||
assert response.status_code == 201
|
||
messages_sent.append(response.json())
|
||
await asyncio.sleep(0.1) # Небольшая задержка
|
||
|
||
# Получаем сообщения
|
||
response = await http_client.get(f"/messages?chat_id={chat_id}&limit=10")
|
||
assert response.status_code == 200
|
||
|
||
messages = response.json()
|
||
|
||
# Фильтруем только наши тестовые сообщения
|
||
test_messages = [m for m in messages if "Ordered message" in m["content"]]
|
||
|
||
# Проверяем хронологический порядок
|
||
if len(test_messages) >= 2:
|
||
for i in range(len(test_messages) - 1):
|
||
current_time = test_messages[i]["created_at"]
|
||
next_time = test_messages[i + 1]["created_at"]
|
||
# Более поздние сообщения должны идти раньше (DESC order)
|
||
assert next_time >= current_time |