mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-02-06 18:56:48 +00:00
132 lines
4.6 KiB
Python
132 lines
4.6 KiB
Python
"""Integration test: ICS dedup against Max's real calendar feed.
|
|
|
|
Fetches the live ICS feed, picks an upcoming event, then simulates
|
|
a duplicate (different UID, same time window) to verify dedup logic.
|
|
"""
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
from icalendar import Calendar
|
|
|
|
from reflector.db import get_database
|
|
from reflector.db.calendar_events import CalendarEvent, calendar_events_controller
|
|
from reflector.db.meetings import meetings
|
|
from reflector.db.rooms import rooms_controller
|
|
from reflector.services.ics_sync import ICSFetchService
|
|
from reflector.worker.ics_sync import create_upcoming_meetings_for_event
|
|
|
|
MAX_ICS_URL = (
|
|
"https://user.fm/calendar/v1-929f6269635c3c3bebd57c32b23bf448/Max%20Calendar.ics"
|
|
)
|
|
|
|
|
|
def _find_upcoming_event(
|
|
fetch_service: ICSFetchService, calendar: Calendar
|
|
) -> dict | None:
|
|
"""Find any upcoming event within next 48h (ignoring room matching)."""
|
|
now = datetime.now(timezone.utc)
|
|
window_end = now + timedelta(hours=48)
|
|
|
|
for component in calendar.walk():
|
|
if component.name != "VEVENT":
|
|
continue
|
|
status = component.get("STATUS", "").upper()
|
|
if status == "CANCELLED":
|
|
continue
|
|
event_data = fetch_service._parse_event(component)
|
|
if event_data and now < event_data["start_time"] < window_end:
|
|
return event_data
|
|
return None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dedup_with_real_ics_feed():
|
|
"""Fetch Max's real ICS, pick an upcoming event, inject duplicate UID, verify dedup."""
|
|
|
|
# 1. Fetch and parse real ICS feed
|
|
fetch_service = ICSFetchService()
|
|
ics_content = await fetch_service.fetch_ics(MAX_ICS_URL)
|
|
calendar = fetch_service.parse_ics(ics_content)
|
|
|
|
upcoming = _find_upcoming_event(fetch_service, calendar)
|
|
if upcoming is None:
|
|
pytest.skip("No upcoming events in Max's calendar within 48h window")
|
|
|
|
# 2. Create a test room
|
|
room = await rooms_controller.add(
|
|
name="max-dedup-integration-test",
|
|
user_id="test-user",
|
|
zulip_auto_post=False,
|
|
zulip_stream="",
|
|
zulip_topic="",
|
|
is_locked=False,
|
|
room_mode="normal",
|
|
recording_type="cloud",
|
|
recording_trigger="automatic-2nd-participant",
|
|
is_shared=False,
|
|
ics_url=MAX_ICS_URL,
|
|
ics_enabled=True,
|
|
)
|
|
|
|
start_time = upcoming["start_time"]
|
|
end_time = upcoming["end_time"]
|
|
title = upcoming["title"]
|
|
|
|
# 3. Insert two calendar events with different UIDs but same time window
|
|
event1 = await calendar_events_controller.upsert(
|
|
CalendarEvent(
|
|
room_id=room.id,
|
|
ics_uid=upcoming["ics_uid"], # real UID from the feed
|
|
title=title,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
)
|
|
)
|
|
event2 = await calendar_events_controller.upsert(
|
|
CalendarEvent(
|
|
room_id=room.id,
|
|
ics_uid=f"SYNTHETIC-DUPLICATE-{upcoming['ics_uid']}",
|
|
title=title,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
)
|
|
)
|
|
|
|
create_window = datetime.now(timezone.utc) - timedelta(minutes=6)
|
|
call_count = 0
|
|
|
|
# 4. Mock the platform client, run both events through meeting creation
|
|
with patch(
|
|
"reflector.worker.ics_sync.create_platform_client"
|
|
) as mock_platform_factory:
|
|
mock_client = AsyncMock()
|
|
|
|
async def mock_create_meeting(room_name_prefix, *, end_date, room):
|
|
nonlocal call_count
|
|
call_count += 1
|
|
return AsyncMock(
|
|
meeting_id=f"integ-meeting-{call_count}",
|
|
room_name=f"max-dedup-integration-test-{call_count}",
|
|
room_url=f"https://mock.video/max-dedup-integration-test-{call_count}",
|
|
host_room_url=f"https://mock.video/max-dedup-integration-test-{call_count}?host=true",
|
|
)
|
|
|
|
mock_client.create_meeting = mock_create_meeting
|
|
mock_client.upload_logo = AsyncMock()
|
|
mock_platform_factory.return_value = mock_client
|
|
|
|
await create_upcoming_meetings_for_event(event1, create_window, room)
|
|
await create_upcoming_meetings_for_event(event2, create_window, room)
|
|
|
|
# 5. Verify: only 1 meeting created
|
|
results = await get_database().fetch_all(
|
|
meetings.select().where(meetings.c.room_id == room.id)
|
|
)
|
|
assert len(results) == 1, (
|
|
f"Expected 1 meeting (dedup should block duplicate), got {len(results)}. "
|
|
f"Event: {title} @ {start_time} - {end_time}"
|
|
)
|
|
assert call_count == 1, f"create_meeting called {call_count} times, expected 1"
|