""" Tests for SQLCipher settings and environment variable overrides. These tests verify that default settings are correct and that environment variables properly override them. """ import shutil import tempfile from pathlib import Path import pytest from src.local_deep_research.database.sqlcipher_utils import ( DEFAULT_KDF_ITERATIONS, DEFAULT_PAGE_SIZE, DEFAULT_HMAC_ALGORITHM, DEFAULT_KDF_ALGORITHM, get_sqlcipher_settings, apply_cipher_defaults_before_key, apply_performance_pragmas, set_sqlcipher_key, ) from src.local_deep_research.database.sqlcipher_compat import ( get_sqlcipher_module, ) @pytest.fixture def temp_db_path(): """Create a temporary database path.""" temp_dir = tempfile.mkdtemp() db_path = Path(temp_dir) / "test_settings.db" yield db_path shutil.rmtree(temp_dir) @pytest.fixture def sqlcipher_module(): """Get the SQLCipher module.""" return get_sqlcipher_module() @pytest.fixture def clean_env(monkeypatch): """Remove all LDR_DB_* environment variables.""" env_vars = [ "LDR_DB_KDF_ITERATIONS", "LDR_DB_PAGE_SIZE", "LDR_DB_HMAC_ALGORITHM", "LDR_DB_KDF_ALGORITHM", "LDR_DB_CACHE_SIZE_MB", "LDR_DB_JOURNAL_MODE", "LDR_DB_SYNCHRONOUS", ] for var in env_vars: monkeypatch.delenv(var, raising=False) return monkeypatch class TestDefaultSettings: """Tests for default SQLCipher settings.""" def test_default_kdf_iterations(self, clean_env): """Verify default KDF iterations value.""" settings = get_sqlcipher_settings() assert settings["kdf_iterations"] == DEFAULT_KDF_ITERATIONS assert settings["kdf_iterations"] == 256000, "Default should be 256000" def test_default_page_size(self, clean_env): """Verify default page size value.""" settings = get_sqlcipher_settings() assert settings["page_size"] == DEFAULT_PAGE_SIZE assert settings["page_size"] == 16384, "Default should be 16384 (16KB)" def test_default_hmac_algorithm(self, clean_env): """Verify default HMAC algorithm value.""" settings = get_sqlcipher_settings() assert settings["hmac_algorithm"] == DEFAULT_HMAC_ALGORITHM assert settings["hmac_algorithm"] == "HMAC_SHA512" def test_default_kdf_algorithm(self, clean_env): """Verify default KDF algorithm value.""" settings = get_sqlcipher_settings() assert settings["kdf_algorithm"] == DEFAULT_KDF_ALGORITHM assert settings["kdf_algorithm"] == "PBKDF2_HMAC_SHA512" def test_settings_returns_all_required_keys(self, clean_env): """Verify settings dictionary has all required keys.""" settings = get_sqlcipher_settings() required_keys = [ "kdf_iterations", "page_size", "hmac_algorithm", "kdf_algorithm", ] for key in required_keys: assert key in settings, f"Missing key: {key}" def test_settings_values_are_correct_types(self, clean_env): """Verify settings values have correct types.""" settings = get_sqlcipher_settings() assert isinstance(settings["kdf_iterations"], int) assert isinstance(settings["page_size"], int) assert isinstance(settings["hmac_algorithm"], str) assert isinstance(settings["kdf_algorithm"], str) class TestEnvironmentOverrides: """Tests for environment variable overrides.""" def test_kdf_iterations_override(self, monkeypatch): """Verify LDR_DB_KDF_ITERATIONS environment variable works.""" monkeypatch.setenv("LDR_DB_KDF_ITERATIONS", "500000") settings = get_sqlcipher_settings() assert settings["kdf_iterations"] == 500000 def test_page_size_override(self, monkeypatch): """Verify LDR_DB_PAGE_SIZE environment variable works.""" monkeypatch.setenv("LDR_DB_PAGE_SIZE", "8192") settings = get_sqlcipher_settings() assert settings["page_size"] == 8192 def test_hmac_algorithm_override(self, monkeypatch): """Verify LDR_DB_HMAC_ALGORITHM environment variable works.""" monkeypatch.setenv("LDR_DB_HMAC_ALGORITHM", "HMAC_SHA256") settings = get_sqlcipher_settings() assert settings["hmac_algorithm"] == "HMAC_SHA256" def test_kdf_algorithm_override(self, monkeypatch): """Verify LDR_DB_KDF_ALGORITHM environment variable works.""" monkeypatch.setenv("LDR_DB_KDF_ALGORITHM", "PBKDF2_HMAC_SHA256") settings = get_sqlcipher_settings() assert settings["kdf_algorithm"] == "PBKDF2_HMAC_SHA256" class TestPerformancePragmaSettings: """Tests for performance pragma environment variable overrides.""" def test_cache_size_override( self, sqlcipher_module, temp_db_path, monkeypatch ): """Verify LDR_DB_CACHE_SIZE_MB environment variable works.""" monkeypatch.setenv("LDR_DB_CACHE_SIZE_MB", "128") conn = sqlcipher_module.connect(str(temp_db_path)) cursor = conn.cursor() apply_cipher_defaults_before_key(cursor) set_sqlcipher_key(cursor, "testpassword") apply_performance_pragmas(cursor) cursor.close() # cache_size in KB (negative means KB) # 128 MB = 131072 KB, stored as -131072 result = conn.execute("PRAGMA cache_size").fetchone() assert result is not None assert result[0] == -131072, ( f"Expected -131072 (-128*1024), got {result[0]}" ) conn.close() def test_journal_mode_override( self, sqlcipher_module, temp_db_path, monkeypatch ): """Verify LDR_DB_JOURNAL_MODE environment variable works.""" monkeypatch.setenv("LDR_DB_JOURNAL_MODE", "DELETE") conn = sqlcipher_module.connect(str(temp_db_path)) cursor = conn.cursor() apply_cipher_defaults_before_key(cursor) set_sqlcipher_key(cursor, "testpassword") apply_performance_pragmas(cursor) cursor.close() result = conn.execute("PRAGMA journal_mode").fetchone() assert result is not None assert result[0].lower() == "delete" conn.close() def test_synchronous_override( self, sqlcipher_module, temp_db_path, monkeypatch ): """Verify LDR_DB_SYNCHRONOUS environment variable works.""" monkeypatch.setenv("LDR_DB_SYNCHRONOUS", "FULL") conn = sqlcipher_module.connect(str(temp_db_path)) cursor = conn.cursor() apply_cipher_defaults_before_key(cursor) set_sqlcipher_key(cursor, "testpassword") apply_performance_pragmas(cursor) cursor.close() result = conn.execute("PRAGMA synchronous").fetchone() assert result is not None # SQLite returns synchronous as integer: 0=OFF, 1=NORMAL, 2=FULL, 3=EXTRA assert result[0] == 2, f"Expected 2 (FULL), got {result[0]}" conn.close() def test_default_journal_mode_is_wal( self, sqlcipher_module, temp_db_path, clean_env ): """Verify default journal mode is WAL.""" conn = sqlcipher_module.connect(str(temp_db_path)) cursor = conn.cursor() apply_cipher_defaults_before_key(cursor) set_sqlcipher_key(cursor, "testpassword") apply_performance_pragmas(cursor) cursor.close() result = conn.execute("PRAGMA journal_mode").fetchone() assert result is not None assert result[0].lower() == "wal", f"Expected WAL, got {result[0]}" conn.close() def test_default_temp_store_is_memory( self, sqlcipher_module, temp_db_path, clean_env ): """Verify temp_store is always set to MEMORY.""" conn = sqlcipher_module.connect(str(temp_db_path)) cursor = conn.cursor() apply_cipher_defaults_before_key(cursor) set_sqlcipher_key(cursor, "testpassword") apply_performance_pragmas(cursor) cursor.close() result = conn.execute("PRAGMA temp_store").fetchone() assert result is not None # temp_store: 0=DEFAULT, 1=FILE, 2=MEMORY assert result[0] == 2, f"Expected 2 (MEMORY), got {result[0]}" conn.close() def test_default_busy_timeout( self, sqlcipher_module, temp_db_path, clean_env ): """Verify busy_timeout is set to 10000ms.""" conn = sqlcipher_module.connect(str(temp_db_path)) cursor = conn.cursor() apply_cipher_defaults_before_key(cursor) set_sqlcipher_key(cursor, "testpassword") apply_performance_pragmas(cursor) cursor.close() result = conn.execute("PRAGMA busy_timeout").fetchone() assert result is not None assert result[0] == 10000, f"Expected 10000, got {result[0]}" conn.close() class TestSettingsAppliedToDatabase: """Tests verifying settings are actually applied to databases.""" def test_cipher_settings_applied_before_key( self, sqlcipher_module, temp_db_path, clean_env ): """Verify cipher_default_* settings are applied before key.""" settings = get_sqlcipher_settings() conn = sqlcipher_module.connect(str(temp_db_path)) cursor = conn.cursor() # Apply settings before key apply_cipher_defaults_before_key(cursor) set_sqlcipher_key(cursor, "testpassword") cursor.close() # Verify all cipher settings page_size = conn.execute("PRAGMA cipher_page_size").fetchone()[0] hmac_algo = conn.execute("PRAGMA cipher_hmac_algorithm").fetchone()[0] kdf_algo = conn.execute("PRAGMA cipher_kdf_algorithm").fetchone()[0] assert str(page_size) == str(settings["page_size"]) assert hmac_algo == settings["hmac_algorithm"] assert kdf_algo == settings["kdf_algorithm"] conn.close() def test_multiple_env_overrides_work_together( self, sqlcipher_module, temp_db_path, monkeypatch ): """Verify multiple environment variable overrides work together.""" monkeypatch.setenv("LDR_DB_PAGE_SIZE", "8192") monkeypatch.setenv("LDR_DB_KDF_ITERATIONS", "100000") monkeypatch.setenv("LDR_DB_CACHE_SIZE_MB", "32") settings = get_sqlcipher_settings() assert settings["page_size"] == 8192 assert settings["kdf_iterations"] == 100000 conn = sqlcipher_module.connect(str(temp_db_path)) cursor = conn.cursor() apply_cipher_defaults_before_key(cursor) set_sqlcipher_key(cursor, "testpassword") apply_performance_pragmas(cursor) cursor.close() # Verify page size page_size = conn.execute("PRAGMA cipher_page_size").fetchone()[0] assert str(page_size) == "8192" # Verify cache size (32MB = 32768KB = -32768) cache_size = conn.execute("PRAGMA cache_size").fetchone()[0] assert cache_size == -32768 conn.close()