""" Unit tests for the Pascal language server auto-update functionality. These tests validate the version comparison, checksum verification, and other helper methods without requiring network access or the actual Pascal language server. """ from __future__ import annotations import hashlib import os import tarfile import tempfile import time import pytest from solidlsp.language_servers.pascal_server import PascalLanguageServer pytestmark = [pytest.mark.pascal] class TestVersionNormalization: """Test version string normalization.""" def test_normalize_version_with_v_prefix(self) -> None: """Test that 'v' prefix is stripped.""" assert PascalLanguageServer._normalize_version("v1.0.0") == "1.0.0" def test_normalize_version_with_capital_v_prefix(self) -> None: """Test that 'V' prefix is stripped.""" assert PascalLanguageServer._normalize_version("V1.0.0") == "1.0.0" def test_normalize_version_without_prefix(self) -> None: """Test version without prefix is unchanged.""" assert PascalLanguageServer._normalize_version("1.0.0") == "1.0.0" def test_normalize_version_with_whitespace(self) -> None: """Test that whitespace is stripped.""" assert PascalLanguageServer._normalize_version(" v1.0.0 ") == "1.0.0" def test_normalize_version_empty(self) -> None: """Test empty version returns empty string.""" assert PascalLanguageServer._normalize_version("") == "" def test_normalize_version_none(self) -> None: """Test None returns empty string.""" assert PascalLanguageServer._normalize_version(None) == "" class TestVersionComparison: """Test version comparison logic.""" def test_newer_version_major(self) -> None: """Test detection of newer major version.""" assert PascalLanguageServer._is_newer_version("v2.0.0", "v1.0.0") is True def test_newer_version_minor(self) -> None: """Test detection of newer minor version.""" assert PascalLanguageServer._is_newer_version("v1.1.0", "v1.0.0") is True def test_newer_version_patch(self) -> None: """Test detection of newer patch version.""" assert PascalLanguageServer._is_newer_version("v1.0.1", "v1.0.0") is True def test_same_version(self) -> None: """Test same version returns False.""" assert PascalLanguageServer._is_newer_version("v1.0.0", "v1.0.0") is False def test_older_version(self) -> None: """Test older version returns False.""" assert PascalLanguageServer._is_newer_version("v1.0.0", "v2.0.0") is False def test_latest_none_returns_false(self) -> None: """Test None latest version returns False.""" assert PascalLanguageServer._is_newer_version(None, "v1.0.0") is False def test_local_none_returns_true(self) -> None: """Test None local version returns True (first install).""" assert PascalLanguageServer._is_newer_version("v1.0.0", None) is True def test_both_none_returns_false(self) -> None: """Test both None returns False.""" assert PascalLanguageServer._is_newer_version(None, None) is False def test_version_with_different_lengths(self) -> None: """Test versions with different number of parts.""" assert PascalLanguageServer._is_newer_version("v1.0.1", "v1.0") is True assert PascalLanguageServer._is_newer_version("v1.0", "v1.0.1") is False def test_version_with_prerelease(self) -> None: """Test versions with prerelease suffixes.""" # Prerelease suffix is ignored, only numeric parts are compared assert PascalLanguageServer._is_newer_version("v1.1.0-beta", "v1.0.0") is True class TestSHA256Checksum: """Test SHA256 checksum calculation and verification.""" def test_calculate_sha256(self) -> None: """Test SHA256 calculation for a known content.""" with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f: f.write(b"test content") temp_path = f.name try: result = PascalLanguageServer._calculate_sha256(temp_path) expected = hashlib.sha256(b"test content").hexdigest() assert result == expected finally: os.unlink(temp_path) def test_verify_checksum_correct(self) -> None: """Test checksum verification with correct checksum.""" with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f: f.write(b"test content") temp_path = f.name try: expected = hashlib.sha256(b"test content").hexdigest() assert PascalLanguageServer._verify_checksum(temp_path, expected) is True finally: os.unlink(temp_path) def test_verify_checksum_incorrect(self) -> None: """Test checksum verification with incorrect checksum.""" with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f: f.write(b"test content") temp_path = f.name try: wrong_checksum = "0" * 64 assert PascalLanguageServer._verify_checksum(temp_path, wrong_checksum) is False finally: os.unlink(temp_path) def test_verify_checksum_case_insensitive(self) -> None: """Test checksum verification is case insensitive.""" with tempfile.NamedTemporaryFile(mode="wb", delete=False) as f: f.write(b"test content") temp_path = f.name try: expected = hashlib.sha256(b"test content").hexdigest().upper() assert PascalLanguageServer._verify_checksum(temp_path, expected) is True finally: os.unlink(temp_path) class TestTarfileSafety: """Test tarfile path traversal protection.""" def test_safe_tar_member_normal_path(self) -> None: """Test normal path is considered safe.""" member = tarfile.TarInfo(name="pasls") assert PascalLanguageServer._is_safe_tar_member(member, "/tmp/target") is True def test_safe_tar_member_nested_path(self) -> None: """Test nested path is considered safe.""" member = tarfile.TarInfo(name="subdir/pasls") assert PascalLanguageServer._is_safe_tar_member(member, "/tmp/target") is True def test_unsafe_tar_member_path_traversal(self) -> None: """Test path traversal is detected.""" member = tarfile.TarInfo(name="../etc/passwd") assert PascalLanguageServer._is_safe_tar_member(member, "/tmp/target") is False def test_unsafe_tar_member_hidden_traversal(self) -> None: """Test hidden path traversal in nested path.""" member = tarfile.TarInfo(name="subdir/../../etc/passwd") assert PascalLanguageServer._is_safe_tar_member(member, "/tmp/target") is False def test_safe_tar_member_similar_name(self) -> None: """Test path containing '..' in filename (not directory) is safe.""" member = tarfile.TarInfo(name="file..name") assert PascalLanguageServer._is_safe_tar_member(member, "/tmp/target") is True class TestMetadataManagement: """Test metadata directory and file management.""" def test_meta_dir_creates_directory(self) -> None: """Test _meta_dir creates directory if not exists.""" with tempfile.TemporaryDirectory() as temp_dir: meta_path = PascalLanguageServer._meta_dir(temp_dir) assert os.path.exists(meta_path) assert meta_path == os.path.join(temp_dir, PascalLanguageServer.META_DIR) def test_meta_file_returns_correct_path(self) -> None: """Test _meta_file returns correct path.""" with tempfile.TemporaryDirectory() as temp_dir: meta_file = PascalLanguageServer._meta_file(temp_dir, "version") expected = os.path.join(temp_dir, PascalLanguageServer.META_DIR, "version") assert meta_file == expected class TestUpdateCheckTiming: """Test update check timing logic.""" def test_should_check_update_no_last_check(self) -> None: """Test should check when no last_check file exists.""" with tempfile.TemporaryDirectory() as temp_dir: assert PascalLanguageServer._should_check_update(temp_dir) is True def test_should_check_update_recent_check(self) -> None: """Test should not check when recently checked.""" with tempfile.TemporaryDirectory() as temp_dir: # Create meta dir and last_check file with current time meta_dir = PascalLanguageServer._meta_dir(temp_dir) last_check_file = os.path.join(meta_dir, "last_check") with open(last_check_file, "w") as f: f.write(str(time.time())) assert PascalLanguageServer._should_check_update(temp_dir) is False def test_should_check_update_old_check(self) -> None: """Test should check when last check was > 24 hours ago.""" with tempfile.TemporaryDirectory() as temp_dir: # Create meta dir and last_check file with old time meta_dir = PascalLanguageServer._meta_dir(temp_dir) last_check_file = os.path.join(meta_dir, "last_check") old_time = time.time() - (PascalLanguageServer.UPDATE_CHECK_INTERVAL + 3600) with open(last_check_file, "w") as f: f.write(str(old_time)) assert PascalLanguageServer._should_check_update(temp_dir) is True def test_update_last_check_creates_file(self) -> None: """Test _update_last_check creates timestamp file.""" with tempfile.TemporaryDirectory() as temp_dir: PascalLanguageServer._update_last_check(temp_dir) last_check_file = PascalLanguageServer._meta_file(temp_dir, "last_check") assert os.path.exists(last_check_file) with open(last_check_file) as f: timestamp = float(f.read().strip()) assert abs(timestamp - time.time()) < 5 # within 5 seconds class TestVersionPersistence: """Test local version persistence.""" def test_save_and_get_local_version(self) -> None: """Test saving and retrieving local version.""" with tempfile.TemporaryDirectory() as temp_dir: PascalLanguageServer._save_local_version(temp_dir, "v1.0.0") version = PascalLanguageServer._get_local_version(temp_dir) assert version == "v1.0.0" def test_get_local_version_not_exists(self) -> None: """Test getting version when file doesn't exist.""" with tempfile.TemporaryDirectory() as temp_dir: version = PascalLanguageServer._get_local_version(temp_dir) assert version is None