2026-07-19 18:57:43 +02:00
|
|
|
# retoor <retoor@molodetz.nl>
|
|
|
|
|
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from devplacepy.db_client import get_table
|
|
|
|
|
from devplacepy.utils import generate_uid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _now():
|
|
|
|
|
return datetime.now(timezone.utc).isoformat()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_user(local_db, **overrides):
|
|
|
|
|
uid = generate_uid()
|
|
|
|
|
row = {
|
|
|
|
|
"uid": uid,
|
|
|
|
|
"username": f"cpd_{uid[:8]}",
|
|
|
|
|
"email": f"cpd_{uid[:8]}@t.dev",
|
|
|
|
|
"api_key": generate_uid(),
|
|
|
|
|
"role": "Member",
|
|
|
|
|
"is_active": True,
|
|
|
|
|
"created_at": _now(),
|
|
|
|
|
"bio": "",
|
|
|
|
|
}
|
|
|
|
|
row.update(overrides)
|
|
|
|
|
get_table("users").insert(row)
|
|
|
|
|
return row
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_post(local_db, user, **overrides):
|
|
|
|
|
from devplacepy.utils import make_combined_slug
|
|
|
|
|
|
|
|
|
|
uid = generate_uid()
|
|
|
|
|
row = {
|
|
|
|
|
"uid": uid,
|
|
|
|
|
"user_uid": user["uid"],
|
|
|
|
|
"title": "Compound test post",
|
|
|
|
|
"content": "Body of the compound test post.",
|
|
|
|
|
"topic": "devlog",
|
|
|
|
|
"slug": make_combined_slug("Compound test post", uid),
|
|
|
|
|
"stars": 0,
|
|
|
|
|
"created_at": _now(),
|
|
|
|
|
"deleted_at": None,
|
|
|
|
|
"deleted_by": None,
|
|
|
|
|
}
|
|
|
|
|
row.update(overrides)
|
|
|
|
|
get_table("posts").insert(row)
|
|
|
|
|
return row
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_project(local_db, user, **overrides):
|
|
|
|
|
from devplacepy.utils import make_combined_slug
|
|
|
|
|
|
|
|
|
|
uid = generate_uid()
|
|
|
|
|
row = {
|
|
|
|
|
"uid": uid,
|
|
|
|
|
"user_uid": user["uid"],
|
|
|
|
|
"title": "Compound test project",
|
|
|
|
|
"description": "",
|
|
|
|
|
"slug": make_combined_slug("Compound test project", uid),
|
|
|
|
|
"stars": 0,
|
|
|
|
|
"is_private": 0,
|
|
|
|
|
"created_at": _now(),
|
|
|
|
|
"deleted_at": None,
|
|
|
|
|
"deleted_by": None,
|
|
|
|
|
}
|
|
|
|
|
row.update(overrides)
|
|
|
|
|
get_table("projects").insert(row)
|
|
|
|
|
return row
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_gist(local_db, user, **overrides):
|
|
|
|
|
from devplacepy.utils import make_combined_slug
|
|
|
|
|
|
|
|
|
|
uid = generate_uid()
|
|
|
|
|
row = {
|
|
|
|
|
"uid": uid,
|
|
|
|
|
"user_uid": user["uid"],
|
|
|
|
|
"title": "Compound test gist",
|
|
|
|
|
"description": "",
|
|
|
|
|
"source_code": "print('hi')",
|
|
|
|
|
"language": "python",
|
|
|
|
|
"slug": make_combined_slug("Compound test gist", uid),
|
|
|
|
|
"stars": 0,
|
|
|
|
|
"created_at": _now(),
|
|
|
|
|
"deleted_at": None,
|
|
|
|
|
"deleted_by": None,
|
|
|
|
|
}
|
|
|
|
|
row.update(overrides)
|
|
|
|
|
get_table("gists").insert(row)
|
|
|
|
|
return row
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Section 5.3 page compounds actually wired to a caller (db_client.build_*)
|
|
|
|
|
# and consumed by a real web route. Each assertion checks the template
|
|
|
|
|
# context keys the corresponding route/schema depends on.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_feed_page_shape(local_db):
|
|
|
|
|
from devplacepy_services.database.compounds_page import build_feed_page
|
|
|
|
|
|
|
|
|
|
user = _make_user(local_db)
|
|
|
|
|
post = _make_post(local_db, user)
|
|
|
|
|
|
|
|
|
|
result = build_feed_page(user=user)
|
|
|
|
|
|
|
|
|
|
for key in ("posts", "next_cursor", "stats", "top_authors", "daily_topic", "current_tab"):
|
|
|
|
|
assert key in result
|
|
|
|
|
uids = [item["post"]["uid"] for item in result["posts"]]
|
|
|
|
|
assert post["uid"] in uids
|
|
|
|
|
item = next(i for i in result["posts"] if i["post"]["uid"] == post["uid"])
|
|
|
|
|
for key in ("attachments", "recent_comments", "reactions", "bookmarked", "poll"):
|
|
|
|
|
assert key in item
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_post_detail_shape(local_db):
|
|
|
|
|
from devplacepy_services.database.compounds_page import build_post_detail
|
|
|
|
|
|
|
|
|
|
user = _make_user(local_db)
|
|
|
|
|
post = _make_post(local_db, user)
|
|
|
|
|
|
|
|
|
|
result = build_post_detail(post["uid"], user=user)
|
|
|
|
|
|
|
|
|
|
assert result["post"]["uid"] == post["uid"]
|
|
|
|
|
assert result["author"]["uid"] == user["uid"]
|
|
|
|
|
assert isinstance(result["comments"], list)
|
|
|
|
|
assert isinstance(result["attachments"], list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_post_detail_missing_post(local_db):
|
|
|
|
|
from devplacepy_services.database.compounds_page import build_post_detail
|
|
|
|
|
|
|
|
|
|
result = build_post_detail("does-not-exist")
|
|
|
|
|
assert result["post"] is None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_profile_bundle_shape(local_db):
|
|
|
|
|
from devplacepy_services.database.compounds_page import build_profile_bundle
|
|
|
|
|
|
|
|
|
|
user = _make_user(local_db)
|
|
|
|
|
|
|
|
|
|
result = build_profile_bundle(user["uid"])
|
|
|
|
|
|
|
|
|
|
assert result["user"]["uid"] == user["uid"]
|
|
|
|
|
assert "followers" in result["follow_counts"]
|
|
|
|
|
assert "following" in result["follow_counts"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_leaderboard_page_shape(local_db):
|
|
|
|
|
from devplacepy_services.database.compounds_page import build_leaderboard_page
|
|
|
|
|
|
|
|
|
|
user = _make_user(local_db)
|
|
|
|
|
|
|
|
|
|
result = build_leaderboard_page(viewer_uid=user["uid"])
|
|
|
|
|
|
|
|
|
|
assert "leaderboard" in result
|
|
|
|
|
assert isinstance(result["leaderboard"], list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_project_detail_shape(local_db):
|
|
|
|
|
from devplacepy_services.database.compounds_page import build_project_detail
|
|
|
|
|
|
|
|
|
|
user = _make_user(local_db)
|
|
|
|
|
project = _make_project(local_db, user)
|
|
|
|
|
|
|
|
|
|
result = build_project_detail(project["uid"])
|
|
|
|
|
|
|
|
|
|
assert result["project"]["uid"] == project["uid"]
|
|
|
|
|
assert result["owner"]["uid"] == user["uid"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_gist_detail_shape(local_db):
|
|
|
|
|
from devplacepy_services.database.compounds_page import build_gist_detail
|
|
|
|
|
|
|
|
|
|
user = _make_user(local_db)
|
|
|
|
|
gist = _make_gist(local_db, user)
|
|
|
|
|
|
|
|
|
|
result = build_gist_detail(gist["uid"], user=user)
|
|
|
|
|
|
|
|
|
|
assert result["gist"]["uid"] == gist["uid"]
|
|
|
|
|
assert result["author"]["uid"] == user["uid"]
|
|
|
|
|
assert isinstance(result["comments"], list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Section 5.3 rows registered as HTTP endpoints (devplacepy_services/database/routes.py)
|
|
|
|
|
# but not yet called by any web route (routers/messages.py, notifications.py,
|
|
|
|
|
# game/, admin/index.py all still do their own direct db_client calls) - these
|
|
|
|
|
# four builders are unfinished stubs, tracked here so a real implementation
|
|
|
|
|
# lands as a visible test change rather than silently.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
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
|
|
|
"fn_name,args,expected_keys",
|
2026-07-19 18:57:43 +02:00
|
|
|
[
|
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
|
|
|
("build_messages_page", ("u1",), {"threads", "unread", "partners"}),
|
|
|
|
|
("build_notifications_page", ("u1",), {"notifications", "actors", "unread_count"}),
|
|
|
|
|
("build_game_state", (), {"store", "leaderboard"}),
|
|
|
|
|
("build_admin_dashboard", (), {"stats", "service_states"}),
|
2026-07-19 18:57:43 +02:00
|
|
|
],
|
|
|
|
|
)
|
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
|
|
|
def test_stub_compounds_documented(local_db, fn_name, args, expected_keys):
|
2026-07-19 18:57:43 +02:00
|
|
|
import devplacepy_services.database.compounds_page as compounds_page
|
|
|
|
|
|
|
|
|
|
fn = getattr(compounds_page, fn_name)
|
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
|
|
|
result = fn(*args)
|
2026-07-19 18:57:43 +02:00
|
|
|
assert set(result.keys()) == expected_keys, (
|
|
|
|
|
f"{fn_name} shape changed - if this is a real implementation now, "
|
|
|
|
|
"replace this stub-tracking test with a real assertion and update "
|
|
|
|
|
"splitup.md Section 17.2"
|
|
|
|
|
)
|