"""
Coverage polish tests targeting specific uncovered lines/branches
in files listed in the 90%+ coverage push issue.

Each test class maps to a single source module.
"""

from unittest.mock import AsyncMock, MagicMock, Mock, patch

import pytest
from dropbox.exceptions import ApiError


# ---------------------------------------------------------------------------
# app/tasks/upload_to_dropbox.py  (88 % → 95 %+)
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestDropboxClientGenericException:
    """Cover the generic except block in get_dropbox_client (lines 100-102)."""

    @patch("app.tasks.upload_to_dropbox.dropbox.Dropbox")
    @patch("app.tasks.upload_to_dropbox.settings")
    def test_generic_exception_during_client_creation(self, mock_settings, mock_dropbox):
        """Non-AuthError exception propagates from get_dropbox_client."""
        from app.tasks.upload_to_dropbox import get_dropbox_client

        mock_settings.dropbox_app_key = "key"
        mock_settings.dropbox_app_secret = "secret"
        mock_settings.dropbox_refresh_token = "token"

        mock_dropbox.side_effect = RuntimeError("network failure")

        with pytest.raises(RuntimeError, match="network failure"):
            get_dropbox_client()


@pytest.mark.unit
class TestDropboxCheckExistsInDropbox:
    """Cover check_exists_in_dropbox inner function (lines 155-161)."""

    @patch("app.tasks.upload_to_dropbox.get_unique_filename")
    @patch("app.tasks.upload_to_dropbox.extract_remote_path")
    @patch("app.tasks.upload_to_dropbox.get_dropbox_client")
    @patch("app.tasks.upload_to_dropbox.log_task_progress")
    @patch("app.tasks.upload_to_dropbox.settings")
    def test_check_exists_returns_true(
        self, mock_settings, mock_log, mock_client_fn, mock_extract, mock_unique, tmp_path
    ):
        """When files_get_metadata succeeds, check_exists returns True."""
        from app.tasks.upload_to_dropbox import upload_to_dropbox

        mock_settings.dropbox_app_key = "key"
        mock_settings.dropbox_app_secret = "secret"
        mock_settings.dropbox_refresh_token = "token"
        mock_settings.dropbox_folder = "/uploads"
        mock_settings.workdir = str(tmp_path)

        test_file = tmp_path / "test.pdf"
        test_file.write_bytes(b"small file")

        mock_dbx = Mock()
        mock_client_fn.return_value = mock_dbx
        mock_extract.return_value = "uploads/test.pdf"

        # Capture the check_exists callback passed to get_unique_filename
        def capture_callback(path, check_fn):
            # Exercise check_exists path where file exists (returns True)
            assert check_fn(path) is True
            # Exercise check_exists path where file doesn't exist (returns False)
            not_found_error = Mock()
            not_found_error.is_path.return_value = True
            not_found_error.get_path.return_value.is_not_found.return_value = True
            mock_dbx.files_get_metadata.side_effect = ApiError("req-id", not_found_error, "not found", "header")
            assert check_fn(path) is False
            # Exercise check_exists path where ApiError is NOT not_found (re-raises)
            other_error = Mock()
            other_error.is_path.return_value = False
            mock_dbx.files_get_metadata.side_effect = ApiError("req-id", other_error, "other", "header")
            with pytest.raises(ApiError):
                check_fn(path)
            # Reset side effect for the actual upload
            mock_dbx.files_get_metadata.side_effect = None
            return path

        mock_unique.side_effect = capture_callback

        result = upload_to_dropbox.apply(args=[str(test_file)], kwargs={"file_id": 1}).get()
        assert result["status"] == "Completed"


@pytest.mark.unit
class TestDropboxUnexpectedExceptionHandler:
    """Cover generic except block in upload_to_dropbox (lines 219-223)."""

    @patch("app.tasks.upload_to_dropbox.get_dropbox_client")
    @patch("app.tasks.upload_to_dropbox.log_task_progress")
    @patch("app.tasks.upload_to_dropbox.settings")
    def test_unexpected_exception_during_upload(self, mock_settings, mock_log, mock_client, tmp_path):
        """Non-Auth/Non-Api exception is caught by the generic handler."""
        from app.tasks.upload_to_dropbox import upload_to_dropbox

        mock_settings.dropbox_app_key = "key"
        mock_settings.dropbox_app_secret = "secret"
        mock_settings.dropbox_refresh_token = "token"
        mock_settings.dropbox_folder = "/uploads"
        mock_settings.workdir = str(tmp_path)

        test_file = tmp_path / "test.pdf"
        test_file.write_bytes(b"test content")

        # Raise a generic exception (not AuthError, not ApiError)
        mock_client.side_effect = RuntimeError("Something unexpected")

        with pytest.raises(Exception, match="Unexpected error"):
            upload_to_dropbox.apply(args=[str(test_file)], kwargs={"file_id": 1}).get()


