Main-thread unhandled exceptions now show the traceback and log path; worker-thread exceptions log without touching Qt widgets. The download watcher catches its own loop failures and reports that it stopped instead of surfacing a blank error dialog. Adds a regression test for the watcher failure path. Co-Authored-By: Claude Code <noreply@anthropic.com>
84 lines
3.0 KiB
Python
84 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from boltclam import realtime
|
|
from boltclam.realtime import browser_download_roots, snapshot
|
|
from boltclam.winlock import copy_for_scan
|
|
|
|
|
|
def test_snapshot_finds_final_files_and_skips_partials(tmp_path):
|
|
root = tmp_path / "Downloads"
|
|
root.mkdir()
|
|
(root / "old.exe").write_bytes(b"abc")
|
|
(root / "new.crdownload").write_bytes(b"partial")
|
|
(root / ".hidden").write_bytes(b"hidden")
|
|
sub = root / "Sub"
|
|
sub.mkdir()
|
|
(sub / "inside.zip").write_bytes(b"zip")
|
|
|
|
found = snapshot([root])
|
|
names = {Path(value[2]).name for value in found.values()}
|
|
assert names == {"old.exe", "inside.zip"}
|
|
|
|
|
|
def test_chromium_download_preference_is_watched(tmp_path, monkeypatch):
|
|
local = tmp_path / "Local"
|
|
roaming = tmp_path / "Roaming"
|
|
monkeypatch.setenv("LOCALAPPDATA", str(local))
|
|
monkeypatch.setenv("APPDATA", str(roaming))
|
|
download = tmp_path / "Custom Downloads"
|
|
download.mkdir()
|
|
profile = local / "Google" / "Chrome" / "User Data" / "Default"
|
|
profile.mkdir(parents=True)
|
|
(profile / "Preferences").write_text(json.dumps({
|
|
"download": {"default_directory": str(download)}}), encoding="utf-8")
|
|
monkeypatch.setattr(realtime.paths, "browser_download_roots", lambda: [])
|
|
|
|
roots = browser_download_roots()
|
|
assert download.resolve() in [path.resolve() for path in roots]
|
|
|
|
|
|
def test_firefox_download_preference_is_watched(tmp_path, monkeypatch):
|
|
local = tmp_path / "Local"
|
|
roaming = tmp_path / "Roaming"
|
|
monkeypatch.setenv("LOCALAPPDATA", str(local))
|
|
monkeypatch.setenv("APPDATA", str(roaming))
|
|
download = tmp_path / "Firefox Downloads"
|
|
download.mkdir()
|
|
profile = roaming / "Mozilla" / "Firefox" / "Profiles" / "abc.default"
|
|
profile.mkdir(parents=True)
|
|
escaped = str(download).replace("\\", "\\\\")
|
|
(profile / "prefs.js").write_text(
|
|
f'user_pref("browser.download.dir", "{escaped}");\n', encoding="utf-8")
|
|
monkeypatch.setattr(realtime.paths, "browser_download_roots", lambda: [])
|
|
|
|
roots = browser_download_roots()
|
|
assert download.resolve() in [path.resolve() for path in roots]
|
|
|
|
|
|
def test_watcher_stops_cleanly_instead_of_raising(monkeypatch, qapp):
|
|
messages: list[str] = []
|
|
watcher = realtime.DownloadWatcher(interval=0.1)
|
|
watcher.watch_status.connect(messages.append)
|
|
monkeypatch.setattr(realtime, "browser_download_roots", lambda extra: (_ for _ in ()).throw(
|
|
RuntimeError("simulated watcher failure")))
|
|
watcher.start()
|
|
assert watcher.wait(3000)
|
|
qapp.processEvents()
|
|
assert not watcher.isRunning()
|
|
assert "stopped" in " ".join(messages).lower()
|
|
|
|
|
|
def test_copy_for_scan_preserves_bytes(tmp_path):
|
|
source = tmp_path / "download.exe"
|
|
source.write_bytes(b"sample bytes")
|
|
destination = tmp_path / "copy.bin"
|
|
locker, exclusive = copy_for_scan(source, destination, attempts=1, delay=0)
|
|
try:
|
|
assert destination.read_bytes() == b"sample bytes"
|
|
assert isinstance(exclusive, bool)
|
|
finally:
|
|
locker.close()
|