From 02b269ad6d9df8eb554278c4717eb2f1454fbf3a Mon Sep 17 00:00:00 2001 From: Igor Loskutov Date: Tue, 25 Nov 2025 19:35:50 -0500 Subject: [PATCH] zombie meetings ignore (no-mistakes) --- server/reflector/worker/process.py | 14 ++++- server/tests/test_poll_daily_recordings.py | 65 ++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/server/reflector/worker/process.py b/server/reflector/worker/process.py index 0e1b4d86..be582d93 100644 --- a/server/reflector/worker/process.py +++ b/server/reflector/worker/process.py @@ -240,6 +240,8 @@ async def _process_multitrack_recording_inner( ) meeting = await meetings_controller.get_by_room_name(daily_room_name) + if not meeting: + raise Exception(f"Meeting not found: {daily_room_name}") room_name_base = extract_base_room_name(daily_room_name) @@ -247,9 +249,6 @@ async def _process_multitrack_recording_inner( if not room: raise Exception(f"Room not found: {room_name_base}") - if not meeting: - raise Exception(f"Meeting not found: {room_name_base}") - logger.info( "Found existing Meeting for recording", meeting_id=meeting.id, @@ -446,6 +445,15 @@ async def poll_daily_recordings(): ) continue + meeting = await meetings_controller.get_by_room_name(recording.room_name) + if not meeting: + logger.warning( + "Skipping recording - no matching meeting", + recording_id=recording.id, + room_name=recording.room_name, + ) + continue + logger.info( "Queueing missing recording for processing", recording_id=recording.id, diff --git a/server/tests/test_poll_daily_recordings.py b/server/tests/test_poll_daily_recordings.py index acbbfcc7..7d5f0fc0 100644 --- a/server/tests/test_poll_daily_recordings.py +++ b/server/tests/test_poll_daily_recordings.py @@ -59,17 +59,37 @@ def mock_recording_response(): ] +@pytest.fixture +def mock_meeting(): + """Mock meeting object.""" + from reflector.db.meetings import Meeting + + return Meeting( + id="meeting-123", + room_name="test-room-20251118120000", + room_url="https://daily.co/test-room", + host_room_url="https://daily.co/test-room", + start_date=datetime.now(timezone.utc), + end_date=datetime.now(timezone.utc) + timedelta(hours=1), + room_id="room-123", + platform="daily", + ) + + @pytest.mark.asyncio @patch("reflector.worker.process.settings") @patch("reflector.worker.process.create_platform_client") @patch("reflector.worker.process.recordings_controller.get_by_ids") +@patch("reflector.worker.process.meetings_controller.get_by_room_name") @patch("reflector.worker.process.process_multitrack_recording.delay") async def test_poll_daily_recordings_processes_missing_recordings( mock_process_delay, + mock_get_meeting, mock_get_recordings, mock_create_client, mock_settings, mock_recording_response, + mock_meeting, ): """Test that poll_daily_recordings queues processing for recordings not in DB.""" mock_settings.DAILYCO_STORAGE_AWS_BUCKET_NAME = "test-bucket" @@ -85,6 +105,9 @@ async def test_poll_daily_recordings_processes_missing_recordings( # Mock DB controller - no existing recordings mock_get_recordings.return_value = [] + # Mock meeting exists for all recordings + mock_get_meeting.return_value = mock_meeting + # Execute - call the unwrapped async function poll_fn = _get_poll_daily_recordings_fn() await poll_fn() @@ -113,6 +136,48 @@ async def test_poll_daily_recordings_processes_missing_recordings( assert calls[1].kwargs["track_keys"] == ["track1.webm"] +@pytest.mark.asyncio +@patch("reflector.worker.process.settings") +@patch("reflector.worker.process.create_platform_client") +@patch("reflector.worker.process.recordings_controller.get_by_ids") +@patch("reflector.worker.process.meetings_controller.get_by_room_name") +@patch("reflector.worker.process.process_multitrack_recording.delay") +async def test_poll_daily_recordings_skips_recordings_without_meeting( + mock_process_delay, + mock_get_meeting, + mock_get_recordings, + mock_create_client, + mock_settings, + mock_recording_response, +): + """Test that poll_daily_recordings skips recordings without matching meeting.""" + mock_settings.DAILYCO_STORAGE_AWS_BUCKET_NAME = "test-bucket" + + # Mock Daily.co API client + mock_daily_client = AsyncMock() + mock_daily_client.list_recordings = AsyncMock(return_value=mock_recording_response) + mock_create_client.return_value.__aenter__ = AsyncMock( + return_value=mock_daily_client + ) + mock_create_client.return_value.__aexit__ = AsyncMock() + + # Mock DB controller - no existing recordings + mock_get_recordings.return_value = [] + + # Mock no meeting found + mock_get_meeting.return_value = None + + # Execute - call the unwrapped async function + poll_fn = _get_poll_daily_recordings_fn() + await poll_fn() + + # Verify Daily.co API was called + assert mock_daily_client.list_recordings.call_count == 1 + + # Verify NO processing was queued (no matching meetings) + assert mock_process_delay.call_count == 0 + + @pytest.mark.asyncio @patch("reflector.worker.process.settings") @patch("reflector.worker.process.create_platform_client")