Files
fastmcp-feedback/tests/unit/test_base_install.py
T
rsp2k 4995143f8f Import instrumentation without the database extras
storage, embeddings and backfill need sqlalchemy[asyncio] (greenlet). The
package __init__ now loads their names on first access and raises an
ImportError naming the extra when they are missing. The middleware finds a
DatabaseSink or EmbeddingSink through sys.modules instead of importing them.
Table prefix, embedding dim, sources and event text keys move to constants.py
so they import on a base install.
2026-10-01 20:52:33 -06:00

91 lines
2.8 KiB
Python

"""The instrumentation works on a base install, without the database extras.
The worker runs in a fresh interpreter with greenlet and aiosqlite made
unimportable, which is what ``pip install fastmcp-feedback`` leaves out.
"""
import json
import subprocess
import sys
from pathlib import Path
import pytest
WORKER = Path(__file__).parent.parent / "fixtures" / "base_install_worker.py"
@pytest.fixture(scope="module")
def report(tmp_path_factory) -> dict:
proc = subprocess.run(
[sys.executable, str(WORKER), str(tmp_path_factory.mktemp("base"))],
capture_output=True,
text=True,
timeout=60,
)
assert proc.returncode == 0, proc.stderr[-4000:]
return json.loads(proc.stdout.strip().splitlines()[-1])
def test_tool_calls_are_recorded(report):
assert report["result"] == "5"
assert report["tools"] == ["add", "submit_feedback"]
assert report["jsonl_lines"] == 4 # two calls, two events
def test_events_are_recorded(report):
assert report["event_kinds"] == ["feedback.submitted", "job.done"]
assert report["events_for"] == 1
def test_feedback_tools_work_alongside(report):
assert report["feedback"]["success"] is True
assert report["feedback"]["linked_calls"] == 0
def test_database_helpers_return_empty_defaults(report):
assert report["capture"] == []
assert report["similar"] == []
assert report["similar_feedback"] == []
assert report["feedback_context"] == []
assert report["link_feedback"] is False
assert report["database_sink"] is True
assert report["embedding_sink"] is True
def test_constants_import_without_the_extra(report):
assert report["prefix"] == "ffb_"
def test_storage_modules_stay_unloaded(report):
assert report["loaded"] == []
@pytest.mark.parametrize(
("name", "extra"),
[
("DatabaseSink", "[instrumentation]"),
("build_metadata", "[instrumentation]"),
("EmbeddingSink", "[instrumentation,embeddings]"),
("backfill_feedback", "[instrumentation,embeddings]"),
],
)
def test_database_names_explain_the_missing_extra(report, name, extra):
# Asked twice: SQLAlchemy reports a missing greenlet only on first import.
for message in report["errors"][name]:
assert message is not None
assert "greenlet" in message
assert f'uv add "fastmcp-feedback{extra}"' in message
def test_lazy_names_resolve_with_the_extra():
import fastmcp_feedback.instrumentation as inst
from fastmcp_feedback.instrumentation import DatabaseSink, storage
assert DatabaseSink is storage.DatabaseSink
assert set(inst._LAZY) <= set(inst.__all__)
assert set(inst.__all__) <= set(dir(inst))
for name in inst.__all__:
assert getattr(inst, name) is not None
with pytest.raises(AttributeError):
_ = inst.not_a_name