191 lines
5.4 KiB
Python
191 lines
5.4 KiB
Python
"""
|
|
Pytest configuration and fixtures for local-context7 tests.
|
|
|
|
This module provides:
|
|
- Mocks for external dependencies (Qdrant, FastEmbed)
|
|
- Database fixtures for SQLite operations
|
|
- Common test utilities
|
|
"""
|
|
from unittest.mock import MagicMock, patch
|
|
import pytest
|
|
import os
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from backend.app.db import init_db, upsert_library, insert_document_chunk, get_chunks_for_library, list_libraries, clear_library_documents, get_connection
|
|
|
|
|
|
# =============================================================================
|
|
# FIXTURES
|
|
# =============================================================================
|
|
|
|
@pytest.fixture(scope="function")
|
|
def test_database():
|
|
"""
|
|
Create a fresh SQLite database for testing.
|
|
|
|
Yields:
|
|
Database connection with tables initialized
|
|
"""
|
|
# Use an in-memory or temporary file database
|
|
db_path = Path(__file__).parent.parent / "backend" / "data" / "test_db.sqlite"
|
|
|
|
# Ensure data directory exists
|
|
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Remove existing test DB if present
|
|
if db_path.exists():
|
|
db_path.unlink()
|
|
|
|
# Initialize database with tables
|
|
result = init_db()
|
|
assert result["success"], f"Failed to initialize test DB: {result.get('error')}"
|
|
|
|
yield
|
|
|
|
# Cleanup: remove test database after tests
|
|
if db_path.exists():
|
|
db_path.unlink()
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def sample_text():
|
|
"""Sample text for chunking tests."""
|
|
return """# Introduction
|
|
|
|
This is the introduction section.
|
|
|
|
## Background
|
|
|
|
Background information goes here to make this longer and test chunking.
|
|
|
|
This paragraph has more content about the background topic.
|
|
|
|
### Details
|
|
|
|
Specific details about the background are provided in this subsection.
|
|
|
|
More details follow here to ensure we have enough text to properly test heading preservation.
|
|
|
|
## Conclusion
|
|
|
|
The conclusion wraps up everything nicely."""
|
|
|
|
|
|
# =============================================================================
|
|
# MOCKS
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def mock_embedding_model():
|
|
"""
|
|
Mock FastEmbed model that returns dummy vectors.
|
|
|
|
This avoids needing to download and load the actual embedding model.
|
|
Returns 384-dimensional zero vectors for any input.
|
|
"""
|
|
mock_model = MagicMock()
|
|
|
|
# Mock embed method - returns list of lists with float values
|
|
def mock_embed(texts):
|
|
return [
|
|
[0.0] * 384 # Zero vector placeholder
|
|
for _ in texts
|
|
]
|
|
|
|
mock_model.embed = mock_embed
|
|
|
|
return mock_model
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_qdrant_client():
|
|
"""
|
|
Mock Qdrant client that returns empty or test results.
|
|
|
|
Allows testing search logic without needing a running Qdrant server.
|
|
"""
|
|
mock_client = MagicMock()
|
|
|
|
# Mock search method
|
|
def mock_search(collection_name, query_vector, limit=10, search_filter=None):
|
|
# Return empty list (simulating no results)
|
|
return []
|
|
|
|
mock_client.search = mock_search
|
|
|
|
# Mock delete_collection for cleanup
|
|
mock_client.delete_collection = MagicMock(return_value=True)
|
|
|
|
return mock_client
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_embedding_model_batch():
|
|
"""
|
|
Batch embedding model mock that returns deterministic fake vectors.
|
|
|
|
Returns slightly different vectors for different input lengths/first chars,
|
|
allowing tests to verify vector retrieval if needed.
|
|
"""
|
|
def hash_text(text):
|
|
# Simple hash-based pseudo-random vector generation
|
|
text_hash = hash(text) % 1000000
|
|
return [(hash_text(text) / 1000000 + (i * 0.001)) for i in range(384)]
|
|
|
|
mock_model = MagicMock()
|
|
mock_model.embed = lambda texts: [hash_text(t) for t in texts]
|
|
|
|
return mock_model
|
|
|
|
|
|
# =============================================================================
|
|
# SETUP TEARDOWN FIXTURES
|
|
# =============================================================================
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_test_database(test_database):
|
|
"""
|
|
Clear test database before and after each test function.
|
|
|
|
Note: This fixture runs the teardown (cleanup) AFTER the test,
|
|
so we manually clear at the end of the yield context.
|
|
The db_path is cleaned up by the test_database fixture's yield block.
|
|
"""
|
|
pass # Cleanup handled in test_database fixture
|
|
|
|
|
|
@pytest.fixture
|
|
def empty_vector():
|
|
"""Empty/dummy embedding vector for tests."""
|
|
return [0.0] * 384
|
|
|
|
|
|
@pytest.fixture
|
|
def fake_embeddings(sample_text):
|
|
"""Fake embedding vectors for sample text."""
|
|
def hash_text(text):
|
|
return [(hash(text) + len(text)) % 1000 / 10000 for _ in range(384)]
|
|
|
|
return [hash_text(s) for s in sample_text.split("\n\n") if s.strip()]
|
|
|
|
|
|
# =============================================================================
|
|
# UTILITY FUNCTIONS
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def temp_file(tmp_path):
|
|
"""Create a temporary file and yield its path."""
|
|
test_file = tmp_path / "test.txt"
|
|
return test_file
|
|
|
|
|
|
# Register custom marker for slow tests (if needed)
|
|
def pytest_configure(config):
|
|
config.addinivalue_line("markers", "slow: marks tests as slow (deselect with '-m \"not slow\"')")
|
|
|
|
|
|
def pytest_runtest_setup(item):
|
|
"""Add custom markers if needed."""
|
|
pass |