"""Extended tests for database/session_context.py - targeting untested paths. Covers: - ensure_db_session() decorator (lines 160-212, entirely untested) - get_user_db_session() reusing g.db_session - get_user_db_session() password from g.user_password - get_user_db_session() password from session_password_store - get_user_db_session() password from thread context - get_user_db_session() get_metrics_session returns None """ from unittest.mock import Mock, patch import pytest from flask import Flask, g, session as flask_session @pytest.fixture def app(): """Create test Flask application.""" app = Flask(__name__) app.config["TESTING"] = True app.secret_key = "test-secret-key" return app # ── ensure_db_session decorator ────────────────────────────────── class TestEnsureDbSession: """Tests for ensure_db_session decorator (lines 160-212, entirely untested).""" def test_no_username_passes_through(self, app): """When no username in session, view runs without db_session setup.""" from local_deep_research.database.session_context import ( ensure_db_session, ) view_called = [] @ensure_db_session def my_view(): view_called.append(True) return "ok" with app.test_request_context(): result = my_view() assert result == "ok" assert view_called == [True] def test_connected_user_gets_session(self, app): """Connected user gets g.db_session set from db_manager.""" from local_deep_research.database.session_context import ( ensure_db_session, ) mock_session = Mock() @ensure_db_session def my_view(): return g.db_session with app.test_request_context(): flask_session["username"] = "testuser" with patch( "local_deep_research.database.session_context.db_manager" ) as mock_db: mock_db.is_user_connected.return_value = True mock_db.get_session.return_value = mock_session result = my_view() assert result is mock_session mock_db.get_session.assert_called_once_with("testuser") def test_encrypted_db_not_connected_redirects_to_login(self, app): """Encrypted DB + disconnected user → clear session + redirect.""" from local_deep_research.database.session_context import ( ensure_db_session, ) # Register auth blueprint with login route from flask import Blueprint auth_bp = Blueprint("auth", __name__) @auth_bp.route("/login") def login(): return "login page" app.register_blueprint(auth_bp) @ensure_db_session def my_view(): return "should not reach" with app.test_request_context(): flask_session["username"] = "testuser" flask_session["some_data"] = "preserve_check" with patch( "local_deep_research.database.session_context.db_manager" ) as mock_db: mock_db.is_user_connected.return_value = False mock_db.has_encryption = True result = my_view() # Should be a redirect response assert result.status_code == 302 assert "/login" in result.location def test_unencrypted_db_not_connected_reopens(self, app): """Unencrypted DB + disconnected user → reopen database.""" from local_deep_research.database.session_context import ( ensure_db_session, UNENCRYPTED_DB_PLACEHOLDER, ) mock_session = Mock() @ensure_db_session def my_view(): return g.db_session with app.test_request_context(): flask_session["username"] = "testuser" with patch( "local_deep_research.database.session_context.db_manager" ) as mock_db: mock_db.is_user_connected.return_value = False mock_db.has_encryption = False mock_engine = Mock() mock_db.open_user_database.return_value = mock_engine mock_db.get_session.return_value = mock_session result = my_view() mock_db.open_user_database.assert_called_once_with( "testuser", UNENCRYPTED_DB_PLACEHOLDER ) assert result is mock_session def test_unencrypted_reopen_fails_gracefully(self, app): """Failed unencrypted reopen doesn't crash — view still runs.""" from local_deep_research.database.session_context import ( ensure_db_session, ) @ensure_db_session def my_view(): return "view ran" with app.test_request_context(): flask_session["username"] = "testuser" with patch( "local_deep_research.database.session_context.db_manager" ) as mock_db: mock_db.is_user_connected.return_value = False mock_db.has_encryption = False mock_db.open_user_database.return_value = None # Engine failed result = my_view() assert result == "view ran" def test_exception_during_session_setup_returns_500(self, app): """Exception in session setup returns 500 error response.""" from local_deep_research.database.session_context import ( ensure_db_session, ) @ensure_db_session def my_view(): return "recovered" with app.test_request_context(): flask_session["username"] = "testuser" with patch( "local_deep_research.database.session_context.db_manager" ) as mock_db: mock_db.is_user_connected.side_effect = RuntimeError("DB crash") result = my_view() assert result[1] == 500 def test_preserves_function_name(self): """Decorator preserves wrapped function name via functools.wraps.""" from local_deep_research.database.session_context import ( ensure_db_session, ) @ensure_db_session def my_special_view(): pass assert my_special_view.__name__ == "my_special_view" def test_passes_args_to_view(self, app): """Decorator passes positional and keyword args to wrapped view.""" from local_deep_research.database.session_context import ( ensure_db_session, ) @ensure_db_session def my_view(item_id, mode="default"): return f"{item_id}:{mode}" with app.test_request_context(): # No username → passes through without DB setup result = my_view(42, mode="edit") assert result == "42:edit" # ── get_user_db_session: g.db_session reuse ────────────────────── class TestGetUserDbSessionReuse: """Tests for g.db_session reuse path (lines 62-65).""" def test_reuses_existing_g_db_session(self, app): """When g.db_session exists, it is reused without creating new session.""" from local_deep_research.database.session_context import ( get_user_db_session, ) existing_session = Mock() with app.test_request_context(): flask_session["username"] = "testuser" g.db_session = existing_session with get_user_db_session() as session: assert session is existing_session # ── get_user_db_session: password resolution paths ─────────────── class TestGetUserDbSessionPasswordPaths: """Tests for password resolution fallback chain.""" def test_password_from_g_user_password(self, app): """Password is retrieved from g.user_password.""" from local_deep_research.database.session_context import ( get_user_db_session, ) mock_session = Mock() with app.test_request_context(): flask_session["username"] = "testuser" g.user_password = "secret123" with patch( "local_deep_research.database.session_context.db_manager" ) as mock_db: mock_db.has_encryption = True with patch( "local_deep_research.database.thread_local_session.get_metrics_session" ) as mock_get: mock_get.return_value = mock_session with get_user_db_session() as session: assert session is mock_session mock_get.assert_called_once_with("testuser", "secret123") def test_password_from_session_password_store(self, app): """Password is retrieved from session_password_store.""" from local_deep_research.database.session_context import ( get_user_db_session, ) mock_session = Mock() with app.test_request_context(): flask_session["username"] = "testuser" flask_session["session_id"] = "sess-abc" with ( patch( "local_deep_research.database.session_context.db_manager" ) as mock_db, patch( "local_deep_research.database.session_context.get_search_context" ) as mock_ctx, ): mock_db.has_encryption = True mock_ctx.return_value = None # No thread context # session_password_store is imported locally inside get_user_db_session with patch( "local_deep_research.database.session_passwords.session_password_store" ) as mock_store: mock_store.get_session_password.return_value = "store-pass" with patch( "local_deep_research.database.thread_local_session.get_metrics_session" ) as mock_get: mock_get.return_value = mock_session with get_user_db_session() as session: assert session is mock_session mock_get.assert_called_once_with( "testuser", "store-pass" ) mock_store.get_session_password.assert_called_once_with( "testuser", "sess-abc" ) def test_password_from_thread_context(self, app): """Password is retrieved from thread context (background threads).""" from local_deep_research.database.session_context import ( get_user_db_session, ) mock_session = Mock() with app.test_request_context(): flask_session["username"] = "testuser" with ( patch( "local_deep_research.database.session_context.db_manager" ) as mock_db, patch( "local_deep_research.database.session_context.get_search_context" ) as mock_ctx, ): mock_db.has_encryption = True mock_ctx.return_value = {"user_password": "thread-pass"} with patch( "local_deep_research.database.thread_local_session.get_metrics_session" ) as mock_get: mock_get.return_value = mock_session with get_user_db_session() as session: assert session is mock_session mock_get.assert_called_once_with("testuser", "thread-pass") def test_get_metrics_session_returns_none_raises(self, app): """When get_metrics_session returns None, DatabaseSessionError is raised.""" from local_deep_research.database.session_context import ( get_user_db_session, DatabaseSessionError, ) with app.test_request_context(): flask_session["username"] = "testuser" with patch( "local_deep_research.database.session_context.db_manager" ) as mock_db: mock_db.has_encryption = False with patch( "local_deep_research.database.thread_local_session.get_metrics_session" ) as mock_get: mock_get.return_value = None with pytest.raises( DatabaseSessionError, match="Could not establish session", ): with get_user_db_session(): pass def test_password_stored_in_g_after_success(self, app): """After successful session creation, password is stored in g.""" from local_deep_research.database.session_context import ( get_user_db_session, ) mock_session = Mock() with app.test_request_context(): flask_session["username"] = "testuser" with patch( "local_deep_research.database.session_context.db_manager" ) as mock_db: mock_db.has_encryption = False with patch( "local_deep_research.database.thread_local_session.get_metrics_session" ) as mock_get: mock_get.return_value = mock_session with get_user_db_session() as _session: # After yield, g.user_password should be set assert hasattr(g, "user_password")