# ---------------------------------------------------------------------------
# app/tasks/rotate_pdf_pages.py  (91 % → 95 %+)
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestDetermineRotationAngle:
    """Cover edge cases in determine_rotation_angle (line 29 & small angles)."""

    def test_very_small_angle_returns_zero(self):
        """Angles < 1° should return 0 (no rotation)."""
        from app.tasks.rotate_pdf_pages import determine_rotation_angle

        assert determine_rotation_angle(0.5) == 0

    def test_angle_close_to_360_returns_zero(self):
        """Angles close to 360° should return 0."""
        from app.tasks.rotate_pdf_pages import determine_rotation_angle

        assert determine_rotation_angle(359.5) == 0

    def test_negative_angle(self):
        """Negative angles should be normalised correctly."""
        from app.tasks.rotate_pdf_pages import determine_rotation_angle

        # -90 should normalise to 270 → rotation = (360-270)%360 = 90
        result = determine_rotation_angle(-90)
        assert result == 90

    def test_angle_45_rounds_to_nearest_90(self):
        """Non-standard angle rounds to nearest 90° multiple."""
        from app.tasks.rotate_pdf_pages import determine_rotation_angle

        # 45° rounds to 0° → rotation_value = (360-0)%360 = 0
        result = determine_rotation_angle(45)
        assert result == 0

    def test_angle_near_90(self):
        """Angle close to 90° should snap to 90°."""
        from app.tasks.rotate_pdf_pages import determine_rotation_angle

        # 88° is within ±5° of 90 → rotation = (360-90)%360 = 270
        result = determine_rotation_angle(88)
        assert result == 270

    def test_angle_near_180(self):
        """Angle close to 180° should snap to 180°."""
        from app.tasks.rotate_pdf_pages import determine_rotation_angle

        result = determine_rotation_angle(182)
        assert result == 180


@pytest.mark.unit
class TestRotatePdfPagesEdgeCases:
    """Cover rotate_pdf_pages edge cases: invalid data, small angles, no rotations."""

    @patch("app.tasks.rotate_pdf_pages.extract_metadata_with_gpt")
    @patch("app.tasks.rotate_pdf_pages.log_task_progress")
    @patch("app.tasks.rotate_pdf_pages.settings")
    def test_invalid_rotation_data_keys(self, mock_settings, mock_log, mock_extract, tmp_path):
        """Invalid rotation data keys/values trigger warning and are skipped (lines 94-95)."""
        from app.tasks.rotate_pdf_pages import rotate_pdf_pages

        workdir = tmp_path / "tmp"
        workdir.mkdir()
        mock_settings.workdir = str(tmp_path)

        pdf_path = workdir / "test.pdf"
        pdf_path.write_bytes(_minimal_pdf())

        mock_extract.delay = Mock()

        result = rotate_pdf_pages.apply(
            args=["test.pdf", "some text"],
            kwargs={"rotation_data": {"not_a_number": "invalid", 0: 0}, "file_id": 1},
        ).get()
        # With only invalid keys and a zero-angle, no rotation needed
        assert result["status"] == "no_rotation_needed"

    @patch("app.tasks.rotate_pdf_pages.extract_metadata_with_gpt")
    @patch("app.tasks.rotate_pdf_pages.log_task_progress")
    @patch("app.tasks.rotate_pdf_pages.settings")
    def test_rotation_angle_determined_as_zero(self, mock_settings, mock_log, mock_extract, tmp_path):
        """Page has rotation data but determine_rotation_angle returns 0 (line 144)."""
        from app.tasks.rotate_pdf_pages import rotate_pdf_pages

        workdir = tmp_path / "tmp"
        workdir.mkdir()
        mock_settings.workdir = str(tmp_path)

        pdf_path = workdir / "test.pdf"
        pdf_path.write_bytes(_minimal_pdf())

        mock_extract.delay = Mock()

        # Angle of 0.5° normalises to < 1° → rotation_angle = 0
        result = rotate_pdf_pages.apply(
            args=["test.pdf", "some text"],
            kwargs={"rotation_data": {0: 0.5}, "file_id": 1},
        ).get()
        # Rotation detected but not applied → "no_rotation_needed" with empty applied_rotations
        assert result["status"] == "no_rotation_needed"
        assert result["applied_rotations"] == {}

    @patch("app.tasks.rotate_pdf_pages.extract_metadata_with_gpt")
    @patch("app.tasks.rotate_pdf_pages.log_task_progress")
    @patch("app.tasks.rotate_pdf_pages.settings")
    def test_successful_rotation(self, mock_settings, mock_log, mock_extract, tmp_path):
        """Verify that a real rotation is applied to the PDF."""
        from app.tasks.rotate_pdf_pages import rotate_pdf_pages

        workdir = tmp_path / "tmp"
        workdir.mkdir()
        mock_settings.workdir = str(tmp_path)

        pdf_path = workdir / "test.pdf"
        pdf_path.write_bytes(_minimal_pdf())

        mock_extract.delay = Mock()

        result = rotate_pdf_pages.apply(
            args=["test.pdf", "some text"],
            kwargs={"rotation_data": {0: 90}, "file_id": 1},
        ).get()
        assert result["status"] == "rotated"
        assert "0" in result["applied_rotations"]


