"""Tests for database initialize module functions.""" import tempfile from pathlib import Path from unittest.mock import Mock, patch from sqlalchemy import create_engine from sqlalchemy.orm import Session from local_deep_research.database.models import Base class TestCheckDatabaseSchema: """Tests for check_database_schema function.""" def test_returns_dict_with_tables_key(self): """check_database_schema returns dict with 'tables' key.""" from local_deep_research.database.initialize import ( check_database_schema, ) with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: # Create tables Base.metadata.create_all(engine) result = check_database_schema(engine) assert isinstance(result, dict) assert "tables" in result finally: engine.dispose() def test_lists_existing_tables(self): """check_database_schema lists existing tables.""" from local_deep_research.database.initialize import ( check_database_schema, ) with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: # Create tables Base.metadata.create_all(engine) result = check_database_schema(engine) # Should have tables dict assert isinstance(result["tables"], dict) finally: engine.dispose() def test_lists_missing_tables(self): """check_database_schema identifies missing tables.""" from local_deep_research.database.initialize import ( check_database_schema, ) with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: # Don't create any tables result = check_database_schema(engine) assert "missing_tables" in result assert isinstance(result["missing_tables"], list) finally: engine.dispose() def test_detects_news_tables(self): """check_database_schema detects news tables presence.""" from local_deep_research.database.initialize import ( check_database_schema, ) with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: # Create tables Base.metadata.create_all(engine) result = check_database_schema(engine) assert "has_news_tables" in result assert isinstance(result["has_news_tables"], bool) finally: engine.dispose() def test_returns_columns_for_each_table(self): """check_database_schema returns column names for existing tables.""" from local_deep_research.database.initialize import ( check_database_schema, ) with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: # Create tables Base.metadata.create_all(engine) result = check_database_schema(engine) # Each table in tables dict should have a list of columns for table_name, columns in result["tables"].items(): assert isinstance(columns, list) finally: engine.dispose() class TestInitializeDefaultSettings: """Tests for _initialize_default_settings function.""" def test_calls_settings_manager(self): """_initialize_default_settings calls SettingsManager methods.""" from local_deep_research.database.initialize import ( _initialize_default_settings, ) mock_session = Mock(spec=Session) with patch( "local_deep_research.settings.manager.SettingsManager" ) as MockSettingsManager: mock_settings_mgr = Mock() mock_settings_mgr.db_version_matches_package.return_value = False MockSettingsManager.return_value = mock_settings_mgr _initialize_default_settings(mock_session) MockSettingsManager.assert_called_once_with(mock_session) mock_settings_mgr.db_version_matches_package.assert_called_once() mock_settings_mgr.load_from_defaults_file.assert_called_once() mock_settings_mgr.update_db_version.assert_called_once() def test_skips_when_version_matches(self): """_initialize_default_settings skips update when version matches.""" from local_deep_research.database.initialize import ( _initialize_default_settings, ) mock_session = Mock(spec=Session) with patch( "local_deep_research.settings.manager.SettingsManager" ) as MockSettingsManager: mock_settings_mgr = Mock() mock_settings_mgr.db_version_matches_package.return_value = True MockSettingsManager.return_value = mock_settings_mgr _initialize_default_settings(mock_session) # Should not call load_from_defaults_file mock_settings_mgr.load_from_defaults_file.assert_not_called() def test_handles_errors_gracefully(self): """_initialize_default_settings swallows SettingsManager errors. Background: PR #2235 originally tried to make DB errors propagate through this code path. PR #2118 (Feb 22 2026, commit 76524cc4de) walked that change back because masking failures here was causing runtime bugs / CI failure masking — startup must be resilient even when the user's settings DB is corrupt or missing. The current contract is: SettingsManager errors during initial defaults seeding are logged-and-swallowed, not raised. This test pins that contract. PUNCHLIST historically flagged this as H5_SWALLOWS_ERROR. That flag is a false positive against the current SUT — see settings/manager.py:780-786 for the catch-and-log site. """ # audit: PUNCHLIST reviewed 2026-05 — issue resolved by prior PR (recommendation: FIX). from local_deep_research.database.initialize import ( _initialize_default_settings, ) mock_session = Mock(spec=Session) with patch( "local_deep_research.settings.manager.SettingsManager" ) as MockSettingsManager: MockSettingsManager.side_effect = Exception("Settings error") # Must not raise — startup-resilience contract per PR #2118. _initialize_default_settings(mock_session) class TestInitializeDatabase: """Tests for initialize_database function.""" def test_creates_all_tables(self): """initialize_database creates all tables from Base.metadata.""" from local_deep_research.database.initialize import initialize_database with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: initialize_database(engine) # Verify tables were created from sqlalchemy import inspect inspector = inspect(engine) tables = inspector.get_table_names() # Should have at least some tables assert len(tables) > 0 finally: engine.dispose() def test_calls_run_migrations(self): """initialize_database calls run_migrations.""" from local_deep_research.database.initialize import initialize_database with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: with patch( "local_deep_research.database.initialize.run_migrations" ) as mock_migrations: initialize_database(engine) mock_migrations.assert_called_once_with(engine) finally: engine.dispose() def test_initializes_settings_when_session_provided(self): """initialize_database initializes settings when session provided.""" from local_deep_research.database.initialize import initialize_database with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: mock_session = Mock(spec=Session) with patch( "local_deep_research.database.initialize._initialize_default_settings" ) as mock_init_settings: initialize_database(engine, db_session=mock_session) mock_init_settings.assert_called_once_with(mock_session) finally: engine.dispose() def test_skips_settings_when_no_session(self): """initialize_database skips settings init when no session provided.""" from local_deep_research.database.initialize import initialize_database with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: with patch( "local_deep_research.database.initialize._initialize_default_settings" ) as mock_init_settings: initialize_database(engine) mock_init_settings.assert_not_called() finally: engine.dispose() def test_handles_checkfirst_for_existing_tables(self): """initialize_database uses checkfirst=True for existing tables.""" from local_deep_research.database.initialize import initialize_database with tempfile.TemporaryDirectory() as temp_dir: db_path = Path(temp_dir) / "test.db" engine = create_engine(f"sqlite:///{db_path}") try: # Create tables first Base.metadata.create_all(engine) # Run initialize again - should not fail initialize_database(engine) # Verify tables still exist from sqlalchemy import inspect inspector = inspect(engine) tables = inspector.get_table_names() assert len(tables) > 0 finally: engine.dispose()