Files
qwenpaw/tests/unit/agents/test_acp_client_trusted.py
agent-radar 1a3a4ccde1
Creator App Tests / PR Spam Gate (push) Skipped
Frontend Tests / PR Spam Gate (push) Skipped
NPM Format (website & console) / PR Spam Gate (push) Skipped
Pre-commit Checks / PR Spam Gate (push) Skipped
QwenPaw Creator Plugin Release / check-version (push) Canceled after 0s
QwenPaw Creator Plugin Release / upload-oss (push) Canceled after 0s
Tests / PR Spam Gate (push) Skipped
CodeQL / CodeQL (python) (push) Canceled after 0s
CodeQL / CodeQL (typescript) (push) Canceled after 0s
Creator App Tests / Creator Backend Tests (push) Canceled after 0s
Creator App Tests / Creator UI Build & Tests (push) Canceled after 0s
E2E Smoke Tests / Playwright UI Smoke (push) Canceled after 0s
Frontend Tests / Detect console changes (push) Canceled after 0s
NPM Format (website & console) / website format check (push) Canceled after 0s
NPM Format (website & console) / console format check (push) Canceled after 0s
Pre-commit Checks / Pre-commit (push) Canceled after 0s
QwenPaw Data Plugin Release / check-version (push) Canceled after 0s
Tests / Detect code changes (push) Canceled after 0s
Frontend Tests / Vitest Unit Tests (push) Canceled after 0s
Frontend Tests / Frontend Summary (push) Canceled after 0s
QwenPaw Data Plugin Release / upload-oss (push) Canceled after 0s
Tests / Maintainer Approval (push) Canceled after 0s
Tests / Unit Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - fallback (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p2 (push) Canceled after 0s
Tests / Coverage Report (push) Canceled after 0s
Tests / Test Summary (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:01:50 +08:00

145 lines
4.7 KiB
Python

# -*- coding: utf-8 -*-
# pylint: disable=protected-access
"""Tests for ACPHostedClient trusted auto-approve logic."""
from __future__ import annotations
import asyncio
from unittest.mock import AsyncMock
import pytest
from qwenpaw.agents.acp.client import ACPHostedClient
from qwenpaw.config.config import ACPAgentConfig
class TestACPHostedClientTrustedAutoApprove:
"""Verify trusted runner auto-approves non-hard-blocked tool calls."""
def _make_client(self, *, trusted: bool = True) -> ACPHostedClient:
config = ACPAgentConfig(
enabled=True,
command="test",
trusted=trusted,
tool_parse_mode="call_title",
)
return ACPHostedClient(
agent_name="test-agent",
agent_config=config,
cwd="/tmp",
)
@pytest.mark.asyncio
async def test_trusted_auto_approves_safe_command(self) -> None:
client = self._make_client(trusted=True)
client._on_message = AsyncMock() # noqa: W0212
options = [
{"optionId": "allow_once", "label": "Allow once"},
{"optionId": "deny", "label": "Deny"},
]
tool_call = {
"title": "Execute",
"kind": "execute",
"rawInput": {"command": "ls -la"},
}
response = await client.request_permission(
options=options,
session_id="sess-1",
tool_call=tool_call,
)
# Should auto-approve without suspending
assert response.outcome.outcome == "selected"
assert response.outcome.option_id == "allow_once"
# Should NOT have set pending permission (no suspend)
assert client._pending_permission is None # noqa: W0212
@pytest.mark.asyncio
async def test_trusted_still_blocks_destructive_command(self) -> None:
client = self._make_client(trusted=True)
client._on_message = AsyncMock() # noqa: W0212
options = [
{"optionId": "allow_once", "label": "Allow once"},
]
tool_call = {
"title": "Execute",
"kind": "execute",
"rawInput": {"command": "rm -rf /"},
}
response = await client.request_permission(
options=options,
session_id="sess-1",
tool_call=tool_call,
)
# Hard-block should intercept even for trusted
assert response.outcome.outcome == "cancelled"
@pytest.mark.asyncio
async def test_untrusted_suspends_for_user_confirmation(self) -> None:
client = self._make_client(trusted=False)
client._on_message = AsyncMock() # noqa: W0212
options = [
{"optionId": "allow_once", "label": "Allow once"},
]
tool_call = {
"title": "Execute",
"kind": "execute",
"rawInput": {"command": "ls -la"},
}
# Start request_permission in a task since it will suspend
task = asyncio.create_task(
client.request_permission(
options=options,
session_id="sess-1",
tool_call=tool_call,
),
)
# Wait for the permission request to be emitted
await asyncio.sleep(0.05)
# Should have suspended (pending permission set)
assert client._pending_permission is not None # noqa: W0212
# Resolve the permission to complete the task
client.resolve_permission("allow_once")
response = await task
assert response.outcome.outcome == "selected"
def test_pick_allow_option_prefers_allow_once(self) -> None:
client = self._make_client(trusted=True)
options = [
{"optionId": "deny", "label": "Deny"},
{"optionId": "allow_always", "label": "Always"},
{"optionId": "allow_once", "label": "Once"},
]
selected = client._pick_allow_option(options) # noqa: W0212
assert selected is not None
assert selected["optionId"] == "allow_once"
def test_pick_allow_option_fallback_to_allow_in_id(self) -> None:
client = self._make_client(trusted=True)
options = [
{"optionId": "deny", "label": "Deny"},
{"optionId": "custom_allow_action", "label": "Custom"},
]
selected = client._pick_allow_option(options) # noqa: W0212
assert selected is not None
assert selected["optionId"] == "custom_allow_action"
def test_pick_allow_option_returns_none_when_no_allow(self) -> None:
client = self._make_client(trusted=True)
options = [
{"optionId": "deny", "label": "Deny"},
{"optionId": "cancel", "label": "Cancel"},
]
selected = client._pick_allow_option(options) # noqa: W0212
assert selected is None