feat(rename): complete searchSpace to workspace transition across frontend and backend

- Introduced a comprehensive specification for renaming `searchSpace` to `workspace` in `surfsense_web` and `surfsense_desktop`, ensuring all TypeScript identifiers, React props, and local data structures are updated.
- Implemented migration shims for persisted local state to prevent data loss during the transition.
- Updated observability metrics and IPC channels to reflect the new naming convention.
- Removed legacy `active-search-space` module and replaced it with `active-workspace` to maintain consistency.
- Ensured no behavioral changes or data loss for users during the renaming process.
This commit is contained in:
Anish Sarkar 2026-07-06 15:12:40 +05:30
parent 2a020629c5
commit a8c1fb660d
259 changed files with 5480 additions and 2285 deletions

View file

@ -16,7 +16,7 @@ test.describe("ClickUp connector journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
clickupConnector,
chatThread,
}) => {
@ -40,7 +40,7 @@ test.describe("ClickUp connector journey", () => {
expect(clickupConnector.config.access_token).toBeUndefined();
expect(clickupConnector.config.refresh_token).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -48,20 +48,20 @@ test.describe("ClickUp connector journey", () => {
await expect(connectorDialog).toBeVisible();
await expect(connectorDialog.getByText("ClickUp")).toBeVisible();
const beforeDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeDocs).toHaveLength(0);
const disabledIndex = await triggerIndexExpectDisabled(
request,
apiToken,
clickupConnector.id,
searchSpace.id
workspace.id
);
expect(disabledIndex.message ?? "").toContain("real-time agent tools");
expect(disabledIndex.message ?? "").toContain("background indexing is disabled");
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: `What is in my ClickUp task titled "${FAKE_CLICKUP_TASKS.canary.name}"?`,
});
@ -73,13 +73,13 @@ test.describe("ClickUp connector journey", () => {
const eventText = JSON.stringify(chat.events);
expect(eventText).toContain("clickup_search");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === clickupConnector.id);
expect(refreshed?.connector_type).toBe("CLICKUP_CONNECTOR");
expect(refreshed?.is_indexable).toBe(false);
expect(refreshed?.last_indexed_at).toBeNull();
const afterDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterDocs).toHaveLength(0);
});
});

View file

@ -17,24 +17,24 @@ test.describe("Composio Calendar journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
composioCalendarConnector,
chatThread,
}) => {
test.setTimeout(90_000); // worker cold-start + live tool chat
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
const connectorDialog = page.getByRole("dialog", { name: "Manage Connectors" });
await expect(connectorDialog).toBeVisible();
const beforeChatDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeChatDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeChatDocs).toHaveLength(0);
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query:
`What Calendar event mentions ${CANARY_TOKENS.calendarCanary} next week? ` +
@ -48,11 +48,11 @@ test.describe("Composio Calendar journey", () => {
const eventText = JSON.stringify(chat.events);
expect(eventText).toContain("search_calendar_events");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === composioCalendarConnector.id);
expect(refreshed?.last_indexed_at).toBeNull();
const afterChatDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterChatDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterChatDocs).toHaveLength(0);
});
});

View file

