ruff format was never run repo-wide; CI now enforces 'ruff format --check', so normalize every module once. No behavior changes. Co-authored-by: Cursor <cursoragent@cursor.com>
148 lines
5.2 KiB
Python
148 lines
5.2 KiB
Python
"""Regression tests for the Sprint-4 server.py fixes.
|
|
|
|
* R20 — _registered_tool_count() uses the public FastMCP accessor
|
|
(mcp._list_tools) and returns a real positive count, never -1/None.
|
|
* R15 — system_about's tool_groups is derived dynamically from each tool's
|
|
tags: it now includes the engineering/premium groups and no longer
|
|
misfiles entity_delete_many under entity_creation.
|
|
* N8 — drawing_plan docstring no longer claims the PlanSpec is "replayed by
|
|
drawing_critique".
|
|
|
|
These tests drive server.py's underlying async functions directly with a
|
|
minimal fake ctx (lifespan_context + async info/warning), the same pattern the
|
|
other sprint test files use. They are ezdxf-backend-agnostic.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
class _Ctx:
|
|
"""Minimal async context mimicking fastmcp.Context for direct tool calls."""
|
|
|
|
def __init__(self, backend):
|
|
self.lifespan_context = {"backend": backend}
|
|
|
|
async def info(self, message):
|
|
pass
|
|
|
|
async def warning(self, message):
|
|
pass
|
|
|
|
|
|
# ── R20 — tool count comes from the public accessor and is a real number ─────
|
|
|
|
|
|
async def test_registered_tool_count_is_positive_never_minus_one():
|
|
import server
|
|
|
|
count = await server._registered_tool_count()
|
|
assert count is not None # never the old sentinel state
|
|
assert isinstance(count, int)
|
|
assert count != -1 # the old silent-failure value must never surface
|
|
assert count > 50 # the server registers dozens of tools
|
|
|
|
|
|
async def test_registered_tools_returns_objects_with_name_and_tags():
|
|
import server
|
|
|
|
tools = await server._registered_tools()
|
|
assert len(tools) > 50
|
|
# Every entry is a real tool object usable for tag-based grouping.
|
|
for t in tools:
|
|
assert getattr(t, "name", None)
|
|
# tags is a set (possibly empty) on FunctionTool
|
|
assert hasattr(t, "tags")
|
|
|
|
|
|
# ── R15 — system_about tool_groups is dynamic + correct ──────────────────────
|
|
|
|
|
|
async def test_system_about_includes_engineering_group(backend):
|
|
import server
|
|
|
|
about = await server.system_about(ctx=_Ctx(backend))
|
|
groups = about["tool_groups"]
|
|
|
|
# An engineering tool must appear somewhere in the breakdown (the old
|
|
# static dict omitted all engineering tools entirely).
|
|
all_listed = {name for names in groups.values() for name in names}
|
|
assert "gear_draw_section_aa" in all_listed
|
|
# The engineering group itself is present and non-empty.
|
|
assert groups.get("engineering")
|
|
assert "gear_draw_section_aa" in groups["engineering"]
|
|
|
|
|
|
async def test_system_about_includes_premium_group(backend):
|
|
import server
|
|
|
|
about = await server.system_about(ctx=_Ctx(backend))
|
|
groups = about["tool_groups"]
|
|
all_listed = {name for names in groups.values() for name in names}
|
|
# Premium meta-tools (e.g. drawing_plan) used to be missing from the dict.
|
|
assert "drawing_plan" in all_listed
|
|
assert groups.get("premium")
|
|
|
|
|
|
async def test_entity_delete_many_not_under_entity_creation(backend):
|
|
import server
|
|
|
|
about = await server.system_about(ctx=_Ctx(backend))
|
|
groups = about["tool_groups"]
|
|
# The bug: entity_delete_many was hardcoded under entity_creation.
|
|
assert "entity_delete_many" not in groups.get("entity_creation", [])
|
|
# It is tagged {"entity", "modify"} so it belongs with modification.
|
|
assert "entity_delete_many" in groups.get("entity_modification", [])
|
|
|
|
|
|
async def test_system_about_total_tools_matches_count(backend):
|
|
import server
|
|
|
|
about = await server.system_about(ctx=_Ctx(backend))
|
|
count = await server._registered_tool_count()
|
|
# total_tools is reported dynamically (never hardcoded) and agrees with
|
|
# the count helper.
|
|
assert about.get("total_tools") == count
|
|
assert about["total_tools"] > 50
|
|
|
|
# The sum of all grouped tools equals the total (every tool buckets once).
|
|
grouped = sum(len(v) for v in about["tool_groups"].values())
|
|
assert grouped == about["total_tools"]
|
|
|
|
|
|
async def test_drawing_close_appears_in_groups(backend):
|
|
import server
|
|
|
|
about = await server.system_about(ctx=_Ctx(backend))
|
|
all_listed = {name for names in about["tool_groups"].values() for name in names}
|
|
# drawing_close was omitted by the old static dict.
|
|
assert "drawing_close" in all_listed
|
|
|
|
|
|
# ── R20 — system_status reports a real tool_count, never -1 ──────────────────
|
|
|
|
|
|
async def test_system_status_reports_real_tool_count(backend):
|
|
import server
|
|
|
|
status = await server.system_status(ctx=_Ctx(backend))
|
|
# tool_count is present and a real positive number (omitted only if unknown).
|
|
assert status.get("tool_count") is not None
|
|
assert status["tool_count"] != -1
|
|
assert status["tool_count"] > 50
|
|
|
|
|
|
# ── N8 — drawing_plan docstring corrected ────────────────────────────────────
|
|
|
|
|
|
async def test_drawing_plan_docstring_not_misleading():
|
|
import server
|
|
|
|
doc = server.drawing_plan.__doc__ or ""
|
|
# The corrected docstring must not claim the plan is replayed as a critique.
|
|
assert "replayed by `drawing_critique`" not in doc
|
|
assert "stored on the backend" in doc
|