""" Tests for web/auth/cleanup_middleware.py Tests cover: - cleanup_completed_research() function - Research cleanup behavior - Database error handling """ from unittest.mock import MagicMock, patch import pytest from flask import Flask class TestCleanupCompletedResearch: """Tests for cleanup_completed_research function.""" @pytest.fixture(autouse=True) def _bypass_random_gate(self): """Ensure the 1% random sampling gate always passes so tests exercise the actual cleanup logic.""" with patch( "local_deep_research.web.auth.cleanup_middleware.random.randint", return_value=1, ): yield def test_skips_when_middleware_should_skip(self): """Should skip cleanup when should_skip_database_middleware returns True.""" app = Flask(__name__) app.secret_key = "test" with patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip: mock_skip.return_value = True from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) with app.test_request_context("/static/app.js"): result = cleanup_completed_research() assert result is None def test_skips_when_no_username_in_session(self): """Should skip cleanup when no username in session.""" app = Flask(__name__) app.secret_key = "test" with patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip: mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) with app.test_request_context("/dashboard"): result = cleanup_completed_research() assert result is None def test_skips_when_no_db_session_in_g(self): """Should skip cleanup when no db_session in g.""" app = Flask(__name__) app.secret_key = "test" with patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip: mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session with app.test_request_context("/dashboard"): session["username"] = "testuser" result = cleanup_completed_research() assert result is None def test_cleans_up_completed_research_records(self): """Should delete records for research not in active_research.""" app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() mock_record = MagicMock() mock_record.research_id = "completed_research_123" mock_db_session.query.return_value.filter_by.return_value.limit.return_value.all.return_value = [ mock_record ] with ( patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip, patch( "local_deep_research.web.routes.globals.is_research_active", return_value=False, ), ): mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session cleanup_completed_research() # Verify delete was called mock_db_session.delete.assert_called_once_with(mock_record) mock_db_session.commit.assert_called_once() def test_does_not_clean_active_research(self): """Should not delete records for active research.""" app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() mock_record = MagicMock() mock_record.research_id = "active_research_456" mock_db_session.query.return_value.filter_by.return_value.limit.return_value.all.return_value = [ mock_record ] with ( patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip, patch( "local_deep_research.web.routes.globals.is_research_active", return_value=True, ), ): mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session cleanup_completed_research() # Verify delete was NOT called mock_db_session.delete.assert_not_called() mock_db_session.commit.assert_not_called() def test_handles_operational_error(self): """Should handle OperationalError gracefully.""" from sqlalchemy.exc import OperationalError app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() mock_db_session.query.side_effect = OperationalError("test", {}, None) with patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip: mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session # Should not raise exception cleanup_completed_research() mock_db_session.rollback.assert_called() def test_handles_pending_rollback_error(self): """Should handle PendingRollbackError gracefully.""" from sqlalchemy.exc import PendingRollbackError app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() mock_db_session.query.side_effect = PendingRollbackError( "pending rollback" ) with patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip: mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session # Should not raise exception cleanup_completed_research() mock_db_session.rollback.assert_called() def test_handles_timeout_error(self): """Should handle TimeoutError gracefully.""" app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() mock_db_session.query.side_effect = TimeoutError("test timeout") with patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip: mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session # Should not raise exception cleanup_completed_research() mock_db_session.rollback.assert_called() def test_handles_generic_exception(self): """Should handle generic exceptions gracefully.""" app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() mock_db_session.query.side_effect = Exception("generic error") with patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip: mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session # Should not raise exception cleanup_completed_research() mock_db_session.rollback.assert_called() def test_handles_rollback_failure(self): """Should handle rollback failure gracefully.""" from sqlalchemy.exc import OperationalError app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() mock_db_session.query.side_effect = OperationalError("test", {}, None) mock_db_session.rollback.side_effect = Exception("rollback failed") with patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip: mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session # Should not raise exception even if rollback fails cleanup_completed_research() def test_limits_query_to_50_records(self): """Should limit query to 50 records.""" app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() mock_limit = MagicMock() mock_db_session.query.return_value.filter_by.return_value.limit.return_value = mock_limit mock_limit.all.return_value = [] with ( patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip, patch( "local_deep_research.web.routes.globals.is_research_active", return_value=False, ), ): mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session cleanup_completed_research() # Verify limit(50) was called mock_db_session.query.return_value.filter_by.return_value.limit.assert_called_with( 50 ) def test_skips_cleanup_on_random_sampling(self, _bypass_random_gate): """Should skip cleanup when random sampling doesn't select this request.""" app = Flask(__name__) app.secret_key = "test" mock_db_session = MagicMock() with ( patch( "local_deep_research.web.auth.cleanup_middleware.should_skip_database_middleware" ) as mock_skip, patch( "local_deep_research.web.auth.cleanup_middleware.random.randint", return_value=50, ), ): mock_skip.return_value = False from local_deep_research.web.auth.cleanup_middleware import ( cleanup_completed_research, ) from flask import session, g with app.test_request_context("/dashboard"): session["username"] = "testuser" g.db_session = mock_db_session result = cleanup_completed_research() assert result is None mock_db_session.query.assert_not_called()