"""Structural tests for the snippets blueprint — registration + handler/service contracts. Full HTTP integration needs a live DB + auth the unit env lacks.""" import inspect def test_snippets_blueprint_registered(): from scribe.routes.snippets import snippets_bp assert snippets_bp.name == "snippets" assert snippets_bp.url_prefix == "/api/snippets" def test_snippets_blueprint_registered_in_app(): from scribe.app import create_app app = create_app() assert "snippets" in app.blueprints def test_snippet_handlers_callable(): from scribe.routes import snippets as routes for name in ( "list_snippets_route", "create_snippet_route", "get_snippet_route", "update_snippet_route", "delete_snippet_route", "merge_snippet_route", ): assert callable(getattr(routes, name)) def test_service_functions_take_user_id(): """Routes must call snippet services with user_id — verify the contract.""" from scribe.services import snippets as svc for fn_name in ( "create_snippet", "list_snippets", "get_snippet", "update_snippet", "delete_snippet", "merge_snippets", ): 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 they drifted apart once: MCP had no delete or `locations`, and the web side had no `system_ids` and no near-duplicate gate.""" from scribe.mcp.tools import snippets as tools from scribe.routes import snippets as routes # Every write verb the web surface offers, the agent surface offers too. for verb in ("create", "get", "list", "update", "delete", "merge"): assert callable(getattr(tools, f"{verb}_snippets", None) or getattr(tools, f"{verb}_snippet", None)), f"MCP lacks {verb}" # Multi-location records are reachable from both. assert "locations" in inspect.signature(tools.create_snippet).parameters assert "locations" in inspect.signature(tools.update_snippet).parameters assert "locations" in inspect.getsource(routes.create_snippet_route) assert "locations" in inspect.getsource(routes.update_snippet_route) # System association and the duplicate gate reach both. assert "system_ids" in inspect.signature(tools.create_snippet).parameters for route in (routes.create_snippet_route, routes.update_snippet_route): assert "system_ids" in inspect.getsource(route) assert "find_duplicate_note" in inspect.getsource(routes.create_snippet_route) def test_project_scoping_reaches_every_caller(): """A snippet search has to be narrowable to one project from both surfaces.""" from scribe.mcp.tools import snippets as tools from scribe.routes import snippets as routes from scribe.services import snippets as svc assert "project_id" in inspect.signature(svc.list_snippets).parameters assert "project_id" in inspect.signature(tools.list_snippets).parameters assert "project_id" in inspect.getsource(routes.list_snippets_route) def test_update_field_map_matches_service_kwargs(): """Every field the PATCH route forwards must be a real update_snippet kwarg (rule #33 interface-contract parity).""" from scribe.routes import snippets as routes from scribe.services import snippets as svc params = inspect.signature(svc.update_snippet).parameters for field in routes._STR_FIELDS: assert field in params, f"update_snippet has no '{field}' kwarg" assert "tags" in params assert "project_id" in params