2026-06-13 16:32:33 +02:00
|
|
|
# retoor <retoor@molodetz.nl>
|
|
|
|
|
|
feat: add leaderboard router, gamification backfill, and shared content creation helpers
Introduce a new `/leaderboard` endpoint ranking top 50 members by total stars, wire it into the app router and documentation. Implement `_backfill_gamification()` in `database.py` to compute XP/levels for existing accounts from prior posts, comments, votes, and follows. Extract `is_owner()`, `create_content_item()`, and `detail_context()` into `content.py` to centralize content creation, reward awarding, mention notifications, and attachment linking. Update comment creation/deletion in `comments.py` to use `award_rewards()` with `XP_COMMENT` and the new `is_owner()` helper. Add shared template partials documentation for vote bars, star buttons, post headers, and topic selectors to `AGENTS.md`.
2026-05-30 20:16:39 +02:00
|
|
|
from tests.conftest import BASE_URL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_leaderboard_page_loads(alice):
|
|
|
|
|
page, _ = alice
|
|
|
|
|
page.goto(f"{BASE_URL}/leaderboard", wait_until="domcontentloaded")
|
|
|
|
|
assert page.locator(".leaderboard-header h1").is_visible()
|
|
|
|
|
assert page.is_visible("text=Ranked by total stars")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_leaderboard_no_pagination(alice):
|
|
|
|
|
page, _ = alice
|
|
|
|
|
page.goto(f"{BASE_URL}/leaderboard", wait_until="domcontentloaded")
|
|
|
|
|
assert page.locator(".pagination").count() == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_leaderboard_ranks_after_upvote(app_server, browser, seeded_db):
|
|
|
|
|
from tests.conftest import login_user
|
|
|
|
|
ctx_a = browser.new_context(viewport={"width": 1400, "height": 900})
|
|
|
|
|
ctx_b = browser.new_context(viewport={"width": 1400, "height": 900})
|
2026-06-12 06:37:12 +02:00
|
|
|
try:
|
|
|
|
|
pa = ctx_a.new_page()
|
|
|
|
|
pb = ctx_b.new_page()
|
|
|
|
|
pa.set_default_timeout(15000)
|
|
|
|
|
pb.set_default_timeout(15000)
|
|
|
|
|
|
|
|
|
|
login_user(pa, seeded_db["alice"])
|
|
|
|
|
login_user(pb, seeded_db["bob"])
|
|
|
|
|
|
|
|
|
|
pb.goto(f"{BASE_URL}/feed", wait_until="domcontentloaded")
|
|
|
|
|
pb.locator(".feed-fab").first.wait_for(state="visible", timeout=10000)
|
|
|
|
|
pb.locator(".feed-fab").first.click()
|
|
|
|
|
pb.fill("#post-content", "Post for leaderboard ranking test")
|
|
|
|
|
pb.locator("#create-post-modal button.btn-primary:has-text('Post')").click()
|
|
|
|
|
pb.wait_for_url("**/posts/*", timeout=10000, wait_until="domcontentloaded")
|
|
|
|
|
post_url = pb.url
|
|
|
|
|
|
|
|
|
|
pa.goto(post_url, wait_until="domcontentloaded")
|
|
|
|
|
pa.wait_for_timeout(1000)
|
test(sveta): Extend vote-state tests to assert the non-color cue
Outcome: done
Changed: tests/conftest.py:293-300, tests/e2e/post.py:5,74-157, tests/e2e/posts/index.py:17,396-418, tests/e2e/notifications/index.py:313,799, tests/e2e/leaderboard.py:42
Verified by: python -m pytest tests/e2e/post.py tests/e2e/posts/index.py tests/e2e/notifications/index.py tests/e2e/leaderboard.py -> 68 passed (verify() passed); make test full suite -> 3344 passed, 1 skipped, 10 failed (9x tests/api/projects/workspace.py: docker binary absent in this environment, same as prior sibling run; 1x tests/e2e/game/farm.py::test_victim_notification_names_the_raider_and_the_amount, passes standalone and 9/9 in its file, game code has no vote markup)
Findings: New vote_glyph helper (tests/conftest.py:293) reads getComputedStyle(el,'::before').content and normalizes CSS escapes; glyph assertions cover post upvote (+ -> U+2295), post downvote (U+2212 -> U+2296), and comment upvote (+ -> U+2295), each also asserting unvoted glyph absence and toggle-off restoration to the unvoted glyph. New test fails against pre-change code: on 72e11db, test_post_upvote_voted_state_persists fails with "assert 'none' == '+'" (no ::before rule existed; buttons held literal text). Fixed all three has_text="+" vote locators (notifications/index.py:313,799, leaderboard.py:42) to class-based button.post-action-btn.vote-up; the three tests now pass. test_comment_voted_state_persists was made deterministic by wrapping the click in expect_response (pre-existing reload race); it now passes reliably
Typosaurus-Run: c36faeceb55d45b6bf38a45eec4f47ab
Typosaurus-Node: 9b6a5ec294a2472e800fbfeed775b27b
Typosaurus-Agent: @sveta
Refs: #162
2026-08-15 05:01:50 +02:00
|
|
|
vote_btn = pa.locator("button.post-action-btn.vote-up").first
|
2026-06-12 06:37:12 +02:00
|
|
|
vote_btn.wait_for(state="visible", timeout=10000)
|
|
|
|
|
vote_btn.click()
|
|
|
|
|
pa.wait_for_timeout(1500)
|
|
|
|
|
|
2026-07-23 19:09:49 +02:00
|
|
|
ranked = False
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
for _ in range(10):
|
2026-07-23 19:09:49 +02:00
|
|
|
pa.goto(f"{BASE_URL}/leaderboard", wait_until="domcontentloaded")
|
|
|
|
|
body = pa.locator("body").text_content()
|
|
|
|
|
assert "Internal Server Error" not in body, f"Got 500 on leaderboard: {body[:300]}"
|
|
|
|
|
if pa.is_visible("a.leaderboard-name:has-text('bob_test')"):
|
|
|
|
|
ranked = True
|
|
|
|
|
break
|
Report every test failure in one pass and fix the whole suite
The suite ran with -x, so a run stopped at the first failure and finding N
failures cost N full runs. Move -rf into the pytest addopts so every run
lists each failure, and add the triage targets test-fast (unit + api, no
browser), test-failed (--last-failed), test-first-failure (the old -x),
test-slowest and test-cache-clean. A stale .pytest_cache holding node ids
from deleted files made --last-failed select everything; make clean and
test-cache-clean drop it.
Fix the fifteen failures this surfaced.
DeepSearch crawling raised AttributeError in its finally block on every run:
async_playwright().__aenter__() returns a Playwright, which has no __aexit__.
Use start()/stop() at both call sites.
Update the tests left behind by changed signatures: VectorStore is async now,
fetch_page takes a browser, _summary_payload takes dom_evidence, and the
messages/notifications page compounds take a user_uid.
Close four real flakes that fail-fast had been hiding, all of them late in
the run. Harvest assertions pinned crop.reward_coins while is_golden pays
five times on about five percent of harvests, so they now assert through
realizable_harvest_coins with the observed golden flag. Market saturation
fixtures assumed a single active farm and landed two tiers milder once the
api and e2e tiers had created farms, so they scale by active_farms(). The
primary-administrator container test raced the one second cross-worker cache
version window and now waits for the server to agree. The isslop tools test
matched the collapsed nav dropdown link instead of the tools grid card.
Stop burning ninety seconds waiting out server-side display caches:
DEVPLACE_RANKING_TTL and DEVPLACE_MARKET_SATURATION_TTL follow the existing
sitemap and home cache precedent and are zero for the suite, taking the
leaderboard test from 60.6s to 4.4s and the saturation test from 30.1s to
under a second.
2881 passed, 1 skipped in 15:13.
2026-07-26 16:02:36 +02:00
|
|
|
pa.wait_for_timeout(500)
|
|
|
|
|
assert ranked, "bob_test never appeared on the leaderboard"
|
2026-06-12 06:37:12 +02:00
|
|
|
finally:
|
|
|
|
|
ctx_a.close()
|
2026-06-13 16:32:33 +02:00
|
|
|
ctx_b.close()
|