144 lines
4.2 KiB
Python
144 lines
4.2 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
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)
|