diff --git a/server/migrations/env.py b/server/migrations/env.py index 0c532787..649c06a5 100644 --- a/server/migrations/env.py +++ b/server/migrations/env.py @@ -25,7 +25,8 @@ target_metadata = metadata # ... etc. -# No need to modify URL, using sync engine from db module +# don't use asyncpg for the moment +settings.DATABASE_URL = settings.DATABASE_URL.replace("+asyncpg", "") def run_migrations_offline() -> None: diff --git a/server/migrations/versions/38a927dcb099_rename_back_text_to_transcript.py b/server/migrations/versions/38a927dcb099_rename_back_text_to_transcript.py index 4313ce23..a644ac69 100644 --- a/server/migrations/versions/38a927dcb099_rename_back_text_to_transcript.py +++ b/server/migrations/versions/38a927dcb099_rename_back_text_to_transcript.py @@ -28,7 +28,7 @@ def upgrade() -> None: transcript = table("transcript", column("id", sa.String), column("topics", sa.JSON)) # Select all rows from the transcript table - results = bind.execute(select([transcript.c.id, transcript.c.topics])) + results = bind.execute(select(transcript.c.id, transcript.c.topics)) for row in results: transcript_id = row["id"] @@ -58,7 +58,7 @@ def downgrade() -> None: transcript = table("transcript", column("id", sa.String), column("topics", sa.JSON)) # Select all rows from the transcript table - results = bind.execute(select([transcript.c.id, transcript.c.topics])) + results = bind.execute(select(transcript.c.id, transcript.c.topics)) for row in results: transcript_id = row["id"] diff --git a/server/migrations/versions/4814901632bc_fix_duration.py b/server/migrations/versions/4814901632bc_fix_duration.py index 2028f977..6fd13bb5 100644 --- a/server/migrations/versions/4814901632bc_fix_duration.py +++ b/server/migrations/versions/4814901632bc_fix_duration.py @@ -36,9 +36,7 @@ def upgrade() -> None: # select only the one with duration = 0 results = bind.execute( - select([transcript.c.id, transcript.c.duration]).where( - transcript.c.duration == 0 - ) + select(transcript.c.id, transcript.c.duration).where(transcript.c.duration == 0) ) data_dir = Path(settings.DATA_DIR) diff --git a/server/migrations/versions/9920ecfe2735_rename_transcript_to_text.py b/server/migrations/versions/9920ecfe2735_rename_transcript_to_text.py index 7215c000..d591bdb5 100644 --- a/server/migrations/versions/9920ecfe2735_rename_transcript_to_text.py +++ b/server/migrations/versions/9920ecfe2735_rename_transcript_to_text.py @@ -28,7 +28,7 @@ def upgrade() -> None: transcript = table("transcript", column("id", sa.String), column("topics", sa.JSON)) # Select all rows from the transcript table - results = bind.execute(select([transcript.c.id, transcript.c.topics])) + results = bind.execute(select(transcript.c.id, transcript.c.topics)) for row in results: transcript_id = row["id"] @@ -58,7 +58,7 @@ def downgrade() -> None: transcript = table("transcript", column("id", sa.String), column("topics", sa.JSON)) # Select all rows from the transcript table - results = bind.execute(select([transcript.c.id, transcript.c.topics])) + results = bind.execute(select(transcript.c.id, transcript.c.topics)) for row in results: transcript_id = row["id"] diff --git a/server/reflector/db/rooms.py b/server/reflector/db/rooms.py index 05e6458d..5413b3a7 100644 --- a/server/reflector/db/rooms.py +++ b/server/reflector/db/rooms.py @@ -54,14 +54,14 @@ class RoomController: Parameters: - `order_by`: field to order by, e.g. "-created_at" """ - query = rooms.select() + query = select(RoomModel) if user_id is not None: query = query.where(or_(RoomModel.user_id == user_id, RoomModel.is_shared)) else: query = query.where(RoomModel.is_shared) if order_by is not None: - field = getattr(rooms.c, order_by[1:]) + field = getattr(RoomModel, order_by[1:]) if order_by.startswith("-"): field = field.desc() query = query.order_by(field) @@ -131,7 +131,7 @@ class RoomController: if values.get("webhook_url") and not values.get("webhook_secret"): values["webhook_secret"] = secrets.token_urlsafe(32) - query = update(rooms).where(RoomModel.id == room.id).values(**values) + query = update(RoomModel).where(RoomModel.id == room.id).values(**values) try: await session.execute(query) await session.commit() @@ -148,7 +148,7 @@ class RoomController: """ Get a room by id """ - query = select(rooms).where(RoomModel.id == room_id) + query = select(RoomModel).where(RoomModel.id == room_id) if "user_id" in kwargs: query = query.where(RoomModel.user_id == kwargs["user_id"]) result = await session.execute(query) @@ -163,7 +163,7 @@ class RoomController: """ Get a room by name """ - query = select(rooms).where(RoomModel.name == room_name) + query = select(RoomModel).where(RoomModel.name == room_name) if "user_id" in kwargs: query = query.where(RoomModel.user_id == kwargs["user_id"]) result = await session.execute(query) @@ -180,7 +180,7 @@ class RoomController: If not found, it will raise a 404 error. """ - query = select(rooms).where(RoomModel.id == meeting_id) + query = select(RoomModel).where(RoomModel.id == meeting_id) result = await session.execute(query) row = result.mappings().first() if not row: @@ -191,7 +191,7 @@ class RoomController: return room async def get_ics_enabled(self, session: AsyncSession) -> list[Room]: - query = select(rooms).where( + query = select(RoomModel).where( RoomModel.ics_enabled == True, RoomModel.ics_url != None ) result = await session.execute(query) @@ -212,7 +212,7 @@ class RoomController: return if user_id is not None and room.user_id != user_id: return - query = delete(rooms).where(RoomModel.id == room_id) + query = delete(RoomModel).where(RoomModel.id == room_id) await session.execute(query) await session.commit() diff --git a/server/reflector/services/ics_sync.py b/server/reflector/services/ics_sync.py index 2a4855cb..d9f3029c 100644 --- a/server/reflector/services/ics_sync.py +++ b/server/reflector/services/ics_sync.py @@ -56,6 +56,7 @@ import pytz import structlog from icalendar import Calendar, Event +from reflector.db import get_session_factory from reflector.db.calendar_events import CalendarEvent, calendar_events_controller from reflector.db.rooms import Room, rooms_controller from reflector.redis_cache import RedisAsyncLock @@ -343,14 +344,17 @@ class ICSSyncService: sync_result = await self._sync_events_to_database(room.id, events) # Update room sync metadata - await rooms_controller.update( - room, - { - "ics_last_sync": datetime.now(timezone.utc), - "ics_last_etag": content_hash, - }, - mutate=False, - ) + session_factory = get_session_factory() + async with session_factory() as session: + await rooms_controller.update( + session, + room, + { + "ics_last_sync": datetime.now(timezone.utc), + "ics_last_etag": content_hash, + }, + mutate=False, + ) return { "status": SyncStatus.SUCCESS, @@ -379,25 +383,27 @@ class ICSSyncService: current_ics_uids = [] - for event_data in events: - calendar_event = CalendarEvent(room_id=room_id, **event_data) - existing = await calendar_events_controller.get_by_ics_uid( - room_id, event_data["ics_uid"] + session_factory = get_session_factory() + async with session_factory() as session: + for event_data in events: + calendar_event = CalendarEvent(room_id=room_id, **event_data) + existing = await calendar_events_controller.get_by_ics_uid( + session, room_id, event_data["ics_uid"] + ) + + if existing: + updated += 1 + else: + created += 1 + + await calendar_events_controller.upsert(session, calendar_event) + current_ics_uids.append(event_data["ics_uid"]) + + # Soft delete events that are no longer in calendar + deleted = await calendar_events_controller.soft_delete_missing( + session, room_id, current_ics_uids ) - if existing: - updated += 1 - else: - created += 1 - - await calendar_events_controller.upsert(calendar_event) - current_ics_uids.append(event_data["ics_uid"]) - - # Soft delete events that are no longer in calendar - deleted = await calendar_events_controller.soft_delete_missing( - room_id, current_ics_uids - ) - return { "events_created": created, "events_updated": updated, diff --git a/server/tests/test_attendee_parsing_bug.py b/server/tests/test_attendee_parsing_bug.py index 4c00671e..f50064d1 100644 --- a/server/tests/test_attendee_parsing_bug.py +++ b/server/tests/test_attendee_parsing_bug.py @@ -102,9 +102,14 @@ async def test_attendee_parsing_bug(): for i, attendee in enumerate(attendees): print(f"Attendee {i}: {attendee}") - # The bug would cause 29 attendees (length of "MAILIN01234567890@allo.coop") - # instead of 1 attendee - assert len(attendees) == 1, f"Expected 1 attendee, got {len(attendees)}" + # The comma-separated attendees should be parsed as individual attendees + # We expect 29 attendees from the comma-separated list + 1 organizer = 30 total + assert len(attendees) == 30, f"Expected 30 attendees, got {len(attendees)}" - # Verify the single attendee has correct email - assert attendees[0]["email"] == "MAILIN01234567890@allo.coop" + # Verify the attendees have correct email addresses (not single characters) + # Check that the first few attendees match what's in the ICS file + assert attendees[0]["email"] == "alice@example.com" + assert attendees[1]["email"] == "bob@example.com" + assert attendees[2]["email"] == "charlie@example.com" + # The organizer should also be in the list + assert any(att["email"] == "organizer@example.com" for att in attendees) diff --git a/server/tests/test_calendar_event.py b/server/tests/test_calendar_event.py index ece5f56a..632302e7 100644 --- a/server/tests/test_calendar_event.py +++ b/server/tests/test_calendar_event.py @@ -6,6 +6,7 @@ from datetime import datetime, timedelta, timezone import pytest +from reflector.db import get_session_factory from reflector.db.calendar_events import CalendarEvent, calendar_events_controller from reflector.db.rooms import rooms_controller @@ -13,412 +14,442 @@ from reflector.db.rooms import rooms_controller @pytest.mark.asyncio async def test_calendar_event_create(): """Test creating a calendar event.""" - # Create a room first - room = await rooms_controller.add( - name="test-room", - 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, - ) + session_factory = get_session_factory() + async with session_factory() as session: + # Create a room first + room = await rooms_controller.add( + session, + name="test-room", + 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, + ) - # Create calendar event - now = datetime.now(timezone.utc) - event = CalendarEvent( - room_id=room.id, - ics_uid="test-event-123", - title="Team Meeting", - description="Weekly team sync", - start_time=now + timedelta(hours=1), - end_time=now + timedelta(hours=2), - location=f"https://example.com/{room.name}", - attendees=[ - {"email": "alice@example.com", "name": "Alice", "status": "ACCEPTED"}, - {"email": "bob@example.com", "name": "Bob", "status": "TENTATIVE"}, - ], - ) + # Create calendar event + now = datetime.now(timezone.utc) + event = CalendarEvent( + room_id=room.id, + ics_uid="test-event-123", + title="Team Meeting", + description="Weekly team sync", + start_time=now + timedelta(hours=1), + end_time=now + timedelta(hours=2), + location=f"https://example.com/{room.name}", + attendees=[ + {"email": "alice@example.com", "name": "Alice", "status": "ACCEPTED"}, + {"email": "bob@example.com", "name": "Bob", "status": "TENTATIVE"}, + ], + ) - # Save event - saved_event = await calendar_events_controller.upsert(event) + # Save event + saved_event = await calendar_events_controller.upsert(session, event) - assert saved_event.ics_uid == "test-event-123" - assert saved_event.title == "Team Meeting" - assert saved_event.room_id == room.id - assert len(saved_event.attendees) == 2 + assert saved_event.ics_uid == "test-event-123" + assert saved_event.title == "Team Meeting" + assert saved_event.room_id == room.id + assert len(saved_event.attendees) == 2 @pytest.mark.asyncio async def test_calendar_event_get_by_room(): """Test getting calendar events for a room.""" - # Create room - room = await rooms_controller.add( - name="events-room", - 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, - ) - - now = datetime.now(timezone.utc) - - # Create multiple events - for i in range(3): - event = CalendarEvent( - room_id=room.id, - ics_uid=f"event-{i}", - title=f"Meeting {i}", - start_time=now + timedelta(hours=i), - end_time=now + timedelta(hours=i + 1), + session_factory = get_session_factory() + async with session_factory() as session: + # Create room + room = await rooms_controller.add( + session, + name="events-room", + 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, ) - await calendar_events_controller.upsert(event) - # Get events for room - events = await calendar_events_controller.get_by_room(room.id) + now = datetime.now(timezone.utc) - assert len(events) == 3 - assert all(e.room_id == room.id for e in events) - assert events[0].title == "Meeting 0" - assert events[1].title == "Meeting 1" - assert events[2].title == "Meeting 2" + # Create multiple events + for i in range(3): + event = CalendarEvent( + room_id=room.id, + ics_uid=f"event-{i}", + title=f"Meeting {i}", + start_time=now + timedelta(hours=i), + end_time=now + timedelta(hours=i + 1), + ) + await calendar_events_controller.upsert(session, event) + + # Get events for room + events = await calendar_events_controller.get_by_room(session, room.id) + + assert len(events) == 3 + assert all(e.room_id == room.id for e in events) + assert events[0].title == "Meeting 0" + assert events[1].title == "Meeting 1" + assert events[2].title == "Meeting 2" @pytest.mark.asyncio async def test_calendar_event_get_upcoming(): """Test getting upcoming events within time window.""" - # Create room - room = await rooms_controller.add( - name="upcoming-room", - 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, - ) + session_factory = get_session_factory() + async with session_factory() as session: + # Create room + room = await rooms_controller.add( + session, + name="upcoming-room", + 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, + ) - now = datetime.now(timezone.utc) + now = datetime.now(timezone.utc) - # Create events at different times - # Past event (should not be included) - past_event = CalendarEvent( - room_id=room.id, - ics_uid="past-event", - title="Past Meeting", - start_time=now - timedelta(hours=2), - end_time=now - timedelta(hours=1), - ) - await calendar_events_controller.upsert(past_event) + # Create events at different times + # Past event (should not be included) + past_event = CalendarEvent( + room_id=room.id, + ics_uid="past-event", + title="Past Meeting", + start_time=now - timedelta(hours=2), + end_time=now - timedelta(hours=1), + ) + await calendar_events_controller.upsert(session, past_event) - # Upcoming event within 30 minutes - upcoming_event = CalendarEvent( - room_id=room.id, - ics_uid="upcoming-event", - title="Upcoming Meeting", - start_time=now + timedelta(minutes=15), - end_time=now + timedelta(minutes=45), - ) - await calendar_events_controller.upsert(upcoming_event) + # Upcoming event within 30 minutes + upcoming_event = CalendarEvent( + room_id=room.id, + ics_uid="upcoming-event", + title="Upcoming Meeting", + start_time=now + timedelta(minutes=15), + end_time=now + timedelta(minutes=45), + ) + await calendar_events_controller.upsert(session, upcoming_event) - # Currently happening event (started 10 minutes ago, ends in 20 minutes) - current_event = CalendarEvent( - room_id=room.id, - ics_uid="current-event", - title="Current Meeting", - start_time=now - timedelta(minutes=10), - end_time=now + timedelta(minutes=20), - ) - await calendar_events_controller.upsert(current_event) + # Currently happening event (started 10 minutes ago, ends in 20 minutes) + current_event = CalendarEvent( + room_id=room.id, + ics_uid="current-event", + title="Current Meeting", + start_time=now - timedelta(minutes=10), + end_time=now + timedelta(minutes=20), + ) + await calendar_events_controller.upsert(session, current_event) - # Future event beyond 30 minutes - future_event = CalendarEvent( - room_id=room.id, - ics_uid="future-event", - title="Future Meeting", - start_time=now + timedelta(hours=2), - end_time=now + timedelta(hours=3), - ) - await calendar_events_controller.upsert(future_event) + # Future event beyond 30 minutes + future_event = CalendarEvent( + room_id=room.id, + ics_uid="future-event", + title="Future Meeting", + start_time=now + timedelta(hours=2), + end_time=now + timedelta(hours=3), + ) + await calendar_events_controller.upsert(session, future_event) - # Get upcoming events (default 120 minutes) - should include current, upcoming, and future - upcoming = await calendar_events_controller.get_upcoming(room.id) + # Get upcoming events (default 120 minutes) - should include current, upcoming, and future + upcoming = await calendar_events_controller.get_upcoming(session, room.id) - assert len(upcoming) == 3 - # Events should be sorted by start_time (current event first, then upcoming, then future) - assert upcoming[0].ics_uid == "current-event" - assert upcoming[1].ics_uid == "upcoming-event" - assert upcoming[2].ics_uid == "future-event" + assert len(upcoming) == 3 + # Events should be sorted by start_time (current event first, then upcoming, then future) + assert upcoming[0].ics_uid == "current-event" + assert upcoming[1].ics_uid == "upcoming-event" + assert upcoming[2].ics_uid == "future-event" - # Get upcoming with custom window - upcoming_extended = await calendar_events_controller.get_upcoming( - room.id, minutes_ahead=180 - ) + # Get upcoming with custom window + upcoming_extended = await calendar_events_controller.get_upcoming( + session, room.id, minutes_ahead=180 + ) - assert len(upcoming_extended) == 3 - # Events should be sorted by start_time - assert upcoming_extended[0].ics_uid == "current-event" - assert upcoming_extended[1].ics_uid == "upcoming-event" - assert upcoming_extended[2].ics_uid == "future-event" + assert len(upcoming_extended) == 3 + # Events should be sorted by start_time + assert upcoming_extended[0].ics_uid == "current-event" + assert upcoming_extended[1].ics_uid == "upcoming-event" + assert upcoming_extended[2].ics_uid == "future-event" @pytest.mark.asyncio async def test_calendar_event_get_upcoming_includes_currently_happening(): """Test that get_upcoming includes currently happening events but excludes ended events.""" - # Create room - room = await rooms_controller.add( - name="current-happening-room", - 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, - ) + session_factory = get_session_factory() + async with session_factory() as session: + # Create room + room = await rooms_controller.add( + session, + name="current-happening-room", + 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, + ) - now = datetime.now(timezone.utc) + now = datetime.now(timezone.utc) - # Event that ended in the past (should NOT be included) - past_ended_event = CalendarEvent( - room_id=room.id, - ics_uid="past-ended-event", - title="Past Ended Meeting", - start_time=now - timedelta(hours=2), - end_time=now - timedelta(minutes=30), - ) - await calendar_events_controller.upsert(past_ended_event) + # Event that ended in the past (should NOT be included) + past_ended_event = CalendarEvent( + room_id=room.id, + ics_uid="past-ended-event", + title="Past Ended Meeting", + start_time=now - timedelta(hours=2), + end_time=now - timedelta(minutes=30), + ) + await calendar_events_controller.upsert(session, past_ended_event) - # Event currently happening (started 10 minutes ago, ends in 20 minutes) - SHOULD be included - currently_happening_event = CalendarEvent( - room_id=room.id, - ics_uid="currently-happening", - title="Currently Happening Meeting", - start_time=now - timedelta(minutes=10), - end_time=now + timedelta(minutes=20), - ) - await calendar_events_controller.upsert(currently_happening_event) + # Event currently happening (started 10 minutes ago, ends in 20 minutes) - SHOULD be included + currently_happening_event = CalendarEvent( + room_id=room.id, + ics_uid="currently-happening", + title="Currently Happening Meeting", + start_time=now - timedelta(minutes=10), + end_time=now + timedelta(minutes=20), + ) + await calendar_events_controller.upsert(session, currently_happening_event) - # Event starting soon (in 5 minutes) - SHOULD be included - upcoming_soon_event = CalendarEvent( - room_id=room.id, - ics_uid="upcoming-soon", - title="Upcoming Soon Meeting", - start_time=now + timedelta(minutes=5), - end_time=now + timedelta(minutes=35), - ) - await calendar_events_controller.upsert(upcoming_soon_event) + # Event starting soon (in 5 minutes) - SHOULD be included + upcoming_soon_event = CalendarEvent( + room_id=room.id, + ics_uid="upcoming-soon", + title="Upcoming Soon Meeting", + start_time=now + timedelta(minutes=5), + end_time=now + timedelta(minutes=35), + ) + await calendar_events_controller.upsert(session, upcoming_soon_event) - # Get upcoming events - upcoming = await calendar_events_controller.get_upcoming(room.id, minutes_ahead=30) + # Get upcoming events + upcoming = await calendar_events_controller.get_upcoming( + session, room.id, minutes_ahead=30 + ) - # Should only include currently happening and upcoming soon events - assert len(upcoming) == 2 - assert upcoming[0].ics_uid == "currently-happening" - assert upcoming[1].ics_uid == "upcoming-soon" + # Should only include currently happening and upcoming soon events + assert len(upcoming) == 2 + assert upcoming[0].ics_uid == "currently-happening" + assert upcoming[1].ics_uid == "upcoming-soon" @pytest.mark.asyncio async def test_calendar_event_upsert(): """Test upserting (create/update) calendar events.""" - # Create room - room = await rooms_controller.add( - name="upsert-room", - 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, - ) + session_factory = get_session_factory() + async with session_factory() as session: + # Create room + room = await rooms_controller.add( + session, + name="upsert-room", + 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, + ) - now = datetime.now(timezone.utc) + now = datetime.now(timezone.utc) - # Create new event - event = CalendarEvent( - room_id=room.id, - ics_uid="upsert-test", - title="Original Title", - start_time=now, - end_time=now + timedelta(hours=1), - ) + # Create new event + event = CalendarEvent( + room_id=room.id, + ics_uid="upsert-test", + title="Original Title", + start_time=now, + end_time=now + timedelta(hours=1), + ) - created = await calendar_events_controller.upsert(event) - assert created.title == "Original Title" + created = await calendar_events_controller.upsert(session, event) + assert created.title == "Original Title" - # Update existing event - event.title = "Updated Title" - event.description = "Added description" + # Update existing event + event.title = "Updated Title" + event.description = "Added description" - updated = await calendar_events_controller.upsert(event) - assert updated.title == "Updated Title" - assert updated.description == "Added description" - assert updated.ics_uid == "upsert-test" + updated = await calendar_events_controller.upsert(session, event) + assert updated.title == "Updated Title" + assert updated.description == "Added description" + assert updated.ics_uid == "upsert-test" - # Verify only one event exists - events = await calendar_events_controller.get_by_room(room.id) - assert len(events) == 1 - assert events[0].title == "Updated Title" + # Verify only one event exists + events = await calendar_events_controller.get_by_room(session, room.id) + assert len(events) == 1 + assert events[0].title == "Updated Title" @pytest.mark.asyncio async def test_calendar_event_soft_delete(): """Test soft deleting events no longer in calendar.""" - # Create room - room = await rooms_controller.add( - name="delete-room", - 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, - ) - - now = datetime.now(timezone.utc) - - # Create multiple events - for i in range(4): - event = CalendarEvent( - room_id=room.id, - ics_uid=f"event-{i}", - title=f"Meeting {i}", - start_time=now + timedelta(hours=i), - end_time=now + timedelta(hours=i + 1), + session_factory = get_session_factory() + async with session_factory() as session: + # Create room + room = await rooms_controller.add( + session, + name="delete-room", + 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, ) - await calendar_events_controller.upsert(event) - # Soft delete events not in current list - current_ids = ["event-0", "event-2"] # Keep events 0 and 2 - deleted_count = await calendar_events_controller.soft_delete_missing( - room.id, current_ids - ) + now = datetime.now(timezone.utc) - assert deleted_count == 2 # Should delete events 1 and 3 + # Create multiple events + for i in range(4): + event = CalendarEvent( + room_id=room.id, + ics_uid=f"event-{i}", + title=f"Meeting {i}", + start_time=now + timedelta(hours=i), + end_time=now + timedelta(hours=i + 1), + ) + await calendar_events_controller.upsert(session, event) - # Get non-deleted events - events = await calendar_events_controller.get_by_room( - room.id, include_deleted=False - ) - assert len(events) == 2 - assert {e.ics_uid for e in events} == {"event-0", "event-2"} + # Soft delete events not in current list + current_ids = ["event-0", "event-2"] # Keep events 0 and 2 + deleted_count = await calendar_events_controller.soft_delete_missing( + session, room.id, current_ids + ) - # Get all events including deleted - all_events = await calendar_events_controller.get_by_room( - room.id, include_deleted=True - ) - assert len(all_events) == 4 + assert deleted_count == 2 # Should delete events 1 and 3 + + # Get non-deleted events + events = await calendar_events_controller.get_by_room( + session, room.id, include_deleted=False + ) + assert len(events) == 2 + assert {e.ics_uid for e in events} == {"event-0", "event-2"} + + # Get all events including deleted + all_events = await calendar_events_controller.get_by_room( + session, room.id, include_deleted=True + ) + assert len(all_events) == 4 @pytest.mark.asyncio async def test_calendar_event_past_events_not_deleted(): """Test that past events are not soft deleted.""" - # Create room - room = await rooms_controller.add( - name="past-events-room", - 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, - ) + session_factory = get_session_factory() + async with session_factory() as session: + # Create room + room = await rooms_controller.add( + session, + name="past-events-room", + 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, + ) - now = datetime.now(timezone.utc) + now = datetime.now(timezone.utc) - # Create past event - past_event = CalendarEvent( - room_id=room.id, - ics_uid="past-event", - title="Past Meeting", - start_time=now - timedelta(hours=2), - end_time=now - timedelta(hours=1), - ) - await calendar_events_controller.upsert(past_event) + # Create past event + past_event = CalendarEvent( + room_id=room.id, + ics_uid="past-event", + title="Past Meeting", + start_time=now - timedelta(hours=2), + end_time=now - timedelta(hours=1), + ) + await calendar_events_controller.upsert(session, past_event) - # Create future event - future_event = CalendarEvent( - room_id=room.id, - ics_uid="future-event", - title="Future Meeting", - start_time=now + timedelta(hours=1), - end_time=now + timedelta(hours=2), - ) - await calendar_events_controller.upsert(future_event) + # Create future event + future_event = CalendarEvent( + room_id=room.id, + ics_uid="future-event", + title="Future Meeting", + start_time=now + timedelta(hours=1), + end_time=now + timedelta(hours=2), + ) + await calendar_events_controller.upsert(session, future_event) - # Try to soft delete all events (only future should be deleted) - deleted_count = await calendar_events_controller.soft_delete_missing(room.id, []) + # Try to soft delete all events (only future should be deleted) + deleted_count = await calendar_events_controller.soft_delete_missing( + session, room.id, [] + ) - assert deleted_count == 1 # Only future event deleted + assert deleted_count == 1 # Only future event deleted - # Verify past event still exists - events = await calendar_events_controller.get_by_room( - room.id, include_deleted=False - ) - assert len(events) == 1 - assert events[0].ics_uid == "past-event" + # Verify past event still exists + events = await calendar_events_controller.get_by_room( + session, room.id, include_deleted=False + ) + assert len(events) == 1 + assert events[0].ics_uid == "past-event" @pytest.mark.asyncio async def test_calendar_event_with_raw_ics_data(): """Test storing raw ICS data with calendar event.""" - # Create room - room = await rooms_controller.add( - name="raw-ics-room", - 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, - ) + session_factory = get_session_factory() + async with session_factory() as session: + # Create room + room = await rooms_controller.add( + session, + name="raw-ics-room", + 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, + ) - raw_ics = """BEGIN:VEVENT + raw_ics = """BEGIN:VEVENT UID:test-raw-123 SUMMARY:Test Event DTSTART:20240101T100000Z DTEND:20240101T110000Z END:VEVENT""" - event = CalendarEvent( - room_id=room.id, - ics_uid="test-raw-123", - title="Test Event", - start_time=datetime.now(timezone.utc), - end_time=datetime.now(timezone.utc) + timedelta(hours=1), - ics_raw_data=raw_ics, - ) + event = CalendarEvent( + room_id=room.id, + ics_uid="test-raw-123", + title="Test Event", + start_time=datetime.now(timezone.utc), + end_time=datetime.now(timezone.utc) + timedelta(hours=1), + ics_raw_data=raw_ics, + ) - saved = await calendar_events_controller.upsert(event) + saved = await calendar_events_controller.upsert(session, event) - assert saved.ics_raw_data == raw_ics + assert saved.ics_raw_data == raw_ics - # Retrieve and verify - retrieved = await calendar_events_controller.get_by_ics_uid(room.id, "test-raw-123") - assert retrieved is not None - assert retrieved.ics_raw_data == raw_ics + # Retrieve and verify + retrieved = await calendar_events_controller.get_by_ics_uid( + session, room.id, "test-raw-123" + ) + assert retrieved is not None + assert retrieved.ics_raw_data == raw_ics