Files
orion/tests/integration/api/v1/admin/test_messages.py
Samir Boulahtit 0098093287 feat: enhance messaging system with improved API and tests
- Refactor messaging API endpoints for admin, shop, and vendor
- Add message-specific exceptions (ConversationNotFoundException, etc.)
- Enhance messaging service with additional helper methods
- Add comprehensive test fixtures for messaging
- Add integration tests for admin and vendor messaging APIs
- Add unit tests for messaging and attachment services

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 21:01:14 +01:00

390 lines
13 KiB
Python

# tests/integration/api/v1/admin/test_messages.py
"""
Integration tests for admin messaging endpoints.
Tests the /api/v1/admin/messages/* endpoints.
"""
import pytest
from models.database.message import ConversationType, ParticipantType
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.admin
class TestAdminMessagesListAPI:
"""Tests for admin message list endpoints."""
def test_list_conversations_empty(self, client, admin_headers):
"""Test listing conversations when none exist."""
response = client.get("/api/v1/admin/messages", headers=admin_headers)
assert response.status_code == 200
data = response.json()
assert "conversations" in data
assert "total" in data
assert "total_unread" in data
assert data["conversations"] == []
assert data["total"] == 0
def test_list_conversations_requires_auth(self, client):
"""Test that listing requires authentication."""
response = client.get("/api/v1/admin/messages")
assert response.status_code == 401
def test_list_conversations_requires_admin(self, client, auth_headers):
"""Test that listing requires admin role."""
response = client.get("/api/v1/admin/messages", headers=auth_headers)
assert response.status_code == 403
def test_list_conversations_with_data(
self, client, admin_headers, test_conversation_admin_vendor
):
"""Test listing conversations with existing data."""
response = client.get("/api/v1/admin/messages", headers=admin_headers)
assert response.status_code == 200
data = response.json()
assert data["total"] >= 1
assert len(data["conversations"]) >= 1
# Check conversation structure
conv = data["conversations"][0]
assert "id" in conv
assert "conversation_type" in conv
assert "subject" in conv
assert "is_closed" in conv
def test_list_conversations_filter_by_type(
self, client, admin_headers, test_conversation_admin_vendor
):
"""Test filtering conversations by type."""
response = client.get(
"/api/v1/admin/messages",
params={"conversation_type": "admin_vendor"},
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
for conv in data["conversations"]:
assert conv["conversation_type"] == "admin_vendor"
def test_list_conversations_filter_closed(
self, client, admin_headers, closed_conversation
):
"""Test filtering closed conversations."""
response = client.get(
"/api/v1/admin/messages",
params={"is_closed": True},
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
for conv in data["conversations"]:
assert conv["is_closed"] is True
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.admin
class TestAdminMessagesUnreadCountAPI:
"""Tests for unread count endpoint."""
def test_get_unread_count(self, client, admin_headers):
"""Test getting unread count."""
response = client.get("/api/v1/admin/messages/unread-count", headers=admin_headers)
assert response.status_code == 200
data = response.json()
assert "total_unread" in data
assert isinstance(data["total_unread"], int)
def test_get_unread_count_with_unread(
self, client, admin_headers, test_message
):
"""Test unread count with unread messages."""
response = client.get("/api/v1/admin/messages/unread-count", headers=admin_headers)
assert response.status_code == 200
data = response.json()
# The test_message is sent by admin, so no unread count for admin
assert data["total_unread"] >= 0
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.admin
class TestAdminMessagesRecipientsAPI:
"""Tests for recipients endpoint."""
def test_get_vendor_recipients(
self, client, admin_headers, test_vendor_with_vendor_user
):
"""Test getting vendor recipients."""
response = client.get(
"/api/v1/admin/messages/recipients",
params={"recipient_type": "vendor"},
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert "recipients" in data
assert "total" in data
def test_get_customer_recipients(self, client, admin_headers, test_customer):
"""Test getting customer recipients."""
response = client.get(
"/api/v1/admin/messages/recipients",
params={"recipient_type": "customer"},
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert "recipients" in data
assert "total" in data
def test_get_recipients_requires_type(self, client, admin_headers):
"""Test that recipient_type is required."""
response = client.get("/api/v1/admin/messages/recipients", headers=admin_headers)
assert response.status_code == 422 # Validation error
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.admin
class TestAdminMessagesCreateAPI:
"""Tests for conversation creation."""
def test_create_conversation_admin_vendor(
self, client, admin_headers, test_vendor_user, test_vendor_with_vendor_user
):
"""Test creating admin-vendor conversation."""
response = client.post(
"/api/v1/admin/messages",
json={
"conversation_type": "admin_vendor",
"subject": "Test Conversation",
"recipient_type": "vendor",
"recipient_id": test_vendor_user.id,
"vendor_id": test_vendor_with_vendor_user.id,
"initial_message": "Hello vendor!",
},
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["subject"] == "Test Conversation"
assert data["conversation_type"] == "admin_vendor"
assert len(data["messages"]) == 1
assert data["messages"][0]["content"] == "Hello vendor!"
def test_create_conversation_admin_customer(
self, client, admin_headers, test_customer, test_vendor
):
"""Test creating admin-customer conversation."""
response = client.post(
"/api/v1/admin/messages",
json={
"conversation_type": "admin_customer",
"subject": "Customer Support",
"recipient_type": "customer",
"recipient_id": test_customer.id,
"vendor_id": test_vendor.id,
"initial_message": "How can I help you?",
},
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["conversation_type"] == "admin_customer"
def test_create_conversation_wrong_recipient_type(
self, client, admin_headers, test_vendor_user, test_vendor
):
"""Test error when recipient type doesn't match conversation type."""
response = client.post(
"/api/v1/admin/messages",
json={
"conversation_type": "admin_vendor",
"subject": "Test",
"recipient_type": "customer", # Wrong type
"recipient_id": 1,
"vendor_id": test_vendor.id,
},
headers=admin_headers,
)
assert response.status_code == 400
def test_create_conversation_invalid_type(
self, client, admin_headers, test_vendor_user, test_vendor
):
"""Test error when admin tries to create vendor_customer conversation."""
response = client.post(
"/api/v1/admin/messages",
json={
"conversation_type": "vendor_customer",
"subject": "Test",
"recipient_type": "customer",
"recipient_id": 1,
"vendor_id": test_vendor.id,
},
headers=admin_headers,
)
assert response.status_code == 400
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.admin
class TestAdminMessagesDetailAPI:
"""Tests for conversation detail."""
def test_get_conversation_detail(
self, client, admin_headers, test_conversation_admin_vendor
):
"""Test getting conversation detail."""
response = client.get(
f"/api/v1/admin/messages/{test_conversation_admin_vendor.id}",
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["id"] == test_conversation_admin_vendor.id
assert "participants" in data
assert "messages" in data
def test_get_conversation_not_found(self, client, admin_headers):
"""Test getting nonexistent conversation."""
response = client.get("/api/v1/admin/messages/99999", headers=admin_headers)
assert response.status_code == 404
def test_get_conversation_marks_read(
self, client, admin_headers, test_conversation_admin_vendor
):
"""Test that getting detail marks as read by default."""
response = client.get(
f"/api/v1/admin/messages/{test_conversation_admin_vendor.id}",
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["unread_count"] == 0
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.admin
class TestAdminMessagesSendAPI:
"""Tests for sending messages."""
def test_send_message(self, client, admin_headers, test_conversation_admin_vendor):
"""Test sending a message."""
response = client.post(
f"/api/v1/admin/messages/{test_conversation_admin_vendor.id}/messages",
data={"content": "Test message content"},
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["content"] == "Test message content"
assert data["sender_type"] == "admin"
def test_send_message_to_closed(self, client, admin_headers, closed_conversation):
"""Test cannot send to closed conversation."""
response = client.post(
f"/api/v1/admin/messages/{closed_conversation.id}/messages",
data={"content": "Test message"},
headers=admin_headers,
)
assert response.status_code == 400
def test_send_message_not_found(self, client, admin_headers):
"""Test sending to nonexistent conversation."""
response = client.post(
"/api/v1/admin/messages/99999/messages",
data={"content": "Test message"},
headers=admin_headers,
)
assert response.status_code == 404
@pytest.mark.integration
@pytest.mark.api
@pytest.mark.admin
class TestAdminMessagesActionsAPI:
"""Tests for conversation actions."""
def test_close_conversation(
self, client, admin_headers, test_conversation_admin_vendor
):
"""Test closing a conversation."""
response = client.post(
f"/api/v1/admin/messages/{test_conversation_admin_vendor.id}/close",
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert "closed" in data["message"].lower()
def test_close_conversation_not_found(self, client, admin_headers):
"""Test closing nonexistent conversation."""
response = client.post(
"/api/v1/admin/messages/99999/close",
headers=admin_headers,
)
assert response.status_code == 404
def test_reopen_conversation(self, client, admin_headers, closed_conversation):
"""Test reopening a closed conversation."""
response = client.post(
f"/api/v1/admin/messages/{closed_conversation.id}/reopen",
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert "reopen" in data["message"].lower()
def test_mark_read(self, client, admin_headers, test_conversation_admin_vendor):
"""Test marking conversation as read."""
response = client.put(
f"/api/v1/admin/messages/{test_conversation_admin_vendor.id}/read",
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["unread_count"] == 0
def test_update_preferences(
self, client, admin_headers, test_conversation_admin_vendor
):
"""Test updating notification preferences."""
response = client.put(
f"/api/v1/admin/messages/{test_conversation_admin_vendor.id}/preferences",
json={"email_notifications": False, "muted": True},
headers=admin_headers,
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True