"""Tests for metrics database module.""" from unittest.mock import MagicMock, Mock, patch import pytest from local_deep_research.metrics.database import MetricsDatabase class TestMetricsDatabaseInit: """Tests for MetricsDatabase initialization.""" def test_initializes_with_no_credentials(self): """Should initialize without credentials.""" db = MetricsDatabase() assert db.username is None assert db.password is None def test_initializes_with_username(self): """Should store username if provided.""" db = MetricsDatabase(username="testuser") assert db.username == "testuser" assert db.password is None def test_initializes_with_credentials(self): """Should store both username and password.""" db = MetricsDatabase(username="testuser", password="testpass") assert db.username == "testuser" assert db.password == "testpass" class TestMetricsDatabaseGetSession: """Tests for get_session method.""" def test_yields_none_when_no_username_available(self): """Should yield None when no username available.""" db = MetricsDatabase() with patch("flask.session", {}): with db.get_session() as session: assert session is None def test_uses_provided_username(self): """Should use username provided to get_session.""" db = MetricsDatabase() mock_session = MagicMock() mock_cm = MagicMock() mock_cm.__enter__ = Mock(return_value=mock_session) mock_cm.__exit__ = Mock(return_value=None) with patch( "local_deep_research.metrics.database.get_user_db_session", return_value=mock_cm, ): with db.get_session(username="testuser") as session: assert session == mock_session def test_uses_stored_username_as_fallback(self): """Should use stored username if not provided.""" db = MetricsDatabase(username="stored_user") mock_session = MagicMock() mock_cm = MagicMock() mock_cm.__enter__ = Mock(return_value=mock_session) mock_cm.__exit__ = Mock(return_value=None) with patch( "local_deep_research.metrics.database.get_user_db_session", return_value=mock_cm, ): with db.get_session() as session: assert session == mock_session def test_uses_flask_session_as_fallback(self): """Should try Flask session if no username provided.""" db = MetricsDatabase() mock_session = MagicMock() mock_cm = MagicMock() mock_cm.__enter__ = Mock(return_value=mock_session) mock_cm.__exit__ = Mock(return_value=None) with patch("flask.session", {"username": "flask_user"}): with patch( "local_deep_research.metrics.database.get_user_db_session", return_value=mock_cm, ): with db.get_session() as session: assert session == mock_session def test_uses_thread_safe_access_with_password(self): """Should use thread metrics writer when password provided.""" db = MetricsDatabase(username="testuser", password="testpass") mock_session = MagicMock() mock_writer = MagicMock() mock_cm = MagicMock() mock_cm.__enter__ = Mock(return_value=mock_session) mock_cm.__exit__ = Mock(return_value=None) mock_writer.get_session.return_value = mock_cm with patch( "local_deep_research.database.thread_metrics.metrics_writer", mock_writer, ): with db.get_session() as session: mock_writer.set_user_password.assert_called_with( "testuser", "testpass" ) assert session == mock_session def test_overrides_stored_username(self): """Should use provided username over stored one.""" db = MetricsDatabase(username="stored") mock_session = MagicMock() mock_cm = MagicMock() mock_cm.__enter__ = Mock(return_value=mock_session) mock_cm.__exit__ = Mock(return_value=None) with patch( "local_deep_research.metrics.database.get_user_db_session", return_value=mock_cm, ) as mock_get_session: # Provide override username with db.get_session(username="override"): mock_get_session.assert_called_with("override") def test_handles_flask_import_error(self): """Should handle case when Flask is not available.""" db = MetricsDatabase() # Simulate Flask not being available with patch.dict("sys.modules", {"flask": None}): with db.get_session() as session: # Should yield None without raising assert session is None class TestMetricsDatabaseContextManager: """Tests for context manager behavior.""" def test_context_manager_returns_session(self): """Context manager should yield a session.""" db = MetricsDatabase(username="testuser") mock_session = MagicMock() mock_cm = MagicMock() mock_cm.__enter__ = Mock(return_value=mock_session) mock_cm.__exit__ = Mock(return_value=None) with patch( "local_deep_research.metrics.database.get_user_db_session", return_value=mock_cm, ): with db.get_session() as session: assert session is not None def test_context_manager_handles_exceptions(self): """Context manager should handle exceptions properly.""" db = MetricsDatabase(username="testuser") mock_cm = MagicMock() mock_cm.__enter__ = Mock(side_effect=Exception("DB Error")) mock_cm.__exit__ = Mock(return_value=None) with patch( "local_deep_research.metrics.database.get_user_db_session", return_value=mock_cm, ): with pytest.raises(Exception, match="DB Error"): with db.get_session(): pass class TestFlaskSessionRuntimeError: """Tests for Flask session RuntimeError handling (PR #2014). PR #2014 changed bare `except:` to `except RuntimeError:` when accessing flask_session outside request context. """ def test_runtime_error_caught_outside_flask_context(self): """RuntimeError is raised when accessing Flask session outside context.""" with pytest.raises(RuntimeError): from flask import session as flask_session flask_session.get("username") def test_runtime_error_is_specific_exception(self): """RuntimeError is caught specifically, not by bare except.""" caught_type = None try: from flask import session as flask_session flask_session.get("username") except RuntimeError: caught_type = "RuntimeError" except Exception: caught_type = "Exception" assert caught_type == "RuntimeError"