Files
wehub-resource-sync 60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:59 +08:00

316 lines
11 KiB
Python

"""Tests for version checking utilities."""
import json
import time
from pathlib import Path
from unittest.mock import MagicMock, patch
import httpx
import pytest
from fastmcp.utilities.version_check import (
CACHE_TTL_SECONDS,
_fetch_latest_version,
_get_cache_path,
_read_cache,
_write_cache,
check_for_newer_version,
get_latest_version,
)
class TestCachePath:
def test_cache_path_in_home_directory(self):
"""Cache file should be in fastmcp home directory."""
cache_path = _get_cache_path()
assert cache_path.name == "version_cache.json"
assert "fastmcp" in str(cache_path).lower()
def test_cache_path_prerelease_suffix(self):
"""Prerelease cache uses different file."""
cache_path = _get_cache_path(include_prereleases=True)
assert cache_path.name == "version_cache_prerelease.json"
class TestReadCache:
def test_read_cache_no_file(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
"""Reading non-existent cache returns None."""
monkeypatch.setattr(
"fastmcp.utilities.version_check._get_cache_path",
lambda include_prereleases=False: tmp_path / "nonexistent.json",
)
version, timestamp = _read_cache()
assert version is None
assert timestamp == 0
def test_read_cache_valid(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
"""Reading valid cache returns version and timestamp."""
cache_file = tmp_path / "version_cache.json"
cache_file.write_text(
json.dumps({"latest_version": "2.5.0", "timestamp": 1000})
)
monkeypatch.setattr(
"fastmcp.utilities.version_check._get_cache_path",
lambda include_prereleases=False: cache_file,
)
version, timestamp = _read_cache()
assert version == "2.5.0"
assert timestamp == 1000
def test_read_cache_invalid_json(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
"""Reading invalid JSON returns None."""
cache_file = tmp_path / "version_cache.json"
cache_file.write_text("not valid json")
monkeypatch.setattr(
"fastmcp.utilities.version_check._get_cache_path",
lambda include_prereleases=False: cache_file,
)
version, timestamp = _read_cache()
assert version is None
assert timestamp == 0
class TestWriteCache:
def test_write_cache_creates_file(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
"""Writing cache creates the cache file."""
cache_file = tmp_path / "subdir" / "version_cache.json"
monkeypatch.setattr(
"fastmcp.utilities.version_check._get_cache_path",
lambda include_prereleases=False: cache_file,
)
_write_cache("2.6.0")
assert cache_file.exists()
data = json.loads(cache_file.read_text())
assert data["latest_version"] == "2.6.0"
assert "timestamp" in data
class TestFetchLatestVersion:
def test_fetch_success(self):
"""Successful fetch returns highest stable version."""
mock_response = MagicMock()
mock_response.json.return_value = {
"releases": {
"2.5.0": [],
"2.4.0": [],
"2.6.0b1": [], # prerelease should be skipped
}
}
with patch("httpx.get", return_value=mock_response) as mock_get:
version = _fetch_latest_version()
assert version == "2.5.0"
mock_get.assert_called_once()
def test_fetch_network_error(self):
"""Network error returns None."""
with patch("httpx.get", side_effect=httpx.HTTPError("Network error")):
version = _fetch_latest_version()
assert version is None
def test_fetch_invalid_response(self):
"""Invalid response returns None."""
mock_response = MagicMock()
mock_response.json.return_value = {"unexpected": "format"}
with patch("httpx.get", return_value=mock_response):
version = _fetch_latest_version()
assert version is None
def test_fetch_prereleases(self):
"""Fetching with prereleases returns highest version."""
mock_response = MagicMock()
mock_response.json.return_value = {
"info": {"version": "2.5.0"},
"releases": {
"2.5.0": [],
"2.6.0b1": [],
"2.6.0b2": [],
"2.4.0": [],
},
}
with patch("httpx.get", return_value=mock_response):
version = _fetch_latest_version(include_prereleases=True)
assert version == "2.6.0b2"
def test_fetch_prereleases_stable_is_highest(self):
"""Prerelease mode still returns stable if it's highest."""
mock_response = MagicMock()
mock_response.json.return_value = {
"info": {"version": "2.5.0"},
"releases": {
"2.5.0": [],
"2.5.0b1": [],
"2.4.0": [],
},
}
with patch("httpx.get", return_value=mock_response):
version = _fetch_latest_version(include_prereleases=True)
assert version == "2.5.0"
class TestGetLatestVersion:
def test_returns_cached_version_if_fresh(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
"""Uses cached version if cache is fresh."""
cache_file = tmp_path / "version_cache.json"
cache_file.write_text(
json.dumps({"latest_version": "2.5.0", "timestamp": time.time()})
)
monkeypatch.setattr(
"fastmcp.utilities.version_check._get_cache_path",
lambda include_prereleases=False: cache_file,
)
with patch(
"fastmcp.utilities.version_check._fetch_latest_version"
) as mock_fetch:
version = get_latest_version()
assert version == "2.5.0"
mock_fetch.assert_not_called()
def test_fetches_if_cache_stale(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
"""Fetches from PyPI if cache is stale."""
cache_file = tmp_path / "version_cache.json"
old_timestamp = time.time() - CACHE_TTL_SECONDS - 100
cache_file.write_text(
json.dumps({"latest_version": "2.4.0", "timestamp": old_timestamp})
)
monkeypatch.setattr(
"fastmcp.utilities.version_check._get_cache_path",
lambda include_prereleases=False: cache_file,
)
with patch(
"fastmcp.utilities.version_check._fetch_latest_version",
return_value="2.5.0",
) as mock_fetch:
version = get_latest_version()
assert version == "2.5.0"
mock_fetch.assert_called_once()
def test_returns_stale_cache_if_fetch_fails(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
"""Returns stale cache if fetch fails."""
cache_file = tmp_path / "version_cache.json"
old_timestamp = time.time() - CACHE_TTL_SECONDS - 100
cache_file.write_text(
json.dumps({"latest_version": "2.4.0", "timestamp": old_timestamp})
)
monkeypatch.setattr(
"fastmcp.utilities.version_check._get_cache_path",
lambda include_prereleases=False: cache_file,
)
with patch(
"fastmcp.utilities.version_check._fetch_latest_version", return_value=None
):
version = get_latest_version()
assert version == "2.4.0"
class TestCheckForNewerVersion:
def test_returns_none_if_disabled(self, monkeypatch: pytest.MonkeyPatch):
"""Returns None if check_for_updates is off."""
import fastmcp
monkeypatch.setattr(fastmcp.settings, "check_for_updates", "off")
result = check_for_newer_version()
assert result is None
def test_returns_none_if_current(self, monkeypatch: pytest.MonkeyPatch):
"""Returns None if current version is latest."""
import fastmcp
monkeypatch.setattr(fastmcp.settings, "check_for_updates", "stable")
monkeypatch.setattr(fastmcp, "__version__", "2.5.0")
with patch(
"fastmcp.utilities.version_check.get_latest_version", return_value="2.5.0"
):
result = check_for_newer_version()
assert result is None
def test_returns_version_if_newer(self, monkeypatch: pytest.MonkeyPatch):
"""Returns new version if available."""
import fastmcp
monkeypatch.setattr(fastmcp.settings, "check_for_updates", "stable")
monkeypatch.setattr(fastmcp, "__version__", "2.4.0")
with patch(
"fastmcp.utilities.version_check.get_latest_version", return_value="2.5.0"
):
result = check_for_newer_version()
assert result == "2.5.0"
def test_returns_none_if_older_available(self, monkeypatch: pytest.MonkeyPatch):
"""Returns None if pypi version is older than current (dev version)."""
import fastmcp
monkeypatch.setattr(fastmcp.settings, "check_for_updates", "stable")
monkeypatch.setattr(fastmcp, "__version__", "3.0.0.dev1")
with patch(
"fastmcp.utilities.version_check.get_latest_version", return_value="2.5.0"
):
result = check_for_newer_version()
assert result is None
def test_handles_invalid_versions(self, monkeypatch: pytest.MonkeyPatch):
"""Handles invalid version strings gracefully."""
import fastmcp
monkeypatch.setattr(fastmcp.settings, "check_for_updates", "stable")
monkeypatch.setattr(fastmcp, "__version__", "invalid")
with patch(
"fastmcp.utilities.version_check.get_latest_version",
return_value="also-invalid",
):
result = check_for_newer_version()
assert result is None
def test_prerelease_setting(self, monkeypatch: pytest.MonkeyPatch):
"""Prerelease setting passes include_prereleases=True."""
import fastmcp
monkeypatch.setattr(fastmcp.settings, "check_for_updates", "prerelease")
monkeypatch.setattr(fastmcp, "__version__", "2.5.0")
with patch(
"fastmcp.utilities.version_check.get_latest_version", return_value="2.6.0b1"
) as mock_get:
result = check_for_newer_version()
assert result == "2.6.0b1"
mock_get.assert_called_once_with(True)
def test_stable_setting(self, monkeypatch: pytest.MonkeyPatch):
"""Stable setting passes include_prereleases=False."""
import fastmcp
monkeypatch.setattr(fastmcp.settings, "check_for_updates", "stable")
monkeypatch.setattr(fastmcp, "__version__", "2.4.0")
with patch(
"fastmcp.utilities.version_check.get_latest_version", return_value="2.5.0"
) as mock_get:
result = check_for_newer_version()
assert result == "2.5.0"
mock_get.assert_called_once_with(False)