test: guard browser and Keychain side effects suite-wide (#35404)

Re-port of PR #35464 onto the rewritten hermetic conftest:
- autouse _neutralize_webbrowser fixture records open/open_new/open_new_tab
  and webbrowser.get() instead of launching a real browser
- autouse _neutralize_macos_keychain_creds defaults the Anthropic Keychain
  reader to None, with an opt-in allow_macos_keychain marker
- regression tests in tests/test_hermetic_side_effect_guards.py
- tests/agent/test_anthropic_keychain.py opts in via pytestmark

Salvaged-from: #35464
Co-authored-by: y0shualee <yuxiangl490@gmail.com>
This commit is contained in:
y0shualee 2026-07-29 18:23:24 -07:00 committed by Teknium
parent e461e86502
commit 8d009e4f3e
3 changed files with 128 additions and 0 deletions

View file

@ -3,6 +3,7 @@
import json
from unittest.mock import patch, MagicMock
import pytest
from agent.anthropic_adapter import (
_read_claude_code_credentials_from_keychain,
@ -11,6 +12,11 @@ from agent.anthropic_adapter import (
)
# This module exercises the reader itself with explicit platform and subprocess
# mocks, so it opts out of the suite-wide guard without touching a real Keychain.
pytestmark = pytest.mark.allow_macos_keychain
class TestReadClaudeCodeCredentialsFromKeychain:
"""Bug 4: macOS Keychain support for Claude Code >=2.1.114."""

View file

@ -449,6 +449,56 @@ def _isolate_hermes_home(_hermetic_environment):
return None
@pytest.fixture(autouse=True)
def _neutralize_webbrowser(monkeypatch):
"""Record browser-open attempts instead of opening real browser windows."""
import webbrowser as _webbrowser
opened: list[object] = []
def _record(url=None, *_args, **_kwargs):
opened.append(url)
return True
class _RecordingBrowser:
def open(self, url, *_args, **_kwargs):
return _record(url)
def open_new(self, url, *_args, **_kwargs):
return _record(url)
def open_new_tab(self, url, *_args, **_kwargs):
return _record(url)
browser = _RecordingBrowser()
for name in ("open", "open_new", "open_new_tab"):
monkeypatch.setattr(_webbrowser, name, _record, raising=False)
monkeypatch.setattr(_webbrowser, "get", lambda *_args, **_kwargs: browser)
return opened
@pytest.fixture(autouse=True)
def _neutralize_macos_keychain_creds(request, monkeypatch):
"""Default Anthropic credential resolution away from the real macOS Keychain."""
if request.node.get_closest_marker(_ALLOW_MACOS_KEYCHAIN_MARK):
return None
try:
import agent.anthropic_adapter as _anthropic_adapter
except Exception:
return None
monkeypatch.setattr(
_anthropic_adapter,
"_read_claude_code_credentials_from_keychain",
lambda *_args, **_kwargs: None,
raising=False,
)
return None
# ── Kanban write guard (#69283) ─────────────────────────────────────────────
# When hermetic isolation is bypassed (stale checkout, wrong rootdir, direct
# invocation), kanban writes silently pollute the real ~/.hermes. This autouse
@ -729,6 +779,7 @@ def _wal_is_usable() -> bool:
# is the env var alone.
_AUDIO_GUARD_BYPASS_MARK = "real_audio_playback"
_ALLOW_MACOS_KEYCHAIN_MARK = "allow_macos_keychain"
def pytest_configure(config): # noqa: D401 — pytest hook
@ -751,6 +802,11 @@ def pytest_configure(config): # noqa: D401 — pytest hook
"for tests that genuinely need real TTS synthesis and speaker "
"playback — there are none in the default suite).",
)
config.addinivalue_line(
"markers",
f"{_ALLOW_MACOS_KEYCHAIN_MARK}: allow a test to exercise the macOS "
"Keychain credential reader with its own subprocess/platform mocks.",
)
# The pyproject addopts pin ``--timeout-method=signal`` relies on
# ``signal.SIGALRM``, which does not exist on Windows — pytest-timeout

View file

@ -0,0 +1,66 @@
"""Regression tests for hermetic guards around local desktop side effects."""
from __future__ import annotations
import webbrowser
def test_webbrowser_open_calls_are_neutralized(monkeypatch):
"""OAuth/browser tests should never reach the real browser registry."""
def _real_browser_lookup_reached(*_args, **_kwargs):
raise AssertionError("test reached the real webbrowser registry")
monkeypatch.setattr(webbrowser, "get", _real_browser_lookup_reached)
url = "https://provider.example.invalid/oauth/authorize"
assert webbrowser.open(url) is True
assert webbrowser.open_new(url) is True
assert webbrowser.open_new_tab(url) is True
def test_webbrowser_get_controller_is_neutralized(_neutralize_webbrowser):
"""Direct controller access should still stay inside the test recorder."""
url = "https://provider.example.invalid/oauth/authorize"
controller = webbrowser.get("hermes-test-browser")
assert controller.open(url) is True
assert controller.open_new(url) is True
assert controller.open_new_tab(url) is True
assert _neutralize_webbrowser == [url, url, url]
def _isolate_anthropic_credentials(monkeypatch, tmp_path):
from agent import anthropic_adapter as aa
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_TOKEN", raising=False)
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
monkeypatch.setattr(aa.Path, "home", lambda: tmp_path)
monkeypatch.setattr(aa.platform, "system", lambda: "Darwin")
def _real_keychain_reached(*_args, **_kwargs):
raise AssertionError("test reached the real macOS Keychain command")
monkeypatch.setattr(aa.subprocess, "run", _real_keychain_reached)
return aa
def test_claude_code_credential_read_does_not_touch_macos_keychain(
monkeypatch, tmp_path
):
"""The real credential reader should be safe under the suite guard."""
aa = _isolate_anthropic_credentials(monkeypatch, tmp_path)
assert aa.read_claude_code_credentials() is None
def test_anthropic_token_resolution_does_not_touch_macos_keychain(
monkeypatch, tmp_path
):
"""Token resolution should be safe under the same suite guard."""
aa = _isolate_anthropic_credentials(monkeypatch, tmp_path)
assert aa.resolve_anthropic_token() is None