# ---------------------------------------------------------------------------
# app/tasks/convert_to_pdf.py  (91 % → 95 %+)
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestConvertToPdfDetection:
    """Cover detection helper edge cases."""

    def test_detect_extension_filetype_fallback(self, tmp_path):
        """Cover _detect_extension filetype.guess branch (line 97-99)."""
        from app.tasks.convert_to_pdf import _detect_extension

        # Create a file with no extension and no puremagic match
        unknown_file = tmp_path / "noext"
        unknown_file.write_bytes(b"\x00" * 100)

        with (
            patch("app.tasks.convert_to_pdf.puremagic.from_file", side_effect=__import__("puremagic").PureError),
            patch("app.tasks.convert_to_pdf.filetype.guess") as mock_guess,
        ):
            mock_result = Mock()
            mock_result.extension = "bin"
            mock_guess.return_value = mock_result
            ext = _detect_extension(str(unknown_file), None, None)
            assert ext == ".bin"

    def test_detect_extension_returns_empty(self, tmp_path):
        """Cover _detect_extension returning empty string when nothing matches."""
        from app.tasks.convert_to_pdf import _detect_extension

        unknown_file = tmp_path / "noext"
        unknown_file.write_bytes(b"\x00" * 100)

        with (
            patch("app.tasks.convert_to_pdf.puremagic.from_file", side_effect=__import__("puremagic").PureError),
            patch("app.tasks.convert_to_pdf.filetype.guess", return_value=None),
        ):
            ext = _detect_extension(str(unknown_file), None, None)
            assert ext == ""

    @patch("app.tasks.convert_to_pdf.requests")
    @patch("app.tasks.convert_to_pdf.process_document")
    @patch("app.tasks.convert_to_pdf.log_task_progress")
    def test_fallback_conversion_for_unknown_mime(self, mock_log, mock_process, mock_requests, tmp_path):
        """Cover the else/fallback branch at lines 300-304."""
        test_file = tmp_path / "unknown.xyz"
        test_file.write_bytes(b"random content")

        mock_response = Mock()
        mock_response.status_code = 200
        mock_response.content = b"%PDF-1.4 fake"
        mock_requests.post.return_value = mock_response

        mock_process.delay = Mock()

        with patch("app.tasks.convert_to_pdf.settings") as mock_settings:
            mock_settings.gotenberg_url = "http://localhost:3000"
            mock_settings.http_request_timeout = 30

            with (
                patch("app.tasks.convert_to_pdf._detect_mime_type", return_value=("application/octet-stream", None)),
                patch("app.tasks.convert_to_pdf._detect_extension", return_value=".xyz"),
            ):
                from app.tasks.convert_to_pdf import convert_to_pdf

                result = convert_to_pdf.apply(args=[str(test_file)]).get()
                expected_pdf = str(tmp_path / "unknown.pdf")
                assert result == expected_pdf

    @patch("app.tasks.convert_to_pdf.requests")
    @patch("app.tasks.convert_to_pdf.process_document")
    @patch("app.tasks.convert_to_pdf.log_task_progress")
    def test_gotenberg_non_200_response(self, mock_log, mock_process, mock_requests, tmp_path):
        """Cover the non-200 status code branch."""
        test_file = tmp_path / "test.docx"
        test_file.write_bytes(b"test content")

        mock_response = Mock()
        mock_response.status_code = 500
        mock_response.text = "Internal Server Error"
        mock_requests.post.return_value = mock_response

        with patch("app.tasks.convert_to_pdf.settings") as mock_settings:
            mock_settings.gotenberg_url = "http://localhost:3000"
            mock_settings.http_request_timeout = 30

            with (
                patch("app.tasks.convert_to_pdf._detect_mime_type", return_value=("application/octet-stream", None)),
                patch("app.tasks.convert_to_pdf._detect_extension", return_value=".xyz"),
            ):
                from app.tasks.convert_to_pdf import convert_to_pdf

                result = convert_to_pdf.apply(args=[str(test_file)]).get()
                assert result is None

    @patch("app.tasks.convert_to_pdf.requests")
    @patch("app.tasks.convert_to_pdf.process_document")
    @patch("app.tasks.convert_to_pdf.log_task_progress")
    def test_no_mime_type_and_no_extension(self, mock_log, mock_process, mock_requests, tmp_path):
        """Cover the branch where both mime_type and file_ext are empty."""
        test_file = tmp_path / "noext"
        test_file.write_bytes(b"test content")

        with patch("app.tasks.convert_to_pdf.settings") as mock_settings:
            mock_settings.gotenberg_url = "http://localhost:3000"
            mock_settings.http_request_timeout = 30

            mock_self = MagicMock()
            mock_self.request.id = "task-notype"

            with (
                patch("app.tasks.convert_to_pdf._detect_mime_type", return_value=(None, None)),
                patch("app.tasks.convert_to_pdf._detect_extension", return_value=""),
            ):
                from app.tasks.convert_to_pdf import convert_to_pdf

                result = convert_to_pdf.__wrapped__(mock_self, str(test_file))
                assert result is None

    def test_detect_mime_type_from_magic_filetype_fallback(self, tmp_path):
        """Cover _detect_mime_type_from_magic filetype.guess fallback (lines 36-39)."""
        from app.tasks.convert_to_pdf import _detect_mime_type_from_magic

        test_file = tmp_path / "testfile"
        test_file.write_bytes(b"\x00" * 100)

        with patch("app.tasks.convert_to_pdf.puremagic.from_file", return_value=[]):
            with patch("app.tasks.convert_to_pdf.filetype.guess") as mock_guess:
                mock_result = Mock()
                mock_result.mime = "application/octet-stream"
                mock_guess.return_value = mock_result
                result = _detect_mime_type_from_magic(str(test_file))
                assert result == "application/octet-stream"

    def test_detect_mime_type_from_magic_returns_none(self, tmp_path):
        """Cover _detect_mime_type_from_magic returning None."""
        from app.tasks.convert_to_pdf import _detect_mime_type_from_magic

        test_file = tmp_path / "testfile"
        test_file.write_bytes(b"\x00" * 100)

        with (
            patch("app.tasks.convert_to_pdf.puremagic.from_file", return_value=[]),
            patch("app.tasks.convert_to_pdf.filetype.guess", return_value=None),
        ):
            result = _detect_mime_type_from_magic(str(test_file))
            assert result is None

    def test_detect_mime_type_original_filename_fallback(self, tmp_path):
        """Cover _detect_mime_type using original_filename fallback (lines 58-62)."""
        from app.tasks.convert_to_pdf import _detect_mime_type

        # File with no extension
        test_file = tmp_path / "noext"
        test_file.write_bytes(b"content")

        # Original filename has extension
        mime, enc = _detect_mime_type(str(test_file), "document.pdf")
        assert mime == "application/pdf"

    def test_build_filename_with_extension(self, tmp_path):
        """Cover _build_filename when original has extension."""
        from app.tasks.convert_to_pdf import _build_filename

        result = _build_filename("/tmp/uuid123", "report.docx", ".docx")
        assert result == "report.docx"

    def test_build_filename_adds_extension(self, tmp_path):
        """Cover _build_filename when base name needs extension."""
        from app.tasks.convert_to_pdf import _build_filename

        result = _build_filename("/tmp/uuid123", None, ".pdf")
        assert result == "uuid123.pdf"


