mirror of
https://github.com/CloakHQ/CloakBrowser.git
synced 2026-06-23 11:41:46 +02:00
359 lines
14 KiB
Python
359 lines
14 KiB
Python
"""Tests for auto-update and version management."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import os
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from cloakbrowser.config import (
|
|
CHROMIUM_VERSION,
|
|
_version_newer,
|
|
_version_tuple,
|
|
get_chromium_version,
|
|
get_download_url,
|
|
get_effective_version,
|
|
get_platform_tag,
|
|
)
|
|
from cloakbrowser.download import (
|
|
_check_wrapper_update,
|
|
_get_latest_chromium_version,
|
|
_parse_checksums,
|
|
_should_check_for_update,
|
|
_verify_checksum,
|
|
)
|
|
|
|
|
|
class TestVersionComparison:
|
|
def test_version_tuple_parsing(self):
|
|
assert _version_tuple("145.0.7718.0") == (145, 0, 7718, 0)
|
|
assert _version_tuple("142.0.7444.175") == (142, 0, 7444, 175)
|
|
|
|
def test_newer_version(self):
|
|
assert _version_newer("145.0.7718.0", "142.0.7444.175") is True
|
|
|
|
def test_older_version(self):
|
|
assert _version_newer("142.0.7444.175", "145.0.7718.0") is False
|
|
|
|
def test_same_version(self):
|
|
assert _version_newer("142.0.7444.175", "142.0.7444.175") is False
|
|
|
|
def test_patch_bump(self):
|
|
assert _version_newer("142.0.7444.176", "142.0.7444.175") is True
|
|
|
|
def test_major_bump(self):
|
|
assert _version_newer("143.0.0.0", "142.9.9999.999") is True
|
|
|
|
def test_5th_segment_parsing(self):
|
|
assert _version_tuple("145.0.7632.109.2") == (145, 0, 7632, 109, 2)
|
|
|
|
def test_build_bump(self):
|
|
assert _version_newer("145.0.7632.109.3", "145.0.7632.109.2") is True
|
|
|
|
def test_build_suffix_newer_than_no_suffix(self):
|
|
assert _version_newer("145.0.7632.109.2", "145.0.7632.109") is True
|
|
|
|
def test_no_suffix_older_than_build_suffix(self):
|
|
assert _version_newer("145.0.7632.109", "145.0.7632.109.2") is False
|
|
|
|
def test_new_chromium_beats_old_build(self):
|
|
assert _version_newer("146.0.0.0", "145.0.7632.109.2") is True
|
|
|
|
|
|
class TestDownloadUrl:
|
|
def test_default_url_format(self):
|
|
url = get_download_url()
|
|
assert "cloakbrowser.dev" in url
|
|
assert f"chromium-v{get_chromium_version()}" in url
|
|
assert url.endswith(".tar.gz")
|
|
|
|
def test_custom_version_url(self):
|
|
url = get_download_url("145.0.7718.0")
|
|
assert "chromium-v145.0.7718.0" in url
|
|
|
|
def test_no_old_repo_reference(self):
|
|
url = get_download_url()
|
|
assert "chromium-stealth-builds" not in url
|
|
|
|
|
|
class TestShouldCheckForUpdate:
|
|
def test_disabled_by_env(self):
|
|
with patch.dict(os.environ, {"CLOAKBROWSER_AUTO_UPDATE": "false"}):
|
|
assert _should_check_for_update() is False
|
|
|
|
def test_disabled_by_env_case_insensitive(self):
|
|
with patch.dict(os.environ, {"CLOAKBROWSER_AUTO_UPDATE": "False"}):
|
|
assert _should_check_for_update() is False
|
|
|
|
def test_disabled_by_binary_override(self):
|
|
with patch.dict(os.environ, {"CLOAKBROWSER_BINARY_PATH": "/some/path"}):
|
|
assert _should_check_for_update() is False
|
|
|
|
def test_disabled_by_custom_download_url(self):
|
|
with patch.dict(
|
|
os.environ, {"CLOAKBROWSER_DOWNLOAD_URL": "https://my-mirror.com"}
|
|
):
|
|
assert _should_check_for_update() is False
|
|
|
|
def test_rate_limited(self, tmp_path):
|
|
import time
|
|
|
|
with patch.dict(
|
|
os.environ,
|
|
{
|
|
"CLOAKBROWSER_CACHE_DIR": str(tmp_path),
|
|
"CLOAKBROWSER_BINARY_PATH": "",
|
|
"CLOAKBROWSER_AUTO_UPDATE": "",
|
|
"CLOAKBROWSER_DOWNLOAD_URL": "",
|
|
},
|
|
):
|
|
check_file = tmp_path / ".last_update_check"
|
|
check_file.write_text(str(time.time()))
|
|
assert _should_check_for_update() is False
|
|
|
|
def test_stale_rate_limit_allows_check(self, tmp_path):
|
|
import time
|
|
|
|
with patch.dict(
|
|
os.environ,
|
|
{
|
|
"CLOAKBROWSER_CACHE_DIR": str(tmp_path),
|
|
"CLOAKBROWSER_BINARY_PATH": "",
|
|
"CLOAKBROWSER_AUTO_UPDATE": "",
|
|
"CLOAKBROWSER_DOWNLOAD_URL": "",
|
|
},
|
|
):
|
|
check_file = tmp_path / ".last_update_check"
|
|
check_file.write_text(str(time.time() - 7200)) # 2 hours ago
|
|
assert _should_check_for_update() is True
|
|
|
|
|
|
class TestEffectiveVersion:
|
|
def test_no_marker_returns_platform_version(self, tmp_path):
|
|
with patch.dict(os.environ, {"CLOAKBROWSER_CACHE_DIR": str(tmp_path)}):
|
|
assert get_effective_version() == get_chromium_version()
|
|
|
|
def test_marker_with_newer_version(self, tmp_path):
|
|
with patch.dict(os.environ, {"CLOAKBROWSER_CACHE_DIR": str(tmp_path)}):
|
|
marker = tmp_path / f"latest_version_{get_platform_tag()}"
|
|
marker.write_text("999.0.0.0")
|
|
# Binary doesn't exist, so should fall back
|
|
assert get_effective_version() == get_chromium_version()
|
|
|
|
def test_marker_with_older_version_ignored(self, tmp_path):
|
|
with patch.dict(os.environ, {"CLOAKBROWSER_CACHE_DIR": str(tmp_path)}):
|
|
marker = tmp_path / f"latest_version_{get_platform_tag()}"
|
|
marker.write_text("100.0.0.0")
|
|
assert get_effective_version() == get_chromium_version()
|
|
|
|
|
|
class TestGetLatestVersion:
|
|
"""Tests for _get_latest_chromium_version with platform-aware asset checking."""
|
|
|
|
def _make_assets(self, platforms: list[str]) -> list[dict]:
|
|
"""Helper to build asset list from platform tags."""
|
|
return [{"name": f"cloakbrowser-{p}.tar.gz"} for p in platforms]
|
|
|
|
def _platform_tarball(self) -> str:
|
|
return f"cloakbrowser-{get_platform_tag()}.tar.gz"
|
|
|
|
def test_parses_chromium_tag_with_platform_asset(self):
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = [
|
|
{
|
|
"tag_name": "chromium-v145.0.7718.0",
|
|
"draft": False,
|
|
"assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]),
|
|
},
|
|
]
|
|
mock_response.raise_for_status = MagicMock()
|
|
|
|
with patch("cloakbrowser.download.httpx.get", return_value=mock_response):
|
|
result = _get_latest_chromium_version()
|
|
assert result == "145.0.7718.0"
|
|
|
|
def test_skips_release_without_platform_asset(self):
|
|
"""If latest release has no asset for our platform, fall back to older release."""
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = [
|
|
{
|
|
"tag_name": "chromium-v145.0.7718.0",
|
|
"draft": False,
|
|
"assets": self._make_assets(["linux-x64"]), # Linux only
|
|
},
|
|
{
|
|
"tag_name": "chromium-v142.0.7444.175",
|
|
"draft": False,
|
|
"assets": self._make_assets(["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]),
|
|
},
|
|
]
|
|
mock_response.raise_for_status = MagicMock()
|
|
|
|
with patch("cloakbrowser.download.httpx.get", return_value=mock_response):
|
|
result = _get_latest_chromium_version()
|
|
tag = get_platform_tag()
|
|
if tag == "linux-x64":
|
|
assert result == "145.0.7718.0"
|
|
else:
|
|
assert result == "142.0.7444.175"
|
|
|
|
def test_skips_draft_releases(self):
|
|
mock_response = MagicMock()
|
|
all_platforms = ["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]
|
|
mock_response.json.return_value = [
|
|
{"tag_name": "chromium-v999.0.0.0", "draft": True, "assets": self._make_assets(all_platforms)},
|
|
{"tag_name": "chromium-v145.0.7718.0", "draft": False, "assets": self._make_assets(all_platforms)},
|
|
]
|
|
mock_response.raise_for_status = MagicMock()
|
|
|
|
with patch("cloakbrowser.download.httpx.get", return_value=mock_response):
|
|
result = _get_latest_chromium_version()
|
|
assert result == "145.0.7718.0"
|
|
|
|
def test_skips_non_chromium_tags(self):
|
|
mock_response = MagicMock()
|
|
all_platforms = ["linux-x64", "darwin-arm64", "darwin-x64", "windows-x64"]
|
|
mock_response.json.return_value = [
|
|
{"tag_name": "v0.2.0", "draft": False, "assets": self._make_assets(all_platforms)},
|
|
{"tag_name": "chromium-v145.0.7718.0", "draft": False, "assets": self._make_assets(all_platforms)},
|
|
]
|
|
mock_response.raise_for_status = MagicMock()
|
|
|
|
with patch("cloakbrowser.download.httpx.get", return_value=mock_response):
|
|
result = _get_latest_chromium_version()
|
|
assert result == "145.0.7718.0"
|
|
|
|
def test_returns_none_when_no_platform_assets(self):
|
|
"""If no release has our platform, return None."""
|
|
mock_response = MagicMock()
|
|
mock_response.json.return_value = [
|
|
{
|
|
"tag_name": "chromium-v145.0.7718.0",
|
|
"draft": False,
|
|
"assets": [{"name": "cloakbrowser-freebsd-x64.tar.gz"}],
|
|
},
|
|
]
|
|
mock_response.raise_for_status = MagicMock()
|
|
|
|
with patch("cloakbrowser.download.httpx.get", return_value=mock_response):
|
|
result = _get_latest_chromium_version()
|
|
assert result is None
|
|
|
|
def test_network_error_returns_none(self):
|
|
with patch("cloakbrowser.download.httpx.get", side_effect=Exception("timeout")):
|
|
result = _get_latest_chromium_version()
|
|
assert result is None
|
|
|
|
|
|
class TestWrapperUpdateCheck:
|
|
"""Tests for _check_wrapper_update (PyPI version check)."""
|
|
|
|
def setup_method(self):
|
|
import cloakbrowser.download as dl
|
|
dl._wrapper_update_checked = False
|
|
|
|
def test_warns_when_newer_version_available(self, caplog):
|
|
mock_resp = MagicMock()
|
|
mock_resp.json.return_value = {"info": {"version": "99.0.0"}}
|
|
mock_resp.raise_for_status = MagicMock()
|
|
|
|
with patch("cloakbrowser.download.httpx.get", return_value=mock_resp):
|
|
import logging
|
|
with caplog.at_level(logging.WARNING):
|
|
_check_wrapper_update()
|
|
assert "Update available" in caplog.text
|
|
assert "99.0.0" in caplog.text
|
|
|
|
def test_silent_when_current(self, caplog):
|
|
import cloakbrowser.download as dl
|
|
mock_resp = MagicMock()
|
|
mock_resp.json.return_value = {"info": {"version": dl._wrapper_version}}
|
|
mock_resp.raise_for_status = MagicMock()
|
|
|
|
with patch("cloakbrowser.download.httpx.get", return_value=mock_resp):
|
|
import logging
|
|
with caplog.at_level(logging.WARNING):
|
|
_check_wrapper_update()
|
|
assert "Update available" not in caplog.text
|
|
|
|
def test_disabled_by_auto_update_env(self):
|
|
with patch.dict(os.environ, {"CLOAKBROWSER_AUTO_UPDATE": "false"}):
|
|
with patch("cloakbrowser.download.httpx.get") as mock_get:
|
|
_check_wrapper_update()
|
|
mock_get.assert_not_called()
|
|
|
|
def test_disabled_by_custom_download_url(self):
|
|
with patch.dict(os.environ, {"CLOAKBROWSER_DOWNLOAD_URL": "https://mirror.example.com"}):
|
|
with patch("cloakbrowser.download.httpx.get") as mock_get:
|
|
_check_wrapper_update()
|
|
mock_get.assert_not_called()
|
|
|
|
def test_network_error_silent(self, caplog):
|
|
with patch("cloakbrowser.download.httpx.get", side_effect=Exception("timeout")):
|
|
import logging
|
|
with caplog.at_level(logging.WARNING):
|
|
_check_wrapper_update()
|
|
assert "Update available" not in caplog.text
|
|
|
|
def test_runs_only_once(self):
|
|
mock_resp = MagicMock()
|
|
mock_resp.json.return_value = {"info": {"version": "0.0.1"}}
|
|
mock_resp.raise_for_status = MagicMock()
|
|
|
|
with patch("cloakbrowser.download.httpx.get", return_value=mock_resp) as mock_get:
|
|
_check_wrapper_update()
|
|
_check_wrapper_update()
|
|
assert mock_get.call_count == 1
|
|
|
|
|
|
class TestParseChecksums:
|
|
HASH_A = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
HASH_B = "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
|
|
|
|
def test_standard_format(self):
|
|
text = (
|
|
f"{self.HASH_A} cloakbrowser-linux-x64.tar.gz\n"
|
|
f"{self.HASH_B} cloakbrowser-darwin-arm64.tar.gz\n"
|
|
)
|
|
result = _parse_checksums(text)
|
|
assert result["cloakbrowser-linux-x64.tar.gz"] == self.HASH_A
|
|
assert result["cloakbrowser-darwin-arm64.tar.gz"] == self.HASH_B
|
|
|
|
def test_binary_mode_asterisk(self):
|
|
text = f"{self.HASH_A} *cloakbrowser-linux-x64.tar.gz\n"
|
|
result = _parse_checksums(text)
|
|
assert "cloakbrowser-linux-x64.tar.gz" in result
|
|
|
|
def test_empty_lines_skipped(self):
|
|
text = f"\n\n{self.HASH_A} file.tar.gz\n\n"
|
|
result = _parse_checksums(text)
|
|
assert len(result) == 1
|
|
|
|
def test_uppercase_lowered(self):
|
|
text = f"{self.HASH_A.upper()} file.tar.gz\n"
|
|
result = _parse_checksums(text)
|
|
assert result["file.tar.gz"] == self.HASH_A
|
|
|
|
def test_empty_input(self):
|
|
assert _parse_checksums("") == {}
|
|
assert _parse_checksums(" \n \n") == {}
|
|
|
|
|
|
class TestVerifyChecksum:
|
|
def test_matching_checksum(self, tmp_path):
|
|
content = b"test binary content"
|
|
file = tmp_path / "test.tar.gz"
|
|
file.write_bytes(content)
|
|
expected = hashlib.sha256(content).hexdigest()
|
|
# Should not raise
|
|
_verify_checksum(file, expected)
|
|
|
|
def test_mismatched_checksum(self, tmp_path):
|
|
file = tmp_path / "test.tar.gz"
|
|
file.write_bytes(b"real content")
|
|
with pytest.raises(RuntimeError, match="Checksum verification failed"):
|
|
_verify_checksum(file, "0" * 64)
|