diff --git a/surfsense_backend/tests/e2e/fakes/chat_llm.py b/surfsense_backend/tests/e2e/fakes/chat_llm.py index 728288c74..fa3a2b158 100644 --- a/surfsense_backend/tests/e2e/fakes/chat_llm.py +++ b/surfsense_backend/tests/e2e/fakes/chat_llm.py @@ -44,6 +44,10 @@ SLACK_CANARY_CHANNEL = "slack-e2e-canary" CLICKUP_CANARY_TOKEN = "SURFSENSE_E2E_CANARY_TOKEN_CLICKUP_001" CLICKUP_CANARY_TITLE = "E2E Canary ClickUp Task" CLICKUP_CANARY_TASK_ID = "fake-clickup-task-canary-001" +MANUAL_UPLOAD_MD_CANARY_TOKEN = "E2E-MANUAL-UPLOAD-MD-CANARY-7f3a" +MANUAL_UPLOAD_MD_CANARY_FILE = "canary.md" +MANUAL_UPLOAD_PDF_CANARY_TOKEN = "E2E-MANUAL-UPLOAD-PDF-CANARY-9d2b" +MANUAL_UPLOAD_PDF_CANARY_FILE = "canary.pdf" NO_RELEVANT_CONTENT_SENTINEL = "No relevant indexed content found." NO_RELEVANT_CONTENT_QUERY = "E2E_NO_RELEVANT_CONTENT_SMOKE" @@ -208,6 +212,30 @@ class FakeChatLLM(BaseChatModel): latest_human, ("clickup", CLICKUP_CANARY_TITLE), ) + wants_manual_upload = _contains_any( + latest_human, + ( + "uploaded", + "manual upload", + MANUAL_UPLOAD_MD_CANARY_FILE, + MANUAL_UPLOAD_PDF_CANARY_FILE, + MANUAL_UPLOAD_MD_CANARY_TOKEN, + MANUAL_UPLOAD_PDF_CANARY_TOKEN, + ), + ) + wants_manual_upload_pdf = wants_manual_upload and _contains_any( + latest_human, + ("pdf", MANUAL_UPLOAD_PDF_CANARY_FILE, MANUAL_UPLOAD_PDF_CANARY_TOKEN), + ) + wants_manual_upload_md = wants_manual_upload and _contains_any( + latest_human, + ( + "markdown", + ".md", + MANUAL_UPLOAD_MD_CANARY_FILE, + MANUAL_UPLOAD_MD_CANARY_TOKEN, + ), + ) has_gmail_evidence = ( GMAIL_CANARY_SUBJECT in prompt_text or GMAIL_CANARY_MESSAGE_ID in prompt_text @@ -285,6 +313,14 @@ class FakeChatLLM(BaseChatModel): or CLICKUP_CANARY_TOKEN in prompt_text or CLICKUP_CANARY_TASK_ID in prompt_text ) + has_manual_upload_md_evidence = ( + MANUAL_UPLOAD_MD_CANARY_FILE in prompt_text + or MANUAL_UPLOAD_MD_CANARY_TOKEN in prompt_text + ) + has_manual_upload_pdf_evidence = ( + MANUAL_UPLOAD_PDF_CANARY_FILE in prompt_text + or MANUAL_UPLOAD_PDF_CANARY_TOKEN in prompt_text + ) if wants_clickup and has_clickup_evidence: return f"ClickUp content found: {CLICKUP_CANARY_TOKEN}" @@ -316,6 +352,10 @@ class FakeChatLLM(BaseChatModel): return f"Drive PDF content found: {COMPOSIO_DRIVE_PDF_CANARY_TOKEN}" if wants_drive and has_drive_evidence: return f"Drive content found: {DRIVE_CANARY_TOKEN}" + if wants_manual_upload_pdf and has_manual_upload_pdf_evidence: + return f"Manual upload PDF content found: {MANUAL_UPLOAD_PDF_CANARY_TOKEN}" + if wants_manual_upload_md and has_manual_upload_md_evidence: + return f"Manual upload MD content found: {MANUAL_UPLOAD_MD_CANARY_TOKEN}" if ( has_notion_evidence and not has_confluence_evidence @@ -468,6 +508,36 @@ class FakeChatLLM(BaseChatModel): and not has_slack_evidence ): return f"ClickUp content found: {CLICKUP_CANARY_TOKEN}" + if ( + has_manual_upload_pdf_evidence + and not has_confluence_evidence + and not has_jira_evidence + and not has_linear_evidence + and not has_notion_evidence + and not has_calendar_evidence + and not has_gmail_evidence + and not has_drive_evidence + and not has_onedrive_evidence + and not has_dropbox_evidence + and not has_slack_evidence + and not has_clickup_evidence + ): + return f"Manual upload PDF content found: {MANUAL_UPLOAD_PDF_CANARY_TOKEN}" + if ( + has_manual_upload_md_evidence + and not has_confluence_evidence + and not has_jira_evidence + and not has_linear_evidence + and not has_notion_evidence + and not has_calendar_evidence + and not has_gmail_evidence + and not has_drive_evidence + and not has_onedrive_evidence + and not has_dropbox_evidence + and not has_slack_evidence + and not has_clickup_evidence + ): + return f"Manual upload MD content found: {MANUAL_UPLOAD_MD_CANARY_TOKEN}" return NO_RELEVANT_CONTENT_SENTINEL def _tool_call_message_for(self, messages: list[BaseMessage]) -> AIMessage | None: diff --git a/surfsense_backend/tests/e2e/run_backend.py b/surfsense_backend/tests/e2e/run_backend.py index 8f0658037..4156a4ea4 100644 --- a/surfsense_backend/tests/e2e/run_backend.py +++ b/surfsense_backend/tests/e2e/run_backend.py @@ -91,8 +91,7 @@ logging.basicConfig( ) logger = logging.getLogger("surfsense.e2e.backend") logger.warning( - "*** SURFSENSE E2E BACKEND ENTRYPOINT — fake Composio + LLM + embeddings, " - "this MUST NOT be reachable in production. ***" + "*** SURFSENSE E2E BACKEND ENTRYPOINT — fake Composio + LLM + embeddings ***" ) diff --git a/surfsense_backend/tests/e2e/run_celery.py b/surfsense_backend/tests/e2e/run_celery.py index 1d75c20dc..b28f67ec1 100644 --- a/surfsense_backend/tests/e2e/run_celery.py +++ b/surfsense_backend/tests/e2e/run_celery.py @@ -78,8 +78,7 @@ logging.basicConfig( ) logger = logging.getLogger("surfsense.e2e.celery") logger.warning( - "*** SURFSENSE E2E CELERY WORKER — fake Composio + LLM + embeddings, " - "this MUST NOT be reachable in production. ***" + "*** SURFSENSE E2E CELERY WORKER — fake Composio + LLM + embeddings ***" ) diff --git a/surfsense_web/tests/documents/file-upload/journey.spec.ts b/surfsense_web/tests/documents/file-upload/journey.spec.ts index 9a7de04c5..6ddfb522f 100644 --- a/surfsense_web/tests/documents/file-upload/journey.spec.ts +++ b/surfsense_web/tests/documents/file-upload/journey.spec.ts @@ -1,6 +1,8 @@ import path from "node:path"; import type { APIRequestContext, Page } from "@playwright/test"; -import { expect, test } from "../../fixtures"; +import { expect, manualUploadWithChatTest as test } from "../../fixtures"; +import type { ChatThreadRow } from "../../fixtures/chat-thread.fixture"; +import { streamChatToCompletion } from "../../helpers/api/chat"; import { getEditorContent, listDocuments } from "../../helpers/api/documents"; import { CANARY_TOKENS } from "../../helpers/canary"; import { waitForDocumentByTitle } from "../../helpers/waits/indexing"; @@ -9,6 +11,7 @@ type UploadFixture = { path: string; name: string; canary: string; + chatQuery: string; }; type SearchSpace = { @@ -19,12 +22,14 @@ const MD_FILE: UploadFixture = { path: path.join(__dirname, "fixtures", "canary.md"), name: "canary.md", canary: CANARY_TOKENS.manualUploadMdCanary, + chatQuery: "What is in my uploaded canary.md markdown file?", }; const PDF_FILE: UploadFixture = { path: path.join(__dirname, "fixtures", "canary.pdf"), name: "canary.pdf", canary: CANARY_TOKENS.manualUploadPdfCanary, + chatQuery: "What is in my uploaded canary.pdf file?", }; async function uploadAndAssert({ @@ -32,12 +37,14 @@ async function uploadAndAssert({ request, apiToken, searchSpace, + chatThread, file, }: { page: Page; request: APIRequestContext; apiToken: string; searchSpace: SearchSpace; + chatThread: ChatThreadRow; file: UploadFixture; }) { await page.goto(`/dashboard/${searchSpace.id}/new-chat`, { @@ -68,6 +75,16 @@ async function uploadAndAssert({ const editor = await getEditorContent(request, apiToken, searchSpace.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, + threadId: chatThread.id, + query: file.chatQuery, + }); + expect( + chat.assistantText, + `chat agent should surface manual upload canary after indexing; got: ${chat.assistantText.slice(0, 200)}` + ).toContain(file.canary); } test.describe("Manual file upload journey", () => { @@ -76,10 +93,18 @@ test.describe("Manual file upload journey", () => { request, apiToken, searchSpace, + chatThread, }) => { test.setTimeout(180_000); - await uploadAndAssert({ page, request, apiToken, searchSpace, file: MD_FILE }); + await uploadAndAssert({ + page, + request, + apiToken, + searchSpace, + chatThread, + file: MD_FILE, + }); }); test("user uploads a PDF (DOCUMENT branch via real Docling)", async ({ @@ -87,9 +112,17 @@ test.describe("Manual file upload journey", () => { request, apiToken, searchSpace, + chatThread, }) => { test.setTimeout(240_000); // Docling cold-start can take 30-60s on first invocation. - await uploadAndAssert({ page, request, apiToken, searchSpace, file: PDF_FILE }); + await uploadAndAssert({ + page, + request, + apiToken, + searchSpace, + chatThread, + file: PDF_FILE, + }); }); }); diff --git a/surfsense_web/tests/fixtures/index.ts b/surfsense_web/tests/fixtures/index.ts index 6f21b13f9..9c7151112 100644 --- a/surfsense_web/tests/fixtures/index.ts +++ b/surfsense_web/tests/fixtures/index.ts @@ -36,6 +36,7 @@ * └─ clickupWithChatTest — chatThread * └─ slackFixtures — slackConnector * └─ slackWithChatTest — chatThread + * └─ manualUploadWithChatTest — chatThread (no connector; uses base search space) * * To add a new connector (Gmail, Slack, manual upload, etc.): * 1. Add a fixture file under `fixtures/connectors/.fixture.ts`. @@ -144,3 +145,11 @@ export const clickupWithChatTest = clickupFixtures.extend(ch export const slackTest = slackFixtures; /** `test` for Slack specs that also need a chat thread. */ export const slackWithChatTest = slackFixtures.extend(chatThreadFixtures); +/** + * `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. + */ +export const manualUploadWithChatTest = + searchSpaceFixtures.extend(chatThreadFixtures);