"""Tests for the hard wall-clock deadline wrapper around blocking LLM calls.

litellm's ``timeout`` is per-read (httpx), so a server slow-streaming a heavy
gpt-5.5 reasoning/vision response can exceed it indefinitely (observed: 180s
timeout, 21min hang). ``call_with_deadline`` enforces a TOTAL wall-clock
deadline so a hung call fails fast → caller marks that unit partial.
"""
from __future__ import annotations

import time

import pytest

from app.modules.pipeline.llm_deadline import (
    HardDeadlineExceeded,
    call_with_deadline,
)


def test_returns_value_when_fast():
    """A call that finishes within the deadline returns its value."""
    out = call_with_deadline(lambda: 42, deadline_seconds=5)
    assert out == 42


def test_passes_args_and_kwargs():
    out = call_with_deadline(lambda a, b=0: a + b, 3, deadline_seconds=5, b=4)
    assert out == 7


def test_raises_hard_deadline_when_slow():
    """A call that exceeds the deadline raises HardDeadlineExceeded fast,
    not after the full sleep — the wrapper returns ~at the deadline."""
    t0 = time.time()
    with pytest.raises(HardDeadlineExceeded):
        call_with_deadline(lambda: time.sleep(30), deadline_seconds=1)
    # returned near the deadline, NOT after the full 30s sleep
    assert time.time() - t0 < 5


def test_propagates_callee_exception():
    """An exception raised inside the call propagates unchanged (not masked
    as a deadline error)."""
    class Boom(Exception):
        pass

    with pytest.raises(Boom):
        call_with_deadline(_raise(Boom), deadline_seconds=5)


def _raise(exc):
    def _fn():
        raise exc("boom")
    return _fn
