fastmcp/tests/conftest.py
Jeremiah Lowin 686082a5b5
Add platform-aware OAuth token persistence (#2218)
* Add comprehensive keyring integration tests

Prevents OS keyring pollution during testing by adding a global mock in
conftest.py. Tests verify keyring behavior across platforms and fallback
scenarios without writing to the actual system keyring.

- Add global mock_keyring fixture to tests/conftest.py
- Add TestOAuthProxyKeyring class with 6 keyring-specific tests
- Remove try/except ImportError for keyring (now required dependency)
- Add keyring extra to py-key-value-aio dependency
- Clean up extraneous implementation comments in oauth_proxy.py

* Update OAuth keyring documentation

Update all OAuth-related documentation to reflect keyring-based key management:
- Add version badges to jwt_signing_key, token_encryption_key, and client_storage parameters
- Standardize "Default behavior (`None`):" formatting with backticks
- Ensure consistent messaging about development-only defaults across all docs
- Update oauth-proxy.mdx, oidc-proxy.mdx, http.mdx, storage-backends.mdx, and upgrade-guide.mdx
2025-10-22 20:42:24 -04:00

73 lines
2.1 KiB
Python

import socket
from collections.abc import Callable
from typing import Any
from unittest.mock import patch
import pytest
def pytest_collection_modifyitems(items):
"""Automatically mark tests in integration_tests folder with 'integration' marker."""
for item in items:
# Check if the test is in the integration_tests folder
if "integration_tests" in str(item.fspath):
item.add_marker(pytest.mark.integration)
@pytest.fixture(autouse=True)
def import_rich_rule():
# What a hack
import rich.rule # noqa: F401
yield
@pytest.fixture(autouse=True)
def mock_keyring():
"""Globally mock keyring to prevent OS keyring pollution during tests.
This prevents any test from accidentally writing to the system keyring.
Individual tests can override this mock if they need to test keyring behavior.
"""
with patch("fastmcp.utilities.key_management.keyring") as mock:
# Return None by default (keyring unavailable)
mock.get_password.return_value = None
yield mock
def get_fn_name(fn: Callable[..., Any]) -> str:
return fn.__name__ # ty: ignore[unresolved-attribute]
@pytest.fixture
def worker_id(request):
"""Get the xdist worker ID, or 'master' if not using xdist."""
return getattr(request.config, "workerinput", {}).get("workerid", "master")
@pytest.fixture
def free_port():
"""Get a free port for the test to use."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
s.listen(1)
port = s.getsockname()[1]
return port
@pytest.fixture
def free_port_factory(worker_id):
"""Factory to get free ports that tracks used ports per test session."""
used_ports = set()
def get_port():
while True:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
s.listen(1)
port = s.getsockname()[1]
if port not in used_ports:
used_ports.add(port)
return port
return get_port