File size: 4,087 Bytes
901e5fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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)