summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_indexer.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_indexer.py')
-rw-r--r--packages/meshbay-node/tests/test_indexer.py76
1 files changed, 76 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_indexer.py b/packages/meshbay-node/tests/test_indexer.py
index 68ccc4c..3eac39e 100644
--- a/packages/meshbay-node/tests/test_indexer.py
+++ b/packages/meshbay-node/tests/test_indexer.py
@@ -356,6 +356,82 @@ async def test_progress_stops_even_when_hashing_raises(tmp_path, sk_node, gek):
"an exception mid-scan must not leave the scanning flag stuck on"
+@pytest.mark.asyncio
+async def test_realtime_watchdog_add_reports_progress_like_a_bulk_scan(tmp_path, sk_node, gek):
+ """
+ Found live: dropping a whole season into an already-watched folder gave
+ no scanning indicator and no progress bar at all — only the initial scan
+ and the periodic reconcile backstop ever touched `progress`, never the
+ real-time per-file watchdog path (_schedule_update/_debounce/
+ _update_entry). Drives that path directly (as _WatchdogHandler would),
+ without a real filesystem observer, exactly like test_root_availability.py
+ already does for _update_entry alone.
+ """
+ d = tmp_path / "shared"
+ d.mkdir()
+ paths = []
+ for i in range(3):
+ p = d / f"ep{i}.mkv"
+ p.write_bytes(os.urandom(1024 * (i + 1)))
+ paths.append(p)
+ total_size = sum(p.stat().st_size for p in paths)
+
+ indexer = DirectoryIndexer(roots=one_root(d), group_id="g", sk_node=sk_node,
+ gek=gek, debounce_secs=0.01)
+ indexer._loop = asyncio.get_running_loop()
+ assert indexer.progress.scanning is False
+
+ for p in paths:
+ indexer._schedule_update(p)
+ # _schedule_update only posts _debounce via call_soon_threadsafe (it is
+ # written to be called from the watchdog thread) — give the loop one
+ # turn to actually run the three posted calls before asserting on them.
+ await asyncio.sleep(0)
+
+ # All three scheduled near-simultaneously (as a burst of watchdog events
+ # for one `mv` would arrive) — the indicator must flip on immediately,
+ # before any single file has actually finished hashing.
+ assert indexer.progress.scanning is True
+ assert indexer.progress.total_bytes == total_size
+ assert indexer.progress.scanned_bytes == 0
+
+ await asyncio.sleep(0.05) # past debounce_secs; lets all three fire and finish
+
+ assert indexer.progress.scanning is False, "must end idle, not stuck scanning"
+ assert indexer.progress.scanned_bytes == total_size
+ assert indexer.progress.total_bytes == total_size
+ assert len(indexer.index.entries) == 3
+
+
+@pytest.mark.asyncio
+async def test_realtime_watchdog_rapid_rewrite_does_not_double_count(tmp_path, sk_node, gek):
+ """A file rewritten during its own debounce window (on_modified firing
+ again before the first timer fires) must count its size once, not once
+ per event — the old timer is cancelled, and its accounting must transfer
+ to whichever fire() actually runs rather than being counted twice."""
+ d = tmp_path / "shared"
+ d.mkdir()
+ p = d / "ep0.mkv"
+ p.write_bytes(os.urandom(2048))
+
+ indexer = DirectoryIndexer(roots=one_root(d), group_id="g", sk_node=sk_node,
+ gek=gek, debounce_secs=0.05)
+ indexer._loop = asyncio.get_running_loop()
+
+ indexer._schedule_update(p)
+ indexer._schedule_update(p) # re-triggered before the first timer fires
+ indexer._schedule_update(p)
+ await asyncio.sleep(0) # let the three posted _debounce calls actually run
+
+ assert indexer.progress.total_bytes == p.stat().st_size, \
+ "one file re-triggered must count its size once, not three times"
+
+ await asyncio.sleep(0.1)
+
+ assert indexer.progress.scanning is False
+ assert indexer.progress.scanned_bytes == p.stat().st_size
+
+
# ── Off-loop directory walks, reconcile backoff ─────────────────────────────
@pytest.mark.asyncio