mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
"""Contract tests for offline_guard: the strict-offline download gate and the
|
|||
|
|
best-effort bundle-weight symlink helpers. Pure (os + filesystem), no models,
|
||
|
|
so this runs on any Python the sidecar supports."""
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||
|
|
import offline_guard # noqa: E402
|
||
|
|
|
||
|
|
|
||
|
|
# --- downloads_allowed ------------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_downloads_allowed_default_true(monkeypatch):
|
||
|
|
monkeypatch.delenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", raising=False)
|
||
|
|
assert offline_guard.downloads_allowed() is True
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("value", ["0", "false", "FALSE", "False"])
|
||
|
|
def test_downloads_blocked_by_explicit_off(monkeypatch, value):
|
||
|
|
monkeypatch.setenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", value)
|
||
|
|
assert offline_guard.downloads_allowed() is False
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("value", ["1", "true", "yes", "anything"])
|
||
|
|
def test_downloads_allowed_for_non_off_values(monkeypatch, value):
|
||
|
|
monkeypatch.setenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", value)
|
||
|
|
assert offline_guard.downloads_allowed() is True
|
||
|
|
|
||
|
|
|
||
|
|
# --- ensure_download_allowed ------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_ensure_noop_when_allowed(monkeypatch):
|
||
|
|
monkeypatch.delenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", raising=False)
|
||
|
|
assert offline_guard.ensure_download_allowed("thing") is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_ensure_raises_actionable_error_when_blocked(monkeypatch):
|
||
|
|
monkeypatch.setenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", "0")
|
||
|
|
with pytest.raises(RuntimeError) as exc:
|
||
|
|
offline_guard.ensure_download_allowed("MyModel weight")
|
||
|
|
msg = str(exc.value)
|
||
|
|
assert "MyModel weight" in msg
|
||
|
|
assert "SNAPOTTER_ALLOW_MODEL_DOWNLOAD" in msg
|
||
|
|
|
||
|
|
|
||
|
|
# --- link_bundled_weight ----------------------------------------------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_link_already_present_returns_true_without_symlink(tmp_path):
|
||
|
|
link = tmp_path / "present.pth"
|
||
|
|
link.write_text("x")
|
||
|
|
target = tmp_path / "target.pth" # intentionally absent
|
||
|
|
assert offline_guard.link_bundled_weight(str(link), str(target)) is True
|
||
|
|
assert not link.is_symlink() # left untouched
|
||
|
|
|
||
|
|
|
||
|
|
def test_link_target_missing_returns_false(tmp_path):
|
||
|
|
link = tmp_path / "missing" / "link.pth"
|
||
|
|
target = tmp_path / "nope.pth" # absent
|
||
|
|
assert offline_guard.link_bundled_weight(str(link), str(target)) is False
|
||
|
|
assert not link.exists()
|
||
|
|
|
||
|
|
|
||
|
|
def test_link_creates_symlink_and_parent_dirs(tmp_path):
|
||
|
|
target = tmp_path / "bundle" / "weight.pth"
|
||
|
|
target.parent.mkdir()
|
||
|
|
target.write_text("weights")
|
||
|
|
link = tmp_path / "nested" / "dir" / "weight.pth"
|
||
|
|
assert offline_guard.link_bundled_weight(str(link), str(target)) is True
|
||
|
|
assert link.is_symlink()
|
||
|
|
assert link.read_text() == "weights" # resolves to the target bytes
|
||
|
|
|
||
|
|
|
||
|
|
def test_link_oserror_returns_false_when_symlink_fails(tmp_path, monkeypatch):
|
||
|
|
target = tmp_path / "t.pth"
|
||
|
|
target.write_text("w")
|
||
|
|
link = tmp_path / "l.pth"
|
||
|
|
|
||
|
|
def raise_oserror(*_a, **_k):
|
||
|
|
raise OSError("read-only fs")
|
||
|
|
|
||
|
|
monkeypatch.setattr(offline_guard.os, "symlink", raise_oserror)
|
||
|
|
# symlink fails and the link never materialised -> False.
|
||
|
|
assert offline_guard.link_bundled_weight(str(link), str(target)) is False
|
||
|
|
|
||
|
|
|
||
|
|
# --- prepare_gfpgan_helper_weights / prepare_codeformer_weights -------------
|
||
|
|
|
||
|
|
|
||
|
|
def test_prepare_gfpgan_links_present_weights(tmp_path, monkeypatch):
|
||
|
|
models = tmp_path / "models"
|
||
|
|
facelib = models / "gfpgan" / "facelib"
|
||
|
|
facelib.mkdir(parents=True)
|
||
|
|
for fname in offline_guard.GFPGAN_HELPER_WEIGHTS:
|
||
|
|
(facelib / fname).write_text("w")
|
||
|
|
monkeypatch.chdir(tmp_path) # link paths are cwd-relative
|
||
|
|
monkeypatch.setenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", "0") # would raise on any miss
|
||
|
|
offline_guard.prepare_gfpgan_helper_weights(str(models)) # must not raise
|
||
|
|
for fname in offline_guard.GFPGAN_HELPER_WEIGHTS:
|
||
|
|
assert (tmp_path / "gfpgan" / "weights" / fname).exists()
|
||
|
|
|
||
|
|
|
||
|
|
def test_prepare_gfpgan_raises_offline_when_weight_missing(tmp_path, monkeypatch):
|
||
|
|
models = tmp_path / "models" # nothing installed
|
||
|
|
monkeypatch.chdir(tmp_path)
|
||
|
|
monkeypatch.setenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", "0")
|
||
|
|
with pytest.raises(RuntimeError):
|
||
|
|
offline_guard.prepare_gfpgan_helper_weights(str(models))
|
||
|
|
|
||
|
|
|
||
|
|
def test_prepare_codeformer_links_present_weights(tmp_path, monkeypatch):
|
||
|
|
models = tmp_path / "models"
|
||
|
|
layout = [
|
||
|
|
("codeformer", "codeformer.pth"),
|
||
|
|
("gfpgan", "facelib", "detection_Resnet50_Final.pth"),
|
||
|
|
("gfpgan", "facelib", "parsing_parsenet.pth"),
|
||
|
|
("realesrgan", "RealESRGAN_x2plus.pth"),
|
||
|
|
]
|
||
|
|
for parts in layout:
|
||
|
|
p = models.joinpath(*parts)
|
||
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
p.write_text("w")
|
||
|
|
monkeypatch.chdir(tmp_path)
|
||
|
|
monkeypatch.setenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", "0")
|
||
|
|
offline_guard.prepare_codeformer_weights(str(models)) # must not raise
|
||
|
|
|
||
|
|
|
||
|
|
def test_prepare_codeformer_raises_offline_when_missing(tmp_path, monkeypatch):
|
||
|
|
models = tmp_path / "models"
|
||
|
|
monkeypatch.chdir(tmp_path)
|
||
|
|
monkeypatch.setenv("SNAPOTTER_ALLOW_MODEL_DOWNLOAD", "0")
|
||
|
|
with pytest.raises(RuntimeError):
|
||
|
|
offline_guard.prepare_codeformer_weights(str(models))
|