From ca9bd28934bb1518de290945a9de2e7c05dda646 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Fri, 26 Jun 2026 18:36:46 +0200 Subject: [PATCH] test: rename SearchSpace -> Workspace across tests + fixtures (Phase 2 Wave F) Apply the same rename to surfsense_backend/tests: workspace_id fields/vars, Workspace* classes/schemas, table name searchspaces -> workspaces in raw SQL, and the API URL spellings -> /workspaces. Preserves the carve-out wire literals tests assert (Celery task names, OTel key "search_space.id"). --- surfsense_backend/tests/conftest.py | 4 +- .../shared/retrieval/test_hybrid_search.py | 58 +++--- .../tools/test_search_knowledge_base.py | 78 ++++---- .../multi_agent_chat/test_agent_turn.py | 18 +- .../test_kb_filesystem_cloud.py | 6 +- .../test_kb_filesystem_desktop.py | 2 +- .../chat/test_append_message_recovery.py | 28 +-- .../integration/chat/test_message_id_sse.py | 6 +- .../integration/chat/test_persistence.py | 42 ++--- .../chat/test_thread_visibility.py | 58 +++--- .../tests/integration/composio/conftest.py | 4 +- .../composio/test_oauth_callback.py | 24 +-- .../tests/integration/conftest.py | 16 +- .../integration/document_upload/conftest.py | 20 +-- .../document_upload/test_document_upload.py | 60 +++---- .../document_upload/test_etl_credits.py | 50 +++--- .../test_stripe_credit_purchases.py | 24 +-- .../document_upload/test_upload_limits.py | 12 +- .../google_unification/conftest.py | 44 ++--- .../test_calendar_indexer_credentials.py | 12 +- .../test_drive_indexer_credentials.py | 12 +- .../test_gmail_indexer_credentials.py | 12 +- .../test_hybrid_search_type_filtering.py | 12 +- .../test_search_includes_legacy_docs.py | 12 +- .../adapters/test_file_upload_adapter.py | 62 +++---- .../test_calendar_pipeline.py | 20 +-- .../indexing_pipeline/test_drive_pipeline.py | 32 ++-- .../test_dropbox_pipeline.py | 22 +-- .../indexing_pipeline/test_gmail_pipeline.py | 20 +-- .../indexing_pipeline/test_index_batch.py | 10 +- .../indexing_pipeline/test_index_document.py | 46 ++--- .../indexing_pipeline/test_index_editions.py | 24 +-- .../test_local_folder_pipeline.py | 168 +++++++++--------- .../test_mark_connector_documents_failed.py | 24 +-- .../test_migrate_legacy_docs.py | 18 +- .../test_onedrive_pipeline.py | 22 +-- .../test_prepare_for_indexing.py | 94 +++++----- .../notifications/test_base_handler.py | 36 ++-- .../test_comment_reply_handler.py | 20 +-- .../test_connector_indexing_handler.py | 42 ++--- .../test_document_processing_handler.py | 26 +-- .../notifications/test_inbox_api.py | 4 +- .../test_insufficient_credits_handler.py | 12 +- .../notifications/test_mention_handler.py | 20 +-- .../tests/integration/podcasts/conftest.py | 14 +- .../integration/podcasts/test_brief_gate.py | 22 +-- .../tests/integration/podcasts/test_cancel.py | 12 +- .../tests/integration/podcasts/test_create.py | 8 +- .../integration/podcasts/test_draft_task.py | 22 +-- .../podcasts/test_public_stream.py | 16 +- .../integration/podcasts/test_regeneration.py | 46 ++--- .../integration/podcasts/test_render_task.py | 12 +- .../integration/podcasts/test_scoping.py | 14 +- .../integration/podcasts/test_streaming.py | 12 +- .../integration/podcasts/test_task_failure.py | 8 +- .../tests/integration/retriever/conftest.py | 28 +-- .../test_optimized_chunk_retriever.py | 20 +-- .../retriever/test_optimized_doc_retriever.py | 12 +- .../integration/test_connector_index_authz.py | 42 ++--- .../integration/test_document_versioning.py | 6 +- .../test_obsidian_plugin_routes.py | 38 ++-- .../integration/test_pat_fail_closed_authz.py | 20 +-- .../integration/test_zero_authz_context.py | 28 +-- .../unit/agents/new_chat/test_action_log.py | 26 +-- .../agents/new_chat/test_mention_resolver.py | 12 +- .../agents/new_chat/test_path_resolver.py | 10 +- .../agents/new_chat/test_plugin_loader.py | 6 +- .../agents/new_chat/test_skills_backends.py | 12 +- .../new_chat/tools/test_resume_page_limits.py | 8 +- .../builtin/agent_task/test_dependencies.py | 56 +++--- .../automations/runtime/test_execute_step.py | 2 +- .../runtime/test_executor_action_ctx.py | 6 +- .../schemas/api/test_api_automation.py | 10 +- .../test_automation_service_policy.py | 62 +++---- .../automations/services/test_model_policy.py | 28 +-- .../automations/templating/test_context.py | 4 +- .../triggers/builtin/event/test_filter.py | 14 +- .../triggers/builtin/event/test_inputs.py | 2 +- .../triggers/builtin/event/test_match.py | 2 +- .../test_confluence_parallel.py | 6 +- .../test_dropbox_parallel.py | 22 +-- .../connector_indexers/test_etl_credits.py | 6 +- .../test_google_drive_parallel.py | 20 +-- .../test_linear_parallel.py | 6 +- .../test_notion_parallel.py | 6 +- .../test_onedrive_parallel.py | 12 +- .../tests/unit/event_bus/test_bus.py | 10 +- .../test_entered_folder_predicate.py | 4 +- .../tests/unit/event_bus/test_event.py | 12 +- .../tests/unit/gateway/test_webhook_routes.py | 4 +- .../test_connector_document.py | 20 +-- .../test_create_placeholder_documents.py | 6 +- .../test_document_hashing.py | 22 +-- .../indexing_pipeline/test_index_batch.py | 6 +- .../test_index_batch_parallel.py | 8 +- .../test_migrate_legacy_docs.py | 8 +- .../test_prepare_placeholder_dedup.py | 2 +- .../test_b_filesystem_rm_rmdir_cloud.py | 6 +- .../middleware/test_filesystem_backends.py | 8 +- .../test_kb_persistence_filesystem_parity.py | 10 +- .../test_kb_persistence_revisions.py | 6 +- .../unit/middleware/test_kb_postgres_read.py | 2 +- .../unit/middleware/test_knowledge_tree.py | 2 +- .../unit/notifications/api/test_transform.py | 2 +- .../messages/test_document_processing.py | 2 +- .../messages/test_insufficient_credits.py | 4 +- .../tests/unit/observability/test_otel.py | 4 +- .../unit/observability/test_retriever_otel.py | 6 +- .../tests/unit/podcasts/test_api_schemas.py | 2 +- .../tests/unit/routes/test_image_gen_quota.py | 22 +-- .../routes/test_regenerate_from_message_id.py | 6 +- .../services/test_agent_billing_resolver.py | 90 +++++----- .../unit/services/test_ai_sort_task_dedupe.py | 6 +- .../services/test_auto_model_pin_service.py | 44 ++--- .../services/test_auto_pin_image_aware.py | 14 +- .../tests/unit/services/test_billable_call.py | 22 +-- .../test_image_gen_api_base_defense.py | 16 +- .../services/test_quota_checked_vision_llm.py | 8 +- .../services/test_revert_filesystem_tools.py | 6 +- .../tasks/chat/streaming/test_llm_bundle.py | 14 +- .../tasks/test_video_presentation_billing.py | 36 ++-- .../tests/unit/test_pat_fail_closed_static.py | 6 +- .../tests/unit/utils/test_validators.py | 10 +- surfsense_backend/tests/utils/helpers.py | 28 +-- 124 files changed, 1269 insertions(+), 1269 deletions(-) diff --git a/surfsense_backend/tests/conftest.py b/surfsense_backend/tests/conftest.py index e227ed287..9e5264062 100644 --- a/surfsense_backend/tests/conftest.py +++ b/surfsense_backend/tests/conftest.py @@ -37,7 +37,7 @@ def sample_user_id() -> str: @pytest.fixture -def sample_search_space_id() -> int: +def sample_workspace_id() -> int: return 1 @@ -59,7 +59,7 @@ def make_connector_document(): "source_markdown": "## Heading\n\nSome content.", "unique_id": "test-id-001", "document_type": DocumentType.CLICKUP_CONNECTOR, - "search_space_id": 1, + "workspace_id": 1, "connector_id": 1, "created_by_id": "00000000-0000-0000-0000-000000000001", } diff --git a/surfsense_backend/tests/integration/agents/multi_agent_chat/shared/retrieval/test_hybrid_search.py b/surfsense_backend/tests/integration/agents/multi_agent_chat/shared/retrieval/test_hybrid_search.py index f7ba86a67..0eb204a38 100644 --- a/surfsense_backend/tests/integration/agents/multi_agent_chat/shared/retrieval/test_hybrid_search.py +++ b/surfsense_backend/tests/integration/agents/multi_agent_chat/shared/retrieval/test_hybrid_search.py @@ -18,7 +18,7 @@ from app.agents.chat.multi_agent_chat.shared.retrieval.hybrid_search import ( ) from app.agents.chat.multi_agent_chat.shared.retrieval.models import SearchScope from app.config import config -from app.db import Chunk, Document, DocumentType, SearchSpace +from app.db import Chunk, Document, DocumentType, Workspace pytestmark = pytest.mark.integration @@ -35,7 +35,7 @@ def _axis(index: int) -> list[float]: async def _add_document( db_session, *, - search_space_id: int, + workspace_id: int, title: str = "Doc", document_type: DocumentType = DocumentType.FILE, state: str = "ready", @@ -47,7 +47,7 @@ async def _add_document( document_type=document_type, content="\n".join(content for content, _, _ in chunks), content_hash=uuid.uuid4().hex, - search_space_id=search_space_id, + workspace_id=workspace_id, status={"state": state}, ) db_session.add(document) @@ -65,17 +65,17 @@ async def _add_document( return document -async def test_keyword_relevant_document_is_retrieved(db_session, db_search_space): +async def test_keyword_relevant_document_is_retrieved(db_session, db_workspace): document = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Asyncio Guide", chunks=[("The asyncio library enables concurrency.", 0, _axis(0))], ) results = await search_chunks( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, query="asyncio", scope=SearchScope(), top_k=5, @@ -85,23 +85,23 @@ async def test_keyword_relevant_document_is_retrieved(db_session, db_search_spac assert document.id in {hit.document_id for hit in results} -async def test_semantically_closest_document_ranks_first(db_session, db_search_space): +async def test_semantically_closest_document_ranks_first(db_session, db_workspace): aligned = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Background Work", chunks=[("Parallel execution of background work.", 0, _axis(0))], ) await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Dessert", chunks=[("Recipes for chocolate cake.", 0, _axis(1))], ) results = await search_chunks( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, query="asynchronous coroutines", scope=SearchScope(), top_k=5, @@ -111,25 +111,25 @@ async def test_semantically_closest_document_ranks_first(db_session, db_search_s assert results[0].document_id == aligned.id -async def test_results_stay_within_the_search_space(db_session, db_search_space): - other_space = SearchSpace(name="Other Space", user_id=db_search_space.user_id) +async def test_results_stay_within_the_workspace(db_session, db_workspace): + other_space = Workspace(name="Other Space", user_id=db_workspace.user_id) db_session.add(other_space) await db_session.flush() mine = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, chunks=[("Shared keyword asyncio here.", 0, _axis(0))], ) foreign = await _add_document( db_session, - search_space_id=other_space.id, + workspace_id=other_space.id, chunks=[("Shared keyword asyncio here.", 0, _axis(0))], ) results = await search_chunks( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, query="asyncio", scope=SearchScope(), top_k=5, @@ -140,21 +140,21 @@ async def test_results_stay_within_the_search_space(db_session, db_search_space) assert mine.id in found and foreign.id not in found -async def test_document_ids_scope_pins_results(db_session, db_search_space): +async def test_document_ids_scope_pins_results(db_session, db_workspace): pinned = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, chunks=[("asyncio appears in the pinned doc.", 0, _axis(0))], ) await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, chunks=[("asyncio appears in the other doc too.", 0, _axis(0))], ) results = await search_chunks( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, query="asyncio", scope=SearchScope(document_ids=(pinned.id,)), top_k=5, @@ -164,22 +164,22 @@ async def test_document_ids_scope_pins_results(db_session, db_search_space): assert {hit.document_id for hit in results} == {pinned.id} -async def test_deleting_documents_are_excluded(db_session, db_search_space): +async def test_deleting_documents_are_excluded(db_session, db_workspace): ready = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, chunks=[("asyncio in a ready document.", 0, _axis(0))], ) deleting = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, state="deleting", chunks=[("asyncio in a deleting document.", 0, _axis(0))], ) results = await search_chunks( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, query="asyncio", scope=SearchScope(), top_k=5, @@ -190,12 +190,12 @@ async def test_deleting_documents_are_excluded(db_session, db_search_space): assert ready.id in found and deleting.id not in found -async def test_matched_chunks_are_ordered_for_reading(db_session, db_search_space): +async def test_matched_chunks_are_ordered_for_reading(db_session, db_workspace): # Insert out of order, and give the later-position chunk the stronger # semantic score, so reading order differs from both insertion and score. document = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, chunks=[ ("asyncio paragraph two.", 1, _axis(0)), ("asyncio paragraph one.", 0, _axis(50)), @@ -204,7 +204,7 @@ async def test_matched_chunks_are_ordered_for_reading(db_session, db_search_spac results = await search_chunks( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, query="asyncio", scope=SearchScope(), top_k=5, @@ -215,18 +215,18 @@ async def test_matched_chunks_are_ordered_for_reading(db_session, db_search_spac assert [chunk.position for chunk in hit.chunks] == [0, 1] -async def test_top_k_caps_the_number_of_documents(db_session, db_search_space): +async def test_top_k_caps_the_number_of_documents(db_session, db_workspace): for index in range(3): await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title=f"Doc {index}", chunks=[(f"asyncio mentioned in doc {index}.", 0, _axis(index))], ) results = await search_chunks( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, query="asyncio", scope=SearchScope(), top_k=2, diff --git a/surfsense_backend/tests/integration/agents/multi_agent_chat/subagents/builtins/knowledge_base/tools/test_search_knowledge_base.py b/surfsense_backend/tests/integration/agents/multi_agent_chat/subagents/builtins/knowledge_base/tools/test_search_knowledge_base.py index 09e5f0abf..2a05665cc 100644 --- a/surfsense_backend/tests/integration/agents/multi_agent_chat/subagents/builtins/knowledge_base/tools/test_search_knowledge_base.py +++ b/surfsense_backend/tests/integration/agents/multi_agent_chat/subagents/builtins/knowledge_base/tools/test_search_knowledge_base.py @@ -48,7 +48,7 @@ def _axis(index: int) -> list[float]: async def _add_document( db_session, *, - search_space_id: int, + workspace_id: int, title: str, text: str, folder_id: int | None = None, @@ -58,7 +58,7 @@ async def _add_document( document_type=DocumentType.FILE, content=text, content_hash=uuid.uuid4().hex, - search_space_id=search_space_id, + workspace_id=workspace_id, folder_id=folder_id, status={"state": "ready"}, ) @@ -71,8 +71,8 @@ async def _add_document( return document -async def _add_folder(db_session, *, search_space_id: int, name: str = "Folder"): - folder = Folder(name=name, position="0", search_space_id=search_space_id) +async def _add_folder(db_session, *, workspace_id: int, name: str = "Folder"): + folder = Folder(name=name, position="0", workspace_id=workspace_id) db_session.add(folder) await db_session.flush() return folder @@ -109,15 +109,15 @@ def _mentions(*, document_ids=(), folder_ids=()): async def test_tool_returns_retrieved_context_with_numbered_passages( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Asyncio Guide", text="The asyncio library enables concurrency.", ) - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke(tool, "asyncio") @@ -129,15 +129,15 @@ async def test_tool_returns_retrieved_context_with_numbered_passages( async def test_tool_populates_citation_registry_on_state( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Asyncio Guide", text="The asyncio library enables concurrency.", ) - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke(tool, "asyncio") @@ -147,15 +147,15 @@ async def test_tool_populates_citation_registry_on_state( async def test_tool_reuses_existing_registry_numbering( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Asyncio Guide", text="The asyncio library enables concurrency.", ) - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) first = await _invoke(tool, "asyncio") carried = first.update["citation_registry"] @@ -166,9 +166,9 @@ async def test_tool_reuses_existing_registry_numbering( async def test_tool_reports_no_matches_without_touching_state( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke(tool, "nonexistent-term-zzz") @@ -177,9 +177,9 @@ async def test_tool_reports_no_matches_without_touching_state( async def test_tool_rejects_empty_query( - db_search_space, _tool_uses_test_session, _pinned_embedding + db_workspace, _tool_uses_test_session, _pinned_embedding ): - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke(tool, " ") @@ -188,21 +188,21 @@ async def test_tool_rejects_empty_query( async def test_document_mention_confines_search_to_pinned_doc( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): pinned = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Pinned", text="asyncio appears in the pinned doc.", ) await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Other", text="asyncio appears in the other doc.", ) - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke(tool, "asyncio", context=_mentions(document_ids=[pinned.id])) @@ -213,23 +213,23 @@ async def test_document_mention_confines_search_to_pinned_doc( async def test_folder_mention_confines_search_to_folder_documents( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): - folder = await _add_folder(db_session, search_space_id=db_search_space.id) + folder = await _add_folder(db_session, workspace_id=db_workspace.id) await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Inside", text="asyncio appears inside the folder.", folder_id=folder.id, ) await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Outside", text="asyncio appears outside the folder.", ) - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke(tool, "asyncio", context=_mentions(folder_ids=[folder.id])) @@ -240,7 +240,7 @@ async def test_folder_mention_confines_search_to_folder_documents( async def test_document_mention_via_state_confines_search( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): """The real subagent path: mentions arrive on ``runtime.state`` (no context). @@ -250,17 +250,17 @@ async def test_document_mention_via_state_confines_search( """ pinned = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Pinned", text="asyncio appears in the pinned doc.", ) await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Other", text="asyncio appears in the other doc.", ) - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke( tool, @@ -275,24 +275,24 @@ async def test_document_mention_via_state_confines_search( async def test_folder_mention_via_state_confines_search( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): """Folder pins delivered via state (subagent path) scope to the folder's docs.""" - folder = await _add_folder(db_session, search_space_id=db_search_space.id) + folder = await _add_folder(db_session, workspace_id=db_workspace.id) await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Inside", text="asyncio appears inside the folder.", folder_id=folder.id, ) await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Outside", text="asyncio appears outside the folder.", ) - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke( tool, @@ -307,22 +307,22 @@ async def test_folder_mention_via_state_confines_search( async def test_state_mentions_take_precedence_over_context( - db_session, db_search_space, _tool_uses_test_session, _pinned_embedding + db_session, db_workspace, _tool_uses_test_session, _pinned_embedding ): """When both carry pins, state wins (the forwarded subagent pin is authoritative).""" state_doc = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="StatePinned", text="asyncio appears in the state-pinned doc.", ) context_doc = await _add_document( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="ContextPinned", text="asyncio appears in the context-pinned doc.", ) - tool = create_search_knowledge_base_tool(search_space_id=db_search_space.id) + tool = create_search_knowledge_base_tool(workspace_id=db_workspace.id) result = await _invoke( tool, diff --git a/surfsense_backend/tests/integration/agents/multi_agent_chat/test_agent_turn.py b/surfsense_backend/tests/integration/agents/multi_agent_chat/test_agent_turn.py index d45570484..59b9d2161 100644 --- a/surfsense_backend/tests/integration/agents/multi_agent_chat/test_agent_turn.py +++ b/surfsense_backend/tests/integration/agents/multi_agent_chat/test_agent_turn.py @@ -35,18 +35,18 @@ def _last_ai_text(messages: list) -> str | None: @pytest.mark.asyncio -async def test_agent_runs_a_scripted_text_turn(db_session, db_user, db_search_space): +async def test_agent_runs_a_scripted_text_turn(db_session, db_user, db_workspace): """A freshly assembled agent streams a scripted final-text turn to completion.""" harness = build_scripted_harness(turns=[ScriptedTurn(text="done")]) agent = await create_multi_agent_chat_deep_agent( llm=harness.model, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, db_session=db_session, connector_service=ConnectorService(db_session), checkpointer=InMemorySaver(), user_id=str(db_user.id), - thread_id=db_search_space.id, + thread_id=db_workspace.id, agent_config=None, ) @@ -59,7 +59,7 @@ async def test_agent_runs_a_scripted_text_turn(db_session, db_user, db_search_sp @pytest.mark.asyncio -async def test_agent_routes_a_scripted_tool_call(db_session, db_user, db_search_space): +async def test_agent_routes_a_scripted_tool_call(db_session, db_user, db_workspace): """The compiled graph routes a model tool call to its tool and resumes.""" harness = build_scripted_harness( turns=[ @@ -79,12 +79,12 @@ async def test_agent_routes_a_scripted_tool_call(db_session, db_user, db_search_ agent = await create_multi_agent_chat_deep_agent( llm=harness.model, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, db_session=db_session, connector_service=ConnectorService(db_session), checkpointer=InMemorySaver(), user_id=str(db_user.id), - thread_id=db_search_space.id, + thread_id=db_workspace.id, agent_config=None, additional_tools=harness.tools, ) @@ -101,7 +101,7 @@ async def test_agent_routes_a_scripted_tool_call(db_session, db_user, db_search_ @pytest.mark.asyncio async def test_agent_checkpoint_round_trips_across_turns( - db_session, db_user, db_search_space + db_session, db_user, db_workspace ): """Turn 2 sees turn 1's history, proving the checkpoint serializes and reloads. @@ -118,12 +118,12 @@ async def test_agent_checkpoint_round_trips_across_turns( async def _build(): return await create_multi_agent_chat_deep_agent( llm=harness.model, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, db_session=db_session, connector_service=ConnectorService(db_session), checkpointer=checkpointer, user_id=str(db_user.id), - thread_id=db_search_space.id, + thread_id=db_workspace.id, agent_config=None, ) diff --git a/surfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_filesystem_cloud.py b/surfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_filesystem_cloud.py index 878473f55..c73382705 100644 --- a/surfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_filesystem_cloud.py +++ b/surfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_filesystem_cloud.py @@ -40,16 +40,16 @@ _SEARCH_SPACE_ID = 1 def _build_cloud_fs_mw(): """Build the production filesystem middleware in cloud mode. - A non-None ``search_space_id`` makes the resolver hand out a + A non-None ``workspace_id`` makes the resolver hand out a ``KBPostgresBackend``, exactly as production does. Staging operations never touch the DB, so a dummy id is sufficient for these tests. """ selection = FilesystemSelection(mode=FilesystemMode.CLOUD) - resolver = build_backend_resolver(selection, search_space_id=_SEARCH_SPACE_ID) + resolver = build_backend_resolver(selection, workspace_id=_SEARCH_SPACE_ID) return build_filesystem_mw( backend_resolver=resolver, filesystem_mode=FilesystemMode.CLOUD, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id="00000000-0000-0000-0000-000000000001", thread_id=_SEARCH_SPACE_ID, read_only=False, diff --git a/surfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_filesystem_desktop.py b/surfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_filesystem_desktop.py index e013ef35b..586dbdfb6 100644 --- a/surfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_filesystem_desktop.py +++ b/surfsense_backend/tests/integration/agents/multi_agent_chat/test_kb_filesystem_desktop.py @@ -51,7 +51,7 @@ def _build_desktop_fs_mw(root: Path): return build_filesystem_mw( backend_resolver=resolver, filesystem_mode=FilesystemMode.DESKTOP_LOCAL_FOLDER, - search_space_id=1, + workspace_id=1, user_id="00000000-0000-0000-0000-000000000001", thread_id=1, read_only=False, diff --git a/surfsense_backend/tests/integration/chat/test_append_message_recovery.py b/surfsense_backend/tests/integration/chat/test_append_message_recovery.py index c6a40c356..b07d60b8f 100644 --- a/surfsense_backend/tests/integration/chat/test_append_message_recovery.py +++ b/surfsense_backend/tests/integration/chat/test_append_message_recovery.py @@ -46,7 +46,7 @@ from app.db import ( NewChatMessage, NewChatMessageRole, NewChatThread, - SearchSpace, + Workspace, TokenUsage, User, ) @@ -69,11 +69,11 @@ pytestmark = pytest.mark.integration @pytest_asyncio.fixture async def db_thread( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ) -> NewChatThread: thread = NewChatThread( title="Test Chat", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, created_by_id=db_user.id, visibility=ChatVisibility.PRIVATE, ) @@ -109,7 +109,7 @@ def bypass_permission_checks(monkeypatch): """Replace RBAC + thread access checks with no-ops. The append_message route under test calls ``check_permission`` and - ``check_thread_access``; those rely on a SearchSpaceMembership row + ``check_thread_access``; those rely on a WorkspaceMembership row that the existing integration fixtures don't create. The contract we want to verify here is the ``IntegrityError`` -> recovery branch, not the RBAC plumbing — so stub them. @@ -208,7 +208,7 @@ class TestToolHeavyTurnFinalize: db_session, db_user, db_thread, - db_search_space, + db_workspace, patched_shielded_session, ): """End-to-end seam: builder snapshot -> finalize -> DB row. @@ -221,7 +221,7 @@ class TestToolHeavyTurnFinalize: """ thread_id = db_thread.id user_id_str = str(db_user.id) - search_space_id = db_search_space.id + workspace_id = db_workspace.id turn_id = f"{thread_id}:tool_heavy" msg_id = await persist_assistant_shell( @@ -258,7 +258,7 @@ class TestToolHeavyTurnFinalize: await finalize_assistant_turn( message_id=msg_id, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id=turn_id, content=snapshot, @@ -300,7 +300,7 @@ class TestToolHeavyTurnFinalize: assert usage.total_tokens == 280 assert usage.cost_micros == 22222 assert usage.thread_id == thread_id - assert usage.search_space_id == search_space_id + assert usage.workspace_id == workspace_id # --------------------------------------------------------------------------- @@ -314,7 +314,7 @@ class TestAppendMessageRecoveryAfterFinalize: db_session, db_user, db_thread, - db_search_space, + db_workspace, patched_shielded_session, bypass_permission_checks, ): @@ -337,7 +337,7 @@ class TestAppendMessageRecoveryAfterFinalize: """ thread_id = db_thread.id user_id_str = str(db_user.id) - search_space_id = db_search_space.id + workspace_id = db_workspace.id turn_id = f"{thread_id}:fe_late_append" # Step 1: server stream completes. Server-built rich content is @@ -353,7 +353,7 @@ class TestAppendMessageRecoveryAfterFinalize: await finalize_assistant_turn( message_id=msg_id, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id=turn_id, content=server_content, @@ -439,7 +439,7 @@ class TestAppendMessageRecoveryAfterFinalize: db_session, db_user, db_thread, - db_search_space, + db_workspace, patched_shielded_session, bypass_permission_checks, ): @@ -456,7 +456,7 @@ class TestAppendMessageRecoveryAfterFinalize: """ thread_id = db_thread.id user_id_str = str(db_user.id) - search_space_id = db_search_space.id + workspace_id = db_workspace.id turn_id = f"{thread_id}:fe_first" # Step 1: legacy FE appendMessage lands first. No prior shell @@ -490,7 +490,7 @@ class TestAppendMessageRecoveryAfterFinalize: await finalize_assistant_turn( message_id=adopted_id, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id=turn_id, content=server_content, diff --git a/surfsense_backend/tests/integration/chat/test_message_id_sse.py b/surfsense_backend/tests/integration/chat/test_message_id_sse.py index 8fc935eaa..bea2b71dd 100644 --- a/surfsense_backend/tests/integration/chat/test_message_id_sse.py +++ b/surfsense_backend/tests/integration/chat/test_message_id_sse.py @@ -48,7 +48,7 @@ from app.db import ( NewChatMessage, NewChatMessageRole, NewChatThread, - SearchSpace, + Workspace, User, ) from app.services.new_streaming_service import VercelStreamingService @@ -68,11 +68,11 @@ pytestmark = pytest.mark.integration @pytest_asyncio.fixture async def db_thread( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ) -> NewChatThread: thread = NewChatThread( title="Test Chat", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, created_by_id=db_user.id, visibility=ChatVisibility.PRIVATE, ) diff --git a/surfsense_backend/tests/integration/chat/test_persistence.py b/surfsense_backend/tests/integration/chat/test_persistence.py index d6f816cc0..8282ab287 100644 --- a/surfsense_backend/tests/integration/chat/test_persistence.py +++ b/surfsense_backend/tests/integration/chat/test_persistence.py @@ -38,7 +38,7 @@ from app.db import ( NewChatMessage, NewChatMessageRole, NewChatThread, - SearchSpace, + Workspace, TokenUsage, User, ) @@ -60,11 +60,11 @@ pytestmark = pytest.mark.integration @pytest_asyncio.fixture async def db_thread( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ) -> NewChatThread: thread = NewChatThread( title="Test Chat", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, created_by_id=db_user.id, visibility=ChatVisibility.PRIVATE, ) @@ -537,13 +537,13 @@ class TestFinalizeAssistantTurn: db_session, db_user, db_thread, - db_search_space, + db_workspace, patched_shielded_session, ): thread_id = db_thread.id user_id_uuid = db_user.id user_id_str = str(user_id_uuid) - search_space_id = db_search_space.id + workspace_id = db_workspace.id turn_id = f"{thread_id}:4000" msg_id = await persist_assistant_shell( @@ -568,7 +568,7 @@ class TestFinalizeAssistantTurn: await finalize_assistant_turn( message_id=msg_id, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id=turn_id, content=rich_content, @@ -597,19 +597,19 @@ class TestFinalizeAssistantTurn: assert usage.total_tokens == 150 assert usage.cost_micros == 12345 assert usage.thread_id == thread_id - assert usage.search_space_id == search_space_id + assert usage.workspace_id == workspace_id async def test_empty_content_writes_status_marker( self, db_session, db_user, db_thread, - db_search_space, + db_workspace, patched_shielded_session, ): thread_id = db_thread.id user_id_str = str(db_user.id) - search_space_id = db_search_space.id + workspace_id = db_workspace.id turn_id = f"{thread_id}:5000" msg_id = await persist_assistant_shell( @@ -624,7 +624,7 @@ class TestFinalizeAssistantTurn: await finalize_assistant_turn( message_id=msg_id, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id=turn_id, content=[], @@ -640,12 +640,12 @@ class TestFinalizeAssistantTurn: db_session, db_user, db_thread, - db_search_space, + db_workspace, patched_shielded_session, ): thread_id = db_thread.id user_id_str = str(db_user.id) - search_space_id = db_search_space.id + workspace_id = db_workspace.id turn_id = f"{thread_id}:6000" msg_id = await persist_assistant_shell( @@ -659,7 +659,7 @@ class TestFinalizeAssistantTurn: await finalize_assistant_turn( message_id=msg_id, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id=turn_id, content=[{"type": "text", "text": "first finalize"}], @@ -681,7 +681,7 @@ class TestFinalizeAssistantTurn: await finalize_assistant_turn( message_id=msg_id, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id=turn_id, content=[{"type": "text", "text": "second finalize"}], @@ -708,7 +708,7 @@ class TestFinalizeAssistantTurn: db_session, db_user, db_thread, - db_search_space, + db_workspace, patched_shielded_session, ): """Cross-writer race: ``append_message`` arrives after ``finalize_assistant_turn``. @@ -722,7 +722,7 @@ class TestFinalizeAssistantTurn: thread_id = db_thread.id user_uuid = db_user.id user_id_str = str(user_uuid) - search_space_id = db_search_space.id + workspace_id = db_workspace.id turn_id = f"{thread_id}:7000" msg_id = await persist_assistant_shell( @@ -735,7 +735,7 @@ class TestFinalizeAssistantTurn: await finalize_assistant_turn( message_id=msg_id, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id=turn_id, content=[{"type": "text", "text": "from server"}], @@ -758,7 +758,7 @@ class TestFinalizeAssistantTurn: call_details=None, thread_id=thread_id, message_id=msg_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_uuid, ) .on_conflict_do_nothing( @@ -783,19 +783,19 @@ class TestFinalizeAssistantTurn: db_session, db_user, db_thread, - db_search_space, + db_workspace, patched_shielded_session, ): thread_id = db_thread.id user_id_str = str(db_user.id) - search_space_id = db_search_space.id + workspace_id = db_workspace.id # message_id that doesn't exist — finalize must log+return, # never raise (called from shielded finally). await finalize_assistant_turn( message_id=999_999_999, chat_id=thread_id, - search_space_id=search_space_id, + workspace_id=workspace_id, user_id=user_id_str, turn_id="anything", content=[{"type": "text", "text": "x"}], diff --git a/surfsense_backend/tests/integration/chat/test_thread_visibility.py b/surfsense_backend/tests/integration/chat/test_thread_visibility.py index ba6f2a66f..f843a0684 100644 --- a/surfsense_backend/tests/integration/chat/test_thread_visibility.py +++ b/surfsense_backend/tests/integration/chat/test_thread_visibility.py @@ -2,7 +2,7 @@ These tests exercise the route handlers directly with real DB-backed users, memberships, and permissions. The important contract is that a -thread shared with a search space stays shared across normal metadata +thread shared with a workspace stays shared across normal metadata updates until the creator explicitly makes it private again. """ @@ -19,9 +19,9 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.auth.context import AuthContext from app.db import ( ChatVisibility, - SearchSpace, - SearchSpaceMembership, - SearchSpaceRole, + Workspace, + WorkspaceMembership, + WorkspaceRole, User, ) from app.routes import new_chat_routes @@ -39,7 +39,7 @@ def _auth(user: User) -> AuthContext: @pytest_asyncio.fixture -async def db_member(db_session: AsyncSession, db_search_space: SearchSpace) -> User: +async def db_member(db_session: AsyncSession, db_workspace: Workspace) -> User: member = User( id=uuid.uuid4(), email="member@surfsense.net", @@ -54,9 +54,9 @@ async def db_member(db_session: AsyncSession, db_search_space: SearchSpace) -> U role = ( ( await db_session.execute( - select(SearchSpaceRole).where( - SearchSpaceRole.search_space_id == db_search_space.id, - SearchSpaceRole.name == "Editor", + select(WorkspaceRole).where( + WorkspaceRole.workspace_id == db_workspace.id, + WorkspaceRole.name == "Editor", ) ) ) @@ -64,9 +64,9 @@ async def db_member(db_session: AsyncSession, db_search_space: SearchSpace) -> U .one() ) db_session.add( - SearchSpaceMembership( + WorkspaceMembership( user_id=member.id, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, role_id=role.id, is_owner=False, ) @@ -78,7 +78,7 @@ async def db_member(db_session: AsyncSession, db_search_space: SearchSpace) -> U async def _create_thread( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, *, title: str = "Visibility Invariant Chat", ): @@ -86,7 +86,7 @@ async def _create_thread( NewChatThreadCreate( title=title, archived=False, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, visibility=ChatVisibility.PRIVATE, ), session=db_session, @@ -102,21 +102,21 @@ def _search_thread_ids(response) -> set[int]: return {thread.id for thread in response} -async def test_private_thread_is_hidden_from_other_search_space_member( +async def test_private_thread_is_hidden_from_other_workspace_member( db_session: AsyncSession, db_user: User, db_member: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - thread = await _create_thread(db_session, db_user, db_search_space) + thread = await _create_thread(db_session, db_user, db_workspace) member_threads = await new_chat_routes.list_threads( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, session=db_session, auth=_auth(db_member), ) member_search = await new_chat_routes.search_threads( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Visibility", session=db_session, auth=_auth(db_member), @@ -137,9 +137,9 @@ async def test_creator_can_share_thread_and_member_can_list_search_read_it( db_session: AsyncSession, db_user: User, db_member: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - thread = await _create_thread(db_session, db_user, db_search_space) + thread = await _create_thread(db_session, db_user, db_workspace) updated = await new_chat_routes.update_thread_visibility( thread_id=thread.id, @@ -151,12 +151,12 @@ async def test_creator_can_share_thread_and_member_can_list_search_read_it( ) member_threads = await new_chat_routes.list_threads( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, session=db_session, auth=_auth(db_member), ) member_search = await new_chat_routes.search_threads( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Visibility", session=db_session, auth=_auth(db_member), @@ -177,9 +177,9 @@ async def test_creator_can_share_thread_and_member_can_list_search_read_it( async def test_rename_and_archive_do_not_reset_shared_visibility( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - thread = await _create_thread(db_session, db_user, db_search_space) + thread = await _create_thread(db_session, db_user, db_workspace) await new_chat_routes.update_thread_visibility( thread_id=thread.id, visibility_update=NewChatThreadVisibilityUpdate( @@ -211,9 +211,9 @@ async def test_non_creator_cannot_change_shared_thread_back_to_private( db_session: AsyncSession, db_user: User, db_member: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - thread = await _create_thread(db_session, db_user, db_search_space) + thread = await _create_thread(db_session, db_user, db_workspace) await new_chat_routes.update_thread_visibility( thread_id=thread.id, visibility_update=NewChatThreadVisibilityUpdate( @@ -240,9 +240,9 @@ async def test_creator_can_make_shared_thread_private_again( db_session: AsyncSession, db_user: User, db_member: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - thread = await _create_thread(db_session, db_user, db_search_space) + thread = await _create_thread(db_session, db_user, db_workspace) await new_chat_routes.update_thread_visibility( thread_id=thread.id, visibility_update=NewChatThreadVisibilityUpdate( @@ -261,12 +261,12 @@ async def test_creator_can_make_shared_thread_private_again( auth=_auth(db_user), ) member_threads = await new_chat_routes.list_threads( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, session=db_session, auth=_auth(db_member), ) member_search = await new_chat_routes.search_threads( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Visibility", session=db_session, auth=_auth(db_member), diff --git a/surfsense_backend/tests/integration/composio/conftest.py b/surfsense_backend/tests/integration/composio/conftest.py index 578b5b228..66d729d83 100644 --- a/surfsense_backend/tests/integration/composio/conftest.py +++ b/surfsense_backend/tests/integration/composio/conftest.py @@ -65,7 +65,7 @@ async def client( async def drive_connector( db_session: AsyncSession, db_user: User, - db_search_space, + db_workspace, ) -> SearchSourceConnector: connector = SearchSourceConnector( name="Google Drive (Composio) - e2e-fake@surfsense.example", @@ -77,7 +77,7 @@ async def drive_connector( "toolkit_name": "Google Drive", "is_indexable": True, }, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=db_user.id, ) db_session.add(connector) diff --git a/surfsense_backend/tests/integration/composio/test_oauth_callback.py b/surfsense_backend/tests/integration/composio/test_oauth_callback.py index d2f4c3752..0c95f0c4a 100644 --- a/surfsense_backend/tests/integration/composio/test_oauth_callback.py +++ b/surfsense_backend/tests/integration/composio/test_oauth_callback.py @@ -9,7 +9,7 @@ from app.config import config from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - SearchSpace, + Workspace, User, ) from app.utils.oauth_security import OAuthStateManager @@ -29,12 +29,12 @@ async def _drive_connectors( session: AsyncSession, *, user_id: UUID, - search_space_id: int, + workspace_id: int, ) -> list[SearchSourceConnector]: result = await session.execute( select(SearchSourceConnector).where( SearchSourceConnector.user_id == user_id, - SearchSourceConnector.search_space_id == search_space_id, + SearchSourceConnector.workspace_id == workspace_id, SearchSourceConnector.connector_type == SearchSourceConnectorType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR, ) @@ -46,9 +46,9 @@ async def test_callback_with_error_param_redirects_to_denied_page( client: httpx.AsyncClient, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - state = _state_for(db_search_space.id, db_user.id) + state = _state_for(db_workspace.id, db_user.id) response = await client.get( f"/api/v1/auth/composio/connector/callback?state={state}&error=access_denied" @@ -57,14 +57,14 @@ async def test_callback_with_error_param_redirects_to_denied_page( assert response.status_code in {302, 303, 307} location = response.headers["location"] assert ( - f"/dashboard/{db_search_space.id}/connectors/callback?" + f"/dashboard/{db_workspace.id}/connectors/callback?" "error=composio_oauth_denied" ) in location connectors = await _drive_connectors( db_session, user_id=db_user.id, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) assert connectors == [] @@ -73,9 +73,9 @@ async def test_second_oauth_for_same_toolkit_takes_reconnection_branch( client: httpx.AsyncClient, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - first_state = _state_for(db_search_space.id, db_user.id) + first_state = _state_for(db_workspace.id, db_user.id) first_response = await client.get( "/api/v1/auth/composio/connector/callback" @@ -86,7 +86,7 @@ async def test_second_oauth_for_same_toolkit_takes_reconnection_branch( first_connectors = await _drive_connectors( db_session, user_id=db_user.id, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) assert len(first_connectors) == 1 first_connector = first_connectors[0] @@ -94,7 +94,7 @@ async def test_second_oauth_for_same_toolkit_takes_reconnection_branch( "fake-acct-googledrive-first" ) - second_state = _state_for(db_search_space.id, db_user.id) + second_state = _state_for(db_workspace.id, db_user.id) second_response = await client.get( "/api/v1/auth/composio/connector/callback" f"?state={second_state}&connectedAccountId=fake-acct-googledrive-second" @@ -104,7 +104,7 @@ async def test_second_oauth_for_same_toolkit_takes_reconnection_branch( second_connectors = await _drive_connectors( db_session, user_id=db_user.id, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) assert len(second_connectors) == 1 assert second_connectors[0].id == first_connector.id diff --git a/surfsense_backend/tests/integration/conftest.py b/surfsense_backend/tests/integration/conftest.py index 6b8aa3cdb..efd8454ac 100644 --- a/surfsense_backend/tests/integration/conftest.py +++ b/surfsense_backend/tests/integration/conftest.py @@ -21,13 +21,13 @@ Base = app_db.Base DocumentType = app_db.DocumentType SearchSourceConnector = app_db.SearchSourceConnector SearchSourceConnectorType = app_db.SearchSourceConnectorType -SearchSpace = app_db.SearchSpace +Workspace = app_db.Workspace User = app_db.User ConnectorDocument = importlib.import_module( "app.indexing_pipeline.connector_document" ).ConnectorDocument create_default_roles_and_membership = importlib.import_module( - "app.routes.search_spaces_routes" + "app.routes.workspaces_routes" ).create_default_roles_and_membership TEST_DATABASE_URL = importlib.import_module("tests.conftest").TEST_DATABASE_URL @@ -95,13 +95,13 @@ async def db_user(db_session: AsyncSession) -> User: @pytest_asyncio.fixture async def db_connector( - db_session: AsyncSession, db_user: User, db_search_space: "SearchSpace" + db_session: AsyncSession, db_user: User, db_workspace: "Workspace" ) -> SearchSourceConnector: connector = SearchSourceConnector( name="Test Connector", connector_type=SearchSourceConnectorType.CLICKUP_CONNECTOR, config={}, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=db_user.id, ) db_session.add(connector) @@ -110,14 +110,14 @@ async def db_connector( @pytest_asyncio.fixture -async def db_search_space(db_session: AsyncSession, db_user: User) -> SearchSpace: - space = SearchSpace( +async def db_workspace(db_session: AsyncSession, db_user: User) -> Workspace: + space = Workspace( name="Test Space", user_id=db_user.id, ) db_session.add(space) await db_session.flush() - # Mirror POST /searchspaces so routes guarded by check_permission find a membership. + # Mirror POST /workspaces so routes guarded by check_permission find a membership. await create_default_roles_and_membership(db_session, space.id, db_user.id) await db_session.flush() return space @@ -180,7 +180,7 @@ def make_connector_document(db_connector, db_user): "source_markdown": "## Heading\n\nSome content.", "unique_id": "test-id-001", "document_type": DocumentType.CLICKUP_CONNECTOR, - "search_space_id": db_connector.search_space_id, + "workspace_id": db_connector.workspace_id, "connector_id": db_connector.id, "created_by_id": str(db_user.id), } diff --git a/surfsense_backend/tests/integration/document_upload/conftest.py b/surfsense_backend/tests/integration/document_upload/conftest.py index f894114d6..894ec1a7a 100644 --- a/surfsense_backend/tests/integration/document_upload/conftest.py +++ b/surfsense_backend/tests/integration/document_upload/conftest.py @@ -34,7 +34,7 @@ from tests.utils.helpers import ( auth_headers, delete_document, get_auth_token, - get_search_space_id, + get_workspace_id, ) limiter.enabled = False @@ -66,7 +66,7 @@ class InlineTaskDispatcher: document_id: int, temp_path: str, filename: str, - search_space_id: int, + workspace_id: int, user_id: str, use_vision_llm: bool = False, processing_mode: str = "basic", @@ -80,7 +80,7 @@ class InlineTaskDispatcher: document_id, temp_path, filename, - search_space_id, + workspace_id, user_id, use_vision_llm=use_vision_llm, processing_mode=processing_mode, @@ -107,7 +107,7 @@ async def _ensure_tables(): # --------------------------------------------------------------------------- -# Auth & search space (session-scoped, via the in-process app) +# Auth & workspace (session-scoped, via the in-process app) # --------------------------------------------------------------------------- @@ -121,12 +121,12 @@ async def auth_token(_ensure_tables) -> str: @pytest.fixture(scope="session") -async def search_space_id(auth_token: str) -> int: - """Discover the first search space belonging to the test user.""" +async def workspace_id(auth_token: str) -> int: + """Discover the first workspace belonging to the test user.""" async with httpx.AsyncClient( transport=ASGITransport(app=app), base_url="http://test", timeout=30.0 ) as c: - return await get_search_space_id(c, auth_token) + return await get_workspace_id(c, auth_token) @pytest.fixture(scope="session") @@ -155,19 +155,19 @@ def cleanup_doc_ids() -> list[int]: @pytest.fixture(scope="session", autouse=True) -async def _purge_test_search_space(search_space_id: int): +async def _purge_test_workspace(workspace_id: int): """Delete stale documents from previous runs before the session starts.""" conn = await asyncpg.connect(_ASYNCPG_URL) try: result = await conn.execute( "DELETE FROM documents WHERE workspace_id = $1", - search_space_id, + workspace_id, ) deleted = int(result.split()[-1]) if deleted: print( f"\n[purge] Deleted {deleted} stale document(s) " - f"from search space {search_space_id}" + f"from workspace {workspace_id}" ) finally: await conn.close() diff --git a/surfsense_backend/tests/integration/document_upload/test_document_upload.py b/surfsense_backend/tests/integration/document_upload/test_document_upload.py index 13ceae828..5ca01d148 100644 --- a/surfsense_backend/tests/integration/document_upload/test_document_upload.py +++ b/surfsense_backend/tests/integration/document_upload/test_document_upload.py @@ -37,11 +37,11 @@ class TestTxtFileUpload: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): resp = await upload_file( - client, headers, "sample.txt", search_space_id=search_space_id + client, headers, "sample.txt", workspace_id=workspace_id ) assert resp.status_code == 200 @@ -54,18 +54,18 @@ class TestTxtFileUpload: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): resp = await upload_file( - client, headers, "sample.txt", search_space_id=search_space_id + client, headers, "sample.txt", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] cleanup_doc_ids.extend(doc_ids) statuses = await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id + client, headers, doc_ids, workspace_id=workspace_id ) for did in doc_ids: assert statuses[did]["status"]["state"] == "ready" @@ -78,18 +78,18 @@ class TestPdfFileUpload: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): resp = await upload_file( - client, headers, "sample.pdf", search_space_id=search_space_id + client, headers, "sample.pdf", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] cleanup_doc_ids.extend(doc_ids) statuses = await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id, timeout=300.0 + client, headers, doc_ids, workspace_id=workspace_id, timeout=300.0 ) for did in doc_ids: assert statuses[did]["status"]["state"] == "ready" @@ -107,14 +107,14 @@ class TestMultiFileUpload: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): resp = await upload_multiple_files( client, headers, ["sample.txt", "sample.md"], - search_space_id=search_space_id, + workspace_id=workspace_id, ) assert resp.status_code == 200 @@ -139,22 +139,22 @@ class TestDuplicateFileUpload: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): resp1 = await upload_file( - client, headers, "sample.txt", search_space_id=search_space_id + client, headers, "sample.txt", workspace_id=workspace_id ) assert resp1.status_code == 200 first_ids = resp1.json()["document_ids"] cleanup_doc_ids.extend(first_ids) await poll_document_status( - client, headers, first_ids, search_space_id=search_space_id + client, headers, first_ids, workspace_id=workspace_id ) resp2 = await upload_file( - client, headers, "sample.txt", search_space_id=search_space_id + client, headers, "sample.txt", workspace_id=workspace_id ) assert resp2.status_code == 200 @@ -179,18 +179,18 @@ class TestDuplicateContentDetection: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], tmp_path: Path, ): resp1 = await upload_file( - client, headers, "sample.txt", search_space_id=search_space_id + client, headers, "sample.txt", workspace_id=workspace_id ) assert resp1.status_code == 200 first_ids = resp1.json()["document_ids"] cleanup_doc_ids.extend(first_ids) await poll_document_status( - client, headers, first_ids, search_space_id=search_space_id + client, headers, first_ids, workspace_id=workspace_id ) src = FIXTURES_DIR / "sample.txt" @@ -202,7 +202,7 @@ class TestDuplicateContentDetection: "/api/v1/documents/fileupload", headers=headers, files={"files": ("renamed_sample.txt", f)}, - data={"search_space_id": str(search_space_id)}, + data={"workspace_id": str(workspace_id)}, ) assert resp2.status_code == 200 second_ids = resp2.json()["document_ids"] @@ -212,7 +212,7 @@ class TestDuplicateContentDetection: ) statuses = await poll_document_status( - client, headers, second_ids, search_space_id=search_space_id + client, headers, second_ids, workspace_id=workspace_id ) for did in second_ids: assert statuses[did]["status"]["state"] == "failed" @@ -231,11 +231,11 @@ class TestEmptyFileUpload: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): resp = await upload_file( - client, headers, "empty.pdf", search_space_id=search_space_id + client, headers, "empty.pdf", workspace_id=workspace_id ) assert resp.status_code == 200 @@ -244,7 +244,7 @@ class TestEmptyFileUpload: assert doc_ids, "Expected at least one document id for empty PDF upload" statuses = await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id, timeout=120.0 + client, headers, doc_ids, workspace_id=workspace_id, timeout=120.0 ) for did in doc_ids: assert statuses[did]["status"]["state"] == "failed" @@ -264,14 +264,14 @@ class TestUnauthenticatedUpload: async def test_upload_without_auth_returns_401( self, client: httpx.AsyncClient, - search_space_id: int, + workspace_id: int, ): file_path = FIXTURES_DIR / "sample.txt" with open(file_path, "rb") as f: resp = await client.post( "/api/v1/documents/fileupload", files={"files": ("sample.txt", f)}, - data={"search_space_id": str(search_space_id)}, + data={"workspace_id": str(workspace_id)}, ) assert resp.status_code == 401 @@ -288,12 +288,12 @@ class TestNoFilesUpload: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, ): resp = await client.post( "/api/v1/documents/fileupload", headers=headers, - data={"search_space_id": str(search_space_id)}, + data={"workspace_id": str(workspace_id)}, ) assert resp.status_code in {400, 422} @@ -310,24 +310,24 @@ class TestDocumentSearchability: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): resp = await upload_file( - client, headers, "sample.txt", search_space_id=search_space_id + client, headers, "sample.txt", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] cleanup_doc_ids.extend(doc_ids) await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id + client, headers, doc_ids, workspace_id=workspace_id ) search_resp = await client.get( "/api/v1/documents/search", headers=headers, - params={"title": "sample", "search_space_id": search_space_id}, + params={"title": "sample", "workspace_id": workspace_id}, ) assert search_resp.status_code == 200 diff --git a/surfsense_backend/tests/integration/document_upload/test_etl_credits.py b/surfsense_backend/tests/integration/document_upload/test_etl_credits.py index 6a2972598..ee8f8c4c8 100644 --- a/surfsense_backend/tests/integration/document_upload/test_etl_credits.py +++ b/surfsense_backend/tests/integration/document_upload/test_etl_credits.py @@ -57,7 +57,7 @@ class TestBalanceDecrementsOnSuccess: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], credits, ): @@ -65,14 +65,14 @@ class TestBalanceDecrementsOnSuccess: before = await _get_balance(client, headers) resp = await upload_file( - client, headers, "sample.pdf", search_space_id=search_space_id + client, headers, "sample.pdf", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] cleanup_doc_ids.extend(doc_ids) statuses = await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id, timeout=300.0 + client, headers, doc_ids, workspace_id=workspace_id, timeout=300.0 ) for did in doc_ids: assert statuses[did]["status"]["state"] == "ready" @@ -94,21 +94,21 @@ class TestUploadRejectedWhenCreditExhausted: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], credits, ): await credits.set(balance_micros=0) resp = await upload_file( - client, headers, "sample.pdf", search_space_id=search_space_id + client, headers, "sample.pdf", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] cleanup_doc_ids.extend(doc_ids) statuses = await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id, timeout=300.0 + client, headers, doc_ids, workspace_id=workspace_id, timeout=300.0 ) for did in doc_ids: assert statuses[did]["status"]["state"] == "failed" @@ -121,21 +121,21 @@ class TestUploadRejectedWhenCreditExhausted: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], credits, ): await credits.set(balance_micros=0) resp = await upload_file( - client, headers, "sample.pdf", search_space_id=search_space_id + client, headers, "sample.pdf", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] cleanup_doc_ids.extend(doc_ids) await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id, timeout=300.0 + client, headers, doc_ids, workspace_id=workspace_id, timeout=300.0 ) balance = await _get_balance(client, headers) @@ -157,28 +157,28 @@ class TestInsufficientCreditsNotification: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], credits, ): await credits.set(balance_micros=0) resp = await upload_file( - client, headers, "sample.pdf", search_space_id=search_space_id + client, headers, "sample.pdf", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] cleanup_doc_ids.extend(doc_ids) await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id, timeout=300.0 + client, headers, doc_ids, workspace_id=workspace_id, timeout=300.0 ) notifications = await get_notifications( client, headers, type_filter="insufficient_credits", - search_space_id=search_space_id, + workspace_id=workspace_id, ) assert len(notifications) >= 1, ( "Expected at least one insufficient_credits notification" @@ -206,28 +206,28 @@ class TestDocumentProcessingNotification: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], credits, ): await credits.set(balance_micros=credits.pages(1000)) resp = await upload_file( - client, headers, "sample.txt", search_space_id=search_space_id + client, headers, "sample.txt", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] cleanup_doc_ids.extend(doc_ids) await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id + client, headers, doc_ids, workspace_id=workspace_id ) notifications = await get_notifications( client, headers, type_filter="document_processing", - search_space_id=search_space_id, + workspace_id=workspace_id, ) completed = [ n @@ -252,7 +252,7 @@ class TestBalanceUnchangedOnProcessingFailure: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], credits, ): @@ -260,7 +260,7 @@ class TestBalanceUnchangedOnProcessingFailure: await credits.set(balance_micros=starting) resp = await upload_file( - client, headers, "empty.pdf", search_space_id=search_space_id + client, headers, "empty.pdf", workspace_id=workspace_id ) assert resp.status_code == 200 doc_ids = resp.json()["document_ids"] @@ -268,7 +268,7 @@ class TestBalanceUnchangedOnProcessingFailure: if doc_ids: statuses = await poll_document_status( - client, headers, doc_ids, search_space_id=search_space_id, timeout=120.0 + client, headers, doc_ids, workspace_id=workspace_id, timeout=120.0 ) for did in doc_ids: assert statuses[did]["status"]["state"] == "failed" @@ -292,7 +292,7 @@ class TestSecondUploadExceedsCredit: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], credits, ): @@ -301,14 +301,14 @@ class TestSecondUploadExceedsCredit: await credits.set(balance_micros=credits.pages(1)) resp1 = await upload_file( - client, headers, "sample.pdf", search_space_id=search_space_id + client, headers, "sample.pdf", workspace_id=workspace_id ) assert resp1.status_code == 200 first_ids = resp1.json()["document_ids"] cleanup_doc_ids.extend(first_ids) statuses1 = await poll_document_status( - client, headers, first_ids, search_space_id=search_space_id, timeout=300.0 + client, headers, first_ids, workspace_id=workspace_id, timeout=300.0 ) for did in first_ids: assert statuses1[did]["status"]["state"] == "ready" @@ -317,7 +317,7 @@ class TestSecondUploadExceedsCredit: client, headers, "sample.pdf", - search_space_id=search_space_id, + workspace_id=workspace_id, filename_override="sample_copy.pdf", ) assert resp2.status_code == 200 @@ -325,7 +325,7 @@ class TestSecondUploadExceedsCredit: cleanup_doc_ids.extend(second_ids) statuses2 = await poll_document_status( - client, headers, second_ids, search_space_id=search_space_id, timeout=300.0 + client, headers, second_ids, workspace_id=workspace_id, timeout=300.0 ) for did in second_ids: assert statuses2[did]["status"]["state"] == "failed" diff --git a/surfsense_backend/tests/integration/document_upload/test_stripe_credit_purchases.py b/surfsense_backend/tests/integration/document_upload/test_stripe_credit_purchases.py index dcd4d1d2f..e53b37904 100644 --- a/surfsense_backend/tests/integration/document_upload/test_stripe_credit_purchases.py +++ b/surfsense_backend/tests/integration/document_upload/test_stripe_credit_purchases.py @@ -187,7 +187,7 @@ class TestStripeCheckoutSessionCreation: self, client, headers, - search_space_id: int, + workspace_id: int, monkeypatch, ): checkout_session = SimpleNamespace( @@ -205,7 +205,7 @@ class TestStripeCheckoutSessionCreation: response = await client.post( "/api/v1/stripe/create-credit-checkout-session", headers=headers, - json={"quantity": 2, "search_space_id": search_space_id}, + json={"quantity": 2, "workspace_id": workspace_id}, ) assert response.status_code == 200, response.text @@ -217,12 +217,12 @@ class TestStripeCheckoutSessionCreation: ] assert ( fake_client.last_params["success_url"] - == f"http://localhost:3000/dashboard/{search_space_id}/purchase-success" + == f"http://localhost:3000/dashboard/{workspace_id}/purchase-success" "?session_id={CHECKOUT_SESSION_ID}" ) assert ( fake_client.last_params["cancel_url"] - == f"http://localhost:3000/dashboard/{search_space_id}/purchase-cancel" + == f"http://localhost:3000/dashboard/{workspace_id}/purchase-cancel" ) assert fake_client.last_params["metadata"]["purchase_type"] == "credits" @@ -244,7 +244,7 @@ class TestStripeCheckoutSessionCreation: self, client, headers, - search_space_id: int, + workspace_id: int, monkeypatch, ): monkeypatch.setattr(stripe_routes.config, "STRIPE_CREDIT_BUYING_ENABLED", False) @@ -252,7 +252,7 @@ class TestStripeCheckoutSessionCreation: response = await client.post( "/api/v1/stripe/create-credit-checkout-session", headers=headers, - json={"quantity": 2, "search_space_id": search_space_id}, + json={"quantity": 2, "workspace_id": workspace_id}, ) assert response.status_code == 503, response.text @@ -270,7 +270,7 @@ class TestStripeWebhookFulfillment: self, client, headers, - search_space_id: int, + workspace_id: int, credits, monkeypatch, ): @@ -291,7 +291,7 @@ class TestStripeWebhookFulfillment: create_response = await client.post( "/api/v1/stripe/create-credit-checkout-session", headers=headers, - json={"quantity": 3, "search_space_id": search_space_id}, + json={"quantity": 3, "workspace_id": workspace_id}, ) assert create_response.status_code == 200, create_response.text @@ -359,7 +359,7 @@ class TestStripeReconciliation: self, client, headers, - search_space_id: int, + workspace_id: int, credits, monkeypatch, ): @@ -380,7 +380,7 @@ class TestStripeReconciliation: create_response = await client.post( "/api/v1/stripe/create-credit-checkout-session", headers=headers, - json={"quantity": 3, "search_space_id": search_space_id}, + json={"quantity": 3, "workspace_id": workspace_id}, ) assert create_response.status_code == 200, create_response.text assert await _get_balance(TEST_EMAIL) == 1_000_000 @@ -433,7 +433,7 @@ class TestStripeReconciliation: self, client, headers, - search_space_id: int, + workspace_id: int, credits, monkeypatch, ): @@ -454,7 +454,7 @@ class TestStripeReconciliation: create_response = await client.post( "/api/v1/stripe/create-credit-checkout-session", headers=headers, - json={"quantity": 1, "search_space_id": search_space_id}, + json={"quantity": 1, "workspace_id": workspace_id}, ) assert create_response.status_code == 200, create_response.text diff --git a/surfsense_backend/tests/integration/document_upload/test_upload_limits.py b/surfsense_backend/tests/integration/document_upload/test_upload_limits.py index a56398baa..027d8a7d0 100644 --- a/surfsense_backend/tests/integration/document_upload/test_upload_limits.py +++ b/surfsense_backend/tests/integration/document_upload/test_upload_limits.py @@ -34,14 +34,14 @@ class TestPerFileSizeLimit: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, ): oversized = io.BytesIO(b"\x00" * (500 * 1024 * 1024 + 1)) resp = await client.post( "/api/v1/documents/fileupload", headers=headers, files=[("files", ("big.pdf", oversized, "application/pdf"))], - data={"search_space_id": str(search_space_id)}, + data={"workspace_id": str(workspace_id)}, ) assert resp.status_code == 413 assert "per-file limit" in resp.json()["detail"].lower() @@ -50,7 +50,7 @@ class TestPerFileSizeLimit: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): at_limit = io.BytesIO(b"\x00" * (500 * 1024 * 1024)) @@ -58,7 +58,7 @@ class TestPerFileSizeLimit: "/api/v1/documents/fileupload", headers=headers, files=[("files", ("exact500mb.txt", at_limit, "text/plain"))], - data={"search_space_id": str(search_space_id)}, + data={"workspace_id": str(workspace_id)}, ) assert resp.status_code == 200 cleanup_doc_ids.extend(resp.json().get("document_ids", [])) @@ -76,7 +76,7 @@ class TestNoFileCountLimit: self, client: httpx.AsyncClient, headers: dict[str, str], - search_space_id: int, + workspace_id: int, cleanup_doc_ids: list[int], ): files = [ @@ -87,7 +87,7 @@ class TestNoFileCountLimit: "/api/v1/documents/fileupload", headers=headers, files=files, - data={"search_space_id": str(search_space_id)}, + data={"workspace_id": str(workspace_id)}, ) assert resp.status_code == 200 cleanup_doc_ids.extend(resp.json().get("document_ids", [])) diff --git a/surfsense_backend/tests/integration/google_unification/conftest.py b/surfsense_backend/tests/integration/google_unification/conftest.py index e096b30d6..8c9d4d9c1 100644 --- a/surfsense_backend/tests/integration/google_unification/conftest.py +++ b/surfsense_backend/tests/integration/google_unification/conftest.py @@ -18,7 +18,7 @@ from app.db import ( DocumentType, SearchSourceConnector, SearchSourceConnectorType, - SearchSpace, + Workspace, User, ) @@ -31,7 +31,7 @@ def make_document( title: str, document_type: DocumentType, content: str, - search_space_id: int, + workspace_id: int, created_by_id: str, ) -> Document: """Build a Document instance with unique hashes and a dummy embedding.""" @@ -43,7 +43,7 @@ def make_document( content_hash=f"content-{uid}", unique_identifier_hash=f"uid-{uid}", source_markdown=content, - search_space_id=search_space_id, + workspace_id=workspace_id, created_by_id=created_by_id, embedding=DUMMY_EMBEDDING, updated_at=datetime.now(UTC), @@ -66,35 +66,35 @@ def make_chunk(*, content: str, document_id: int) -> Chunk: @pytest_asyncio.fixture async def seed_google_docs( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """Insert a native Drive doc, a legacy Composio Drive doc, and a FILE doc. Returns a dict with keys ``native_doc``, ``legacy_doc``, ``file_doc``, - plus ``search_space`` and ``user``. + plus ``workspace`` and ``user``. """ user_id = str(db_user.id) - space_id = db_search_space.id + space_id = db_workspace.id native_doc = make_document( title="Native Drive Document", document_type=DocumentType.GOOGLE_DRIVE_FILE, content="quarterly report from native google drive connector", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, ) legacy_doc = make_document( title="Legacy Composio Drive Document", document_type=DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR, content="quarterly report from composio google drive connector", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, ) file_doc = make_document( title="Uploaded PDF", document_type=DocumentType.FILE, content="unrelated uploaded file about quarterly reports", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, ) @@ -121,7 +121,7 @@ async def seed_google_docs( "native_doc": native_doc, "legacy_doc": legacy_doc, "file_doc": file_doc, - "search_space": db_search_space, + "workspace": db_workspace, "user": db_user, } @@ -136,8 +136,8 @@ async def seed_google_docs( async def committed_google_data(async_engine): """Insert native, legacy, and FILE docs via a committed transaction. - Yields ``{"search_space_id": int, "user_id": str}``. - Cleans up by deleting the search space (cascades to documents / chunks). + Yields ``{"workspace_id": int, "user_id": str}``. + Cleans up by deleting the workspace (cascades to documents / chunks). """ space_id = None @@ -155,7 +155,7 @@ async def committed_google_data(async_engine): session.add(user) await session.flush() - space = SearchSpace(name=f"Google Test {uuid.uuid4().hex[:6]}", user_id=user.id) + space = Workspace(name=f"Google Test {uuid.uuid4().hex[:6]}", user_id=user.id) session.add(space) await session.flush() space_id = space.id @@ -165,21 +165,21 @@ async def committed_google_data(async_engine): title="Native Drive Doc", document_type=DocumentType.GOOGLE_DRIVE_FILE, content="quarterly budget from native google drive", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, ) legacy_doc = make_document( title="Legacy Composio Drive Doc", document_type=DocumentType.COMPOSIO_GOOGLE_DRIVE_CONNECTOR, content="quarterly budget from composio google drive", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, ) file_doc = make_document( title="Plain File", document_type=DocumentType.FILE, content="quarterly budget uploaded as file", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, ) session.add_all([native_doc, legacy_doc, file_doc]) @@ -195,7 +195,7 @@ async def committed_google_data(async_engine): ) await session.flush() - yield {"search_space_id": space_id, "user_id": user_id} + yield {"workspace_id": space_id, "user_id": user_id} async with async_engine.begin() as conn: await conn.execute( @@ -257,7 +257,7 @@ async def seed_connector( ): """Seed a connector with committed data. Returns dict and cleanup function. - Yields ``{"connector_id", "search_space_id", "user_id"}``. + Yields ``{"connector_id", "workspace_id", "user_id"}``. """ space_id = None @@ -275,7 +275,7 @@ async def seed_connector( session.add(user) await session.flush() - space = SearchSpace( + space = Workspace( name=f"{name_prefix} {uuid.uuid4().hex[:6]}", user_id=user.id ) session.add(space) @@ -287,7 +287,7 @@ async def seed_connector( connector_type=connector_type, is_indexable=True, config=config, - search_space_id=space_id, + workspace_id=space_id, user_id=user.id, ) session.add(connector) @@ -297,13 +297,13 @@ async def seed_connector( return { "connector_id": connector_id, - "search_space_id": space_id, + "workspace_id": space_id, "user_id": user_id, } async def cleanup_space(async_engine, space_id: int): - """Delete a search space (cascades to connectors/documents).""" + """Delete a workspace (cascades to connectors/documents).""" async with async_engine.begin() as conn: await conn.execute( text("DELETE FROM workspaces WHERE id = :sid"), {"sid": space_id} diff --git a/surfsense_backend/tests/integration/google_unification/test_calendar_indexer_credentials.py b/surfsense_backend/tests/integration/google_unification/test_calendar_indexer_credentials.py index 44ff5c48a..2b674c336 100644 --- a/surfsense_backend/tests/integration/google_unification/test_calendar_indexer_credentials.py +++ b/surfsense_backend/tests/integration/google_unification/test_calendar_indexer_credentials.py @@ -37,7 +37,7 @@ async def composio_calendar(async_engine): name_prefix="cal-composio", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @pytest_asyncio.fixture @@ -49,7 +49,7 @@ async def composio_calendar_no_id(async_engine): name_prefix="cal-noid", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @pytest_asyncio.fixture @@ -67,7 +67,7 @@ async def native_calendar(async_engine): name_prefix="cal-native", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @patch(_GET_ACCESS_TOKEN) @@ -98,7 +98,7 @@ async def test_composio_calendar_uses_composio_service( await index_google_calendar_events( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], ) @@ -137,7 +137,7 @@ async def test_composio_calendar_without_account_id_returns_error( count, _skipped, error = await index_google_calendar_events( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], ) @@ -179,7 +179,7 @@ async def test_native_calendar_uses_google_calendar_connector( await index_google_calendar_events( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], ) diff --git a/surfsense_backend/tests/integration/google_unification/test_drive_indexer_credentials.py b/surfsense_backend/tests/integration/google_unification/test_drive_indexer_credentials.py index 810c3ceab..9795628ed 100644 --- a/surfsense_backend/tests/integration/google_unification/test_drive_indexer_credentials.py +++ b/surfsense_backend/tests/integration/google_unification/test_drive_indexer_credentials.py @@ -62,7 +62,7 @@ async def committed_drive_connector(async_engine): name_prefix="drive-composio", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @pytest_asyncio.fixture @@ -80,7 +80,7 @@ async def committed_native_drive_connector(async_engine): name_prefix="drive-native", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @pytest_asyncio.fixture @@ -92,7 +92,7 @@ async def committed_composio_no_account_id(async_engine): name_prefix="drive-noid", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @patch(_GET_ACCESS_TOKEN) @@ -127,7 +127,7 @@ async def test_composio_drive_indexer_uses_composio_drive_client( await index_google_drive_files( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], folder_id="test-folder-id", ) @@ -167,7 +167,7 @@ async def test_composio_connector_without_account_id_returns_error( count, _skipped, error, _unsupported = await index_google_drive_files( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], folder_id="test-folder-id", ) @@ -207,7 +207,7 @@ async def test_native_connector_uses_google_drive_client( await index_google_drive_files( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], folder_id="test-folder-id", ) diff --git a/surfsense_backend/tests/integration/google_unification/test_gmail_indexer_credentials.py b/surfsense_backend/tests/integration/google_unification/test_gmail_indexer_credentials.py index b869f5607..88cdbb439 100644 --- a/surfsense_backend/tests/integration/google_unification/test_gmail_indexer_credentials.py +++ b/surfsense_backend/tests/integration/google_unification/test_gmail_indexer_credentials.py @@ -37,7 +37,7 @@ async def composio_gmail(async_engine): name_prefix="gmail-composio", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @pytest_asyncio.fixture @@ -49,7 +49,7 @@ async def composio_gmail_no_id(async_engine): name_prefix="gmail-noid", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @pytest_asyncio.fixture @@ -67,7 +67,7 @@ async def native_gmail(async_engine): name_prefix="gmail-native", ) yield data - await cleanup_space(async_engine, data["search_space_id"]) + await cleanup_space(async_engine, data["workspace_id"]) @patch(_GET_ACCESS_TOKEN) @@ -101,7 +101,7 @@ async def test_composio_gmail_uses_composio_service( await index_google_gmail_messages( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], ) @@ -140,7 +140,7 @@ async def test_composio_gmail_without_account_id_returns_error( count, _skipped, error = await index_google_gmail_messages( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], ) @@ -180,7 +180,7 @@ async def test_native_gmail_uses_google_gmail_connector( await index_google_gmail_messages( session=session, connector_id=data["connector_id"], - search_space_id=data["search_space_id"], + workspace_id=data["workspace_id"], user_id=data["user_id"], ) diff --git a/surfsense_backend/tests/integration/google_unification/test_hybrid_search_type_filtering.py b/surfsense_backend/tests/integration/google_unification/test_hybrid_search_type_filtering.py index 402d3ee0b..8230fb16b 100644 --- a/surfsense_backend/tests/integration/google_unification/test_hybrid_search_type_filtering.py +++ b/surfsense_backend/tests/integration/google_unification/test_hybrid_search_type_filtering.py @@ -19,13 +19,13 @@ async def test_list_of_types_returns_both_matching_doc_types( db_session, seed_google_docs ): """Searching with a list of document types returns documents of ALL listed types.""" - space_id = seed_google_docs["search_space"].id + space_id = seed_google_docs["workspace"].id retriever = ChucksHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly report", top_k=10, - search_space_id=space_id, + workspace_id=space_id, document_type=["GOOGLE_DRIVE_FILE", "COMPOSIO_GOOGLE_DRIVE_CONNECTOR"], query_embedding=DUMMY_EMBEDDING, ) @@ -40,13 +40,13 @@ async def test_list_of_types_returns_both_matching_doc_types( async def test_single_string_type_returns_only_that_type(db_session, seed_google_docs): """Searching with a single string type returns only documents of that exact type.""" - space_id = seed_google_docs["search_space"].id + space_id = seed_google_docs["workspace"].id retriever = ChucksHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly report", top_k=10, - search_space_id=space_id, + workspace_id=space_id, document_type="GOOGLE_DRIVE_FILE", query_embedding=DUMMY_EMBEDDING, ) @@ -59,13 +59,13 @@ async def test_single_string_type_returns_only_that_type(db_session, seed_google async def test_all_invalid_types_returns_empty(db_session, seed_google_docs): """Searching with a list of nonexistent types returns an empty list, no exceptions.""" - space_id = seed_google_docs["search_space"].id + space_id = seed_google_docs["workspace"].id retriever = ChucksHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly report", top_k=10, - search_space_id=space_id, + workspace_id=space_id, document_type=["NONEXISTENT_TYPE"], query_embedding=DUMMY_EMBEDDING, ) diff --git a/surfsense_backend/tests/integration/google_unification/test_search_includes_legacy_docs.py b/surfsense_backend/tests/integration/google_unification/test_search_includes_legacy_docs.py index d9b089c12..e720f43ba 100644 --- a/surfsense_backend/tests/integration/google_unification/test_search_includes_legacy_docs.py +++ b/surfsense_backend/tests/integration/google_unification/test_search_includes_legacy_docs.py @@ -19,13 +19,13 @@ async def test_search_google_drive_includes_legacy_composio_docs( async_engine, committed_google_data, patched_session_factory, patched_embed ): """search_google_drive returns both GOOGLE_DRIVE_FILE and COMPOSIO_GOOGLE_DRIVE_CONNECTOR docs.""" - space_id = committed_google_data["search_space_id"] + space_id = committed_google_data["workspace_id"] async with patched_session_factory() as session: - service = ConnectorService(session, search_space_id=space_id) + service = ConnectorService(session, workspace_id=space_id) _, raw_docs = await service.search_google_drive( user_query="quarterly budget", - search_space_id=space_id, + workspace_id=space_id, top_k=10, ) @@ -51,13 +51,13 @@ async def test_search_files_does_not_include_google_types( async_engine, committed_google_data, patched_session_factory, patched_embed ): """search_files returns only FILE docs, not Google Drive docs.""" - space_id = committed_google_data["search_space_id"] + space_id = committed_google_data["workspace_id"] async with patched_session_factory() as session: - service = ConnectorService(session, search_space_id=space_id) + service = ConnectorService(session, workspace_id=space_id) _, raw_docs = await service.search_files( user_query="quarterly budget", - search_space_id=space_id, + workspace_id=space_id, top_k=10, ) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/adapters/test_file_upload_adapter.py b/surfsense_backend/tests/integration/indexing_pipeline/adapters/test_file_upload_adapter.py index 814129c8d..23829eebd 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/adapters/test_file_upload_adapter.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/adapters/test_file_upload_adapter.py @@ -8,19 +8,19 @@ pytestmark = pytest.mark.integration @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") -async def test_sets_status_ready(db_session, db_search_space, db_user, mocker): +async def test_sets_status_ready(db_session, db_workspace, db_user, mocker): """Document status is READY after successful indexing.""" adapter = UploadDocumentAdapter(db_session) await adapter.index( markdown_content="## Hello\n\nSome content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() @@ -28,19 +28,19 @@ async def test_sets_status_ready(db_session, db_search_space, db_user, mocker): @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") -async def test_content_is_source_markdown(db_session, db_search_space, db_user, mocker): +async def test_content_is_source_markdown(db_session, db_workspace, db_user, mocker): """Document content is set to the extracted source markdown.""" adapter = UploadDocumentAdapter(db_session) await adapter.index( markdown_content="## Hello\n\nSome content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() @@ -48,19 +48,19 @@ async def test_content_is_source_markdown(db_session, db_search_space, db_user, @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") -async def test_chunks_written_to_db(db_session, db_search_space, db_user, mocker): +async def test_chunks_written_to_db(db_session, db_workspace, db_user, mocker): """Chunks derived from the source markdown are persisted in the DB.""" adapter = UploadDocumentAdapter(db_session) await adapter.index( markdown_content="## Hello\n\nSome content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() @@ -74,7 +74,7 @@ async def test_chunks_written_to_db(db_session, db_search_space, db_user, mocker @pytest.mark.usefixtures("patched_embed_texts_raises", "patched_chunk_text") -async def test_raises_on_indexing_failure(db_session, db_search_space, db_user, mocker): +async def test_raises_on_indexing_failure(db_session, db_workspace, db_user, mocker): """RuntimeError is raised when the indexing step fails so the caller can fire a failure notification.""" adapter = UploadDocumentAdapter(db_session) with pytest.raises(RuntimeError, match=r"Embedding failed|Indexing failed"): @@ -82,7 +82,7 @@ async def test_raises_on_indexing_failure(db_session, db_search_space, db_user, markdown_content="## Hello\n\nSome content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) @@ -93,19 +93,19 @@ async def test_raises_on_indexing_failure(db_session, db_search_space, db_user, @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") -async def test_reindex_updates_content(db_session, db_search_space, db_user, mocker): +async def test_reindex_updates_content(db_session, db_workspace, db_user, mocker): """Document content is updated to the new source markdown after reindexing.""" adapter = UploadDocumentAdapter(db_session) await adapter.index( markdown_content="## Original\n\nOriginal content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() @@ -120,7 +120,7 @@ async def test_reindex_updates_content(db_session, db_search_space, db_user, moc @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_reindex_updates_content_hash( - db_session, db_search_space, db_user, mocker + db_session, db_workspace, db_user, mocker ): """Content hash is recomputed after reindexing with new source markdown.""" adapter = UploadDocumentAdapter(db_session) @@ -128,12 +128,12 @@ async def test_reindex_updates_content_hash( markdown_content="## Original\n\nOriginal content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() original_hash = document.content_hash @@ -148,19 +148,19 @@ async def test_reindex_updates_content_hash( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") -async def test_reindex_sets_status_ready(db_session, db_search_space, db_user, mocker): +async def test_reindex_sets_status_ready(db_session, db_workspace, db_user, mocker): """Document status is READY after successful reindexing.""" adapter = UploadDocumentAdapter(db_session) await adapter.index( markdown_content="## Original\n\nOriginal content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() @@ -174,7 +174,7 @@ async def test_reindex_sets_status_ready(db_session, db_search_space, db_user, m @pytest.mark.usefixtures("patched_embed_texts") -async def test_reindex_replaces_chunks(db_session, db_search_space, db_user, mocker): +async def test_reindex_replaces_chunks(db_session, db_workspace, db_user, mocker): """Reindexing replaces old chunks with new content rather than appending.""" mocker.patch( "app.indexing_pipeline.cache.cached_indexing.chunk_text_hybrid", @@ -186,12 +186,12 @@ async def test_reindex_replaces_chunks(db_session, db_search_space, db_user, moc markdown_content="## Original\n\nOriginal content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() document_id = document.id @@ -212,7 +212,7 @@ async def test_reindex_replaces_chunks(db_session, db_search_space, db_user, moc @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_reindex_clears_reindexing_flag( - db_session, db_search_space, db_user, mocker + db_session, db_workspace, db_user, mocker ): """After successful reindex, content_needs_reindexing is False.""" adapter = UploadDocumentAdapter(db_session) @@ -220,12 +220,12 @@ async def test_reindex_clears_reindexing_flag( markdown_content="## Original\n\nOriginal content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() @@ -241,7 +241,7 @@ async def test_reindex_clears_reindexing_flag( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_reindex_raises_on_failure( - db_session, db_search_space, db_user, patched_embed_texts, mocker + db_session, db_workspace, db_user, patched_embed_texts, mocker ): """RuntimeError is raised when reindexing fails so the caller can handle it.""" @@ -250,12 +250,12 @@ async def test_reindex_raises_on_failure( markdown_content="## Original\n\nOriginal content.", filename="test.pdf", etl_service="UNSTRUCTURED", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), ) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) document = result.scalars().first() @@ -269,7 +269,7 @@ async def test_reindex_raises_on_failure( async def test_reindex_raises_on_empty_source_markdown( - db_session, db_search_space, db_user, mocker + db_session, db_workspace, db_user, mocker ): """Reindexing a document with no source_markdown raises immediately.""" from app.db import DocumentType @@ -281,7 +281,7 @@ async def test_reindex_raises_on_empty_source_markdown( content_hash="abc123", unique_identifier_hash="def456", source_markdown="", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, created_by_id=str(db_user.id), ) db_session.add(document) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_calendar_pipeline.py b/surfsense_backend/tests/integration/indexing_pipeline/test_calendar_pipeline.py index 8e1ed3752..6147fb9e7 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_calendar_pipeline.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_calendar_pipeline.py @@ -15,14 +15,14 @@ pytestmark = pytest.mark.integration def _cal_doc( - *, unique_id: str, search_space_id: int, connector_id: int, user_id: str + *, unique_id: str, workspace_id: int, connector_id: int, user_id: str ) -> ConnectorDocument: return ConnectorDocument( title=f"Event {unique_id}", source_markdown=f"## Calendar Event\n\nDetails for {unique_id}", unique_id=unique_id, document_type=DocumentType.GOOGLE_CALENDAR_CONNECTOR, - search_space_id=search_space_id, + workspace_id=workspace_id, connector_id=connector_id, created_by_id=user_id, metadata={ @@ -36,13 +36,13 @@ def _cal_doc( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_calendar_pipeline_creates_ready_document( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """A Calendar ConnectorDocument flows through prepare + index to a READY document.""" - space_id = db_search_space.id + space_id = db_workspace.id doc = _cal_doc( unique_id="evt-1", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=str(db_user.id), ) @@ -54,7 +54,7 @@ async def test_calendar_pipeline_creates_ready_document( await service.index(prepared[0], doc) result = await db_session.execute( - select(Document).filter(Document.search_space_id == space_id) + select(Document).filter(Document.workspace_id == space_id) ) row = result.scalars().first() @@ -65,10 +65,10 @@ async def test_calendar_pipeline_creates_ready_document( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_calendar_legacy_doc_migrated( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """A legacy Composio Calendar doc is migrated and reused.""" - space_id = db_search_space.id + space_id = db_workspace.id user_id = str(db_user.id) evt_id = "evt-legacy-cal" @@ -82,7 +82,7 @@ async def test_calendar_legacy_doc_migrated( content_hash=f"ch-{legacy_hash[:12]}", unique_identifier_hash=legacy_hash, source_markdown="## Old event", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, embedding=[0.1] * _EMBEDDING_DIM, status={"state": "ready"}, @@ -93,7 +93,7 @@ async def test_calendar_legacy_doc_migrated( connector_doc = _cal_doc( unique_id=evt_id, - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=user_id, ) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_drive_pipeline.py b/surfsense_backend/tests/integration/indexing_pipeline/test_drive_pipeline.py index 6e85421ea..a2bd7cd30 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_drive_pipeline.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_drive_pipeline.py @@ -15,14 +15,14 @@ pytestmark = pytest.mark.integration def _drive_doc( - *, unique_id: str, search_space_id: int, connector_id: int, user_id: str + *, unique_id: str, workspace_id: int, connector_id: int, user_id: str ) -> ConnectorDocument: return ConnectorDocument( title=f"File {unique_id}.pdf", source_markdown=f"## Document Content\n\nText from file {unique_id}", unique_id=unique_id, document_type=DocumentType.GOOGLE_DRIVE_FILE, - search_space_id=search_space_id, + workspace_id=workspace_id, connector_id=connector_id, created_by_id=user_id, metadata={ @@ -35,13 +35,13 @@ def _drive_doc( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_drive_pipeline_creates_ready_document( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """A Drive ConnectorDocument flows through prepare + index to a READY document.""" - space_id = db_search_space.id + space_id = db_workspace.id doc = _drive_doc( unique_id="file-abc", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=str(db_user.id), ) @@ -53,7 +53,7 @@ async def test_drive_pipeline_creates_ready_document( await service.index(prepared[0], doc) result = await db_session.execute( - select(Document).filter(Document.search_space_id == space_id) + select(Document).filter(Document.workspace_id == space_id) ) row = result.scalars().first() @@ -64,10 +64,10 @@ async def test_drive_pipeline_creates_ready_document( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_drive_legacy_doc_migrated( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """A legacy Composio Drive doc is migrated and reused.""" - space_id = db_search_space.id + space_id = db_workspace.id user_id = str(db_user.id) file_id = "file-legacy-drive" @@ -81,7 +81,7 @@ async def test_drive_legacy_doc_migrated( content_hash=f"ch-{legacy_hash[:12]}", unique_identifier_hash=legacy_hash, source_markdown="## Old file content", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, embedding=[0.1] * _EMBEDDING_DIM, status={"state": "ready"}, @@ -92,7 +92,7 @@ async def test_drive_legacy_doc_migrated( connector_doc = _drive_doc( unique_id=file_id, - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=user_id, ) @@ -114,7 +114,7 @@ async def test_drive_legacy_doc_migrated( async def test_should_skip_file_skips_failed_document( db_session, - db_search_space, + db_workspace, db_user, ): """A FAILED document with unchanged md5 must be skipped — user can manually retry via Quick Index.""" @@ -139,7 +139,7 @@ async def test_should_skip_file_skips_failed_document( if stub: sys.modules.pop(pkg, None) - space_id = db_search_space.id + space_id = db_workspace.id file_id = "file-failed-drive" md5 = "abc123deadbeef" @@ -153,7 +153,7 @@ async def test_should_skip_file_skips_failed_document( content_hash=f"ch-{doc_hash[:12]}", unique_identifier_hash=doc_hash, source_markdown="## Real content", - search_space_id=space_id, + workspace_id=space_id, created_by_id=str(db_user.id), embedding=[0.1] * _EMBEDDING_DIM, status=DocumentStatus.failed("LLM rate limit exceeded"), @@ -182,7 +182,7 @@ async def test_should_skip_file_skips_failed_document( @pytest.mark.parametrize("stuck_state", ["pending", "processing"]) async def test_should_skip_file_retries_stuck_document( db_session, - db_search_space, + db_workspace, db_user, stuck_state, ): @@ -208,7 +208,7 @@ async def test_should_skip_file_retries_stuck_document( if stub: sys.modules.pop(pkg, None) - space_id = db_search_space.id + space_id = db_workspace.id file_id = f"file-{stuck_state}-drive" md5 = "stuck123checksum" @@ -227,7 +227,7 @@ async def test_should_skip_file_retries_stuck_document( content_hash=f"ch-{doc_hash[:12]}", unique_identifier_hash=doc_hash, source_markdown="", - search_space_id=space_id, + workspace_id=space_id, created_by_id=str(db_user.id), status=status, document_metadata={ diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_dropbox_pipeline.py b/surfsense_backend/tests/integration/indexing_pipeline/test_dropbox_pipeline.py index 9faa3db91..7e4849db0 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_dropbox_pipeline.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_dropbox_pipeline.py @@ -14,14 +14,14 @@ pytestmark = pytest.mark.integration def _dropbox_doc( - *, unique_id: str, search_space_id: int, connector_id: int, user_id: str + *, unique_id: str, workspace_id: int, connector_id: int, user_id: str ) -> ConnectorDocument: return ConnectorDocument( title=f"File {unique_id}.docx", source_markdown=f"## Document\n\nContent from {unique_id}", unique_id=unique_id, document_type=DocumentType.DROPBOX_FILE, - search_space_id=search_space_id, + workspace_id=workspace_id, connector_id=connector_id, created_by_id=user_id, metadata={ @@ -34,13 +34,13 @@ def _dropbox_doc( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_dropbox_pipeline_creates_ready_document( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """A Dropbox ConnectorDocument flows through prepare + index to a READY document.""" - space_id = db_search_space.id + space_id = db_workspace.id doc = _dropbox_doc( unique_id="db-file-abc", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=str(db_user.id), ) @@ -52,7 +52,7 @@ async def test_dropbox_pipeline_creates_ready_document( await service.index(prepared[0], doc) result = await db_session.execute( - select(Document).filter(Document.search_space_id == space_id) + select(Document).filter(Document.workspace_id == space_id) ) row = result.scalars().first() @@ -63,15 +63,15 @@ async def test_dropbox_pipeline_creates_ready_document( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_dropbox_duplicate_content_skipped( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """Re-indexing a Dropbox doc with the same content is skipped (content hash match).""" - space_id = db_search_space.id + space_id = db_workspace.id user_id = str(db_user.id) doc = _dropbox_doc( unique_id="db-dup-file", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=user_id, ) @@ -83,13 +83,13 @@ async def test_dropbox_duplicate_content_skipped( await service.index(prepared[0], doc) result = await db_session.execute( - select(Document).filter(Document.search_space_id == space_id) + select(Document).filter(Document.workspace_id == space_id) ) first_doc = result.scalars().first() assert first_doc is not None doc2 = _dropbox_doc( unique_id="db-dup-file", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=user_id, ) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_gmail_pipeline.py b/surfsense_backend/tests/integration/indexing_pipeline/test_gmail_pipeline.py index 2026393c5..f0267b583 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_gmail_pipeline.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_gmail_pipeline.py @@ -17,7 +17,7 @@ pytestmark = pytest.mark.integration def _gmail_doc( - *, unique_id: str, search_space_id: int, connector_id: int, user_id: str + *, unique_id: str, workspace_id: int, connector_id: int, user_id: str ) -> ConnectorDocument: """Build a Gmail-style ConnectorDocument like the real indexer does.""" return ConnectorDocument( @@ -25,7 +25,7 @@ def _gmail_doc( source_markdown=f"## Email\n\nBody of {unique_id}", unique_id=unique_id, document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, - search_space_id=search_space_id, + workspace_id=workspace_id, connector_id=connector_id, created_by_id=user_id, metadata={ @@ -38,13 +38,13 @@ def _gmail_doc( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_gmail_pipeline_creates_ready_document( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """A Gmail ConnectorDocument flows through prepare + index to a READY document.""" - space_id = db_search_space.id + space_id = db_workspace.id doc = _gmail_doc( unique_id="msg-pipeline-1", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=str(db_user.id), ) @@ -56,7 +56,7 @@ async def test_gmail_pipeline_creates_ready_document( await service.index(prepared[0], doc) result = await db_session.execute( - select(Document).filter(Document.search_space_id == space_id) + select(Document).filter(Document.workspace_id == space_id) ) row = result.scalars().first() @@ -68,10 +68,10 @@ async def test_gmail_pipeline_creates_ready_document( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_gmail_legacy_doc_migrated_then_reused( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """A legacy Composio Gmail doc is migrated then reused by the pipeline.""" - space_id = db_search_space.id + space_id = db_workspace.id user_id = str(db_user.id) msg_id = "msg-legacy-gmail" @@ -85,7 +85,7 @@ async def test_gmail_legacy_doc_migrated_then_reused( content_hash=f"ch-{legacy_hash[:12]}", unique_identifier_hash=legacy_hash, source_markdown="## Old content", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, embedding=[0.1] * _EMBEDDING_DIM, status={"state": "ready"}, @@ -96,7 +96,7 @@ async def test_gmail_legacy_doc_migrated_then_reused( connector_doc = _gmail_doc( unique_id=msg_id, - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=user_id, ) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_index_batch.py b/surfsense_backend/tests/integration/indexing_pipeline/test_index_batch.py index 855676f61..85cf77902 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_index_batch.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_index_batch.py @@ -11,21 +11,21 @@ pytestmark = pytest.mark.integration @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_index_batch_creates_ready_documents( - db_session, db_search_space, make_connector_document, mocker + db_session, db_workspace, make_connector_document, mocker ): """index_batch prepares and indexes a batch, resulting in READY documents.""" - space_id = db_search_space.id + space_id = db_workspace.id docs = [ make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-batch-1", - search_space_id=space_id, + workspace_id=space_id, source_markdown="## Email 1\n\nBody", ), make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-batch-2", - search_space_id=space_id, + workspace_id=space_id, source_markdown="## Email 2\n\nDifferent body", ), ] @@ -36,7 +36,7 @@ async def test_index_batch_creates_ready_documents( assert len(results) == 2 result = await db_session.execute( - select(Document).filter(Document.search_space_id == space_id) + select(Document).filter(Document.workspace_id == space_id) ) rows = result.scalars().all() assert len(rows) == 2 diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_index_document.py b/surfsense_backend/tests/integration/indexing_pipeline/test_index_document.py index ee895c61b..14e07a498 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_index_document.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_index_document.py @@ -13,12 +13,12 @@ pytestmark = pytest.mark.integration @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_sets_status_ready( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """Document status is READY after successful indexing.""" - connector_doc = make_connector_document(search_space_id=db_search_space.id) + connector_doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) prepared = await service.prepare_for_indexing([connector_doc]) @@ -38,12 +38,12 @@ async def test_sets_status_ready( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_content_is_source_markdown_by_default( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """Document content is set to source_markdown by default.""" - connector_doc = make_connector_document(search_space_id=db_search_space.id) + connector_doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) prepared = await service.prepare_for_indexing([connector_doc]) @@ -63,12 +63,12 @@ async def test_content_is_source_markdown_by_default( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_content_is_source_markdown_when_custom_content( db_session, - db_search_space, + db_workspace, make_connector_document, ): """Document content is set to source_markdown verbatim.""" connector_doc = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="## Raw content", ) service = IndexingPipelineService(session=db_session) @@ -90,12 +90,12 @@ async def test_content_is_source_markdown_when_custom_content( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_chunks_written_to_db( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """Chunks derived from source_markdown are persisted in the DB.""" - connector_doc = make_connector_document(search_space_id=db_search_space.id) + connector_doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) prepared = await service.prepare_for_indexing([connector_doc]) @@ -116,12 +116,12 @@ async def test_chunks_written_to_db( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_embedding_written_to_db( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """Document embedding vector is persisted in the DB after indexing.""" - connector_doc = make_connector_document(search_space_id=db_search_space.id) + connector_doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) prepared = await service.prepare_for_indexing([connector_doc]) @@ -142,12 +142,12 @@ async def test_embedding_written_to_db( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_updated_at_advances_after_indexing( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """updated_at timestamp is later after indexing than it was at prepare time.""" - connector_doc = make_connector_document(search_space_id=db_search_space.id) + connector_doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) prepared = await service.prepare_for_indexing([connector_doc]) @@ -172,12 +172,12 @@ async def test_updated_at_advances_after_indexing( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_no_llm_falls_back_to_source_markdown( db_session, - db_search_space, + db_workspace, make_connector_document, ): """Content stays deterministic source markdown without an LLM.""" connector_doc = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="## Fallback content", ) service = IndexingPipelineService(session=db_session) @@ -200,12 +200,12 @@ async def test_no_llm_falls_back_to_source_markdown( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_source_markdown_used_without_preview( db_session, - db_search_space, + db_workspace, make_connector_document, ): """Source markdown is used without fallback preview fields.""" connector_doc = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="## Full raw content", ) service = IndexingPipelineService(session=db_session) @@ -227,13 +227,13 @@ async def test_source_markdown_used_without_preview( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_reindex_replaces_old_chunks( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """Re-indexing a document replaces its old chunks rather than appending.""" connector_doc = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="## v1", ) service = IndexingPipelineService(session=db_session) @@ -245,7 +245,7 @@ async def test_reindex_replaces_old_chunks( await service.index(document, connector_doc) updated_doc = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="## v2", ) re_prepared = await service.prepare_for_indexing([updated_doc]) @@ -262,12 +262,12 @@ async def test_reindex_replaces_old_chunks( @pytest.mark.usefixtures("patched_embed_texts_raises", "patched_chunk_text") async def test_embedding_error_sets_status_failed( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """Document status is FAILED when embedding raises during indexing.""" - connector_doc = make_connector_document(search_space_id=db_search_space.id) + connector_doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) prepared = await service.prepare_for_indexing([connector_doc]) @@ -287,12 +287,12 @@ async def test_embedding_error_sets_status_failed( @pytest.mark.usefixtures("patched_embed_texts_raises", "patched_chunk_text") async def test_embedding_error_leaves_no_partial_data( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """A failed indexing attempt leaves no partial embedding or chunks in the DB.""" - connector_doc = make_connector_document(search_space_id=db_search_space.id) + connector_doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) prepared = await service.prepare_for_indexing([connector_doc]) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_index_editions.py b/surfsense_backend/tests/integration/indexing_pipeline/test_index_editions.py index 68d5ec0af..d93a4c483 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_index_editions.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_index_editions.py @@ -50,13 +50,13 @@ async def _load_chunks(db_session, document_id): @pytest.mark.usefixtures("paragraph_chunker") async def test_edit_keeps_unchanged_rows_and_embeds_only_the_new_text( db_session, - db_search_space, + db_workspace, make_connector_document, patched_embed_texts, ): service = IndexingPipelineService(session=db_session) doc_v1 = make_connector_document( - search_space_id=db_search_space.id, source_markdown=_V1 + workspace_id=db_workspace.id, source_markdown=_V1 ) document = await _index(service, doc_v1) @@ -65,7 +65,7 @@ async def test_edit_keeps_unchanged_rows_and_embeds_only_the_new_text( edited = "Intro paragraph.\n\nBody paragraph EDITED.\n\nOutro paragraph." doc_v2 = make_connector_document( - search_space_id=db_search_space.id, source_markdown=edited + workspace_id=db_workspace.id, source_markdown=edited ) await _index(service, doc_v2) @@ -94,14 +94,14 @@ async def test_edit_keeps_unchanged_rows_and_embeds_only_the_new_text( @pytest.mark.usefixtures("paragraph_chunker", "patched_embed_texts") async def test_head_insert_shifts_positions_without_new_rows_for_old_text( db_session, - db_search_space, + db_workspace, make_connector_document, ): service = IndexingPipelineService(session=db_session) document = await _index( service, make_connector_document( - search_space_id=db_search_space.id, source_markdown=_V1 + workspace_id=db_workspace.id, source_markdown=_V1 ), ) ids_v1 = {c.content: c.id for c in await _load_chunks(db_session, document.id)} @@ -109,7 +109,7 @@ async def test_head_insert_shifts_positions_without_new_rows_for_old_text( await _index( service, make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="Brand new opener.\n\n" + _V1, ), ) @@ -130,14 +130,14 @@ async def test_head_insert_shifts_positions_without_new_rows_for_old_text( @pytest.mark.usefixtures("paragraph_chunker", "patched_embed_texts") async def test_removed_paragraph_is_deleted_and_order_compacts( db_session, - db_search_space, + db_workspace, make_connector_document, ): service = IndexingPipelineService(session=db_session) document = await _index( service, make_connector_document( - search_space_id=db_search_space.id, source_markdown=_V1 + workspace_id=db_workspace.id, source_markdown=_V1 ), ) ids_v1 = {c.content: c.id for c in await _load_chunks(db_session, document.id)} @@ -145,7 +145,7 @@ async def test_removed_paragraph_is_deleted_and_order_compacts( await _index( service, make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="Intro paragraph.\n\nOutro paragraph.", ), ) @@ -162,7 +162,7 @@ async def test_removed_paragraph_is_deleted_and_order_compacts( @pytest.mark.usefixtures("paragraph_chunker", "patched_embed_texts") async def test_kill_switch_falls_back_to_full_replace( db_session, - db_search_space, + db_workspace, make_connector_document, monkeypatch, ): @@ -172,7 +172,7 @@ async def test_kill_switch_falls_back_to_full_replace( document = await _index( service, make_connector_document( - search_space_id=db_search_space.id, source_markdown=_V1 + workspace_id=db_workspace.id, source_markdown=_V1 ), ) ids_v1 = {c.id for c in await _load_chunks(db_session, document.id)} @@ -181,7 +181,7 @@ async def test_kill_switch_falls_back_to_full_replace( await _index( service, make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown=_V1 + "\n\nAppended paragraph.", ), ) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_local_folder_pipeline.py b/surfsense_backend/tests/integration/indexing_pipeline/test_local_folder_pipeline.py index e37c34388..1a120843e 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_local_folder_pipeline.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_local_folder_pipeline.py @@ -14,7 +14,7 @@ from app.db import ( DocumentType, DocumentVersion, Folder, - SearchSpace, + Workspace, User, ) @@ -66,7 +66,7 @@ class TestFullIndexer: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """I1: Single new .md file is indexed with status READY.""" @@ -76,7 +76,7 @@ class TestFullIndexer: count, _skipped, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -90,7 +90,7 @@ class TestFullIndexer: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ) @@ -106,7 +106,7 @@ class TestFullIndexer: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """I2: Second run on unchanged directory creates no new documents.""" @@ -116,7 +116,7 @@ class TestFullIndexer: count1, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -125,7 +125,7 @@ class TestFullIndexer: count2, _, _, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -139,7 +139,7 @@ class TestFullIndexer: .select_from(Document) .where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ).scalar_one() @@ -150,7 +150,7 @@ class TestFullIndexer: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """I3: Modified file content triggers re-index and creates a version.""" @@ -161,7 +161,7 @@ class TestFullIndexer: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -172,7 +172,7 @@ class TestFullIndexer: count, _, _, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -187,7 +187,7 @@ class TestFullIndexer: .join(Document) .where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ) @@ -201,7 +201,7 @@ class TestFullIndexer: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """I4: Deleted file is removed from DB on re-sync.""" @@ -212,7 +212,7 @@ class TestFullIndexer: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -224,7 +224,7 @@ class TestFullIndexer: .select_from(Document) .where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ).scalar_one() @@ -234,7 +234,7 @@ class TestFullIndexer: await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -247,7 +247,7 @@ class TestFullIndexer: .select_from(Document) .where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ).scalar_one() @@ -258,7 +258,7 @@ class TestFullIndexer: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """I5: Batch mode with a single file only processes that file.""" @@ -270,7 +270,7 @@ class TestFullIndexer: count, _, _, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -283,7 +283,7 @@ class TestFullIndexer: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ) @@ -305,7 +305,7 @@ class TestFolderMirroring: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """F1: First sync creates a root Folder and returns root_folder_id.""" @@ -315,7 +315,7 @@ class TestFolderMirroring: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -333,7 +333,7 @@ class TestFolderMirroring: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """F2: Nested dirs create Folder rows with correct parent_id chain.""" @@ -348,7 +348,7 @@ class TestFolderMirroring: await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -357,7 +357,7 @@ class TestFolderMirroring: folders = ( ( await db_session.execute( - select(Folder).where(Folder.search_space_id == db_search_space.id) + select(Folder).where(Folder.workspace_id == db_workspace.id) ) ) .scalars() @@ -381,7 +381,7 @@ class TestFolderMirroring: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """F3: Re-sync reuses existing Folder rows, no duplicates.""" @@ -393,7 +393,7 @@ class TestFolderMirroring: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -402,7 +402,7 @@ class TestFolderMirroring: folders_before = ( ( await db_session.execute( - select(Folder).where(Folder.search_space_id == db_search_space.id) + select(Folder).where(Folder.workspace_id == db_workspace.id) ) ) .scalars() @@ -412,7 +412,7 @@ class TestFolderMirroring: await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -422,7 +422,7 @@ class TestFolderMirroring: folders_after = ( ( await db_session.execute( - select(Folder).where(Folder.search_space_id == db_search_space.id) + select(Folder).where(Folder.workspace_id == db_workspace.id) ) ) .scalars() @@ -437,7 +437,7 @@ class TestFolderMirroring: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """F4: Documents get correct folder_id based on their directory.""" @@ -450,7 +450,7 @@ class TestFolderMirroring: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -461,7 +461,7 @@ class TestFolderMirroring: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ) @@ -485,7 +485,7 @@ class TestFolderMirroring: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """F5: Deleted dir's empty Folder row is cleaned up on re-sync.""" @@ -502,7 +502,7 @@ class TestFolderMirroring: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -517,7 +517,7 @@ class TestFolderMirroring: await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -539,7 +539,7 @@ class TestFolderMirroring: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """F6: Single-file mode creates missing Folder rows and assigns correct folder_id.""" @@ -549,7 +549,7 @@ class TestFolderMirroring: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -561,7 +561,7 @@ class TestFolderMirroring: count, _, _, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -597,7 +597,7 @@ class TestFolderMirroring: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """F7: Deleting the only file in a subfolder via batch mode removes empty Folder rows.""" @@ -610,7 +610,7 @@ class TestFolderMirroring: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -626,7 +626,7 @@ class TestFolderMirroring: await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -656,7 +656,7 @@ class TestBatchMode: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, patched_batch_sessions, ): @@ -669,7 +669,7 @@ class TestBatchMode: count, failed, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -689,7 +689,7 @@ class TestBatchMode: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ) @@ -707,7 +707,7 @@ class TestBatchMode: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, patched_batch_sessions, ): @@ -720,7 +720,7 @@ class TestBatchMode: count, failed, _, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -740,7 +740,7 @@ class TestBatchMode: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ) @@ -762,7 +762,7 @@ class TestPipelineIntegration: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, mocker, ): """P1: LOCAL_FOLDER_FILE ConnectorDocument through prepare+index to READY.""" @@ -776,7 +776,7 @@ class TestPipelineIntegration: source_markdown="## Local file\n\nContent from disk.", unique_id="test-folder:test.md", document_type=DocumentType.LOCAL_FOLDER_FILE, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, connector_id=None, created_by_id=str(db_user.id), ) @@ -794,7 +794,7 @@ class TestPipelineIntegration: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ) @@ -816,7 +816,7 @@ class TestDirectConvert: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """DC1: CSV file is indexed as a markdown table, not raw comma-separated text.""" @@ -826,7 +826,7 @@ class TestDirectConvert: count, _skipped, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -839,7 +839,7 @@ class TestDirectConvert: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ).scalar_one() @@ -853,7 +853,7 @@ class TestDirectConvert: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """DC2: TSV file is indexed as a markdown table.""" @@ -865,7 +865,7 @@ class TestDirectConvert: count, _skipped, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -878,7 +878,7 @@ class TestDirectConvert: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ).scalar_one() @@ -891,7 +891,7 @@ class TestDirectConvert: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """DC3: HTML file is indexed as clean markdown, not raw HTML.""" @@ -901,7 +901,7 @@ class TestDirectConvert: count, _skipped, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -914,7 +914,7 @@ class TestDirectConvert: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ).scalar_one() @@ -927,7 +927,7 @@ class TestDirectConvert: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """DC4: CSV via single-file batch mode also produces a markdown table.""" @@ -937,7 +937,7 @@ class TestDirectConvert: count, _skipped, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -951,7 +951,7 @@ class TestDirectConvert: await db_session.execute( select(Document).where( Document.document_type == DocumentType.LOCAL_FOLDER_FILE, - Document.search_space_id == db_search_space.id, + Document.workspace_id == db_workspace.id, ) ) ).scalar_one() @@ -984,7 +984,7 @@ class TestEtlCredits: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """CR1: Successful full-scan sync debits user.credit_micros_balance.""" @@ -998,7 +998,7 @@ class TestEtlCredits: count, _skipped, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1017,7 +1017,7 @@ class TestEtlCredits: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """CR2: Full-scan skips file when the wallet is empty.""" @@ -1030,7 +1030,7 @@ class TestEtlCredits: count, _skipped, _root_folder_id, _err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1048,7 +1048,7 @@ class TestEtlCredits: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """CR3: Single-file mode debits balance on success.""" @@ -1062,7 +1062,7 @@ class TestEtlCredits: count, _skipped, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1082,7 +1082,7 @@ class TestEtlCredits: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """CR4: Single-file mode skips file when the wallet is empty.""" @@ -1095,7 +1095,7 @@ class TestEtlCredits: count, _skipped, _root_folder_id, err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1116,7 +1116,7 @@ class TestEtlCredits: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """CR5: Re-syncing an unchanged file does not consume additional credit.""" @@ -1129,7 +1129,7 @@ class TestEtlCredits: count1, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1142,7 +1142,7 @@ class TestEtlCredits: count2, _, _, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1160,7 +1160,7 @@ class TestEtlCredits: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, patched_batch_sessions, ): @@ -1177,7 +1177,7 @@ class TestEtlCredits: count, failed, _root_folder_id, _err = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1209,7 +1209,7 @@ class TestIndexingProgressFlag: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """IP1: Full-scan mode clears indexing_in_progress after completion.""" @@ -1219,7 +1219,7 @@ class TestIndexingProgressFlag: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1237,7 +1237,7 @@ class TestIndexingProgressFlag: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """IP2: Single-file (Chokidar) mode clears indexing_in_progress after completion.""" @@ -1246,7 +1246,7 @@ class TestIndexingProgressFlag: (tmp_path / "root.md").write_text("root") _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1256,7 +1256,7 @@ class TestIndexingProgressFlag: await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", @@ -1275,7 +1275,7 @@ class TestIndexingProgressFlag: self, db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, tmp_path: Path, ): """IP3: indexing_in_progress is True on the root folder while indexing is running.""" @@ -1294,7 +1294,7 @@ class TestIndexingProgressFlag: folder = ( await db_session.execute( select(Folder).where( - Folder.search_space_id == db_search_space.id, + Folder.workspace_id == db_workspace.id, Folder.parent_id.is_(None), ) ) @@ -1308,7 +1308,7 @@ class TestIndexingProgressFlag: try: _, _, root_folder_id, _ = await index_local_folder( session=db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user_id=str(db_user.id), folder_path=str(tmp_path), folder_name="test-folder", diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_mark_connector_documents_failed.py b/surfsense_backend/tests/integration/indexing_pipeline/test_mark_connector_documents_failed.py index 9e3feee1e..ab634cb25 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_mark_connector_documents_failed.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_mark_connector_documents_failed.py @@ -20,14 +20,14 @@ pytestmark = pytest.mark.integration async def _make_doc( db_session, *, - search_space_id: int, + workspace_id: int, connector_id: int, user_id: str, file_id: str, status: dict, ) -> Document: uid_hash = compute_identifier_hash( - DocumentType.GOOGLE_DRIVE_FILE.value, file_id, search_space_id + DocumentType.GOOGLE_DRIVE_FILE.value, file_id, workspace_id ) doc = Document( title=f"{file_id}.pdf", @@ -36,7 +36,7 @@ async def _make_doc( content_hash=hashlib.sha256(f"placeholder:{uid_hash}".encode()).hexdigest(), unique_identifier_hash=uid_hash, document_metadata={"google_drive_file_id": file_id}, - search_space_id=search_space_id, + workspace_id=workspace_id, connector_id=connector_id, created_by_id=user_id, status=status, @@ -47,11 +47,11 @@ async def _make_doc( async def test_pending_placeholder_marked_failed( - db_session, db_search_space, db_connector, db_user + db_session, db_workspace, db_connector, db_user ): doc = await _make_doc( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, connector_id=db_connector.id, user_id=str(db_user.id), file_id="file-pending", @@ -61,7 +61,7 @@ async def test_pending_placeholder_marked_failed( marked = await mark_connector_documents_failed( db_session, document_type=DocumentType.GOOGLE_DRIVE_FILE, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, failures=[("file-pending", "Download/ETL failed: boom")], ) @@ -72,11 +72,11 @@ async def test_pending_placeholder_marked_failed( async def test_ready_document_not_clobbered( - db_session, db_search_space, db_connector, db_user + db_session, db_workspace, db_connector, db_user ): doc = await _make_doc( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, connector_id=db_connector.id, user_id=str(db_user.id), file_id="file-ready", @@ -86,7 +86,7 @@ async def test_ready_document_not_clobbered( marked = await mark_connector_documents_failed( db_session, document_type=DocumentType.GOOGLE_DRIVE_FILE, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, failures=[("file-ready", "should be ignored")], ) @@ -95,16 +95,16 @@ async def test_ready_document_not_clobbered( assert DocumentStatus.is_state(doc.status, DocumentStatus.READY) -async def test_missing_document_is_noop(db_session, db_search_space): +async def test_missing_document_is_noop(db_session, db_workspace): marked = await mark_connector_documents_failed( db_session, document_type=DocumentType.GOOGLE_DRIVE_FILE, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, failures=[("does-not-exist", "reason")], ) assert marked == 0 result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) assert result.scalars().first() is None diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_migrate_legacy_docs.py b/surfsense_backend/tests/integration/indexing_pipeline/test_migrate_legacy_docs.py index 8fc0e7586..be67a887b 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_migrate_legacy_docs.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_migrate_legacy_docs.py @@ -14,10 +14,10 @@ pytestmark = pytest.mark.integration async def test_legacy_composio_gmail_doc_migrated_in_db( - db_session, db_search_space, db_user, make_connector_document + db_session, db_workspace, db_user, make_connector_document ): """A Composio Gmail doc in the DB gets its hash and type updated to native.""" - space_id = db_search_space.id + space_id = db_workspace.id user_id = str(db_user.id) unique_id = "msg-legacy-123" @@ -34,7 +34,7 @@ async def test_legacy_composio_gmail_doc_migrated_in_db( content="legacy content", content_hash=f"ch-{legacy_hash[:12]}", unique_identifier_hash=legacy_hash, - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, embedding=[0.1] * _EMBEDDING_DIM, status={"state": "ready"}, @@ -46,7 +46,7 @@ async def test_legacy_composio_gmail_doc_migrated_in_db( connector_doc = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id=unique_id, - search_space_id=space_id, + workspace_id=space_id, ) service = IndexingPipelineService(session=db_session) @@ -60,32 +60,32 @@ async def test_legacy_composio_gmail_doc_migrated_in_db( async def test_no_legacy_doc_is_noop( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """When no legacy document exists, migrate_legacy_docs does nothing.""" connector_doc = make_connector_document( document_type=DocumentType.GOOGLE_CALENDAR_CONNECTOR, unique_id="evt-no-legacy", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) service = IndexingPipelineService(session=db_session) await service.migrate_legacy_docs([connector_doc]) result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) assert result.scalars().all() == [] async def test_non_google_type_is_skipped( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """migrate_legacy_docs skips ConnectorDocuments that are not Google types.""" connector_doc = make_connector_document( document_type=DocumentType.CLICKUP_CONNECTOR, unique_id="task-1", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) service = IndexingPipelineService(session=db_session) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_onedrive_pipeline.py b/surfsense_backend/tests/integration/indexing_pipeline/test_onedrive_pipeline.py index e368ec256..a5000e197 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_onedrive_pipeline.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_onedrive_pipeline.py @@ -14,14 +14,14 @@ pytestmark = pytest.mark.integration def _onedrive_doc( - *, unique_id: str, search_space_id: int, connector_id: int, user_id: str + *, unique_id: str, workspace_id: int, connector_id: int, user_id: str ) -> ConnectorDocument: return ConnectorDocument( title=f"File {unique_id}.docx", source_markdown=f"## Document\n\nContent from {unique_id}", unique_id=unique_id, document_type=DocumentType.ONEDRIVE_FILE, - search_space_id=search_space_id, + workspace_id=workspace_id, connector_id=connector_id, created_by_id=user_id, metadata={ @@ -34,13 +34,13 @@ def _onedrive_doc( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_onedrive_pipeline_creates_ready_document( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """A OneDrive ConnectorDocument flows through prepare + index to a READY document.""" - space_id = db_search_space.id + space_id = db_workspace.id doc = _onedrive_doc( unique_id="od-file-abc", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=str(db_user.id), ) @@ -52,7 +52,7 @@ async def test_onedrive_pipeline_creates_ready_document( await service.index(prepared[0], doc) result = await db_session.execute( - select(Document).filter(Document.search_space_id == space_id) + select(Document).filter(Document.workspace_id == space_id) ) row = result.scalars().first() @@ -63,15 +63,15 @@ async def test_onedrive_pipeline_creates_ready_document( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_onedrive_duplicate_content_skipped( - db_session, db_search_space, db_connector, db_user, mocker + db_session, db_workspace, db_connector, db_user, mocker ): """Re-indexing a OneDrive doc with the same content is skipped (content hash match).""" - space_id = db_search_space.id + space_id = db_workspace.id user_id = str(db_user.id) doc = _onedrive_doc( unique_id="od-dup-file", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=user_id, ) @@ -83,13 +83,13 @@ async def test_onedrive_duplicate_content_skipped( await service.index(prepared[0], doc) result = await db_session.execute( - select(Document).filter(Document.search_space_id == space_id) + select(Document).filter(Document.workspace_id == space_id) ) first_doc = result.scalars().first() assert first_doc is not None doc2 = _onedrive_doc( unique_id="od-dup-file", - search_space_id=space_id, + workspace_id=space_id, connector_id=db_connector.id, user_id=user_id, ) diff --git a/surfsense_backend/tests/integration/indexing_pipeline/test_prepare_for_indexing.py b/surfsense_backend/tests/integration/indexing_pipeline/test_prepare_for_indexing.py index 4b6662fc8..3f8914045 100644 --- a/surfsense_backend/tests/integration/indexing_pipeline/test_prepare_for_indexing.py +++ b/surfsense_backend/tests/integration/indexing_pipeline/test_prepare_for_indexing.py @@ -11,10 +11,10 @@ pytestmark = pytest.mark.integration async def test_new_document_is_persisted_with_pending_status( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """A new document is created in the DB with PENDING status and correct markdown.""" - doc = make_connector_document(search_space_id=db_search_space.id) + doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) results = await service.prepare_for_indexing([doc]) @@ -35,12 +35,12 @@ async def test_new_document_is_persisted_with_pending_status( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_unchanged_ready_document_is_skipped( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """A READY document with unchanged content is not returned for re-indexing.""" - doc = make_connector_document(search_space_id=db_search_space.id) + doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) # Index fully so the document reaches ready state @@ -56,13 +56,13 @@ async def test_unchanged_ready_document_is_skipped( @pytest.mark.usefixtures("patched_embed_texts", "patched_chunk_text") async def test_title_only_change_updates_title_in_db( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """A title-only change updates the DB title without re-queuing the document.""" original = make_connector_document( - search_space_id=db_search_space.id, title="Original Title" + workspace_id=db_workspace.id, title="Original Title" ) service = IndexingPipelineService(session=db_session) @@ -71,7 +71,7 @@ async def test_title_only_change_updates_title_in_db( await service.index(prepared[0], original) renamed = make_connector_document( - search_space_id=db_search_space.id, title="Updated Title" + workspace_id=db_workspace.id, title="Updated Title" ) results = await service.prepare_for_indexing([renamed]) @@ -86,11 +86,11 @@ async def test_title_only_change_updates_title_in_db( async def test_changed_content_is_returned_for_reprocessing( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """A document with changed content is returned for re-indexing with updated markdown.""" original = make_connector_document( - search_space_id=db_search_space.id, source_markdown="## v1" + workspace_id=db_workspace.id, source_markdown="## v1" ) service = IndexingPipelineService(session=db_session) @@ -98,7 +98,7 @@ async def test_changed_content_is_returned_for_reprocessing( original_id = first[0].id updated = make_connector_document( - search_space_id=db_search_space.id, source_markdown="## v2" + workspace_id=db_workspace.id, source_markdown="## v2" ) results = await service.prepare_for_indexing([updated]) @@ -115,24 +115,24 @@ async def test_changed_content_is_returned_for_reprocessing( async def test_all_documents_in_batch_are_persisted( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """All documents in a batch are persisted and returned.""" docs = [ make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="id-1", title="Doc 1", source_markdown="## Content 1", ), make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="id-2", title="Doc 2", source_markdown="## Content 2", ), make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="id-3", title="Doc 3", source_markdown="## Content 3", @@ -145,7 +145,7 @@ async def test_all_documents_in_batch_are_persisted( assert len(results) == 3 result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) rows = result.scalars().all() @@ -153,10 +153,10 @@ async def test_all_documents_in_batch_are_persisted( async def test_duplicate_in_batch_is_persisted_once( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """The same document passed twice in a batch is only persisted once.""" - doc = make_connector_document(search_space_id=db_search_space.id) + doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) results = await service.prepare_for_indexing([doc, doc]) @@ -164,7 +164,7 @@ async def test_duplicate_in_batch_is_persisted_once( assert len(results) == 1 result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) rows = result.scalars().all() @@ -172,11 +172,11 @@ async def test_duplicate_in_batch_is_persisted_once( async def test_created_by_id_is_persisted( - db_session, db_user, db_search_space, make_connector_document + db_session, db_user, db_workspace, make_connector_document ): """created_by_id from the connector document is persisted on the DB row.""" doc = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, created_by_id=str(db_user.id), ) service = IndexingPipelineService(session=db_session) @@ -193,11 +193,11 @@ async def test_created_by_id_is_persisted( async def test_metadata_is_updated_when_content_changes( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """document_metadata is overwritten with the latest metadata when content changes.""" original = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="## v1", metadata={"status": "in_progress"}, ) @@ -207,7 +207,7 @@ async def test_metadata_is_updated_when_content_changes( document_id = first[0].id updated = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, source_markdown="## v2", metadata={"status": "done"}, ) @@ -222,11 +222,11 @@ async def test_metadata_is_updated_when_content_changes( async def test_updated_at_advances_when_title_only_changes( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """updated_at advances even when only the title changes.""" original = make_connector_document( - search_space_id=db_search_space.id, title="Old Title" + workspace_id=db_workspace.id, title="Old Title" ) service = IndexingPipelineService(session=db_session) @@ -239,7 +239,7 @@ async def test_updated_at_advances_when_title_only_changes( updated_at_v1 = result.scalars().first().updated_at renamed = make_connector_document( - search_space_id=db_search_space.id, title="New Title" + workspace_id=db_workspace.id, title="New Title" ) await service.prepare_for_indexing([renamed]) @@ -252,11 +252,11 @@ async def test_updated_at_advances_when_title_only_changes( async def test_updated_at_advances_when_content_changes( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """updated_at advances when document content changes.""" original = make_connector_document( - search_space_id=db_search_space.id, source_markdown="## v1" + workspace_id=db_workspace.id, source_markdown="## v1" ) service = IndexingPipelineService(session=db_session) @@ -269,7 +269,7 @@ async def test_updated_at_advances_when_content_changes( updated_at_v1 = result.scalars().first().updated_at updated = make_connector_document( - search_space_id=db_search_space.id, source_markdown="## v2" + workspace_id=db_workspace.id, source_markdown="## v2" ) await service.prepare_for_indexing([updated]) @@ -282,16 +282,16 @@ async def test_updated_at_advances_when_content_changes( async def test_same_content_from_different_source_skipped_in_single_batch( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """Two documents with identical content in the same batch result in only one being persisted.""" first = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="source-a", source_markdown="## Shared content", ) second = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="source-b", source_markdown="## Shared content", ) @@ -302,22 +302,22 @@ async def test_same_content_from_different_source_skipped_in_single_batch( assert len(results) == 1 result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) assert len(result.scalars().all()) == 1 async def test_same_content_from_different_source_is_skipped( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """A document with content identical to an already-indexed document is skipped.""" first = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="source-a", source_markdown="## Shared content", ) second = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="source-b", source_markdown="## Shared content", ) @@ -329,7 +329,7 @@ async def test_same_content_from_different_source_is_skipped( assert results == [] result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) assert len(result.scalars().all()) == 1 @@ -337,12 +337,12 @@ async def test_same_content_from_different_source_is_skipped( @pytest.mark.usefixtures("patched_embed_texts_raises", "patched_chunk_text") async def test_failed_document_with_unchanged_content_is_requeued( db_session, - db_search_space, + db_workspace, make_connector_document, mocker, ): """A FAILED document with unchanged content is re-queued as PENDING on the next run.""" - doc = make_connector_document(search_space_id=db_search_space.id) + doc = make_connector_document(workspace_id=db_workspace.id) service = IndexingPipelineService(session=db_session) # First run: document is created and indexing crashes, so status becomes failed. @@ -372,11 +372,11 @@ async def test_failed_document_with_unchanged_content_is_requeued( async def test_title_and_content_change_updates_both_and_returns_document( - db_session, db_search_space, make_connector_document + db_session, db_workspace, make_connector_document ): """When both title and content change, both are updated and the document is returned for re-indexing.""" original = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Original Title", source_markdown="## v1", ) @@ -386,7 +386,7 @@ async def test_title_and_content_change_updates_both_and_returns_document( original_id = first[0].id updated = make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, title="Updated Title", source_markdown="## v2", ) @@ -406,7 +406,7 @@ async def test_title_and_content_change_updates_both_and_returns_document( async def test_one_bad_document_in_batch_does_not_prevent_others_from_being_persisted( db_session, - db_search_space, + db_workspace, make_connector_document, monkeypatch, ): @@ -416,17 +416,17 @@ async def test_one_bad_document_in_batch_does_not_prevent_others_from_being_pers """ docs = [ make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="good-1", source_markdown="## Good doc 1", ), make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="will-fail", source_markdown="## Bad doc", ), make_connector_document( - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, unique_id="good-2", source_markdown="## Good doc 2", ), @@ -448,6 +448,6 @@ async def test_one_bad_document_in_batch_does_not_prevent_others_from_being_pers assert len(results) == 2 result = await db_session.execute( - select(Document).filter(Document.search_space_id == db_search_space.id) + select(Document).filter(Document.workspace_id == db_workspace.id) ) assert len(result.scalars().all()) == 2 diff --git a/surfsense_backend/tests/integration/notifications/test_base_handler.py b/surfsense_backend/tests/integration/notifications/test_base_handler.py index ef7d9ee6c..df73edf49 100644 --- a/surfsense_backend/tests/integration/notifications/test_base_handler.py +++ b/surfsense_backend/tests/integration/notifications/test_base_handler.py @@ -1,7 +1,7 @@ """Behavior guard for the shared find/upsert/update logic (BaseNotificationHandler). Uses the connector-indexing handler instance to drive the base methods against -real Postgres, pinning upsert dedup, search-space scoping, and status stamping. +real Postgres, pinning upsert dedup, workspace scoping, and status stamping. """ from __future__ import annotations @@ -10,7 +10,7 @@ import pytest from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession -from app.db import SearchSpace, User +from app.db import Workspace, User from app.notifications.persistence import Notification from app.notifications.service import NotificationService @@ -22,7 +22,7 @@ handler = NotificationService.connector_indexing async def test_find_or_create_creates_with_progress_metadata( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """Creating a notification seeds operation id, in-progress status, and start time.""" notification = await handler.find_or_create_notification( @@ -31,7 +31,7 @@ async def test_find_or_create_creates_with_progress_metadata( operation_id="op-create", title="Title", message="Message", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) assert notification.notification_metadata["operation_id"] == "op-create" @@ -42,7 +42,7 @@ async def test_find_or_create_creates_with_progress_metadata( async def test_find_or_create_upserts_same_operation( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """Reusing an operation id updates the same row instead of creating a duplicate.""" first = await handler.find_or_create_notification( @@ -51,7 +51,7 @@ async def test_find_or_create_upserts_same_operation( operation_id="op-upsert", title="First", message="First message", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) second = await handler.find_or_create_notification( @@ -60,7 +60,7 @@ async def test_find_or_create_upserts_same_operation( operation_id="op-upsert", title="Second", message="Second message", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) assert second.id == first.id @@ -76,22 +76,22 @@ async def test_find_or_create_upserts_same_operation( assert count == 1 -async def test_find_by_operation_is_scoped_to_search_space( +async def test_find_by_operation_is_scoped_to_workspace( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - """Operation-id lookup is scoped per search space, so other spaces don't match.""" + """Operation-id lookup is scoped per workspace, so other spaces don't match.""" await handler.find_or_create_notification( session=db_session, user_id=db_user.id, operation_id="op-scoped", title="Title", message="Message", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) - other_space = SearchSpace(name="Other Space", user_id=db_user.id) + other_space = Workspace(name="Other Space", user_id=db_user.id) db_session.add(other_space) await db_session.flush() @@ -99,7 +99,7 @@ async def test_find_by_operation_is_scoped_to_search_space( session=db_session, user_id=db_user.id, operation_id="op-scoped", - search_space_id=other_space.id, + workspace_id=other_space.id, ) assert found_other is None @@ -107,7 +107,7 @@ async def test_find_by_operation_is_scoped_to_search_space( session=db_session, user_id=db_user.id, operation_id="op-scoped", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) assert found_same is not None @@ -115,7 +115,7 @@ async def test_find_by_operation_is_scoped_to_search_space( async def test_update_notification_completed_stamps_completed_at( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """Completing a notification stamps completed_at and merges metadata updates.""" notification = await handler.find_or_create_notification( @@ -124,7 +124,7 @@ async def test_update_notification_completed_stamps_completed_at( operation_id="op-complete", title="Title", message="Message", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) updated = await handler.update_notification( @@ -142,7 +142,7 @@ async def test_update_notification_completed_stamps_completed_at( async def test_update_notification_failed_stamps_completed_at( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """Failing a notification also stamps completed_at for the terminal state.""" notification = await handler.find_or_create_notification( @@ -151,7 +151,7 @@ async def test_update_notification_failed_stamps_completed_at( operation_id="op-fail", title="Title", message="Message", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) updated = await handler.update_notification( diff --git a/surfsense_backend/tests/integration/notifications/test_comment_reply_handler.py b/surfsense_backend/tests/integration/notifications/test_comment_reply_handler.py index 894f036f0..004075975 100644 --- a/surfsense_backend/tests/integration/notifications/test_comment_reply_handler.py +++ b/surfsense_backend/tests/integration/notifications/test_comment_reply_handler.py @@ -5,7 +5,7 @@ from __future__ import annotations import pytest from sqlalchemy.ext.asyncio import AsyncSession -from app.db import SearchSpace, User +from app.db import Workspace, User from app.notifications.service import NotificationService pytestmark = pytest.mark.integration @@ -13,7 +13,7 @@ pytestmark = pytest.mark.integration handler = NotificationService.comment_reply -async def _notify(db_session, db_user, db_search_space, *, reply_id=1, preview="hi"): +async def _notify(db_session, db_user, db_workspace, *, reply_id=1, preview="hi"): """Raise a comment-reply notification for the assertions in the tests below.""" return await handler.notify_comment_reply( session=db_session, @@ -28,15 +28,15 @@ async def _notify(db_session, db_user, db_search_space, *, reply_id=1, preview=" author_avatar_url=None, author_email="bob@surfsense.net", content_preview=preview, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) async def test_comment_reply_title_and_message( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """A reply notification names the author and carries the comment preview.""" - notification = await _notify(db_session, db_user, db_search_space, preview="thanks") + notification = await _notify(db_session, db_user, db_workspace, preview="thanks") assert notification.type == "comment_reply" assert notification.title == "Bob replied in a thread" @@ -44,21 +44,21 @@ async def test_comment_reply_title_and_message( async def test_comment_reply_truncates_long_preview( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """A long comment preview is truncated in the reply message.""" notification = await _notify( - db_session, db_user, db_search_space, preview="y" * 150 + db_session, db_user, db_workspace, preview="y" * 150 ) assert notification.message == "y" * 100 + "..." async def test_comment_reply_is_idempotent( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """Re-notifying the same reply id reuses the existing notification row.""" - first = await _notify(db_session, db_user, db_search_space, reply_id=5) - second = await _notify(db_session, db_user, db_search_space, reply_id=5) + first = await _notify(db_session, db_user, db_workspace, reply_id=5) + second = await _notify(db_session, db_user, db_workspace, reply_id=5) assert second.id == first.id diff --git a/surfsense_backend/tests/integration/notifications/test_connector_indexing_handler.py b/surfsense_backend/tests/integration/notifications/test_connector_indexing_handler.py index a882716b9..596c02943 100644 --- a/surfsense_backend/tests/integration/notifications/test_connector_indexing_handler.py +++ b/surfsense_backend/tests/integration/notifications/test_connector_indexing_handler.py @@ -10,7 +10,7 @@ from __future__ import annotations import pytest from sqlalchemy.ext.asyncio import AsyncSession -from app.db import SearchSpace, User +from app.db import Workspace, User from app.notifications.service import NotificationService pytestmark = pytest.mark.integration @@ -19,7 +19,7 @@ pytestmark = pytest.mark.integration async def test_indexing_started_opens_notification( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """Starting indexing opens an unread notification with connecting-stage metadata.""" notification = await NotificationService.connector_indexing.notify_indexing_started( @@ -28,7 +28,7 @@ async def test_indexing_started_opens_notification( connector_id=42, connector_name="Notion - My Workspace", connector_type="NOTION_CONNECTOR", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) assert notification.id is not None @@ -50,7 +50,7 @@ async def test_indexing_started_opens_notification( async def _started( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, *, connector_name: str = "Notion - My Workspace", ): @@ -61,17 +61,17 @@ async def _started( connector_id=42, connector_name=connector_name, connector_type="NOTION_CONNECTOR", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) async def test_indexing_progress_reports_stage_and_percent( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """Progress updates surface the stage message and compute a percent complete.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) updated = await NotificationService.connector_indexing.notify_indexing_progress( session=db_session, @@ -93,10 +93,10 @@ async def test_indexing_progress_reports_stage_and_percent( async def test_indexing_completed_clean_success( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """A clean multi-file sync reports ready/completed with plural wording.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) done = await NotificationService.connector_indexing.notify_indexing_completed( session=db_session, @@ -113,10 +113,10 @@ async def test_indexing_completed_clean_success( async def test_indexing_completed_singular_file( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """A single synced file uses singular 'file' wording.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) done = await NotificationService.connector_indexing.notify_indexing_completed( session=db_session, @@ -130,10 +130,10 @@ async def test_indexing_completed_singular_file( async def test_indexing_completed_nothing_to_sync( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """Completing with nothing new reports 'Already up to date!'.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) done = await NotificationService.connector_indexing.notify_indexing_completed( session=db_session, @@ -149,10 +149,10 @@ async def test_indexing_completed_nothing_to_sync( async def test_indexing_completed_hard_failure( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """An error with nothing synced reports a hard failure.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) done = await NotificationService.connector_indexing.notify_indexing_completed( session=db_session, @@ -170,10 +170,10 @@ async def test_indexing_completed_hard_failure( async def test_indexing_completed_partial_with_error_note( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """An error after partial progress still completes, with an appended note.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) done = await NotificationService.connector_indexing.notify_indexing_completed( session=db_session, @@ -190,10 +190,10 @@ async def test_indexing_completed_partial_with_error_note( async def test_retry_progress_frames_delay_as_providers( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """A retry message frames the delay as the provider's, using its short name.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) retry = await NotificationService.connector_indexing.notify_retry_progress( session=db_session, @@ -214,10 +214,10 @@ async def test_retry_progress_frames_delay_as_providers( async def test_retry_progress_shows_wait_and_synced_count( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): """A retry surfaces the wait time and how many items synced so far.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) retry = await NotificationService.connector_indexing.notify_retry_progress( session=db_session, diff --git a/surfsense_backend/tests/integration/notifications/test_document_processing_handler.py b/surfsense_backend/tests/integration/notifications/test_document_processing_handler.py index 5ca560f11..c5a9a9bd5 100644 --- a/surfsense_backend/tests/integration/notifications/test_document_processing_handler.py +++ b/surfsense_backend/tests/integration/notifications/test_document_processing_handler.py @@ -5,7 +5,7 @@ from __future__ import annotations import pytest from sqlalchemy.ext.asyncio import AsyncSession -from app.db import SearchSpace, User +from app.db import Workspace, User from app.notifications.service import NotificationService pytestmark = pytest.mark.integration @@ -13,22 +13,22 @@ pytestmark = pytest.mark.integration handler = NotificationService.document_processing -async def _started(db_session, db_user, db_search_space, *, name="report.pdf"): +async def _started(db_session, db_user, db_workspace, *, name="report.pdf"): """Open a document-processing notification to update in the tests below.""" return await handler.notify_processing_started( session=db_session, user_id=db_user.id, document_type="FILE", document_name=name, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) async def test_processing_started_queues( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """Starting processing queues a notification in the 'queued' stage.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) assert notification.type == "document_processing" assert notification.title == "Processing: report.pdf" @@ -37,10 +37,10 @@ async def test_processing_started_queues( async def test_processing_progress_maps_stage( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """A progress update maps the stage to its user-facing message.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) updated = await handler.notify_processing_progress( session=db_session, notification=notification, stage="parsing" @@ -51,10 +51,10 @@ async def test_processing_progress_maps_stage( async def test_processing_completed_success( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """Successful processing reports ready/searchable and a completed status.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) done = await handler.notify_processing_completed( session=db_session, notification=notification, document_id=99 @@ -66,10 +66,10 @@ async def test_processing_completed_success( async def test_processing_completed_failure( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """Failed processing reports a failed status with the error in the message.""" - notification = await _started(db_session, db_user, db_search_space) + notification = await _started(db_session, db_user, db_workspace) done = await handler.notify_processing_completed( session=db_session, notification=notification, error_message="bad file" @@ -81,7 +81,7 @@ async def test_processing_completed_failure( async def test_processing_started_truncates_long_filename( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """A long filename is truncated in the title but kept in metadata.""" long_name = "a" * 250 @@ -91,7 +91,7 @@ async def test_processing_started_truncates_long_filename( user_id=db_user.id, document_type="FILE", document_name=long_name, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) assert len(notification.title) <= 200 diff --git a/surfsense_backend/tests/integration/notifications/test_inbox_api.py b/surfsense_backend/tests/integration/notifications/test_inbox_api.py index 524a0ba60..4ab481754 100644 --- a/surfsense_backend/tests/integration/notifications/test_inbox_api.py +++ b/surfsense_backend/tests/integration/notifications/test_inbox_api.py @@ -28,14 +28,14 @@ async def _seed( title: str = "Title", message: str = "Message", read: bool = False, - search_space_id: int | None = None, + workspace_id: int | None = None, metadata: dict | None = None, created_at: datetime | None = None, ) -> Notification: """Insert a notification row directly for the API tests to read back.""" notification = Notification( user_id=user.id, - search_space_id=search_space_id, + workspace_id=workspace_id, type=type, title=title, message=message, diff --git a/surfsense_backend/tests/integration/notifications/test_insufficient_credits_handler.py b/surfsense_backend/tests/integration/notifications/test_insufficient_credits_handler.py index bdfa1b30c..1cc934a08 100644 --- a/surfsense_backend/tests/integration/notifications/test_insufficient_credits_handler.py +++ b/surfsense_backend/tests/integration/notifications/test_insufficient_credits_handler.py @@ -5,7 +5,7 @@ from __future__ import annotations import pytest from sqlalchemy.ext.asyncio import AsyncSession -from app.db import SearchSpace, User +from app.db import Workspace, User from app.notifications.service import NotificationService pytestmark = pytest.mark.integration @@ -14,7 +14,7 @@ handler = NotificationService.insufficient_credits async def test_insufficient_credits_message_and_action( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """An insufficient-credits notification states cost and carries a buy-credits link.""" notification = await handler.notify_insufficient_credits( @@ -22,7 +22,7 @@ async def test_insufficient_credits_message_and_action( user_id=db_user.id, document_name="short.pdf", document_type="FILE", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, balance_micros=250_000, required_micros=1_000_000, ) @@ -36,12 +36,12 @@ async def test_insufficient_credits_message_and_action( assert notification.notification_metadata["status"] == "failed" assert notification.notification_metadata["action_label"] == "Buy credits" assert notification.notification_metadata["action_url"] == ( - f"/dashboard/{db_search_space.id}/buy-more" + f"/dashboard/{db_workspace.id}/buy-more" ) async def test_insufficient_credits_truncates_long_name( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """A long document name is truncated in the notification title.""" long_name = "a" * 50 @@ -51,7 +51,7 @@ async def test_insufficient_credits_truncates_long_name( user_id=db_user.id, document_name=long_name, document_type="FILE", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, balance_micros=250_000, required_micros=1_000_000, ) diff --git a/surfsense_backend/tests/integration/notifications/test_mention_handler.py b/surfsense_backend/tests/integration/notifications/test_mention_handler.py index 3254d737c..b2995bcca 100644 --- a/surfsense_backend/tests/integration/notifications/test_mention_handler.py +++ b/surfsense_backend/tests/integration/notifications/test_mention_handler.py @@ -5,7 +5,7 @@ from __future__ import annotations import pytest from sqlalchemy.ext.asyncio import AsyncSession -from app.db import SearchSpace, User +from app.db import Workspace, User from app.notifications.service import NotificationService pytestmark = pytest.mark.integration @@ -13,7 +13,7 @@ pytestmark = pytest.mark.integration handler = NotificationService.mention -async def _notify(db_session, db_user, db_search_space, *, mention_id=1, preview="hi"): +async def _notify(db_session, db_user, db_workspace, *, mention_id=1, preview="hi"): """Raise an @mention notification for the assertions in the tests below.""" return await handler.notify_new_mention( session=db_session, @@ -28,15 +28,15 @@ async def _notify(db_session, db_user, db_search_space, *, mention_id=1, preview author_avatar_url=None, author_email="alice@surfsense.net", content_preview=preview, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, ) async def test_new_mention_title_and_message( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """A mention notification names the author and carries the comment preview.""" - notification = await _notify(db_session, db_user, db_search_space, preview="hello") + notification = await _notify(db_session, db_user, db_workspace, preview="hello") assert notification.type == "new_mention" assert notification.title == "Alice mentioned you" @@ -44,21 +44,21 @@ async def test_new_mention_title_and_message( async def test_new_mention_truncates_long_preview( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """A long comment preview is truncated in the mention message.""" notification = await _notify( - db_session, db_user, db_search_space, preview="x" * 150 + db_session, db_user, db_workspace, preview="x" * 150 ) assert notification.message == "x" * 100 + "..." async def test_new_mention_is_idempotent( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """Re-notifying the same mention id reuses the existing notification row.""" - first = await _notify(db_session, db_user, db_search_space, mention_id=7) - second = await _notify(db_session, db_user, db_search_space, mention_id=7) + first = await _notify(db_session, db_user, db_workspace, mention_id=7) + second = await _notify(db_session, db_user, db_workspace, mention_id=7) assert second.id == first.id diff --git a/surfsense_backend/tests/integration/podcasts/conftest.py b/surfsense_backend/tests/integration/podcasts/conftest.py index 067924ad5..a3e7a2c09 100644 --- a/surfsense_backend/tests/integration/podcasts/conftest.py +++ b/surfsense_backend/tests/integration/podcasts/conftest.py @@ -26,7 +26,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.app import app, limiter from app.auth.context import AuthContext from app.config import config as app_config -from app.db import SearchSpace, User, get_async_session +from app.db import Workspace, User, get_async_session from app.podcasts.persistence import Podcast, PodcastStatus from app.podcasts.schemas import ( DurationTarget, @@ -39,7 +39,7 @@ from app.podcasts.schemas import ( ) from app.podcasts.service import PodcastService from app.podcasts.tts import SynthesisRequest, SynthesizedAudio, TextToSpeech -from app.routes.search_spaces_routes import create_default_roles_and_membership +from app.routes.workspaces_routes import create_default_roles_and_membership from app.users import get_auth_context pytestmark = pytest.mark.integration @@ -248,14 +248,14 @@ def make_podcast(db_session: AsyncSession): async def _make( *, - search_space_id: int, + workspace_id: int, status: PodcastStatus = PodcastStatus.AWAITING_BRIEF, title: str = "Test Podcast", thread_id: int | None = None, ) -> Podcast: service = PodcastService(db_session) podcast = await service.create( - title=title, search_space_id=search_space_id, thread_id=thread_id + title=title, workspace_id=workspace_id, thread_id=thread_id ) if status is PodcastStatus.PENDING: await db_session.flush() @@ -298,7 +298,7 @@ def act_as(): @pytest_asyncio.fixture async def db_other_user(db_session: AsyncSession) -> User: - """A second user who is not a member of ``db_search_space``.""" + """A second user who is not a member of ``db_workspace``.""" user = User( id=uuid.uuid4(), email="stranger@surfsense.net", @@ -317,9 +317,9 @@ async def foreign_podcast( db_session: AsyncSession, db_other_user: User, make_podcast ) -> Podcast: """A podcast in a space owned by the other user, invisible to db_user.""" - space = SearchSpace(name="Stranger Space", user_id=db_other_user.id) + space = Workspace(name="Stranger Space", user_id=db_other_user.id) db_session.add(space) await db_session.flush() await create_default_roles_and_membership(db_session, space.id, db_other_user.id) await db_session.flush() - return await make_podcast(search_space_id=space.id, title="Foreign") + return await make_podcast(workspace_id=space.id, title="Foreign") diff --git a/surfsense_backend/tests/integration/podcasts/test_brief_gate.py b/surfsense_backend/tests/integration/podcasts/test_brief_gate.py index 46d97172d..752e1a043 100644 --- a/surfsense_backend/tests/integration/podcasts/test_brief_gate.py +++ b/surfsense_backend/tests/integration/podcasts/test_brief_gate.py @@ -14,12 +14,12 @@ pytestmark = pytest.mark.integration BASE = "/api/v1/podcasts" -async def _create(client, search_space_id: int) -> dict: +async def _create(client, workspace_id: int) -> dict: resp = await client.post( BASE, json={ "title": "Episode", - "search_space_id": search_space_id, + "workspace_id": workspace_id, "source_content": "Source content.", }, ) @@ -28,20 +28,20 @@ async def _create(client, search_space_id: int) -> dict: async def test_approve_brief_starts_drafting_and_enqueues_draft( - client, db_search_space, captured_tasks + client, db_workspace, captured_tasks ): - podcast = await _create(client, db_search_space.id) + podcast = await _create(client, db_workspace.id) resp = await client.post(f"{BASE}/{podcast['id']}/brief/approve") assert resp.status_code == 200 assert resp.json()["status"] == "drafting" - assert captured_tasks.draft == [((podcast["id"], db_search_space.id), {})] + assert captured_tasks.draft == [((podcast["id"], db_workspace.id), {})] assert captured_tasks.render == [] -async def test_update_spec_bumps_version_and_persists(client, db_search_space): - podcast = await _create(client, db_search_space.id) +async def test_update_spec_bumps_version_and_persists(client, db_workspace): + podcast = await _create(client, db_workspace.id) spec = podcast["spec"] spec["focus"] = "A sharper angle" @@ -57,8 +57,8 @@ async def test_update_spec_bumps_version_and_persists(client, db_search_space): assert body["status"] == "awaiting_brief" -async def test_update_spec_with_stale_version_conflicts(client, db_search_space): - podcast = await _create(client, db_search_space.id) +async def test_update_spec_with_stale_version_conflicts(client, db_workspace): + podcast = await _create(client, db_workspace.id) resp = await client.patch( f"{BASE}/{podcast['id']}/spec", @@ -68,8 +68,8 @@ async def test_update_spec_with_stale_version_conflicts(client, db_search_space) assert resp.status_code == 409 -async def test_update_spec_after_approval_is_rejected(client, db_search_space): - podcast = await _create(client, db_search_space.id) +async def test_update_spec_after_approval_is_rejected(client, db_workspace): + podcast = await _create(client, db_workspace.id) await client.post(f"{BASE}/{podcast['id']}/brief/approve") resp = await client.patch( diff --git a/surfsense_backend/tests/integration/podcasts/test_cancel.py b/surfsense_backend/tests/integration/podcasts/test_cancel.py index 4fe4cfc55..c8307d89e 100644 --- a/surfsense_backend/tests/integration/podcasts/test_cancel.py +++ b/surfsense_backend/tests/integration/podcasts/test_cancel.py @@ -15,9 +15,9 @@ pytestmark = pytest.mark.integration BASE = "/api/v1/podcasts" -async def test_cancel_from_a_live_state_succeeds(client, db_search_space, make_podcast): +async def test_cancel_from_a_live_state_succeeds(client, db_workspace, make_podcast): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.AWAITING_BRIEF + workspace_id=db_workspace.id, status=PodcastStatus.AWAITING_BRIEF ) resp = await client.post(f"{BASE}/{podcast.id}/cancel") @@ -27,10 +27,10 @@ async def test_cancel_from_a_live_state_succeeds(client, db_search_space, make_p async def test_cancel_from_a_terminal_state_conflicts( - client, db_search_space, make_podcast + client, db_workspace, make_podcast ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) resp = await client.post(f"{BASE}/{podcast.id}/cancel") @@ -39,12 +39,12 @@ async def test_cancel_from_a_terminal_state_conflicts( async def test_cancel_of_a_regeneration_is_rejected( - client, db_search_space, make_podcast + client, db_workspace, make_podcast ): # Cancelling here would destroy a playable episode; reverting the # regeneration is the way back. podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) await client.post(f"{BASE}/{podcast.id}/transcript/regenerate") diff --git a/surfsense_backend/tests/integration/podcasts/test_create.py b/surfsense_backend/tests/integration/podcasts/test_create.py index 19b5aeca2..fcac1148d 100644 --- a/surfsense_backend/tests/integration/podcasts/test_create.py +++ b/surfsense_backend/tests/integration/podcasts/test_create.py @@ -14,12 +14,12 @@ pytestmark = pytest.mark.integration BASE = "/api/v1/podcasts" -async def test_create_proposes_brief_and_opens_gate(client, db_search_space): +async def test_create_proposes_brief_and_opens_gate(client, db_workspace): resp = await client.post( BASE, json={ "title": "My Episode", - "search_space_id": db_search_space.id, + "workspace_id": db_workspace.id, "source_content": "A long piece of source content about a topic.", }, ) @@ -36,12 +36,12 @@ async def test_create_proposes_brief_and_opens_gate(client, db_search_space): assert body["has_audio"] is False -async def test_create_honors_requested_speaker_count(client, db_search_space): +async def test_create_honors_requested_speaker_count(client, db_workspace): resp = await client.post( BASE, json={ "title": "Solo", - "search_space_id": db_search_space.id, + "workspace_id": db_workspace.id, "source_content": "Content.", "speaker_count": 3, }, diff --git a/surfsense_backend/tests/integration/podcasts/test_draft_task.py b/surfsense_backend/tests/integration/podcasts/test_draft_task.py index 014d98b1f..dd43b3656 100644 --- a/surfsense_backend/tests/integration/podcasts/test_draft_task.py +++ b/surfsense_backend/tests/integration/podcasts/test_draft_task.py @@ -33,22 +33,22 @@ pytestmark = pytest.mark.integration def _wire_billing(monkeypatch, *, billable_call, transcript=None) -> None: """Replace the billing + LLM externals the draft body reaches for.""" - async def _resolver(_session, _search_space_id, *, thread_id=None): + async def _resolver(_session, _workspace_id, *, thread_id=None): return uuid4(), "free", "openrouter/model" async def _ainvoke(_state, config=None): return {"transcript": transcript} - monkeypatch.setattr(draft, "_resolve_agent_billing_for_search_space", _resolver) + monkeypatch.setattr(draft, "_resolve_agent_billing_for_workspace", _resolver) monkeypatch.setattr(draft, "billable_call", billable_call) monkeypatch.setattr(draft, "transcript_graph", SimpleNamespace(ainvoke=_ainvoke)) async def test_successful_draft_stores_transcript_and_starts_rendering( - monkeypatch, db_search_space, make_podcast, bind_task_session, captured_tasks + monkeypatch, db_workspace, make_podcast, bind_task_session, captured_tasks ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.DRAFTING + workspace_id=db_workspace.id, status=PodcastStatus.DRAFTING ) @asynccontextmanager @@ -57,7 +57,7 @@ async def test_successful_draft_stores_transcript_and_starts_rendering( _wire_billing(monkeypatch, billable_call=_ok, transcript=build_transcript()) - result = await draft._draft_transcript(podcast.id, db_search_space.id) + result = await draft._draft_transcript(podcast.id, db_workspace.id) assert result["status"] == "rendering" assert podcast.status == PodcastStatus.RENDERING @@ -66,10 +66,10 @@ async def test_successful_draft_stores_transcript_and_starts_rendering( async def test_quota_denial_fails_the_podcast_without_a_transcript( - monkeypatch, db_search_space, make_podcast, bind_task_session + monkeypatch, db_workspace, make_podcast, bind_task_session ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.DRAFTING + workspace_id=db_workspace.id, status=PodcastStatus.DRAFTING ) @asynccontextmanager @@ -83,7 +83,7 @@ async def test_quota_denial_fails_the_podcast_without_a_transcript( _wire_billing(monkeypatch, billable_call=_deny) - result = await draft._draft_transcript(podcast.id, db_search_space.id) + result = await draft._draft_transcript(podcast.id, db_workspace.id) assert result["reason"] == "quota" assert podcast.status == PodcastStatus.FAILED @@ -91,10 +91,10 @@ async def test_quota_denial_fails_the_podcast_without_a_transcript( async def test_billing_settlement_failure_fails_the_podcast( - monkeypatch, db_search_space, make_podcast, bind_task_session + monkeypatch, db_workspace, make_podcast, bind_task_session ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.DRAFTING + workspace_id=db_workspace.id, status=PodcastStatus.DRAFTING ) @asynccontextmanager @@ -110,7 +110,7 @@ async def test_billing_settlement_failure_fails_the_podcast( monkeypatch, billable_call=_settlement_fails, transcript=build_transcript() ) - result = await draft._draft_transcript(podcast.id, db_search_space.id) + result = await draft._draft_transcript(podcast.id, db_workspace.id) assert result["reason"] == "billing" assert podcast.status == PodcastStatus.FAILED diff --git a/surfsense_backend/tests/integration/podcasts/test_public_stream.py b/surfsense_backend/tests/integration/podcasts/test_public_stream.py index 63f634234..db08cc828 100644 --- a/surfsense_backend/tests/integration/podcasts/test_public_stream.py +++ b/surfsense_backend/tests/integration/podcasts/test_public_stream.py @@ -12,9 +12,9 @@ from app.db import NewChatThread, PublicChatSnapshot, User pytestmark = pytest.mark.integration -async def _snapshot(db_session, *, search_space_id, user: User, token: str, podcasts): +async def _snapshot(db_session, *, workspace_id, user: User, token: str, podcasts): thread = NewChatThread( - title="Shared", search_space_id=search_space_id, created_by_id=user.id + title="Shared", workspace_id=workspace_id, created_by_id=user.id ) db_session.add(thread) await db_session.flush() @@ -30,11 +30,11 @@ async def _snapshot(db_session, *, search_space_id, user: User, token: str, podc async def test_public_stream_serves_audio_via_storage_key( - client, db_session, db_search_space, db_user, fake_storage + client, db_session, db_workspace, db_user, fake_storage ): await _snapshot( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user=db_user, token="tok-audio", podcasts=[{"original_id": 555, "storage_key": "podcasts/x.mp3"}], @@ -49,11 +49,11 @@ async def test_public_stream_serves_audio_via_storage_key( async def test_public_stream_404_when_object_missing( - client, db_session, db_search_space, db_user, fake_storage + client, db_session, db_workspace, db_user, fake_storage ): await _snapshot( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user=db_user, token="tok-gone", podcasts=[{"original_id": 556, "storage_key": "podcasts/gone.mp3"}], @@ -65,11 +65,11 @@ async def test_public_stream_404_when_object_missing( async def test_public_stream_404_when_podcast_absent_from_snapshot( - client, db_session, db_search_space, db_user + client, db_session, db_workspace, db_user ): await _snapshot( db_session, - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, user=db_user, token="tok-empty", podcasts=[], diff --git a/surfsense_backend/tests/integration/podcasts/test_regeneration.py b/surfsense_backend/tests/integration/podcasts/test_regeneration.py index fd31df4ca..c93bf8dfd 100644 --- a/surfsense_backend/tests/integration/podcasts/test_regeneration.py +++ b/surfsense_backend/tests/integration/podcasts/test_regeneration.py @@ -24,10 +24,10 @@ BASE = "/api/v1/podcasts" async def test_regenerate_from_ready_reopens_the_brief_gate( - client, db_search_space, make_podcast, captured_tasks + client, db_workspace, make_podcast, captured_tasks ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) resp = await client.post(f"{BASE}/{podcast.id}/transcript/regenerate") @@ -43,10 +43,10 @@ async def test_regenerate_from_ready_reopens_the_brief_gate( async def test_approving_the_reopened_brief_starts_a_fresh_draft( - client, db_search_space, make_podcast, captured_tasks + client, db_workspace, make_podcast, captured_tasks ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) await client.post(f"{BASE}/{podcast.id}/transcript/regenerate") @@ -54,15 +54,15 @@ async def test_approving_the_reopened_brief_starts_a_fresh_draft( assert resp.status_code == 200 assert resp.json()["status"] == "drafting" - assert captured_tasks.draft == [((podcast.id, db_search_space.id), {})] + assert captured_tasks.draft == [((podcast.id, db_workspace.id), {})] async def test_regenerate_from_brief_gate_is_rejected( - client, db_search_space, make_podcast, captured_tasks + client, db_workspace, make_podcast, captured_tasks ): # Nothing has been drafted yet, so there is nothing to regenerate. podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.AWAITING_BRIEF + workspace_id=db_workspace.id, status=PodcastStatus.AWAITING_BRIEF ) resp = await client.post(f"{BASE}/{podcast.id}/transcript/regenerate") @@ -72,10 +72,10 @@ async def test_regenerate_from_brief_gate_is_rejected( async def test_regenerate_from_cancelled_is_rejected( - client, db_search_space, make_podcast, captured_tasks + client, db_workspace, make_podcast, captured_tasks ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.AWAITING_BRIEF + workspace_id=db_workspace.id, status=PodcastStatus.AWAITING_BRIEF ) await client.post(f"{BASE}/{podcast.id}/cancel") @@ -86,10 +86,10 @@ async def test_regenerate_from_cancelled_is_rejected( async def test_reverting_a_regeneration_restores_the_ready_episode( - client, db_search_space, make_podcast, captured_tasks + client, db_workspace, make_podcast, captured_tasks ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) await client.post(f"{BASE}/{podcast.id}/transcript/regenerate") @@ -105,12 +105,12 @@ async def test_reverting_a_regeneration_restores_the_ready_episode( async def test_reverting_mid_draft_keeps_the_episode( - client, db_search_space, make_podcast + client, db_workspace, make_podcast ): # Changing one's mind is allowed even after the reopened brief was # approved: the episode survives until a new render replaces it. podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) await client.post(f"{BASE}/{podcast.id}/transcript/regenerate") await client.post(f"{BASE}/{podcast.id}/brief/approve") @@ -122,10 +122,10 @@ async def test_reverting_mid_draft_keeps_the_episode( async def test_reverting_mid_render_keeps_the_episode( - client, db_session, db_search_space, make_podcast + client, db_session, db_workspace, make_podcast ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) service = PodcastService(db_session) await service.regenerate(podcast) @@ -139,12 +139,12 @@ async def test_reverting_mid_render_keeps_the_episode( async def test_reverted_episode_can_be_regenerated_again( - client, db_search_space, make_podcast + client, db_workspace, make_podcast ): # Reverting must not strand the episode: the user can change their mind # again immediately. podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) await client.post(f"{BASE}/{podcast.id}/transcript/regenerate") await client.post(f"{BASE}/{podcast.id}/regenerate/revert") @@ -156,11 +156,11 @@ async def test_reverted_episode_can_be_regenerated_again( async def test_revert_on_a_fresh_brief_gate_is_rejected( - client, db_search_space, make_podcast + client, db_workspace, make_podcast ): # A first-time brief has no regeneration to revert. podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.AWAITING_BRIEF + workspace_id=db_workspace.id, status=PodcastStatus.AWAITING_BRIEF ) resp = await client.post(f"{BASE}/{podcast.id}/regenerate/revert") @@ -170,10 +170,10 @@ async def test_revert_on_a_fresh_brief_gate_is_rejected( async def test_revert_when_nothing_was_regenerated_is_rejected( - client, db_search_space, make_podcast + client, db_workspace, make_podcast ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) resp = await client.post(f"{BASE}/{podcast.id}/regenerate/revert") @@ -182,13 +182,13 @@ async def test_revert_when_nothing_was_regenerated_is_rejected( async def test_regenerate_without_a_brief_is_rejected( - client, db_session, db_search_space, captured_tasks + client, db_session, db_workspace, captured_tasks ): # Legacy episodes finished before briefs existed; reopening a gate with # nothing to review would strand them there. podcast = Podcast( title="Legacy Episode", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, status=PodcastStatus.READY, spec_version=1, file_location="/var/old/podcast.mp3", diff --git a/surfsense_backend/tests/integration/podcasts/test_render_task.py b/surfsense_backend/tests/integration/podcasts/test_render_task.py index 5a97a00c7..8c185e3c2 100644 --- a/surfsense_backend/tests/integration/podcasts/test_render_task.py +++ b/surfsense_backend/tests/integration/podcasts/test_render_task.py @@ -20,10 +20,10 @@ pytestmark = pytest.mark.integration async def test_render_marks_ready_and_stores_audio( - db_search_space, make_podcast, bind_task_session, fake_tts, fake_merge, fake_storage + db_workspace, make_podcast, bind_task_session, fake_tts, fake_merge, fake_storage ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.RENDERING + workspace_id=db_workspace.id, status=PodcastStatus.RENDERING ) result = await render._render_audio(podcast.id) @@ -37,7 +37,7 @@ async def test_render_marks_ready_and_stores_audio( async def test_rerender_replaces_audio_and_purges_the_old_object( db_session, - db_search_space, + db_workspace, make_podcast, bind_task_session, fake_tts, @@ -47,7 +47,7 @@ async def test_rerender_replaces_audio_and_purges_the_old_object( # A regenerated episode keeps exactly one stored object: the new render # must not leak the superseded audio in the object store. podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) old_key = podcast.storage_key fake_storage.objects[old_key] = b"old-audio" @@ -68,7 +68,7 @@ async def test_rerender_replaces_audio_and_purges_the_old_object( async def test_render_losing_to_a_user_revert_keeps_the_episode_and_leaks_nothing( db_session, - db_search_space, + db_workspace, make_podcast, bind_task_session, fake_tts, @@ -79,7 +79,7 @@ async def test_render_losing_to_a_user_revert_keeps_the_episode_and_leaks_nothin # stale render must neither resurrect the redo nor leak the object it # already stored. podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) old_key = podcast.storage_key fake_storage.objects[old_key] = b"old-audio" diff --git a/surfsense_backend/tests/integration/podcasts/test_scoping.py b/surfsense_backend/tests/integration/podcasts/test_scoping.py index 304af6b6e..d5d497091 100644 --- a/surfsense_backend/tests/integration/podcasts/test_scoping.py +++ b/surfsense_backend/tests/integration/podcasts/test_scoping.py @@ -1,4 +1,4 @@ -"""Podcasts are scoped to search-space membership. +"""Podcasts are scoped to workspace membership. A user can only create or read podcasts in spaces they belong to, and an unscoped listing returns only the caller's own podcasts — never another @@ -13,9 +13,9 @@ BASE = "/api/v1/podcasts" async def test_reading_a_podcast_in_a_nonmember_space_is_forbidden( - client, db_search_space, make_podcast, act_as, db_other_user + client, db_workspace, make_podcast, act_as, db_other_user ): - podcast = await make_podcast(search_space_id=db_search_space.id) + podcast = await make_podcast(workspace_id=db_workspace.id) act_as(db_other_user) resp = await client.get(f"{BASE}/{podcast.id}") @@ -24,7 +24,7 @@ async def test_reading_a_podcast_in_a_nonmember_space_is_forbidden( async def test_creating_in_a_nonmember_space_is_forbidden( - client, db_search_space, act_as, db_other_user + client, db_workspace, act_as, db_other_user ): act_as(db_other_user) @@ -32,7 +32,7 @@ async def test_creating_in_a_nonmember_space_is_forbidden( BASE, json={ "title": "X", - "search_space_id": db_search_space.id, + "workspace_id": db_workspace.id, "source_content": "content", }, ) @@ -41,9 +41,9 @@ async def test_creating_in_a_nonmember_space_is_forbidden( async def test_listing_returns_only_the_callers_podcasts( - client, db_search_space, make_podcast, foreign_podcast + client, db_workspace, make_podcast, foreign_podcast ): - mine = await make_podcast(search_space_id=db_search_space.id, title="Mine") + mine = await make_podcast(workspace_id=db_workspace.id, title="Mine") resp = await client.get(BASE) diff --git a/surfsense_backend/tests/integration/podcasts/test_streaming.py b/surfsense_backend/tests/integration/podcasts/test_streaming.py index b924e2971..b8d748c07 100644 --- a/surfsense_backend/tests/integration/podcasts/test_streaming.py +++ b/surfsense_backend/tests/integration/podcasts/test_streaming.py @@ -16,10 +16,10 @@ BASE = "/api/v1/podcasts" async def test_stream_serves_stored_audio( - client, db_search_space, make_podcast, fake_storage + client, db_workspace, make_podcast, fake_storage ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) fake_storage.objects["podcasts/audio.mp3"] = b"the-audio" @@ -30,9 +30,9 @@ async def test_stream_serves_stored_audio( assert resp.content == b"the-audio" -async def test_stream_409_while_in_flight(client, db_search_space, make_podcast): +async def test_stream_409_while_in_flight(client, db_workspace, make_podcast): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.DRAFTING + workspace_id=db_workspace.id, status=PodcastStatus.DRAFTING ) resp = await client.get(f"{BASE}/{podcast.id}/stream") @@ -41,10 +41,10 @@ async def test_stream_409_while_in_flight(client, db_search_space, make_podcast) async def test_stream_404_when_object_missing( - client, db_search_space, make_podcast, fake_storage + client, db_workspace, make_podcast, fake_storage ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) resp = await client.get(f"{BASE}/{podcast.id}/stream") diff --git a/surfsense_backend/tests/integration/podcasts/test_task_failure.py b/surfsense_backend/tests/integration/podcasts/test_task_failure.py index 43212f58f..8b07f4753 100644 --- a/surfsense_backend/tests/integration/podcasts/test_task_failure.py +++ b/surfsense_backend/tests/integration/podcasts/test_task_failure.py @@ -17,10 +17,10 @@ pytestmark = pytest.mark.integration async def test_marking_failed_records_the_reason_on_a_running_podcast( - db_search_space, make_podcast, bind_task_session + db_workspace, make_podcast, bind_task_session ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.DRAFTING + workspace_id=db_workspace.id, status=PodcastStatus.DRAFTING ) await runtime.mark_failed(podcast.id, "tts provider unavailable") @@ -30,10 +30,10 @@ async def test_marking_failed_records_the_reason_on_a_running_podcast( async def test_marking_failed_leaves_an_already_terminal_podcast_untouched( - db_search_space, make_podcast, bind_task_session + db_workspace, make_podcast, bind_task_session ): podcast = await make_podcast( - search_space_id=db_search_space.id, status=PodcastStatus.READY + workspace_id=db_workspace.id, status=PodcastStatus.READY ) await runtime.mark_failed(podcast.id, "too late") diff --git a/surfsense_backend/tests/integration/retriever/conftest.py b/surfsense_backend/tests/integration/retriever/conftest.py index d2443723c..a8dbc090f 100644 --- a/surfsense_backend/tests/integration/retriever/conftest.py +++ b/surfsense_backend/tests/integration/retriever/conftest.py @@ -9,7 +9,7 @@ import pytest_asyncio from sqlalchemy.ext.asyncio import AsyncSession from app.config import config as app_config -from app.db import Chunk, Document, DocumentType, SearchSpace, User +from app.db import Chunk, Document, DocumentType, Workspace, User EMBEDDING_DIM = app_config.embedding_model_instance.dimension DUMMY_EMBEDDING = [0.1] * EMBEDDING_DIM @@ -20,7 +20,7 @@ def _make_document( title: str, document_type: DocumentType, content: str, - search_space_id: int, + workspace_id: int, created_by_id: str, updated_at: datetime | None = None, ) -> Document: @@ -32,7 +32,7 @@ def _make_document( content_hash=f"content-{uid}", unique_identifier_hash=f"uid-{uid}", source_markdown=content, - search_space_id=search_space_id, + workspace_id=workspace_id, created_by_id=created_by_id, embedding=DUMMY_EMBEDDING, updated_at=updated_at or datetime.now(UTC), @@ -50,29 +50,29 @@ def _make_chunk(*, content: str, document_id: int) -> Chunk: @pytest_asyncio.fixture async def seed_large_doc( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """Insert a document with 35 chunks (more than _MAX_FETCH_CHUNKS_PER_DOC=20). Also inserts a small 3-chunk document for diversity testing. - Returns a dict with ``large_doc``, ``small_doc``, ``search_space``, ``user``, + Returns a dict with ``large_doc``, ``small_doc``, ``workspace``, ``user``, and ``large_chunk_ids`` (all 35 chunk IDs). """ user_id = str(db_user.id) - space_id = db_search_space.id + space_id = db_workspace.id large_doc = _make_document( title="Large PDF Document", document_type=DocumentType.FILE, content="large document about quarterly performance reviews and budgets", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, ) small_doc = _make_document( title="Small Note", document_type=DocumentType.NOTE, content="quarterly performance review summary note", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, ) @@ -102,25 +102,25 @@ async def seed_large_doc( "small_doc": small_doc, "large_chunk_ids": [c.id for c in large_chunks], "small_chunk_ids": [c.id for c in small_chunks], - "search_space": db_search_space, + "workspace": db_workspace, "user": db_user, } @pytest_asyncio.fixture async def seed_date_filtered_docs( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ): """Insert matching docs with different timestamps for date-filter tests.""" user_id = str(db_user.id) - space_id = db_search_space.id + space_id = db_workspace.id now = datetime.now(UTC) recent_doc = _make_document( title="Recent OCV Notes", document_type=DocumentType.FILE, content="ocv meeting decisions and action items", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, updated_at=now, ) @@ -128,7 +128,7 @@ async def seed_date_filtered_docs( title="Old OCV Notes", document_type=DocumentType.FILE, content="ocv meeting decisions and action items", - search_space_id=space_id, + workspace_id=space_id, created_by_id=user_id, updated_at=now - timedelta(days=730), ) @@ -153,6 +153,6 @@ async def seed_date_filtered_docs( return { "recent_doc": recent_doc, "old_doc": old_doc, - "search_space": db_search_space, + "workspace": db_workspace, "user": db_user, } diff --git a/surfsense_backend/tests/integration/retriever/test_optimized_chunk_retriever.py b/surfsense_backend/tests/integration/retriever/test_optimized_chunk_retriever.py index f80e59304..78da3224e 100644 --- a/surfsense_backend/tests/integration/retriever/test_optimized_chunk_retriever.py +++ b/surfsense_backend/tests/integration/retriever/test_optimized_chunk_retriever.py @@ -18,13 +18,13 @@ pytestmark = pytest.mark.integration async def test_per_doc_chunk_limit_respected(db_session, seed_large_doc): """A document with 35 chunks should have at most _MAX_FETCH_CHUNKS_PER_DOC chunks returned.""" - space_id = seed_large_doc["search_space"].id + space_id = seed_large_doc["workspace"].id retriever = ChucksHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly performance review", top_k=10, - search_space_id=space_id, + workspace_id=space_id, query_embedding=DUMMY_EMBEDDING, ) @@ -40,13 +40,13 @@ async def test_per_doc_chunk_limit_respected(db_session, seed_large_doc): async def test_doc_metadata_populated_from_rrf(db_session, seed_large_doc): """Document metadata (title, type, etc.) should be present even without joinedload.""" - space_id = seed_large_doc["search_space"].id + space_id = seed_large_doc["workspace"].id retriever = ChucksHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly performance review", top_k=10, - search_space_id=space_id, + workspace_id=space_id, query_embedding=DUMMY_EMBEDDING, ) @@ -62,13 +62,13 @@ async def test_doc_metadata_populated_from_rrf(db_session, seed_large_doc): async def test_matched_chunk_ids_tracked(db_session, seed_large_doc): """matched_chunk_ids should contain the chunk IDs that appeared in the RRF results.""" - space_id = seed_large_doc["search_space"].id + space_id = seed_large_doc["workspace"].id retriever = ChucksHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly performance review", top_k=10, - search_space_id=space_id, + workspace_id=space_id, query_embedding=DUMMY_EMBEDDING, ) @@ -83,13 +83,13 @@ async def test_matched_chunk_ids_tracked(db_session, seed_large_doc): async def test_chunks_ordered_by_id(db_session, seed_large_doc): """Chunks within each document should be ordered by chunk ID (original order).""" - space_id = seed_large_doc["search_space"].id + space_id = seed_large_doc["workspace"].id retriever = ChucksHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly performance review", top_k=10, - search_space_id=space_id, + workspace_id=space_id, query_embedding=DUMMY_EMBEDDING, ) @@ -100,13 +100,13 @@ async def test_chunks_ordered_by_id(db_session, seed_large_doc): async def test_score_is_positive_float(db_session, seed_large_doc): """Each result should have a positive float score from RRF.""" - space_id = seed_large_doc["search_space"].id + space_id = seed_large_doc["workspace"].id retriever = ChucksHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly performance review", top_k=10, - search_space_id=space_id, + workspace_id=space_id, query_embedding=DUMMY_EMBEDDING, ) diff --git a/surfsense_backend/tests/integration/retriever/test_optimized_doc_retriever.py b/surfsense_backend/tests/integration/retriever/test_optimized_doc_retriever.py index 435f1eebf..d43d76cce 100644 --- a/surfsense_backend/tests/integration/retriever/test_optimized_doc_retriever.py +++ b/surfsense_backend/tests/integration/retriever/test_optimized_doc_retriever.py @@ -17,13 +17,13 @@ pytestmark = pytest.mark.integration async def test_per_doc_chunk_limit_respected(db_session, seed_large_doc): """A document with 35 chunks should have at most _MAX_FETCH_CHUNKS_PER_DOC chunks returned.""" - space_id = seed_large_doc["search_space"].id + space_id = seed_large_doc["workspace"].id retriever = DocumentHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly performance review", top_k=10, - search_space_id=space_id, + workspace_id=space_id, query_embedding=DUMMY_EMBEDDING, ) @@ -39,13 +39,13 @@ async def test_per_doc_chunk_limit_respected(db_session, seed_large_doc): async def test_doc_metadata_populated(db_session, seed_large_doc): """Document metadata should be present from the RRF results.""" - space_id = seed_large_doc["search_space"].id + space_id = seed_large_doc["workspace"].id retriever = DocumentHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly performance review", top_k=10, - search_space_id=space_id, + workspace_id=space_id, query_embedding=DUMMY_EMBEDDING, ) @@ -61,13 +61,13 @@ async def test_doc_metadata_populated(db_session, seed_large_doc): async def test_chunks_ordered_by_id(db_session, seed_large_doc): """Chunks within each document should be ordered by chunk ID.""" - space_id = seed_large_doc["search_space"].id + space_id = seed_large_doc["workspace"].id retriever = DocumentHybridSearchRetriever(db_session) results = await retriever.hybrid_search( query_text="quarterly performance review", top_k=10, - search_space_id=space_id, + workspace_id=space_id, query_embedding=DUMMY_EMBEDDING, ) diff --git a/surfsense_backend/tests/integration/test_connector_index_authz.py b/surfsense_backend/tests/integration/test_connector_index_authz.py index b25df7087..32a2b3e50 100644 --- a/surfsense_backend/tests/integration/test_connector_index_authz.py +++ b/surfsense_backend/tests/integration/test_connector_index_authz.py @@ -1,13 +1,13 @@ -"""Cross-search-space authorization on the connector index endpoint. +"""Cross-workspace authorization on the connector index endpoint. -``POST /search-source-connectors/{connector_id}/index?search_space_id=`` must -authorize against the **connector's own** ``search_space_id`` (matching the -read/update/delete handlers), not the caller-supplied ``search_space_id`` query +``POST /search-source-connectors/{connector_id}/index?workspace_id=`` must +authorize against the **connector's own** ``workspace_id`` (matching the +read/update/delete handlers), not the caller-supplied ``workspace_id`` query parameter, and must reject a connector that does not belong to the requested -search space. +workspace. -Without this, a user who owns search space B could index another user's -connector (which lives in space A) by passing ``search_space_id=B``: the +Without this, a user who owns workspace B could index another user's +connector (which lives in space A) by passing ``workspace_id=B``: the background indexer would run with the **victim connector's stored credentials** and write the fetched content into the attacker's space. These tests pin that boundary. @@ -27,11 +27,11 @@ from app.auth.context import AuthContext from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - SearchSpace, + Workspace, User, ) from app.routes.search_source_connectors_routes import index_connector_content -from app.routes.search_spaces_routes import create_default_roles_and_membership +from app.routes.workspaces_routes import create_default_roles_and_membership pytestmark = pytest.mark.integration @@ -39,9 +39,9 @@ pytestmark = pytest.mark.integration _CHECK_PERMISSION = "app.routes.search_source_connectors_routes.check_permission" -async def _make_user_with_space(session: AsyncSession) -> tuple[User, SearchSpace]: - """A user plus a search space they own, with the default roles/membership - the ``POST /searchspaces`` route would create (so ``check_permission`` would +async def _make_user_with_space(session: AsyncSession) -> tuple[User, Workspace]: + """A user plus a workspace they own, with the default roles/membership + the ``POST /workspaces`` route would create (so ``check_permission`` would legitimately pass for this user on this space).""" user = User( id=uuid.uuid4(), @@ -53,7 +53,7 @@ async def _make_user_with_space(session: AsyncSession) -> tuple[User, SearchSpac ) session.add(user) await session.flush() - space = SearchSpace(name=f"Space {uuid.uuid4().hex[:8]}", user_id=user.id) + space = Workspace(name=f"Space {uuid.uuid4().hex[:8]}", user_id=user.id) session.add(space) await session.flush() await create_default_roles_and_membership(session, space.id, user.id) @@ -64,7 +64,7 @@ async def _make_user_with_space(session: AsyncSession) -> tuple[User, SearchSpac async def _make_connector( session: AsyncSession, owner: User, - space: SearchSpace, + space: Workspace, connector_type: SearchSourceConnectorType, ) -> SearchSourceConnector: connector = SearchSourceConnector( @@ -77,7 +77,7 @@ async def _make_connector( "repo_full_names": ["octocat/Hello-World"], }, is_indexable=True, - search_space_id=space.id, + workspace_id=space.id, user_id=owner.id, ) session.add(connector) @@ -90,7 +90,7 @@ class TestConnectorIndexCrossSpaceAuthz: self, db_session: AsyncSession ): """Attacker (owns space B) cannot index victim's connector (in space A) - by passing ``search_space_id=B``. + by passing ``workspace_id=B``. The mismatch is rejected with 404 **before** ``check_permission`` runs — which is essential, because that permission check *would* pass: the @@ -108,13 +108,13 @@ class TestConnectorIndexCrossSpaceAuthz: ): await index_connector_content( connector_id=connector_a.id, - search_space_id=space_b.id, # the attacker's own space + workspace_id=space_b.id, # the attacker's own space session=db_session, auth=AuthContext.session(attacker), ) assert exc_info.value.status_code == 404 - # Rejected at the search-space reconciliation, never reaching (or relying + # Rejected at the workspace reconciliation, never reaching (or relying # on) the permission check — which would have passed for space B. check_permission_mock.assert_not_awaited() @@ -122,7 +122,7 @@ class TestConnectorIndexCrossSpaceAuthz: self, db_session: AsyncSession ): """A legitimate same-space index passes the reconciliation and authorizes - ``check_permission`` against the connector's **own** search space (not the + ``check_permission`` against the connector's **own** workspace (not the client-supplied query param).""" owner, space = await _make_user_with_space(db_session) # A "live" connector type returns early (no Celery dispatch) right after @@ -139,11 +139,11 @@ class TestConnectorIndexCrossSpaceAuthz: ): await index_connector_content( connector_id=connector.id, - search_space_id=space.id, # the connector's own space + workspace_id=space.id, # the connector's own space session=db_session, auth=AuthContext.session(owner), ) check_permission_mock.assert_awaited_once() # The space passed to check_permission must be the connector's own space. - assert connector.search_space_id in check_permission_mock.await_args.args + assert connector.workspace_id in check_permission_mock.await_args.args diff --git a/surfsense_backend/tests/integration/test_document_versioning.py b/surfsense_backend/tests/integration/test_document_versioning.py index 9bd03d219..b98f164fd 100644 --- a/surfsense_backend/tests/integration/test_document_versioning.py +++ b/surfsense_backend/tests/integration/test_document_versioning.py @@ -7,14 +7,14 @@ import pytest_asyncio from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession -from app.db import Document, DocumentType, DocumentVersion, SearchSpace, User +from app.db import Document, DocumentType, DocumentVersion, Workspace, User pytestmark = pytest.mark.integration @pytest_asyncio.fixture async def db_document( - db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + db_session: AsyncSession, db_user: User, db_workspace: Workspace ) -> Document: doc = Document( title="Test Doc", @@ -24,7 +24,7 @@ async def db_document( content_hash="abc123", unique_identifier_hash="local_folder:test-folder:test.md", source_markdown="# Test\n\nOriginal content.", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, created_by_id=db_user.id, ) db_session.add(doc) diff --git a/surfsense_backend/tests/integration/test_obsidian_plugin_routes.py b/surfsense_backend/tests/integration/test_obsidian_plugin_routes.py index 576255fe0..ae9870ac7 100644 --- a/surfsense_backend/tests/integration/test_obsidian_plugin_routes.py +++ b/surfsense_backend/tests/integration/test_obsidian_plugin_routes.py @@ -32,7 +32,7 @@ from app.auth.context import AuthContext from app.db import ( SearchSourceConnector, SearchSourceConnectorType, - SearchSpace, + Workspace, User, ) from app.routes.obsidian_plugin_routes import ( @@ -43,7 +43,7 @@ from app.routes.obsidian_plugin_routes import ( obsidian_stats, obsidian_sync, ) -from app.routes.search_spaces_routes import create_default_roles_and_membership +from app.routes.workspaces_routes import create_default_roles_and_membership from app.schemas.obsidian_plugin import ( ConnectRequest, DeleteAck, @@ -90,7 +90,7 @@ def _make_note_payload(vault_id: str, path: str, content_hash: str) -> NotePaylo @pytest_asyncio.fixture async def race_user_and_space(async_engine): - """User + SearchSpace committed via the live engine so the two + """User + Workspace committed via the live engine so the two concurrent /connect sessions in the race test can both see them. We can't use the savepoint-trapped ``db_session`` fixture here @@ -106,7 +106,7 @@ async def race_user_and_space(async_engine): is_superuser=False, is_verified=True, ) - space = SearchSpace(name="Race Space", user_id=user_id) + space = Workspace(name="Race Space", user_id=user_id) setup.add_all([user, space]) await setup.flush() await create_default_roles_and_membership(setup, space.id, user_id) @@ -167,7 +167,7 @@ class TestConnectRace: payload = ConnectRequest( vault_id=vault_id, vault_name=f"My Vault {name_suffix}", - search_space_id=space_id, + workspace_id=space_id, vault_fingerprint=fingerprint, ) await obsidian_connect(payload, auth=_auth(fresh_user), session=s) @@ -207,7 +207,7 @@ class TestConnectRace: "vault_fingerprint": "fp-1", }, user_id=user_id, - search_space_id=space_id, + workspace_id=space_id, ) ) await s.commit() @@ -226,7 +226,7 @@ class TestConnectRace: "vault_fingerprint": "fp-2", }, user_id=user_id, - search_space_id=space_id, + workspace_id=space_id, ) ) await s.commit() @@ -252,7 +252,7 @@ class TestConnectRace: "vault_fingerprint": fingerprint, }, user_id=user_id, - search_space_id=space_id, + workspace_id=space_id, ) ) await s.commit() @@ -271,7 +271,7 @@ class TestConnectRace: "vault_fingerprint": fingerprint, }, user_id=user_id, - search_space_id=space_id, + workspace_id=space_id, ) ) await s.commit() @@ -294,7 +294,7 @@ class TestConnectRace: ConnectRequest( vault_id=vault_id_a, vault_name="Shared Vault", - search_space_id=space_id, + workspace_id=space_id, vault_fingerprint=fingerprint, ), auth=_auth(fresh_user), @@ -307,7 +307,7 @@ class TestConnectRace: ConnectRequest( vault_id=vault_id_b, vault_name="Shared Vault", - search_space_id=space_id, + workspace_id=space_id, vault_fingerprint=fingerprint, ), auth=_auth(fresh_user), @@ -341,7 +341,7 @@ class TestWireContractSmoke: field renames the way the TypeScript decoder would catch them.""" async def test_full_flow_returns_typed_payloads( - self, db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + self, db_session: AsyncSession, db_user: User, db_workspace: Workspace ): vault_id = str(uuid.uuid4()) @@ -350,7 +350,7 @@ class TestWireContractSmoke: ConnectRequest( vault_id=vault_id, vault_name="Smoke Vault", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, vault_fingerprint="fp-" + uuid.uuid4().hex, ), auth=_auth(db_user), @@ -488,14 +488,14 @@ class TestWireContractSmoke: assert stats_resp.last_sync_at is None async def test_sync_queues_binary_attachments( - self, db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + self, db_session: AsyncSession, db_user: User, db_workspace: Workspace ): vault_id = str(uuid.uuid4()) await obsidian_connect( ConnectRequest( vault_id=vault_id, vault_name="Queue Vault", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, vault_fingerprint="fp-" + uuid.uuid4().hex, ), auth=_auth(db_user), @@ -539,14 +539,14 @@ class TestWireContractSmoke: queue_mock.assert_called_once() async def test_sync_rejects_unsupported_attachment_extension( - self, db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + self, db_session: AsyncSession, db_user: User, db_workspace: Workspace ): vault_id = str(uuid.uuid4()) await obsidian_connect( ConnectRequest( vault_id=vault_id, vault_name="Reject Vault", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, vault_fingerprint="fp-" + uuid.uuid4().hex, ), auth=_auth(db_user), @@ -593,14 +593,14 @@ class TestWireContractSmoke: queue_mock.assert_not_called() async def test_sync_rejects_mime_extension_mismatch( - self, db_session: AsyncSession, db_user: User, db_search_space: SearchSpace + self, db_session: AsyncSession, db_user: User, db_workspace: Workspace ): vault_id = str(uuid.uuid4()) await obsidian_connect( ConnectRequest( vault_id=vault_id, vault_name="Mismatch Vault", - search_space_id=db_search_space.id, + workspace_id=db_workspace.id, vault_fingerprint="fp-" + uuid.uuid4().hex, ), auth=_auth(db_user), diff --git a/surfsense_backend/tests/integration/test_pat_fail_closed_authz.py b/surfsense_backend/tests/integration/test_pat_fail_closed_authz.py index 5bec3f48a..b8cca33bf 100644 --- a/surfsense_backend/tests/integration/test_pat_fail_closed_authz.py +++ b/surfsense_backend/tests/integration/test_pat_fail_closed_authz.py @@ -7,9 +7,9 @@ from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.auth.context import AuthContext -from app.db import PersonalAccessToken, SearchSpace, User +from app.db import PersonalAccessToken, Workspace, User from app.users import allow_any_principal, require_session_context -from app.utils.rbac import check_search_space_access +from app.utils.rbac import check_workspace_access pytestmark = pytest.mark.integration @@ -43,29 +43,29 @@ async def test_pat_is_allowed_by_bootstrap_dependency(db_user: User): async def test_pat_is_rejected_for_api_disabled_space( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - db_search_space.api_access_enabled = False + db_workspace.api_access_enabled = False await db_session.flush() auth = _pat_auth(db_user) with pytest.raises(HTTPException) as exc_info: - await check_search_space_access(db_session, auth, db_search_space.id) + await check_workspace_access(db_session, auth, db_workspace.id) assert exc_info.value.status_code == 403 - assert exc_info.value.detail == "API access is not enabled for this search space." + assert exc_info.value.detail == "API access is not enabled for this workspace." async def test_pat_is_allowed_for_api_enabled_space( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - db_search_space.api_access_enabled = True + db_workspace.api_access_enabled = True await db_session.flush() auth = _pat_auth(db_user) - membership = await check_search_space_access(db_session, auth, db_search_space.id) + membership = await check_workspace_access(db_session, auth, db_workspace.id) assert membership.user_id == db_user.id - assert membership.search_space_id == db_search_space.id + assert membership.workspace_id == db_workspace.id diff --git a/surfsense_backend/tests/integration/test_zero_authz_context.py b/surfsense_backend/tests/integration/test_zero_authz_context.py index dcb0fe34a..12c42098d 100644 --- a/surfsense_backend/tests/integration/test_zero_authz_context.py +++ b/surfsense_backend/tests/integration/test_zero_authz_context.py @@ -7,9 +7,9 @@ from fastapi import HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.auth.context import AuthContext -from app.db import PersonalAccessToken, SearchSpace, User -from app.routes.search_spaces_routes import create_default_roles_and_membership -from app.utils.rbac import check_search_space_access, get_allowed_read_space_ids +from app.db import PersonalAccessToken, Workspace, User +from app.routes.workspaces_routes import create_default_roles_and_membership +from app.utils.rbac import check_workspace_access, get_allowed_read_space_ids pytestmark = pytest.mark.integration @@ -30,8 +30,8 @@ async def _space_with_membership( user: User, *, api_access_enabled: bool, -) -> SearchSpace: - space = SearchSpace( +) -> Workspace: + space = Workspace( name="Zero Authz Space", user_id=user.id, api_access_enabled=api_access_enabled, @@ -43,10 +43,10 @@ async def _space_with_membership( return space -async def test_zero_read_set_matches_session_search_space_access( +async def test_zero_read_set_matches_session_workspace_access( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): disabled_space = await _space_with_membership( db_session, @@ -57,17 +57,17 @@ async def test_zero_read_set_matches_session_search_space_access( allowed_ids = set(await get_allowed_read_space_ids(db_session, session_auth)) - for space in (db_search_space, disabled_space): - membership = await check_search_space_access(db_session, session_auth, space.id) - assert membership.search_space_id in allowed_ids + for space in (db_workspace, disabled_space): + membership = await check_workspace_access(db_session, session_auth, space.id) + assert membership.workspace_id in allowed_ids async def test_zero_read_set_applies_pat_api_access_gate( db_session: AsyncSession, db_user: User, - db_search_space: SearchSpace, + db_workspace: Workspace, ): - db_search_space.api_access_enabled = True + db_workspace.api_access_enabled = True disabled_space = await _space_with_membership( db_session, db_user, @@ -78,8 +78,8 @@ async def test_zero_read_set_applies_pat_api_access_gate( allowed_ids = set(await get_allowed_read_space_ids(db_session, pat_auth)) - assert db_search_space.id in allowed_ids + assert db_workspace.id in allowed_ids assert disabled_space.id not in allowed_ids with pytest.raises(HTTPException) as exc_info: - await check_search_space_access(db_session, pat_auth, disabled_space.id) + await check_workspace_access(db_session, pat_auth, disabled_space.id) assert exc_info.value.status_code == 403 diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_action_log.py b/surfsense_backend/tests/unit/agents/new_chat/test_action_log.py index e476538bd..30f45f2e4 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_action_log.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_action_log.py @@ -94,7 +94,7 @@ def fake_session_factory(): class TestActionLogMiddlewareDisabled: @pytest.mark.asyncio async def test_no_op_when_flag_off(self, patch_get_flags) -> None: - mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None) + mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None) request = _FakeRequest( tool_call={ "name": "make_widget", @@ -110,7 +110,7 @@ class TestActionLogMiddlewareDisabled: @pytest.mark.asyncio async def test_no_op_when_thread_id_none(self, patch_get_flags) -> None: - mw = ActionLogMiddleware(thread_id=None, search_space_id=1, user_id=None) + mw = ActionLogMiddleware(thread_id=None, workspace_id=1, user_id=None) request = _FakeRequest( tool_call={"name": "make_widget", "args": {}, "id": "tc1"} ) @@ -126,7 +126,7 @@ class TestActionLogMiddlewarePersistence: self, patch_get_flags, fake_session_factory ) -> None: captured, factory = fake_session_factory - mw = ActionLogMiddleware(thread_id=42, search_space_id=7, user_id="u1") + mw = ActionLogMiddleware(thread_id=42, workspace_id=7, user_id="u1") request = _FakeRequest( tool_call={ "name": "make_widget", @@ -150,7 +150,7 @@ class TestActionLogMiddlewarePersistence: assert len(captured["rows"]) == 1 row = captured["rows"][0] assert row.thread_id == 42 - assert row.search_space_id == 7 + assert row.workspace_id == 7 assert row.user_id == "u1" assert row.tool_name == "make_widget" assert row.args == {"color": "red", "size": 3} @@ -170,7 +170,7 @@ class TestActionLogMiddlewarePersistence: ) -> None: """``chat_turn_id`` falls back to NULL when ``runtime.config`` is absent.""" captured, factory = fake_session_factory - mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None) + mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None) request = _FakeRequest( tool_call={"name": "make_widget", "args": {}, "id": "tc-1"}, runtime=None, @@ -190,7 +190,7 @@ class TestActionLogMiddlewarePersistence: self, patch_get_flags, fake_session_factory ) -> None: captured, factory = fake_session_factory - mw = ActionLogMiddleware(thread_id=42, search_space_id=7, user_id="u1") + mw = ActionLogMiddleware(thread_id=42, workspace_id=7, user_id="u1") request = _FakeRequest( tool_call={"name": "make_widget", "args": {"color": "red"}, "id": "tc1"} ) @@ -214,7 +214,7 @@ class TestActionLogMiddlewarePersistence: self, patch_get_flags ) -> None: """Even if the DB write blows up, the tool's result must reach the model.""" - mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None) + mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None) request = _FakeRequest( tool_call={"name": "make_widget", "args": {}, "id": "tc1"} ) @@ -250,7 +250,7 @@ class TestReverseDescriptor: ) mw = ActionLogMiddleware( thread_id=1, - search_space_id=1, + workspace_id=1, user_id="u", tool_definitions={"make_widget": tool_def}, ) @@ -296,7 +296,7 @@ class TestReverseDescriptor: ) mw = ActionLogMiddleware( thread_id=1, - search_space_id=1, + workspace_id=1, user_id=None, tool_definitions={"make_widget": tool_def}, ) @@ -321,7 +321,7 @@ class TestReverseDescriptor: self, patch_get_flags, fake_session_factory ) -> None: captured, factory = fake_session_factory - mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None) + mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None) request = _FakeRequest( tool_call={"name": "unknown_tool", "args": {}, "id": "tc1"} ) @@ -343,7 +343,7 @@ class TestActionLogDispatch: self, patch_get_flags, fake_session_factory ) -> None: _captured, factory = fake_session_factory - mw = ActionLogMiddleware(thread_id=42, search_space_id=7, user_id="u1") + mw = ActionLogMiddleware(thread_id=42, workspace_id=7, user_id="u1") request = _FakeRequest( tool_call={ "name": "make_widget", @@ -383,7 +383,7 @@ class TestActionLogDispatch: @pytest.mark.asyncio async def test_no_dispatch_when_persistence_fails(self, patch_get_flags) -> None: """If commit fails the dispatch is suppressed (no row to surface).""" - mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None) + mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None) request = _FakeRequest( tool_call={"name": "make_widget", "args": {}, "id": "tc1"} ) @@ -411,7 +411,7 @@ class TestArgsTruncation: self, patch_get_flags, fake_session_factory ) -> None: captured, factory = fake_session_factory - mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None) + mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None) # Build a > 32KB string so the persisted payload triggers the truncation path. huge = "x" * (40 * 1024) request = _FakeRequest( diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_mention_resolver.py b/surfsense_backend/tests/unit/agents/new_chat/test_mention_resolver.py index 6aebee093..b568f5a99 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_mention_resolver.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_mention_resolver.py @@ -99,7 +99,7 @@ class TestResolveMentions: session.execute = AsyncMock() result = await resolve_mentions( session, - search_space_id=1, + workspace_id=1, mentioned_documents=None, ) assert isinstance(result, ResolvedMentionSet) @@ -134,7 +134,7 @@ class TestResolveMentions: out = await resolve_mentions( session, - search_space_id=5, + workspace_id=5, mentioned_documents=[chip], ) assert len(out.mentions) == 1 @@ -170,7 +170,7 @@ class TestResolveMentions: out = await resolve_mentions( session, - search_space_id=3, + workspace_id=3, mentioned_documents=[chip], ) assert len(out.mentions) == 1 @@ -201,7 +201,7 @@ class TestResolveMentions: out = await resolve_mentions( session, - search_space_id=1, + workspace_id=1, mentioned_documents=[chip], ) assert out.mentions == [] @@ -238,7 +238,7 @@ class TestResolveMentions: out = await resolve_mentions( session, - search_space_id=1, + workspace_id=1, mentioned_documents=[chip_short, chip_long], ) tokens = [tok for tok, _ in out.token_to_path] @@ -265,7 +265,7 @@ class TestResolveMentions: out = await resolve_mentions( session, - search_space_id=2, + workspace_id=2, mentioned_documents=None, mentioned_document_ids=[7], ) diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_path_resolver.py b/surfsense_backend/tests/unit/agents/new_chat/test_path_resolver.py index 2617bff8e..ba9dc242b 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_path_resolver.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_path_resolver.py @@ -149,7 +149,7 @@ class TestVirtualPathToDoc: document = await virtual_path_to_doc( session, - search_space_id=5, + workspace_id=5, virtual_path=f"{DOCUMENTS_ROOT}/{encoded_basename}", ) assert document is target_doc @@ -169,7 +169,7 @@ class TestVirtualPathToDoc: document = await virtual_path_to_doc( session, - search_space_id=5, + workspace_id=5, virtual_path=f"{DOCUMENTS_ROOT}/Calendar_ Happy birthday!.xml", ) assert document is None @@ -191,7 +191,7 @@ class TestVirtualPathToDoc: document = await virtual_path_to_doc( session, - search_space_id=5, + workspace_id=5, virtual_path=f"{DOCUMENTS_ROOT}/Plain Note.xml", ) assert document is target_doc @@ -217,7 +217,7 @@ class TestVirtualPathToDoc: document = await virtual_path_to_doc( session, - search_space_id=5, + workspace_id=5, virtual_path=f"{DOCUMENTS_ROOT}/2025-W2.pdf.xml", ) assert document is target_doc @@ -239,7 +239,7 @@ class TestVirtualPathToDoc: document = await virtual_path_to_doc( session, - search_space_id=5, + workspace_id=5, virtual_path=f"{DOCUMENTS_ROOT}/2025-W2.pdf", ) assert document is target_doc diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_plugin_loader.py b/surfsense_backend/tests/unit/agents/new_chat/test_plugin_loader.py index 9b3931549..6477bc672 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_plugin_loader.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_plugin_loader.py @@ -30,7 +30,7 @@ class _DummyMiddleware(AgentMiddleware): def _ctx() -> PluginContext: return PluginContext.build( - search_space_id=1, + workspace_id=1, user_id="u", thread_visibility="PRIVATE", # type: ignore[arg-type] llm=MagicMock(), @@ -165,12 +165,12 @@ class TestPluginContext: def test_build_includes_required_fields(self) -> None: llm = MagicMock() ctx = PluginContext.build( - search_space_id=42, + workspace_id=42, user_id="user-1", thread_visibility="PRIVATE", # type: ignore[arg-type] llm=llm, ) - assert ctx["search_space_id"] == 42 + assert ctx["workspace_id"] == 42 assert ctx["user_id"] == "user-1" assert ctx["llm"] is llm diff --git a/surfsense_backend/tests/unit/agents/new_chat/test_skills_backends.py b/surfsense_backend/tests/unit/agents/new_chat/test_skills_backends.py index 1c497d99b..f068a8ca4 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/test_skills_backends.py +++ b/surfsense_backend/tests/unit/agents/new_chat/test_skills_backends.py @@ -11,7 +11,7 @@ from app.agents.chat.multi_agent_chat.main_agent.skills.backends import ( SKILLS_BUILTIN_PREFIX, SKILLS_SPACE_PREFIX, BuiltinSkillsBackend, - SearchSpaceSkillsBackend, + WorkspaceSkillsBackend, build_skills_backend_factory, default_skills_sources, ) @@ -176,7 +176,7 @@ class _FakeKBBackend: return out -class TestSearchSpaceSkillsBackend: +class TestWorkspaceSkillsBackend: def test_remaps_paths_when_listing(self) -> None: listing = [ {"path": "/documents/_skills/policy", "is_dir": True}, @@ -184,7 +184,7 @@ class TestSearchSpaceSkillsBackend: {"path": "/documents/other-folder/x.md", "is_dir": False}, ] kb = _FakeKBBackend(listing=listing, file_contents={}) - backend = SearchSpaceSkillsBackend(kb) + backend = WorkspaceSkillsBackend(kb) infos = asyncio.run(backend.als_info("/")) assert kb.last_ls_path == "/documents/_skills" paths = [info["path"] for info in infos] @@ -200,7 +200,7 @@ class TestSearchSpaceSkillsBackend: "/documents/_skills/policy/SKILL.md": b"---\nname: policy\n---\n", }, ) - backend = SearchSpaceSkillsBackend(kb) + backend = WorkspaceSkillsBackend(kb) responses = asyncio.run(backend.adownload_files(["/policy/SKILL.md"])) assert kb.last_download_paths == ["/documents/_skills/policy/SKILL.md"] assert responses[0].path == "/policy/SKILL.md" @@ -208,7 +208,7 @@ class TestSearchSpaceSkillsBackend: assert responses[0].content is not None def test_sync_methods_raise_not_implemented(self) -> None: - backend = SearchSpaceSkillsBackend(_FakeKBBackend([], {})) + backend = WorkspaceSkillsBackend(_FakeKBBackend([], {})) with pytest.raises(NotImplementedError): backend.ls_info("/") with pytest.raises(NotImplementedError): @@ -221,7 +221,7 @@ class TestSearchSpaceSkillsBackend: ], file_contents={}, ) - backend = SearchSpaceSkillsBackend(kb, kb_root="/skills_admin") + backend = WorkspaceSkillsBackend(kb, kb_root="/skills_admin") infos = asyncio.run(backend.als_info("/")) assert kb.last_ls_path == "/skills_admin" assert infos[0]["path"] == "/x" diff --git a/surfsense_backend/tests/unit/agents/new_chat/tools/test_resume_page_limits.py b/surfsense_backend/tests/unit/agents/new_chat/tools/test_resume_page_limits.py index 61fa87b76..5dbdfd7ae 100644 --- a/surfsense_backend/tests/unit/agents/new_chat/tools/test_resume_page_limits.py +++ b/surfsense_backend/tests/unit/agents/new_chat/tools/test_resume_page_limits.py @@ -148,7 +148,7 @@ async def test_generate_resume_defaults_to_one_page_target(monkeypatch) -> None: monkeypatch.setattr(resume_tool, "_compile_typst", lambda _source: b"pdf") monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: 1) - tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1) + tool = resume_tool.create_generate_resume_tool(workspace_id=1, thread_id=1) result = await _invoke(tool, {"user_info": "Jane Doe experience"}) assert result["status"] == "ready" @@ -178,7 +178,7 @@ async def test_generate_resume_compresses_when_over_limit(monkeypatch) -> None: page_counts = iter([2, 1]) monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts)) - tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1) + tool = resume_tool.create_generate_resume_tool(workspace_id=1, thread_id=1) result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1}) assert result["status"] == "ready" @@ -213,7 +213,7 @@ async def test_generate_resume_returns_ready_when_target_not_met(monkeypatch) -> page_counts = iter([3, 3, 2]) monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts)) - tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1) + tool = resume_tool.create_generate_resume_tool(workspace_id=1, thread_id=1) result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1}) assert result["status"] == "ready" @@ -246,7 +246,7 @@ async def test_generate_resume_fails_when_hard_limit_exceeded(monkeypatch) -> No page_counts = iter([7, 6, 6]) monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts)) - tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1) + tool = resume_tool.create_generate_resume_tool(workspace_id=1, thread_id=1) result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1}) assert result["status"] == "failed" diff --git a/surfsense_backend/tests/unit/automations/actions/builtin/agent_task/test_dependencies.py b/surfsense_backend/tests/unit/automations/actions/builtin/agent_task/test_dependencies.py index f5709e517..bc59ee99e 100644 --- a/surfsense_backend/tests/unit/automations/actions/builtin/agent_task/test_dependencies.py +++ b/surfsense_backend/tests/unit/automations/actions/builtin/agent_task/test_dependencies.py @@ -1,10 +1,10 @@ """Lock the runtime model-policy backstop in ``build_dependencies``. Automations resolve their LLM from the *captured* ``chat_model_id`` snapshot (so -runs are insulated from later chat/search-space model changes), and the model +runs are insulated from later chat/workspace model changes), and the model policy is re-checked at run time so a captured model that is no longer billable fails the run clearly. When no snapshot is present, resolution falls back to the -live search space. +live workspace. """ from __future__ import annotations @@ -25,20 +25,20 @@ pytestmark = pytest.mark.unit class _FakeSession: - """Minimal async session whose ``get`` returns a preset search space.""" + """Minimal async session whose ``get`` returns a preset workspace.""" - def __init__(self, search_space: Any) -> None: - self._search_space = search_space + def __init__(self, workspace: Any) -> None: + self._workspace = workspace async def get(self, _model: Any, _pk: int) -> Any: - return self._search_space + return self._workspace @pytest.fixture def patched_side_effects(monkeypatch: pytest.MonkeyPatch): """Stub the connector setup + checkpointer so only policy/LLM logic runs.""" - async def _fake_setup(_session, *, search_space_id): + async def _fake_setup(_session, *, workspace_id): return (SimpleNamespace(name="connector"), "fc-key") monkeypatch.setattr(deps_mod, "setup_connector_and_firecrawl", _fake_setup) @@ -48,35 +48,35 @@ def patched_side_effects(monkeypatch: pytest.MonkeyPatch): async def test_build_dependencies_resolves_captured_chat_model_id( monkeypatch: pytest.MonkeyPatch, patched_side_effects ) -> None: - """The bundle loads with the *captured* ``chat_model_id``, not the live search space.""" + """The bundle loads with the *captured* ``chat_model_id``, not the live workspace.""" captured: dict[str, Any] = {} - async def _fake_load(_session, *, config_id, search_space_id): + async def _fake_load(_session, *, config_id, workspace_id): captured["config_id"] = config_id - captured["search_space_id"] = search_space_id + captured["workspace_id"] = workspace_id return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load) # Captured path validates the explicit ids; passes for this test. monkeypatch.setattr(deps_mod, "assert_models_billable", lambda **_kw: None) - # A different value on the live search space proves we ignore it when a + # A different value on the live workspace proves we ignore it when a # snapshot is supplied. monkeypatch.setattr( deps_mod, "assert_automation_models_billable", - lambda _ss: pytest.fail("search-space policy should not run on captured path"), + lambda _ss: pytest.fail("workspace policy should not run on captured path"), ) - search_space = SimpleNamespace(chat_model_id=-99) + workspace = SimpleNamespace(chat_model_id=-99) result = await build_dependencies( - session=_FakeSession(search_space), - search_space_id=42, + session=_FakeSession(workspace), + workspace_id=42, chat_model_id=-7, image_gen_model_id=5, vision_model_id=-1, ) - assert captured == {"config_id": -7, "search_space_id": 42} + assert captured == {"config_id": -7, "workspace_id": 42} assert result.llm.name == "llm" assert result.firecrawl_api_key == "fc-key" @@ -84,7 +84,7 @@ async def test_build_dependencies_resolves_captured_chat_model_id( async def test_build_dependencies_validates_captured_ids( monkeypatch: pytest.MonkeyPatch, patched_side_effects ) -> None: - """The captured ids (not the search space) are what gets policy-checked.""" + """The captured ids (not the workspace) are what gets policy-checked.""" seen: dict[str, Any] = {} def _capture(**kwargs): @@ -92,14 +92,14 @@ async def test_build_dependencies_validates_captured_ids( monkeypatch.setattr(deps_mod, "assert_models_billable", _capture) - async def _fake_load(_session, *, config_id, search_space_id): + async def _fake_load(_session, *, config_id, workspace_id): return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load) await build_dependencies( session=_FakeSession(SimpleNamespace(chat_model_id=0)), - search_space_id=42, + workspace_id=42, chat_model_id=-7, image_gen_model_id=5, vision_model_id=-1, @@ -132,20 +132,20 @@ async def test_build_dependencies_raises_on_captured_policy_violation( with pytest.raises(DependencyError): await build_dependencies( session=_FakeSession(SimpleNamespace(chat_model_id=-7)), - search_space_id=42, + workspace_id=42, chat_model_id=-7, image_gen_model_id=-2, vision_model_id=-1, ) -async def test_build_dependencies_falls_back_to_search_space( +async def test_build_dependencies_falls_back_to_workspace( monkeypatch: pytest.MonkeyPatch, patched_side_effects ) -> None: - """With no captured snapshot, resolve + validate the live search space.""" + """With no captured snapshot, resolve + validate the live workspace.""" captured: dict[str, Any] = {} - async def _fake_load(_session, *, config_id, search_space_id): + async def _fake_load(_session, *, config_id, workspace_id): captured["config_id"] = config_id return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None) @@ -157,18 +157,18 @@ async def test_build_dependencies_falls_back_to_search_space( lambda **_kw: pytest.fail("captured policy should not run on fallback path"), ) - search_space = SimpleNamespace(chat_model_id=-7) + workspace = SimpleNamespace(chat_model_id=-7) result = await build_dependencies( - session=_FakeSession(search_space), search_space_id=42 + session=_FakeSession(workspace), workspace_id=42 ) assert captured == {"config_id": -7} assert result.llm.name == "llm" -async def test_build_dependencies_raises_when_search_space_missing( +async def test_build_dependencies_raises_when_workspace_missing( patched_side_effects, ) -> None: - """A missing search space (fallback path) surfaces as a ``DependencyError``.""" + """A missing workspace (fallback path) surfaces as a ``DependencyError``.""" with pytest.raises(DependencyError): - await build_dependencies(session=_FakeSession(None), search_space_id=999) + await build_dependencies(session=_FakeSession(None), workspace_id=999) diff --git a/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py b/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py index 9b203fdba..c2fedbcaf 100644 --- a/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py +++ b/surfsense_backend/tests/unit/automations/runtime/test_execute_step.py @@ -34,7 +34,7 @@ def _action_context() -> ActionContext: session=cast(AsyncSession, None), run_id=1, step_id="s1", - search_space_id=1, + workspace_id=1, creator_user_id=None, ) diff --git a/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py b/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py index c89624fbf..646c0fcc0 100644 --- a/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py +++ b/surfsense_backend/tests/unit/automations/runtime/test_executor_action_ctx.py @@ -1,6 +1,6 @@ """Lock that the executor propagates the captured model snapshot into the ``ActionContext``, so runs resolve their own model (insulated from chat / -search-space changes) and not the live search space. +workspace changes) and not the live workspace. """ from __future__ import annotations @@ -21,7 +21,7 @@ pytestmark = pytest.mark.unit def _run() -> SimpleNamespace: return SimpleNamespace( id=1, - automation=SimpleNamespace(search_space_id=42, created_by_user_id="u-1"), + automation=SimpleNamespace(workspace_id=42, created_by_user_id="u-1"), ) @@ -39,7 +39,7 @@ def test_build_action_ctx_propagates_captured_models() -> None: models, ) - assert ctx.search_space_id == 42 + assert ctx.workspace_id == 42 assert ctx.chat_model_id == -1 assert ctx.image_gen_model_id == 5 assert ctx.vision_model_id == -1 diff --git a/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py b/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py index 6ae3ce794..1471778e1 100644 --- a/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py +++ b/surfsense_backend/tests/unit/automations/schemas/api/test_api_automation.py @@ -17,11 +17,11 @@ _VALID_DEFINITION = { def test_automation_create_accepts_valid_minimal_payload() -> None: - """Happy path: just search_space_id, name, and a valid definition. + """Happy path: just workspace_id, name, and a valid definition. Triggers default to ``[]`` so users can attach them later.""" payload = AutomationCreate.model_validate( { - "search_space_id": 1, + "workspace_id": 1, "name": "Daily digest", "definition": _VALID_DEFINITION, } @@ -39,7 +39,7 @@ def test_automation_create_cascades_validation_into_nested_definition() -> None: with pytest.raises(ValidationError): AutomationCreate.model_validate( { - "search_space_id": 1, + "workspace_id": 1, "name": "Bad", "definition": {"name": "X", "plan": []}, # empty plan } @@ -51,7 +51,7 @@ def test_automation_create_rejects_unknown_top_level_field() -> None: with pytest.raises(ValidationError): AutomationCreate.model_validate( { - "search_space_id": 1, + "workspace_id": 1, "name": "X", "definition": _VALID_DEFINITION, "owner": "tg", # not allowed @@ -64,7 +64,7 @@ def test_automation_create_rejects_empty_name() -> None: with pytest.raises(ValidationError): AutomationCreate.model_validate( { - "search_space_id": 1, + "workspace_id": 1, "name": "", "definition": _VALID_DEFINITION, } diff --git a/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py b/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py index 477d927e2..ce13512a6 100644 --- a/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py +++ b/surfsense_backend/tests/unit/automations/services/test_automation_service_policy.py @@ -1,6 +1,6 @@ """Lock creation-time model-policy enforcement in ``AutomationService``. -Creation (REST + manual builder) rejects search spaces whose models aren't +Creation (REST + manual builder) rejects workspaces whose models aren't billable for automations with HTTP 422, mirroring the runtime backstop. These tests isolate the new ``_assert_models_billable`` / ``model_eligibility`` paths without touching the DB commit. @@ -29,13 +29,13 @@ pytestmark = pytest.mark.unit class _FakeSession: - def __init__(self, search_space: Any) -> None: - self._search_space = search_space + def __init__(self, workspace: Any) -> None: + self._workspace = workspace self.added: list[Any] = [] self.commits = 0 async def get(self, _model: Any, _pk: int) -> Any: - return self._search_space + return self._workspace def add(self, obj: Any) -> None: self.added.append(obj) @@ -44,9 +44,9 @@ class _FakeSession: self.commits += 1 -def _service(search_space: Any) -> AutomationService: +def _service(workspace: Any) -> AutomationService: return AutomationService( - session=_FakeSession(search_space), + session=_FakeSession(workspace), auth=AuthContext.session(SimpleNamespace(id="u-1")), ) @@ -81,7 +81,7 @@ async def test_assert_models_billable_raises_422_on_violation( async def test_assert_models_billable_raises_404_when_missing( monkeypatch: pytest.MonkeyPatch, ) -> None: - """A missing search space is a 404, not a policy error.""" + """A missing workspace is a 404, not a policy error.""" monkeypatch.setattr( automation_mod, "assert_automation_models_billable", lambda _ss: None ) @@ -93,23 +93,23 @@ async def test_assert_models_billable_raises_404_when_missing( assert exc_info.value.status_code == 404 -async def test_assert_models_billable_returns_search_space_when_ok( +async def test_assert_models_billable_returns_workspace_when_ok( monkeypatch: pytest.MonkeyPatch, ) -> None: - """When the policy accepts, the loaded search space is returned for reuse.""" + """When the policy accepts, the loaded workspace is returned for reuse.""" monkeypatch.setattr( automation_mod, "assert_automation_models_billable", lambda _ss: None ) - search_space = SimpleNamespace(chat_model_id=-1) - service = _service(search_space) - assert await service._assert_models_billable(1) is search_space + workspace = SimpleNamespace(chat_model_id=-1) + service = _service(workspace) + assert await service._assert_models_billable(1) is workspace -async def test_create_injects_captured_models_from_search_space( +async def test_create_injects_captured_models_from_workspace( monkeypatch: pytest.MonkeyPatch, ) -> None: - """create() snapshots the search space's model prefs onto the definition.""" + """create() snapshots the workspace's model prefs onto the definition.""" monkeypatch.setattr( automation_mod, "assert_automation_models_billable", lambda _ss: None ) @@ -124,14 +124,14 @@ async def test_create_injects_captured_models_from_search_space( monkeypatch.setattr(AutomationService, "_get_with_triggers_or_raise", _return_added) - search_space = SimpleNamespace( + workspace = SimpleNamespace( chat_model_id=-1, image_gen_model_id=5, vision_model_id=-1, ) - service = _service(search_space) + service = _service(workspace) payload = AutomationCreate( - search_space_id=1, + workspace_id=1, name="A", definition=_definition(), ) @@ -148,7 +148,7 @@ async def test_create_injects_captured_models_from_search_space( async def test_create_treats_unset_prefs_as_auto_zero( monkeypatch: pytest.MonkeyPatch, ) -> None: - """``None`` search-space prefs are captured as ``0`` (Auto) ids.""" + """``None`` workspace prefs are captured as ``0`` (Auto) ids.""" monkeypatch.setattr( automation_mod, "assert_automation_models_billable", lambda _ss: None ) @@ -163,13 +163,13 @@ async def test_create_treats_unset_prefs_as_auto_zero( monkeypatch.setattr(AutomationService, "_get_with_triggers_or_raise", _return_added) - search_space = SimpleNamespace( + workspace = SimpleNamespace( chat_model_id=None, image_gen_model_id=None, vision_model_id=None, ) - service = _service(search_space) - payload = AutomationCreate(search_space_id=1, name="A", definition=_definition()) + service = _service(workspace) + payload = AutomationCreate(workspace_id=1, name="A", definition=_definition()) automation = await service.create(payload) @@ -185,7 +185,7 @@ async def test_create_honors_selected_models_when_provided( ) -> None: """When the payload carries ``definition.models`` they are validated + kept. - The search-space snapshot path is bypassed entirely (no + The workspace snapshot path is bypassed entirely (no ``assert_automation_models_billable`` call). """ @@ -217,7 +217,7 @@ async def test_create_honors_selected_models_when_provided( service = _service(SimpleNamespace(chat_model_id=-99)) payload = AutomationCreate( - search_space_id=1, + workspace_id=1, name="A", definition=_definition( models=AutomationModels( @@ -257,7 +257,7 @@ async def test_create_rejects_unbillable_selected_models( service = _service(SimpleNamespace(chat_model_id=-3)) payload = AutomationCreate( - search_space_id=1, + workspace_id=1, name="A", definition=_definition( models=AutomationModels( @@ -284,7 +284,7 @@ async def test_update_preserves_captured_models( "vision_model_id": -1, } existing = SimpleNamespace( - search_space_id=1, + workspace_id=1, definition={"name": "A", "plan": [], "models": captured}, version=3, ) @@ -315,7 +315,7 @@ async def test_update_honors_changed_models_when_valid( ) -> None: """A definition edit with a *changed* models block validates + keeps it.""" existing = SimpleNamespace( - search_space_id=1, + workspace_id=1, definition={ "name": "A", "plan": [], @@ -376,7 +376,7 @@ async def test_update_rejects_changed_unbillable_models( ) -> None: """A *changed* non-billable models block is rejected with HTTP 422.""" existing = SimpleNamespace( - search_space_id=1, + workspace_id=1, definition={ "name": "A", "plan": [], @@ -438,7 +438,7 @@ async def test_update_keeps_unchanged_models_without_revalidation( "vision_model_id": -1, } existing = SimpleNamespace( - search_space_id=1, + workspace_id=1, definition={"name": "A", "plan": [], "models": captured}, version=3, ) @@ -477,7 +477,7 @@ async def test_model_eligibility_authorizes_and_returns_payload( authorized: dict[str, Any] = {} async def _fake_check_permission(_session, _user, ss_id, permission, _msg): - authorized["search_space_id"] = ss_id + authorized["workspace_id"] = ss_id authorized["permission"] = permission monkeypatch.setattr(automation_mod, "check_permission", _fake_check_permission) @@ -488,8 +488,8 @@ async def test_model_eligibility_authorizes_and_returns_payload( ) service = _service(SimpleNamespace(chat_model_id=-2)) - result = await service.model_eligibility(search_space_id=5) + result = await service.model_eligibility(workspace_id=5) assert result == {"allowed": False, "violations": [{"kind": "image"}]} - assert authorized["search_space_id"] == 5 + assert authorized["workspace_id"] == 5 assert authorized["permission"] == "automations:read" diff --git a/surfsense_backend/tests/unit/automations/services/test_model_policy.py b/surfsense_backend/tests/unit/automations/services/test_model_policy.py index 574f6d9fd..654786d38 100644 --- a/surfsense_backend/tests/unit/automations/services/test_model_policy.py +++ b/surfsense_backend/tests/unit/automations/services/test_model_policy.py @@ -24,8 +24,8 @@ from app.automations.services.model_policy import ( pytestmark = pytest.mark.unit -def _search_space(*, llm: int | None, image: int | None, vision: int | None): - """Minimal stand-in for the ``SearchSpace`` ORM row the policy reads.""" +def _workspace(*, llm: int | None, image: int | None, vision: int | None): + """Minimal stand-in for the ``Workspace`` ORM row the policy reads.""" return SimpleNamespace( chat_model_id=llm, image_gen_model_id=image, @@ -95,15 +95,15 @@ def test_unknown_global_id_is_blocked(kind: str, patched_globals) -> None: def test_eligibility_all_billable(patched_globals) -> None: """Premium LLM + BYOK image + premium vision → allowed, no violations.""" - search_space = _search_space(llm=-1, image=5, vision=-1) - result = get_automation_model_eligibility(search_space) + workspace = _workspace(llm=-1, image=5, vision=-1) + result = get_automation_model_eligibility(workspace) assert result == {"allowed": True, "violations": []} def test_eligibility_reports_each_violation(patched_globals) -> None: """A free LLM, Auto image, and free vision each produce a violation.""" - search_space = _search_space(llm=-2, image=0, vision=-2) - result = get_automation_model_eligibility(search_space) + workspace = _workspace(llm=-2, image=0, vision=-2) + result = get_automation_model_eligibility(workspace) assert result["allowed"] is False kinds = {v["kind"] for v in result["violations"]} @@ -115,9 +115,9 @@ def test_eligibility_reports_each_violation(patched_globals) -> None: def test_assert_raises_with_violations(patched_globals) -> None: """``assert_automation_models_billable`` raises when any slot is blocked.""" - search_space = _search_space(llm=0, image=5, vision=-1) + workspace = _workspace(llm=0, image=5, vision=-1) with pytest.raises(AutomationModelPolicyError) as exc_info: - assert_automation_models_billable(search_space) + assert_automation_models_billable(workspace) assert len(exc_info.value.violations) == 1 assert exc_info.value.violations[0]["kind"] == "chat" @@ -125,8 +125,8 @@ def test_assert_raises_with_violations(patched_globals) -> None: def test_assert_passes_when_all_billable(patched_globals) -> None: """No exception when every slot is premium or BYOK.""" - search_space = _search_space(llm=3, image=-1, vision=4) - assert assert_automation_models_billable(search_space) is None + workspace = _workspace(llm=3, image=-1, vision=4) + assert assert_automation_models_billable(workspace) is None # --- ID-based core (used by the runtime backstop against captured snapshots) --- @@ -170,9 +170,9 @@ def test_assert_models_billable_passes(patched_globals) -> None: ) -def test_search_space_wrapper_delegates_to_core(patched_globals) -> None: - """The search-space wrapper produces the same result as the ID core.""" - search_space = _search_space(llm=-2, image=0, vision=-2) - assert get_automation_model_eligibility(search_space) == get_model_eligibility( +def test_workspace_wrapper_delegates_to_core(patched_globals) -> None: + """The workspace wrapper produces the same result as the ID core.""" + workspace = _workspace(llm=-2, image=0, vision=-2) + assert get_automation_model_eligibility(workspace) == get_model_eligibility( chat_model_id=-2, image_gen_model_id=0, vision_model_id=-2 ) diff --git a/surfsense_backend/tests/unit/automations/templating/test_context.py b/surfsense_backend/tests/unit/automations/templating/test_context.py index 54f372e77..b6bb75e10 100644 --- a/surfsense_backend/tests/unit/automations/templating/test_context.py +++ b/surfsense_backend/tests/unit/automations/templating/test_context.py @@ -25,7 +25,7 @@ def test_build_run_context_exposes_run_inputs_and_steps_namespaces() -> None: automation_id=7, automation_name="Weekly digest", automation_version=3, - search_space_id=1, + workspace_id=1, creator_id=creator, trigger_id=11, trigger_type="schedule", @@ -41,7 +41,7 @@ def test_build_run_context_exposes_run_inputs_and_steps_namespaces() -> None: "automation_id": 7, "automation_name": "Weekly digest", "automation_version": 3, - "search_space_id": 1, + "workspace_id": 1, "creator_id": creator, "trigger_id": 11, "trigger_type": "schedule", diff --git a/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_filter.py b/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_filter.py index 9ddc3503a..7f6b5a3b4 100644 --- a/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_filter.py +++ b/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_filter.py @@ -21,9 +21,9 @@ def test_scalar_value_is_implicit_equality() -> None: def test_multiple_fields_are_anded() -> None: - flt = {"document_type": "FILE", "search_space_id": 7} - assert matches(flt, {"document_type": "FILE", "search_space_id": 7}) is True - assert matches(flt, {"document_type": "FILE", "search_space_id": 9}) is False + flt = {"document_type": "FILE", "workspace_id": 7} + assert matches(flt, {"document_type": "FILE", "workspace_id": 7}) is True + assert matches(flt, {"document_type": "FILE", "workspace_id": 9}) is False def test_gt_operator_compares_greater_than() -> None: @@ -97,12 +97,12 @@ def test_missing_field_never_matches_and_never_raises() -> None: def test_logical_operators_compose_with_fields() -> None: flt = { - "search_space_id": 7, + "workspace_id": 7, "$or": [{"document_type": "FILE"}, {"document_type": "WEBPAGE"}], } - assert matches(flt, {"search_space_id": 7, "document_type": "FILE"}) is True - assert matches(flt, {"search_space_id": 9, "document_type": "FILE"}) is False - assert matches(flt, {"search_space_id": 7, "document_type": "SLACK"}) is False + assert matches(flt, {"workspace_id": 7, "document_type": "FILE"}) is True + assert matches(flt, {"workspace_id": 9, "document_type": "FILE"}) is False + assert matches(flt, {"workspace_id": 7, "document_type": "SLACK"}) is False def test_unknown_field_operator_raises_filter_error() -> None: diff --git a/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_inputs.py b/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_inputs.py index e6191d7a7..a4302e4f0 100644 --- a/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_inputs.py +++ b/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_inputs.py @@ -14,7 +14,7 @@ def test_runtime_inputs_flatten_payload_with_event_metadata() -> None: event = Event( event_type="document.indexed", payload={"document_id": 42, "document_type": "FILE"}, - search_space_id=7, + workspace_id=7, ) inputs = event_runtime_inputs(event) diff --git a/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_match.py b/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_match.py index d83db97a4..e43e53e2e 100644 --- a/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_match.py +++ b/surfsense_backend/tests/unit/automations/triggers/builtin/event/test_match.py @@ -11,7 +11,7 @@ pytestmark = pytest.mark.unit def _event(event_type: str = "document.indexed", **payload) -> Event: - return Event(event_type=event_type, payload=payload, search_space_id=7) + return Event(event_type=event_type, payload=payload, workspace_id=7) def test_matches_when_event_type_equal_and_filter_passes() -> None: diff --git a/surfsense_backend/tests/unit/connector_indexers/test_confluence_parallel.py b/surfsense_backend/tests/unit/connector_indexers/test_confluence_parallel.py index ff85096d4..99af07b72 100644 --- a/surfsense_backend/tests/unit/connector_indexers/test_confluence_parallel.py +++ b/surfsense_backend/tests/unit/connector_indexers/test_confluence_parallel.py @@ -69,7 +69,7 @@ async def test_build_connector_doc_produces_correct_fields(): page, markdown, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -77,7 +77,7 @@ async def test_build_connector_doc_produces_correct_fields(): assert doc.unique_id == "abc-123" assert doc.document_type == DocumentType.CONFLUENCE_CONNECTOR assert doc.source_markdown == markdown - assert doc.search_space_id == _SEARCH_SPACE_ID + assert doc.workspace_id == _SEARCH_SPACE_ID assert doc.connector_id == _CONNECTOR_ID assert doc.created_by_id == _USER_ID assert doc.metadata["page_id"] == "abc-123" @@ -181,7 +181,7 @@ async def _run_index(mocks, **overrides): return await index_confluence_pages( session=mocks["session"], connector_id=overrides.get("connector_id", _CONNECTOR_ID), - search_space_id=overrides.get("search_space_id", _SEARCH_SPACE_ID), + workspace_id=overrides.get("workspace_id", _SEARCH_SPACE_ID), user_id=overrides.get("user_id", _USER_ID), start_date=overrides.get("start_date", "2025-01-01"), end_date=overrides.get("end_date", "2025-12-31"), diff --git a/surfsense_backend/tests/unit/connector_indexers/test_dropbox_parallel.py b/surfsense_backend/tests/unit/connector_indexers/test_dropbox_parallel.py index a74591169..034aeb8b8 100644 --- a/surfsense_backend/tests/unit/connector_indexers/test_dropbox_parallel.py +++ b/surfsense_backend/tests/unit/connector_indexers/test_dropbox_parallel.py @@ -69,7 +69,7 @@ async def test_single_file_returns_one_connector_document( mock_dropbox_client, [_make_file_dict("f1", "test.txt")], connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -94,7 +94,7 @@ async def test_multiple_files_all_produce_documents( mock_dropbox_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -121,7 +121,7 @@ async def test_one_download_exception_does_not_block_others( mock_dropbox_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -147,7 +147,7 @@ async def test_etl_error_counts_as_download_failure( mock_dropbox_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -185,7 +185,7 @@ async def test_concurrency_bounded_by_semaphore( mock_dropbox_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, max_concurrency=2, ) @@ -224,7 +224,7 @@ async def test_heartbeat_fires_during_parallel_downloads( mock_dropbox_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, on_heartbeat=_on_heartbeat, ) @@ -257,7 +257,7 @@ def full_scan_mocks(mock_dropbox_client, monkeypatch): monkeypatch.setattr("app.config.config.ETL_SERVICE", "LLAMACLOUD") - async def _fake_skip(session, file, search_space_id): + async def _fake_skip(session, file, workspace_id): from app.connectors.dropbox.file_types import should_skip_file as _skip item_skip, unsup_ext = _skip(file) @@ -389,7 +389,7 @@ def selected_files_mocks(mock_dropbox_client, monkeypatch): skip_results: dict[str, tuple[bool, str | None]] = {} - async def _fake_skip(session, file, search_space_id): + async def _fake_skip(session, file, workspace_id): return skip_results.get(file["id"], (False, None)) monkeypatch.setattr(_mod, "_should_skip_file", _fake_skip) @@ -430,7 +430,7 @@ async def _run_selected(mocks, file_tuples): mocks["session"], file_tuples, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -547,7 +547,7 @@ async def test_delta_sync_deletions_call_remove_document(monkeypatch): remove_calls: list[str] = [] - async def _fake_remove(session, file_id, search_space_id): + async def _fake_remove(session, file_id, workspace_id): remove_calls.append(file_id) monkeypatch.setattr(_mod, "_remove_document", _fake_remove) @@ -641,7 +641,7 @@ async def test_delta_sync_mix_deletions_and_upserts(monkeypatch): remove_calls: list[str] = [] - async def _fake_remove(session, file_id, search_space_id): + async def _fake_remove(session, file_id, workspace_id): remove_calls.append(file_id) monkeypatch.setattr(_mod, "_remove_document", _fake_remove) diff --git a/surfsense_backend/tests/unit/connector_indexers/test_etl_credits.py b/surfsense_backend/tests/unit/connector_indexers/test_etl_credits.py index aca811ee9..a2f78751c 100644 --- a/surfsense_backend/tests/unit/connector_indexers/test_etl_credits.py +++ b/surfsense_backend/tests/unit/connector_indexers/test_etl_credits.py @@ -201,7 +201,7 @@ async def _run_gdrive_selected(mocks, file_ids): mocks["session"], file_ids, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -542,7 +542,7 @@ async def _run_onedrive_selected(mocks, file_ids): mocks["session"], file_ids, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -641,7 +641,7 @@ async def _run_dropbox_selected(mocks, file_paths): mocks["session"], file_paths, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) diff --git a/surfsense_backend/tests/unit/connector_indexers/test_google_drive_parallel.py b/surfsense_backend/tests/unit/connector_indexers/test_google_drive_parallel.py index 4f61976a6..53ccb690d 100644 --- a/surfsense_backend/tests/unit/connector_indexers/test_google_drive_parallel.py +++ b/surfsense_backend/tests/unit/connector_indexers/test_google_drive_parallel.py @@ -64,7 +64,7 @@ async def test_single_file_returns_one_connector_document( mock_drive_client, [_make_file_dict("f1", "test.txt")], connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -88,7 +88,7 @@ async def test_multiple_files_all_produce_documents( mock_drive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -115,7 +115,7 @@ async def test_one_download_exception_does_not_block_others( mock_drive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -141,7 +141,7 @@ async def test_etl_error_counts_as_download_failure( mock_drive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -180,7 +180,7 @@ async def test_concurrency_bounded_by_semaphore( mock_drive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, max_concurrency=2, ) @@ -219,7 +219,7 @@ async def test_heartbeat_fires_during_parallel_downloads( mock_drive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, on_heartbeat=_on_heartbeat, ) @@ -284,7 +284,7 @@ def full_scan_mocks(mock_drive_client, monkeypatch): skip_results: dict[str, tuple[bool, str | None]] = {} - async def _fake_skip(session, file, search_space_id): + async def _fake_skip(session, file, workspace_id): return skip_results.get(file["id"], (False, None)) monkeypatch.setattr(_mod, "_should_skip_file", _fake_skip) @@ -458,7 +458,7 @@ async def test_delta_sync_removals_serial_rest_parallel(monkeypatch): remove_calls: list[str] = [] - async def _fake_remove(session, file_id, search_space_id): + async def _fake_remove(session, file_id, workspace_id): remove_calls.append(file_id) monkeypatch.setattr(_mod, "_remove_document", _fake_remove) @@ -532,7 +532,7 @@ def selected_files_mocks(mock_drive_client, monkeypatch): skip_results: dict[str, tuple[bool, str | None]] = {} - async def _fake_skip(session, file, search_space_id): + async def _fake_skip(session, file, workspace_id): return skip_results.get(file["id"], (False, None)) monkeypatch.setattr(_mod, "_should_skip_file", _fake_skip) @@ -563,7 +563,7 @@ async def _run_selected(mocks, file_ids): mocks["session"], file_ids, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) diff --git a/surfsense_backend/tests/unit/connector_indexers/test_linear_parallel.py b/surfsense_backend/tests/unit/connector_indexers/test_linear_parallel.py index f057a6352..3639dcebb 100644 --- a/surfsense_backend/tests/unit/connector_indexers/test_linear_parallel.py +++ b/surfsense_backend/tests/unit/connector_indexers/test_linear_parallel.py @@ -68,7 +68,7 @@ async def test_build_connector_doc_produces_correct_fields(): formatted, markdown, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -76,7 +76,7 @@ async def test_build_connector_doc_produces_correct_fields(): assert doc.unique_id == "abc-123" assert doc.document_type == DocumentType.LINEAR_CONNECTOR assert doc.source_markdown == markdown - assert doc.search_space_id == _SEARCH_SPACE_ID + assert doc.workspace_id == _SEARCH_SPACE_ID assert doc.connector_id == _CONNECTOR_ID assert doc.created_by_id == _USER_ID assert doc.metadata["issue_id"] == "abc-123" @@ -196,7 +196,7 @@ async def _run_index(mocks, **overrides): return await index_linear_issues( session=mocks["session"], connector_id=overrides.get("connector_id", _CONNECTOR_ID), - search_space_id=overrides.get("search_space_id", _SEARCH_SPACE_ID), + workspace_id=overrides.get("workspace_id", _SEARCH_SPACE_ID), user_id=overrides.get("user_id", _USER_ID), start_date=overrides.get("start_date", "2025-01-01"), end_date=overrides.get("end_date", "2025-12-31"), diff --git a/surfsense_backend/tests/unit/connector_indexers/test_notion_parallel.py b/surfsense_backend/tests/unit/connector_indexers/test_notion_parallel.py index e40f739d8..35c3f3f69 100644 --- a/surfsense_backend/tests/unit/connector_indexers/test_notion_parallel.py +++ b/surfsense_backend/tests/unit/connector_indexers/test_notion_parallel.py @@ -39,7 +39,7 @@ async def test_build_connector_doc_produces_correct_fields(): page, markdown, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -47,7 +47,7 @@ async def test_build_connector_doc_produces_correct_fields(): assert doc.unique_id == "abc-123" assert doc.document_type == DocumentType.NOTION_CONNECTOR assert doc.source_markdown == markdown - assert doc.search_space_id == _SEARCH_SPACE_ID + assert doc.workspace_id == _SEARCH_SPACE_ID assert doc.connector_id == _CONNECTOR_ID assert doc.created_by_id == _USER_ID assert doc.metadata["page_title"] == "My Notion Page" @@ -159,7 +159,7 @@ async def _run_index(mocks, **overrides): return await index_notion_pages( session=mocks["session"], connector_id=overrides.get("connector_id", _CONNECTOR_ID), - search_space_id=overrides.get("search_space_id", _SEARCH_SPACE_ID), + workspace_id=overrides.get("workspace_id", _SEARCH_SPACE_ID), user_id=overrides.get("user_id", _USER_ID), start_date=overrides.get("start_date", "2025-01-01"), end_date=overrides.get("end_date", "2025-12-31"), diff --git a/surfsense_backend/tests/unit/connector_indexers/test_onedrive_parallel.py b/surfsense_backend/tests/unit/connector_indexers/test_onedrive_parallel.py index 01e81da17..3fca64bbc 100644 --- a/surfsense_backend/tests/unit/connector_indexers/test_onedrive_parallel.py +++ b/surfsense_backend/tests/unit/connector_indexers/test_onedrive_parallel.py @@ -63,7 +63,7 @@ async def test_single_file_returns_one_connector_document( mock_onedrive_client, [_make_file_dict("f1", "test.txt")], connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -88,7 +88,7 @@ async def test_multiple_files_all_produce_documents( mock_onedrive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -115,7 +115,7 @@ async def test_one_download_exception_does_not_block_others( mock_onedrive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -141,7 +141,7 @@ async def test_etl_error_counts_as_download_failure( mock_onedrive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, ) @@ -179,7 +179,7 @@ async def test_concurrency_bounded_by_semaphore( mock_onedrive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, max_concurrency=2, ) @@ -218,7 +218,7 @@ async def test_heartbeat_fires_during_parallel_downloads( mock_onedrive_client, files, connector_id=_CONNECTOR_ID, - search_space_id=_SEARCH_SPACE_ID, + workspace_id=_SEARCH_SPACE_ID, user_id=_USER_ID, on_heartbeat=_on_heartbeat, ) diff --git a/surfsense_backend/tests/unit/event_bus/test_bus.py b/surfsense_backend/tests/unit/event_bus/test_bus.py index 6c970760f..c038092a5 100644 --- a/surfsense_backend/tests/unit/event_bus/test_bus.py +++ b/surfsense_backend/tests/unit/event_bus/test_bus.py @@ -13,7 +13,7 @@ pytestmark = pytest.mark.unit def _event() -> Event: - return Event(event_type="x.happened", payload={"k": "v"}, search_space_id=1) + return Event(event_type="x.happened", payload={"k": "v"}, workspace_id=1) async def _noop(_event: Event) -> None: @@ -152,13 +152,13 @@ async def test_publish_builds_a_stamped_event_and_fans_it_out() -> None: received.append(event) bus.subscribe(handler) - await bus.publish("document.indexed", {"document_id": 42}, search_space_id=7) + await bus.publish("document.indexed", {"document_id": 42}, workspace_id=7) assert len(received) == 1 event = received[0] assert event.event_type == "document.indexed" assert event.payload == {"document_id": 42} - assert event.search_space_id == 7 + assert event.workspace_id == 7 # Engine-stamped identity/time on the way through. assert event.event_id assert event.occurred_at @@ -172,10 +172,10 @@ async def test_publish_defaults_payload_to_empty_dict() -> None: received.append(event) bus.subscribe(handler) - await bus.publish("x.happened", search_space_id=1) + await bus.publish("x.happened", workspace_id=1) assert received[0].payload == {} async def test_publish_with_no_subscribers_is_a_noop() -> None: - await EventBus().publish("x.happened", search_space_id=1) # must not raise + await EventBus().publish("x.happened", workspace_id=1) # must not raise diff --git a/surfsense_backend/tests/unit/event_bus/test_entered_folder_predicate.py b/surfsense_backend/tests/unit/event_bus/test_entered_folder_predicate.py index 1f71e3abb..32f908083 100644 --- a/surfsense_backend/tests/unit/event_bus/test_entered_folder_predicate.py +++ b/surfsense_backend/tests/unit/event_bus/test_entered_folder_predicate.py @@ -14,7 +14,7 @@ pytestmark = pytest.mark.unit def _call(**overrides: Any) -> dict[str, Any] | None: defaults: dict[str, Any] = { "document_id": 1, - "search_space_id": 10, + "workspace_id": 10, "new_folder_id": 7, "previous_folder_id": None, "folder_id_changed": True, @@ -33,7 +33,7 @@ def test_folder_set_ready_fires() -> None: assert result is not None assert result["event_type"] == "document.entered_folder" - assert result["search_space_id"] == 10 + assert result["workspace_id"] == 10 assert result["payload"]["folder_id"] == 7 assert result["payload"]["previous_folder_id"] is None diff --git a/surfsense_backend/tests/unit/event_bus/test_event.py b/surfsense_backend/tests/unit/event_bus/test_event.py index d09cb4364..23a6a2393 100644 --- a/surfsense_backend/tests/unit/event_bus/test_event.py +++ b/surfsense_backend/tests/unit/event_bus/test_event.py @@ -16,17 +16,17 @@ def test_event_carries_caller_supplied_facts() -> None: event = Event( event_type="document.indexed", payload={"document_id": 42, "content_type": "pdf"}, - search_space_id=7, + workspace_id=7, ) assert event.event_type == "document.indexed" assert event.payload == {"document_id": 42, "content_type": "pdf"} - assert event.search_space_id == 7 + assert event.workspace_id == 7 def test_event_stamps_identity_and_time_when_not_supplied() -> None: """Engine stamps id + time so subscribers can dedup/order.""" - event = Event(event_type="x.happened", payload={}, search_space_id=1) + event = Event(event_type="x.happened", payload={}, workspace_id=1) assert event.event_id assert isinstance(event.occurred_at, datetime) @@ -34,8 +34,8 @@ def test_event_stamps_identity_and_time_when_not_supplied() -> None: def test_event_ids_are_unique_per_instance() -> None: """Two events published with identical content are still distinct facts.""" - first = Event(event_type="x.happened", payload={}, search_space_id=1) - second = Event(event_type="x.happened", payload={}, search_space_id=1) + first = Event(event_type="x.happened", payload={}, workspace_id=1) + second = Event(event_type="x.happened", payload={}, workspace_id=1) assert first.event_id != second.event_id @@ -45,7 +45,7 @@ def test_event_survives_json_round_trip() -> None: original = Event( event_type="podcast.generated", payload={"podcast_id": 9, "duration_s": 123.5}, - search_space_id=3, + workspace_id=3, ) restored = Event.model_validate_json(original.model_dump_json()) diff --git a/surfsense_backend/tests/unit/gateway/test_webhook_routes.py b/surfsense_backend/tests/unit/gateway/test_webhook_routes.py index 354c3037d..967d3ba58 100644 --- a/surfsense_backend/tests/unit/gateway/test_webhook_routes.py +++ b/surfsense_backend/tests/unit/gateway/test_webhook_routes.py @@ -330,10 +330,10 @@ async def test_discord_gateway_install_returns_oauth_url(monkeypatch, mocker): "http://localhost:8000/api/v1/gateway/discord/callback", ) monkeypatch.setattr(routes.config, "SECRET_KEY", "test-secret") - monkeypatch.setattr(routes, "check_search_space_access", mocker.AsyncMock()) + monkeypatch.setattr(routes, "check_workspace_access", mocker.AsyncMock()) response = await routes.install_discord_gateway( - search_space_id=123, + workspace_id=123, auth=AuthContext.session( SimpleNamespace(id="00000000-0000-0000-0000-000000000001") ), diff --git a/surfsense_backend/tests/unit/indexing_pipeline/test_connector_document.py b/surfsense_backend/tests/unit/indexing_pipeline/test_connector_document.py index f85c632ef..20607508e 100644 --- a/surfsense_backend/tests/unit/indexing_pipeline/test_connector_document.py +++ b/surfsense_backend/tests/unit/indexing_pipeline/test_connector_document.py @@ -14,7 +14,7 @@ def test_valid_document_created_with_required_fields(): source_markdown="## Task\n\nSome content.", unique_id="task-1", document_type=DocumentType.CLICKUP_CONNECTOR, - search_space_id=1, + workspace_id=1, connector_id=42, created_by_id="00000000-0000-0000-0000-000000000001", ) @@ -32,7 +32,7 @@ def test_omitting_created_by_id_raises(): source_markdown="## Content", unique_id="task-1", document_type=DocumentType.CLICKUP_CONNECTOR, - search_space_id=1, + workspace_id=1, connector_id=42, ) @@ -45,7 +45,7 @@ def test_empty_source_markdown_raises(): source_markdown="", unique_id="task-1", document_type=DocumentType.CLICKUP_CONNECTOR, - search_space_id=1, + workspace_id=1, ) @@ -57,7 +57,7 @@ def test_whitespace_only_source_markdown_raises(): source_markdown=" \n\t ", unique_id="task-1", document_type=DocumentType.CLICKUP_CONNECTOR, - search_space_id=1, + workspace_id=1, ) @@ -69,7 +69,7 @@ def test_empty_title_raises(): source_markdown="## Content", unique_id="task-1", document_type=DocumentType.CLICKUP_CONNECTOR, - search_space_id=1, + workspace_id=1, ) @@ -81,21 +81,21 @@ def test_empty_created_by_id_raises(): source_markdown="## Content", unique_id="task-1", document_type=DocumentType.CLICKUP_CONNECTOR, - search_space_id=1, + workspace_id=1, connector_id=42, created_by_id="", ) -def test_zero_search_space_id_raises(): - """search_space_id of zero raises a validation error.""" +def test_zero_workspace_id_raises(): + """workspace_id of zero raises a validation error.""" with pytest.raises(ValidationError): ConnectorDocument( title="Task", source_markdown="## Content", unique_id="task-1", document_type=DocumentType.CLICKUP_CONNECTOR, - search_space_id=0, + workspace_id=0, connector_id=42, created_by_id="00000000-0000-0000-0000-000000000001", ) @@ -109,5 +109,5 @@ def test_empty_unique_id_raises(): source_markdown="## Content", unique_id="", document_type=DocumentType.CLICKUP_CONNECTOR, - search_space_id=1, + workspace_id=1, ) diff --git a/surfsense_backend/tests/unit/indexing_pipeline/test_create_placeholder_documents.py b/surfsense_backend/tests/unit/indexing_pipeline/test_create_placeholder_documents.py index 096134efd..44603dcf8 100644 --- a/surfsense_backend/tests/unit/indexing_pipeline/test_create_placeholder_documents.py +++ b/surfsense_backend/tests/unit/indexing_pipeline/test_create_placeholder_documents.py @@ -27,7 +27,7 @@ def _make_placeholder(**overrides) -> PlaceholderInfo: "title": "Test Doc", "document_type": DocumentType.GOOGLE_DRIVE_FILE, "unique_id": "file-001", - "search_space_id": 1, + "workspace_id": 1, "connector_id": 42, "created_by_id": "00000000-0000-0000-0000-000000000001", } @@ -37,7 +37,7 @@ def _make_placeholder(**overrides) -> PlaceholderInfo: def _uid_hash(p: PlaceholderInfo) -> str: return compute_identifier_hash( - p.document_type.value, p.unique_id, p.search_space_id + p.document_type.value, p.unique_id, p.workspace_id ) @@ -82,7 +82,7 @@ async def test_creates_documents_with_pending_status_and_commits(): assert doc.document_type == DocumentType.GOOGLE_DRIVE_FILE assert doc.content == "Pending..." assert DocumentStatus.is_state(doc.status, DocumentStatus.PENDING) - assert doc.search_space_id == 1 + assert doc.workspace_id == 1 assert doc.connector_id == 42 session.commit.assert_awaited_once() diff --git a/surfsense_backend/tests/unit/indexing_pipeline/test_document_hashing.py b/surfsense_backend/tests/unit/indexing_pipeline/test_document_hashing.py index d04d8b048..6a7c0ddf0 100644 --- a/surfsense_backend/tests/unit/indexing_pipeline/test_document_hashing.py +++ b/surfsense_backend/tests/unit/indexing_pipeline/test_document_hashing.py @@ -19,12 +19,12 @@ def test_different_unique_id_produces_different_hash(make_connector_document): ) -def test_different_search_space_produces_different_identifier_hash( +def test_different_workspace_produces_different_identifier_hash( make_connector_document, ): - """Same document in different search spaces produces different identifier hashes.""" - doc_a = make_connector_document(search_space_id=1) - doc_b = make_connector_document(search_space_id=2) + """Same document in different workspaces produces different identifier hashes.""" + doc_a = make_connector_document(workspace_id=1) + doc_b = make_connector_document(workspace_id=2) assert compute_unique_identifier_hash(doc_a) != compute_unique_identifier_hash( doc_b ) @@ -42,18 +42,18 @@ def test_different_document_type_produces_different_identifier_hash( def test_same_content_same_space_produces_same_content_hash(make_connector_document): - """Identical content in the same search space always produces the same content hash.""" - doc_a = make_connector_document(source_markdown="Hello world", search_space_id=1) - doc_b = make_connector_document(source_markdown="Hello world", search_space_id=1) + """Identical content in the same workspace always produces the same content hash.""" + doc_a = make_connector_document(source_markdown="Hello world", workspace_id=1) + doc_b = make_connector_document(source_markdown="Hello world", workspace_id=1) assert compute_content_hash(doc_a) == compute_content_hash(doc_b) def test_same_content_different_space_produces_different_content_hash( make_connector_document, ): - """Identical content in different search spaces produces different content hashes.""" - doc_a = make_connector_document(source_markdown="Hello world", search_space_id=1) - doc_b = make_connector_document(source_markdown="Hello world", search_space_id=2) + """Identical content in different workspaces produces different content hashes.""" + doc_a = make_connector_document(source_markdown="Hello world", workspace_id=1) + doc_b = make_connector_document(source_markdown="Hello world", workspace_id=2) assert compute_content_hash(doc_a) != compute_content_hash(doc_b) @@ -69,7 +69,7 @@ def test_compute_identifier_hash_matches_connector_doc_hash(make_connector_docum doc = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-123", - search_space_id=5, + workspace_id=5, ) raw_hash = compute_identifier_hash("GOOGLE_GMAIL_CONNECTOR", "msg-123", 5) assert raw_hash == compute_unique_identifier_hash(doc) diff --git a/surfsense_backend/tests/unit/indexing_pipeline/test_index_batch.py b/surfsense_backend/tests/unit/indexing_pipeline/test_index_batch.py index 963ac6792..c0d18c9c2 100644 --- a/surfsense_backend/tests/unit/indexing_pipeline/test_index_batch.py +++ b/surfsense_backend/tests/unit/indexing_pipeline/test_index_batch.py @@ -24,12 +24,12 @@ async def test_calls_prepare_then_index_per_document(pipeline, make_connector_do doc1 = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-1", - search_space_id=1, + workspace_id=1, ) doc2 = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-2", - search_space_id=1, + workspace_id=1, ) orm1 = MagicMock(spec=Document) @@ -63,7 +63,7 @@ async def test_skips_document_without_matching_connector_doc( doc1 = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-1", - search_space_id=1, + workspace_id=1, ) orphan_orm = MagicMock(spec=Document) diff --git a/surfsense_backend/tests/unit/indexing_pipeline/test_index_batch_parallel.py b/surfsense_backend/tests/unit/indexing_pipeline/test_index_batch_parallel.py index feb7bbc52..13fc5fc70 100644 --- a/surfsense_backend/tests/unit/indexing_pipeline/test_index_batch_parallel.py +++ b/surfsense_backend/tests/unit/indexing_pipeline/test_index_batch_parallel.py @@ -82,7 +82,7 @@ async def test_index_calls_embed_and_chunk_via_to_thread( connector_doc = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-1", - search_space_id=1, + workspace_id=1, ) document = MagicMock(spec=Document) document.id = 1 @@ -140,7 +140,7 @@ async def test_non_code_documents_use_hybrid_chunker( connector_doc = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-1", - search_space_id=1, + workspace_id=1, should_use_code_chunker=False, ) document = MagicMock(spec=Document) @@ -184,7 +184,7 @@ async def test_batch_parallel_indexes_all_documents( make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id=f"msg-{i}", - search_space_id=1, + workspace_id=1, ) for i in range(3) ] @@ -222,7 +222,7 @@ async def test_batch_parallel_one_failure_does_not_affect_others( make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id=f"msg-{i}", - search_space_id=1, + workspace_id=1, ) for i in range(3) ] diff --git a/surfsense_backend/tests/unit/indexing_pipeline/test_migrate_legacy_docs.py b/surfsense_backend/tests/unit/indexing_pipeline/test_migrate_legacy_docs.py index 9334fe678..db3864803 100644 --- a/surfsense_backend/tests/unit/indexing_pipeline/test_migrate_legacy_docs.py +++ b/surfsense_backend/tests/unit/indexing_pipeline/test_migrate_legacy_docs.py @@ -42,7 +42,7 @@ async def test_updates_hash_and_type_for_legacy_document( doc = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-abc", - search_space_id=1, + workspace_id=1, ) legacy_hash = compute_identifier_hash("COMPOSIO_GMAIL_CONNECTOR", "msg-abc", 1) @@ -70,7 +70,7 @@ async def test_noop_when_no_legacy_document_exists( doc = make_connector_document( document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR, unique_id="msg-xyz", - search_space_id=1, + workspace_id=1, ) result_mock = MagicMock() @@ -89,7 +89,7 @@ async def test_skips_non_google_doc_types( doc = make_connector_document( document_type=DocumentType.SLACK_CONNECTOR, unique_id="slack-123", - search_space_id=1, + workspace_id=1, ) await pipeline.migrate_legacy_docs([doc]) @@ -111,7 +111,7 @@ async def test_handles_all_three_google_types( doc = make_connector_document( document_type=native_type, unique_id="id-1", - search_space_id=1, + workspace_id=1, ) existing = MagicMock(spec=Document) diff --git a/surfsense_backend/tests/unit/indexing_pipeline/test_prepare_placeholder_dedup.py b/surfsense_backend/tests/unit/indexing_pipeline/test_prepare_placeholder_dedup.py index 262885880..bee3ab1a4 100644 --- a/surfsense_backend/tests/unit/indexing_pipeline/test_prepare_placeholder_dedup.py +++ b/surfsense_backend/tests/unit/indexing_pipeline/test_prepare_placeholder_dedup.py @@ -32,7 +32,7 @@ def _make_connector_doc(**overrides) -> ConnectorDocument: "source_markdown": "## Some new content", "unique_id": "file-001", "document_type": DocumentType.GOOGLE_DRIVE_FILE, - "search_space_id": 1, + "workspace_id": 1, "connector_id": 42, "created_by_id": "00000000-0000-0000-0000-000000000001", } diff --git a/surfsense_backend/tests/unit/middleware/test_b_filesystem_rm_rmdir_cloud.py b/surfsense_backend/tests/unit/middleware/test_b_filesystem_rm_rmdir_cloud.py index 898ec3765..b6d7bb10e 100644 --- a/surfsense_backend/tests/unit/middleware/test_b_filesystem_rm_rmdir_cloud.py +++ b/surfsense_backend/tests/unit/middleware/test_b_filesystem_rm_rmdir_cloud.py @@ -39,11 +39,11 @@ pytestmark = pytest.mark.unit def _make_middleware(mode: FilesystemMode = FilesystemMode.CLOUD): selection = FilesystemSelection(mode=mode) - resolver = build_backend_resolver(selection, search_space_id=1) + resolver = build_backend_resolver(selection, workspace_id=1) return build_filesystem_mw( backend_resolver=resolver, filesystem_mode=mode, - search_space_id=1, + workspace_id=1, user_id="00000000-0000-0000-0000-000000000001", thread_id=1, ) @@ -290,7 +290,7 @@ class TestKBPostgresBackendDeleteFilter: def _make_backend(self, state: dict[str, Any]) -> KBPostgresBackend: runtime = SimpleNamespace(state=state) - return KBPostgresBackend(search_space_id=1, runtime=runtime) + return KBPostgresBackend(workspace_id=1, runtime=runtime) def test_pending_filesystem_view_returns_deleted_paths(self): backend = self._make_backend( diff --git a/surfsense_backend/tests/unit/middleware/test_filesystem_backends.py b/surfsense_backend/tests/unit/middleware/test_filesystem_backends.py index dafda17d2..54696a09a 100644 --- a/surfsense_backend/tests/unit/middleware/test_filesystem_backends.py +++ b/surfsense_backend/tests/unit/middleware/test_filesystem_backends.py @@ -38,16 +38,16 @@ def test_backend_resolver_returns_multi_root_backend_for_single_root(tmp_path: P def test_backend_resolver_uses_cloud_mode_by_default(): resolver = build_backend_resolver(FilesystemSelection()) backend = resolver(_RuntimeStub()) - # When no search_space_id is provided we fall back to StateBackend so + # When no workspace_id is provided we fall back to StateBackend so # sub-agents / tests without DB access still work. assert backend.__class__.__name__ == "StateBackend" -def test_backend_resolver_uses_kb_postgres_in_cloud_with_search_space(): - resolver = build_backend_resolver(FilesystemSelection(), search_space_id=42) +def test_backend_resolver_uses_kb_postgres_in_cloud_with_workspace(): + resolver = build_backend_resolver(FilesystemSelection(), workspace_id=42) backend = resolver(_RuntimeStub()) assert backend.__class__.__name__ == "KBPostgresBackend" - assert backend.search_space_id == 42 + assert backend.workspace_id == 42 def test_backend_resolver_returns_multi_root_backend_for_multiple_roots(tmp_path: Path): diff --git a/surfsense_backend/tests/unit/middleware/test_kb_persistence_filesystem_parity.py b/surfsense_backend/tests/unit/middleware/test_kb_persistence_filesystem_parity.py index e78db1e76..ddd2b58c7 100644 --- a/surfsense_backend/tests/unit/middleware/test_kb_persistence_filesystem_parity.py +++ b/surfsense_backend/tests/unit/middleware/test_kb_persistence_filesystem_parity.py @@ -92,7 +92,7 @@ async def test_create_document_allows_identical_content_at_different_paths() -> session, # type: ignore[arg-type] virtual_path="/documents/a/notes.md", content=content, - search_space_id=42, + workspace_id=42, created_by_id="user-1", ) assert isinstance(first, Document) @@ -104,7 +104,7 @@ async def test_create_document_allows_identical_content_at_different_paths() -> session, # type: ignore[arg-type] virtual_path="/documents/b/notes-copy.md", content=content, - search_space_id=42, + workspace_id=42, created_by_id="user-1", ) assert isinstance(second, Document) @@ -121,7 +121,7 @@ async def test_create_document_still_rejects_path_collision() -> None: """Path uniqueness remains the hard invariant. If ``unique_identifier_hash`` already points at an existing row in - the same search space, the create call must raise ``ValueError`` + the same workspace, the create call must raise ``ValueError`` with a clear message — matching the behavior the commit loop relies on to upsert via the existing-row code path. """ @@ -137,7 +137,7 @@ async def test_create_document_still_rejects_path_collision() -> None: session, # type: ignore[arg-type] virtual_path="/documents/notes.md", content="anything", - search_space_id=42, + workspace_id=42, created_by_id="user-1", ) @@ -160,7 +160,7 @@ async def test_create_document_does_not_query_for_content_hash_collision( session, # type: ignore[arg-type] virtual_path="/documents/notes.md", content="hello", - search_space_id=42, + workspace_id=42, created_by_id="user-1", ) # Path-collision SELECT only. No content_hash SELECT. diff --git a/surfsense_backend/tests/unit/middleware/test_kb_persistence_revisions.py b/surfsense_backend/tests/unit/middleware/test_kb_persistence_revisions.py index 023213aaa..bf195d6bb 100644 --- a/surfsense_backend/tests/unit/middleware/test_kb_persistence_revisions.py +++ b/surfsense_backend/tests/unit/middleware/test_kb_persistence_revisions.py @@ -217,7 +217,7 @@ async def test_pre_write_snapshot_defers_dispatch_when_list_provided( session, # type: ignore[arg-type] doc=doc, action_id=42, - search_space_id=1, + workspace_id=1, turn_id="t-1", deferred_dispatches=deferred, ) @@ -262,7 +262,7 @@ async def test_pre_write_snapshot_dispatches_inline_when_list_omitted( session, # type: ignore[arg-type] doc=doc, action_id=88, - search_space_id=1, + workspace_id=1, turn_id="t-1", # No deferred_dispatches arg — fall back to inline dispatch. ) @@ -302,7 +302,7 @@ async def test_pre_mkdir_snapshot_defers_dispatch_when_list_provided( session, # type: ignore[arg-type] folder=folder, action_id=55, - search_space_id=1, + workspace_id=1, turn_id="t-1", deferred_dispatches=deferred, ) diff --git a/surfsense_backend/tests/unit/middleware/test_kb_postgres_read.py b/surfsense_backend/tests/unit/middleware/test_kb_postgres_read.py index 8117a6bdb..e153c0f94 100644 --- a/surfsense_backend/tests/unit/middleware/test_kb_postgres_read.py +++ b/surfsense_backend/tests/unit/middleware/test_kb_postgres_read.py @@ -28,7 +28,7 @@ pytestmark = pytest.mark.unit def _backend(state: dict) -> KBPostgresBackend: - return KBPostgresBackend(search_space_id=1, runtime=SimpleNamespace(state=state)) + return KBPostgresBackend(workspace_id=1, runtime=SimpleNamespace(state=state)) def test_render_full_document_uses_full_view_and_registers() -> None: diff --git a/surfsense_backend/tests/unit/middleware/test_knowledge_tree.py b/surfsense_backend/tests/unit/middleware/test_knowledge_tree.py index c14eca080..5c4bb7504 100644 --- a/surfsense_backend/tests/unit/middleware/test_knowledge_tree.py +++ b/surfsense_backend/tests/unit/middleware/test_knowledge_tree.py @@ -101,7 +101,7 @@ class TestFormatTreeRendering: docs = [_Row(**spec) for spec in doc_specs] mw = KnowledgeTreeMiddleware( - search_space_id=1, + workspace_id=1, filesystem_mode=None, # type: ignore[arg-type] ) return mw._format_tree(index, docs) diff --git a/surfsense_backend/tests/unit/notifications/api/test_transform.py b/surfsense_backend/tests/unit/notifications/api/test_transform.py index 96624fe61..621859d6e 100644 --- a/surfsense_backend/tests/unit/notifications/api/test_transform.py +++ b/surfsense_backend/tests/unit/notifications/api/test_transform.py @@ -53,7 +53,7 @@ def _notification(**overrides) -> Notification: defaults = { "id": 1, "user_id": uuid.uuid4(), - "search_space_id": 3, + "workspace_id": 3, "type": "document_processing", "title": "Title", "message": "Message", diff --git a/surfsense_backend/tests/unit/notifications/service/messages/test_document_processing.py b/surfsense_backend/tests/unit/notifications/service/messages/test_document_processing.py index 9fc93d3ed..a27d9cdb7 100644 --- a/surfsense_backend/tests/unit/notifications/service/messages/test_document_processing.py +++ b/surfsense_backend/tests/unit/notifications/service/messages/test_document_processing.py @@ -10,7 +10,7 @@ pytestmark = pytest.mark.unit def test_operation_id_encodes_type_and_space(): - """The operation id embeds the document type and search space id.""" + """The operation id embeds the document type and workspace id.""" op = msg.operation_id("FILE", "report.pdf", 9) assert op.startswith("doc_FILE_9_") diff --git a/surfsense_backend/tests/unit/notifications/service/messages/test_insufficient_credits.py b/surfsense_backend/tests/unit/notifications/service/messages/test_insufficient_credits.py index c5366cce2..a7959af6e 100644 --- a/surfsense_backend/tests/unit/notifications/service/messages/test_insufficient_credits.py +++ b/surfsense_backend/tests/unit/notifications/service/messages/test_insufficient_credits.py @@ -9,8 +9,8 @@ from app.notifications.service.messages import insufficient_credits as msg pytestmark = pytest.mark.unit -def test_operation_id_encodes_search_space(): - """The operation id embeds the search space id.""" +def test_operation_id_encodes_workspace(): + """The operation id embeds the workspace id.""" assert msg.operation_id("doc.pdf", 9).startswith("insufficient_credits_9_") diff --git a/surfsense_backend/tests/unit/observability/test_otel.py b/surfsense_backend/tests/unit/observability/test_otel.py index d3718e7b9..535f191fa 100644 --- a/surfsense_backend/tests/unit/observability/test_otel.py +++ b/surfsense_backend/tests/unit/observability/test_otel.py @@ -170,7 +170,7 @@ class TestMetricHelpers: metrics.record_tool_call_error(tool_name="web_search") metrics.record_kb_search_duration( 4.0, - search_space_id=1, + workspace_id=1, surface="documents", ) metrics.record_compaction_run(reason="auto") @@ -250,7 +250,7 @@ class TestNoopSpansWhenDisabled: helpers = [ otel.tool_call_span("write_file", input_size=42), otel.model_call_span(model_id="openai:gpt-4o", provider="openai"), - otel.kb_search_span(search_space_id=1, query_chars=99), + otel.kb_search_span(workspace_id=1, query_chars=99), otel.kb_persist_span(document_type="NOTE", document_id=7), otel.compaction_span(reason="overflow", messages_in=120), otel.interrupt_span(interrupt_type="permission_ask"), diff --git a/surfsense_backend/tests/unit/observability/test_retriever_otel.py b/surfsense_backend/tests/unit/observability/test_retriever_otel.py index 9712a3150..b98317376 100644 --- a/surfsense_backend/tests/unit/observability/test_retriever_otel.py +++ b/surfsense_backend/tests/unit/observability/test_retriever_otel.py @@ -48,14 +48,14 @@ async def test_retriever_wrapper_records_one_span_and_metric(monkeypatch) -> Non self, query_text: str, top_k: int, - search_space_id: int, + workspace_id: int, ) -> list[str]: - del query_text, top_k, search_space_id + del query_text, top_k, workspace_id return ["doc-1", "doc-2"] result = await Retriever().search("hello", 3, 42) assert result == ["doc-1", "doc-2"] assert len(calls) == 1 - assert calls[0]["search_space_id"] == 42 + assert calls[0]["workspace_id"] == 42 assert calls[0]["surface"] == "documents" diff --git a/surfsense_backend/tests/unit/podcasts/test_api_schemas.py b/surfsense_backend/tests/unit/podcasts/test_api_schemas.py index 41664ac64..59a4b7abf 100644 --- a/surfsense_backend/tests/unit/podcasts/test_api_schemas.py +++ b/surfsense_backend/tests/unit/podcasts/test_api_schemas.py @@ -22,7 +22,7 @@ def _podcast(*, status: PodcastStatus = PodcastStatus.PENDING, **columns) -> Pod """A persisted-looking row: the id and created_at a saved podcast would carry.""" podcast = Podcast( title="Episode", - search_space_id=3, + workspace_id=3, status=status, spec_version=1, **columns, diff --git a/surfsense_backend/tests/unit/routes/test_image_gen_quota.py b/surfsense_backend/tests/unit/routes/test_image_gen_quota.py index 3d94c6c51..b54309dfe 100644 --- a/surfsense_backend/tests/unit/routes/test_image_gen_quota.py +++ b/surfsense_backend/tests/unit/routes/test_image_gen_quota.py @@ -36,11 +36,11 @@ async def test_resolve_billing_for_auto_mode(monkeypatch): _no_auto_candidates, ) - search_space = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None) + workspace = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None) tier, model, reserve = await image_generation_routes._resolve_billing_for_image_gen( session=None, config_id=0, # IMAGE_GEN_AUTO_MODE_ID - search_space=search_space, + workspace=workspace, ) assert tier == "free" assert model == "auto" @@ -95,11 +95,11 @@ async def test_resolve_billing_for_premium_global_config(monkeypatch): raising=False, ) - search_space = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None) + workspace = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None) # Premium with override. tier, model, reserve = await image_generation_routes._resolve_billing_for_image_gen( - session=None, config_id=-1, search_space=search_space + session=None, config_id=-1, workspace=workspace ) assert tier == "premium" assert model == "openai/gpt-image-1" @@ -109,7 +109,7 @@ async def test_resolve_billing_for_premium_global_config(monkeypatch): from app.services.billable_calls import DEFAULT_IMAGE_RESERVE_MICROS tier, model, reserve = await image_generation_routes._resolve_billing_for_image_gen( - session=None, config_id=-2, search_space=search_space + session=None, config_id=-2, workspace=workspace ) assert tier == "free" # Provider-prefixed model string for OpenRouter. @@ -125,9 +125,9 @@ async def test_resolve_billing_for_user_owned_byok_is_free(): from app.routes import image_generation_routes from app.services.billable_calls import DEFAULT_IMAGE_RESERVE_MICROS - search_space = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None) + workspace = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None) tier, model, reserve = await image_generation_routes._resolve_billing_for_image_gen( - session=None, config_id=42, search_space=search_space + session=None, config_id=42, workspace=workspace ) assert tier == "free" assert model == "user_byok" @@ -135,9 +135,9 @@ async def test_resolve_billing_for_user_owned_byok_is_free(): @pytest.mark.asyncio -async def test_resolve_billing_falls_back_to_search_space_default(monkeypatch): +async def test_resolve_billing_falls_back_to_workspace_default(monkeypatch): """When the request omits ``image_gen_model_id``, the helper - must consult the search space's default — so a search space pinned + must consult the workspace's default — so a workspace pinned to a premium global config still gates new requests by quota. """ from app.config import config @@ -172,13 +172,13 @@ async def test_resolve_billing_falls_back_to_search_space_default(monkeypatch): raising=False, ) - search_space = SimpleNamespace(id=1, user_id=None, image_gen_model_id=-7) + workspace = SimpleNamespace(id=1, user_id=None, image_gen_model_id=-7) ( tier, model, _reserve, ) = await image_generation_routes._resolve_billing_for_image_gen( - session=None, config_id=None, search_space=search_space + session=None, config_id=None, workspace=workspace ) assert tier == "premium" assert model == "openai/gpt-image-1" diff --git a/surfsense_backend/tests/unit/routes/test_regenerate_from_message_id.py b/surfsense_backend/tests/unit/routes/test_regenerate_from_message_id.py index 709014d55..2209628de 100644 --- a/surfsense_backend/tests/unit/routes/test_regenerate_from_message_id.py +++ b/surfsense_backend/tests/unit/routes/test_regenerate_from_message_id.py @@ -117,7 +117,7 @@ class TestRegenerateRequestValidation: def test_revert_actions_requires_from_message_id(self) -> None: with pytest.raises(Exception) as exc: RegenerateRequest( - search_space_id=1, + workspace_id=1, user_query="hi", revert_actions=True, ) @@ -126,7 +126,7 @@ class TestRegenerateRequestValidation: def test_from_message_id_without_revert_is_allowed(self) -> None: req = RegenerateRequest( - search_space_id=1, + workspace_id=1, user_query="hi", from_message_id=42, ) @@ -135,7 +135,7 @@ class TestRegenerateRequestValidation: def test_revert_actions_with_from_message_id_passes(self) -> None: req = RegenerateRequest( - search_space_id=1, + workspace_id=1, user_query="hi", from_message_id=42, revert_actions=True, diff --git a/surfsense_backend/tests/unit/services/test_agent_billing_resolver.py b/surfsense_backend/tests/unit/services/test_agent_billing_resolver.py index b43540ba7..da486b77c 100644 --- a/surfsense_backend/tests/unit/services/test_agent_billing_resolver.py +++ b/surfsense_backend/tests/unit/services/test_agent_billing_resolver.py @@ -1,4 +1,4 @@ -"""Unit tests for ``_resolve_agent_billing_for_search_space``.""" +"""Unit tests for ``_resolve_agent_billing_for_workspace``.""" from __future__ import annotations @@ -39,7 +39,7 @@ class _FakePinResolution: from_existing_pin: bool = False -def _make_search_space(*, chat_model_id: int | None, user_id: UUID) -> SimpleNamespace: +def _make_workspace(*, chat_model_id: int | None, user_id: UUID) -> SimpleNamespace: return SimpleNamespace(id=42, chat_model_id=chat_model_id, user_id=user_id) @@ -50,16 +50,16 @@ def _make_byok_model( id=id_, model_id=model_id, catalog={"base_model": base_model} if base_model else {}, - connection=SimpleNamespace(enabled=True, search_space_id=42, user_id=None), + connection=SimpleNamespace(enabled=True, workspace_id=42, user_id=None), ) @pytest.mark.asyncio async def test_auto_mode_with_thread_id_resolves_to_premium_global(monkeypatch): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - session = _FakeSession([_make_search_space(chat_model_id=0, user_id=user_id)]) + session = _FakeSession([_make_workspace(chat_model_id=0, user_id=user_id)]) async def _fake_resolve_pin(*_args, **kwargs): assert kwargs["selected_llm_config_id"] == 0 @@ -84,8 +84,8 @@ async def test_auto_mode_with_thread_id_resolves_to_premium_global(monkeypatch): ) monkeypatch.setattr(llm_module, "get_global_llm_config", _fake_get_global) - owner, tier, base_model = await _resolve_agent_billing_for_search_space( - session, search_space_id=42, thread_id=99 + owner, tier, base_model = await _resolve_agent_billing_for_workspace( + session, workspace_id=42, thread_id=99 ) assert owner == user_id @@ -95,14 +95,14 @@ async def test_auto_mode_with_thread_id_resolves_to_premium_global(monkeypatch): @pytest.mark.asyncio async def test_auto_mode_with_thread_id_resolves_to_byok_is_free(monkeypatch): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - search_space = _make_search_space(chat_model_id=0, user_id=user_id) + workspace = _make_workspace(chat_model_id=0, user_id=user_id) byok_model = _make_byok_model( id_=17, base_model="anthropic/claude-3-haiku", model_id="my-claude" ) - session = _FakeSession([search_space, byok_model]) + session = _FakeSession([workspace, byok_model]) async def _fake_resolve_pin(*_args, **_kwargs): return _FakePinResolution(resolved_llm_config_id=17, resolved_tier="free") @@ -113,8 +113,8 @@ async def test_auto_mode_with_thread_id_resolves_to_byok_is_free(monkeypatch): pin_module, "resolve_or_get_pinned_llm_config_id", _fake_resolve_pin ) - owner, tier, base_model = await _resolve_agent_billing_for_search_space( - session, search_space_id=42, thread_id=99 + owner, tier, base_model = await _resolve_agent_billing_for_workspace( + session, workspace_id=42, thread_id=99 ) assert owner == user_id @@ -124,13 +124,13 @@ async def test_auto_mode_with_thread_id_resolves_to_byok_is_free(monkeypatch): @pytest.mark.asyncio async def test_auto_mode_without_thread_id_falls_back_to_free(): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - session = _FakeSession([_make_search_space(chat_model_id=0, user_id=user_id)]) + session = _FakeSession([_make_workspace(chat_model_id=0, user_id=user_id)]) - owner, tier, base_model = await _resolve_agent_billing_for_search_space( - session, search_space_id=42, thread_id=None + owner, tier, base_model = await _resolve_agent_billing_for_workspace( + session, workspace_id=42, thread_id=None ) assert owner == user_id @@ -140,10 +140,10 @@ async def test_auto_mode_without_thread_id_falls_back_to_free(): @pytest.mark.asyncio async def test_auto_mode_pin_failure_falls_back_to_free(monkeypatch): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - session = _FakeSession([_make_search_space(chat_model_id=0, user_id=user_id)]) + session = _FakeSession([_make_workspace(chat_model_id=0, user_id=user_id)]) async def _fake_resolve_pin(*args, **kwargs): raise ValueError("thread missing") @@ -154,8 +154,8 @@ async def test_auto_mode_pin_failure_falls_back_to_free(monkeypatch): pin_module, "resolve_or_get_pinned_llm_config_id", _fake_resolve_pin ) - owner, tier, base_model = await _resolve_agent_billing_for_search_space( - session, search_space_id=42, thread_id=99 + owner, tier, base_model = await _resolve_agent_billing_for_workspace( + session, workspace_id=42, thread_id=99 ) assert owner == user_id @@ -165,10 +165,10 @@ async def test_auto_mode_pin_failure_falls_back_to_free(monkeypatch): @pytest.mark.asyncio async def test_negative_id_premium_global_returns_premium(monkeypatch): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - session = _FakeSession([_make_search_space(chat_model_id=-1, user_id=user_id)]) + session = _FakeSession([_make_workspace(chat_model_id=-1, user_id=user_id)]) def _fake_get_global(cfg_id): return { @@ -182,8 +182,8 @@ async def test_negative_id_premium_global_returns_premium(monkeypatch): monkeypatch.setattr(llm_module, "get_global_llm_config", _fake_get_global) - owner, tier, base_model = await _resolve_agent_billing_for_search_space( - session, search_space_id=42, thread_id=99 + owner, tier, base_model = await _resolve_agent_billing_for_workspace( + session, workspace_id=42, thread_id=99 ) assert owner == user_id @@ -193,10 +193,10 @@ async def test_negative_id_premium_global_returns_premium(monkeypatch): @pytest.mark.asyncio async def test_negative_id_missing_base_model_falls_back_to_model_name(monkeypatch): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - session = _FakeSession([_make_search_space(chat_model_id=-5, user_id=user_id)]) + session = _FakeSession([_make_workspace(chat_model_id=-5, user_id=user_id)]) def _fake_get_global(cfg_id): return {"id": cfg_id, "model_name": "fallback-model", "billing_tier": "premium"} @@ -205,8 +205,8 @@ async def test_negative_id_missing_base_model_falls_back_to_model_name(monkeypat monkeypatch.setattr(llm_module, "get_global_llm_config", _fake_get_global) - _, tier, base_model = await _resolve_agent_billing_for_search_space( - session, search_space_id=42 + _, tier, base_model = await _resolve_agent_billing_for_workspace( + session, workspace_id=42 ) assert tier == "premium" @@ -215,15 +215,15 @@ async def test_negative_id_missing_base_model_falls_back_to_model_name(monkeypat @pytest.mark.asyncio async def test_positive_id_byok_is_always_free(): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - search_space = _make_search_space(chat_model_id=23, user_id=user_id) + workspace = _make_workspace(chat_model_id=23, user_id=user_id) byok_model = _make_byok_model(id_=23, base_model="anthropic/claude-3.5-sonnet") - session = _FakeSession([search_space, byok_model]) + session = _FakeSession([workspace, byok_model]) - owner, tier, base_model = await _resolve_agent_billing_for_search_space( - session, search_space_id=42 + owner, tier, base_model = await _resolve_agent_billing_for_workspace( + session, workspace_id=42 ) assert owner == user_id @@ -233,13 +233,13 @@ async def test_positive_id_byok_is_always_free(): @pytest.mark.asyncio async def test_positive_id_byok_missing_returns_free_with_empty_base_model(): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - session = _FakeSession([_make_search_space(chat_model_id=99, user_id=user_id)]) + session = _FakeSession([_make_workspace(chat_model_id=99, user_id=user_id)]) - owner, tier, base_model = await _resolve_agent_billing_for_search_space( - session, search_space_id=42 + owner, tier, base_model = await _resolve_agent_billing_for_workspace( + session, workspace_id=42 ) assert owner == user_id @@ -248,21 +248,21 @@ async def test_positive_id_byok_missing_returns_free_with_empty_base_model(): @pytest.mark.asyncio -async def test_search_space_not_found_raises_value_error(): - from app.services.billable_calls import _resolve_agent_billing_for_search_space +async def test_workspace_not_found_raises_value_error(): + from app.services.billable_calls import _resolve_agent_billing_for_workspace - with pytest.raises(ValueError, match="Search space"): - await _resolve_agent_billing_for_search_space( - _FakeSession([None]), search_space_id=999 + with pytest.raises(ValueError, match="Workspace"): + await _resolve_agent_billing_for_workspace( + _FakeSession([None]), workspace_id=999 ) @pytest.mark.asyncio async def test_chat_model_id_none_raises_value_error(): - from app.services.billable_calls import _resolve_agent_billing_for_search_space + from app.services.billable_calls import _resolve_agent_billing_for_workspace user_id = uuid4() - session = _FakeSession([_make_search_space(chat_model_id=None, user_id=user_id)]) + session = _FakeSession([_make_workspace(chat_model_id=None, user_id=user_id)]) with pytest.raises(ValueError, match="chat_model_id"): - await _resolve_agent_billing_for_search_space(session, search_space_id=42) + await _resolve_agent_billing_for_workspace(session, workspace_id=42) diff --git a/surfsense_backend/tests/unit/services/test_ai_sort_task_dedupe.py b/surfsense_backend/tests/unit/services/test_ai_sort_task_dedupe.py index fd9018514..2acfde1ad 100644 --- a/surfsense_backend/tests/unit/services/test_ai_sort_task_dedupe.py +++ b/surfsense_backend/tests/unit/services/test_ai_sort_task_dedupe.py @@ -11,7 +11,7 @@ def test_lock_key_format(): from app.tasks.celery_tasks.document_tasks import _ai_sort_lock_key key = _ai_sort_lock_key(42) - assert key == "ai_sort:search_space:42:lock" + assert key == "ai_sort:workspace:42:lock" def test_lock_prevents_duplicate_run(): @@ -31,11 +31,11 @@ def test_lock_prevents_duplicate_run(): ): import asyncio - from app.tasks.celery_tasks.document_tasks import _ai_sort_search_space_async + from app.tasks.celery_tasks.document_tasks import _ai_sort_workspace_async loop = asyncio.new_event_loop() try: - loop.run_until_complete(_ai_sort_search_space_async(1, "user-123")) + loop.run_until_complete(_ai_sort_workspace_async(1, "user-123")) finally: loop.close() diff --git a/surfsense_backend/tests/unit/services/test_auto_model_pin_service.py b/surfsense_backend/tests/unit/services/test_auto_model_pin_service.py index 598e9b1ab..909fcbd95 100644 --- a/surfsense_backend/tests/unit/services/test_auto_model_pin_service.py +++ b/surfsense_backend/tests/unit/services/test_auto_model_pin_service.py @@ -140,12 +140,12 @@ def _set_global_llm_configs(monkeypatch, config, configs: list[dict]): def _thread( *, - search_space_id: int = 10, + workspace_id: int = 10, pinned_llm_config_id: int | None = None, ): return SimpleNamespace( id=1, - search_space_id=search_space_id, + workspace_id=workspace_id, pinned_llm_config_id=pinned_llm_config_id, ) @@ -186,7 +186,7 @@ async def test_auto_first_turn_pins_one_model(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -234,7 +234,7 @@ async def test_premium_eligible_auto_prefers_premium_over_free(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -321,7 +321,7 @@ async def test_premium_eligible_auto_uses_quality_pool_not_single_preferred_mode result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -361,7 +361,7 @@ async def test_next_turn_reuses_existing_pin(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -400,7 +400,7 @@ async def test_premium_eligible_auto_can_pin_premium(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -445,7 +445,7 @@ async def test_premium_ineligible_auto_pins_free_only(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -490,7 +490,7 @@ async def test_pinned_premium_stays_premium_after_quota_exhaustion(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -535,7 +535,7 @@ async def test_force_repin_free_switches_auto_premium_pin_to_free(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, force_repin_free=True, @@ -567,7 +567,7 @@ async def test_explicit_user_model_change_clears_pin(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=7, ) @@ -605,7 +605,7 @@ async def test_invalid_pinned_config_repairs_with_new_pin(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -664,7 +664,7 @@ async def test_health_gated_config_is_excluded_from_selection(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -716,7 +716,7 @@ async def test_tier_a_locks_first_premium_user_skips_or(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -768,7 +768,7 @@ async def test_tier_a_falls_through_to_or_when_a_pool_empty_for_user(monkeypatch result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -825,7 +825,7 @@ async def test_top_k_picks_only_high_score_models(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=thread_id, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -889,7 +889,7 @@ async def test_pin_reuse_survives_health_gating_for_existing_pin(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -941,7 +941,7 @@ async def test_pin_reuse_regression_existing_healthy_pin(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -1001,7 +1001,7 @@ async def test_runtime_cooled_down_pin_is_not_reused(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -1069,7 +1069,7 @@ async def test_shared_runtime_cooldown_blocks_pin_across_workers(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -1113,7 +1113,7 @@ async def test_clearing_runtime_cooldown_restores_pin_reuse(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, ) @@ -1165,7 +1165,7 @@ async def test_auto_pin_repin_excludes_previous_config_on_runtime_retry(monkeypa result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id="00000000-0000-0000-0000-000000000001", selected_llm_config_id=0, exclude_config_ids={-1}, diff --git a/surfsense_backend/tests/unit/services/test_auto_pin_image_aware.py b/surfsense_backend/tests/unit/services/test_auto_pin_image_aware.py index c1e66feb9..993a0e439 100644 --- a/surfsense_backend/tests/unit/services/test_auto_pin_image_aware.py +++ b/surfsense_backend/tests/unit/services/test_auto_pin_image_aware.py @@ -76,7 +76,7 @@ class _FakeSession: def _thread(*, pinned: int | None = None): - return SimpleNamespace(id=1, search_space_id=10, pinned_llm_config_id=pinned) + return SimpleNamespace(id=1, workspace_id=10, pinned_llm_config_id=pinned) def _set_global_llm_configs(monkeypatch, config, configs: list[dict]): @@ -179,7 +179,7 @@ async def test_image_turn_filters_out_text_only_candidates(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id=None, selected_llm_config_id=0, requires_image_input=True, @@ -207,7 +207,7 @@ async def test_image_turn_force_repins_stale_text_only_pin(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id=None, selected_llm_config_id=0, requires_image_input=True, @@ -239,7 +239,7 @@ async def test_image_turn_reuses_existing_vision_pin(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id=None, selected_llm_config_id=0, requires_image_input=True, @@ -269,7 +269,7 @@ async def test_image_turn_with_no_vision_candidates_raises(monkeypatch): await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id=None, selected_llm_config_id=0, requires_image_input=True, @@ -292,7 +292,7 @@ async def test_non_image_turn_keeps_text_only_in_pool(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id=None, selected_llm_config_id=0, ) @@ -326,7 +326,7 @@ async def test_image_turn_unannotated_cfg_resolves_via_helper(monkeypatch): result = await resolve_or_get_pinned_llm_config_id( session, thread_id=1, - search_space_id=10, + workspace_id=10, user_id=None, selected_llm_config_id=0, requires_image_input=True, diff --git a/surfsense_backend/tests/unit/services/test_billable_call.py b/surfsense_backend/tests/unit/services/test_billable_call.py index 8e2c2f1da..a117037bf 100644 --- a/surfsense_backend/tests/unit/services/test_billable_call.py +++ b/surfsense_backend/tests/unit/services/test_billable_call.py @@ -169,7 +169,7 @@ async def test_free_path_skips_reserve_but_writes_audit_row(monkeypatch): async with billable_call( user_id=user_id, - search_space_id=42, + workspace_id=42, billing_tier="free", base_model="openai/gpt-image-1", usage_type="image_generation", @@ -210,7 +210,7 @@ async def test_premium_reserve_denied_raises_quota_insufficient(monkeypatch): with pytest.raises(QuotaInsufficientError) as exc_info: async with billable_call( user_id=user_id, - search_space_id=42, + workspace_id=42, billing_tier="premium", base_model="openai/gpt-image-1", quota_reserve_micros_override=50_000, @@ -242,7 +242,7 @@ async def test_premium_success_finalizes_with_actual_cost(monkeypatch): async with billable_call( user_id=user_id, - search_space_id=42, + workspace_id=42, billing_tier="premium", base_model="openai/gpt-image-1", quota_reserve_micros_override=50_000, @@ -292,7 +292,7 @@ async def test_premium_failure_releases_reservation(monkeypatch): with pytest.raises(_ProviderError): async with billable_call( user_id=user_id, - search_space_id=42, + workspace_id=42, billing_tier="premium", base_model="openai/gpt-image-1", quota_reserve_micros_override=50_000, @@ -336,7 +336,7 @@ async def test_premium_uses_estimator_when_no_micros_override(monkeypatch): user_id = uuid4() async with billable_call( user_id=user_id, - search_space_id=1, + workspace_id=1, billing_tier="premium", base_model="openai/gpt-4o", quota_reserve_tokens=4000, @@ -367,7 +367,7 @@ async def test_premium_finalize_failure_propagates_and_releases(monkeypatch): with pytest.raises(BillingSettlementError): async with billable_call( user_id=user_id, - search_space_id=42, + workspace_id=42, billing_tier="premium", base_model="openai/gpt-image-1", quota_reserve_micros_override=50_000, @@ -408,7 +408,7 @@ async def test_premium_audit_commit_hang_times_out_after_finalize(monkeypatch): async with billable_call( user_id=user_id, - search_space_id=42, + workspace_id=42, billing_tier="premium", base_model="openai/gpt-image-1", quota_reserve_micros_override=50_000, @@ -450,7 +450,7 @@ async def test_free_audit_failure_is_best_effort(monkeypatch): async with billable_call( user_id=uuid4(), - search_space_id=42, + workspace_id=42, billing_tier="free", base_model="openai/gpt-image-1", usage_type="image_generation", @@ -488,7 +488,7 @@ async def test_free_podcast_path_audits_with_podcast_usage_type(monkeypatch): async with billable_call( user_id=user_id, - search_space_id=42, + workspace_id=42, billing_tier="free", base_model="openrouter/some-free-model", quota_reserve_micros_override=200_000, @@ -514,7 +514,7 @@ async def test_free_podcast_path_audits_with_podcast_usage_type(monkeypatch): row = spies["record"][0] assert row["usage_type"] == "podcast_generation" assert row["thread_id"] is None - assert row["search_space_id"] == 42 + assert row["workspace_id"] == 42 assert row["call_details"] == {"podcast_id": 7, "title": "Test Podcast"} @@ -539,7 +539,7 @@ async def test_premium_video_denial_raises_quota_insufficient(monkeypatch): with pytest.raises(QuotaInsufficientError) as exc_info: async with billable_call( user_id=user_id, - search_space_id=42, + workspace_id=42, billing_tier="premium", base_model="gpt-5.4", quota_reserve_micros_override=1_000_000, diff --git a/surfsense_backend/tests/unit/services/test_image_gen_api_base_defense.py b/surfsense_backend/tests/unit/services/test_image_gen_api_base_defense.py index adcfeed48..d6cc7ba2a 100644 --- a/surfsense_backend/tests/unit/services/test_image_gen_api_base_defense.py +++ b/surfsense_backend/tests/unit/services/test_image_gen_api_base_defense.py @@ -46,8 +46,8 @@ async def test_global_openrouter_image_gen_sets_explicit_api_base(): image_gen.response_format = None image_gen.model = None - search_space = MagicMock() - search_space.image_gen_model_id = global_model["id"] + workspace = MagicMock() + workspace.image_gen_model_id = global_model["id"] session = MagicMock() with ( @@ -68,7 +68,7 @@ async def test_global_openrouter_image_gen_sets_explicit_api_base(): ), ): await image_generation_routes._execute_image_generation( - session=session, image_gen=image_gen, search_space=search_space + session=session, image_gen=image_gen, workspace=workspace ) assert captured.get("api_base") == "https://openrouter.ai/api/v1" @@ -109,16 +109,16 @@ async def test_generate_image_tool_global_sets_explicit_api_base(): response._hidden_params = {"model": "openrouter/openai/gpt-image-1"} return response - search_space = MagicMock() - search_space.id = 1 - search_space.image_gen_model_id = global_model["id"] + workspace = MagicMock() + workspace.id = 1 + workspace.image_gen_model_id = global_model["id"] session_cm = AsyncMock() session = AsyncMock() session_cm.__aenter__.return_value = session scalars = MagicMock() - scalars.first.return_value = search_space + scalars.first.return_value = workspace exec_result = MagicMock() exec_result.scalars.return_value = scalars session.execute.return_value = exec_result @@ -146,7 +146,7 @@ async def test_generate_image_tool_global_sets_explicit_api_base(): ), ): tool = gi_module.create_generate_image_tool( - search_space_id=1, db_session=MagicMock() + workspace_id=1, db_session=MagicMock() ) # The live tool takes an injected ToolRuntime and returns a Command; # drive the raw coroutine with a minimal runtime (the tool only reads diff --git a/surfsense_backend/tests/unit/services/test_quota_checked_vision_llm.py b/surfsense_backend/tests/unit/services/test_quota_checked_vision_llm.py index 0f5dd531f..f50564141 100644 --- a/surfsense_backend/tests/unit/services/test_quota_checked_vision_llm.py +++ b/surfsense_backend/tests/unit/services/test_quota_checked_vision_llm.py @@ -77,7 +77,7 @@ async def test_ainvoke_routes_through_billable_call(monkeypatch): wrapper = QuotaCheckedVisionLLM( inner, user_id=user_id, - search_space_id=99, + workspace_id=99, billing_tier="premium", base_model="openai/gpt-4o", quota_reserve_tokens=4000, @@ -89,7 +89,7 @@ async def test_ainvoke_routes_through_billable_call(monkeypatch): assert len(captured_kwargs) == 1 bc_kwargs = captured_kwargs[0] assert bc_kwargs["user_id"] == user_id - assert bc_kwargs["search_space_id"] == 99 + assert bc_kwargs["workspace_id"] == 99 assert bc_kwargs["billing_tier"] == "premium" assert bc_kwargs["base_model"] == "openai/gpt-4o" assert bc_kwargs["quota_reserve_tokens"] == 4000 @@ -120,7 +120,7 @@ async def test_ainvoke_propagates_quota_insufficient_error(monkeypatch): wrapper = QuotaCheckedVisionLLM( inner, user_id=uuid4(), - search_space_id=1, + workspace_id=1, billing_tier="premium", base_model="openai/gpt-4o", quota_reserve_tokens=4000, @@ -146,7 +146,7 @@ async def test_proxies_non_overridden_attributes_to_inner(): wrapper = QuotaCheckedVisionLLM( inner, user_id=uuid4(), - search_space_id=1, + workspace_id=1, billing_tier="premium", base_model="openai/gpt-4o", quota_reserve_tokens=4000, diff --git a/surfsense_backend/tests/unit/services/test_revert_filesystem_tools.py b/surfsense_backend/tests/unit/services/test_revert_filesystem_tools.py index 95314741a..d614f5e61 100644 --- a/surfsense_backend/tests/unit/services/test_revert_filesystem_tools.py +++ b/surfsense_backend/tests/unit/services/test_revert_filesystem_tools.py @@ -93,7 +93,7 @@ def _action(*, tool_name: str, action_id: int = 7): id=action_id, tool_name=tool_name, thread_id=1, - search_space_id=2, + workspace_id=2, user_id="user-1", reverse_descriptor=None, ) @@ -111,7 +111,7 @@ def _doc_revision( revision = MagicMock() revision.id = 100 revision.document_id = document_id - revision.search_space_id = 2 + revision.workspace_id = 2 revision.content_before = content_before revision.title_before = title_before revision.folder_id_before = folder_id_before @@ -130,7 +130,7 @@ def _folder_revision( revision = MagicMock() revision.id = 200 revision.folder_id = folder_id - revision.search_space_id = 2 + revision.workspace_id = 2 revision.name_before = name_before revision.parent_id_before = parent_id_before revision.position_before = position_before diff --git a/surfsense_backend/tests/unit/tasks/chat/streaming/test_llm_bundle.py b/surfsense_backend/tests/unit/tasks/chat/streaming/test_llm_bundle.py index 7bb169496..bc3f0f7e5 100644 --- a/surfsense_backend/tests/unit/tasks/chat/streaming/test_llm_bundle.py +++ b/surfsense_backend/tests/unit/tasks/chat/streaming/test_llm_bundle.py @@ -32,12 +32,12 @@ def _patch_common_bundle_dependencies(monkeypatch: pytest.MonkeyPatch): _CapturedChatLiteLLM.calls = [] - async def _fake_search_space( - _session: Any, _search_space_id: int + async def _fake_workspace( + _session: Any, _workspace_id: int ) -> SimpleNamespace: return SimpleNamespace(id=42, user_id="user-1") - monkeypatch.setattr(llm_bundle, "_load_search_space", _fake_search_space) + monkeypatch.setattr(llm_bundle, "_load_workspace", _fake_workspace) monkeypatch.setattr(llm_bundle, "SanitizedChatLiteLLM", _CapturedChatLiteLLM) monkeypatch.setattr(llm_bundle, "register_model_usage_metadata", lambda **_kw: None) monkeypatch.setattr( @@ -65,9 +65,9 @@ async def test_load_llm_bundle_enables_streaming_for_db_models( connection=connection, ) - async def _fake_db_model(_session: Any, *, model_id: int, search_space: Any) -> Any: + async def _fake_db_model(_session: Any, *, model_id: int, workspace: Any) -> Any: assert model_id == 7 - assert search_space.id == 42 + assert workspace.id == 42 return model monkeypatch.setattr(llm_bundle, "_load_db_model", _fake_db_model) @@ -83,7 +83,7 @@ async def test_load_llm_bundle_enables_streaming_for_db_models( llm, agent_config, error = await llm_bundle.load_llm_bundle( object(), config_id=7, - search_space_id=42, + workspace_id=42, ) assert error is None @@ -140,7 +140,7 @@ async def test_load_llm_bundle_enables_streaming_for_global_models( llm, agent_config, error = await llm_bundle.load_llm_bundle( object(), config_id=-11, - search_space_id=42, + workspace_id=42, ) assert error is None diff --git a/surfsense_backend/tests/unit/tasks/test_video_presentation_billing.py b/surfsense_backend/tests/unit/tasks/test_video_presentation_billing.py index 7183024ed..ad84d0e0f 100644 --- a/surfsense_backend/tests/unit/tasks/test_video_presentation_billing.py +++ b/surfsense_backend/tests/unit/tasks/test_video_presentation_billing.py @@ -145,14 +145,14 @@ async def test_billable_call_invoked_with_correct_kwargs_for_free_config(monkeyp user_id = uuid4() - async def _fake_resolver(sess, search_space_id, *, thread_id=None): - assert search_space_id == 777 + async def _fake_resolver(sess, workspace_id, *, thread_id=None): + assert workspace_id == 777 assert thread_id == 99 return user_id, "free", "openrouter/some-free-model" monkeypatch.setattr( video_presentation_tasks, - "_resolve_agent_billing_for_search_space", + "_resolve_agent_billing_for_workspace", _fake_resolver, ) monkeypatch.setattr(video_presentation_tasks, "billable_call", _ok_billable_call) @@ -169,7 +169,7 @@ async def test_billable_call_invoked_with_correct_kwargs_for_free_config(monkeyp result = await video_presentation_tasks._generate_video_presentation( video_presentation_id=11, source_content="content", - search_space_id=777, + workspace_id=777, user_prompt=None, ) @@ -180,7 +180,7 @@ async def test_billable_call_invoked_with_correct_kwargs_for_free_config(monkeyp assert len(_CALL_LOG) == 1 call = _CALL_LOG[0] assert call["user_id"] == user_id - assert call["search_space_id"] == 777 + assert call["workspace_id"] == 777 assert call["billing_tier"] == "free" assert call["base_model"] == "openrouter/some-free-model" assert call["usage_type"] == "video_presentation_generation" @@ -213,12 +213,12 @@ async def test_billable_call_invoked_with_premium_tier(monkeypatch): user_id = uuid4() - async def _fake_resolver(sess, search_space_id, *, thread_id=None): + async def _fake_resolver(sess, workspace_id, *, thread_id=None): return user_id, "premium", "gpt-5.4" monkeypatch.setattr( video_presentation_tasks, - "_resolve_agent_billing_for_search_space", + "_resolve_agent_billing_for_workspace", _fake_resolver, ) monkeypatch.setattr(video_presentation_tasks, "billable_call", _ok_billable_call) @@ -235,7 +235,7 @@ async def test_billable_call_invoked_with_premium_tier(monkeypatch): await video_presentation_tasks._generate_video_presentation( video_presentation_id=11, source_content="content", - search_space_id=777, + workspace_id=777, user_prompt=None, ) @@ -256,12 +256,12 @@ async def test_quota_insufficient_marks_video_failed_and_skips_graph(monkeypatch lambda: _FakeSessionMaker(session), ) - async def _fake_resolver(sess, search_space_id, *, thread_id=None): + async def _fake_resolver(sess, workspace_id, *, thread_id=None): return uuid4(), "premium", "gpt-5.4" monkeypatch.setattr( video_presentation_tasks, - "_resolve_agent_billing_for_search_space", + "_resolve_agent_billing_for_workspace", _fake_resolver, ) monkeypatch.setattr( @@ -283,7 +283,7 @@ async def test_quota_insufficient_marks_video_failed_and_skips_graph(monkeypatch result = await video_presentation_tasks._generate_video_presentation( video_presentation_id=12, source_content="content", - search_space_id=777, + workspace_id=777, user_prompt=None, ) @@ -309,12 +309,12 @@ async def test_billing_settlement_failure_marks_video_failed(monkeypatch): lambda: _FakeSessionMaker(session), ) - async def _fake_resolver(sess, search_space_id, *, thread_id=None): + async def _fake_resolver(sess, workspace_id, *, thread_id=None): return uuid4(), "premium", "gpt-5.4" monkeypatch.setattr( video_presentation_tasks, - "_resolve_agent_billing_for_search_space", + "_resolve_agent_billing_for_workspace", _fake_resolver, ) monkeypatch.setattr( @@ -335,7 +335,7 @@ async def test_billing_settlement_failure_marks_video_failed(monkeypatch): result = await video_presentation_tasks._generate_video_presentation( video_presentation_id=14, source_content="content", - search_space_id=777, + workspace_id=777, user_prompt=None, ) @@ -360,12 +360,12 @@ async def test_resolver_failure_marks_video_failed(monkeypatch): lambda: _FakeSessionMaker(session), ) - async def _failing_resolver(sess, search_space_id, *, thread_id=None): - raise ValueError("Search space 777 not found") + async def _failing_resolver(sess, workspace_id, *, thread_id=None): + raise ValueError("Workspace 777 not found") monkeypatch.setattr( video_presentation_tasks, - "_resolve_agent_billing_for_search_space", + "_resolve_agent_billing_for_workspace", _failing_resolver, ) @@ -384,7 +384,7 @@ async def test_resolver_failure_marks_video_failed(monkeypatch): result = await video_presentation_tasks._generate_video_presentation( video_presentation_id=13, source_content="content", - search_space_id=777, + workspace_id=777, user_prompt=None, ) diff --git a/surfsense_backend/tests/unit/test_pat_fail_closed_static.py b/surfsense_backend/tests/unit/test_pat_fail_closed_static.py index 88b8f9151..0b4f3aa9c 100644 --- a/surfsense_backend/tests/unit/test_pat_fail_closed_static.py +++ b/surfsense_backend/tests/unit/test_pat_fail_closed_static.py @@ -16,7 +16,7 @@ ALLOW_ANY_EXPECTED = { "routes/obsidian_plugin_routes.py": ( "_auth: AuthContext = Depends(allow_any_principal)" ), - "routes/search_spaces_routes.py": ( + "routes/workspaces_routes.py": ( "auth: AuthContext = Depends(allow_any_principal)" ), } @@ -60,12 +60,12 @@ def test_allow_any_principal_is_only_used_by_bootstrap_allowlist() -> None: assert expected_snippet in text -def test_connector_listers_route_pat_through_search_space_gate() -> None: +def test_connector_listers_route_pat_through_workspace_gate() -> None: for rel_path in CONNECTOR_LISTERS: text = (APP_ROOT / rel_path).read_text() assert "auth: AuthContext = Depends(get_auth_context)" in text, rel_path assert ( - "await check_search_space_access(session, auth, connector.search_space_id)" + "await check_workspace_access(session, auth, connector.workspace_id)" in text ), rel_path diff --git a/surfsense_backend/tests/unit/utils/test_validators.py b/surfsense_backend/tests/unit/utils/test_validators.py index e0e7c6da8..7757bb157 100644 --- a/surfsense_backend/tests/unit/utils/test_validators.py +++ b/surfsense_backend/tests/unit/utils/test_validators.py @@ -11,7 +11,7 @@ from app.utils.validators import ( validate_messages, validate_research_mode, validate_search_mode, - validate_search_space_id, + validate_workspace_id, validate_top_k, validate_url, validate_uuid, @@ -34,8 +34,8 @@ pytestmark = pytest.mark.unit (" 42 ", 42), ], ) -def test_validate_search_space_id_valid(valid_input, expected): - assert validate_search_space_id(valid_input) == expected +def test_validate_workspace_id_valid(valid_input, expected): + assert validate_workspace_id(valid_input) == expected @pytest.mark.parametrize( @@ -54,9 +54,9 @@ def test_validate_search_space_id_valid(valid_input, expected): "-5", ], ) -def test_validate_search_space_id_invalid(invalid_input): +def test_validate_workspace_id_invalid(invalid_input): with pytest.raises(HTTPException) as excinfo: - validate_search_space_id(invalid_input) + validate_workspace_id(invalid_input) assert excinfo.value.status_code == 400 diff --git a/surfsense_backend/tests/utils/helpers.py b/surfsense_backend/tests/utils/helpers.py index fc77c6e6b..601761ed0 100644 --- a/surfsense_backend/tests/utils/helpers.py +++ b/surfsense_backend/tests/utils/helpers.py @@ -40,17 +40,17 @@ async def get_auth_token(client: httpx.AsyncClient) -> str: return response.json()["access_token"] -async def get_search_space_id(client: httpx.AsyncClient, token: str) -> int: - """Fetch the first search space owned by the test user.""" +async def get_workspace_id(client: httpx.AsyncClient, token: str) -> int: + """Fetch the first workspace owned by the test user.""" resp = await client.get( - "/api/v1/searchspaces", + "/api/v1/workspaces", headers=auth_headers(token), ) assert resp.status_code == 200, ( - f"Failed to list search spaces ({resp.status_code}): {resp.text}" + f"Failed to list workspaces ({resp.status_code}): {resp.text}" ) spaces = resp.json() - assert len(spaces) > 0, "No search spaces found for test user" + assert len(spaces) > 0, "No workspaces found for test user" return spaces[0]["id"] @@ -64,7 +64,7 @@ async def upload_file( headers: dict[str, str], fixture_name: str, *, - search_space_id: int, + workspace_id: int, filename_override: str | None = None, ) -> httpx.Response: """Upload a single fixture file and return the raw response.""" @@ -75,7 +75,7 @@ async def upload_file( "/api/v1/documents/fileupload", headers=headers, files={"files": (upload_name, f)}, - data={"search_space_id": str(search_space_id)}, + data={"workspace_id": str(workspace_id)}, ) @@ -84,7 +84,7 @@ async def upload_multiple_files( headers: dict[str, str], fixture_names: list[str], *, - search_space_id: int, + workspace_id: int, ) -> httpx.Response: """Upload multiple fixture files in a single request.""" files = [] @@ -99,7 +99,7 @@ async def upload_multiple_files( "/api/v1/documents/fileupload", headers=headers, files=files, - data={"search_space_id": str(search_space_id)}, + data={"workspace_id": str(workspace_id)}, ) finally: for fh in open_handles: @@ -111,7 +111,7 @@ async def poll_document_status( headers: dict[str, str], document_ids: list[int], *, - search_space_id: int, + workspace_id: int, timeout: float = 180.0, interval: float = 3.0, ) -> dict[int, dict]: @@ -135,7 +135,7 @@ async def poll_document_status( "/api/v1/documents/status", headers=headers, params={ - "search_space_id": search_space_id, + "workspace_id": workspace_id, "document_ids": ids_param, }, ) @@ -199,15 +199,15 @@ async def get_notifications( headers: dict[str, str], *, type_filter: str | None = None, - search_space_id: int | None = None, + workspace_id: int | None = None, limit: int = 50, ) -> list[dict]: """Fetch notifications for the authenticated user, optionally filtered by type.""" params: dict[str, str | int] = {"limit": limit} if type_filter: params["type"] = type_filter - if search_space_id is not None: - params["search_space_id"] = search_space_id + if workspace_id is not None: + params["workspace_id"] = workspace_id resp = await client.get( "/api/v1/notifications",