Initial DocsMCP stack
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user