Initial DocsMCP stack
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
"""
|
||||
Tests for backend/app/db.py
|
||||
|
||||
These tests verify SQLite database operations including:
|
||||
- Table creation (init_db)
|
||||
- Library CRUD operations
|
||||
- Document chunk storage and retrieval
|
||||
- Full-text search functionality
|
||||
|
||||
All tests use a temporary test database file.
|
||||
"""
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class TestInitDatabase:
|
||||
"""Tests for init_db() - table creation."""
|
||||
|
||||
def test_init_db_creates_tables(self, test_database):
|
||||
"""Database should have libraries and documents tables after init."""
|
||||
import sqlite3
|
||||
from backend.app.db import get_connection, get_db_path
|
||||
|
||||
conn = get_connection()
|
||||
cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
||||
tables = [row[0] for row in cursor.fetchall()]
|
||||
|
||||
# Should have libraries, documents, and FTS virtual table
|
||||
assert "libraries" in tables or any("libraries" in t.lower() for t in tables)
|
||||
conn.close()
|
||||
|
||||
def test_init_db_returns_success(self, test_database):
|
||||
"""init_db should return success indicator."""
|
||||
from backend.app.db import init_db
|
||||
|
||||
result = init_db()
|
||||
assert result["success"] is True
|
||||
|
||||
|
||||
class TestLibraryOperations:
|
||||
"""Tests for library CRUD operations."""
|
||||
|
||||
def test_upsert_library_new(self, test_database):
|
||||
"""Upsert should create new library."""
|
||||
from backend.app.db import upsert_library
|
||||
|
||||
result = upsert_library(
|
||||
library_id="/local/testlib",
|
||||
name="Test Library",
|
||||
description="A test library for unit tests"
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["id"] == "/local/testlib"
|
||||
|
||||
def test_upsert_library_update(self, test_database):
|
||||
"""Upsert should update existing library."""
|
||||
from backend.app.db import upsert_library
|
||||
|
||||
# Insert first library
|
||||
upsert_library(
|
||||
library_id="/local/upsertlib",
|
||||
name="Original Name",
|
||||
description="Original description"
|
||||
)
|
||||
|
||||
# Update it
|
||||
result = upsert_library(
|
||||
library_id="/local/upsertlib",
|
||||
name="Updated Name",
|
||||
description="Updated description"
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
def test_upsert_library_id_normalization(self, test_database):
|
||||
"""Library ID normalization - /local/ prefix should be preserved."""
|
||||
from backend.app.db import upsert_library
|
||||
|
||||
# Test various ID formats
|
||||
test_ids = [
|
||||
"/local/foundryvtt",
|
||||
"foundryvtt",
|
||||
"/local/mydocs",
|
||||
]
|
||||
|
||||
for lib_id in test_ids:
|
||||
result = upsert_library(library_id=lib_id, name="Test", description="Desc")
|
||||
assert result["success"] is True
|
||||
# Verify we can retrieve it back
|
||||
from backend.app.db import get_chunks_for_library
|
||||
# Just ensure no errors occur
|
||||
|
||||
def test_list_libraries(self, test_database):
|
||||
"""list_libraries should return list of libraries."""
|
||||
from backend.app.db import upsert_library, list_libraries
|
||||
|
||||
# Create some libraries
|
||||
for i in range(3):
|
||||
upsert_library(
|
||||
library_id=f"/local/lib{i}",
|
||||
name=f"Library {i}",
|
||||
description=f"Description {i}"
|
||||
)
|
||||
|
||||
libs = list_libraries()
|
||||
assert isinstance(libs, list)
|
||||
assert len(libs) >= 3
|
||||
|
||||
def test_search_libraries(self, test_database):
|
||||
"""search_libraries should find libraries by name/description."""
|
||||
from backend.app.db import upsert_library, search_libraries
|
||||
|
||||
# Create libraries with searchable names
|
||||
upsert_library(library_id="/local/foo1", name="Foo Library", description="Bar baz")
|
||||
upsert_library(library_id="/local/foo2", name="Other Library", description="Different content")
|
||||
|
||||
results = search_libraries("foo")
|
||||
assert isinstance(results, list)
|
||||
|
||||
|
||||
class TestDocumentChunkOperations:
|
||||
"""Tests for document chunk storage and retrieval."""
|
||||
|
||||
def test_insert_document_chunk_new(self, test_database):
|
||||
"""insert_document_chunk should create new chunk record."""
|
||||
from backend.app.db import insert_document_chunk
|
||||
|
||||
result = insert_document_chunk(
|
||||
doc_id="doc-1",
|
||||
library_id="/local/testlib",
|
||||
path="docs/example.md",
|
||||
title="Example Document",
|
||||
content="# Example\n\nThis is the content.",
|
||||
chunk_index=0,
|
||||
token_estimate=100
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
def test_insert_document_chunk_update(self, test_database):
|
||||
"""insert_document_chunk should update existing record."""
|
||||
from backend.app.db import insert_document_chunk
|
||||
|
||||
# Insert first
|
||||
insert_document_chunk(
|
||||
doc_id="doc-update-test",
|
||||
library_id="/local/uplib",
|
||||
path="old-path.md",
|
||||
title="Old Title",
|
||||
content="# Old\nContent here.",
|
||||
chunk_index=0,
|
||||
token_estimate=50
|
||||
)
|
||||
|
||||
# Update it
|
||||
result = insert_document_chunk(
|
||||
doc_id="doc-update-test",
|
||||
library_id="/local/uplib",
|
||||
path="new-path.md",
|
||||
title="New Title",
|
||||
content="# New\nUpdated content.",
|
||||
chunk_index=1,
|
||||
token_estimate=75
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
def test_get_document_by_id(self, test_database):
|
||||
"""get_document_by_id should retrieve document by ID."""
|
||||
from backend.app.db import insert_document_chunk, get_document_by_id
|
||||
|
||||
# Insert document
|
||||
doc_id = "unique-doc-id-12345"
|
||||
insert_document_chunk(
|
||||
doc_id=doc_id,
|
||||
library_id="/local/testlib",
|
||||
path="docs/test.md",
|
||||
title="Test Document",
|
||||
content="# Test\n\nTest content here.",
|
||||
chunk_index=None,
|
||||
token_estimate=200
|
||||
)
|
||||
|
||||
# Retrieve it
|
||||
doc = get_document_by_id(doc_id)
|
||||
assert doc is not None
|
||||
assert doc["id"] == doc_id
|
||||
|
||||
def test_get_chunks_for_library(self, test_database):
|
||||
"""get_chunks_for_library should return all chunks for a library."""
|
||||
from backend.app.db import upsert_library, insert_document_chunk, get_chunks_for_library
|
||||
|
||||
# Create library
|
||||
upsert_library(library_id="/local/chunktest", name="Chunk Test", description="Test")
|
||||
|
||||
# Add some chunks
|
||||
for i in range(3):
|
||||
insert_document_chunk(
|
||||
doc_id=f"chunk-{i}",
|
||||
library_id="/local/chunktest",
|
||||
path=f"path{i}.md",
|
||||
title=f"Section {i}",
|
||||
content=f"Content section {i}.",
|
||||
chunk_index=i,
|
||||
token_estimate=50
|
||||
)
|
||||
|
||||
chunks = get_chunks_for_library("/local/chunktest")
|
||||
assert isinstance(chunks, list)
|
||||
assert len(chunks) >= 3
|
||||
|
||||
def test_clear_library_documents(self, test_database):
|
||||
"""clear_library_documents should delete all docs for a library."""
|
||||
from backend.app.db import upsert_library, insert_document_chunk, clear_library_documents, get_chunks_for_library
|
||||
|
||||
# Create and populate library
|
||||
upsert_library(library_id="/local/cleartest", name="Clear Test", description="Test")
|
||||
for i in range(5):
|
||||
insert_document_chunk(
|
||||
doc_id=f"clear-{i}",
|
||||
library_id="/local/cleartest",
|
||||
path=f"path{i}.md",
|
||||
content=f"Content {i}.",
|
||||
token_estimate=20
|
||||
)
|
||||
|
||||
# Clear it
|
||||
result = clear_library_documents("/local/cleartest")
|
||||
assert result["success"] is True
|
||||
|
||||
# Verify cleared
|
||||
remaining = get_chunks_for_library("/local/cleartest")
|
||||
assert len(remaining) == 0
|
||||
|
||||
|
||||
class TestDatabaseEdgeCases:
|
||||
"""Tests for edge cases and error handling."""
|
||||
|
||||
def test_empty_library_id(self, test_database):
|
||||
"""Operations with empty ID should handle gracefully."""
|
||||
from backend.app.db import upsert_library
|
||||
|
||||
result = upsert_library(library_id="", name="Test", description="Desc")
|
||||
# Should not crash, though may not be a valid operation
|
||||
|
||||
def test_special_characters_in_content(self, test_database):
|
||||
"""Content with special characters should be stored."""
|
||||
from backend.app.db import insert_document_chunk
|
||||
|
||||
content = "Hello \"world\" <tag /> & amp; 'apostrophe'"
|
||||
result = insert_document_chunk(
|
||||
doc_id="special-test",
|
||||
library_id="/local/speciallib",
|
||||
path="special.md",
|
||||
content=content,
|
||||
token_estimate=100
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
def test_very_long_content(self, test_database):
|
||||
"""Long content should be stored."""
|
||||
from backend.app.db import insert_document_chunk
|
||||
|
||||
long_content = "a" * 5000
|
||||
result = insert_document_chunk(
|
||||
doc_id="long-test",
|
||||
library_id="/local/longlib",
|
||||
path="long.md",
|
||||
content=long_content,
|
||||
token_estimate=1000
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
def test_none_description(self, test_database):
|
||||
"""Library with None description should work."""
|
||||
from backend.app.db import upsert_library
|
||||
|
||||
result = upsert_library(
|
||||
library_id="/local/nonedesc",
|
||||
name="No Description Lib",
|
||||
description=None
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
|
||||
|
||||
class TestDatabaseInitialization:
|
||||
"""Tests for database initialization state."""
|
||||
|
||||
def test_database_is_empty_after_init(self, test_database):
|
||||
"""Database should be empty right after init."""
|
||||
from backend.app.db import list_libraries
|
||||
|
||||
libs = list_libraries()
|
||||
assert isinstance(libs, list)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# FIXTURES
|
||||
# =============================================================================
|
||||
|
||||
@pytest.fixture
|
||||
def sample_doc():
|
||||
"""Sample document chunk for testing."""
|
||||
return {
|
||||
"doc_id": "sample-doc-1",
|
||||
"library_id": "/local/samplelib",
|
||||
"path": "docs/guide.md",
|
||||
"title": "Getting Started Guide",
|
||||
"content": "# Getting Started\n\nWelcome to the guide. This is a sample document for testing.\n\n## Installation\n\nInstall with pip.",
|
||||
"chunk_index": 0,
|
||||
"token_estimate": 500
|
||||
}
|
||||
Reference in New Issue
Block a user