108 lines
3.4 KiB
Python
108 lines
3.4 KiB
Python
"""notebooks/llm_search.py — structure and headless degradation (#573)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
NB = Path(__file__).resolve().parents[2] / "notebooks" / "llm_search.py"
|
|
|
|
|
|
def _load():
|
|
spec = importlib.util.spec_from_file_location("llm_search_nb", NB)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
def test_notebook_is_a_marimo_app():
|
|
mod = _load()
|
|
assert mod.app.__class__.__name__ == "App"
|
|
|
|
|
|
def test_cells_are_anonymous():
|
|
src = NB.read_text()
|
|
tree = ast.parse(src)
|
|
names = [n.name for n in tree.body if isinstance(n, ast.FunctionDef)]
|
|
assert names and set(names) == {"_"}
|
|
|
|
|
|
def test_headless_run_degrades_without_service_or_data(monkeypatch):
|
|
"""With the llm service unreachable and no replica/bib, every guard cell
|
|
should render a note instead of raising."""
|
|
mod = _load()
|
|
|
|
import conf.connect as cc
|
|
from llm.client import LlmClient
|
|
|
|
monkeypatch.setattr(LlmClient, "health", lambda self: False)
|
|
monkeypatch.setattr(
|
|
cc,
|
|
"duckdb",
|
|
lambda *a, **k: (_ for _ in ()).throw(FileNotFoundError("no replica")),
|
|
)
|
|
monkeypatch.setattr(
|
|
cc, "bib", lambda *a, **k: (_ for _ in ()).throw(FileNotFoundError("no bib"))
|
|
)
|
|
|
|
outputs, _defs = mod.app.run()
|
|
assert outputs is not None
|
|
rendered = "\n".join(o._repr_html_() for o in outputs if hasattr(o, "_repr_html_"))
|
|
assert "Traceback" not in rendered
|
|
assert "llm service unreachable" in rendered
|
|
assert "replica unavailable" in rendered
|
|
assert "bibliography unavailable" in rendered
|
|
|
|
|
|
def test_headless_run_still_renders_with_service_up_but_no_data(monkeypatch):
|
|
"""Health true but no replica/bib and no real search results (network
|
|
still stubbed out) — the search cell should degrade to "no hits", not
|
|
raise, and the duckdb cell should still show "replica unavailable"."""
|
|
mod = _load()
|
|
|
|
import conf.connect as cc
|
|
from llm.client import LlmClient
|
|
|
|
monkeypatch.setattr(LlmClient, "health", lambda self: True)
|
|
monkeypatch.setattr(
|
|
LlmClient,
|
|
"search",
|
|
lambda self, *a, **k: (_ for _ in ()).throw(
|
|
__import__("httpx").ConnectError("refused")
|
|
),
|
|
)
|
|
monkeypatch.setattr(
|
|
cc,
|
|
"duckdb",
|
|
lambda *a, **k: (_ for _ in ()).throw(FileNotFoundError("no replica")),
|
|
)
|
|
monkeypatch.setattr(
|
|
cc, "bib", lambda *a, **k: (_ for _ in ()).throw(FileNotFoundError("no bib"))
|
|
)
|
|
|
|
outputs, _defs = mod.app.run()
|
|
rendered = "\n".join(o._repr_html_() for o in outputs if hasattr(o, "_repr_html_"))
|
|
assert "Traceback" not in rendered
|
|
assert "replica unavailable" in rendered
|
|
|
|
|
|
_ROOT = Path(__file__).resolve().parents[2]
|
|
_HAS_DATA = (_ROOT / "data" / "replica" / "aco.ro.duckdb").exists() and (
|
|
_ROOT / "data" / "bib.sqlite"
|
|
).exists()
|
|
|
|
|
|
@pytest.mark.skipif(not _HAS_DATA, reason="needs the live replica and bib")
|
|
def test_docket_picker_offers_known_dockets_against_real_bib(monkeypatch):
|
|
"""Live smoke test: with the real bib store, the docket dropdown's
|
|
options come from ``store.dockets()`` and are non-empty."""
|
|
from llm.client import LlmClient
|
|
|
|
monkeypatch.setattr(LlmClient, "health", lambda self: False)
|
|
mod = _load()
|
|
outputs, _defs = mod.app.run()
|
|
assert outputs is not None
|