Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

117 lines
4.2 KiB
Python

"""Unit tests for the shared REPL execution policy.
Alpha mode: policy helpers resolve to ``allow`` with no confirmation prompt and
there is no command guardrail. The ``ask`` verdict is retained for
``trust_mode`` / future opt-in stricter policy, so those paths are covered here
by exercising :func:`resolve_confirmation` with explicitly-constructed ``ask`` /
``deny`` results.
This module is pure (no console, no ``input``, no analytics). The interaction
layer (``execution_allowed``) and its terminal/analytics behavior are covered by
``tests/interactive_shell/ui/test_execution_confirm.py``. Shell-specific policy
lives in ``tools.interactive_shell.shell.policy`` and is covered by
``tests/interactive_shell/shell/test_policy.py``.
"""
from __future__ import annotations
from tools.interactive_shell.shared import (
ConfirmationOutcome,
ExecutionPolicyResult,
ToolExecutionMode,
ToolExecutionPlan,
allow_tool,
plan_foreground_tool,
resolve_confirmation,
)
def _ask_result() -> ExecutionPolicyResult:
"""An explicit ``ask`` verdict (the default policy no longer emits these)."""
return ExecutionPolicyResult(
verdict="ask",
tool_type="slash",
reason="this command may change configuration or run heavy work",
)
# --- Default-allow policy decisions -----------------------------------------
def test_allow_tool_is_allow() -> None:
r = allow_tool("slash")
assert r.verdict == "allow"
assert r.tool_type == "slash"
assert r.reason is None
def test_allow_tool_carries_arbitrary_tool_type() -> None:
for tool_type in ("investigation", "sample_alert", "synthetic_test", "code_agent"):
r = allow_tool(tool_type)
assert r.verdict == "allow"
assert r.tool_type == tool_type
# --- plan_foreground_tool ---------------------------------------------------
def test_plan_foreground_tool_defaults_classification_to_tool_type() -> None:
plan = plan_foreground_tool("slash")
assert isinstance(plan, ToolExecutionPlan)
assert plan.tool_type == "slash"
assert plan.classification == "slash"
assert plan.execution_mode is ToolExecutionMode.FOREGROUND
assert plan.policy.verdict == "allow"
def test_plan_foreground_tool_accepts_explicit_classification() -> None:
plan = plan_foreground_tool("investigation", "investigation_launch")
assert plan.tool_type == "investigation"
assert plan.classification == "investigation_launch"
assert plan.execution_mode is ToolExecutionMode.FOREGROUND
assert plan.policy.verdict == "allow"
# --- resolve_confirmation: pure decision (no side effects) ------------------
def test_resolve_allow_verdict_proceeds() -> None:
plan = resolve_confirmation(allow_tool("slash"), trust_mode=False, is_tty=True)
assert plan.outcome == ConfirmationOutcome.ALLOW
assert plan.analytics_outcome == "allowed"
def test_resolve_deny_verdict_blocks() -> None:
result = ExecutionPolicyResult(
verdict="deny",
tool_type="shell",
reason="empty command.",
hint="Enter a command to run.",
)
plan = resolve_confirmation(result, trust_mode=False, is_tty=True)
assert plan.outcome == ConfirmationOutcome.DENY
assert plan.analytics_outcome == "blocked"
assert plan.analytics_reason == "empty command."
def test_resolve_ask_trust_mode_allows_without_prompt() -> None:
plan = resolve_confirmation(_ask_result(), trust_mode=True, is_tty=True)
assert plan.outcome == ConfirmationOutcome.ALLOW
assert plan.analytics_outcome == "allowed"
assert plan.analytics_reason == "trust_mode_skipped_prompt"
def test_resolve_ask_non_tty_blocks() -> None:
plan = resolve_confirmation(_ask_result(), trust_mode=False, is_tty=False)
assert plan.outcome == ConfirmationOutcome.BLOCK_NON_TTY
assert plan.analytics_outcome == "blocked"
assert plan.analytics_reason == "non_interactive_stdin"
def test_resolve_ask_tty_needs_confirmation() -> None:
plan = resolve_confirmation(_ask_result(), trust_mode=False, is_tty=True)
assert plan.outcome == ConfirmationOutcome.NEEDS_CONFIRMATION
# The analytics outcome for a prompt is decided by the interaction layer.
assert plan.analytics_outcome is None
assert plan.analytics_reason is None