"""Pytest fixtures for end-to-end browser tests. Boots the Flask app in a background thread on an ephemeral port and yields the base URL. The app uses a temp SQLite DB so e2e runs don't contaminate the dev database. """ import socket import threading import pytest from werkzeug.serving import make_server 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, User def _free_port() -> int: s = socket.socket() s.bind(("127.0.0.1", 0)) port = s.getsockname()[1] s.close() return port @pytest.fixture(scope="function") def live_server(tmp_path, monkeypatch): db_path = tmp_path / "e2e.db" db_url = f"sqlite:///{db_path}" monkeypatch.setenv("DATABASE_URL", db_url) # app.py:57 sets SESSION_COOKIE_SECURE = not TESTING, which would # mark the session cookie Secure. The browser then drops it over # http://127.0.0.1 in e2e tests and the login flow silently fails # with a redirect back to /login. Force it off explicitly via the # env-var override (app.py:53-55) rather than flipping TESTING, # which would skip the SECRET_KEY guard and other production paths. monkeypatch.setenv("SESSION_COOKIE_SECURE", "0") app = create_app({"TESTING": False, "DATABASE_URL": db_url, "SECRET_KEY": "e2e"}) # create_app() already calls init_db() inside an app context, which # binds tables to the in-app engine. The seed work below uses # session_scope() OUTSIDE any app context, which reads DATABASE_URL # from the environment and binds its own engine. This second init_db() # call creates the tables on that env-derived engine so the seed # inserts have somewhere to land. init_db() with session_scope() as session: user = User( username="alice", password_digest=hash_password("secret"), admin=False, ) session.add(user) session.flush() bp = Blueprint( user_id=user.id, name="bp", arguments="[]", config="[]" ) session.add(bp) session.flush() blueprint_id = bp.id user_id = user.id port = _free_port() server = make_server("127.0.0.1", port, app) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() try: yield { "base_url": f"http://127.0.0.1:{port}", "user_id": user_id, "blueprint_id": blueprint_id, } finally: server.shutdown() thread.join(timeout=2)