forked from retoor/devplacepy
feat: scaffold initial DevPlace project with FastAPI, SQLite auth, and SSR routing
Add complete project skeleton including .gitignore, Makefile, AGENTS.md, config, database init with indexes, main app with router mounting for auth/feed/posts/comments/projects/profile/messages/notifications/votes, Pydantic models for signup/login/post/comment/project/message/profile, and auth router with signup/login page rendering and form handling.
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import os, sys, tempfile, subprocess, time
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
BASE_URL = "http://127.0.0.1:10501"
|
||||
SCREENSHOT_DIR = Path("/tmp/devplace_test_screenshots")
|
||||
|
||||
|
||||
def save_failure_screenshot(page, test_name):
|
||||
SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
if page and not page.is_closed():
|
||||
path = str(SCREENSHOT_DIR / f"{test_name}.png")
|
||||
page.screenshot(path=path, full_page=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
outcome = yield
|
||||
report = outcome.get_result()
|
||||
if report.when == "call" and report.failed:
|
||||
for name in ("page", "alice", "bob"):
|
||||
if name in item.funcargs:
|
||||
try:
|
||||
obj = item.funcargs[name]
|
||||
if name in ("alice", "bob"):
|
||||
obj = obj[0]
|
||||
save_failure_screenshot(obj, item.nodeid.replace("::", "_").replace("/", "_"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_db_path():
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
||||
tmp.close()
|
||||
yield tmp.name
|
||||
try:
|
||||
os.unlink(tmp.name)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def app_server(test_db_path):
|
||||
env = os.environ.copy()
|
||||
env["DEVPLACE_DATABASE_URL"] = f"sqlite:///{test_db_path}"
|
||||
env["SECRET_KEY"] = "test-secret-key"
|
||||
proc = subprocess.Popen(
|
||||
[sys.executable, "-m", "uvicorn", "devplacepy.main:app",
|
||||
"--host", "127.0.0.1", "--port", "10501"],
|
||||
cwd=str(PROJECT_ROOT), env=env,
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
)
|
||||
deadline = time.time() + 20
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
import urllib.request
|
||||
urllib.request.urlopen(f"{BASE_URL}/", timeout=2)
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
proc.terminate(); proc.wait()
|
||||
raise RuntimeError("Server did not start")
|
||||
yield proc
|
||||
proc.terminate()
|
||||
proc.wait(timeout=10)
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def playwright_instance():
|
||||
from playwright.sync_api import sync_playwright
|
||||
with sync_playwright() as p:
|
||||
yield p
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def browser(playwright_instance):
|
||||
headless = os.environ.get("PLAYWRIGHT_HEADLESS", "1") == "1"
|
||||
b = playwright_instance.chromium.launch(
|
||||
headless=headless, slow_mo=300, args=["--window-size=1400,900"],
|
||||
)
|
||||
yield b
|
||||
b.close()
|
||||
|
||||
@pytest.fixture
|
||||
def browser_context(browser):
|
||||
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
|
||||
yield ctx
|
||||
ctx.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def page(browser_context):
|
||||
p = browser_context.new_page()
|
||||
p.set_default_timeout(10000)
|
||||
yield p
|
||||
p.close()
|
||||
|
||||
def signup_user(page, user):
|
||||
page.goto(f"{BASE_URL}/auth/signup")
|
||||
page.fill("#username", user["username"])
|
||||
page.fill("#email", user["email"])
|
||||
page.fill("#password", user["password"])
|
||||
page.fill("#confirm_password", user["password"])
|
||||
page.click("button:has-text('Create account')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
|
||||
def login_user(page, user):
|
||||
page.goto(f"{BASE_URL}/auth/login")
|
||||
page.fill("#email", user["email"])
|
||||
page.fill("#password", user["password"])
|
||||
page.click("button:has-text('Sign in')")
|
||||
page.wait_for_url("**/feed", timeout=10000)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def seeded_db(app_server):
|
||||
"""Create test users once at session level using requests directly."""
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
users = [
|
||||
("alice_test", "alice@test.devplace", "secret123"),
|
||||
("bob_test", "bob@test.devplace", "secret456"),
|
||||
]
|
||||
for username, email, pw in users:
|
||||
data = urllib.parse.urlencode({
|
||||
"username": username, "email": email,
|
||||
"password": pw, "confirm_password": pw,
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{BASE_URL}/auth/signup",
|
||||
data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
)
|
||||
urllib.request.urlopen(req)
|
||||
return {
|
||||
"alice": {"username": "alice_test", "email": "alice@test.devplace", "password": "secret123"},
|
||||
"bob": {"username": "bob_test", "email": "bob@test.devplace", "password": "secret456"},
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def alice(page, seeded_db):
|
||||
login_user(page, seeded_db["alice"])
|
||||
return page, seeded_db["alice"]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def bob(browser, seeded_db):
|
||||
ctx = browser.new_context(viewport={"width": 1400, "height": 900})
|
||||
p = ctx.new_page()
|
||||
p.set_default_timeout(10000)
|
||||
login_user(p, seeded_db["bob"])
|
||||
yield p, seeded_db["bob"]
|
||||
p.close()
|
||||
ctx.close()
|
||||
Reference in New Issue
Block a user