import os from pathlib import Path from l4d2web.db import init_db, session_scope from l4d2web.models import GlobalOverlayItem, GlobalOverlayItemFile, GlobalOverlaySource, Overlay from l4d2web.services.overlay_builders import BUILDERS def seed_source(tmp_path: Path, monkeypatch) -> int: monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path/'builder.db'}") monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path)) init_db() cache_vpk = tmp_path / "global_overlay_cache" / "l4d2center-maps" / "vpks" / "carriedoff.vpk" cache_vpk.parent.mkdir(parents=True, exist_ok=True) cache_vpk.write_bytes(b"vpk") with session_scope() as db: overlay = Overlay(name="l4d2center-maps", path="7", type="l4d2center_maps", user_id=None) db.add(overlay) db.flush() source = GlobalOverlaySource( overlay_id=overlay.id, source_key="l4d2center-maps", source_type="l4d2center_csv", source_url="https://l4d2center.com/maps/servers/index.csv", ) db.add(source) db.flush() item = GlobalOverlayItem( source_id=source.id, item_key="carriedoff.vpk", display_name="carriedoff.vpk", download_url="https://example.invalid/carriedoff.7z", expected_vpk_name="carriedoff.vpk", ) db.add(item) db.flush() db.add( GlobalOverlayItemFile( item_id=item.id, vpk_name="carriedoff.vpk", cache_path="l4d2center-maps/vpks/carriedoff.vpk", size=3, md5="", ) ) db.flush() return overlay.id def test_registry_contains_global_map_builders(): assert "l4d2center_maps" in BUILDERS assert "cedapug_maps" in BUILDERS def test_global_builder_creates_absolute_symlink(tmp_path, monkeypatch): overlay_id = seed_source(tmp_path, monkeypatch) out: list[str] = [] err: list[str] = [] with session_scope() as db: overlay = db.query(Overlay).filter_by(id=overlay_id).one() BUILDERS["l4d2center_maps"].build(overlay, on_stdout=out.append, on_stderr=err.append, should_cancel=lambda: False) link = tmp_path / "overlays" / "7" / "left4dead2" / "addons" / "carriedoff.vpk" assert link.is_symlink() assert os.path.isabs(os.readlink(link)) assert link.resolve() == (tmp_path / "global_overlay_cache" / "l4d2center-maps" / "vpks" / "carriedoff.vpk").resolve() assert any("global overlay" in line for line in out) def test_global_builder_removes_obsolete_managed_symlink_but_keeps_foreign(tmp_path, monkeypatch): overlay_id = seed_source(tmp_path, monkeypatch) addons = tmp_path / "overlays" / "7" / "left4dead2" / "addons" addons.mkdir(parents=True, exist_ok=True) foreign_target = tmp_path / "foreign.vpk" foreign_target.write_bytes(b"foreign") os.symlink(str(foreign_target), addons / "foreign.vpk") with session_scope() as db: overlay = db.query(Overlay).filter_by(id=overlay_id).one() BUILDERS["l4d2center_maps"].build(overlay, on_stdout=lambda line: None, on_stderr=lambda line: None, should_cancel=lambda: False) source = db.query(GlobalOverlaySource).filter_by(source_key="l4d2center-maps").one() db.query(GlobalOverlayItem).filter_by(source_id=source.id).delete() with session_scope() as db: overlay = db.query(Overlay).filter_by(id=overlay_id).one() BUILDERS["l4d2center_maps"].build(overlay, on_stdout=lambda line: None, on_stderr=lambda line: None, should_cancel=lambda: False) assert not (addons / "carriedoff.vpk").exists() assert (addons / "foreign.vpk").is_symlink()