omnigent-ai--omnigent
127 行
5.3 KiB
Python
127 行
5.3 KiB
Python
"""Tests for the claude-native "don't ask again" pure helpers.
|
|
|
|
The persistent allow-rule affordance for non-edit native tools relies
|
|
on two synchronous helpers in the sessions route:
|
|
|
|
- ``_allow_remember_eligible`` — server-side gate (stamp + verdict)
|
|
deciding which tool/mode combinations may offer/honor the rule.
|
|
- ``_claude_native_remember_host`` — derives the WebFetch domain host
|
|
the rule scopes to, with a tool-wide fallback.
|
|
|
|
Both are exercised end-to-end in the integration suite; these unit
|
|
tests pin the gating and URL-parsing edge cases directly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from omnigent.server.routes.sessions import (
|
|
_allow_remember_eligible,
|
|
_claude_native_remember_host,
|
|
)
|
|
|
|
|
|
class TestAllowRememberEligible:
|
|
"""Which (tool, mode) pairs may carry a persistent allow rule."""
|
|
|
|
def test_webfetch_default_mode_eligible(self) -> None:
|
|
assert _allow_remember_eligible("WebFetch", "default") is True
|
|
|
|
def test_bash_default_mode_eligible(self) -> None:
|
|
assert _allow_remember_eligible("Bash", "default") is True
|
|
|
|
def test_eligible_with_absent_mode(self) -> None:
|
|
# ``permission_mode`` can be absent on the payload; only
|
|
# bypassPermissions (never prompts) is excluded.
|
|
assert _allow_remember_eligible("WebFetch", None) is True
|
|
|
|
def test_webfetch_eligible_under_accept_edits(self) -> None:
|
|
# acceptEdits auto-approves only edit tools; a WebFetch still
|
|
# prompts under it, so the rule is meaningful.
|
|
assert _allow_remember_eligible("WebFetch", "acceptEdits") is True
|
|
|
|
def test_bypass_permissions_not_eligible(self) -> None:
|
|
# bypassPermissions never prompts (the hook doesn't fire), so a
|
|
# rule would be inert.
|
|
assert _allow_remember_eligible("WebFetch", "bypassPermissions") is False
|
|
|
|
def test_edit_tools_not_eligible(self) -> None:
|
|
# Edit tools take the acceptEdits/setMode path instead.
|
|
for tool in ("Edit", "Write", "MultiEdit", "NotebookEdit"):
|
|
assert _allow_remember_eligible(tool, "default") is False
|
|
|
|
def test_exit_plan_mode_not_eligible(self) -> None:
|
|
# ExitPlanMode has its own plan-review card.
|
|
assert _allow_remember_eligible("ExitPlanMode", "plan") is False
|
|
|
|
def test_ask_user_question_not_eligible(self) -> None:
|
|
# AskUserQuestion is an interactive form, not a yes/no gate.
|
|
assert _allow_remember_eligible("AskUserQuestion", "default") is False
|
|
|
|
|
|
class TestClaudeNativeRememberHost:
|
|
"""Domain derivation for WebFetch rule scoping."""
|
|
|
|
def test_webfetch_simple_host(self) -> None:
|
|
assert (
|
|
_claude_native_remember_host("WebFetch", {"url": "https://github.com/cli/cli"})
|
|
== "github.com"
|
|
)
|
|
|
|
def test_host_is_lowercased_and_port_stripped(self) -> None:
|
|
# urlparse().hostname lowercases and drops the port.
|
|
assert (
|
|
_claude_native_remember_host("WebFetch", {"url": "https://GitHub.com:8443/x"})
|
|
== "github.com"
|
|
)
|
|
|
|
def test_non_webfetch_tool_returns_none(self) -> None:
|
|
# Only WebFetch gets a domain scope; everything else is tool-wide.
|
|
assert _claude_native_remember_host("Bash", {"url": "https://github.com"}) is None
|
|
|
|
def test_missing_url_returns_none(self) -> None:
|
|
assert _claude_native_remember_host("WebFetch", {"prompt": "summarize"}) is None
|
|
|
|
def test_non_dict_input_returns_none(self) -> None:
|
|
assert _claude_native_remember_host("WebFetch", None) is None
|
|
|
|
def test_url_without_scheme_returns_none(self) -> None:
|
|
# No scheme → urlparse puts everything in ``path`` and hostname
|
|
# is empty → tool-wide fallback.
|
|
assert _claude_native_remember_host("WebFetch", {"url": "github.com/cli"}) is None
|
|
|
|
def test_non_string_url_returns_none(self) -> None:
|
|
assert _claude_native_remember_host("WebFetch", {"url": 123}) is None
|
|
|
|
def test_userinfo_is_stripped(self) -> None:
|
|
# Credentials in the authority must not leak into the rule host.
|
|
assert (
|
|
_claude_native_remember_host(
|
|
"WebFetch", {"url": "https://user:pass@GitHub.com:8443/x"}
|
|
)
|
|
== "github.com"
|
|
)
|
|
|
|
def test_ipv6_host_is_bracketed(self) -> None:
|
|
# urlparse().hostname strips the brackets off an IPv6 literal, but
|
|
# Claude's colon-delimited ``domain:<host>`` grammar needs them
|
|
# re-added so the persisted rule (``domain:[2001:db8::1]``) is
|
|
# valid rather than a broken/inert colon-laden atom.
|
|
assert (
|
|
_claude_native_remember_host("WebFetch", {"url": "https://[2001:db8::1]:8443/x"})
|
|
== "[2001:db8::1]"
|
|
)
|
|
|
|
def test_ipv6_host_without_port_is_bracketed(self) -> None:
|
|
# Bracketing is independent of the port being present.
|
|
assert (
|
|
_claude_native_remember_host("WebFetch", {"url": "https://[2001:db8::1]/x"})
|
|
== "[2001:db8::1]"
|
|
)
|
|
|
|
def test_non_http_scheme_returns_none(self) -> None:
|
|
# WebFetch domain rules are HTTP(S)-oriented; other schemes fall
|
|
# back to a tool-wide rule rather than persisting a domain that
|
|
# could never match a real fetch.
|
|
assert _claude_native_remember_host("WebFetch", {"url": "ftp://github.com/x"}) is None
|
|
assert _claude_native_remember_host("WebFetch", {"url": "file://host/path"}) is None
|