left4me/l4d2web/tests/test_global_overlays.py
mwiegand ffc4cdbd7d
refactor(l4d2-web): remove legacy external overlay type
The workshop + managed-global overlay surface fully covers the
admin-SFTP flow that 'external' was a placeholder for. Drop the type
from the model defaults, builder registry, routes, template, and
tests, and add migration 0004 that deletes any leftover external
rows along with their blueprint and job references.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 09:31:04 +02:00

167 lines
6.4 KiB
Python

from sqlalchemy import select
from l4d2web.db import init_db, session_scope
from l4d2web.models import GlobalOverlaySource, Job, Overlay, User
from l4d2web.services.global_overlays import (
enqueue_refresh_global_overlays,
ensure_global_overlays,
is_creatable_overlay_type,
)
def test_ensure_global_overlays_creates_singletons_and_directories(tmp_path, monkeypatch):
monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path/'global_overlays.db'}")
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
init_db()
with session_scope() as session:
created = ensure_global_overlays(session)
assert created == {"cedapug-maps", "l4d2center-maps"}
second = ensure_global_overlays(session)
assert second == set()
overlays = session.scalars(select(Overlay).order_by(Overlay.name)).all()
assert [overlay.name for overlay in overlays] == ["cedapug-maps", "l4d2center-maps"]
assert [overlay.type for overlay in overlays] == ["cedapug_maps", "l4d2center_maps"]
assert [overlay.user_id for overlay in overlays] == [None, None]
assert len({overlay.path for overlay in overlays}) == 2
for overlay in overlays:
assert (tmp_path / "overlays" / overlay.path).is_dir()
sources = session.scalars(select(GlobalOverlaySource).order_by(GlobalOverlaySource.source_key)).all()
assert [source.source_key for source in sources] == ["cedapug-maps", "l4d2center-maps"]
assert [source.source_type for source in sources] == [
"cedapug_custom_page",
"l4d2center_csv",
]
def test_ensure_global_overlays_repairs_existing_rows(tmp_path, monkeypatch):
monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path/'global_overlay_repair.db'}")
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
init_db()
with session_scope() as session:
overlay = Overlay(name="cedapug-maps", path="legacy", type="cedapug_maps", user_id=None)
session.add(overlay)
session.flush()
session.add(
GlobalOverlaySource(
overlay_id=overlay.id,
source_key="cedapug-maps",
source_type="wrong",
source_url="https://example.invalid/wrong",
)
)
(tmp_path / "overlays" / "legacy").mkdir(parents=True)
with session_scope() as session:
created = ensure_global_overlays(session)
assert created == {"l4d2center-maps"}
repaired = session.scalar(select(Overlay).where(Overlay.name == "cedapug-maps"))
assert repaired is not None
assert repaired.type == "cedapug_maps"
assert repaired.user_id is None
assert (tmp_path / "overlays" / repaired.path).is_dir()
source = session.scalar(
select(GlobalOverlaySource).where(GlobalOverlaySource.source_key == "cedapug-maps")
)
assert source is not None
assert source.source_type == "cedapug_custom_page"
assert source.source_url == "https://cedapug.com/custom"
def test_ensure_global_overlays_does_not_hijack_private_overlay_name(tmp_path, monkeypatch):
monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path/'global_overlay_private_name.db'}")
monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path))
init_db()
with session_scope() as session:
user = User(username="alice", password_digest="digest", admin=False)
session.add(user)
session.flush()
private = Overlay(
name="l4d2center-maps",
path="private-l4d2center",
type="workshop",
user_id=user.id,
)
session.add(private)
session.flush()
private_id = private.id
private_user_id = user.id
with session_scope() as session:
created = ensure_global_overlays(session)
assert created == {"cedapug-maps", "l4d2center-maps"}
private = session.scalar(select(Overlay).where(Overlay.id == private_id))
assert private is not None
assert private.user_id == private_user_id
assert private.type == "workshop"
assert private.path == "private-l4d2center"
system = session.scalar(
select(Overlay).where(Overlay.name == "l4d2center-maps", Overlay.user_id.is_(None))
)
assert system is not None
assert system.id != private_id
assert system.type == "l4d2center_maps"
assert (tmp_path / "overlays" / system.path).is_dir()
source = session.scalar(
select(GlobalOverlaySource).where(GlobalOverlaySource.source_key == "l4d2center-maps")
)
assert source is not None
assert source.overlay_id == system.id
def test_enqueue_refresh_global_overlays_coalesces_active_jobs(tmp_path, monkeypatch):
monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path/'refresh_jobs.db'}")
init_db()
for state in ("queued", "running", "cancelling"):
with session_scope() as session:
session.query(Job).delete()
existing = Job(
user_id=7,
server_id=None,
overlay_id=None,
operation="refresh_global_overlays",
state=state,
)
session.add(existing)
session.flush()
existing_id = existing.id
job = enqueue_refresh_global_overlays(session, user_id=None)
assert job.id == existing_id
assert session.query(Job).filter_by(operation="refresh_global_overlays").count() == 1
def test_enqueue_refresh_global_overlays_creates_system_job(tmp_path, monkeypatch):
monkeypatch.setenv("DATABASE_URL", f"sqlite:///{tmp_path/'refresh_system_job.db'}")
init_db()
with session_scope() as session:
job = enqueue_refresh_global_overlays(session, user_id=None)
assert job.id is not None
assert job.user_id is None
assert job.server_id is None
assert job.overlay_id is None
assert job.operation == "refresh_global_overlays"
assert job.state == "queued"
def test_is_creatable_overlay_type_policy():
assert is_creatable_overlay_type("workshop", admin=False) is True
assert is_creatable_overlay_type("workshop", admin=True) is True
assert is_creatable_overlay_type("external", admin=False) is False
assert is_creatable_overlay_type("external", admin=True) is False
assert is_creatable_overlay_type("l4d2center_maps", admin=True) is False
assert is_creatable_overlay_type("cedapug_maps", admin=True) is False