feat: add fast-path early return for empty input in parse function

The parse function now checks for empty input at the top and returns immediately, avoiding unnecessary processing overhead for trivial cases. This optimization reduces latency for empty-string calls by skipping regex compilation and match attempts.
This commit is contained in:
2026-05-11 01:14:43 +00:00
parent ba91b1b3b1
commit ccb885607a
37 changed files with 1203 additions and 366 deletions
+16 -17
View File
@@ -47,16 +47,14 @@ 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"
err_path = Path("/tmp/devplace_test_server_err.log")
with open(err_path, "w") as err_f:
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=err_f,
)
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:
@@ -69,8 +67,13 @@ def app_server(test_db_path):
proc.terminate(); proc.wait()
raise RuntimeError("Server did not start")
yield proc
proc.terminate()
proc.wait(timeout=10)
try:
proc.terminate()
proc.wait(timeout=10)
except Exception:
proc.kill()
proc.wait(timeout=5)
@pytest.fixture(scope="session")
def playwright_instance():
@@ -120,11 +123,7 @@ def login_user(page, user):
page.fill("#email", user["email"])
page.fill("#password", user["password"])
page.click("button:has-text('Sign in')")
try:
page.wait_for_url("**/feed", timeout=10000, wait_until="domcontentloaded")
except Exception as e:
print(f"\n[LOGIN FAIL] URL: {page.url}, title: {page.title()}, err: {e}")
raise
page.wait_for_url("**/feed", timeout=10000, wait_until="domcontentloaded")
@pytest.fixture(scope="session")
+63
View File
@@ -0,0 +1,63 @@
from devplacepy.avatar import avatar_url, generate_avatar_svg
def test_avatar_url_format():
url = avatar_url("multiavatar", "testuser", 128)
assert url == "/avatar/multiavatar/testuser?size=128"
def test_avatar_url_different_size():
url = avatar_url("multiavatar", "testuser", 64)
assert "size=64" in url
def test_avatar_url_contains_seed():
url = avatar_url("multiavatar", "customuser", 128)
assert "customuser" in url
def test_generate_avatar_svg_returns_string():
svg = generate_avatar_svg("testuser")
assert isinstance(svg, str)
assert len(svg) > 100
def test_generate_avatar_svg_is_svg():
svg = generate_avatar_svg("testuser")
assert svg.strip().startswith("<svg")
assert "xmlns" in svg
def test_generate_avatar_svg_deterministic():
svg1 = generate_avatar_svg("testuser")
svg2 = generate_avatar_svg("testuser")
assert svg1 == svg2
def test_generate_avatar_svg_different_for_different_seeds():
svg1 = generate_avatar_svg("alice")
svg2 = generate_avatar_svg("bob")
assert svg1 != svg2
def test_generate_avatar_svg_fallback_on_exception():
svg = generate_avatar_svg("a")
assert isinstance(svg, str)
assert len(svg) > 50
def test_generate_avatar_svg_includes_rect_for_fallback():
svg = generate_avatar_svg("a")
assert "rect" in svg or "path" in svg
def test_avatar_url_with_different_seeds():
url1 = avatar_url("multiavatar", "alice", 128)
url2 = avatar_url("multiavatar", "bob", 128)
assert url1 != url2
def test_generate_avatar_svg_empty_seed_uses_fallback():
svg = generate_avatar_svg("")
assert isinstance(svg, str)
assert "rect" in svg or "svg" in svg
+16 -16
View File
@@ -3,7 +3,7 @@ from tests.conftest import BASE_URL
def create_post(page, content, topic="random", title=None):
page.goto(f"{BASE_URL}/feed")
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.locator(".feed-fab").first.wait_for(state="visible", timeout=10000)
page.locator(".feed-fab").first.click()
page.check(f"input[value='{topic}']")
@@ -15,7 +15,7 @@ def create_post(page, content, topic="random", title=None):
def signup(page, username, email, password):
page.goto(f"{BASE_URL}/auth/signup")
page.goto(f"{BASE_URL}/auth/signup", wait_until="domcontentloaded")
page.fill("#username", username)
page.fill("#email", email)
page.fill("#password", password)
@@ -35,7 +35,7 @@ def test_full_user_journey(browser, app_server):
# ═══════════════════════════════════════════════
# ACT 1 — LANDING PAGE
# ═══════════════════════════════════════════════
pa.goto(f"{BASE_URL}/")
pa.goto(f"{BASE_URL}/", wait_until="domcontentloaded")
assert pa.is_visible("text=Devplace.net")
assert pa.is_visible("text=The Developer Social Network")
assert pa.is_visible("text=Join DevPlace Free")
@@ -58,7 +58,7 @@ def test_full_user_journey(browser, app_server):
# ACT 3 — ALICE EXPLORES FEED
# ═══════════════════════════════════════════════
pa.bring_to_front()
pa.goto(f"{BASE_URL}/feed")
pa.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
for tab_text in ("Topics", "All", "Trending", "Recent", "Following",
"Devlog", "Showcase", "Question", "Rant", "Fun"):
assert pa.is_visible(f"text={tab_text}")
@@ -95,7 +95,7 @@ def test_full_user_journey(browser, app_server):
# ACT 5 — ALICE CREATES TWO PROJECTS
# ═══════════════════════════════════════════════
pa.bring_to_front()
pa.goto(f"{BASE_URL}/projects")
pa.goto(f"{BASE_URL}/projects", wait_until="domcontentloaded")
pa.locator("#create-project-btn").click()
pa.fill("#title", "N8Nme")
pa.fill("#description", "Visual workflow builder with 300+ integrations.")
@@ -124,7 +124,7 @@ def test_full_user_journey(browser, app_server):
# ACT 6 — ALICE EDITS HER PROFILE
# ═══════════════════════════════════════════════
pa.bring_to_front()
pa.goto(f"{BASE_URL}/profile/alice_demo")
pa.goto(f"{BASE_URL}/profile/alice_demo", wait_until="domcontentloaded")
assert pa.is_visible("text=alice_demo")
assert pa.is_visible("text=Member")
assert pa.is_visible("text=Posts")
@@ -146,11 +146,11 @@ def test_full_user_journey(browser, app_server):
assert pa.is_visible("text=https://github.com/alice_demo")
# Switch profile tabs
pa.goto(f"{BASE_URL}/profile/alice_demo?tab=projects")
pa.goto(f"{BASE_URL}/profile/alice_demo?tab=projects", wait_until="domcontentloaded")
pa.wait_for_timeout(300)
assert pa.is_visible("text=N8Nme")
assert pa.is_visible("text=DWN")
pa.goto(f"{BASE_URL}/profile/alice_demo?tab=posts")
pa.goto(f"{BASE_URL}/profile/alice_demo?tab=posts", wait_until="domcontentloaded")
pa.wait_for_timeout(300)
assert pa.is_visible("text=API Progress Update")
assert pa.is_visible("text=Pixel Editor WASM")
@@ -159,17 +159,17 @@ def test_full_user_journey(browser, app_server):
# ACT 7 — BOB EXPLORES ALICE'S STUFF
# ═══════════════════════════════════════════════
pb.bring_to_front()
pb.goto(f"{BASE_URL}/profile/alice_demo")
pb.goto(f"{BASE_URL}/profile/alice_demo", wait_until="domcontentloaded")
assert pb.is_visible("text=alice_demo")
assert pb.is_visible("text=Full-stack developer")
assert pb.is_visible("text=Berlin, Germany")
pb.goto(f"{BASE_URL}/profile/alice_demo?tab=projects")
pb.goto(f"{BASE_URL}/profile/alice_demo?tab=projects", wait_until="domcontentloaded")
assert pb.is_visible("text=N8Nme")
assert pb.is_visible("text=DWN")
# Browse projects, search
pb.goto(f"{BASE_URL}/projects")
pb.goto(f"{BASE_URL}/projects", wait_until="domcontentloaded")
assert pb.is_visible("text=Recently Released")
assert pb.is_visible("text=N8Nme")
pb.fill("input[placeholder='Search projects...']", "N8Nme")
@@ -226,7 +226,7 @@ def test_full_user_journey(browser, app_server):
# ACT 10 — ALICE CHECKS NOTIFICATIONS
# ═══════════════════════════════════════════════
pa.bring_to_front()
pa.goto(f"{BASE_URL}/notifications")
pa.goto(f"{BASE_URL}/notifications", wait_until="domcontentloaded")
assert pa.is_visible("h2:has-text('Notifications')")
mark_all = pa.locator("button:has-text('Mark all read')")
if mark_all.is_visible():
@@ -237,13 +237,13 @@ def test_full_user_journey(browser, app_server):
# ACT 11 — BOB CHECKS NOTIFICATIONS TOO
# ═══════════════════════════════════════════════
pb.bring_to_front()
pb.goto(f"{BASE_URL}/notifications")
pb.goto(f"{BASE_URL}/notifications", wait_until="domcontentloaded")
assert pb.is_visible("h2:has-text('Notifications')")
# ═══════════════════════════════════════════════
# ACT 12 — LOGOUT, LOGIN AGAIN
# ═══════════════════════════════════════════════
pa.goto(f"{BASE_URL}/auth/logout")
pa.goto(f"{BASE_URL}/auth/logout", wait_until="domcontentloaded")
pa.wait_for_url("**/", wait_until="domcontentloaded")
# Log back in
@@ -257,13 +257,13 @@ def test_full_user_journey(browser, app_server):
assert pa.is_visible("text=alice_demo")
# Bob logs out
pb.goto(f"{BASE_URL}/auth/logout")
pb.goto(f"{BASE_URL}/auth/logout", wait_until="domcontentloaded")
pb.wait_for_url("**/", wait_until="domcontentloaded")
# ═══════════════════════════════════════════════
# ACT 13 — FINAL VERIFICATION
# ═══════════════════════════════════════════════
pa.goto(f"{BASE_URL}/feed")
pa.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
assert pa.is_visible("text=Topics")
assert pa.is_visible("text=alice_demo")
+31 -4
View File
@@ -3,13 +3,13 @@ from tests.conftest import BASE_URL
def test_messages_page_loads(alice):
page, _ = alice
page.goto(f"{BASE_URL}/messages", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/messages")
assert page.is_visible(".messages-layout")
def test_messages_search_input(alice):
page, _ = alice
page.goto(f"{BASE_URL}/messages", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/messages")
search = page.locator("#message-search")
assert search.is_visible()
assert search.get_attribute("placeholder") == "Search conversations..."
@@ -17,7 +17,7 @@ def test_messages_search_input(alice):
def test_messages_empty_state(alice):
page, _ = alice
page.goto(f"{BASE_URL}/messages", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/messages")
empty = page.locator("text=No conversations yet")
if empty.is_visible():
pass
@@ -27,8 +27,35 @@ def test_messages_empty_state(alice):
def test_messages_search_for_bob(alice):
page, _ = alice
page.goto(f"{BASE_URL}/messages", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/messages")
page.fill("#message-search", "bob_test")
page.locator("#message-search").press("Enter")
page.wait_for_timeout(500)
assert page.is_visible(".messages-layout")
def test_messages_header_visible(alice):
page, _ = alice
page.goto(f"{BASE_URL}/messages")
assert page.is_visible("text=Home")
assert page.is_visible("text=Projects")
def test_messages_bell_icon(alice):
page, _ = alice
page.goto(f"{BASE_URL}/messages")
bell = page.locator(".topnav-icon").first
assert bell.is_visible()
def test_messages_topnav_user(alice):
page, user = alice
page.goto(f"{BASE_URL}/messages")
assert page.is_visible(f"text={user['username']}")
def test_messages_avatar_on_page(alice):
page, user = alice
page.goto(f"{BASE_URL}/messages")
avatar = page.locator("img.avatar-img").first
assert avatar.is_visible()
+35 -5
View File
@@ -3,13 +3,13 @@ from tests.conftest import BASE_URL
def test_notifications_page_loads(alice):
page, _ = alice
page.goto(f"{BASE_URL}/notifications", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/notifications")
assert page.is_visible("h2:has-text('Notifications')")
def test_mark_all_read(alice):
page, _ = alice
page.goto(f"{BASE_URL}/notifications", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/notifications")
mark_all = page.locator("button:has-text('Mark all read')")
if mark_all.is_visible():
mark_all.click()
@@ -18,13 +18,43 @@ def test_mark_all_read(alice):
def test_notifications_navigation(alice):
page, _ = alice
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/notifications", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/feed")
page.goto(f"{BASE_URL}/notifications")
assert page.is_visible("h2:has-text('Notifications')")
def test_notifications_bell_visible(alice):
page, _ = alice
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
page.goto(f"{BASE_URL}/feed")
bell = page.locator(".topnav-icon").first
assert bell.is_visible()
def test_notifications_empty_state(alice):
page, _ = alice
page.goto(f"{BASE_URL}/notifications")
empty = page.locator("text=No notifications yet")
if empty.is_visible():
pass
else:
assert page.is_visible("h2:has-text('Notifications')")
def test_notifications_header_has_mark_all(alice):
page, _ = alice
page.goto(f"{BASE_URL}/notifications")
mark_all = page.locator("button:has-text('Mark all read')")
assert mark_all.is_visible()
def test_notifications_topnav_user(alice):
page, user = alice
page.goto(f"{BASE_URL}/notifications")
assert page.is_visible(f"text={user['username']}")
def test_notifications_avatar_visible(alice):
page, _ = alice
page.goto(f"{BASE_URL}/notifications")
avatar = page.locator("img.avatar-img").first
assert avatar.is_visible()
-8
View File
@@ -3,14 +3,6 @@ from tests.conftest import BASE_URL
def create_post(page, topic="random", content="Test post content", title=None):
page.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
fab_count = page.locator(".feed-fab").count()
if fab_count == 0:
title = page.title()
has_fab = page.evaluate("document.body.innerHTML.includes('feed-fab')")
body_start = page.evaluate("document.body.innerHTML.substring(0, 500)")
print(f"\n[CREATE_POST] No FAB! URL={page.url} Title={title} has_fab={has_fab}")
print(f"[CREATE_POST] Body start: {body_start[:300]}")
page.wait_for_timeout(2000)
page.locator(".feed-fab").first.wait_for(state="visible", timeout=10000)
page.locator(".feed-fab").first.click()
page.check(f"input[value='{topic}']")
+82
View File
@@ -0,0 +1,82 @@
from devplacepy.utils import hash_password, verify_password, generate_uid, slugify, time_ago
from datetime import datetime, timedelta
def test_hash_password_returns_string():
result = hash_password("test123")
assert isinstance(result, str)
assert len(result) > 20
def test_verify_password_correct():
hashed = hash_password("correct-password")
assert verify_password("correct-password", hashed) is True
def test_verify_password_wrong():
hashed = hash_password("correct-password")
assert verify_password("wrong-password", hashed) is False
def test_generate_uid_is_unique():
uid1 = generate_uid()
uid2 = generate_uid()
assert uid1 != uid2
def test_generate_uid_format():
uid = generate_uid()
assert isinstance(uid, str)
assert len(uid) == 36 # UUID v4 format
def test_slugify_basic():
assert slugify("Hello World") == "hello-world"
def test_slugify_special_chars():
assert slugify("Hello! World???") == "hello-world"
def test_slugify_multiple_dashes():
assert slugify("hello---world") == "hello-world"
def test_slugify_leading_trailing():
assert slugify("--hello--") == "hello"
def test_time_ago_just_now():
now = datetime.utcnow().isoformat()
result = time_ago(now)
assert result == "just now"
def test_time_ago_minutes():
dt = (datetime.utcnow() - timedelta(minutes=5)).isoformat()
result = time_ago(dt)
assert "m ago" in result
def test_time_ago_hours():
dt = (datetime.utcnow() - timedelta(hours=3)).isoformat()
result = time_ago(dt)
assert "h ago" in result
def test_time_ago_days():
dt = (datetime.utcnow() - timedelta(days=5)).isoformat()
result = time_ago(dt)
assert "d ago" in result
def test_time_ago_months():
dt = (datetime.utcnow() - timedelta(days=60)).isoformat()
result = time_ago(dt)
assert "mo ago" in result
def test_time_ago_years():
dt = (datetime.utcnow() - timedelta(days=400)).isoformat()
result = time_ago(dt)
assert "y ago" in result