ai-agent / tests /test_previews_cache.py
katospiegel's picture
Deploy develop: FastAPI+React frontend, multi-stage Docker (ai_agent serve)
07c2476 verified
Raw
History Blame Contribute Delete
4.09 kB
from __future__ import annotations
import json
from pathlib import Path
import pytest
from PIL import Image
from ai_agent.utils import previews
from ai_agent.utils import cache_db
from ai_agent.utils.cache_db import CacheDB, reset_cache_db
@pytest.fixture(autouse=True)
def _isolated_cache_db():
"""Give every test a fresh in-memory SQLite cache."""
db = CacheDB(":memory:")
reset_cache_db(db)
yield
reset_cache_db(None)
def test_preview_cache_roundtrip_hit(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
key = ("/tmp/example.png::1::1",)
preview_file = tmp_path / "preview.png"
preview_file.write_bytes(b"x")
monkeypatch.setattr(previews, "PREVIEW_CACHE_TTL_SECONDS", 30)
previews._preview_cache_set(key, str(preview_file), "meta")
out_path, out_meta = previews._preview_cache_get(key)
assert out_path == str(preview_file)
assert out_meta == "meta"
def test_preview_cache_evicts_expired_entries(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
):
key = ("/tmp/expired.png::1::1",)
preview_file = tmp_path / "expired.png"
preview_file.write_bytes(b"x")
now = {"t": 1_000_000.0}
monkeypatch.setattr(cache_db.time, "time", lambda: now["t"])
monkeypatch.setattr(previews, "PREVIEW_CACHE_TTL_SECONDS", 5)
previews._preview_cache_set(key, str(preview_file), "meta")
now["t"] = 1_000_006.0 # past TTL
out_path, out_meta = previews._preview_cache_get(key)
assert out_path is None
assert out_meta is None
def test_preview_cache_drops_missing_file_entry(monkeypatch: pytest.MonkeyPatch):
key = ("/tmp/missing.png::1::1",)
monkeypatch.setattr(previews, "PREVIEW_CACHE_TTL_SECONDS", 3000)
# Manually insert an entry pointing to a non-existent file
db_key = json.dumps(key)
value = json.dumps({"path": "/tmp/does-not-exist.png", "meta": "meta"})
cache_db.get_cache_db().set(
previews._PREVIEW_NS, db_key, value, ttl_seconds=3000
)
out_path, out_meta = previews._preview_cache_get(key)
assert out_path is None
assert out_meta is None
def test_preview_cache_capacity_eviction(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(previews, "PREVIEW_CACHE_TTL_SECONDS", 100)
monkeypatch.setattr(previews, "PREVIEW_CACHE_MAX_ENTRIES", 2)
now = {"t": 1_000_000.0}
monkeypatch.setattr(cache_db.time, "time", lambda: now["t"])
a = tmp_path / "a.png"
b = tmp_path / "b.png"
c = tmp_path / "c.png"
a.write_bytes(b"a")
b.write_bytes(b"b")
c.write_bytes(b"c")
key_a = ("a",)
key_b = ("b",)
key_c = ("c",)
now["t"] = 1_000_001.0
previews._preview_cache_set(key_a, str(a), "a")
now["t"] = 1_000_002.0
previews._preview_cache_set(key_b, str(b), "b")
now["t"] = 1_000_003.0
previews._preview_cache_set(key_c, str(c), "c")
# key_a should have been evicted (oldest-accessed, capacity=2)
assert previews._preview_cache_get(key_a) == (None, None)
assert previews._preview_cache_get(key_b)[0] == str(b)
assert previews._preview_cache_get(key_c)[0] == str(c)
def test_preview_cache_clear_helper_empties_cache(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(previews, "PREVIEW_CACHE_TTL_SECONDS", 30)
preview_file = tmp_path / "x.png"
preview_file.write_bytes(b"x")
previews._preview_cache_set(("x",), str(preview_file), "meta")
previews._clear_preview_cache_for_tests()
assert previews._preview_cache_get(("x",)) == (None, None)
def test_resize_for_preview_preserves_aspect_ratio():
img = Image.new("L", (2000, 1000), color=128)
resized = previews._resize_for_preview(img, max_side_px=500)
assert resized.size == (500, 250)
def test_resize_for_preview_does_not_upscale():
img = Image.new("RGB", (320, 200), color=(0, 0, 0))
resized = previews._resize_for_preview(img, max_side_px=500)
assert resized.size == (320, 200)