un-inception/clients/python/sync/tests/test_caching.py
russell@unturf.com 1e01d09883 feat: per-client Makefile infrastructure for 4-mode testing
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.
2026-01-15 16:39:56 -05:00

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