import json import pytest from l4d2web.app import create_app from l4d2web.auth import hash_password from l4d2web.db import init_db, session_scope from l4d2web.models import Blueprint, BlueprintOverlay, Overlay, Server, User @pytest.fixture def user_client(tmp_path, monkeypatch): db_url = f"sqlite:///{tmp_path/'blueprint.db'}" monkeypatch.setenv("DATABASE_URL", db_url) 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() user_id = user.id session.add_all( [ Overlay(name="o1", path="/opt/l4d2/overlays/o1"), Overlay(name="o2", path="/opt/l4d2/overlays/o2"), ] ) client = app.test_client() with client.session_transaction() as sess: sess["user_id"] = user_id sess["csrf_token"] = "test-token" return client @pytest.fixture def linked_blueprint(tmp_path, monkeypatch): db_url = f"sqlite:///{tmp_path/'linked.db'}" monkeypatch.setenv("DATABASE_URL", db_url) app = create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"}) init_db() with session_scope() as session: user = User(username="bob", password_digest=hash_password("secret"), admin=False) session.add(user) session.flush() blueprint = Blueprint(user_id=user.id, name="linked", arguments="[]", config="[]") session.add(blueprint) session.flush() session.add(Server(user_id=user.id, blueprint_id=blueprint.id, name="alpha", port=27015)) blueprint_id = blueprint.id user_id = user.id client = app.test_client() with client.session_transaction() as sess: sess["user_id"] = user_id sess["csrf_token"] = "test-token" return client, blueprint_id def test_user_can_create_private_blueprint(user_client) -> None: payload = { "name": "comp", "arguments": ["-tickrate 100"], "config": ["sv_consistency 1"], "overlay_ids": [1, 2], } response = user_client.post( "/blueprints", data=json.dumps(payload), content_type="application/json", headers={"X-CSRF-Token": "test-token"}, ) assert response.status_code == 201 def test_delete_blueprint_blocked_when_in_use(linked_blueprint) -> None: client, blueprint_id = linked_blueprint response = client.delete(f"/blueprints/{blueprint_id}", headers={"X-CSRF-Token": "test-token"}) assert response.status_code == 409 def test_form_update_preserves_ordered_overlays_and_multiline_fields(user_client) -> None: create = user_client.post( "/blueprints", data={ "name": "comp", "arguments": "-tickrate 100", "config": "sv_consistency 1", "overlay_ids": ["2", "1"], "overlay_position_1": "2", "overlay_position_2": "1", }, headers={"X-CSRF-Token": "test-token"}, ) assert create.status_code == 302 update = user_client.post( "/blueprints/1", data={ "name": "edited", "arguments": "-tickrate 100\n+sv_lan 0", "config": "sv_consistency 1\nsv_allow_lobby_connect_only 0", "overlay_ids": ["1", "2"], "overlay_position_1": "1", "overlay_position_2": "2", }, headers={"X-CSRF-Token": "test-token"}, ) assert update.status_code == 302 assert update.headers["Location"] == "/blueprints/1"