"""빈 응답은 **타입과 이유 칸**을 달고 올라간다 (2026-09-19).

S48sh5 의 도면 확인이 같은 샷에서 여러 번 연속 빈 응답으로 죽었는데,
오류문에 이유가 없어서 「아이 그림이라 검열」로 잘못 읽었다(Codex 지적 —
확인되지 않은 원인이었다). 그리고 빈 응답만 골라 둘째 관찰자로 넘기려면
**타입**이 있어야 한다 — 오류문 글자로 가르지 않는다.

바깥 SDK 대역은 그 SDK 의 응답 타입(`litellm.ModelResponse`)으로 만든다.
"""
from __future__ import annotations

import pytest


def _empty_response(finish_reason):
    import litellm

    return litellm.ModelResponse(
        model="gemini/gemini-3.1-pro-preview",
        choices=[{"index": 0, "finish_reason": finish_reason,
                  "message": {"role": "assistant", "content": None}}])


def _drive(monkeypatch, finish_reason):
    from app.modules.llm import llm_client

    monkeypatch.setattr(llm_client, "_get_router_binding", lambda: object())
    monkeypatch.setattr(llm_client, "_completion",
                        lambda b, m, kw: _empty_response(finish_reason))
    with pytest.raises(llm_client.EmptyLLMResponse) as ei:
        llm_client.call_structured(
            step="confined_fp_readback_SAMPLE", system_prompt="s",
            user_prompt="u", response_schema={"type": "object"},
            schema_name="t", enable_fallback=False)
    return ei.value


def test_empty_content_raises_typed_error_with_the_reason(monkeypatch):
    err = _drive(monkeypatch, "content_filter")
    assert "finish_reason='content_filter'" in str(err)


def test_old_catchers_still_catch_it(monkeypatch):
    """예전에 `RuntimeError`·「empty response」 글자로 잡던 자리가 그대로 잡는다
    — `enable_fallback=True` 경로의 안전 분류가 이것으로 갈린다."""
    from app.modules.llm.safety import is_safety_related_error

    err = _drive(monkeypatch, "stop")
    assert isinstance(err, RuntimeError)
    assert "empty response" in str(err)
    assert is_safety_related_error(err)
