diff --git a/surfsense_backend/tests/e2e/fakes/chat_llm.py b/surfsense_backend/tests/e2e/fakes/chat_llm.py index 7b66f3b21..a5634bfaa 100644 --- a/surfsense_backend/tests/e2e/fakes/chat_llm.py +++ b/surfsense_backend/tests/e2e/fakes/chat_llm.py @@ -16,6 +16,8 @@ DRIVE_CANARY_TOKEN = "SURFSENSE_E2E_CANARY_TOKEN_DRIVE_001" GMAIL_CANARY_TOKEN = "SURFSENSE_E2E_CANARY_TOKEN_GMAIL_001" GMAIL_CANARY_SUBJECT = "E2E Canary Email" GMAIL_CANARY_MESSAGE_ID = "fake-msg-canary-001" +CALENDAR_CANARY_TOKEN = "SURFSENSE_E2E_CANARY_TOKEN_CALENDAR_001" +CALENDAR_CANARY_SUMMARY = "E2E Canary Calendar Event" NO_RELEVANT_CONTENT_SENTINEL = "No relevant indexed content found." NO_RELEVANT_CONTENT_QUERY = "E2E_NO_RELEVANT_CONTENT_SMOKE" @@ -77,11 +79,20 @@ class FakeChatLLM(BaseChatModel): return f"Gmail live tool content found: {GMAIL_CANARY_TOKEN}" if latest_tool_name == "search_gmail" and GMAIL_CANARY_MESSAGE_ID in latest_tool_text: return "Reading the matching Gmail message next." + if ( + latest_tool_name == "search_calendar_events" + and CALENDAR_CANARY_TOKEN in latest_tool_text + ): + return f"Calendar live tool content found: {CALENDAR_CANARY_TOKEN}" wants_gmail = _contains_any( latest_human, ("gmail", "email", "message", GMAIL_CANARY_SUBJECT), ) + wants_calendar = _contains_any( + latest_human, + ("calendar", "event", "meeting", CALENDAR_CANARY_SUMMARY), + ) wants_drive = _contains_any( latest_human, ("drive", "file", "e2e-canary.txt"), @@ -91,16 +102,23 @@ class FakeChatLLM(BaseChatModel): or GMAIL_CANARY_MESSAGE_ID in prompt_text or GMAIL_CANARY_TOKEN in prompt_text ) + has_calendar_evidence = ( + CALENDAR_CANARY_SUMMARY in prompt_text or CALENDAR_CANARY_TOKEN in prompt_text + ) has_drive_evidence = ( "e2e-canary.txt" in prompt_text or "fake-file-canary" in prompt_text or DRIVE_CANARY_TOKEN in prompt_text ) + if wants_calendar and has_calendar_evidence: + return f"Calendar content found: {CALENDAR_CANARY_TOKEN}" if wants_gmail and has_gmail_evidence: return f"Gmail content found: {GMAIL_CANARY_TOKEN}" if wants_drive and has_drive_evidence: return f"Drive content found: {DRIVE_CANARY_TOKEN}" + if has_calendar_evidence and not has_gmail_evidence and not has_drive_evidence: + return f"Calendar content found: {CALENDAR_CANARY_TOKEN}" if has_gmail_evidence and not has_drive_evidence: return f"Gmail content found: {GMAIL_CANARY_TOKEN}" if has_drive_evidence and not has_gmail_evidence: @@ -150,6 +168,25 @@ class FakeChatLLM(BaseChatModel): ], ) + if latest_tool is None and _contains_any( + latest_human, + ("calendar", "event", "meeting", CALENDAR_CANARY_SUMMARY), + ): + return AIMessage( + content="", + tool_calls=[ + { + "name": "search_calendar_events", + "args": { + "start_date": "2026-05-07", + "end_date": "2026-05-21", + "max_results": 10, + }, + "id": "call_e2e_search_calendar_events", + } + ], + ) + return None def _generate( diff --git a/surfsense_backend/tests/e2e/fakes/composio_module.py b/surfsense_backend/tests/e2e/fakes/composio_module.py index b038c3d78..0699992af 100644 --- a/surfsense_backend/tests/e2e/fakes/composio_module.py +++ b/surfsense_backend/tests/e2e/fakes/composio_module.py @@ -31,6 +31,7 @@ logger = logging.getLogger(__name__) _DRIVE_FIXTURE_PATH = Path(__file__).parent / "fixtures" / "drive_files.json" _GMAIL_FIXTURE_PATH = Path(__file__).parent / "fixtures" / "gmail_messages.json" +_CALENDAR_FIXTURE_PATH = Path(__file__).parent / "fixtures" / "calendar_events.json" _DRIVE_DOWNLOAD_DIR = Path("/tmp/surfsense-e2e-composio-downloads") @@ -46,8 +47,15 @@ def _load_gmail_fixture() -> dict[str, Any]: return json.load(f) +def _load_calendar_fixture() -> dict[str, Any]: + """Load the canned Calendar fixture once per process.""" + with _CALENDAR_FIXTURE_PATH.open() as f: + return json.load(f) + + _DRIVE_FIXTURE = _load_drive_fixture() _GMAIL_FIXTURE = _load_gmail_fixture() +_CALENDAR_FIXTURE = _load_calendar_fixture() def _get_scenario() -> str: @@ -290,6 +298,17 @@ class _Tools(_StrictFakeMixin): return _gmail_fetch_emails(args) if slug == "GMAIL_FETCH_MESSAGE_BY_MESSAGE_ID": return _gmail_fetch_message_by_message_id(args) + if slug == "GOOGLECALENDAR_EVENTS_LIST": + return _calendar_events_list(args) + if slug == "GOOGLECALENDAR_GET_CALENDAR": + return { + "data": { + "id": "primary", + "summary": "e2e-fake@surfsense.example", + "primary": True, + "timeZone": "UTC", + } + } if slug == "GOOGLECALENDAR_CALENDARS_LIST": return { "data": { @@ -462,6 +481,24 @@ def _gmail_fetch_message_by_message_id(args: dict[str, Any]) -> dict[str, Any]: return {"data": detail} +# --------------------------------------------------------------------------- +# Google Calendar tool handlers +# --------------------------------------------------------------------------- + + +def _calendar_events_list(args: dict[str, Any]) -> dict[str, Any]: + """Mimic GOOGLECALENDAR_EVENTS_LIST for live calendar chat tools.""" + max_results = int(args.get("max_results", 250) or 250) + items = list(_CALENDAR_FIXTURE.get("items", []))[:max_results] + return { + "data": { + "items": items, + "summary": "e2e-fake@surfsense.example", + "timeZone": "UTC", + } + } + + # --------------------------------------------------------------------------- # Errors # --------------------------------------------------------------------------- diff --git a/surfsense_backend/tests/e2e/fakes/fixtures/calendar_events.json b/surfsense_backend/tests/e2e/fakes/fixtures/calendar_events.json new file mode 100644 index 000000000..d71c6d3b7 --- /dev/null +++ b/surfsense_backend/tests/e2e/fakes/fixtures/calendar_events.json @@ -0,0 +1,48 @@ +{ + "items": [ + { + "id": "fake-calendar-event-canary-001", + "status": "confirmed", + "summary": "E2E Canary Calendar Event", + "description": "This Calendar event proves the Composio Calendar live tool fetched event details. SURFSENSE_E2E_CANARY_TOKEN_CALENDAR_001", + "location": "SurfSense E2E Room", + "htmlLink": "https://calendar.google.com/calendar/event?eid=fake-calendar-event-canary-001", + "start": { + "dateTime": "2026-05-12T10:00:00Z", + "timeZone": "UTC" + }, + "end": { + "dateTime": "2026-05-12T10:30:00Z", + "timeZone": "UTC" + }, + "attendees": [ + { + "email": "e2e-fake@surfsense.example", + "responseStatus": "accepted" + } + ] + }, + { + "id": "fake-calendar-event-planning-001", + "status": "confirmed", + "summary": "E2E Planning Sync", + "description": "Non-canary planning sync used to prove list responses can contain multiple events.", + "location": "SurfSense Planning Room", + "htmlLink": "https://calendar.google.com/calendar/event?eid=fake-calendar-event-planning-001", + "start": { + "dateTime": "2026-05-13T15:00:00Z", + "timeZone": "UTC" + }, + "end": { + "dateTime": "2026-05-13T15:45:00Z", + "timeZone": "UTC" + }, + "attendees": [ + { + "email": "planner@surfsense.example", + "responseStatus": "needsAction" + } + ] + } + ] +} diff --git a/surfsense_web/tests/helpers/canary.ts b/surfsense_web/tests/helpers/canary.ts index 8f5551798..2cead2933 100644 --- a/surfsense_web/tests/helpers/canary.ts +++ b/surfsense_web/tests/helpers/canary.ts @@ -19,6 +19,7 @@ export const CANARY_TOKENS = { driveRoadmap: "SURFSENSE_E2E_ROADMAP_MARKER", driveArchive: "SURFSENSE_E2E_ARCHIVE_MARKER", gmailCanary: "SURFSENSE_E2E_CANARY_TOKEN_GMAIL_001", + calendarCanary: "SURFSENSE_E2E_CANARY_TOKEN_CALENDAR_001", } as const; /** @@ -65,6 +66,23 @@ export const FAKE_GMAIL_MESSAGES = { }, } as const; +/** + * Fake Calendar event IDs that match what the backend fake returns from + * GOOGLECALENDAR_EVENTS_LIST. + */ +export const FAKE_CALENDAR_EVENTS = { + canary: { + id: "fake-calendar-event-canary-001", + summary: "E2E Canary Calendar Event", + location: "SurfSense E2E Room", + }, + planning: { + id: "fake-calendar-event-planning-001", + summary: "E2E Planning Sync", + location: "SurfSense Planning Room", + }, +} as const; + /** Generate a unique-per-run search space name. Keeps parallel tests isolated. */ export function uniqueSearchSpaceName(prefix = "e2e"): string { return `${prefix}-${randomUUID().slice(0, 8)}`;