Add per-client Makefiles for C, Python, and Go with: - CLI mode: Tests --help, arg parsing, syntax validation - Library mode: Unit tests, import verification - Integration mode: API contract validation (with credentials) - Functional mode: Real-world scenario tests C client: - 22 library tests (SHA-256, HMAC-SHA256, detect_language) - Full unsandbox.c implementation with examples Python client (sync + async): - Delegates to sync/ and async/ subdirectories - pytest-based test suites with coverage - Examples for concurrent execution, streaming Go client: - Delegates to sync/ and async/ subdirectories - go test integration with vet and fmt Also update detect-changes.sh to detect changes in both root-level un.* files AND clients/ directory.
173 lines
6.5 KiB
Python
173 lines
6.5 KiB
Python
"""Tests for languages cache functionality"""
|
|
|
|
import os
|
|
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
import time
|
|
from unittest.mock import patch, MagicMock
|
|
from un import _load_languages_cache, _save_languages_cache
|
|
|
|
|
|
class TestLanguagesCaching:
|
|
"""Test languages list caching"""
|
|
|
|
def test_save_and_load_cache(self):
|
|
"""Test saving and loading languages cache"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
languages = ["python", "javascript", "go"]
|
|
|
|
# Mock the cache path
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
_save_languages_cache(languages)
|
|
|
|
# Verify file was created
|
|
assert cache_path.exists()
|
|
|
|
# Load and verify
|
|
loaded = _load_languages_cache()
|
|
assert loaded == languages
|
|
|
|
def test_cache_ttl_expiration(self):
|
|
"""Test that cache expires after TTL"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
languages = ["python", "javascript"]
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
_save_languages_cache(languages)
|
|
|
|
# Make the file old
|
|
old_time = time.time() - 7200 # 2 hours ago
|
|
os.utime(cache_path, (old_time, old_time))
|
|
|
|
# Should return None (expired)
|
|
loaded = _load_languages_cache()
|
|
assert loaded is None
|
|
|
|
def test_cache_not_expired_within_ttl(self):
|
|
"""Test that cache is valid within TTL"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
languages = ["python", "javascript", "go", "rust"]
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
_save_languages_cache(languages)
|
|
|
|
# Should still be valid
|
|
loaded = _load_languages_cache()
|
|
assert loaded == languages
|
|
|
|
def test_cache_corrupted_json(self):
|
|
"""Test that corrupted JSON is handled gracefully"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
# Write invalid JSON
|
|
with open(cache_path, "w") as f:
|
|
f.write("invalid json {{{")
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
# Should return None on JSON error
|
|
loaded = _load_languages_cache()
|
|
assert loaded is None
|
|
|
|
def test_cache_missing_languages_key(self):
|
|
"""Test cache file with missing 'languages' key"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
# Write valid JSON but missing 'languages' key
|
|
with open(cache_path, "w") as f:
|
|
json.dump({"timestamp": int(time.time())}, f)
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
# Should return None when 'languages' key is missing
|
|
loaded = _load_languages_cache()
|
|
assert loaded is None
|
|
|
|
def test_cache_nonexistent_file(self):
|
|
"""Test loading cache when file doesn't exist"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "nonexistent.json"
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
loaded = _load_languages_cache()
|
|
assert loaded is None
|
|
|
|
def test_cache_permissions_error(self):
|
|
"""Test that permission errors are handled gracefully"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
# Create a directory instead of a file
|
|
os.makedirs(cache_path, exist_ok=True)
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
# Should return None on permission error
|
|
loaded = _load_languages_cache()
|
|
assert loaded is None
|
|
|
|
def test_cache_content_format(self):
|
|
"""Test that cache file has correct format"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
languages = ["python", "javascript"]
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
_save_languages_cache(languages)
|
|
|
|
# Read and verify format
|
|
with open(cache_path, "r") as f:
|
|
data = json.load(f)
|
|
|
|
assert "languages" in data
|
|
assert "timestamp" in data
|
|
assert data["languages"] == languages
|
|
assert isinstance(data["timestamp"], int)
|
|
|
|
def test_cache_save_permission_error(self):
|
|
"""Test that cache save errors are handled gracefully"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
# Create a read-only directory
|
|
cache_path = Path(tmpdir) / "readonly"
|
|
cache_path.mkdir(mode=0o444)
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path / "languages.json"):
|
|
# Should not raise, just silently fail
|
|
try:
|
|
_save_languages_cache(["python"])
|
|
except Exception:
|
|
# Permission errors might still occur, but should be caught
|
|
pass
|
|
|
|
def test_cache_empty_languages_list(self):
|
|
"""Test caching empty languages list"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
languages = []
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
_save_languages_cache(languages)
|
|
loaded = _load_languages_cache()
|
|
assert loaded == []
|
|
|
|
def test_cache_large_languages_list(self):
|
|
"""Test caching large languages list"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
cache_path = Path(tmpdir) / "languages.json"
|
|
|
|
# Create a list with 100 languages
|
|
languages = [f"lang_{i}" for i in range(100)]
|
|
|
|
with patch("un._get_languages_cache_path", return_value=cache_path):
|
|
_save_languages_cache(languages)
|
|
loaded = _load_languages_cache()
|
|
assert loaded == languages
|
|
assert len(loaded) == 100
|