You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
120 lines
3.3 KiB
120 lines
3.3 KiB
from __future__ import annotations
|
|
|
|
import os
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
|
|
def _find_free_port() -> int:
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
sock.bind(("127.0.0.1", 0))
|
|
return int(sock.getsockname()[1])
|
|
|
|
|
|
def _wait_for_server_ready(base_url: str, timeout_seconds: float = 20.0) -> None:
|
|
import urllib.error
|
|
import urllib.request
|
|
|
|
health_url = f"{base_url}/health"
|
|
deadline = time.monotonic() + timeout_seconds
|
|
while time.monotonic() < deadline:
|
|
try:
|
|
with urllib.request.urlopen(health_url, timeout=1.0) as response:
|
|
if response.status == 200:
|
|
return
|
|
except (urllib.error.URLError, TimeoutError):
|
|
time.sleep(0.2)
|
|
|
|
raise RuntimeError(f"Timed out waiting for test server at {health_url}")
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def e2e_environment(tmp_path_factory: pytest.TempPathFactory) -> Iterator[dict[str, str]]:
|
|
workspace = tmp_path_factory.mktemp("hw-e2e")
|
|
db_path = workspace / "happywedding-e2e.db"
|
|
port = _find_free_port()
|
|
base_url = f"http://127.0.0.1:{port}"
|
|
|
|
env = os.environ.copy()
|
|
env["DATABASE_URL"] = f"sqlite:///{db_path}"
|
|
env["FLASK_APP"] = "run.py"
|
|
env["PYTHONUNBUFFERED"] = "1"
|
|
|
|
for command in (
|
|
[sys.executable, "-m", "flask", "db", "upgrade"],
|
|
[sys.executable, "-m", "flask", "seed-all"],
|
|
[sys.executable, "-m", "flask", "seed-demo"],
|
|
):
|
|
subprocess.run(command, cwd=PROJECT_ROOT, env=env, check=True)
|
|
|
|
server_process = subprocess.Popen(
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"flask",
|
|
"run",
|
|
"--host",
|
|
"127.0.0.1",
|
|
"--port",
|
|
str(port),
|
|
"--no-debugger",
|
|
"--no-reload",
|
|
],
|
|
cwd=PROJECT_ROOT,
|
|
env=env,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
|
|
try:
|
|
_wait_for_server_ready(base_url)
|
|
yield {
|
|
"base_url": base_url,
|
|
"db_path": os.fspath(db_path),
|
|
}
|
|
finally:
|
|
server_process.terminate()
|
|
try:
|
|
server_process.wait(timeout=10)
|
|
except subprocess.TimeoutExpired:
|
|
server_process.kill()
|
|
server_process.wait(timeout=5)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def base_url(e2e_environment: dict[str, str]) -> str:
|
|
return e2e_environment["base_url"]
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def browser_context_args(browser_context_args: dict[str, object]) -> dict[str, object]:
|
|
return {
|
|
**browser_context_args,
|
|
"locale": "zh-CN",
|
|
"timezone_id": "Asia/Shanghai",
|
|
"viewport": {"width": 1440, "height": 1080},
|
|
}
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def admin_credentials() -> dict[str, str]:
|
|
return {"username": "admin", "password": "ChangeMe123!"}
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def entry_credentials() -> dict[str, str]:
|
|
return {"username": "entry-demo", "password": "EntryDemo123!"}
|
|
|
|
|
|
@pytest.fixture(scope="function")
|
|
def quick_editor_credentials() -> dict[str, str]:
|
|
return {"username": "quick-editor-demo", "password": "QuickEditor123!"}
|
|
|