"""Regression: langfuse_client surface + span context manager behaviour. Bugs captured: 1. AttributeError: module 'api.langfuse_client' has no attribute 'span' — the module was at the old observability.py shape with `langfuse_span`. 2. RuntimeError: generator didn't stop after throw() — the @contextmanager wrapped the user's `with` body in `try/except` and yielded a second value on exception, which @contextmanager rejects. """ import pytest from api import langfuse_client as lf def test_public_surface(): assert callable(lf.span) assert callable(lf.flush) assert callable(lf.get_client) def test_span_returns_nullspan_without_keys(monkeypatch): # Ensure no keys → no client → null span. from api.config import get_settings get_settings.cache_clear() monkeypatch.setenv("LANGFUSE_PUBLIC_KEY", "") monkeypatch.setenv("LANGFUSE_SECRET_KEY", "") # Reset cached client too lf._client = None with lf.span("t") as s: assert isinstance(s, lf._NullSpan) s.update(output={"x": 1}) # no-op should not raise get_settings.cache_clear() def test_span_body_exception_propagates(): """The body's exception MUST propagate — wrapping it inside @contextmanager causes 'generator didn't stop after throw()' if the except clause yields.""" with pytest.raises(ValueError, match="kaboom"): with lf.span("t"): raise ValueError("kaboom")