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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
"""Tests for the (path, size, mtime) -> hash cache (indexer/cache.py)."""
import pytest
from meshbay_node.indexer.cache import IndexCache
@pytest.fixture
async def cache(tmp_path):
c = IndexCache(db_path=tmp_path / "index_cache.db")
await c.open()
yield c
await c.close()
@pytest.mark.asyncio
async def test_put_then_lookup_hits(cache):
await cache.put("/lib/a.mkv", size=1000, mtime=111.0, hash="abc123",
type="video", added_at=42)
hit = await cache.lookup("/lib/a.mkv", size=1000, mtime=111.0)
assert hit is not None
assert hit.hash == "abc123"
assert hit.type == "video"
assert hit.added_at == 42
@pytest.mark.asyncio
async def test_lookup_misses_on_unknown_path(cache):
assert await cache.lookup("/lib/never-seen.mkv", size=1, mtime=1.0) is None
@pytest.mark.asyncio
async def test_lookup_misses_on_different_mtime(cache):
await cache.put("/lib/a.mkv", size=1000, mtime=111.0, hash="abc123",
type="video", added_at=42)
assert await cache.lookup("/lib/a.mkv", size=1000, mtime=222.0) is None
@pytest.mark.asyncio
async def test_lookup_misses_on_different_size(cache):
await cache.put("/lib/a.mkv", size=1000, mtime=111.0, hash="abc123",
type="video", added_at=42)
assert await cache.lookup("/lib/a.mkv", size=2000, mtime=111.0) is None
@pytest.mark.asyncio
async def test_put_overwrites_previous_row_for_same_path(cache):
await cache.put("/lib/a.mkv", size=1000, mtime=111.0, hash="old",
type="video", added_at=1)
await cache.put("/lib/a.mkv", size=2000, mtime=222.0, hash="new",
type="video", added_at=2)
assert await cache.lookup("/lib/a.mkv", size=1000, mtime=111.0) is None
hit = await cache.lookup("/lib/a.mkv", size=2000, mtime=222.0)
assert hit.hash == "new"
@pytest.mark.asyncio
async def test_count_reflects_number_of_rows(cache):
assert await cache.count() == 0
await cache.put("/lib/a.mkv", size=1000, mtime=111.0, hash="a",
type="video", added_at=1)
await cache.put("/lib/b.mkv", size=2000, mtime=222.0, hash="b",
type="video", added_at=2)
assert await cache.count() == 2
@pytest.mark.asyncio
async def test_all_paths_returns_every_row(cache):
await cache.put("/lib/a.mkv", size=1000, mtime=111.0, hash="a",
type="video", added_at=1)
await cache.put("/lib/b.mkv", size=2000, mtime=222.0, hash="b",
type="video", added_at=2)
assert set(await cache.all_paths()) == {"/lib/a.mkv", "/lib/b.mkv"}
@pytest.mark.asyncio
async def test_remove_many_drops_only_the_given_paths(cache):
await cache.put("/lib/a.mkv", size=1000, mtime=111.0, hash="a",
type="video", added_at=1)
await cache.put("/lib/b.mkv", size=2000, mtime=222.0, hash="b",
type="video", added_at=2)
await cache.remove_many(["/lib/a.mkv"])
assert await cache.lookup("/lib/a.mkv", size=1000, mtime=111.0) is None
assert await cache.lookup("/lib/b.mkv", size=2000, mtime=222.0) is not None
assert await cache.count() == 1
@pytest.mark.asyncio
async def test_remove_many_with_empty_list_is_a_no_op(cache):
await cache.put("/lib/a.mkv", size=1000, mtime=111.0, hash="a",
type="video", added_at=1)
await cache.remove_many([])
assert await cache.count() == 1
@pytest.mark.asyncio
async def test_cache_survives_reopen(tmp_path):
db_path = tmp_path / "index_cache.db"
c1 = IndexCache(db_path=db_path)
await c1.open()
await c1.put("/lib/a.mkv", size=1000, mtime=111.0, hash="abc123",
type="video", added_at=42)
await c1.close()
c2 = IndexCache(db_path=db_path)
await c2.open()
hit = await c2.lookup("/lib/a.mkv", size=1000, mtime=111.0)
await c2.close()
assert hit is not None
assert hit.hash == "abc123"
# ── hash_version support (indexing v2) ─────────────────────────────────────────
@pytest.mark.asyncio
async def test_put_v2_then_lookup_hits(cache):
await cache.put("/lib/big.mkv", size=50_000_000, mtime=111.0,
hash="partial_abc", type="video", added_at=42,
hash_version=2)
hit = await cache.lookup("/lib/big.mkv", size=50_000_000, mtime=111.0,
hash_version=2)
assert hit is not None
assert hit.hash == "partial_abc"
assert hit.hash_version == 2
@pytest.mark.asyncio
async def test_lookup_misses_on_wrong_hash_version(cache):
await cache.put("/lib/big.mkv", size=50_000_000, mtime=111.0,
hash="full_hash", type="video", added_at=42,
hash_version=1)
assert await cache.lookup("/lib/big.mkv", size=50_000_000, mtime=111.0,
hash_version=2) is None
@pytest.mark.asyncio
async def test_put_v2_overwrites_v1_for_same_path(cache):
await cache.put("/lib/big.mkv", size=50_000_000, mtime=111.0,
hash="full_hash", type="video", added_at=42,
hash_version=1)
await cache.put("/lib/big.mkv", size=50_000_000, mtime=111.0,
hash="partial_hash", type="video", added_at=42,
hash_version=2)
assert await cache.lookup("/lib/big.mkv", size=50_000_000, mtime=111.0,
hash_version=1) is None
hit = await cache.lookup("/lib/big.mkv", size=50_000_000, mtime=111.0,
hash_version=2)
assert hit is not None
assert hit.hash == "partial_hash"
@pytest.mark.asyncio
async def test_v1_schema_auto_migrates(tmp_path):
"""An index_cache.db created by old code (no hash_version column) gains
the column on the next open(), and existing rows default to hash_version=1."""
import aiosqlite
db_path = tmp_path / "old_cache.db"
async with aiosqlite.connect(str(db_path)) as db:
await db.executescript("""
CREATE TABLE files (
path TEXT PRIMARY KEY,
mtime REAL NOT NULL,
size INTEGER NOT NULL,
hash TEXT NOT NULL,
type TEXT NOT NULL,
added_at INTEGER NOT NULL
);
""")
await db.execute(
"INSERT INTO files (path, mtime, size, hash, type, added_at) "
"VALUES (?, ?, ?, ?, ?, ?)",
("/lib/old.mkv", 111.0, 1000, "oldhash", "video", 42))
await db.commit()
cache = IndexCache(db_path=db_path)
await cache.open()
hit = await cache.lookup("/lib/old.mkv", size=1000, mtime=111.0,
hash_version=1)
await cache.close()
assert hit is not None
assert hit.hash == "oldhash"
assert hit.hash_version == 1
|