Files
autocad-mcp-pro/tests/test_sprint4_server.py
T
Umutcan EdizsalanandCursor 9ec6c4a989 style: apply ruff format across the repository
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>
2026-07-23 15:14:51 +03:00

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