""" 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 def test_replace_library_documents_is_atomic(self, test_database): """Replacing chunks should remove old rows and insert the new set.""" from backend.app.db import ( get_chunks_for_library, insert_document_chunk, replace_library_documents, upsert_library, ) library_id = "/local/replacetest" upsert_library(library_id, "Replace test", source_path=library_id) insert_document_chunk( "old-chunk", library_id, "old.md", content="old content", chunk_index=0, ) result = replace_library_documents( library_id, [ { "id": "new-chunk", "path": "new.md", "title": "new", "content": "new content", "chunk_index": 0, "token_estimate": 2, } ], ) chunks = get_chunks_for_library(library_id) assert result["success"] is True assert result["deleted"] >= 1 assert result["inserted"] == 1 assert [chunk["id"] for chunk in chunks] == ["new-chunk"] def test_failed_replacement_keeps_existing_chunks(self, test_database): """A bad replacement must roll back instead of erasing the old index.""" from backend.app.db import ( get_chunks_for_library, insert_document_chunk, replace_library_documents, upsert_library, ) library_id = "/local/rollbacktest" upsert_library(library_id, "Rollback test", source_path=library_id) insert_document_chunk( "old-chunk", library_id, "old.md", content="old content", chunk_index=0, ) duplicate = { "id": "duplicate", "path": "new.md", "content": "new content", "chunk_index": 0, } result = replace_library_documents(library_id, [duplicate, duplicate]) chunks = get_chunks_for_library(library_id) assert result["success"] is False assert [chunk["id"] for chunk in chunks] == ["old-chunk"] 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\" & 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 }