# ---------------------------------------------------------------------------
# app/utils/file_queries.py  (93 % → 95 %+)
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestFileQueriesDuplicateFilter:
    """Cover the 'duplicate' status filter (line 122)."""

    def test_apply_status_filter_duplicate(self, db_session):
        """Filtering by 'duplicate' returns only is_duplicate=True files."""
        from app.models import FileRecord
        from app.utils.file_queries import apply_status_filter

        # Create a normal file and a duplicate file
        normal = FileRecord(
            filehash="normal",
            original_filename="normal.pdf",
            local_filename="/tmp/normal.pdf",
            file_size=100,
            mime_type="application/pdf",
        )
        dup = FileRecord(
            filehash="dup",
            original_filename="dup.pdf",
            local_filename="/tmp/dup.pdf",
            file_size=100,
            mime_type="application/pdf",
            is_duplicate=True,
        )
        db_session.add_all([normal, dup])
        db_session.commit()

        query = db_session.query(FileRecord)
        results = apply_status_filter(query, db_session, "duplicate").all()

        assert len(results) == 1
        assert results[0].is_duplicate is True
        assert results[0].filehash == "dup"


@pytest.mark.unit
class TestFileQueriesDeduplicationEnabled:
    """Cover the enable_deduplication branch (line 81)."""

    @patch("app.config.settings")
    def test_deduplication_enabled_adds_check_for_duplicates(self, mock_settings, db_session):
        """When deduplication is enabled, check_for_duplicates is a real step."""
        from app.models import FileProcessingStep, FileRecord
        from app.utils.file_queries import apply_status_filter

        mock_settings.enable_deduplication = True

        file1 = FileRecord(
            filehash="h1",
            original_filename="f1.pdf",
            local_filename="/tmp/f1.pdf",
            file_size=100,
            mime_type="application/pdf",
        )
        db_session.add(file1)
        db_session.flush()

        for step_name in ("check_for_duplicates", "send_to_all_destinations"):
            step = FileProcessingStep(
                file_id=file1.id,
                step_name=step_name,
                status="success",
            )
            db_session.add(step)
        db_session.commit()

        query = db_session.query(FileRecord)
        results = apply_status_filter(query, db_session, "completed").all()
        assert len(results) == 1


