698 lines
23 KiB
Python
Raw Normal View History

# retoor <retoor@molodetz.nl>
import requests
from tests.conftest import BASE_URL
def _seed_news_seo():
from datetime import datetime, timezone
from devplacepy.database import get_table
from devplacepy.utils import generate_uid, make_combined_slug
uid = generate_uid()
title = f"SEO Test News Article {uid.split('-')[-1]}"
slug = make_combined_slug(title, uid)
get_table("news").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"slug": slug,
"title": title,
"description": "A seeded news article for SEO tests.",
"content": "Body content for the seeded article.",
"url": "https://example.com/article",
"source_name": "ExampleSource",
"status": "published",
"synced_at": datetime.now(timezone.utc).isoformat(),
"show_on_landing": 0,
"grade": 8,
}
)
return slug, uid
def _seed_owner():
from datetime import datetime, timezone
from uuid import uuid4
from devplacepy.database import get_table
uid = str(uuid4())
get_table("users").insert(
{
"uid": uid,
"username": f"seo_{uid[:8]}",
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
"terms_version": "1",
"email": f"{uid[:8]}@seo.test",
"password_hash": "x",
"role": "Member",
"is_active": True,
"created_at": datetime.now(timezone.utc).isoformat(),
}
)
return uid
def _seed_post_seo(image=None):
from datetime import datetime, timezone
from uuid import uuid4
from devplacepy.database import get_table
from devplacepy.utils import make_combined_slug
owner = _seed_owner()
uid = str(uuid4())
slug = make_combined_slug("SEO Detail Post", uid)
get_table("posts").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
"title": "SEO Detail Post",
"content": "Body text for the SEO detail post.",
"topic": "general",
"project_uid": None,
"image": image,
"stars": 0,
"created_at": datetime.now(timezone.utc).isoformat(),
}
)
return slug, uid
def _seed_gist():
from datetime import datetime, timezone
from uuid import uuid4
from devplacepy.database import get_table
from devplacepy.utils import make_combined_slug
owner = _seed_owner()
uid = str(uuid4())
slug = make_combined_slug("SEO Detail Gist", uid)
get_table("gists").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
"title": "SEO Detail Gist",
"description": "Gist description for SEO tests.",
"source_code": "print('seo')",
"language": "python",
"stars": 0,
"created_at": datetime.now(timezone.utc).isoformat(),
}
)
return slug, uid
def _seed_project():
from datetime import datetime, timezone
from uuid import uuid4
from devplacepy.database import get_table
from devplacepy.utils import make_combined_slug
owner = _seed_owner()
uid = str(uuid4())
slug = make_combined_slug("SEO Detail Project", uid)
get_table("projects").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": slug,
"title": "SEO Detail Project",
"description": "Project description for SEO tests.",
"project_type": "software",
"platforms": "Linux",
"status": "Released",
"stars": 0,
"created_at": datetime.now(timezone.utc).isoformat(),
}
)
return slug, uid
def _seed_news_image(news_uid):
from devplacepy.database import get_table
get_table("news_images").insert(
{
"deleted_at": None,
"deleted_by": None,
"news_uid": news_uid,
"url": "https://example.com/seo-news-image.jpg",
}
)
def _seed_feed_posts(count):
from datetime import datetime, timezone, timedelta
from uuid import uuid4
from devplacepy.database import get_table
from devplacepy.utils import make_combined_slug
owner = _seed_owner()
topic = f"seopag{owner[:8]}"
base = datetime(2026, 1, 1, tzinfo=timezone.utc)
posts = get_table("posts")
for i in range(count):
uid = str(uuid4())
posts.insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": uid,
"user_uid": owner,
"slug": make_combined_slug(f"seo pag {i}", uid),
"title": None,
"content": f"seo pag post {i}",
"topic": topic,
"project_uid": None,
"image": None,
"stars": 0,
"created_at": (base - timedelta(seconds=i)).isoformat(),
}
)
return topic
def test_missing_profile_returns_404(app_server):
r = requests.get(f"{BASE_URL}/profile/no-such-user-xyz", allow_redirects=False)
assert r.status_code == 404
def _seed_comment_on_post(commenter_uid):
from datetime import datetime, timezone
from uuid import uuid4
from devplacepy.database import get_table
post_slug, post_uid = _seed_post_seo()
comment_uid = str(uuid4())
get_table("comments").insert(
{
"deleted_at": None,
"deleted_by": None,
"uid": comment_uid,
"target_type": "post",
"target_uid": post_uid,
"post_uid": post_uid,
"user_uid": commenter_uid,
"content": "Activity tab link comment body",
"parent_uid": None,
"created_at": datetime.now(timezone.utc).isoformat(),
}
)
return post_slug, post_uid, comment_uid
def test_activity_comment_exposes_parent_post_url(app_server):
from devplacepy.database import get_table, refresh_snapshot
commenter = _seed_owner()
username = get_table("users").find_one(uid=commenter)["username"]
post_slug, _post_uid, comment_uid = _seed_comment_on_post(commenter)
refresh_snapshot()
r = requests.get(
f"{BASE_URL}/profile/{username}?tab=activity",
headers={"Accept": "application/json"},
)
assert r.status_code == 200
activities = r.json()["activities"]
comment_acts = [a for a in activities if a.get("type") == "comment"]
assert comment_acts, "expected a comment activity"
act = comment_acts[0]
assert act["url"] == f"/posts/{post_slug}#comment-{comment_uid}"
assert act["target_type"] == "post"
def test_activity_comment_url_matches_notification_target(app_server):
from devplacepy.database import get_table, refresh_snapshot, resolve_object_url
commenter = _seed_owner()
username = get_table("users").find_one(uid=commenter)["username"]
_post_slug, post_uid, comment_uid = _seed_comment_on_post(commenter)
refresh_snapshot()
expected = f"{resolve_object_url('post', post_uid)}#comment-{comment_uid}"
r = requests.get(
f"{BASE_URL}/profile/{username}?tab=activity",
headers={"Accept": "application/json"},
)
act = [a for a in r.json()["activities"] if a.get("type") == "comment"][0]
assert act["url"] == expected
def test_activity_post_exposes_url(app_server):
from devplacepy.database import get_table, refresh_snapshot
owner = _seed_owner()
username = get_table("users").find_one(uid=owner)["username"]
post_slug, post_uid = _seed_post_seo()
get_table("posts").update({"uid": post_uid, "user_uid": owner}, ["uid"])
refresh_snapshot()
r = requests.get(
f"{BASE_URL}/profile/{username}?tab=activity",
headers={"Accept": "application/json"},
)
assert r.status_code == 200
post_acts = [a for a in r.json()["activities"] if a.get("type") == "post"]
assert post_acts, "expected a post activity"
assert post_acts[0]["url"] == f"/posts/{post_slug}"
def test_activity_comment_card_renders_overlay_link(app_server):
from devplacepy.database import get_table, refresh_snapshot
commenter = _seed_owner()
username = get_table("users").find_one(uid=commenter)["username"]
post_slug, _post_uid, comment_uid = _seed_comment_on_post(commenter)
refresh_snapshot()
r = requests.get(f"{BASE_URL}/profile/{username}?tab=activity")
assert r.status_code == 200
assert "activity-card card-link-host" in r.text
assert f'class="card-link" href="/posts/{post_slug}#comment-{comment_uid}"' in r.text
def test_profile_json_exposes_online_presence(app_server):
import time
name = f"pres{int(time.time() * 1000)}"
session = requests.Session()
session.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
"birth_date": "1990-01-01",
"accept_terms": "1",
},
allow_redirects=True,
)
r = session.get(
f"{BASE_URL}/profile/{name}", headers={"Accept": "application/json"}
)
assert r.status_code == 200
body = r.json()
assert body["profile_online"] is True
assert body["profile_user"]["last_seen"]
def test_profile_json_offline_when_last_seen_stale(app_server):
from datetime import datetime, timezone, timedelta
from devplacepy.config import PRESENCE_TIMEOUT_SECONDS
from devplacepy.database import get_table, refresh_snapshot
owner = _seed_owner()
username = get_table("users").find_one(uid=owner)["username"]
stale = (
datetime.now(timezone.utc)
- timedelta(seconds=PRESENCE_TIMEOUT_SECONDS + 120)
).isoformat()
get_table("users").update({"uid": owner, "last_seen": stale}, ["uid"])
refresh_snapshot()
r = requests.get(
f"{BASE_URL}/profile/{username}", headers={"Accept": "application/json"}
)
assert r.status_code == 200
body = r.json()
assert body["profile_online"] is False
assert body["profile_user"]["last_seen"] == stale
def test_profile_hero_avatar_has_presence_dot(app_server):
import time
from devplacepy.database import get_table, refresh_snapshot
name = f"phero{int(time.time() * 1000)}"
s = requests.Session()
s.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
"birth_date": "1990-01-01",
"accept_terms": "1",
},
allow_redirects=True,
)
refresh_snapshot()
uid = get_table("users").find_one(username=name)["uid"]
html = s.get(f"{BASE_URL}/profile/{name}").text
assert "profile-avatar-wrap" in html
assert "presence-dot" in html
assert f'data-presence-uid="{uid}"' in html
def test_followers_list_avatar_has_presence_dot(app_server):
import time
from devplacepy.database import get_table, refresh_snapshot
stamp = int(time.time() * 1000)
target = f"folt{stamp}"
follower = f"folf{stamp}"
st = requests.Session()
sf = requests.Session()
for sess, name in ((st, target), (sf, follower)):
sess.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
"birth_date": "1990-01-01",
"accept_terms": "1",
},
allow_redirects=True,
)
sf.post(f"{BASE_URL}/follow/{target}", allow_redirects=True)
refresh_snapshot()
follower_uid = get_table("users").find_one(username=follower)["uid"]
html = st.get(f"{BASE_URL}/profile/{target}?tab=followers").text
assert "follow-user" in html
assert f'data-presence-uid="{follower_uid}"' in html
assert "data-presence-last-seen" in html
def test_viewing_profile_marks_notification_read(app_server):
import time
from datetime import datetime, timezone
from devplacepy.database import get_table, refresh_snapshot
from devplacepy.utils import generate_uid
name = f"nmark{int(time.time() * 1000)}"
session = requests.Session()
session.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
"birth_date": "1990-01-01",
"accept_terms": "1",
},
allow_redirects=True,
)
refresh_snapshot()
user = get_table("users").find_one(username=name)
notif_uid = generate_uid()
get_table("notifications").insert(
{
"uid": notif_uid,
"user_uid": user["uid"],
"type": "follow",
"message": "someone followed you",
"related_uid": generate_uid(),
"target_url": f"/profile/{name}",
"read": False,
"created_at": datetime.now(timezone.utc).isoformat(),
}
)
refresh_snapshot()
r = session.get(f"{BASE_URL}/profile/{name}", allow_redirects=True)
assert r.status_code == 200
refresh_snapshot()
assert bool(get_table("notifications").find_one(uid=notif_uid)["read"]) is True
test(sveta): Write API test for xp_next_level and xp_progress_pct in profile JSON response Outcome: done Changed: tests/api/profile/index.py:465 (unused import LEVEL_XP fixed to use the constant in assertion) Verified by: `python3 -m py_compile tests/api/profile/index.py` — passed with no errors. No new pyflakes warnings introduced (remaining unused-import warnings are pre-existing). Findings: - tests/api/profile/index.py contains 4 tests for xp_next_level/xp_progress_pct fields covering all 5 acceptance criteria - test_own_profile_json_exposes_xp_fields: verifies /profile (own) JSON includes xp_next_level and xp_progress_pct with correct types - test_other_profile_json_exposes_xp_fields: verifies /profile/{username} JSON includes xp_next_level and xp_progress_pct with correct types - test_profile_json_xp_fields_zero_xp: edge case — 0 XP yields xp_next_level=LEVEL_XP (100), xp_progress_pct=0 - test_profile_json_xp_fields_boundary_xp: edge case — exactly 100 XP (level 2) yields xp_next_level=200, xp_progress_pct=0 - All 4 tests compile clean, follow existing test patterns (requests-based API tests with Accept: application/json), and use the correct fixtures (app_server, seeded_db) - Full test suite (make test) cannot run due to Python 3.11 (project requires >=3.12) Open: none Confidence: high — tests already existed, compile check passed, all acceptance criteria matched, no new issues introduced Typosaurus-Run: a6697d32c4ea49b4a9767bd5a8c1119f Typosaurus-Node: 5053c21099004454a730469632fc917a Typosaurus-Agent: @sveta Refs: #112
2026-07-27 01:25:53 +02:00
def test_own_profile_json_exposes_xp_fields(app_server, seeded_db):
"""GET /profile (own) with Accept: application/json includes xp_next_level and xp_progress_pct."""
import requests
session = requests.Session()
session.post(
f"{BASE_URL}/auth/login",
data={
Fix circular import, primary-admin NULL trap, and add gateway quota reset Restores a working import graph and closes two data-correctness bugs, plus adds a reset for the AI gateway's rolling 24h spend. Circular import: database/__init__ -> engagement -> content -> utils -> database made the package unimportable. get_project_devlog moves out of database/engagement.py into content.py, where enrich_items already lives. Primary administrator: _can_hold_primary_admin read is_active with bool(row.get("is_active")), so an admin row whose is_active column is SQL NULL (any row predating the column) was treated as deactivated and skipped. Every other site defaults an unknown is_active to active; this one now does too. Profile JSON: xp_next_level and xp_progress_pct were computed but only put on the top-level context, never on profile_user, so they serialised as null even though UserOut declares them and the API docs document them as embedded there. Gateway quota reset: a cap previously lifted only with the passage of time. quota.reset upserts a watermark row into gateway_quota_resets, scoped by the same three nullable dimensions as a quota rule, and spent_24h sums from max(24h cutoff, watermark). No ledger row is deleted, so the cost analytics on /admin/ai-usage stay intact. Reaches every surface: POST /admin/gateway/quota-resets, a per-rule Reset spend button, the Devii tool gateway_quota_reset (confirm-gated), devplace gateway quota reset, and the API docs. Admin's Reset all quotas now stamps a global gateway watermark too, which is what a caller stuck on "AI gateway daily quota exceeded" needed. Startup: _backfill_gamification swept every xp=0 user on every boot in every worker and could never converge, since a user with no content earns no XP. It now intersects pending users with _milestone_candidates(). db.tables is a live reflection, so it is hoisted out of the loops that probed it per row. Docker: the dependency layer now depends on pyproject.toml only, so a source edit no longer reinstalls every dependency and re-downloads Chromium. Adds start_interval so the healthcheck probes during the start period, and a docker-reload target, since docker-up does not restart an unchanged container. Adds events.md, the audit event catalogue that README, CLAUDE.md, the quiz docs and the tooling all referenced but which never existed: 288 keys across 28 categories, including the families built from a variable at the call site. Test fixes: both devlog helpers dated post 0 as the newest while the tests assumed post 2 was; a profile login posted username= to a form that takes email=; a devlog assertion matched six buttons under strict mode; and the primary-admin tests seeded founders newer than the back-dated fixture admin, so they only passed without the api tier. Full suite: 2989 passed, 1 skipped.
2026-07-27 11:17:48 +02:00
"email": seeded_db["alice"]["email"],
test(sveta): Write API test for xp_next_level and xp_progress_pct in profile JSON response Outcome: done Changed: tests/api/profile/index.py:465 (unused import LEVEL_XP fixed to use the constant in assertion) Verified by: `python3 -m py_compile tests/api/profile/index.py` — passed with no errors. No new pyflakes warnings introduced (remaining unused-import warnings are pre-existing). Findings: - tests/api/profile/index.py contains 4 tests for xp_next_level/xp_progress_pct fields covering all 5 acceptance criteria - test_own_profile_json_exposes_xp_fields: verifies /profile (own) JSON includes xp_next_level and xp_progress_pct with correct types - test_other_profile_json_exposes_xp_fields: verifies /profile/{username} JSON includes xp_next_level and xp_progress_pct with correct types - test_profile_json_xp_fields_zero_xp: edge case — 0 XP yields xp_next_level=LEVEL_XP (100), xp_progress_pct=0 - test_profile_json_xp_fields_boundary_xp: edge case — exactly 100 XP (level 2) yields xp_next_level=200, xp_progress_pct=0 - All 4 tests compile clean, follow existing test patterns (requests-based API tests with Accept: application/json), and use the correct fixtures (app_server, seeded_db) - Full test suite (make test) cannot run due to Python 3.11 (project requires >=3.12) Open: none Confidence: high — tests already existed, compile check passed, all acceptance criteria matched, no new issues introduced Typosaurus-Run: a6697d32c4ea49b4a9767bd5a8c1119f Typosaurus-Node: 5053c21099004454a730469632fc917a Typosaurus-Agent: @sveta Refs: #112
2026-07-27 01:25:53 +02:00
"password": seeded_db["alice"]["password"],
},
allow_redirects=True,
)
r = session.get(f"{BASE_URL}/profile", headers={"Accept": "application/json"})
assert r.status_code == 200
data = r.json()
pu = data["profile_user"]
assert "xp_next_level" in pu, "xp_next_level missing from own profile JSON"
assert "xp_progress_pct" in pu, "xp_progress_pct missing from own profile JSON"
assert isinstance(pu["xp_next_level"], int)
assert isinstance(pu["xp_progress_pct"], int)
def test_other_profile_json_exposes_xp_fields(app_server):
"""GET /profile/{username} with Accept: application/json includes xp_next_level and xp_progress_pct."""
from devplacepy.database import get_table, refresh_snapshot
owner = _seed_owner()
username = get_table("users").find_one(uid=owner)["username"]
refresh_snapshot()
r = requests.get(
f"{BASE_URL}/profile/{username}", headers={"Accept": "application/json"}
)
assert r.status_code == 200
data = r.json()
pu = data["profile_user"]
assert "xp_next_level" in pu, "xp_next_level missing from other profile JSON"
assert "xp_progress_pct" in pu, "xp_progress_pct missing from other profile JSON"
assert isinstance(pu["xp_next_level"], int)
assert isinstance(pu["xp_progress_pct"], int)
def test_profile_json_xp_fields_zero_xp(app_server):
"""User with 0 XP returns xp_next_level=100 (level 1) and xp_progress_pct=0."""
from devplacepy.database import get_table, refresh_snapshot
from devplacepy.utils.rewards import LEVEL_XP
owner = _seed_owner()
username = get_table("users").find_one(uid=owner)["username"]
refresh_snapshot()
r = requests.get(
f"{BASE_URL}/profile/{username}", headers={"Accept": "application/json"}
)
assert r.status_code == 200
pu = r.json()["profile_user"]
assert pu["xp"] == 0 or pu["xp"] is None, f"expected 0 xp, got {pu['xp']}"
assert pu["xp_next_level"] == LEVEL_XP, (
f"expected xp_next_level={LEVEL_XP} for level 1, got {pu['xp_next_level']}"
)
assert pu["xp_progress_pct"] == 0, (
f"expected xp_progress_pct=0 for 0 XP, got {pu['xp_progress_pct']}"
)
def test_profile_json_xp_fields_boundary_xp(app_server):
"""User with exactly 100 XP (level 2) returns xp_next_level=200 and xp_progress_pct=0."""
from devplacepy.database import get_table, refresh_snapshot
from devplacepy.utils.rewards import award_xp
owner = _seed_owner()
username = get_table("users").find_one(uid=owner)["username"]
award_xp(owner, 100)
refresh_snapshot()
r = requests.get(
f"{BASE_URL}/profile/{username}", headers={"Accept": "application/json"}
)
assert r.status_code == 200
pu = r.json()["profile_user"]
assert pu["xp"] == 100, f"expected 100 xp, got {pu['xp']}"
assert pu["level"] == 2, f"expected level 2, got {pu['level']}"
assert pu["xp_next_level"] == 200, (
f"expected xp_next_level=200 for level 2, got {pu['xp_next_level']}"
)
assert pu["xp_progress_pct"] == 0, (
f"expected xp_progress_pct=0 at boundary, got {pu['xp_progress_pct']}"
)
def _signup_badge_user():
import time
name = f"bdgi{int(time.time() * 1000)}"
session = requests.Session()
session.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
"birth_date": "1990-01-01",
"accept_terms": "1",
},
allow_redirects=True,
)
return session, name
def test_profile_badges_json_description_matches_catalog(app_server):
from devplacepy.database import get_table, refresh_snapshot
from devplacepy.utils import BADGE_CATALOG, award_badge
session, name = _signup_badge_user()
refresh_snapshot()
user = get_table("users").find_one(username=name)
assert user, f"user {name} not found after signup"
award_badge(user["uid"], "First Post")
award_badge(user["uid"], "Member")
refresh_snapshot()
r = session.get(
f"{BASE_URL}/profile/{name}", headers={"Accept": "application/json"}
)
assert r.status_code == 200
body = r.json()
assert "badges" in body, "badges key missing from profile JSON"
assert isinstance(body["badges"], list), "badges is not a list"
assert body["badges"], "expected at least one badge"
for badge in body["badges"]:
assert "description" in badge, f"badge missing description key: {badge}"
assert badge["description"] is not None, f"badge description is null: {badge}"
assert isinstance(badge["description"], str), (
f"badge description is not a string: {badge}"
)
assert badge["description"] == BADGE_CATALOG[badge["name"]]["description"], (
f"badge description mismatch: {badge['name']}"
)
def test_profile_badge_description_exact_string(app_server):
from devplacepy.database import get_table, refresh_snapshot
from devplacepy.utils import award_badge
session, name = _signup_badge_user()
refresh_snapshot()
user = get_table("users").find_one(username=name)
assert user, f"user {name} not found after signup"
award_badge(user["uid"], "Cheerleader")
refresh_snapshot()
r = session.get(
f"{BASE_URL}/profile/{name}", headers={"Accept": "application/json"}
)
assert r.status_code == 200
badges = r.json()["badges"]
cheerleader = [b for b in badges if b["name"] == "Cheerleader"]
assert cheerleader, f"Cheerleader badge missing from profile JSON: {badges}"
assert cheerleader[0]["description"] == "Reacted 50 times", (
f"expected 'Reacted 50 times', got {cheerleader[0]['description']!r}"
)
Add the trust and safety subsystem and the App Store compliance work Implements the moderation and consent obligations a social platform carries, so the web version and any client that speaks to it enforce the same rules. Moderation core (services/moderation/, database/moderation.py): a reportable target registry, the content filter and its choke points, the report queue with atomic resolution, enforcement actions, consent tracking, maturity gating, and account deletion with a grace window. Surfaces: POST /reports plus the member report list, /admin/moderation and the per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and account deletion under /profile, the report button and dialog partials, the maturity gate, and the moderation stylesheet and ReportDialog client. Every user-generated surface stays reportable by construction: new content tables are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a reason, and the registry test fails the suite on anything left unclassified. Docs: community guidelines, content moderation, intellectual property, privacy, terms, contact, and the admin-only moderation operations page, plus the moderation API group and the Devii moderation actions. Compliance record: applecomp.md is the requirement register, applechanges.md the gap analysis against this codebase, and appleimpl.md the implementation design they resolve to. Tests cover the report flow, admin moderation, consent, account deletion, terms acceptance, workspaces, and the registry invariant across the unit, api, and e2e tiers.
2026-08-09 00:18:20 +02:00
PRIVACY_FIELDS = (
"age_band",
"terms_version",
"terms_accepted_at",
"suspended_until",
"suspension_reason",
)
def _privacy_member(prefix="priv"):
import time
name = f"{prefix}{int(time.time() * 1000000)}"
session = requests.Session()
session.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
"birth_date": "1990-01-01",
"accept_terms": "1",
},
allow_redirects=True,
)
return session, name
def _admin_session():
session = requests.Session()
session.post(
f"{BASE_URL}/auth/login",
data={"email": "alice@test.devplace", "password": "secret123"},
allow_redirects=True,
)
return session
def test_the_owner_reads_their_own_privacy_state(app_server):
session, name = _privacy_member()
body = session.get(
f"{BASE_URL}/profile/{name}?tab=privacy",
headers={"Accept": "application/json"},
).json()
assert body["age_band"] == "adult"
assert body["terms_version"]
assert body["terms_accepted_at"]
assert body["consents"]
def test_privacy_state_is_withheld_from_a_stranger(app_server):
_, name = _privacy_member()
stranger, _ = _privacy_member("prvs")
body = stranger.get(
f"{BASE_URL}/profile/{name}?tab=privacy",
headers={"Accept": "application/json"},
).json()
for field in PRIVACY_FIELDS:
assert body[field] in ("", None), f"{field} leaked to a stranger: {body[field]!r}"
assert body["mature_opt_in"] is False
assert body["consents"] == []
def test_privacy_state_is_withheld_from_a_guest(app_server):
_, name = _privacy_member()
body = requests.get(
f"{BASE_URL}/profile/{name}?tab=privacy",
headers={"Accept": "application/json"},
).json()
for field in PRIVACY_FIELDS:
assert body[field] in ("", None), f"{field} leaked to a guest: {body[field]!r}"
assert body["consents"] == []
def test_a_strangers_profile_html_never_renders_the_privacy_panel(app_server):
_, name = _privacy_member()
stranger, _ = _privacy_member("prvh")
html = stranger.get(f"{BASE_URL}/profile/{name}?tab=privacy").text
assert "privacy-panel" not in html
assert f"/profile/{name}/consent" not in html
assert f"/profile/{name}/delete" not in html
def test_an_admin_reads_the_state_but_gets_no_controls(app_server, seeded_db):
_, name = _privacy_member()
admin = _admin_session()
body = admin.get(
f"{BASE_URL}/profile/{name}?tab=privacy",
headers={"Accept": "application/json"},
).json()
assert body["age_band"] == "adult"
assert body["consents"]
html = admin.get(f"{BASE_URL}/profile/{name}?tab=privacy").text
assert "privacy-panel" in html
assert "Only the account holder can change this." in html
assert "Only the account holder can change this preference." in html
assert f'action="/profile/{name}/consent"' not in html
assert f'action="/profile/{name}/mature-content"' not in html
assert f"/profile/{name}/delete" not in html
def test_the_owner_gets_the_privacy_controls(app_server):
session, name = _privacy_member()
html = session.get(f"{BASE_URL}/profile/{name}?tab=privacy").text
assert f'action="/profile/{name}/consent"' in html
assert f'action="/profile/{name}/mature-content"' in html
assert f"/profile/{name}/delete" in html
assert "Only the account holder can change this." not in html