from datetime import UTC, datetime from flask import render_template_string from l4d2web.app import create_app from l4d2web.auth import hash_password from l4d2web.db import init_db, session_scope from l4d2web.models import Overlay, User def _make_app(tmp_path, monkeypatch, db_name: str): db_url = f"sqlite:///{tmp_path/db_name}" monkeypatch.setenv("DATABASE_URL", db_url) return create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"}) def test_base_layout_is_modal_partial_when_hx_modal_header_set(tmp_path, monkeypatch): app = _make_app(tmp_path, monkeypatch, "layout-modal.db") with app.test_request_context("/", headers={"HX-Modal": "1"}): assert render_template_string("{{ base_layout }}") == "_modal_partial.html" def test_base_layout_is_base_html_for_normal_request(tmp_path, monkeypatch): app = _make_app(tmp_path, monkeypatch, "layout-default.db") with app.test_request_context("/"): assert render_template_string("{{ base_layout }}") == "base.html" def test_base_layout_does_not_react_to_plain_hx_request_header(tmp_path, monkeypatch): # HTMX sets HX-Request on every request including the build-status poll; # only HX-Modal should switch the layout. app = _make_app(tmp_path, monkeypatch, "layout-hxreq.db") with app.test_request_context("/", headers={"HX-Request": "true"}): assert render_template_string("{{ base_layout }}") == "base.html" def _auth_client_with_files_overlay(tmp_path, monkeypatch, db_name: str): db_url = f"sqlite:///{tmp_path/db_name}" monkeypatch.setenv("DATABASE_URL", db_url) monkeypatch.setenv("LEFT4ME_ROOT", str(tmp_path)) app = create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"}) init_db() with session_scope() as session: user = User(username="alice", password_digest=hash_password("secret"), admin=False) session.add(user) session.flush() overlay = Overlay(name="cfgs", path="", type="files", user_id=user.id) session.add(overlay) session.flush() overlay.path = str(overlay.id) overlay_root = tmp_path / "overlays" / str(overlay.id) overlay_root.mkdir(parents=True) (overlay_root / "server.cfg").write_text("hostname \"left4me\"\nrcon_password \"hunter2\"\n", encoding="utf-8") user_id = user.id overlay_id = overlay.id client = app.test_client() with client.session_transaction() as sess: sess["user_id"] = user_id sess["pw_changed_at"] = datetime.now(UTC).isoformat() return client, overlay_id def test_edit_route_renders_full_page_without_modal_header(tmp_path, monkeypatch): client, overlay_id = _auth_client_with_files_overlay(tmp_path, monkeypatch, "edit-full.db") response = client.get(f"/overlays/{overlay_id}/files/edit?path=server.cfg") text = response.get_data(as_text=True) assert response.status_code == 200 assert "" in text.lower() # full base.html rendered assert 'href="/dashboard"' in text # nav present assert 'class="files-editor-content"' in text assert 'rcon_password' in text # content pre-filled def test_edit_route_renders_fragment_with_modal_header(tmp_path, monkeypatch): client, overlay_id = _auth_client_with_files_overlay(tmp_path, monkeypatch, "edit-fragment.db") response = client.get( f"/overlays/{overlay_id}/files/edit?path=server.cfg", headers={"HX-Modal": "1"}, ) text = response.get_data(as_text=True) assert response.status_code == 200 assert "