# ---------------------------------------------------------------------------
# app/api/url_upload.py  (91 % → 95 %+)
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestURLUploadAdditionalCoverage:
    """Cover remaining branches in url_upload."""

    def test_validate_url_safety_invalid_scheme(self):
        """Cover scheme rejection in validate_url_safety (line 86)."""
        from fastapi import HTTPException

        from app.api.url_upload import validate_url_safety

        with pytest.raises(HTTPException) as exc_info:
            validate_url_safety("ftp://example.com/file.pdf")
        assert exc_info.value.status_code == 400
        assert "HTTP" in exc_info.value.detail

    def test_validate_url_safety_blocks_gcp_metadata(self):
        """Cover metadata endpoint blocking for GCP (line 106)."""
        from fastapi import HTTPException

        from app.api.url_upload import validate_url_safety

        with pytest.raises(HTTPException) as exc_info:
            validate_url_safety("http://metadata.google.internal/computeMetadata/v1/")
        assert exc_info.value.status_code == 400

    def test_is_private_ip_unresolvable_hostname(self):
        """Cover DNS resolution failure branch (lines 67-72)."""
        import socket as _socket

        from app.utils.network import is_private_ip

        with patch("socket.getaddrinfo", side_effect=_socket.gaierror("nope")):
            result = is_private_ip("nonexistent.invalid.hostname.test")
            assert result is False

    def test_is_private_ip_hostname_resolves_to_private(self):
        """Cover branch where hostname resolves to a private IP (line 64-65)."""
        from app.utils.network import is_private_ip

        with patch("socket.getaddrinfo") as mock_gai:
            # Simulate resolving to a private IP
            mock_gai.return_value = [
                (2, 1, 6, "", ("192.168.1.1", 0)),
            ]
            result = is_private_ip("evil.internal.corp")
            assert result is True

    def test_validate_file_type_no_content_type_no_extension(self):
        """Cover the path where both content_type and extension are empty."""
        from app.api.url_upload import validate_file_type

        assert validate_file_type("", "noextfile") is False

    @patch("app.api.url_upload.httpx.AsyncClient.stream")
    @patch("app.api.url_upload.process_document")
    def test_process_url_empty_url_path(self, mock_process, mock_stream, client):
        """URL with empty path defaults to 'download' filename (line 197-202)."""
        mock_response = AsyncMock()
        mock_response.status_code = 200
        mock_response.headers = {"Content-Type": "application/pdf", "Content-Length": "50"}

        async def mock_aiter_bytes(chunk_size=None):
            yield b"PDF"

        mock_response.aiter_bytes = mock_aiter_bytes
        mock_response.raise_for_status = Mock()

        mock_context = AsyncMock()
        mock_context.__aenter__.return_value = mock_response
        mock_stream.return_value = mock_context

        mock_task = Mock()
        mock_task.id = "task-empty-path"
        mock_process.delay.return_value = mock_task

        response = client.post("/api/process-url", json={"url": "https://example.com/"})
        assert response.status_code == 200

    def test_validate_url_safety_blocks_aws_link_local(self):
        """Cover metadata endpoint 169.254.169.253 (line 103)."""
        from fastapi import HTTPException

        from app.api.url_upload import validate_url_safety

        with pytest.raises(HTTPException) as exc_info:
            validate_url_safety("http://169.254.169.253/something")
        assert exc_info.value.status_code == 400


