CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 19s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 44s
Operator's call, and it corrects a scope error rather than a bug: "this is a path for a user to go from a rulebook to a design system. we don't need to build this path in the app itself ... you should be the one that does the import ... going forward no one else should have to do such a migration." Right. Nobody starting from a design system will ever go rulebook -> system, so the whole path was permanent product code serving a single act on one install. Rule #22: remove it, don't flag it off. Gone from the service, the REST route, the MCP tool, the UI panel, the API client and its tests. There is a second consequence I had missed, and it is the better argument. The parser was WORSE at this than doing it by hand. `propose_tokens` leaves radius steps and type sizes valueless because "Small 4px" is not a hex and nothing here parses it — a limitation I documented carefully and shipped anyway. But that limitation only exists because the importer had to run unattended. Done as work rather than as a feature, those values are just read and written, and the result is a complete design system instead of one with a dozen blanks and a count explaining them. Scaffolding built around my own absence from the loop, when I am the loop. KEPT: `extract_expectations` and `design_expectations` in services/design_rulebook_import.py. The live drift panel still reads them until it is repointed at a resolved design system (#2295), and removing them now would take the /design page's only content with it. They go with that change, not this one.
119 lines
4.7 KiB
Python
119 lines
4.7 KiB
Python
"""Structural tests for the design-systems blueprint + the parity guard.
|
|
|
|
Modelled on tests/test_routes_snippets.py. The enumerations below are
|
|
deliberate: relaxing one to a pattern would stop it catching the thing it was
|
|
written for, which is a capability landing on one surface and not the other.
|
|
"""
|
|
import inspect
|
|
|
|
|
|
def test_design_systems_blueprint_registered():
|
|
from scribe.routes.design_systems import design_systems_bp
|
|
assert design_systems_bp.name == "design_systems"
|
|
assert design_systems_bp.url_prefix == "/api"
|
|
|
|
|
|
def test_design_systems_blueprint_registered_in_app():
|
|
from scribe.app import create_app
|
|
app = create_app()
|
|
assert "design_systems" in app.blueprints
|
|
|
|
|
|
def test_route_handlers_callable():
|
|
from scribe.routes import design_systems as routes
|
|
for name in (
|
|
"list_design_systems", "create_design_system", "get_design_system",
|
|
"update_design_system", "delete_design_system", "resolve_design_system",
|
|
"get_design_system_stylesheet",
|
|
"check_snippets_against_system",
|
|
"list_design_tokens", "create_design_token",
|
|
"update_design_token", "delete_design_token", "set_project_design_system",
|
|
):
|
|
assert callable(getattr(routes, name))
|
|
|
|
|
|
def test_every_endpoint_is_reachable_on_the_app():
|
|
"""The handlers existing is not the same as them being routed. This catches
|
|
a decorator that was copied without its path changing — two handlers on one
|
|
rule, where the second silently never runs."""
|
|
from scribe.app import create_app
|
|
app = create_app()
|
|
rules = {
|
|
str(r.rule) for r in app.url_map.iter_rules()
|
|
if r.endpoint.startswith("design_systems.")
|
|
}
|
|
assert rules == {
|
|
"/api/design-systems",
|
|
"/api/design-systems/<int:design_system_id>",
|
|
"/api/design-systems/<int:design_system_id>/resolved",
|
|
"/api/design-systems/<int:design_system_id>/stylesheet",
|
|
"/api/design-systems/<int:design_system_id>/snippet-check",
|
|
"/api/design-systems/<int:design_system_id>/tokens",
|
|
"/api/design-tokens/<int:token_id>",
|
|
"/api/projects/<int:project_id>/design-system",
|
|
}
|
|
|
|
|
|
def test_service_functions_take_user_id():
|
|
"""Routes must call the services with user_id — verify the contract (#33)."""
|
|
from scribe.services import design_systems as svc
|
|
for fn_name in (
|
|
"create_design_system", "list_design_systems", "get_design_system",
|
|
"update_design_system", "delete_design_system", "resolve_design_system",
|
|
"create_token", "list_tokens", "update_token", "delete_token",
|
|
"set_project_design_system",
|
|
"stylesheet_for_system", "check_snippets_against_system",
|
|
):
|
|
fn = getattr(svc, fn_name)
|
|
assert callable(fn)
|
|
assert "user_id" in inspect.signature(fn).parameters
|
|
|
|
|
|
def test_agent_and_web_surfaces_stay_at_parity():
|
|
"""The MCP tools and the REST routes are two callers of one service; a
|
|
capability on one has to exist on the other (rule #33).
|
|
|
|
This guard exists because the snippet surfaces drifted apart once — MCP had
|
|
no delete, the web side had no near-duplicate gate — and neither failed
|
|
anything until someone went looking.
|
|
"""
|
|
from scribe.mcp.tools import design_systems as tools
|
|
from scribe.routes import design_systems as routes
|
|
|
|
for name in (
|
|
"create_design_system", "list_design_systems", "get_design_system",
|
|
"resolve_design_system", "update_design_system", "delete_design_system",
|
|
"create_design_token", "list_design_tokens", "update_design_token",
|
|
"delete_design_token", "set_project_design_system",
|
|
"get_design_system_stylesheet",
|
|
):
|
|
assert callable(getattr(tools, name)), f"MCP tool missing: {name}"
|
|
assert callable(getattr(routes, name)), f"REST route missing: {name}"
|
|
|
|
# The snippet check is the one verb whose handler names differ between the
|
|
# surfaces (the tool says what it checks AGAINST; the route is already under
|
|
# the system), so the loop above can't pair it. It still has to exist on both.
|
|
assert callable(tools.check_snippets_against_design_system)
|
|
assert callable(routes.check_snippets_against_system)
|
|
|
|
|
|
def test_every_mcp_tool_in_the_module_is_registered():
|
|
"""A tool written but never registered is invisible to an agent, and nothing
|
|
else in the codebase would notice."""
|
|
from scribe.mcp.tools import design_systems as tools
|
|
|
|
registered = []
|
|
|
|
class _Recorder:
|
|
def tool(self, name):
|
|
registered.append(name)
|
|
return lambda fn: fn
|
|
|
|
tools.register(_Recorder())
|
|
|
|
public = {
|
|
name for name, obj in vars(tools).items()
|
|
if inspect.iscoroutinefunction(obj) and not name.startswith("_")
|
|
}
|
|
assert set(registered) == public
|