mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2025-12-20 20:29:06 +00:00
324 lines
12 KiB
Python
324 lines
12 KiB
Python
"""Tests for video platform clients."""
|
|
|
|
from datetime import datetime, timedelta
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from reflector.db.rooms import Room
|
|
from reflector.video_platforms.base import MeetingData, VideoPlatformConfig
|
|
from reflector.video_platforms.daily import DailyClient
|
|
from reflector.video_platforms.mock import MockPlatformClient
|
|
from reflector.video_platforms.registry import get_platform_client
|
|
from reflector.video_platforms.whereby import WherebyClient
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_room():
|
|
"""Create a mock room for testing."""
|
|
room = MagicMock(spec=Room)
|
|
room.id = "test-room-123"
|
|
room.name = "Test Room"
|
|
room.recording_type = "cloud"
|
|
room.platform = "whereby"
|
|
return room
|
|
|
|
|
|
@pytest.fixture
|
|
def config():
|
|
"""Create a test configuration."""
|
|
return VideoPlatformConfig(
|
|
api_key="test-api-key",
|
|
webhook_secret="test-webhook-secret",
|
|
subdomain="test-subdomain",
|
|
)
|
|
|
|
|
|
class TestPlatformFactory:
|
|
"""Test platform client factory."""
|
|
|
|
def test_create_whereby_client(self, config):
|
|
"""Test creating Whereby client."""
|
|
client = get_platform_client("whereby", config)
|
|
assert isinstance(client, WherebyClient)
|
|
|
|
def test_create_daily_client(self, config):
|
|
"""Test creating Daily.co client."""
|
|
client = get_platform_client("daily", config)
|
|
assert isinstance(client, DailyClient)
|
|
|
|
def test_create_mock_client(self, config):
|
|
"""Test creating mock client."""
|
|
client = get_platform_client("mock", config)
|
|
assert isinstance(client, MockPlatformClient)
|
|
|
|
def test_invalid_platform_raises_error(self, config):
|
|
"""Test that invalid platform raises ValueError."""
|
|
with pytest.raises(ValueError, match="Unknown platform: invalid"):
|
|
get_platform_client("invalid", config)
|
|
|
|
|
|
class TestMockPlatformClient:
|
|
"""Test mock platform client implementation."""
|
|
|
|
@pytest.fixture
|
|
def mock_client(self, config):
|
|
return MockPlatformClient(config)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_meeting(self, mock_client, mock_room):
|
|
"""Test creating a meeting with mock client."""
|
|
end_date = datetime.utcnow() + timedelta(hours=1)
|
|
|
|
meeting_data = await mock_client.create_meeting(
|
|
room_name_prefix="test", end_date=end_date, room=mock_room
|
|
)
|
|
|
|
assert isinstance(meeting_data, MeetingData)
|
|
assert meeting_data.room_url.startswith("https://mock.video/")
|
|
assert meeting_data.host_room_url.startswith("https://mock.video/")
|
|
assert meeting_data.room_name.startswith("test")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_room_sessions(self, mock_client):
|
|
"""Test getting room sessions."""
|
|
# First create a room so it exists
|
|
end_date = datetime.utcnow() + timedelta(hours=1)
|
|
mock_room = MagicMock()
|
|
mock_room.id = "test-room"
|
|
meeting = await mock_client.create_meeting("test", end_date, mock_room)
|
|
|
|
sessions = await mock_client.get_room_sessions(meeting.room_name)
|
|
assert isinstance(sessions, dict)
|
|
assert "sessions" in sessions
|
|
assert len(sessions["sessions"]) == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_room(self, mock_client):
|
|
"""Test deleting a room."""
|
|
# First create a room so it exists
|
|
end_date = datetime.utcnow() + timedelta(hours=1)
|
|
mock_room = MagicMock()
|
|
mock_room.id = "test-room"
|
|
meeting = await mock_client.create_meeting("test", end_date, mock_room)
|
|
|
|
result = await mock_client.delete_room(meeting.room_name)
|
|
assert result is True
|
|
|
|
def test_verify_webhook_signature_valid(self, mock_client):
|
|
"""Test webhook signature verification with valid signature."""
|
|
payload = b'{"event": "test"}'
|
|
signature = "valid" # Mock accepts "valid" as valid signature
|
|
|
|
result = mock_client.verify_webhook_signature(payload, signature)
|
|
assert result is True
|
|
|
|
def test_verify_webhook_signature_invalid(self, mock_client):
|
|
"""Test webhook signature verification with invalid signature."""
|
|
payload = b'{"event": "test"}'
|
|
signature = "invalid-signature"
|
|
|
|
result = mock_client.verify_webhook_signature(payload, signature)
|
|
assert result is False
|
|
|
|
|
|
class TestDailyClient:
|
|
"""Test Daily.co platform client."""
|
|
|
|
@pytest.fixture
|
|
def daily_client(self, config):
|
|
return DailyClient(config)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_meeting_success(self, daily_client, mock_room):
|
|
"""Test successful meeting creation."""
|
|
end_date = datetime.utcnow() + timedelta(hours=1)
|
|
|
|
mock_response = {
|
|
"url": "https://test.daily.co/test-room-123-abc",
|
|
"name": "test-room-123-abc",
|
|
"api_created": True,
|
|
"privacy": "public",
|
|
"config": {"enable_recording": "cloud"},
|
|
}
|
|
|
|
with patch.object(
|
|
daily_client, "_make_request", new_callable=AsyncMock
|
|
) as mock_request:
|
|
mock_request.return_value = mock_response
|
|
|
|
meeting_data = await daily_client.create_meeting(
|
|
room_name_prefix="test", end_date=end_date, room=mock_room
|
|
)
|
|
|
|
assert isinstance(meeting_data, MeetingData)
|
|
assert meeting_data.room_url == "https://test.daily.co/test-room-123-abc"
|
|
assert (
|
|
meeting_data.host_room_url == "https://test.daily.co/test-room-123-abc"
|
|
)
|
|
assert meeting_data.room_name == "test-room-123-abc"
|
|
|
|
# Verify request was made with correct parameters
|
|
mock_request.assert_called_once()
|
|
call_args = mock_request.call_args
|
|
assert call_args[0][0] == "POST"
|
|
assert "/rooms" in call_args[0][1]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_room_sessions_success(self, daily_client):
|
|
"""Test successful room sessions retrieval."""
|
|
mock_response = {
|
|
"data": [
|
|
{
|
|
"id": "session-1",
|
|
"room_name": "test-room",
|
|
"start_time": "2025-01-01T10:00:00Z",
|
|
"participants": [],
|
|
}
|
|
]
|
|
}
|
|
|
|
with patch.object(
|
|
daily_client, "_make_request", new_callable=AsyncMock
|
|
) as mock_request:
|
|
mock_request.return_value = mock_response
|
|
|
|
sessions = await daily_client.get_room_sessions("test-room")
|
|
|
|
assert isinstance(sessions, list)
|
|
assert len(sessions) == 1
|
|
assert sessions[0]["id"] == "session-1"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_delete_room_success(self, daily_client):
|
|
"""Test successful room deletion."""
|
|
with patch.object(
|
|
daily_client, "_make_request", new_callable=AsyncMock
|
|
) as mock_request:
|
|
mock_request.return_value = {"deleted": True}
|
|
|
|
result = await daily_client.delete_room("test-room")
|
|
|
|
assert result is True
|
|
mock_request.assert_called_once_with("DELETE", "/rooms/test-room")
|
|
|
|
def test_verify_webhook_signature_valid(self, daily_client):
|
|
"""Test webhook signature verification with valid HMAC."""
|
|
import hashlib
|
|
import hmac
|
|
|
|
payload = b'{"event": "participant.joined"}'
|
|
expected_signature = hmac.new(
|
|
daily_client.webhook_secret.encode(), payload, hashlib.sha256
|
|
).hexdigest()
|
|
|
|
result = daily_client.verify_webhook_signature(payload, expected_signature)
|
|
assert result is True
|
|
|
|
def test_verify_webhook_signature_invalid(self, daily_client):
|
|
"""Test webhook signature verification with invalid HMAC."""
|
|
payload = b'{"event": "participant.joined"}'
|
|
invalid_signature = "invalid-signature"
|
|
|
|
result = daily_client.verify_webhook_signature(payload, invalid_signature)
|
|
assert result is False
|
|
|
|
|
|
class TestWherebyClient:
|
|
"""Test Whereby platform client."""
|
|
|
|
@pytest.fixture
|
|
def whereby_client(self, config):
|
|
return WherebyClient(config)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_meeting_delegates_to_whereby_client(
|
|
self, whereby_client, mock_room
|
|
):
|
|
"""Test that create_meeting delegates to existing Whereby client."""
|
|
end_date = datetime.utcnow() + timedelta(hours=1)
|
|
|
|
mock_whereby_response = {
|
|
"roomUrl": "https://whereby.com/test-room",
|
|
"hostRoomUrl": "https://whereby.com/test-room?host",
|
|
"meetingId": "meeting-123",
|
|
}
|
|
|
|
with patch("reflector.video_platforms.whereby.whereby_client") as mock_client:
|
|
mock_client.create_meeting.return_value = mock_whereby_response
|
|
|
|
meeting_data = await whereby_client.create_meeting(
|
|
room_name_prefix="test", end_date=end_date, room=mock_room
|
|
)
|
|
|
|
assert isinstance(meeting_data, MeetingData)
|
|
assert meeting_data.room_url == "https://whereby.com/test-room"
|
|
assert meeting_data.host_room_url == "https://whereby.com/test-room?host"
|
|
assert meeting_data.meeting_id == "meeting-123"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_room_sessions_delegates_to_whereby_client(self, whereby_client):
|
|
"""Test that get_room_sessions delegates to existing Whereby client."""
|
|
mock_sessions = [{"id": "session-1"}]
|
|
|
|
with patch("reflector.video_platforms.whereby.whereby_client") as mock_client:
|
|
mock_client.get_room_sessions.return_value = mock_sessions
|
|
|
|
sessions = await whereby_client.get_room_sessions("test-room")
|
|
|
|
assert sessions == mock_sessions
|
|
|
|
def test_verify_webhook_signature_delegates_to_whereby_client(self, whereby_client):
|
|
"""Test that webhook verification delegates to existing Whereby client."""
|
|
payload = b'{"event": "test"}'
|
|
signature = "test-signature"
|
|
|
|
with patch("reflector.video_platforms.whereby.whereby_client") as mock_client:
|
|
mock_client.verify_webhook_signature.return_value = True
|
|
|
|
result = whereby_client.verify_webhook_signature(payload, signature)
|
|
|
|
assert result is True
|
|
mock_client.verify_webhook_signature.assert_called_once_with(
|
|
payload, signature
|
|
)
|
|
|
|
|
|
class TestPlatformIntegration:
|
|
"""Integration tests for platform switching."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_platform_switching_preserves_interface(self, config, mock_room):
|
|
"""Test that different platforms provide consistent interface."""
|
|
end_date = datetime.utcnow() + timedelta(hours=1)
|
|
|
|
# Test with mock platform
|
|
mock_client = get_platform_client("mock", config)
|
|
mock_meeting = await mock_client.create_meeting("test", end_date, mock_room)
|
|
|
|
# Test with Daily platform (mocked)
|
|
daily_client = get_platform_client("daily", config)
|
|
with patch.object(
|
|
daily_client, "_make_request", new_callable=AsyncMock
|
|
) as mock_request:
|
|
mock_request.return_value = {
|
|
"url": "https://test.daily.co/test-room",
|
|
"name": "test-room",
|
|
"api_created": True,
|
|
}
|
|
|
|
daily_meeting = await daily_client.create_meeting(
|
|
"test", end_date, mock_room
|
|
)
|
|
|
|
# Both should return MeetingData objects with consistent fields
|
|
assert isinstance(mock_meeting, MeetingData)
|
|
assert isinstance(daily_meeting, MeetingData)
|
|
|
|
# Both should have required fields
|
|
for meeting in [mock_meeting, daily_meeting]:
|
|
assert hasattr(meeting, "room_url")
|
|
assert hasattr(meeting, "host_room_url")
|
|
assert hasattr(meeting, "room_name")
|
|
assert meeting.room_url.startswith("https://")
|