# ---------------------------------------------------------------------------
# app/main.py  (92 % → 95 %+)
# ---------------------------------------------------------------------------
@pytest.mark.unit
class TestMainAppCoverage:
    """Cover remaining branches in app/main.py."""

    @pytest.mark.asyncio
    async def test_lifespan_no_config_issues(self):
        """Cover the branch where config has no issues (line 92)."""
        with (
            patch("app.database.init_db"),
            patch("app.database.SessionLocal") as mock_session_cls,
            patch("app.utils.config_loader.load_settings_from_db"),
            patch("app.utils.config_validator.dump_all_settings"),
            patch(
                "app.main.check_all_configs",
                return_value={"email": [], "storage": {}},
            ),
            patch("app.main.init_apprise"),
            patch("app.main.notify_startup"),
            patch("app.main.notify_shutdown"),
            patch("app.utils.ocr_language_manager.ensure_ocr_languages_async"),
        ):
            mock_db = MagicMock()
            mock_session_cls.return_value = mock_db

            import logging as _logging

            from app.main import app, lifespan

            with patch.object(_logging, "info") as mock_info:
                async with lifespan(app):
                    pass

                # Verify "valid configuration" was logged
                calls = [str(c) for c in mock_info.call_args_list]
                assert any("valid configuration" in c for c in calls)

    @pytest.mark.asyncio
    async def test_lifespan_with_storage_issues(self):
        """Cover the branch where storage config has issues (line 89-90)."""
        with (
            patch("app.database.init_db"),
            patch("app.database.SessionLocal") as mock_session_cls,
            patch("app.utils.config_loader.load_settings_from_db"),
            patch("app.utils.config_validator.dump_all_settings"),
            patch("app.main.check_all_configs") as mock_check,
            patch("app.main.init_apprise"),
            patch("app.main.notify_startup"),
            patch("app.main.notify_shutdown"),
            patch("app.utils.ocr_language_manager.ensure_ocr_languages_async"),
        ):
            mock_db = MagicMock()
            mock_session_cls.return_value = mock_db
            mock_check.return_value = {
                "email": [],
                "storage": {"s3": ["Missing bucket name"]},
            }

            import logging as _logging

            from app.main import app, lifespan

            with patch.object(_logging, "warning") as mock_warning:
                async with lifespan(app):
                    pass

                # Should log warning about config issues
                calls = [str(c) for c in mock_warning.call_args_list]
                assert any("configuration issues" in c for c in calls)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _minimal_pdf() -> bytes:
    """Return bytes for a minimal valid PDF with one page."""
    return b"""%PDF-1.4
1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj
<<
/Type /Page
/Parent 2 0 R
/MediaBox [0 0 612 792]
>>
endobj
xref
0 4
0000000000 65535 f\x20
0000000009 00000 n\x20
0000000058 00000 n\x20
0000000115 00000 n\x20
trailer
<<
/Size 4
/Root 1 0 R
>>
startxref
197
%%EOF
"""
