test: bootstrap pytest environment for backend

This commit is contained in:
CREDO23 2026-02-24 18:19:56 +02:00
parent 47e6a7f29e
commit 10a6ba6924
10 changed files with 126 additions and 0 deletions

View file

@ -70,6 +70,21 @@ dependencies = [
[dependency-groups]
dev = [
"ruff>=0.12.5",
"pytest>=8.0",
"pytest-asyncio>=0.25",
"pytest-mock>=3.14",
]
[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
testpaths = ["tests"]
markers = [
"unit: pure logic tests, no DB or external services",
"integration: tests that require a real PostgreSQL database",
]
filterwarnings = [
"ignore::UserWarning:chonkie",
]
[tool.ruff]

View file

View file

@ -0,0 +1,16 @@
import pytest
@pytest.fixture
def sample_user_id() -> str:
return "00000000-0000-0000-0000-000000000001"
@pytest.fixture
def sample_search_space_id() -> int:
return 1
@pytest.fixture
def sample_connector_id() -> int:
return 42

View file

@ -0,0 +1,46 @@
import os
import pytest_asyncio
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import NullPool
from app.db import Base
_DEFAULT_TEST_DB = "postgresql+asyncpg://postgres:postgres@localhost:5432/surfsense_test"
TEST_DATABASE_URL = os.environ.get("TEST_DATABASE_URL", _DEFAULT_TEST_DB)
@pytest_asyncio.fixture(scope="session")
async def async_engine():
engine = create_async_engine(TEST_DATABASE_URL, poolclass=NullPool, echo=False)
async with engine.begin() as conn:
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
await conn.run_sync(Base.metadata.create_all)
yield engine
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await engine.dispose()
@pytest_asyncio.fixture
async def db_session(async_engine) -> AsyncSession:
# Bind the session to a connection that holds an outer transaction.
# join_transaction_mode="create_savepoint" makes session.commit() release
# a SAVEPOINT instead of committing the outer transaction, so the final
# transaction.rollback() undoes everything — including commits made by the
# service under test — leaving the DB clean for the next test.
async with async_engine.connect() as conn:
transaction = await conn.begin()
async with AsyncSession(
bind=conn,
expire_on_commit=False,
join_transaction_mode="create_savepoint",
) as session:
yield session
await transaction.rollback()

View file

View file

@ -0,0 +1,49 @@
from unittest.mock import AsyncMock, MagicMock
import pytest
_EMBEDDING_DIM = 4 # keep vectors tiny in tests; real model uses 768+
@pytest.fixture
def mock_session() -> AsyncMock:
session = AsyncMock()
session.add = MagicMock() # synchronous in real SQLAlchemy
session.execute = AsyncMock()
session.scalar = AsyncMock()
session.scalars = AsyncMock()
session.flush = AsyncMock()
session.commit = AsyncMock()
session.rollback = AsyncMock()
session.refresh = AsyncMock()
return session
@pytest.fixture
def mock_llm() -> AsyncMock:
llm = AsyncMock()
llm.ainvoke = AsyncMock(return_value=MagicMock(content="Mocked summary."))
return llm
@pytest.fixture
def mock_embedding_model() -> MagicMock:
model = MagicMock()
model.embed = MagicMock(
side_effect=lambda texts: [[0.1] * _EMBEDDING_DIM for _ in texts]
)
return model
@pytest.fixture
def mock_chunker() -> MagicMock:
chunker = MagicMock()
chunker.chunk = MagicMock(return_value=["chunk one", "chunk two"])
return chunker
@pytest.fixture
def mock_code_chunker() -> MagicMock:
chunker = MagicMock()
chunker.chunk = MagicMock(return_value=["chunk one", "chunk two"])
return chunker