vibe/tests/test_permission_store.py
Mathias Gesbert 228f3c65a9
v2.10.0 (#697)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Corentin André <corentin.andre@mistral.ai>
Co-authored-by: Guillaume LE GOFF <guillaume.lgf@gmail.com>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com>
Co-authored-by: Peter Evers <pevers90@gmail.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com>
Co-authored-by: MichisGitIsKing <MichisGitIsKing@users.noreply.github.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-05-19 11:56:25 +02:00

166 lines
5.6 KiB
Python

from __future__ import annotations
from pydantic import BaseModel
import pytest
from tests.conftest import build_test_agent_loop, build_test_vibe_config
from tests.mock.utils import mock_llm_chunk
from tests.stubs.fake_backend import FakeBackend
from vibe.core.tools.base import ToolPermission
from vibe.core.tools.permissions import (
ApprovedRule,
PermissionScope,
PermissionStore,
RequiredPermission,
)
from vibe.core.types import ApprovalResponse, FunctionCall, ToolCall, ToolResultEvent
class TestPermissionStore:
def test_covers_returns_false_when_empty(self):
store = PermissionStore()
rp = RequiredPermission(
scope=PermissionScope.COMMAND_PATTERN,
invocation_pattern="npm install foo",
session_pattern="npm install *",
label="npm install *",
)
assert not store.covers("bash", rp)
def test_covers_returns_true_after_matching_rule_added(self):
store = PermissionStore()
store.add_rule(
ApprovedRule(
tool_name="bash",
scope=PermissionScope.COMMAND_PATTERN,
session_pattern="npm install *",
)
)
rp = RequiredPermission(
scope=PermissionScope.COMMAND_PATTERN,
invocation_pattern="npm install foo",
session_pattern="npm install *",
label="npm install *",
)
assert store.covers("bash", rp)
def test_covers_isolates_by_tool_name(self):
store = PermissionStore()
store.add_rule(
ApprovedRule(
tool_name="bash",
scope=PermissionScope.COMMAND_PATTERN,
session_pattern="npm install *",
)
)
rp = RequiredPermission(
scope=PermissionScope.COMMAND_PATTERN,
invocation_pattern="npm install foo",
session_pattern="npm install *",
label="npm install *",
)
assert not store.covers("read_file", rp)
def test_tool_permission_round_trip(self):
store = PermissionStore()
assert store.get_tool_permission("bash") is None
store.set_tool_permission("bash", ToolPermission.ALWAYS)
assert store.get_tool_permission("bash") == ToolPermission.ALWAYS
class TestAgentLoopSharesStore:
def test_subagent_inherits_parent_session_rules(self):
store = PermissionStore()
parent = build_test_agent_loop(permission_store=store)
subagent = build_test_agent_loop(permission_store=store)
parent.approve_always(
"bash",
[
RequiredPermission(
scope=PermissionScope.COMMAND_PATTERN,
invocation_pattern="npm install foo",
session_pattern="npm install *",
label="npm install *",
)
],
)
rp = RequiredPermission(
scope=PermissionScope.COMMAND_PATTERN,
invocation_pattern="npm install bar",
session_pattern="npm install *",
label="npm install *",
)
assert subagent._permission_store.covers("bash", rp)
def test_subagent_inherits_parent_tool_permission(self):
store = PermissionStore()
parent = build_test_agent_loop(permission_store=store)
subagent = build_test_agent_loop(permission_store=store)
parent.approve_always("bash", None)
assert (
subagent._permission_store.get_tool_permission("bash")
== ToolPermission.ALWAYS
)
@pytest.mark.asyncio
async def test_subagent_applies_parent_tool_permission_before_resolution(self):
store = PermissionStore()
parent = build_test_agent_loop(permission_store=store)
parent.approve_always("bash", None)
approval_requested = False
async def approval_callback(
tool_name: str,
args: BaseModel,
tool_call_id: str,
required_permissions: list[RequiredPermission] | None,
) -> tuple[ApprovalResponse, str | None]:
nonlocal approval_requested
approval_requested = True
return ApprovalResponse.NO, None
tool_call = ToolCall(
id="call_1",
index=0,
function=FunctionCall(name="bash", arguments='{"command":"true"}'),
)
subagent = build_test_agent_loop(
config=build_test_vibe_config(enabled_tools=["bash"]),
backend=FakeBackend([
[mock_llm_chunk(content="Running it.", tool_calls=[tool_call])],
[mock_llm_chunk(content="Done.")],
]),
permission_store=store,
)
subagent.set_approval_callback(approval_callback)
events = [event async for event in subagent.act("run true")]
tool_results = [event for event in events if isinstance(event, ToolResultEvent)]
assert not approval_requested
assert len(tool_results) == 1
assert tool_results[0].skipped is False
assert "permission" not in subagent.config.tools.get("bash", {})
def test_default_store_is_per_loop_when_not_shared(self):
a = build_test_agent_loop()
b = build_test_agent_loop()
assert a._permission_store is not b._permission_store
assert a._permission_store.lock is not b._permission_store.lock
def test_subagent_shares_parent_approval_lock(self):
store = PermissionStore()
parent = build_test_agent_loop(permission_store=store)
subagent = build_test_agent_loop(permission_store=store)
assert parent._permission_store.lock is subagent._permission_store.lock