@ -22,13 +22,13 @@ test.describe("Composio Drive journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
composioDriveConnector,
chatThread,
}) => {
test.setTimeout(240_000); // worker cold-start + Docling + summarize + embed + chunk
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -60,29 +60,29 @@ test.describe("Composio Drive journey", () => {
indexing_options: indexingOptions,
});
await triggerIndex(request, apiToken, composioDriveConnector.id, searchSpace.id, {
await triggerIndex(request, apiToken, composioDriveConnector.id, workspace.id, {
files: selectedFiles,
indexing_options: indexingOptions,
});
await waitForIndexingComplete(request, apiToken, composioDriveConnector.id, searchSpace.id, {
await waitForIndexingComplete(request, apiToken, composioDriveConnector.id, workspace.id, {
timeoutMs: 240_000,
intervalMs: 1_500,
minDocuments: 2,
});
await waitForDocumentByTitle(request, apiToken, searchSpace.id, FAKE_DRIVE_FILES.canary.name, {
await waitForDocumentByTitle(request, apiToken, workspace.id, FAKE_DRIVE_FILES.canary.name, {
timeoutMs: 30_000,
});
await waitForDocumentByTitle(
request,
apiToken,
searchSpace.id,
workspace.id,
FAKE_DRIVE_FILES.pdfComposio.name,
{ timeoutMs: 60_000 }
);
const docs = await listDocuments(request, apiToken, searchSpace.id);
const docs = await listDocuments(request, apiToken, workspace.id);
const canaryDoc = docs.find((d) => d.title === FAKE_DRIVE_FILES.canary.name);
const pdfDoc = docs.find((d) => d.title === FAKE_DRIVE_FILES.pdfComposio.name);
@ -95,7 +95,7 @@ test.describe("Composio Drive journey", () => {
// content holds the LLM summary; the raw file body lives in source_markdown.
// editor-content is the same endpoint the UI hits when opening a document.
const editor = await getEditorContent(request, apiToken, searchSpace.id, canaryDoc.id);
const editor = await getEditorContent(request, apiToken, workspace.id, canaryDoc.id);
expect(
editor.source_markdown,
`canary token ${CANARY_TOKENS.driveCanaryFile} should appear in editor source_markdown; ` +
@ -104,7 +104,7 @@ test.describe("Composio Drive journey", () => {
expect(editor.document_type).toBe("GOOGLE_DRIVE_FILE");
expect(editor.chunk_count).toBeGreaterThan(0);
const pdfEditor = await getEditorContent(request, apiToken, searchSpace.id, pdfDoc.id);
const pdfEditor = await getEditorContent(request, apiToken, workspace.id, pdfDoc.id);
expect(
pdfEditor.source_markdown,
`PDF canary token ${CANARY_TOKENS.composioDrivePdfCanary} should appear in editor source_markdown; ` +
@ -113,12 +113,12 @@ test.describe("Composio Drive journey", () => {
expect(pdfEditor.document_type).toBe("GOOGLE_DRIVE_FILE");
expect(pdfEditor.chunk_count).toBeGreaterThan(0);
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === composioDriveConnector.id);
expect(refreshed?.last_indexed_at).not.toBeNull();
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: "What is in my e2e-canary.txt Drive file?",
});
@ -128,7 +128,7 @@ test.describe("Composio Drive journey", () => {
).toContain(CANARY_TOKENS.driveCanaryFile);
const pdfChat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: "What is in my e2e-composio-canary.pdf Drive file?",
});

View file

@ -17,24 +17,24 @@ test.describe("Composio Gmail journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
composioGmailConnector,
chatThread,
}) => {
test.setTimeout(90_000); // worker cold-start + live tool chat
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
const connectorDialog = page.getByRole("dialog", { name: "Manage Connectors" });
await expect(connectorDialog).toBeVisible();
const beforeChatDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeChatDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeChatDocs).toHaveLength(0);
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: `What is in my Gmail message titled "${FAKE_GMAIL_MESSAGES.canary.subject}"?`,
});
@ -47,11 +47,11 @@ test.describe("Composio Gmail journey", () => {
expect(eventText).toContain("search_gmail");
expect(eventText).toContain("read_gmail_email");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === composioGmailConnector.id);
expect(refreshed?.last_indexed_at).toBeNull();
const afterChatDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterChatDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterChatDocs).toHaveLength(0);
});
});

View file

@ -18,7 +18,7 @@ test.describe("Confluence connector journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
confluenceConnector,
chatThread,
}) => {
@ -44,7 +44,7 @@ test.describe("Confluence connector journey", () => {
expect(confluenceConnector.config.access_token).toBeUndefined();
expect(confluenceConnector.config.refresh_token).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -53,20 +53,20 @@ test.describe("Confluence connector journey", () => {
await connectorDialog.getByPlaceholder("Search").fill("Confluence");
await expect(connectorDialog.getByText("Confluence", { exact: true })).toBeVisible();
const beforeDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeDocs).toHaveLength(0);
const disabledIndex = await triggerIndexExpectDisabled(
request,
apiToken,
confluenceConnector.id,
searchSpace.id
workspace.id
);
expect(disabledIndex.message ?? "").toContain("real-time agent tools");
expect(disabledIndex.message ?? "").toContain("background indexing is disabled");
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: `What is in my Confluence page titled "${FAKE_CONFLUENCE_PAGES.canary.title}"?`,
});
@ -78,13 +78,13 @@ test.describe("Confluence connector journey", () => {
const eventText = JSON.stringify(chat.events);
expect(eventText).toContain("searchConfluenceUsingCql");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === confluenceConnector.id);
expect(refreshed?.connector_type).toBe("CONFLUENCE_CONNECTOR");
expect(refreshed?.is_indexable).toBe(false);
expect(refreshed?.last_indexed_at).toBeNull();
const afterDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterDocs).toHaveLength(0);
});
});

View file

@ -18,7 +18,7 @@ test.describe("Native Dropbox journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
nativeDropboxConnector,
chatThread,
}) => {
@ -29,7 +29,7 @@ test.describe("Native Dropbox journey", () => {
expect(nativeDropboxConnector.config._token_encrypted).toBe(true);
expect(nativeDropboxConnector.config.composio_connected_account_id).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -63,29 +63,25 @@ test.describe("Native Dropbox journey", () => {
indexing_options: indexingOptions,
});
await triggerIndex(request, apiToken, nativeDropboxConnector.id, searchSpace.id, {
await triggerIndex(request, apiToken, nativeDropboxConnector.id, workspace.id, {
files: selectedFiles,
indexing_options: indexingOptions,
});
await waitForIndexingComplete(request, apiToken, nativeDropboxConnector.id, searchSpace.id, {
await waitForIndexingComplete(request, apiToken, nativeDropboxConnector.id, workspace.id, {
timeoutMs: 240_000,
intervalMs: 1_500,
minDocuments: 2,
});
await waitForDocumentByTitle(
request,
apiToken,
searchSpace.id,
FAKE_DROPBOX_FILES.canary.name,
{ timeoutMs: 30_000 }
);
await waitForDocumentByTitle(request, apiToken, searchSpace.id, FAKE_DROPBOX_FILES.pdf.name, {
await waitForDocumentByTitle(request, apiToken, workspace.id, FAKE_DROPBOX_FILES.canary.name, {
timeoutMs: 30_000,
});
await waitForDocumentByTitle(request, apiToken, workspace.id, FAKE_DROPBOX_FILES.pdf.name, {
timeoutMs: 60_000,
});
const docs = await listDocuments(request, apiToken, searchSpace.id);
const docs = await listDocuments(request, apiToken, workspace.id);
const canaryDoc = docs.find((d) => d.title === FAKE_DROPBOX_FILES.canary.name);
const pdfDoc = docs.find((d) => d.title === FAKE_DROPBOX_FILES.pdf.name);
@ -96,7 +92,7 @@ test.describe("Native Dropbox journey", () => {
if (!pdfDoc) throw new Error("unreachable: pdfDoc asserted defined above");
expect(pdfDoc.document_type).toBe("DROPBOX_FILE");
const editor = await getEditorContent(request, apiToken, searchSpace.id, canaryDoc.id);
const editor = await getEditorContent(request, apiToken, workspace.id, canaryDoc.id);
expect(
editor.source_markdown,
`canary token ${CANARY_TOKENS.dropboxCanary} should appear in editor source_markdown; ` +
@ -105,7 +101,7 @@ test.describe("Native Dropbox journey", () => {
expect(editor.document_type).toBe("DROPBOX_FILE");
expect(editor.chunk_count).toBeGreaterThan(0);
const pdfEditor = await getEditorContent(request, apiToken, searchSpace.id, pdfDoc.id);
const pdfEditor = await getEditorContent(request, apiToken, workspace.id, pdfDoc.id);
expect(
pdfEditor.source_markdown,
`PDF canary token ${CANARY_TOKENS.dropboxPdfCanary} should appear in editor source_markdown; ` +
@ -114,14 +110,14 @@ test.describe("Native Dropbox journey", () => {
expect(pdfEditor.document_type).toBe("DROPBOX_FILE");
expect(pdfEditor.chunk_count).toBeGreaterThan(0);
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === nativeDropboxConnector.id);
expect(refreshed?.connector_type).toBe("DROPBOX_CONNECTOR");
expect(refreshed?.is_indexable).toBe(true);
expect(refreshed?.last_indexed_at).not.toBeNull();
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: "What is in my e2e-dropbox-canary.txt Dropbox file?",
});
@ -132,7 +128,7 @@ test.describe("Native Dropbox journey", () => {
).toContain(CANARY_TOKENS.dropboxCanary);
const pdfChat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: "What is in my e2e-dropbox-canary.pdf Dropbox file?",
});

View file

@ -17,7 +17,7 @@ test.describe("Native Google Calendar journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
nativeCalendarConnector,
chatThread,
}) => {
@ -28,27 +28,27 @@ test.describe("Native Google Calendar journey", () => {
expect(nativeCalendarConnector.config._token_encrypted).toBe(true);
expect(nativeCalendarConnector.config.composio_connected_account_id).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
const connectorDialog = page.getByRole("dialog", { name: "Manage Connectors" });
await expect(connectorDialog).toBeVisible();
const beforeDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeDocs).toHaveLength(0);
const disabledIndex = await triggerIndexExpectDisabled(
request,
apiToken,
nativeCalendarConnector.id,
searchSpace.id
workspace.id
);
expect(disabledIndex.message ?? "").toContain("real-time agent tools");
expect(disabledIndex.message ?? "").toContain("background indexing is disabled");
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query:
`What Calendar event mentions ${CANARY_TOKENS.calendarCanary} next week? ` +
@ -62,13 +62,13 @@ test.describe("Native Google Calendar journey", () => {
const eventText = JSON.stringify(chat.events);
expect(eventText).toContain("search_calendar_events");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === nativeCalendarConnector.id);
expect(refreshed?.connector_type).toBe("GOOGLE_CALENDAR_CONNECTOR");
expect(refreshed?.is_indexable).toBe(false);
expect(refreshed?.last_indexed_at).toBeNull();
const afterDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterDocs).toHaveLength(0);
});
});

View file

@ -22,7 +22,7 @@ test.describe("Native Google Drive journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
nativeDriveConnector,
chatThread,
}) => {
@ -33,7 +33,7 @@ test.describe("Native Google Drive journey", () => {
expect(nativeDriveConnector.config._token_encrypted).toBe(true);
expect(nativeDriveConnector.config.composio_connected_account_id).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -65,29 +65,25 @@ test.describe("Native Google Drive journey", () => {
indexing_options: indexingOptions,
});
await triggerIndex(request, apiToken, nativeDriveConnector.id, searchSpace.id, {
await triggerIndex(request, apiToken, nativeDriveConnector.id, workspace.id, {
files: selectedFiles,
indexing_options: indexingOptions,
});
await waitForIndexingComplete(request, apiToken, nativeDriveConnector.id, searchSpace.id, {
await waitForIndexingComplete(request, apiToken, nativeDriveConnector.id, workspace.id, {
timeoutMs: 240_000,
intervalMs: 1_500,
minDocuments: 2,
});
await waitForDocumentByTitle(request, apiToken, searchSpace.id, FAKE_DRIVE_FILES.canary.name, {
await waitForDocumentByTitle(request, apiToken, workspace.id, FAKE_DRIVE_FILES.canary.name, {
timeoutMs: 30_000,
});
await waitForDocumentByTitle(
request,
apiToken,
searchSpace.id,
FAKE_DRIVE_FILES.pdfNative.name,
{ timeoutMs: 60_000 }
);
await waitForDocumentByTitle(request, apiToken, workspace.id, FAKE_DRIVE_FILES.pdfNative.name, {
timeoutMs: 60_000,
});
const docs = await listDocuments(request, apiToken, searchSpace.id);
const docs = await listDocuments(request, apiToken, workspace.id);
const canaryDoc = docs.find((d) => d.title === FAKE_DRIVE_FILES.canary.name);
const pdfDoc = docs.find((d) => d.title === FAKE_DRIVE_FILES.pdfNative.name);
@ -98,7 +94,7 @@ test.describe("Native Google Drive journey", () => {
if (!pdfDoc) throw new Error("unreachable: pdfDoc asserted defined above");
expect(pdfDoc.document_type).toBe("GOOGLE_DRIVE_FILE");
const editor = await getEditorContent(request, apiToken, searchSpace.id, canaryDoc.id);
const editor = await getEditorContent(request, apiToken, workspace.id, canaryDoc.id);
expect(
editor.source_markdown,
`canary token ${CANARY_TOKENS.driveCanaryFile} should appear in editor source_markdown; ` +
@ -107,7 +103,7 @@ test.describe("Native Google Drive journey", () => {
expect(editor.document_type).toBe("GOOGLE_DRIVE_FILE");
expect(editor.chunk_count).toBeGreaterThan(0);
const pdfEditor = await getEditorContent(request, apiToken, searchSpace.id, pdfDoc.id);
const pdfEditor = await getEditorContent(request, apiToken, workspace.id, pdfDoc.id);
expect(
pdfEditor.source_markdown,
`PDF canary token ${CANARY_TOKENS.drivePdfCanary} should appear in editor source_markdown; ` +
@ -116,14 +112,14 @@ test.describe("Native Google Drive journey", () => {
expect(pdfEditor.document_type).toBe("GOOGLE_DRIVE_FILE");
expect(pdfEditor.chunk_count).toBeGreaterThan(0);
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === nativeDriveConnector.id);
expect(refreshed?.connector_type).toBe("GOOGLE_DRIVE_CONNECTOR");
expect(refreshed?.is_indexable).toBe(true);
expect(refreshed?.last_indexed_at).not.toBeNull();
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: "What is in my e2e-canary.txt native Drive file?",
});
@ -133,7 +129,7 @@ test.describe("Native Google Drive journey", () => {
).toContain(CANARY_TOKENS.driveCanaryFile);
const pdfChat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: "What is in my e2e-canary.pdf native Drive file?",
});

View file

@ -17,7 +17,7 @@ test.describe("Native Google Gmail journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
nativeGmailConnector,
chatThread,
}) => {
@ -28,27 +28,27 @@ test.describe("Native Google Gmail journey", () => {
expect(nativeGmailConnector.config._token_encrypted).toBe(true);
expect(nativeGmailConnector.config.composio_connected_account_id).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
const connectorDialog = page.getByRole("dialog", { name: "Manage Connectors" });
await expect(connectorDialog).toBeVisible();
const beforeDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeDocs).toHaveLength(0);
const disabledIndex = await triggerIndexExpectDisabled(
request,
apiToken,
nativeGmailConnector.id,
searchSpace.id
workspace.id
);
expect(disabledIndex.message ?? "").toContain("real-time agent tools");
expect(disabledIndex.message ?? "").toContain("background indexing is disabled");
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: `What is in my Gmail message titled "${FAKE_GMAIL_MESSAGES.canary.subject}"?`,
});
@ -61,13 +61,13 @@ test.describe("Native Google Gmail journey", () => {
expect(eventText).toContain("search_gmail");
expect(eventText).toContain("read_gmail_email");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === nativeGmailConnector.id);
expect(refreshed?.connector_type).toBe("GOOGLE_GMAIL_CONNECTOR");
expect(refreshed?.is_indexable).toBe(false);
expect(refreshed?.last_indexed_at).toBeNull();
const afterDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterDocs).toHaveLength(0);
});
});

View file

@ -16,7 +16,7 @@ test.describe("Jira connector journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
jiraConnector,
chatThread,
}) => {
@ -38,7 +38,7 @@ test.describe("Jira connector journey", () => {
expect(jiraConnector.config.access_token).toBeUndefined();
expect(jiraConnector.config.refresh_token).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -47,20 +47,20 @@ test.describe("Jira connector journey", () => {
await connectorDialog.getByPlaceholder("Search").fill("Jira");
await expect(connectorDialog.getByText("Jira", { exact: true })).toBeVisible();
const beforeDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeDocs).toHaveLength(0);
const disabledIndex = await triggerIndexExpectDisabled(
request,
apiToken,
jiraConnector.id,
searchSpace.id
workspace.id
);
expect(disabledIndex.message ?? "").toContain("real-time agent tools");
expect(disabledIndex.message ?? "").toContain("background indexing is disabled");
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: `What is in my Jira issue titled "${FAKE_JIRA_ISSUES.canary.summary}"?`,
});
@ -72,13 +72,13 @@ test.describe("Jira connector journey", () => {
const eventText = JSON.stringify(chat.events);
expect(eventText).toContain("searchJiraIssuesUsingJql");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === jiraConnector.id);
expect(refreshed?.connector_type).toBe("JIRA_CONNECTOR");
expect(refreshed?.is_indexable).toBe(false);
expect(refreshed?.last_indexed_at).toBeNull();
const afterDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterDocs).toHaveLength(0);
});
});

View file

@ -16,7 +16,7 @@ test.describe("Linear connector journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
linearConnector,
chatThread,
}) => {
@ -38,27 +38,27 @@ test.describe("Linear connector journey", () => {
expect(linearConnector.config.access_token).toBeUndefined();
expect(linearConnector.config.refresh_token).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
const connectorDialog = page.getByRole("dialog", { name: "Manage Connectors" });
await expect(connectorDialog).toBeVisible();
const beforeDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeDocs).toHaveLength(0);
const disabledIndex = await triggerIndexExpectDisabled(
request,
apiToken,
linearConnector.id,
searchSpace.id
workspace.id
);
expect(disabledIndex.message ?? "").toContain("real-time agent tools");
expect(disabledIndex.message ?? "").toContain("background indexing is disabled");
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: `What is in my Linear issue titled "${FAKE_LINEAR_ISSUES.canary.title}"?`,
});
@ -70,13 +70,13 @@ test.describe("Linear connector journey", () => {
const eventText = JSON.stringify(chat.events);
expect(eventText).toContain("list_issues");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === linearConnector.id);
expect(refreshed?.connector_type).toBe("LINEAR_CONNECTOR");
expect(refreshed?.is_indexable).toBe(false);
expect(refreshed?.last_indexed_at).toBeNull();
const afterDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterDocs).toHaveLength(0);
});
});

View file

@ -17,7 +17,7 @@ test.describe("Notion connector journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
notionConnector,
chatThread,
}) => {
@ -35,13 +35,11 @@ test.describe("Notion connector journey", () => {
client_id: "fake-notion-mcp-client-id",
token_endpoint: "https://mcp.notion.com/token",
});
expect(
(notionConnector.config.mcp_oauth as Record<string, unknown>).access_token
).toBeTruthy();
expect((notionConnector.config.mcp_oauth as Record<string, unknown>).access_token).toBeTruthy();
expect(notionConnector.config.access_token).toBeUndefined();
expect(notionConnector.config.refresh_token).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -50,20 +48,20 @@ test.describe("Notion connector journey", () => {
await connectorDialog.getByPlaceholder("Search").fill("Notion");
await expect(connectorDialog.getByText("Notion", { exact: true })).toBeVisible();
const beforeDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeDocs).toHaveLength(0);
const disabledIndex = await triggerIndexExpectDisabled(
request,
apiToken,
notionConnector.id,
searchSpace.id
workspace.id
);
expect(disabledIndex.message ?? "").toContain("real-time agent tools");
expect(disabledIndex.message ?? "").toContain("background indexing is disabled");
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: `What is in my Notion page titled "${FAKE_NOTION_PAGES.canary.title}"?`,
});
@ -75,13 +73,13 @@ test.describe("Notion connector journey", () => {
const eventText = JSON.stringify(chat.events);
expect(eventText).toContain(FAKE_NOTION_PAGES.canary.id);
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === notionConnector.id);
expect(refreshed?.connector_type).toBe("NOTION_CONNECTOR");
expect(refreshed?.is_indexable).toBe(false);
expect(refreshed?.last_indexed_at).toBeNull();
const afterDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterDocs).toHaveLength(0);
});
});

View file

@ -18,7 +18,7 @@ test.describe("Native OneDrive journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
nativeOneDriveConnector,
chatThread,
}) => {
@ -29,7 +29,7 @@ test.describe("Native OneDrive journey", () => {
expect(nativeOneDriveConnector.config._token_encrypted).toBe(true);
expect(nativeOneDriveConnector.config.composio_connected_account_id).toBeUndefined();
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -63,29 +63,25 @@ test.describe("Native OneDrive journey", () => {
indexing_options: indexingOptions,
});
await triggerIndex(request, apiToken, nativeOneDriveConnector.id, searchSpace.id, {
await triggerIndex(request, apiToken, nativeOneDriveConnector.id, workspace.id, {
files: selectedFiles,
indexing_options: indexingOptions,
});
await waitForIndexingComplete(request, apiToken, nativeOneDriveConnector.id, searchSpace.id, {
await waitForIndexingComplete(request, apiToken, nativeOneDriveConnector.id, workspace.id, {
timeoutMs: 240_000,
intervalMs: 1_500,
minDocuments: 2,
});
await waitForDocumentByTitle(
request,
apiToken,
searchSpace.id,
FAKE_ONEDRIVE_FILES.canary.name,
{ timeoutMs: 30_000 }
);
await waitForDocumentByTitle(request, apiToken, searchSpace.id, FAKE_ONEDRIVE_FILES.pdf.name, {
await waitForDocumentByTitle(request, apiToken, workspace.id, FAKE_ONEDRIVE_FILES.canary.name, {
timeoutMs: 30_000,
});
await waitForDocumentByTitle(request, apiToken, workspace.id, FAKE_ONEDRIVE_FILES.pdf.name, {
timeoutMs: 60_000,
});
const docs = await listDocuments(request, apiToken, searchSpace.id);
const docs = await listDocuments(request, apiToken, workspace.id);
const canaryDoc = docs.find((d) => d.title === FAKE_ONEDRIVE_FILES.canary.name);
const pdfDoc = docs.find((d) => d.title === FAKE_ONEDRIVE_FILES.pdf.name);
@ -96,7 +92,7 @@ test.describe("Native OneDrive journey", () => {
if (!pdfDoc) throw new Error("unreachable: pdfDoc asserted defined above");
expect(pdfDoc.document_type).toBe("ONEDRIVE_FILE");
const editor = await getEditorContent(request, apiToken, searchSpace.id, canaryDoc.id);
const editor = await getEditorContent(request, apiToken, workspace.id, canaryDoc.id);
expect(
editor.source_markdown,
`canary token ${CANARY_TOKENS.onedriveCanary} should appear in editor source_markdown; ` +
@ -105,7 +101,7 @@ test.describe("Native OneDrive journey", () => {
expect(editor.document_type).toBe("ONEDRIVE_FILE");
expect(editor.chunk_count).toBeGreaterThan(0);
const pdfEditor = await getEditorContent(request, apiToken, searchSpace.id, pdfDoc.id);
const pdfEditor = await getEditorContent(request, apiToken, workspace.id, pdfDoc.id);
expect(
pdfEditor.source_markdown,
`PDF canary token ${CANARY_TOKENS.onedrivePdfCanary} should appear in editor source_markdown; ` +
@ -114,14 +110,14 @@ test.describe("Native OneDrive journey", () => {
expect(pdfEditor.document_type).toBe("ONEDRIVE_FILE");
expect(pdfEditor.chunk_count).toBeGreaterThan(0);
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === nativeOneDriveConnector.id);
expect(refreshed?.connector_type).toBe("ONEDRIVE_CONNECTOR");
expect(refreshed?.is_indexable).toBe(true);
expect(refreshed?.last_indexed_at).not.toBeNull();
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: "What is in my e2e-onedrive-canary.txt OneDrive file?",
});
@ -132,7 +128,7 @@ test.describe("Native OneDrive journey", () => {
).toContain(CANARY_TOKENS.onedriveCanary);
const pdfChat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: "What is in my e2e-onedrive-canary.pdf OneDrive file?",
});

View file

@ -16,7 +16,7 @@ test.describe("Slack connector journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
slackConnector,
chatThread,
}) => {
@ -40,7 +40,7 @@ test.describe("Slack connector journey", () => {
expect(slackConnector.config.team_id).toBe(FAKE_SLACK_CHANNELS.canary.teamId);
expect(slackConnector.config.team_name).toBe(FAKE_SLACK_CHANNELS.canary.teamName);
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
await openConnectorPopup(page);
@ -49,20 +49,20 @@ test.describe("Slack connector journey", () => {
await connectorDialog.getByPlaceholder("Search").fill("Slack");
await expect(connectorDialog.getByText("Slack", { exact: true })).toBeVisible();
const beforeDocs = await listDocuments(request, apiToken, searchSpace.id);
const beforeDocs = await listDocuments(request, apiToken, workspace.id);
expect(beforeDocs).toHaveLength(0);
const disabledIndex = await triggerIndexExpectDisabled(
request,
apiToken,
slackConnector.id,
searchSpace.id
workspace.id
);
expect(disabledIndex.message ?? "").toContain("real-time agent tools");
expect(disabledIndex.message ?? "").toContain("background indexing is disabled");
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: `What is in my Slack channel "${FAKE_SLACK_CHANNELS.canary.name}"?`,
});
@ -74,13 +74,13 @@ test.describe("Slack connector journey", () => {
const eventText = JSON.stringify(chat.events);
expect(eventText).toContain("slack_search_channels");
const refreshedConnectors = await listConnectors(request, apiToken, searchSpace.id);
const refreshedConnectors = await listConnectors(request, apiToken, workspace.id);
const refreshed = refreshedConnectors.find((c) => c.id === slackConnector.id);
expect(refreshed?.connector_type).toBe("SLACK_CONNECTOR");
expect(refreshed?.is_indexable).toBe(false);
expect(refreshed?.last_indexed_at).toBeNull();
const afterDocs = await listDocuments(request, apiToken, searchSpace.id);
const afterDocs = await listDocuments(request, apiToken, workspace.id);
expect(afterDocs).toHaveLength(0);
});
});

View file

@ -14,7 +14,7 @@ type UploadFixture = {
chatQuery: string;
};
type SearchSpace = {
type Workspace = {
id: number;
};
@ -36,18 +36,18 @@ async function uploadAndAssert({
page,
request,
apiToken,
searchSpace,
workspace,
chatThread,
file,
}: {
page: Page;
request: APIRequestContext;
apiToken: string;
searchSpace: SearchSpace;
workspace: Workspace;
chatThread: ChatThreadRow;
file: UploadFixture;
}) {
await page.goto(`/dashboard/${searchSpace.id}/new-chat`, {
await page.goto(`/dashboard/${workspace.id}/new-chat`, {
waitUntil: "domcontentloaded",
});
@ -62,22 +62,22 @@ async function uploadAndAssert({
timeout: 15_000,
});
await waitForDocumentByTitle(request, apiToken, searchSpace.id, file.name, {
await waitForDocumentByTitle(request, apiToken, workspace.id, file.name, {
timeoutMs: 200_000,
});
const docs = await listDocuments(request, apiToken, searchSpace.id);
const docs = await listDocuments(request, apiToken, workspace.id);
const uploaded = docs.find((d) => d.title === file.name);
expect(uploaded, `${file.name} must exist after indexing`).toBeDefined();
if (!uploaded) throw new Error("unreachable: uploaded asserted defined above");
expect(uploaded.document_type).toBe("FILE");
const editor = await getEditorContent(request, apiToken, searchSpace.id, uploaded.id);
const editor = await getEditorContent(request, apiToken, workspace.id, uploaded.id);
expect(editor.source_markdown).toContain(file.canary);
expect(editor.chunk_count).toBeGreaterThan(0);
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: chatThread.id,
query: file.chatQuery,
});
@ -92,7 +92,7 @@ test.describe("Manual file upload journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
chatThread,
}) => {
test.setTimeout(180_000);
@ -101,7 +101,7 @@ test.describe("Manual file upload journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
chatThread,
file: MD_FILE,
});
@ -111,7 +111,7 @@ test.describe("Manual file upload journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
chatThread,
}) => {
test.setTimeout(180_000);
@ -120,7 +120,7 @@ test.describe("Manual file upload journey", () => {
page,
request,
apiToken,
searchSpace,
workspace,
chatThread,
file: PDF_FILE,
});

View file

@ -1,6 +1,6 @@
import type { APIRequestContext } from "@playwright/test";
import { authHeaders, BACKEND_URL } from "../helpers/api/auth";
import type { SearchSpaceFixtures } from "./workspace.fixture";
import type { WorkspaceFixtures } from "./workspace.fixture";
export type ChatThreadRow = {
id: number;
@ -16,20 +16,20 @@ export type ChatThreadFixtures = {
chatThread: ChatThreadRow;
};
type ChatThreadFixtureArgs = SearchSpaceFixtures & {
type ChatThreadFixtureArgs = WorkspaceFixtures & {
request: APIRequestContext;
};
export const chatThreadFixtures = {
chatThread: async (
{ request, apiToken, searchSpace }: ChatThreadFixtureArgs,
{ request, apiToken, workspace }: ChatThreadFixtureArgs,
use: (thread: ChatThreadRow) => Promise<void>
) => {
const response = await request.post(`${BACKEND_URL}/api/v1/threads`, {
headers: authHeaders(apiToken),
data: {
title: "e2e-drive-journey",
workspace_id: searchSpace.id,
workspace_id: workspace.id,
visibility: "PRIVATE",
},
});

View file

@ -1,18 +1,18 @@
import { type ConnectorRow, deleteConnector, runClickupOAuth } from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type ClickupFixtures = {
/**
* A pre-connected ClickUp connector inside the fixture's `searchSpace`.
* A pre-connected ClickUp connector inside the fixture's `workspace`.
* OAuth and MCP tool calls use E2E ClickUp fakes and are cleaned up
* automatically after the test.
*/
clickupConnector: ConnectorRow;
};
export const clickupFixtures = searchSpaceFixtures.extend<ClickupFixtures>({
clickupConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runClickupOAuth(request, apiToken, searchSpace.id);
export const clickupFixtures = workspaceFixtures.extend<ClickupFixtures>({
clickupConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runClickupOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"clickupConnector fixture: OAuth completed but no CLICKUP_CONNECTOR was created. " +

View file

@ -1,23 +1,18 @@
import { type ConnectorRow, deleteConnector, runComposioOAuth } from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type ComposioCalendarFixtures = {
/**
* A pre-connected Composio Google Calendar connector inside the
* fixture's `searchSpace`. OAuth uses the strict fake and is cleaned
* fixture's `workspace`. OAuth uses the strict fake and is cleaned
* up automatically after the test.
*/
composioCalendarConnector: ConnectorRow;
};
export const composioCalendarFixtures = searchSpaceFixtures.extend<ComposioCalendarFixtures>({
composioCalendarConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runComposioOAuth(
request,
apiToken,
searchSpace.id,
"googlecalendar"
);
export const composioCalendarFixtures = workspaceFixtures.extend<ComposioCalendarFixtures>({
composioCalendarConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runComposioOAuth(request, apiToken, workspace.id, "googlecalendar");
if (!connector) {
throw new Error(
"composioCalendarConnector fixture: OAuth completed but no connector was created. " +

View file

@ -1,18 +1,18 @@
import { type ConnectorRow, deleteConnector, runComposioOAuth } from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type ComposioDriveFixtures = {
/**
* A pre-connected Composio Google Drive connector inside the
* fixture's `searchSpace`. OAuth happens against the strict fake
* fixture's `workspace`. OAuth happens against the strict fake
* (no real network). Cleaned up automatically after the test.
*/
composioDriveConnector: ConnectorRow;
};
export const composioDriveFixtures = searchSpaceFixtures.extend<ComposioDriveFixtures>({
composioDriveConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runComposioOAuth(request, apiToken, searchSpace.id, "googledrive");
export const composioDriveFixtures = workspaceFixtures.extend<ComposioDriveFixtures>({
composioDriveConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runComposioOAuth(request, apiToken, workspace.id, "googledrive");
if (!connector) {
throw new Error(
"composioDriveConnector fixture: OAuth completed but no connector was created. " +

View file

@ -1,18 +1,18 @@
import { type ConnectorRow, deleteConnector, runComposioOAuth } from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type ComposioGmailFixtures = {
/**
* A pre-connected Composio Gmail connector inside the fixture's
* `searchSpace`. OAuth happens against the strict fake (no real
* `workspace`. OAuth happens against the strict fake (no real
* network). Cleaned up automatically after the test.
*/
composioGmailConnector: ConnectorRow;
};
export const composioGmailFixtures = searchSpaceFixtures.extend<ComposioGmailFixtures>({
composioGmailConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runComposioOAuth(request, apiToken, searchSpace.id, "gmail");
export const composioGmailFixtures = workspaceFixtures.extend<ComposioGmailFixtures>({
composioGmailConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runComposioOAuth(request, apiToken, workspace.id, "gmail");
if (!connector) {
throw new Error(
"composioGmailConnector fixture: OAuth completed but no connector was created. " +

View file

@ -3,20 +3,20 @@ import {
deleteConnector,
runConfluenceOAuth,
} from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type ConfluenceFixtures = {
/**
* A pre-connected Confluence connector inside the fixture's `searchSpace`.
* A pre-connected Confluence connector inside the fixture's `workspace`.
* OAuth uses E2E Atlassian fakes and is cleaned up automatically after
* the test.
*/
confluenceConnector: ConnectorRow;
};
export const confluenceFixtures = searchSpaceFixtures.extend<ConfluenceFixtures>({
confluenceConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runConfluenceOAuth(request, apiToken, searchSpace.id);
export const confluenceFixtures = workspaceFixtures.extend<ConfluenceFixtures>({
confluenceConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runConfluenceOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"confluenceConnector fixture: OAuth completed but no CONFLUENCE_CONNECTOR was created. " +

View file

@ -1,18 +1,18 @@
import { type ConnectorRow, deleteConnector, runJiraOAuth } from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type JiraFixtures = {
/**
* A pre-connected Jira connector inside the fixture's `searchSpace`.
* A pre-connected Jira connector inside the fixture's `workspace`.
* OAuth and MCP tool calls use E2E Jira fakes and are cleaned up
* automatically after the test.
*/
jiraConnector: ConnectorRow;
};
export const jiraFixtures = searchSpaceFixtures.extend<JiraFixtures>({
jiraConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runJiraOAuth(request, apiToken, searchSpace.id);
export const jiraFixtures = workspaceFixtures.extend<JiraFixtures>({
jiraConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runJiraOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"jiraConnector fixture: OAuth completed but no JIRA_CONNECTOR was created. " +

View file

@ -1,18 +1,18 @@
import { type ConnectorRow, deleteConnector, runLinearOAuth } from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type LinearFixtures = {
/**
* A pre-connected Linear connector inside the fixture's `searchSpace`.
* A pre-connected Linear connector inside the fixture's `workspace`.
* OAuth and MCP tool calls use E2E Linear fakes and are cleaned up
* automatically after the test.
*/
linearConnector: ConnectorRow;
};
export const linearFixtures = searchSpaceFixtures.extend<LinearFixtures>({
linearConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runLinearOAuth(request, apiToken, searchSpace.id);
export const linearFixtures = workspaceFixtures.extend<LinearFixtures>({
linearConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runLinearOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"linearConnector fixture: OAuth completed but no LINEAR_CONNECTOR was created. " +

View file

@ -3,20 +3,20 @@ import {
deleteConnector,
runNativeGoogleCalendarOAuth,
} from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.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
* `workspace`. OAuth uses E2E native Google fakes and is cleaned up
* automatically after the test.
*/
nativeCalendarConnector: ConnectorRow;
};
export const nativeCalendarFixtures = searchSpaceFixtures.extend<NativeCalendarFixtures>({
nativeCalendarConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runNativeGoogleCalendarOAuth(request, apiToken, searchSpace.id);
export const nativeCalendarFixtures = workspaceFixtures.extend<NativeCalendarFixtures>({
nativeCalendarConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runNativeGoogleCalendarOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"nativeCalendarConnector fixture: OAuth completed but no GOOGLE_CALENDAR_CONNECTOR was created. " +

View file

@ -3,20 +3,20 @@ import {
deleteConnector,
runNativeGoogleDriveOAuth,
} from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type NativeDriveFixtures = {
/**
* A pre-connected native Google Drive connector inside the fixture's
* `searchSpace`. OAuth uses E2E native Google fakes and is cleaned up
* `workspace`. OAuth uses E2E native Google fakes and is cleaned up
* automatically after the test.
*/
nativeDriveConnector: ConnectorRow;
};
export const nativeDriveFixtures = searchSpaceFixtures.extend<NativeDriveFixtures>({
nativeDriveConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runNativeGoogleDriveOAuth(request, apiToken, searchSpace.id);
export const nativeDriveFixtures = workspaceFixtures.extend<NativeDriveFixtures>({
nativeDriveConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runNativeGoogleDriveOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"nativeDriveConnector fixture: OAuth completed but no GOOGLE_DRIVE_CONNECTOR was created. " +

View file

@ -3,20 +3,20 @@ import {
deleteConnector,
runNativeDropboxOAuth,
} from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type NativeDropboxFixtures = {
/**
* A pre-connected native Dropbox connector inside the fixture's
* `searchSpace`. OAuth uses E2E Dropbox fakes and is cleaned up
* `workspace`. OAuth uses E2E Dropbox fakes and is cleaned up
* automatically after the test.
*/
nativeDropboxConnector: ConnectorRow;
};
export const nativeDropboxFixtures = searchSpaceFixtures.extend<NativeDropboxFixtures>({
nativeDropboxConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runNativeDropboxOAuth(request, apiToken, searchSpace.id);
export const nativeDropboxFixtures = workspaceFixtures.extend<NativeDropboxFixtures>({
nativeDropboxConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runNativeDropboxOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"nativeDropboxConnector fixture: OAuth completed but no DROPBOX_CONNECTOR was created. " +

View file

@ -3,20 +3,20 @@ import {
deleteConnector,
runNativeGoogleGmailOAuth,
} from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type NativeGmailFixtures = {
/**
* A pre-connected native Google Gmail connector inside the fixture's
* `searchSpace`. OAuth uses E2E native Google fakes and is cleaned up
* `workspace`. OAuth uses E2E native Google fakes and is cleaned up
* automatically after the test.
*/
nativeGmailConnector: ConnectorRow;
};
export const nativeGmailFixtures = searchSpaceFixtures.extend<NativeGmailFixtures>({
nativeGmailConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runNativeGoogleGmailOAuth(request, apiToken, searchSpace.id);
export const nativeGmailFixtures = workspaceFixtures.extend<NativeGmailFixtures>({
nativeGmailConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runNativeGoogleGmailOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"nativeGmailConnector fixture: OAuth completed but no GOOGLE_GMAIL_CONNECTOR was created. " +

View file

@ -3,20 +3,20 @@ import {
deleteConnector,
runNativeOneDriveOAuth,
} from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type NativeOneDriveFixtures = {
/**
* A pre-connected native Microsoft OneDrive connector inside the
* fixture's `searchSpace`. OAuth uses E2E Microsoft Graph fakes and is
* fixture's `workspace`. OAuth uses E2E Microsoft Graph fakes and is
* cleaned up automatically after the test.
*/
nativeOneDriveConnector: ConnectorRow;
};
export const nativeOneDriveFixtures = searchSpaceFixtures.extend<NativeOneDriveFixtures>({
nativeOneDriveConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runNativeOneDriveOAuth(request, apiToken, searchSpace.id);
export const nativeOneDriveFixtures = workspaceFixtures.extend<NativeOneDriveFixtures>({
nativeOneDriveConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runNativeOneDriveOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"nativeOneDriveConnector fixture: OAuth completed but no ONEDRIVE_CONNECTOR was created. " +

View file

@ -1,17 +1,17 @@
import { type ConnectorRow, deleteConnector, runNotionOAuth } from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type NotionFixtures = {
/**
* A pre-connected Notion connector inside the fixture's `searchSpace`.
* A pre-connected Notion connector inside the fixture's `workspace`.
* OAuth uses E2E Notion fakes and is cleaned up automatically after the test.
*/
notionConnector: ConnectorRow;
};
export const notionFixtures = searchSpaceFixtures.extend<NotionFixtures>({
notionConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runNotionOAuth(request, apiToken, searchSpace.id);
export const notionFixtures = workspaceFixtures.extend<NotionFixtures>({
notionConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runNotionOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"notionConnector fixture: OAuth completed but no NOTION_CONNECTOR was created. " +

View file

@ -1,18 +1,18 @@
import { type ConnectorRow, deleteConnector, runSlackOAuth } from "../../helpers/api/connectors";
import { searchSpaceFixtures } from "../workspace.fixture";
import { workspaceFixtures } from "../workspace.fixture";
export type SlackFixtures = {
/**
* A pre-connected Slack connector inside the fixture's `searchSpace`.
* A pre-connected Slack connector inside the fixture's `workspace`.
* OAuth and MCP tool calls use E2E Slack fakes and are cleaned up
* automatically after the test.
*/
slackConnector: ConnectorRow;
};
export const slackFixtures = searchSpaceFixtures.extend<SlackFixtures>({
slackConnector: async ({ request, apiToken, searchSpace }, use) => {
const { connector } = await runSlackOAuth(request, apiToken, searchSpace.id);
export const slackFixtures = workspaceFixtures.extend<SlackFixtures>({
slackConnector: async ({ request, apiToken, workspace }, use) => {
const { connector } = await runSlackOAuth(request, apiToken, workspace.id);
if (!connector) {
throw new Error(
"slackConnector fixture: OAuth completed but no SLACK_CONNECTOR was created. " +

View file

@ -7,7 +7,7 @@
*
* Inheritance chain:
* base (@playwright/test)
* searchSpaceFixtures apiToken, searchSpace
* workspaceFixtures apiToken, workspace
* composioDriveFixtures composioDriveConnector
* composioDriveWithChatTest chatThread
* composioGmailFixtures composioGmailConnector
@ -36,7 +36,7 @@
* clickupWithChatTest chatThread
* slackFixtures slackConnector
* slackWithChatTest chatThread
* manualUploadWithChatTest chatThread (no connector; uses base search space)
* manualUploadWithChatTest chatThread (no connector; uses base workspace)
*
* To add a new connector (Gmail, Slack, manual upload, etc.):
* 1. Add a fixture file under `fixtures/connectors/<name>.fixture.ts`.
@ -59,7 +59,7 @@ export { nativeGmailFixtures } from "./connectors/native-gmail.fixture";
export { nativeOneDriveFixtures } from "./connectors/native-onedrive.fixture";
export { notionFixtures } from "./connectors/notion.fixture";
export { slackFixtures } from "./connectors/slack.fixture";
export { searchSpaceFixtures } from "./workspace.fixture";
export { workspaceFixtures } from "./workspace.fixture";
import { type ChatThreadFixtures, chatThreadFixtures } from "./chat-thread.fixture";
import { clickupFixtures } from "./connectors/clickup.fixture";
@ -76,10 +76,10 @@ import { nativeGmailFixtures } from "./connectors/native-gmail.fixture";
import { nativeOneDriveFixtures } from "./connectors/native-onedrive.fixture";
import { notionFixtures } from "./connectors/notion.fixture";
import { slackFixtures } from "./connectors/slack.fixture";
import { searchSpaceFixtures } from "./workspace.fixture";
import { workspaceFixtures } from "./workspace.fixture";
/** Default `test` for specs that just need auth + a clean search space. */
export const test = searchSpaceFixtures;
/** Default `test` for specs that just need auth + a clean workspace. */
export const test = workspaceFixtures;
/** `test` for specs that also need a pre-connected Composio Drive connector. */
export const composioDriveTest = composioDriveFixtures;
/** `test` for Drive specs that also need a chat thread. */
@ -149,7 +149,7 @@ export const slackWithChatTest = slackFixtures.extend<ChatThreadFixtures>(chatTh
* `test` for manual upload specs that also need a chat thread.
*
* Manual upload has no connector fixture the user uploads files directly via
* the Documents-sidebar UI so this composes chat onto the bare search-space.
* the Documents-sidebar UI so this composes chat onto the bare workspace.
*/
export const manualUploadWithChatTest =
searchSpaceFixtures.extend<ChatThreadFixtures>(chatThreadFixtures);
workspaceFixtures.extend<ChatThreadFixtures>(chatThreadFixtures);

View file

@ -2,24 +2,20 @@ import fs from "node:fs";
import path from "node:path";
import { test as base } from "@playwright/test";
import { acquireTestToken } from "../helpers/api/auth";
import {
createSearchSpace,
deleteSearchSpace,
type SearchSpaceRow,
} from "../helpers/api/workspaces";
import { uniqueSearchSpaceName } from "../helpers/canary";
import { createWorkspace, deleteWorkspace, type WorkspaceRow } from "../helpers/api/workspaces";
import { uniqueWorkspaceName } from "../helpers/canary";
export type SearchSpaceFixtures = {
export type WorkspaceFixtures = {
/**
* Bearer token for the seeded test user. Worker-scoped so we only
* log in once per worker (logins are cheap, but caching is cheaper).
*/
apiToken: string;
/**
* A fresh, named search space for the current test. Cleaned up
* A fresh, named workspace for the current test. Cleaned up
* automatically after the test finishes.
*/
searchSpace: SearchSpaceRow;
workspace: WorkspaceRow;
};
const SESSION_COOKIE_NAME = process.env.SESSION_COOKIE_NAME || "surfsense_session";
@ -45,7 +41,7 @@ function loadCachedSessionToken(): string | null {
return null;
}
export const searchSpaceFixtures = base.extend<SearchSpaceFixtures, { apiTokenWorker: string }>({
export const workspaceFixtures = base.extend<WorkspaceFixtures, { apiTokenWorker: string }>({
apiTokenWorker: [
async ({ playwright }, use) => {
const cached = loadCachedSessionToken();
@ -66,16 +62,16 @@ export const searchSpaceFixtures = base.extend<SearchSpaceFixtures, { apiTokenWo
apiToken: async ({ apiTokenWorker }, use) => {
await use(apiTokenWorker);
},
searchSpace: async ({ request, apiToken }, use) => {
const space = await createSearchSpace(
workspace: async ({ request, apiToken }, use) => {
const space = await createWorkspace(
request,
apiToken,
uniqueSearchSpaceName("composio-drive-e2e")
uniqueWorkspaceName("composio-drive-e2e")
);
try {
await use(space);
} finally {
await deleteSearchSpace(request, apiToken, space.id);
await deleteWorkspace(request, apiToken, space.id);
}
},
});

View file

@ -18,13 +18,13 @@ function isRecord(value: unknown): value is Record<string, unknown> {
export async function streamChatToCompletion(
request: APIRequestContext,
token: string,
args: { searchSpaceId: number; threadId: number; query: string }
args: { workspaceId: number; threadId: number; query: string }
): Promise<ChatStreamResult> {
const response = await request.post(`${BACKEND_URL}/api/v1/new_chat`, {
headers: authHeaders(token),
data: {
chat_id: args.threadId,
workspace_id: args.searchSpaceId,
workspace_id: args.workspaceId,
user_query: args.query,
},
});

View file

@ -13,10 +13,10 @@ export type ConnectorRow = {
export async function listConnectors(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<ConnectorRow[]> {
const response = await request.get(
`${BACKEND_URL}/api/v1/search-source-connectors?workspace_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/search-source-connectors?workspace_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!response.ok()) {
@ -111,11 +111,11 @@ export async function triggerIndex(
request: APIRequestContext,
token: string,
connectorId: number,
searchSpaceId: number,
workspaceId: number,
body: IndexBody
): Promise<{ ok: true }> {
const response = await request.post(
`${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}/index?workspace_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}/index?workspace_id=${workspaceId}`,
{ headers: authHeaders(token), data: body }
);
if (!response.ok()) {
@ -130,11 +130,11 @@ export async function triggerIndexExpectDisabled(
request: APIRequestContext,
token: string,
connectorId: number,
searchSpaceId: number,
workspaceId: number,
body: IndexBody = {}
): Promise<{ indexing_started: false; message?: string }> {
const response = await request.post(
`${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}/index?workspace_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/search-source-connectors/${connectorId}/index?workspace_id=${workspaceId}`,
{ headers: authHeaders(token), data: body }
);
if (!response.ok()) {
@ -166,7 +166,7 @@ export async function triggerIndexExpectDisabled(
export async function runComposioOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number,
workspaceId: number,
toolkitId: "googledrive" | "gmail" | "googlecalendar" = "googledrive"
): Promise<{
authUrl: string;
@ -175,7 +175,7 @@ export async function runComposioOAuth(
}> {
// Step 1: kick off OAuth, get auth_url.
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/composio/connector/add?space_id=${searchSpaceId}&toolkit_id=${toolkitId}`,
`${BACKEND_URL}/api/v1/auth/composio/connector/add?space_id=${workspaceId}&toolkit_id=${toolkitId}`,
{
headers: authHeaders(token),
}
@ -202,7 +202,7 @@ export async function runComposioOAuth(
const location = callbackResp.headers().location ?? auth_url;
// Step 3: look up the resulting connector (if any).
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const composioType =
toolkitId === "googledrive"
? "COMPOSIO_GOOGLE_DRIVE_CONNECTOR"
@ -223,14 +223,14 @@ export async function runComposioOAuth(
export async function runNativeGoogleDriveOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/google/drive/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/google/drive/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -250,7 +250,7 @@ export async function runNativeGoogleDriveOAuth(
});
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "GOOGLE_DRIVE_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -266,14 +266,14 @@ export async function runNativeGoogleDriveOAuth(
export async function runNativeOneDriveOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/onedrive/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/onedrive/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -306,7 +306,7 @@ export async function runNativeOneDriveOAuth(
}
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "ONEDRIVE_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -322,14 +322,14 @@ export async function runNativeOneDriveOAuth(
export async function runNativeDropboxOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/dropbox/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/dropbox/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -362,7 +362,7 @@ export async function runNativeDropboxOAuth(
}
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "DROPBOX_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -377,14 +377,14 @@ export async function runNativeDropboxOAuth(
export async function runNativeGoogleGmailOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/google/gmail/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/google/gmail/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -404,7 +404,7 @@ export async function runNativeGoogleGmailOAuth(
});
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "GOOGLE_GMAIL_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -419,14 +419,14 @@ export async function runNativeGoogleGmailOAuth(
export async function runNativeGoogleCalendarOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: 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}`,
`${BACKEND_URL}/api/v1/auth/google/calendar/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -446,7 +446,7 @@ export async function runNativeGoogleCalendarOAuth(
});
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector =
connectors.find((c) => c.connector_type === "GOOGLE_CALENDAR_CONNECTOR") ?? null;
@ -464,14 +464,14 @@ export async function runNativeGoogleCalendarOAuth(
export async function runNotionOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/mcp/notion/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/mcp/notion/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -499,7 +499,7 @@ export async function runNotionOAuth(
);
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "NOTION_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -518,14 +518,14 @@ export async function runNotionOAuth(
export async function runConfluenceOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/mcp/confluence/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/mcp/confluence/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -553,7 +553,7 @@ export async function runConfluenceOAuth(
);
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "CONFLUENCE_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -568,14 +568,14 @@ export async function runConfluenceOAuth(
export async function runLinearOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/mcp/linear/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/mcp/linear/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -603,7 +603,7 @@ export async function runLinearOAuth(
);
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "LINEAR_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -618,14 +618,14 @@ export async function runLinearOAuth(
export async function runJiraOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/mcp/jira/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/mcp/jira/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -653,7 +653,7 @@ export async function runJiraOAuth(
);
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "JIRA_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -668,14 +668,14 @@ export async function runJiraOAuth(
export async function runClickupOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/mcp/clickup/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/mcp/clickup/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -703,7 +703,7 @@ export async function runClickupOAuth(
);
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "CLICKUP_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };
@ -718,14 +718,14 @@ export async function runClickupOAuth(
export async function runSlackOAuth(
request: APIRequestContext,
token: string,
searchSpaceId: number
workspaceId: number
): Promise<{
authUrl: string;
finalUrl: string;
connector: ConnectorRow | null;
}> {
const initiateResp = await request.get(
`${BACKEND_URL}/api/v1/auth/mcp/slack/connector/add?space_id=${searchSpaceId}`,
`${BACKEND_URL}/api/v1/auth/mcp/slack/connector/add?space_id=${workspaceId}`,
{ headers: authHeaders(token) }
);
if (!initiateResp.ok()) {
@ -758,7 +758,7 @@ export async function runSlackOAuth(
}
const location = callbackResp.headers().location ?? auth_url;
const connectors = await listConnectors(request, token, searchSpaceId);
const connectors = await listConnectors(request, token, workspaceId);
const connector = connectors.find((c) => c.connector_type === "SLACK_CONNECTOR") ?? null;
return { authUrl: auth_url, finalUrl: location, connector };

View file

@ -17,11 +17,11 @@ type Paginated<T> = {
export async function listDocuments(
request: APIRequestContext,
token: string,
searchSpaceId: number,
workspaceId: number,
limit = 100
): Promise<DocumentRow[]> {
const response = await request.get(
`${BACKEND_URL}/api/v1/documents?workspace_id=${searchSpaceId}&limit=${limit}`,
`${BACKEND_URL}/api/v1/documents?workspace_id=${workspaceId}&limit=${limit}`,
{ headers: authHeaders(token) }
);
if (!response.ok()) {
@ -51,11 +51,11 @@ export type EditorContent = {
export async function getEditorContent(
request: APIRequestContext,
token: string,
searchSpaceId: number,
workspaceId: number,
documentId: number
): Promise<EditorContent> {
const response = await request.get(
`${BACKEND_URL}/api/v1/workspaces/${searchSpaceId}/documents/${documentId}/editor-content`,
`${BACKEND_URL}/api/v1/workspaces/${workspaceId}/documents/${documentId}/editor-content`,
{ headers: authHeaders(token) }
);
if (!response.ok()) {

View file

@ -1,29 +1,29 @@
import type { APIRequestContext } from "@playwright/test";
import { authHeaders, BACKEND_URL } from "./auth";
export type SearchSpaceRow = {
export type WorkspaceRow = {
id: number;
name: string;
description: string | null;
};
export async function createSearchSpace(
export async function createWorkspace(
request: APIRequestContext,
token: string,
name: string,
description = "E2E test search space"
): Promise<SearchSpaceRow> {
description = "E2E test workspace"
): Promise<WorkspaceRow> {
const response = await request.post(`${BACKEND_URL}/api/v1/workspaces`, {
headers: authHeaders(token),
data: { name, description },
});
if (!response.ok()) {
throw new Error(`createSearchSpace failed (${response.status()}): ${await response.text()}`);
throw new Error(`createWorkspace failed (${response.status()}): ${await response.text()}`);
}
return (await response.json()) as SearchSpaceRow;
return (await response.json()) as WorkspaceRow;
}
export async function deleteSearchSpace(
export async function deleteWorkspace(
request: APIRequestContext,
token: string,
id: number
@ -34,7 +34,7 @@ export async function deleteSearchSpace(
if (!response.ok() && response.status() !== 404) {
// 404 is acceptable: the test may have already deleted the space.
throw new Error(
`deleteSearchSpace(${id}) failed (${response.status()}): ${await response.text()}`
`deleteWorkspace(${id}) failed (${response.status()}): ${await response.text()}`
);
}
}

View file

@ -223,7 +223,7 @@ export const FAKE_CLICKUP_TASKS = {
},
} as const;
/** Generate a unique-per-run search space name. Keeps parallel tests isolated. */
export function uniqueSearchSpaceName(prefix = "e2e"): string {
/** Generate a unique-per-run workspace name. Keeps parallel tests isolated. */
export function uniqueWorkspaceName(prefix = "e2e"): string {
return `${prefix}-${randomUUID().slice(0, 8)}`;
}

View file

@ -6,16 +6,16 @@ import { expect } from "@playwright/test";
* route changes only require an update in one place.
*/
export function newChatUrl(searchSpaceId: number): string {
return `/dashboard/${searchSpaceId}/new-chat`;
export function newChatUrl(workspaceId: number): string {
return `/dashboard/${workspaceId}/new-chat`;
}
export function connectorsCallbackUrl(searchSpaceId: number): string {
return `/dashboard/${searchSpaceId}/connectors/callback`;
export function connectorsCallbackUrl(workspaceId: number): string {
return `/dashboard/${workspaceId}/connectors/callback`;
}
export async function gotoNewChat(page: Page, searchSpaceId: number): Promise<void> {
const target = newChatUrl(searchSpaceId);
export async function gotoNewChat(page: Page, workspaceId: number): Promise<void> {
const target = newChatUrl(workspaceId);
await page.goto(target, { waitUntil: "domcontentloaded" });
await expect(page).toHaveURL((url) => url.pathname === target);
}

View file

@ -11,7 +11,7 @@ export async function waitForIndexingComplete(
request: APIRequestContext,
token: string,
connectorId: number,
searchSpaceId: number,
workspaceId: number,
options: { timeoutMs?: number; intervalMs?: number; minDocuments?: number } = {}
): Promise<void> {
const timeoutMs = options.timeoutMs ?? 60_000;
@ -23,7 +23,7 @@ export async function waitForIndexingComplete(
while (Date.now() - startedAt < timeoutMs) {
const connector = await getConnector(request, token, connectorId);
const docs = await listDocuments(request, token, searchSpaceId);
const docs = await listDocuments(request, token, workspaceId);
const readyDocs = docs.filter(isDocumentReady);
const connectorIndexed = connector.last_indexed_at !== null;
@ -39,19 +39,19 @@ export async function waitForIndexingComplete(
throw new Error(
`waitForIndexingComplete: timed out after ${timeoutMs}ms waiting for ` +
`connector ${connectorId} in space ${searchSpaceId}. Last observed: ${lastState}`
`connector ${connectorId} in space ${workspaceId}. Last observed: ${lastState}`
);
}
/**
* Polls until the given document title appears in the search space with
* Polls until the given document title appears in the workspace with
* status=ready. Useful when a spec wants to assert on a specific file
* by name rather than count.
*/
export async function waitForDocumentByTitle(
request: APIRequestContext,
token: string,
searchSpaceId: number,
workspaceId: number,
title: string,
options: { timeoutMs?: number; intervalMs?: number } = {}
): Promise<void> {
@ -60,7 +60,7 @@ export async function waitForDocumentByTitle(
const startedAt = Date.now();
while (Date.now() - startedAt < timeoutMs) {
const docs = await listDocuments(request, token, searchSpaceId);
const docs = await listDocuments(request, token, workspaceId);
const match = docs.find((d) => d.title === title && isDocumentReady(d));
if (match) {
return;
@ -70,6 +70,6 @@ export async function waitForDocumentByTitle(
throw new Error(
`waitForDocumentByTitle: timed out after ${timeoutMs}ms waiting for ` +
`title=${JSON.stringify(title)} in space ${searchSpaceId}.`
`title=${JSON.stringify(title)} in space ${workspaceId}.`
);
}

View file

@ -3,16 +3,12 @@ import { authHeaders, BACKEND_URL } from "../helpers/api/auth";
import { streamChatToCompletion } from "../helpers/api/chat";
test.describe("Smoke", () => {
test("chat stream completes for an unrelated query", async ({
request,
apiToken,
searchSpace,
}) => {
test("chat stream completes for an unrelated query", async ({ request, apiToken, workspace }) => {
const threadResponse = await request.post(`${BACKEND_URL}/api/v1/threads`, {
headers: authHeaders(apiToken),
data: {
title: "e2e-chat-stream-smoke",
workspace_id: searchSpace.id,
workspace_id: workspace.id,
visibility: "PRIVATE",
},
});
@ -20,7 +16,7 @@ test.describe("Smoke", () => {
const thread = (await threadResponse.json()) as { id: number };
const chat = await streamChatToCompletion(request, apiToken, {
searchSpaceId: searchSpace.id,
workspaceId: workspace.id,
threadId: thread.id,
query: "E2E_NO_RELEVANT_CONTENT_SMOKE",
});