Files
ss-tools/agent/tests/test_agent/test_feature_035_contracts.py
busya b95df37cd5 refactor(agent): extract agent+shared into standalone packages with full GRACE semantic markup
- Move agent code from backend/src/agent/ to agent/src/ss_tools/agent/
- Extract shared stdlib-only utilities to shared/src/ss_tools/shared/
- Add #region/#endregion contracts to all ~140 functions (INV_1 compliance)
- Update docker files, entrypoint, build scripts for new package layout
- Backend now imports ss_tools.shared._llm_health (no gradio/langchain deps)
- Add specs for 036-039 feature plans
2026-07-07 15:18:24 +03:00

83 lines
3.2 KiB
Python

# #region Test.Agent.Feature035 [C:3] [TYPE Module] [SEMANTICS test,agent-chat,context,tools,rbac]
# @BRIEF Contract tests for feature 035 context filtering, invocation RBAC, and tool response summarisation.
# @RELATION BINDS_TO -> [AgentChat.ToolFilter]
# @RELATION BINDS_TO -> [AgentChat.Tools]
# @TEST_EDGE: dashboard_context_admin -> Dashboard affinity keeps dashboard tools plus mandatory capabilities.
# @TEST_EDGE: dashboard_context_viewer -> RBAC removes admin-only dashboard tools.
# @TEST_EDGE: invocation_guard_denied -> Mutating tool rejects before HTTP side effect.
import pytest
from types import SimpleNamespace
from ss_tools.agent._tool_filter import build_tool_pipeline
from ss_tools.agent.context import set_user_role
from ss_tools.agent.tools import _guard_tool_permission, _summarise_response
def _tools(names: list[str]) -> list[SimpleNamespace]:
return [SimpleNamespace(name=name) for name in names]
# #region test_dashboard_context_filters_tools [C:2] [TYPE Function]
# @BRIEF Dashboard context keeps only dashboard-affinity tools and mandatory capabilities.
def test_dashboard_context_filters_tools():
tools = _tools([
"search_dashboards",
"get_health_summary",
"deploy_dashboard",
"superset_execute_sql",
"run_backup",
"show_capabilities",
])
result = [tool.name for tool in build_tool_pipeline(tools, "admin", "dashboard")]
assert result == [
"search_dashboards",
"get_health_summary",
"deploy_dashboard",
"show_capabilities",
]
# #endregion test_dashboard_context_filters_tools
# #region test_dashboard_context_viewer_removes_admin_tools [C:2] [TYPE Function]
# @BRIEF Viewer role removes admin-only tools even when they are dashboard-affinity tools.
def test_dashboard_context_viewer_removes_admin_tools():
tools = _tools([
"search_dashboards",
"deploy_dashboard",
"execute_migration",
"show_capabilities",
])
result = [tool.name for tool in build_tool_pipeline(tools, "viewer", "dashboard")]
assert result == ["search_dashboards", "show_capabilities"]
# #endregion test_dashboard_context_viewer_removes_admin_tools
# #region test_invocation_guard_blocks_mutating_tool [C:2] [TYPE Function]
# @BRIEF Invocation guard rejects admin-only tools for non-admin role before side effects.
def test_invocation_guard_blocks_mutating_tool():
set_user_role("viewer")
with pytest.raises(PermissionError, match="PERMISSION_DENIED:deploy_dashboard:admin:viewer"):
_guard_tool_permission("deploy_dashboard")
# #endregion test_invocation_guard_blocks_mutating_tool
# #region test_summarise_response_preserves_json_array_shape [C:2] [TYPE Function]
# @BRIEF Large JSON arrays are summarised as top-N plus total count, not cut mid-structure.
def test_summarise_response_preserves_json_array_shape():
text = "[" + ",".join(f'{{"id":{idx},"name":"dashboard-{idx}"}}' for idx in range(20)) + "]"
summary = _summarise_response(text, limit=100)
assert summary.startswith("Found 20 items:")
assert "dashboard-0" in summary
assert "15 more items" in summary
# #endregion test_summarise_response_preserves_json_array_shape
# #endregion Test.Agent.Feature035