Adds two managed system overlays (l4d2center-maps, cedapug-maps) that fetch curated map archives from upstream sources and reconcile addons symlinks for non-Steam maps. A daily systemd timer enqueues a coalesced refresh_global_overlays worker job; downloads, extraction, and rebuilds run in the existing job worker and surface in the job log UI. Schema: GlobalOverlaySource / GlobalOverlayItem / GlobalOverlayItemFile plus nullable Job.user_id so system jobs render as "system" in the UI. The new builder reconciles symlinks against the per-source vpk cache and leaves foreign symlinks untouched. Initialize-time guard refuses to mount a partial overlay if any expected vpk is missing from cache. Refresh service uses shutil.move to handle EXDEV when /tmp and the cache live on different filesystems. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
3.5 KiB
Python
69 lines
3.5 KiB
Python
from pathlib import Path
|
|
|
|
from l4d2web.db import init_db, session_scope
|
|
from l4d2web.models import GlobalOverlayItem, GlobalOverlayItemFile, GlobalOverlaySource
|
|
from l4d2web.services.global_map_sources import GlobalMapManifestItem
|
|
|
|
|
|
def test_refresh_global_overlays_updates_manifest_items_and_invokes_builders(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path/'refresh.db'}")
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
init_db()
|
|
|
|
from l4d2web.services import global_overlay_refresh
|
|
monkeypatch.setattr(
|
|
global_overlay_refresh,
|
|
"fetch_l4d2center_manifest",
|
|
lambda: ("hash-center", [GlobalMapManifestItem("carriedoff.vpk", "carriedoff.vpk", "https://example.invalid/carriedoff.7z", "carriedoff.vpk", 3, "" )]),
|
|
)
|
|
monkeypatch.setattr(
|
|
global_overlay_refresh,
|
|
"fetch_cedapug_manifest",
|
|
lambda: ("hash-ceda", [GlobalMapManifestItem("FatalFreight.zip", "Fatal Freight", "https://example.invalid/FatalFreight.zip")]),
|
|
)
|
|
|
|
def fake_download_and_extract(source_key, item, *, should_cancel):
|
|
target = tmp_path / "global_overlay_cache" / source_key / "vpks" / (item.expected_vpk_name or item.item_key.replace(".zip", ".vpk"))
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
target.write_bytes(b"vpk")
|
|
return [(target.name, f"{source_key}/vpks/{target.name}", 3, "")], "etag", "last-modified", 3
|
|
|
|
built: list[str] = []
|
|
monkeypatch.setattr(global_overlay_refresh, "download_and_extract_item", fake_download_and_extract)
|
|
monkeypatch.setattr(global_overlay_refresh, "build_global_overlay", lambda overlay, **kwargs: built.append(overlay.name))
|
|
|
|
out: list[str] = []
|
|
result = global_overlay_refresh.refresh_global_overlays(on_stdout=out.append, on_stderr=out.append, should_cancel=lambda: False)
|
|
|
|
assert result == ["cedapug-maps", "l4d2center-maps"]
|
|
assert set(built) == {"cedapug-maps", "l4d2center-maps"}
|
|
with session_scope() as db:
|
|
assert db.query(GlobalOverlaySource).count() == 2
|
|
assert db.query(GlobalOverlayItem).count() == 2
|
|
assert db.query(GlobalOverlayItemFile).count() == 2
|
|
|
|
|
|
def test_refresh_removes_items_absent_from_manifest(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path/'remove.db'}")
|
|
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
|
|
init_db()
|
|
|
|
from l4d2web.services.global_overlays import ensure_global_overlays
|
|
from l4d2web.services import global_overlay_refresh
|
|
|
|
with session_scope() as db:
|
|
ensure_global_overlays(db)
|
|
source = db.query(GlobalOverlaySource).filter_by(source_key="l4d2center-maps").one()
|
|
item = GlobalOverlayItem(source_id=source.id, item_key="old.vpk", display_name="old.vpk", download_url="https://example.invalid/old.7z")
|
|
db.add(item)
|
|
db.flush()
|
|
db.add(GlobalOverlayItemFile(item_id=item.id, vpk_name="old.vpk", cache_path="l4d2center-maps/vpks/old.vpk", size=3))
|
|
|
|
monkeypatch.setattr(global_overlay_refresh, "fetch_l4d2center_manifest", lambda: ("empty-center", []))
|
|
monkeypatch.setattr(global_overlay_refresh, "fetch_cedapug_manifest", lambda: ("empty-ceda", []))
|
|
monkeypatch.setattr(global_overlay_refresh, "build_global_overlay", lambda overlay, **kwargs: None)
|
|
|
|
global_overlay_refresh.refresh_global_overlays(on_stdout=lambda line: None, on_stderr=lambda line: None, should_cancel=lambda: False)
|
|
|
|
with session_scope() as db:
|
|
assert db.query(GlobalOverlayItem).filter_by(item_key="old.vpk").count() == 0
|