Files
reflector/server/tests/test_video_platforms.py

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://")