Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions surfsense_backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,3 @@ DAYTONA_API_URL=https://app.daytona.io/api
DAYTONA_TARGET=us
# Directory for locally-persisted sandbox files (after sandbox deletion)
SANDBOX_FILES_DIR=sandbox_files


# ============================================================
# Testing (optional — all have sensible defaults)
# ============================================================
# TEST_BACKEND_URL=http://localhost:8000
# TEST_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense
# TEST_USER_EMAIL=testuser@surfsense.com
# TEST_USER_PASSWORD=testpassword123
10 changes: 4 additions & 6 deletions surfsense_backend/app/routes/documents_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
DocumentWithChunksRead,
PaginatedResponse,
)
from app.services.task_dispatcher import TaskDispatcher, get_task_dispatcher
from app.users import current_active_user
from app.utils.rbac import check_permission

Expand Down Expand Up @@ -120,6 +121,7 @@ async def create_documents_file_upload(
search_space_id: int = Form(...),
session: AsyncSession = Depends(get_async_session),
user: User = Depends(current_active_user),
dispatcher: TaskDispatcher = Depends(get_task_dispatcher),
):
"""
Upload files as documents with real-time status tracking.
Expand Down Expand Up @@ -290,14 +292,10 @@ async def create_documents_file_upload(
for doc in created_documents:
await session.refresh(doc)

# ===== PHASE 2: Dispatch Celery tasks for each file =====
# ===== PHASE 2: Dispatch tasks for each file =====
# Each task will update document status: pending → processing → ready/failed
from app.tasks.celery_tasks.document_tasks import (
process_file_upload_with_document_task,
)

for document, temp_path, filename in files_to_process:
process_file_upload_with_document_task.delay(
await dispatcher.dispatch_file_processing(
document_id=document.id,
temp_path=temp_path,
filename=filename,
Expand Down
50 changes: 50 additions & 0 deletions surfsense_backend/app/services/task_dispatcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Task dispatcher abstraction for background document processing.

Decouples the upload endpoint from Celery so tests can swap in a
synchronous (inline) implementation that needs only PostgreSQL.
"""

from __future__ import annotations

from typing import Protocol


class TaskDispatcher(Protocol):
async def dispatch_file_processing(
self,
*,
document_id: int,
temp_path: str,
filename: str,
search_space_id: int,
user_id: str,
) -> None: ...


class CeleryTaskDispatcher:
"""Production dispatcher — fires Celery tasks via Redis broker."""

async def dispatch_file_processing(
self,
*,
document_id: int,
temp_path: str,
filename: str,
search_space_id: int,
user_id: str,
) -> None:
from app.tasks.celery_tasks.document_tasks import (
process_file_upload_with_document_task,
)

process_file_upload_with_document_task.delay(
document_id=document_id,
temp_path=temp_path,
filename=filename,
search_space_id=search_space_id,
user_id=user_id,
)


async def get_task_dispatcher() -> TaskDispatcher:
return CeleryTaskDispatcher()
3 changes: 1 addition & 2 deletions surfsense_backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ python_functions = ["test_*"]
addopts = "-v --tb=short -x --strict-markers -ra --durations=5"
markers = [
"unit: pure logic tests, no DB or external services",
"integration: tests that require a real PostgreSQL database",
"e2e: tests requiring a running backend and real HTTP calls"
"integration: tests that require a real PostgreSQL database"
]
filterwarnings = [
"ignore::UserWarning:chonkie",
Expand Down
20 changes: 9 additions & 11 deletions surfsense_backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,21 @@
from __future__ import annotations

import os
from pathlib import Path

_DEFAULT_TEST_DB = (
"postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense_test"
)
TEST_DATABASE_URL = os.environ.get("TEST_DATABASE_URL", _DEFAULT_TEST_DB)

# Force the app to use the test database regardless of any pre-existing
# DATABASE_URL in the environment (e.g. from .env or shell profile).
os.environ["DATABASE_URL"] = TEST_DATABASE_URL

import pytest
from dotenv import load_dotenv

from app.db import DocumentType
from app.indexing_pipeline.connector_document import ConnectorDocument

load_dotenv(Path(__file__).resolve().parent.parent / ".env")

# Shared DB URL referenced by both e2e and integration helper functions.
DATABASE_URL = os.environ.get(
"TEST_DATABASE_URL",
os.environ.get("DATABASE_URL", ""),
).replace("postgresql+asyncpg://", "postgresql://")


# ---------------------------------------------------------------------------
# Unit test fixtures
# ---------------------------------------------------------------------------
Expand Down
198 changes: 0 additions & 198 deletions surfsense_backend/tests/e2e/conftest.py

This file was deleted.

10 changes: 3 additions & 7 deletions surfsense_backend/tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import uuid
from unittest.mock import AsyncMock, MagicMock

Expand All @@ -8,6 +7,7 @@
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.pool import NullPool

from app.config import config as app_config
from app.db import (
Base,
DocumentType,
Expand All @@ -17,13 +17,9 @@
User,
)
from app.indexing_pipeline.connector_document import ConnectorDocument
from tests.conftest import TEST_DATABASE_URL

_EMBEDDING_DIM = 1024 # must match the Vector() dimension used in DB column creation

_DEFAULT_TEST_DB = (
"postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense_test"
)
TEST_DATABASE_URL = os.environ.get("TEST_DATABASE_URL", _DEFAULT_TEST_DB)
_EMBEDDING_DIM = app_config.embedding_model_instance.dimension


@pytest_asyncio.fixture(scope="session")
Expand Down
Loading
Loading