Systematic rename of all semantic anchors (#region, [DEF], @RELATION) across 1400+ files — backend Python, frontend Svelte/TS, specs, docs: - Flat anchors become Namespace.Module.Entity - @RELATION references updated to match new anchor paths - Zero business logic changes
242 lines
11 KiB
Python
242 lines
11 KiB
Python
# agent/tests/test_agent/test_agent_confirmation_v2.py
|
|
# #region Test.Agent.ConfirmationV2 [C:3] [TYPE Module] [SEMANTICS test,agent-chat,confirmation,guardrails,hitl]
|
|
# @BRIEF Contract tests for confirmation_metadata_for_tool and build_confirmation_contract_v2 — three-axis risk, env resolution, permission denied.
|
|
# @RELATION BINDS_TO -> [AgentChat.Confirmation]
|
|
# @TEST_FIXTURE: deploy_prod -> guarded + prod env_context
|
|
# @TEST_FIXTURE: deploy_staging -> guarded + staging env_context
|
|
# @TEST_FIXTURE: deploy_dev -> guarded + dev env_context
|
|
# @TEST_FIXTURE: delete_any -> dangerous risk_level
|
|
# @TEST_FIXTURE: read_only -> safe risk_level
|
|
# @TEST_FIXTURE: analyst_deploy -> permission_granted=false
|
|
# @TEST_FIXTURE: env_resolution -> tool_args.env_id > target_env > None
|
|
|
|
|
|
from ss_tools.agent._confirmation import (
|
|
_resolve_env_tier,
|
|
build_confirmation_contract_v2,
|
|
confirmation_metadata_for_tool,
|
|
permission_denied_payload,
|
|
)
|
|
|
|
# ── _resolve_env_tier ───────────────────────────────────────────────
|
|
|
|
# #region Test.Agent.TestEnvResolutionFromToolArgs [C:2] [TYPE Function]
|
|
def test_resolve_env_tier_from_tool_args_env_id():
|
|
"""env_id in tool_args takes highest priority."""
|
|
assert _resolve_env_tier({"env_id": "prod-01"}, None) == "prod"
|
|
assert _resolve_env_tier({"env_id": "prod-01"}, "staging") == "prod" # tool_args wins
|
|
# #endregion Test.Agent.TestEnvResolutionFromToolArgs
|
|
|
|
# #region Test.Agent.TestEnvResolutionFromEnvironmentId [C:2] [TYPE Function]
|
|
def test_resolve_env_tier_from_environment_id():
|
|
"""environment_id in tool_args (alternative key) works."""
|
|
assert _resolve_env_tier({"environment_id": "staging-v2"}, None) == "staging"
|
|
# #endregion Test.Agent.TestEnvResolutionFromEnvironmentId
|
|
|
|
# #region Test.Agent.TestEnvResolutionFromTargetEnv [C:2] [TYPE Function]
|
|
def test_resolve_env_tier_from_target_env():
|
|
"""target_env fallback when tool_args has no env."""
|
|
assert _resolve_env_tier({}, "ss-dev") == "dev"
|
|
assert _resolve_env_tier({"query": "test"}, "prod-v2") == "prod"
|
|
# #endregion Test.Agent.TestEnvResolutionFromTargetEnv
|
|
|
|
# #region Test.Agent.TestEnvResolutionNullWhenNoEnv [C:2] [TYPE Function]
|
|
def test_resolve_env_tier_null_when_no_env():
|
|
"""When neither tool_args nor target_env provides env, return None."""
|
|
assert _resolve_env_tier({}, None) is None
|
|
assert _resolve_env_tier({"dashboard_id": 42}, None) is None
|
|
# #endregion Test.Agent.TestEnvResolutionNullWhenNoEnv
|
|
|
|
# #region Test.Agent.TestEnvResolutionAmbiguousNames [C:2] [TYPE Function]
|
|
def test_resolve_env_tier_ambiguous_names():
|
|
"""Environment names containing stag/test/local/dev are correctly tiered."""
|
|
assert _resolve_env_tier({"env_id": "autotest"}, None) == "staging" # "test" in autotest
|
|
assert _resolve_env_tier({"env_id": "localhost"}, None) == "dev" # "local" in localhost
|
|
# #endregion Test.Agent.TestEnvResolutionAmbiguousNames
|
|
|
|
|
|
# ── build_confirmation_contract_v2 ───────────────────────────────────
|
|
|
|
# #region Test.Agent.TestDeployToProdIsGuardedWithProdContext [C:2] [TYPE Function]
|
|
def test_deploy_to_prod_is_guarded_with_prod_context():
|
|
"""Deploy to production → guarded risk + prod env_context."""
|
|
contract = build_confirmation_contract_v2(
|
|
"deploy_dashboard", {"env_id": "prod-01"}, "admin", "prod-01",
|
|
)
|
|
assert contract["risk"] == "write"
|
|
assert contract["risk_level"] == "guarded"
|
|
assert contract["dangerous"] is False
|
|
assert contract["env_context"] == "prod"
|
|
assert contract["permission_granted"] is True
|
|
# #endregion Test.Agent.TestDeployToProdIsGuardedWithProdContext
|
|
|
|
|
|
# #region Test.Agent.TestDeployToStagingIsGuardedWithStagingContext [C:2] [TYPE Function]
|
|
def test_deploy_to_staging_is_guarded_with_staging_context():
|
|
"""Deploy to staging → guarded risk + staging env_context."""
|
|
contract = build_confirmation_contract_v2(
|
|
"deploy_dashboard", {"env_id": "staging-v2"}, "admin", "staging-v2",
|
|
)
|
|
assert contract["risk"] == "write"
|
|
assert contract["risk_level"] == "guarded"
|
|
assert contract["env_context"] == "staging"
|
|
# #endregion Test.Agent.TestDeployToStagingIsGuardedWithStagingContext
|
|
|
|
|
|
# #region Test.Agent.TestDeployToDevIsGuardedWithDevContext [C:2] [TYPE Function]
|
|
def test_deploy_to_dev_is_guarded_with_dev_context():
|
|
"""Deploy to dev → guarded risk + dev env_context."""
|
|
contract = build_confirmation_contract_v2(
|
|
"deploy_dashboard", {"env_id": "my-dev-env"}, "admin", "my-dev-env",
|
|
)
|
|
assert contract["risk"] == "write"
|
|
assert contract["risk_level"] == "guarded"
|
|
assert contract["env_context"] == "dev"
|
|
# #endregion Test.Agent.TestDeployToDevIsGuardedWithDevContext
|
|
|
|
|
|
# #region Test.Agent.TestDeleteOperationIsDangerous [C:2] [TYPE Function]
|
|
def test_delete_operation_is_dangerous():
|
|
"""Delete-prefixed tools should be classified as dangerous."""
|
|
contract = build_confirmation_contract_v2(
|
|
"delete_dashboard", {}, "admin",
|
|
)
|
|
assert contract["risk"] == "write"
|
|
assert contract["risk_level"] == "dangerous"
|
|
assert contract["dangerous"] is True
|
|
# #endregion Test.Agent.TestDeleteOperationIsDangerous
|
|
|
|
|
|
# #region Test.Agent.TestReadOnlyToolIsSafe [C:2] [TYPE Function]
|
|
def test_read_only_tool_is_safe():
|
|
"""Search/list/get tools should be classified as safe/read."""
|
|
contract = build_confirmation_contract_v2(
|
|
"search_dashboards", {"env_id": "prod"}, "admin", "prod",
|
|
)
|
|
assert contract["risk"] == "read"
|
|
assert contract["risk_level"] == "safe"
|
|
assert contract["dangerous"] is False
|
|
# #endregion Test.Agent.TestReadOnlyToolIsSafe
|
|
|
|
|
|
# #region Test.Agent.TestViewerGetsPermissionDenied [C:2] [TYPE Function]
|
|
def test_viewer_permission_denied_for_write_tools():
|
|
"""Viewer role should be denied permission for write tools."""
|
|
contract = build_confirmation_contract_v2(
|
|
"deploy_dashboard", {"env_id": "prod"}, "viewer", "prod",
|
|
)
|
|
assert contract["permission_granted"] is False
|
|
assert contract["required_role"] == "admin"
|
|
assert contract["alternatives"] is not None
|
|
assert len(contract["alternatives"]) >= 1
|
|
# #endregion Test.Agent.TestViewerGetsPermissionDenied
|
|
|
|
|
|
# #region Test.Agent.TestAnalystPermissionDeniedSpecificTools [C:2] [TYPE Function]
|
|
def test_analyst_permission_denied_for_admin_tools():
|
|
"""Analyst/editor role should be denied for admin-only tools."""
|
|
for tool_name in ("deploy_dashboard", "execute_migration", "run_backup"):
|
|
contract = build_confirmation_contract_v2(tool_name, {}, "analyst")
|
|
assert contract["permission_granted"] is False, f"{tool_name} should be denied for analyst"
|
|
# #endregion Test.Agent.TestAnalystPermissionDeniedSpecificTools
|
|
|
|
|
|
# #region Test.Agent.TestAdminWriteToolPermissionGranted [C:2] [TYPE Function]
|
|
def test_admin_write_tool_permission_granted():
|
|
"""Admin role should always have permission_granted=True for guarded tools."""
|
|
write_tools = ["deploy_dashboard", "commit_changes", "execute_migration"]
|
|
for tool_name in write_tools:
|
|
contract = build_confirmation_contract_v2(tool_name, {}, "admin")
|
|
assert contract["permission_granted"] is True, f"{tool_name} should be allowed for admin"
|
|
# #endregion Test.Agent.TestAdminWriteToolPermissionGranted
|
|
|
|
|
|
# ── confirmation_metadata_for_tool ────────────────────────────────────
|
|
|
|
# #region Test.Agent.TestMetadataForToolContainsRequiredFields [C:2] [TYPE Function]
|
|
def test_metadata_for_tool_contains_all_required_fields():
|
|
"""confirmation_metadata_for_tool should produce a complete metadata dict."""
|
|
meta = confirmation_metadata_for_tool(
|
|
"conv-123", "deploy_dashboard",
|
|
{"env_id": "prod-01"}, "admin", "prod-01",
|
|
)
|
|
required_fields = [
|
|
"type", "thread_id", "prompt", "tool_name", "tool_args",
|
|
"risk", "risk_level", "requires_confirmation",
|
|
"dangerous", "env_context",
|
|
]
|
|
for field in required_fields:
|
|
assert field in meta, f"Missing field '{field}' in metadata"
|
|
assert meta["type"] == "confirm_required"
|
|
assert meta["thread_id"] == "conv-123"
|
|
assert meta["tool_name"] == "deploy_dashboard"
|
|
assert meta["risk"] == "write"
|
|
assert meta["risk_level"] == "guarded"
|
|
assert meta["requires_confirmation"] is True
|
|
# #endregion Test.Agent.TestMetadataForToolContainsRequiredFields
|
|
|
|
|
|
# ── permission_denied_payload ─────────────────────────────────────────
|
|
|
|
# #region Test.Agent.TestPermissionDeniedPayloadStructure [C:2] [TYPE Function]
|
|
def test_permission_denied_payload_structure():
|
|
"""permission_denied_payload should produce valid JSON with correct type."""
|
|
import json
|
|
|
|
payload_str = permission_denied_payload("deploy_dashboard", "admin", "viewer")
|
|
payload = json.loads(payload_str)
|
|
|
|
assert payload["content"] == "⛔ Недостаточно прав для deploy_dashboard"
|
|
assert payload["metadata"]["type"] == "permission_denied"
|
|
assert payload["metadata"]["tool_name"] == "deploy_dashboard"
|
|
assert payload["metadata"]["required_role"] == "admin"
|
|
assert payload["metadata"]["user_role"] == "viewer"
|
|
assert payload["metadata"]["alternatives"] == []
|
|
# #endregion Test.Agent.TestPermissionDeniedPayloadStructure
|
|
|
|
|
|
# #region Test.Agent.TestPermissionDeniedPayloadWithAlternatives [C:2] [TYPE Function]
|
|
def test_permission_denied_payload_with_alternatives():
|
|
"""Alternatives list should be preserved in the payload."""
|
|
import json
|
|
|
|
alternatives = [{"action": "get_health_summary", "prompt": "Check system health"}]
|
|
payload_str = permission_denied_payload(
|
|
"deploy_dashboard", "admin", "viewer", alternatives,
|
|
)
|
|
payload = json.loads(payload_str)
|
|
|
|
assert payload["metadata"]["alternatives"] == alternatives
|
|
# #endregion Test.Agent.TestPermissionDeniedPayloadWithAlternatives
|
|
|
|
|
|
# ── Unknown/null tool ─────────────────────────────────────────────────
|
|
|
|
# #region Test.Agent.TestNullToolNameHandled [C:2] [TYPE Function]
|
|
def test_null_tool_name_handled_gracefully():
|
|
"""Null tool_name should produce 'unknown_action' fallback."""
|
|
contract = build_confirmation_contract_v2(None)
|
|
assert contract["operation"] == "unknown_action"
|
|
assert contract["risk_level"] == "safe"
|
|
# #endregion Test.Agent.TestNullToolNameHandled
|
|
|
|
|
|
# #region Test.Agent.TestExecuteMigrationIsGuarded [C:2] [TYPE Function]
|
|
def test_execute_migration_is_guarded():
|
|
"""execute_migration should be classified as guarded (write)."""
|
|
contract = build_confirmation_contract_v2("execute_migration", {})
|
|
assert contract["risk"] == "write"
|
|
assert contract["risk_level"] == "guarded"
|
|
# #endregion Test.Agent.TestExecuteMigrationIsGuarded
|
|
|
|
|
|
# #region Test.Agent.TestCommitChangesIsGuarded [C:2] [TYPE Function]
|
|
def test_commit_changes_is_guarded():
|
|
"""commit_changes should be classified as guarded (write)."""
|
|
contract = build_confirmation_contract_v2("commit_changes", {})
|
|
assert contract["risk"] == "write"
|
|
assert contract["risk_level"] == "guarded"
|
|
# #endregion Test.Agent.TestCommitChangesIsGuarded
|
|
|
|
# #endregion Test.Agent.ConfirmationV2
|