Files
camoufox/pythonlib/tests/test_addons.py
T
codechrl 6b8b08646d fix(addons): re-download addons with a missing manifest
maybe_download_addons() treated an addon as already downloaded whenever its
directory existed. A download that fails partway leaves an empty directory
behind, which is then trusted on every later launch, so confirm_paths()
raises InvalidAddonPath: manifest.json is missing and never recovers. Gate
the check on manifest.json presence and rmtree the partial directory on
failure. Closes #308.

(cherry picked from commit 0a8211969b)
2026-09-05 14:26:37 -06:00

96 lines
2.9 KiB
Python

"""
Tests for camoufox.addons default-addon download/caching.
Regression guard for #308: a partial/failed first download leaves an empty
addon directory behind. The old "already downloaded" check was a bare
os.path.exists(dir), so that empty dir was trusted forever and every later
launch raised InvalidAddonPath ("manifest.json is missing"), unrecoverable
short of manually deleting the cache.
Run with:
cd pythonlib && python -m pytest tests/test_addons.py -v
"""
import os
import sys
import pytest
# Make `import camoufox` resolve to the in-tree pythonlib without an install.
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from camoufox import addons as addons_mod # noqa: E402
from camoufox.addons import DefaultAddons, maybe_download_addons # noqa: E402
UBO = DefaultAddons.UBO.name
@pytest.fixture
def addons_dir(tmp_path, monkeypatch):
# Point the addon store at a throwaway dir so no real cache is touched.
root = tmp_path / "addons"
monkeypatch.setattr(addons_mod, "get_addon_path", lambda name: str(root / name))
return root
def _write_manifest(url, extract_path, name):
os.makedirs(extract_path, exist_ok=True)
with open(os.path.join(extract_path, "manifest.json"), "w") as f:
f.write("{}")
def test_partial_dir_is_redownloaded(addons_dir, monkeypatch):
# Leftover empty dir from a failed first download.
partial = addons_dir / UBO
partial.mkdir(parents=True)
assert not (partial / "manifest.json").exists()
calls = []
def fake(url, extract_path, name):
calls.append(name)
_write_manifest(url, extract_path, name)
monkeypatch.setattr(addons_mod, "download_and_extract", fake)
out = []
maybe_download_addons([DefaultAddons.UBO], out)
# An empty dir must trigger a re-download, not be trusted.
assert calls == [UBO]
assert (partial / "manifest.json").exists()
assert out == [str(partial)]
def test_extracted_addon_is_not_redownloaded(addons_dir, monkeypatch):
path = addons_dir / UBO
path.mkdir(parents=True)
(path / "manifest.json").write_text("{}")
def boom(*a, **k):
raise AssertionError("must not re-download an already-extracted addon")
monkeypatch.setattr(addons_mod, "download_and_extract", boom)
out = []
maybe_download_addons([DefaultAddons.UBO], out)
assert out == [str(path)]
def test_failed_download_removes_partial_dir(addons_dir, monkeypatch):
path = addons_dir / UBO
def fail(url, extract_path, name):
os.makedirs(extract_path, exist_ok=True) # partial write, then die
raise RuntimeError("network died mid-download")
monkeypatch.setattr(addons_mod, "download_and_extract", fail)
out = []
maybe_download_addons([DefaultAddons.UBO], out)
# The partial dir must be gone so the next run re-downloads instead of
# trusting an addon that has no manifest.json.
assert not path.exists()
assert out == []