test: add unit tests and fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
153
tests/test_cache.py
Normal file
153
tests/test_cache.py
Normal file
@@ -0,0 +1,153 @@
|
||||
"""Unit tests for src/cache.py."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from src.cache import Cache, CacheError, MANIFEST_VERSION
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_cache(tmp_path):
|
||||
return Cache(tmp_path)
|
||||
|
||||
|
||||
class TestIsCached:
|
||||
def test_miss_when_no_entry(self, tmp_cache):
|
||||
assert tmp_cache.is_cached("claude", "conv-abc", "2024-01-01T00:00:00Z") is False
|
||||
|
||||
def test_hit_after_mark_exported(self, tmp_cache):
|
||||
tmp_cache.mark_exported("claude", "conv-abc", {"updated_at": "2024-01-01T00:00:00Z"})
|
||||
assert tmp_cache.is_cached("claude", "conv-abc", "2024-01-01T00:00:00Z") is True
|
||||
|
||||
def test_stale_when_provider_has_newer_date(self, tmp_cache):
|
||||
tmp_cache.mark_exported("claude", "conv-abc", {"updated_at": "2024-01-01T00:00:00Z"})
|
||||
assert tmp_cache.is_cached("claude", "conv-abc", "2024-06-01T00:00:00Z") is False
|
||||
|
||||
def test_hit_when_provider_has_same_date(self, tmp_cache):
|
||||
tmp_cache.mark_exported("chatgpt", "conv-xyz", {"updated_at": "2024-06-01T00:00:00Z"})
|
||||
assert tmp_cache.is_cached("chatgpt", "conv-xyz", "2024-06-01T00:00:00Z") is True
|
||||
|
||||
def test_miss_for_different_provider(self, tmp_cache):
|
||||
tmp_cache.mark_exported("claude", "conv-abc", {"updated_at": "2024-01-01T00:00:00Z"})
|
||||
assert tmp_cache.is_cached("chatgpt", "conv-abc", "2024-01-01T00:00:00Z") is False
|
||||
|
||||
|
||||
class TestAtomicWrite:
|
||||
def test_manifest_has_600_permissions(self, tmp_path):
|
||||
c = Cache(tmp_path)
|
||||
c.mark_exported("claude", "x", {"updated_at": "2024-01-01"})
|
||||
manifest = tmp_path / "manifest.json"
|
||||
mode = oct(os.stat(manifest).st_mode)[-3:]
|
||||
assert mode == "600"
|
||||
|
||||
def test_no_tmp_file_left_after_write(self, tmp_path):
|
||||
c = Cache(tmp_path)
|
||||
c.mark_exported("claude", "x", {"updated_at": "2024-01-01"})
|
||||
tmp_files = list(tmp_path.glob("*.tmp"))
|
||||
assert tmp_files == []
|
||||
|
||||
def test_manifest_is_valid_json(self, tmp_path):
|
||||
c = Cache(tmp_path)
|
||||
c.mark_exported("claude", "x", {})
|
||||
manifest = tmp_path / "manifest.json"
|
||||
data = json.loads(manifest.read_text())
|
||||
assert isinstance(data, dict)
|
||||
assert "claude" in data
|
||||
|
||||
|
||||
class TestStats:
|
||||
def test_empty_stats(self, tmp_cache):
|
||||
stats = tmp_cache.stats()
|
||||
assert stats["chatgpt"] == 0
|
||||
assert stats["claude"] == 0
|
||||
|
||||
def test_stats_after_exports(self, tmp_cache):
|
||||
tmp_cache.mark_exported("claude", "c1", {})
|
||||
tmp_cache.mark_exported("claude", "c2", {})
|
||||
tmp_cache.mark_exported("chatgpt", "g1", {})
|
||||
stats = tmp_cache.stats()
|
||||
assert stats["claude"] == 2
|
||||
assert stats["chatgpt"] == 1
|
||||
|
||||
|
||||
class TestClear:
|
||||
def test_clear_single_provider(self, tmp_cache):
|
||||
tmp_cache.mark_exported("claude", "c1", {})
|
||||
tmp_cache.mark_exported("chatgpt", "g1", {})
|
||||
tmp_cache.clear("claude")
|
||||
assert tmp_cache.stats()["claude"] == 0
|
||||
assert tmp_cache.stats()["chatgpt"] == 1
|
||||
|
||||
def test_clear_all(self, tmp_cache):
|
||||
tmp_cache.mark_exported("claude", "c1", {})
|
||||
tmp_cache.mark_exported("chatgpt", "g1", {})
|
||||
tmp_cache.clear()
|
||||
assert tmp_cache.stats()["claude"] == 0
|
||||
assert tmp_cache.stats()["chatgpt"] == 0
|
||||
|
||||
|
||||
class TestCorruptManifestRecovery:
|
||||
def test_recovers_from_invalid_json(self, tmp_path):
|
||||
manifest = tmp_path / "manifest.json"
|
||||
manifest.write_text("{invalid json!!!", encoding="utf-8")
|
||||
# Should not raise, should start fresh
|
||||
c = Cache(tmp_path)
|
||||
assert c.stats()["claude"] == 0
|
||||
# Backup should exist
|
||||
backup = tmp_path / "manifest.json.bak"
|
||||
assert backup.exists()
|
||||
assert backup.read_text() == "{invalid json!!!"
|
||||
|
||||
def test_raises_on_future_version(self, tmp_path):
|
||||
manifest = tmp_path / "manifest.json"
|
||||
manifest.write_text(
|
||||
json.dumps({"version": MANIFEST_VERSION + 99, "chatgpt": {}, "claude": {}}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
with pytest.raises(CacheError, match="Unsupported manifest version"):
|
||||
Cache(tmp_path)
|
||||
|
||||
|
||||
class TestTosAcknowledgement:
|
||||
def test_not_acknowledged_by_default(self, tmp_cache):
|
||||
assert tmp_cache.is_tos_acknowledged() is False
|
||||
|
||||
def test_acknowledged_after_call(self, tmp_cache):
|
||||
tmp_cache.acknowledge_tos()
|
||||
assert tmp_cache.is_tos_acknowledged() is True
|
||||
|
||||
def test_acknowledgement_persists_across_instances(self, tmp_path):
|
||||
c1 = Cache(tmp_path)
|
||||
c1.acknowledge_tos()
|
||||
c2 = Cache(tmp_path)
|
||||
assert c2.is_tos_acknowledged() is True
|
||||
|
||||
|
||||
class TestGetNewOrUpdated:
|
||||
def test_returns_all_when_cache_empty(self, tmp_cache):
|
||||
convs = [
|
||||
{"id": "a", "updated_at": "2024-01-01T00:00:00Z"},
|
||||
{"id": "b", "updated_at": "2024-01-02T00:00:00Z"},
|
||||
]
|
||||
result = tmp_cache.get_new_or_updated("claude", convs)
|
||||
assert len(result) == 2
|
||||
|
||||
def test_skips_cached_unchanged(self, tmp_cache):
|
||||
tmp_cache.mark_exported("claude", "a", {"updated_at": "2024-01-01T00:00:00Z"})
|
||||
convs = [
|
||||
{"id": "a", "updated_at": "2024-01-01T00:00:00Z"},
|
||||
{"id": "b", "updated_at": "2024-01-02T00:00:00Z"},
|
||||
]
|
||||
result = tmp_cache.get_new_or_updated("claude", convs)
|
||||
assert len(result) == 1
|
||||
assert result[0]["id"] == "b"
|
||||
|
||||
def test_includes_stale_conversations(self, tmp_cache):
|
||||
tmp_cache.mark_exported("claude", "a", {"updated_at": "2024-01-01T00:00:00Z"})
|
||||
convs = [{"id": "a", "updated_at": "2024-06-01T00:00:00Z"}]
|
||||
result = tmp_cache.get_new_or_updated("claude", convs)
|
||||
assert len(result) == 1
|
||||
Reference in New Issue
Block a user