# retoor import os import shutil import subprocess import sys import tempfile import time from pathlib import Path import pytest REPO_ROOT = Path(__file__).resolve().parent.parent TEST_DATA_DIR = Path(tempfile.mkdtemp(prefix="molodetz-test-")) TEST_PORT = int(os.environ.get("MOLODETZ_TEST_PORT", "8099")) ADMIN_USERNAME = "retoor" ADMIN_PASSWORD = "test-admin-pass-4f9c2a" MEMBER_USERNAME = "lezer" MEMBER_PASSWORD = "test-member-pass-77d1" TEST_ENV = { "MOLODETZ_DATA_DIR": str(TEST_DATA_DIR), "MOLODETZ_DATABASE_URL": f"sqlite:///{TEST_DATA_DIR / 'molodetz.db'}", "MOLODETZ_ADMIN_USERNAME": ADMIN_USERNAME, "MOLODETZ_ADMIN_EMAIL": "retoor@molodetz.nl", "MOLODETZ_ADMIN_PASSWORD": ADMIN_PASSWORD, "MOLODETZ_DISABLE_SERVICES": "1", "MOLODETZ_DISABLE_RATE_LIMIT": "1", "MOLODETZ_PORT": str(TEST_PORT), "MOLODETZ_INTERNAL_BASE_URL": f"http://127.0.0.1:{TEST_PORT}", "MOLODETZ_SITEMAP_TTL": "0", "MOLODETZ_LANDING_TTL": "0", "MOLODETZ_UNREAD_TTL": "0", "MOLODETZ_STATIC_VERSION": "test", "SECRET_KEY": "test-secret-key-not-for-production", } os.environ.update(TEST_ENV) BASE_URL = f"http://127.0.0.1:{TEST_PORT}" def _wait_ready(process, timeout=30.0): import httpx deadline = time.monotonic() + timeout while time.monotonic() < deadline: if process.poll() is not None: raise RuntimeError("test server exited during startup") try: if httpx.get(f"{BASE_URL}/health", timeout=1.0).status_code == 200: return except httpx.HTTPError: pass time.sleep(0.2) raise RuntimeError("test server did not become ready") @pytest.fixture(scope="session") def app_server(): env = {**os.environ, **TEST_ENV} log = open(TEST_DATA_DIR / "server.log", "w") process = subprocess.Popen( [sys.executable, "-m", "uvicorn", "molodetz.main:app", "--host", "127.0.0.1", "--port", str(TEST_PORT)], cwd=REPO_ROOT, env=env, stdout=log, stderr=subprocess.STDOUT, ) try: _wait_ready(process) _ensure_member() yield BASE_URL finally: process.terminate() try: process.wait(timeout=10) except subprocess.TimeoutExpired: process.kill() log.close() def _ensure_member(): from molodetz.database import create_user, get_user_by_username from molodetz.utils.auth import hash_password if get_user_by_username(MEMBER_USERNAME) is None: create_user(MEMBER_USERNAME, "lezer@example.invalid", hash_password(MEMBER_PASSWORD), role="Member") def _client(base_url): import httpx return httpx.Client(base_url=base_url, timeout=15.0, follow_redirects=False) def _login(client, username, password): response = client.post("/auth/login", data={"username": username, "password": password}) assert response.status_code in (302, 303), response.text[:300] return client @pytest.fixture() def anon(app_server): with _client(app_server) as client: yield client @pytest.fixture() def member(app_server): with _client(app_server) as client: yield _login(client, MEMBER_USERNAME, MEMBER_PASSWORD) @pytest.fixture() def admin(app_server): with _client(app_server) as client: yield _login(client, ADMIN_USERNAME, ADMIN_PASSWORD) @pytest.fixture(scope="session") def browser(app_server): from playwright.sync_api import sync_playwright headless = os.environ.get("PLAYWRIGHT_HEADLESS", "1") == "1" slow_mo = int(os.environ.get("PLAYWRIGHT_SLOW_MO", "0") or 0) with sync_playwright() as playwright: browser = playwright.chromium.launch(headless=headless, slow_mo=slow_mo) yield browser browser.close() @pytest.fixture() def page(browser): context = browser.new_context(viewport={"width": 1280, "height": 900}, locale="nl-NL") page = context.new_page() page.console_errors = [] page.on("pageerror", lambda error: page.console_errors.append(str(error))) page.on("console", lambda message: message.type == "error" and page.console_errors.append(message.text)) yield page context.close() def pytest_sessionfinish(session, exitstatus): shutil.rmtree(TEST_DATA_DIR, ignore_errors=True)