diff --git a/surfsense_web/tests/fixtures/connectors/native-calendar.fixture.ts b/surfsense_web/tests/fixtures/connectors/native-calendar.fixture.ts new file mode 100644 index 000000000..e8ba08828 --- /dev/null +++ b/surfsense_web/tests/fixtures/connectors/native-calendar.fixture.ts @@ -0,0 +1,36 @@ +import { + type ConnectorRow, + deleteConnector, + runNativeGoogleCalendarOAuth, +} from "../../helpers/api/connectors"; +import { searchSpaceFixtures } from "../search-space.fixture"; + +export type NativeCalendarFixtures = { + /** + * A pre-connected native Google Calendar connector inside the fixture's + * `searchSpace`. OAuth uses E2E native Google fakes and is cleaned up + * automatically after the test. + */ + nativeCalendarConnector: ConnectorRow; +}; + +export const nativeCalendarFixtures = searchSpaceFixtures.extend({ + nativeCalendarConnector: async ({ request, apiToken, searchSpace }, use) => { + const { connector } = await runNativeGoogleCalendarOAuth( + request, + apiToken, + searchSpace.id + ); + if (!connector) { + throw new Error( + "nativeCalendarConnector fixture: OAuth completed but no GOOGLE_CALENDAR_CONNECTOR was created. " + + "Check the backend log — the native Google fake likely rejected an unmodelled call." + ); + } + try { + await use(connector); + } finally { + await deleteConnector(request, apiToken, connector.id); + } + }, +}); diff --git a/surfsense_web/tests/fixtures/index.ts b/surfsense_web/tests/fixtures/index.ts index 39e0d2e73..1c88701fc 100644 --- a/surfsense_web/tests/fixtures/index.ts +++ b/surfsense_web/tests/fixtures/index.ts @@ -18,6 +18,8 @@ * └─ nativeDriveWithChatTest — chatThread * └─ nativeGmailFixtures — nativeGmailConnector * └─ nativeGmailWithChatTest — chatThread + * └─ nativeCalendarFixtures — nativeCalendarConnector + * └─ nativeCalendarWithChatTest — chatThread * * To add a new connector (Gmail, Slack, manual upload, etc.): * 1. Add a fixture file under `fixtures/connectors/.fixture.ts`. @@ -29,6 +31,7 @@ export { chatThreadFixtures } from "./chat-thread.fixture"; export { composioCalendarFixtures } from "./connectors/composio-calendar.fixture"; export { composioDriveFixtures } from "./connectors/composio-drive.fixture"; export { composioGmailFixtures } from "./connectors/composio-gmail.fixture"; +export { nativeCalendarFixtures } from "./connectors/native-calendar.fixture"; export { nativeDriveFixtures } from "./connectors/native-drive.fixture"; export { nativeGmailFixtures } from "./connectors/native-gmail.fixture"; export { searchSpaceFixtures } from "./search-space.fixture"; @@ -37,6 +40,7 @@ import { type ChatThreadFixtures, chatThreadFixtures } from "./chat-thread.fixtu import { composioCalendarFixtures } from "./connectors/composio-calendar.fixture"; import { composioDriveFixtures } from "./connectors/composio-drive.fixture"; import { composioGmailFixtures } from "./connectors/composio-gmail.fixture"; +import { nativeCalendarFixtures } from "./connectors/native-calendar.fixture"; import { nativeDriveFixtures } from "./connectors/native-drive.fixture"; import { nativeGmailFixtures } from "./connectors/native-gmail.fixture"; import { searchSpaceFixtures } from "./search-space.fixture"; @@ -68,3 +72,8 @@ export const nativeGmailTest = nativeGmailFixtures; /** `test` for native Gmail specs that also need a chat thread. */ export const nativeGmailWithChatTest = nativeGmailFixtures.extend(chatThreadFixtures); +/** `test` for specs that also need a pre-connected native Calendar connector. */ +export const nativeCalendarTest = nativeCalendarFixtures; +/** `test` for native Calendar specs that also need a chat thread. */ +export const nativeCalendarWithChatTest = + nativeCalendarFixtures.extend(chatThreadFixtures); diff --git a/surfsense_web/tests/helpers/api/connectors.ts b/surfsense_web/tests/helpers/api/connectors.ts index 39c1d340e..99c27ebf2 100644 --- a/surfsense_web/tests/helpers/api/connectors.ts +++ b/surfsense_web/tests/helpers/api/connectors.ts @@ -297,3 +297,46 @@ export async function runNativeGoogleGmailOAuth( return { authUrl: auth_url, finalUrl: location, connector }; } + +/** + * Drives the native Google Calendar OAuth flow programmatically. + * + * The E2E backend patches Google OAuth so the returned auth_url points + * straight back to the backend callback with a deterministic code/state. + */ +export async function runNativeGoogleCalendarOAuth( + request: APIRequestContext, + token: string, + searchSpaceId: number +): Promise<{ + authUrl: string; + finalUrl: string; + connector: ConnectorRow | null; +}> { + const initiateResp = await request.get( + `${BACKEND_URL}/api/v1/auth/google/calendar/connector/add?space_id=${searchSpaceId}`, + { headers: authHeaders(token) } + ); + if (!initiateResp.ok()) { + throw new Error( + `native Google Calendar initiate failed (${initiateResp.status()}): ${await initiateResp.text()}` + ); + } + const { auth_url } = (await initiateResp.json()) as { auth_url: string }; + if (!auth_url) { + throw new Error("native Google Calendar initiate response missing auth_url"); + } + + const callbackResp = await request.get(auth_url, { + headers: authHeaders(token), + maxRedirects: 0, + failOnStatusCode: false, + }); + const location = callbackResp.headers().location ?? auth_url; + + const connectors = await listConnectors(request, token, searchSpaceId); + const connector = + connectors.find((c) => c.connector_type === "GOOGLE_CALENDAR_CONNECTOR") ?? null; + + return { authUrl: auth_url, finalUrl: location, connector }; +}