test(auth): stub macOS Keychain and skip botocore soft-dep tests#15958
test(auth): stub macOS Keychain and skip botocore soft-dep tests#15958briandevans wants to merge 2 commits into
Conversation
Two independent test isolation failures on clean main: 1. test_anthropic_adapter.py — read_claude_code_credentials checks the macOS Keychain before falling back to the JSON file (added in e110677). Tests that mock Path.home() to a tmp_path were not suppressing the keychain path, so on a developer machine with real Claude Code credentials the keychain wins and every file-based assertion fails. Add monkeypatch.setattr("..._read_claude_code_credentials_from_keychain", lambda: None) to each affected test so only the file path is exercised (10 tests fixed). 2. test_bedrock_adapter.py — TestIsStaleConnectionError and TestCallConverseInvalidatesOnStaleError import botocore.exceptions directly at test-function scope. botocore is an optional runtime dep (boto3 brings it in production; the test venv doesn't include it). The production code already handles ImportError defensively; the tests should follow suit. Add pytest.importorskip("botocore") to the 6 affected tests so they skip cleanly when botocore is absent rather than failing with ModuleNotFoundError. Before: 16 additional failures beyond the 7-dingtalk/1-matrix baseline. After: 0 additional failures (6 botocore tests skip instead of error). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes test-isolation and optional-dependency issues in the agent adapter test suite so tests behave consistently across developer machines and minimal CI environments.
Changes:
- Stubs the macOS Keychain credential source in file-based Anthropic credential tests to prevent real Keychain entries from affecting assertions.
- Adds
pytest.importorskip("botocore")to Bedrock adapter tests that importbotocore.exceptions, so they skip cleanly when botocore isn’t installed.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
tests/agent/test_anthropic_adapter.py |
Disables Keychain credential lookup within tests that are intended to exercise file/env credential paths. |
tests/agent/test_bedrock_adapter.py |
Skips botocore-specific stale-connection tests when botocore is not available. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Keychain is checked before the file path on macOS; suppress it so | ||
| # the test exercises the file-based credential path exclusively. | ||
| monkeypatch.setattr( | ||
| "agent.anthropic_adapter._read_claude_code_credentials_from_keychain", | ||
| lambda: None, | ||
| ) |
There was a problem hiding this comment.
These tests all repeat the same monkeypatch to disable _read_claude_code_credentials_from_keychain. To reduce duplication and the risk of missing it in future file-based credential tests, consider extracting this into a small fixture/helper (e.g., an autouse fixture in TestReadClaudeCodeCredentials / TestResolveAnthropicToken) that forces the keychain reader to return None unless a test explicitly opts into keychain behavior.
| def test_detects_botocore_connection_closed_error(self): | ||
| pytest.importorskip("botocore") | ||
| from agent.bedrock_adapter import is_stale_connection_error | ||
| from botocore.exceptions import ConnectionClosedError | ||
| exc = ConnectionClosedError(endpoint_url="https://bedrock.example") | ||
| assert is_stale_connection_error(exc) is True | ||
|
|
||
| def test_detects_botocore_endpoint_connection_error(self): | ||
| pytest.importorskip("botocore") | ||
| from agent.bedrock_adapter import is_stale_connection_error | ||
| from botocore.exceptions import EndpointConnectionError | ||
| exc = EndpointConnectionError(endpoint_url="https://bedrock.example") | ||
| assert is_stale_connection_error(exc) is True | ||
|
|
||
| def test_detects_botocore_read_timeout(self): | ||
| pytest.importorskip("botocore") | ||
| from agent.bedrock_adapter import is_stale_connection_error | ||
| from botocore.exceptions import ReadTimeoutError | ||
| exc = ReadTimeoutError(endpoint_url="https://bedrock.example") |
There was a problem hiding this comment.
pytest.importorskip("botocore") is duplicated across multiple tests in this section. For maintainability, consider factoring this into a fixture (e.g. requires_botocore) or doing a single botocore = pytest.importorskip("botocore", reason=...) at the class level and referencing botocore.exceptions in the tests, so the skip behavior is defined once.
| def test_detects_botocore_connection_closed_error(self): | |
| pytest.importorskip("botocore") | |
| from agent.bedrock_adapter import is_stale_connection_error | |
| from botocore.exceptions import ConnectionClosedError | |
| exc = ConnectionClosedError(endpoint_url="https://bedrock.example") | |
| assert is_stale_connection_error(exc) is True | |
| def test_detects_botocore_endpoint_connection_error(self): | |
| pytest.importorskip("botocore") | |
| from agent.bedrock_adapter import is_stale_connection_error | |
| from botocore.exceptions import EndpointConnectionError | |
| exc = EndpointConnectionError(endpoint_url="https://bedrock.example") | |
| assert is_stale_connection_error(exc) is True | |
| def test_detects_botocore_read_timeout(self): | |
| pytest.importorskip("botocore") | |
| from agent.bedrock_adapter import is_stale_connection_error | |
| from botocore.exceptions import ReadTimeoutError | |
| exc = ReadTimeoutError(endpoint_url="https://bedrock.example") | |
| botocore = pytest.importorskip( | |
| "botocore", | |
| reason="botocore is required for botocore-specific stale connection tests", | |
| ) | |
| def test_detects_botocore_connection_closed_error(self): | |
| from agent.bedrock_adapter import is_stale_connection_error | |
| exc = TestIsStaleConnectionError.botocore.exceptions.ConnectionClosedError( | |
| endpoint_url="https://bedrock.example" | |
| ) | |
| assert is_stale_connection_error(exc) is True | |
| def test_detects_botocore_endpoint_connection_error(self): | |
| from agent.bedrock_adapter import is_stale_connection_error | |
| exc = TestIsStaleConnectionError.botocore.exceptions.EndpointConnectionError( | |
| endpoint_url="https://bedrock.example" | |
| ) | |
| assert is_stale_connection_error(exc) is True | |
| def test_detects_botocore_read_timeout(self): | |
| from agent.bedrock_adapter import is_stale_connection_error | |
| exc = TestIsStaleConnectionError.botocore.exceptions.ReadTimeoutError( | |
| endpoint_url="https://bedrock.example" | |
| ) |
… into fixtures Copilot review (PR NousResearch#15958) flagged two maintenance concerns: - All file-based credential tests in TestReadClaudeCodeCredentials, TestResolveAnthropicToken, and TestRunOauthSetupToken individually repeated the same monkeypatch to suppress keychain lookup; extract into per-class autouse _stub_keychain fixtures. - pytest.importorskip("botocore") appeared once per method in TestIsStaleConnectionError; replace with a single requires_botocore fixture so the skip guard is defined once without losing per-method granularity (urllib3/AssertionError tests still run without botocore). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks @copilot — both findings are real. Addressed in `2cd9b730`. 1. Repeated keychain monkeypatch (test_anthropic_adapter.py) 2. Repeated `pytest.importorskip("botocore")` (test_bedrock_adapter.py) All 1951 agent tests green (7 botocore tests skip as expected on this machine where botocore isn't installed). |
|
@copilot Both are style suggestions rather than correctness issues. The monkeypatch duplications are intentional test isolation — each test class is self-contained. The |
|
Closing to keep the queue clean — happy to reopen if this is still useful. |
Summary
test_anthropic_adapter.pytests that broke on developer machines with real Claude Code credentials in the macOS Keychaintest_bedrock_adapter.pytests that fail withModuleNotFoundError: No module named 'botocore'when botocore isn't installedThe bug
Anthropic adapter (10 tests): Commit
e1106772added_read_claude_code_credentials_from_keychain()as the first credential source, checked before the JSON file path. The existing tests only mockPath.home()to atmp_path, leaving the Keychain path unsuppressed. On any machine with Claude Code installed and an active Keychain entry, the Keychain wins and every file-based assertion fails (wrong token, orNoneinstead of the expected value, orTypeError: the JSON object must be str, bytes or bytearraywhensubprocess.runis globally patched but its.stdout.strip()returns aMagicMock).Bedrock adapter (6 tests): The production code in
is_stale_connection_erroralready guardsfrom botocore.exceptions import ...withtry/except ImportError(comment:# pragma: no cover — botocore always present with boto3). The test functions import frombotocore.exceptionsdirectly at function scope without a guard, so they fail withModuleNotFoundErrorin environments (CI, contributor machines) without botocore installed.The fix
Add
monkeypatch.setattr("agent.anthropic_adapter._read_claude_code_credentials_from_keychain", lambda: None)to each of the 10 tests that exercise file-based credential paths — this isolates the test to the file path only, which is what the test intends.Add
pytest.importorskip("botocore")at the top of each of the 6 bedrock tests that require botocore exceptions — they skip cleanly when botocore isn't installed (matching the production code's defensive posture), and still run and pass in boto3-equipped environments.Test plan
main247 passed, 6 skippedRelated
Fixes the unreported test-isolation regressions introduced by:
e1106772(macOS Keychain credential path added without updating test stubs)a9ccb03c(bedrock stale-connection tests added withoutimportorskipguard)🤖 Generated with Claude Code