"""
Tests for cleanup_db_session teardown behavior in apply_middleware().

Source: app_factory.py lines 547-596.
Tests the teardown_appcontext handler that cleans up DB sessions,
thread-local sessions, and sweeps dead engines after each request.
"""

import pytest
from unittest.mock import patch, Mock

from flask import g


@pytest.fixture
def app():
    """Create a minimal Flask app with the teardown handler registered."""
    from local_deep_research.web.app_factory import create_app

    with patch("local_deep_research.web.app_factory.SocketIOService"):
        app, _ = create_app()
        app.config["TESTING"] = True
        app.config["WTF_CSRF_ENABLED"] = False
        return app


class TestCleanupDbSession:
    """Tests for the cleanup_db_session teardown handler."""

    def test_normal_cleanup_rollback_and_close(self, app):
        """Session in g.db_session gets rollback() and close() called."""
        mock_session = Mock()

        with app.test_request_context("/"):
            g.db_session = mock_session

        # After exiting context, teardown runs
        mock_session.rollback.assert_called_once()
        mock_session.close.assert_called_once()

    def test_no_session_in_g_no_error(self, app):
        """When g has no db_session, teardown completes without error."""
        # audit: PUNCHLIST reviewed 2026-05 — KEEP (ASSERT_TRUE).
        with app.test_request_context("/"):
            # Don't set g.db_session
            pass
        # Should not raise

    def test_rollback_fails_close_still_called(self, app):
        """If rollback() raises, close() is still called."""
        mock_session = Mock()
        mock_session.rollback.side_effect = RuntimeError("rollback failed")

        with app.test_request_context("/"):
            g.db_session = mock_session

        mock_session.rollback.assert_called_once()
        mock_session.close.assert_called_once()

    def test_close_fails_no_propagation(self, app):
        """If close() raises, the exception does not propagate."""
        mock_session = Mock()
        mock_session.close.side_effect = RuntimeError("close failed")

        with app.test_request_context("/"):
            g.db_session = mock_session

        # Should not raise despite close() failure
        mock_session.rollback.assert_called_once()
        mock_session.close.assert_called_once()

    def test_cleanup_dead_threads_called(self, app):
        """cleanup_dead_threads is called during teardown."""
        mock_cleanup = Mock()

        with patch(
            "local_deep_research.database.thread_local_session.cleanup_dead_threads",
            mock_cleanup,
        ):
            with app.test_request_context("/"):
                pass

        mock_cleanup.assert_called_once()

    def test_cleanup_current_thread_called(self, app):
        """cleanup_current_thread is called during teardown for defense-in-depth."""
        mock_cleanup = Mock()

        with patch(
            "local_deep_research.database.thread_local_session.cleanup_current_thread",
            mock_cleanup,
        ):
            with app.test_request_context("/"):
                pass

        mock_cleanup.assert_called_once()
