"""Regression guard for the encrypted-DB CreateIndex defect. Until v1.6.x, ``encrypted_db.create_user_database()`` only emitted ``CreateTable`` statements and never ``CreateIndex``. As a result every model-declared index (``index=True``, ``unique=True``, explicit ``Index(...)`` in ``__table_args__``) was missing in user databases. This test asserts that for every table in ``Base.metadata``, every named index declared by the model is present in a freshly created encrypted database — both column-level and table-level. """ from __future__ import annotations import sys import tempfile from pathlib import Path import pytest from sqlalchemy import inspect pytest.importorskip( "sqlcipher3", reason="SQLCipher is required to test the encrypted-DB schema path.", ) sys.path.insert( 0, str(Path(__file__).parent.parent.parent.resolve()), ) from local_deep_research.database.encrypted_db import DatabaseManager from local_deep_research.database.models import Base @pytest.fixture def temp_data_dir(monkeypatch): with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) monkeypatch.setattr( "local_deep_research.database.encrypted_db.get_data_directory", lambda: path, ) yield path @pytest.fixture def db_manager(temp_data_dir): m = DatabaseManager() yield m for username in list(m.connections.keys()): m.close_user_database(username) def test_every_model_declared_index_exists_in_fresh_db(db_manager): """Iterate Base.metadata; assert every named index is present.""" assert db_manager.has_encryption, ( "sqlcipher3 imports but DatabaseManager reported has_encryption=False" ) username = "indexcheck" password = "StrongPassword1!" db_manager.create_user_database(username, password) engine = db_manager.connections[username] inspector = inspect(engine) missing: list[tuple[str, str]] = [] for table in Base.metadata.sorted_tables: if table.name == "users": continue if not inspector.has_table(table.name): continue existing = {idx["name"] for idx in inspector.get_indexes(table.name)} for index in table.indexes: if not index.name: continue if index.name not in existing: missing.append((table.name, index.name)) assert not missing, ( f"Missing model-declared indexes in fresh user DB: {missing}. " f"This usually means encrypted_db.create_user_database() is not " f"emitting CreateIndex for declared indexes." ) def test_download_tracker_url_hash_has_unique_backing(db_manager): """The exact regression that caused #3697 — url_hash must be UNIQUE. Without UNIQUE backing on the FK target, SQLCipher raises "foreign key mismatch — download_attempts referencing download_tracker" on cascade delete. """ assert db_manager.has_encryption username = "trackercheck" password = "StrongPassword1!" db_manager.create_user_database(username, password) engine = db_manager.connections[username] inspector = inspect(engine) indexes = inspector.get_indexes("download_tracker") unique_on_url_hash = [ idx for idx in indexes if idx.get("unique") and idx.get("column_names") == ["url_hash"] ] assert unique_on_url_hash, ( "download_tracker.url_hash must have a UNIQUE backing index for " "FK references from download_attempts/download_duplicates to " f"resolve. Indexes present: {indexes}" )