675 lines
21 KiB
Python
Raw Normal View History

# retoor <retoor@molodetz.nl>
from datetime import datetime, timezone
from devplacepy.database import (
get_table,
get_users_by_uids,
get_vote_counts,
get_user_votes,
get_comment_counts_by_post_uids,
resolve_by_slug,
get_user_stars,
get_user_rank,
build_pagination,
)
from devplacepy.utils import generate_uid
def _now_db_helpers():
return datetime.now(timezone.utc).isoformat()
def _user_db_helpers():
uid = generate_uid()
get_table("users").insert(
{
"uid": uid,
"username": f"dbh_{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]}@t.dev",
"created_at": _now_db_helpers(),
}
)
return uid
def _post(owner, **extra):
uid = generate_uid()
get_table("posts").insert(
{
"uid": uid,
"user_uid": owner,
"slug": f"{uid[:8]}-post",
"title": None,
"content": "db helper post body",
"topic": "random",
"project_uid": None,
"image": None,
"stars": 0,
"created_at": _now_db_helpers(),
"deleted_at": None,
"deleted_by": None,
**extra,
}
)
return uid
def _comment(owner, target_uid):
uid = generate_uid()
get_table("comments").insert(
{
"uid": uid,
"user_uid": owner,
"target_uid": target_uid,
"target_type": "post",
"content": "db helper comment",
"created_at": _now_db_helpers(),
"deleted_at": None,
"deleted_by": None,
}
)
return uid
def _vote(target_type, target_uid, value):
get_table("votes").insert(
{
"uid": generate_uid(),
"user_uid": generate_uid(),
"target_uid": target_uid,
"target_type": target_type,
"value": value,
"created_at": _now_db_helpers(),
"deleted_at": None,
"deleted_by": None,
}
)
from uuid import uuid4
from tests.conftest import BASE_URL
from devplacepy.database import get_table
def test_get_users_by_uids_dedupes(local_db):
a, b = generate_uid(), generate_uid()
users = get_table("users")
users.insert({"uid": a, "username": f"u_{a[:6]}", "created_at": _now_db_helpers()})
users.insert({"uid": b, "username": f"u_{b[:6]}", "created_at": _now_db_helpers()})
result = get_users_by_uids([a, b, a])
assert set(result.keys()) == {a, b}
def test_get_vote_counts_groups_up_and_down(local_db):
target = generate_uid()
votes = get_table("votes")
for value in (1, 1, -1):
votes.insert(
{
"uid": generate_uid(),
"user_uid": generate_uid(),
"target_uid": target,
"target_type": "post",
"value": value,
"created_at": _now_db_helpers(),
"deleted_at": None,
"deleted_by": None,
}
)
ups, downs = get_vote_counts([target])
assert ups[target] == 2
assert downs[target] == 1
def test_get_user_votes_returns_user_value(local_db):
target = generate_uid()
user = generate_uid()
get_table("votes").insert(
{
"uid": generate_uid(),
"user_uid": user,
"target_uid": target,
"target_type": "post",
"value": 1,
"created_at": _now_db_helpers(),
"deleted_at": None,
"deleted_by": None,
}
)
assert get_user_votes(user, [target]) == {target: 1}
def test_get_comment_counts_by_post_uids(local_db):
post = generate_uid()
comments = get_table("comments")
for _ in range(2):
comments.insert(
{
"uid": generate_uid(),
"user_uid": generate_uid(),
"target_type": "post",
"target_uid": post,
"content": "hi",
"created_at": _now_db_helpers(),
"deleted_at": None,
"deleted_by": None,
}
)
assert get_comment_counts_by_post_uids([post]) == {post: 2}
def test_resolve_by_slug_finds_by_slug_or_uid(local_db):
owner = _user_db_helpers()
uid = _post(owner)
posts = get_table("posts")
slug = posts.find_one(uid=uid)["slug"]
assert resolve_by_slug(posts, slug)["uid"] == uid
assert resolve_by_slug(posts, uid)["uid"] == uid
assert resolve_by_slug(posts, "missing-slug-xyz") is None
def test_get_user_stars_counts_all_votable_contributions(local_db):
user = _user_db_helpers()
post_uid = _post(user)
for _ in range(7):
_vote("post", post_uid, 1)
comment_uid = _comment(user, post_uid)
_vote("comment", comment_uid, 1)
_vote("comment", comment_uid, 1)
_vote("comment", comment_uid, -1)
assert get_user_stars(user) == 8
def test_get_user_rank_unknown_user_is_none(local_db):
assert get_user_rank(generate_uid()) is None
def test_build_pagination_metadata():
first = build_pagination(1, 100, 25)
assert first["total_pages"] == 4
assert first["has_prev"] is False
assert first["has_next"] is True
middle = build_pagination(2, 100, 25)
assert middle["has_prev"] is True
assert middle["has_next"] is True
clamped = build_pagination(99, 10, 25)
assert clamped["page"] == 1
assert clamped["total_pages"] == 1
def test_news_usage_accumulates_and_computes_averages(local_db):
from devplacepy.database import add_news_usage, get_news_usage
get_table("news_usage").delete()
empty = get_news_usage()
assert empty["calls"] == 0
assert empty["avg_tokens"] == 0.0
totals = {
"calls": 2,
"prompt_tokens": 2000,
"completion_tokens": 400,
"total_tokens": 2400,
"cost_usd": 0.0005,
"upstream_latency_ms": 1600.0,
"total_latency_ms": 1800.0,
}
add_news_usage(totals)
add_news_usage(totals)
usage = get_news_usage()
assert usage["calls"] == 4
assert usage["total_tokens"] == 4800
assert abs(usage["cost_usd"] - 0.001) < 1e-9
assert usage["avg_tokens"] == 1200.0
assert usage["avg_cost_usd"] == 0.00025
assert usage["avg_tokens_per_second"] == 250.0
def test_add_news_usage_ignores_zero_call_batches(local_db):
from devplacepy.database import add_news_usage, get_news_usage
get_table("news_usage").delete()
add_news_usage({"calls": 0, "total_tokens": 999, "cost_usd": 1.0})
assert get_news_usage()["calls"] == 0
def test_issue_usage_accumulates_and_computes_averages(local_db):
from devplacepy.database import add_issue_usage, get_issue_usage
get_table("issue_usage").delete()
empty = get_issue_usage()
assert empty["calls"] == 0
assert empty["avg_tokens"] == 0.0
totals = {
"calls": 2,
"prompt_tokens": 2000,
"completion_tokens": 400,
"total_tokens": 2400,
"cost_usd": 0.0005,
"upstream_latency_ms": 1600.0,
"total_latency_ms": 1800.0,
}
add_issue_usage(totals)
add_issue_usage(totals)
usage = get_issue_usage()
assert usage["calls"] == 4
assert usage["total_tokens"] == 4800
assert abs(usage["cost_usd"] - 0.001) < 1e-9
assert usage["avg_tokens"] == 1200.0
assert usage["avg_cost_usd"] == 0.00025
assert usage["avg_tokens_per_second"] == 250.0
def test_add_issue_usage_ignores_zero_call_batches(local_db):
from devplacepy.database import add_issue_usage, get_issue_usage
get_table("issue_usage").delete()
add_issue_usage({"calls": 0, "total_tokens": 999, "cost_usd": 1.0})
assert get_issue_usage()["calls"] == 0
def test_seo_usage_accumulates_and_computes_averages(local_db):
from devplacepy.database import add_seo_usage, get_seo_usage
get_table("seo_usage").delete()
empty = get_seo_usage()
assert empty["calls"] == 0
assert empty["avg_tokens"] == 0.0
assert empty["avg_cost_usd"] == 0.0
totals = {
"calls": 2,
"prompt_tokens": 2000,
"completion_tokens": 400,
"total_tokens": 2400,
"cost_usd": 0.0005,
"upstream_latency_ms": 1600.0,
"total_latency_ms": 1800.0,
}
add_seo_usage(totals)
add_seo_usage(totals)
usage = get_seo_usage()
assert usage["calls"] == 4
assert usage["total_tokens"] == 4800
assert abs(usage["cost_usd"] - 0.001) < 1e-9
assert usage["avg_tokens"] == 1200.0
assert usage["avg_cost_usd"] == 0.00025
assert usage["avg_tokens_per_second"] == 250.0
def test_add_seo_usage_ignores_zero_call_batches(local_db):
from devplacepy.database import add_seo_usage, get_seo_usage
get_table("seo_usage").delete()
add_seo_usage({"calls": 0, "total_tokens": 999, "cost_usd": 1.0})
assert get_seo_usage()["calls"] == 0
def test_upsert_seo_metadata_inserts_then_updates_single_row(local_db):
from devplacepy.database import (
get_seo_metadata,
has_fresh_seo_metadata,
upsert_seo_metadata,
)
target_uid = generate_uid()
table = get_table("seo_metadata")
upsert_seo_metadata(
"post", target_uid, "Plain Title", "Plain description", "alpha, beta", "pending", "plain"
)
assert has_fresh_seo_metadata("post", target_uid) is False
assert get_seo_metadata("post", target_uid) is None
upsert_seo_metadata(
"post", target_uid, "AI Title", "AI description", "python, sqlite", "ready", "ai"
)
ready = get_seo_metadata("post", target_uid)
assert ready is not None
assert ready["seo_title"] == "AI Title"
assert ready["seo_keywords"] == "python, sqlite"
assert ready["generated_at"]
assert ready["deleted_at"] is None
assert has_fresh_seo_metadata("post", target_uid) is True
rows = list(table.find(target_type="post", target_uid=target_uid))
assert len(rows) == 1
def test_seo_metadata_batch_returns_only_ready_live_rows(local_db):
from devplacepy.database import (
get_seo_metadata_batch,
upsert_seo_metadata,
)
ready_uid = generate_uid()
pending_uid = generate_uid()
upsert_seo_metadata("gist", ready_uid, "T", "D", "k1, k2", "ready", "ai")
upsert_seo_metadata("gist", pending_uid, "T", "D", "k1, k2", "pending", "plain")
batch = get_seo_metadata_batch("gist", [ready_uid, pending_uid])
assert ready_uid in batch
assert pending_uid not in batch
assert batch[ready_uid]["status"] == "ready"
def test_mark_seo_metadata_stale_drops_ready_status(local_db):
from devplacepy.database import (
get_seo_metadata,
mark_seo_metadata_stale,
upsert_seo_metadata,
)
target_uid = generate_uid()
upsert_seo_metadata("project", target_uid, "T", "D", "k", "ready", "ai")
assert get_seo_metadata("project", target_uid) is not None
mark_seo_metadata_stale("project", target_uid)
assert get_seo_metadata("project", target_uid) is None
def test_seo_metadata_is_soft_deletable_and_indexed(local_db):
from devplacepy.database import SOFT_DELETE_TABLES
assert "seo_metadata" in SOFT_DELETE_TABLES
columns = get_table("seo_metadata").columns
assert "deleted_at" in columns
assert "deleted_by" in columns
def test_interleave_by_author_spreads_consecutive_runs():
from devplacepy.database import interleave_by_author
rows = [
{"uid": 1, "user_uid": "a"},
{"uid": 2, "user_uid": "a"},
{"uid": 3, "user_uid": "a"},
{"uid": 4, "user_uid": "b"},
{"uid": 5, "user_uid": "b"},
{"uid": 6, "user_uid": "c"},
]
spread = interleave_by_author(rows)
authors = [r["user_uid"] for r in spread]
assert len(spread) == len(rows)
assert all(authors[i] != authors[i + 1] for i in range(len(authors) - 1))
def test_interleave_by_author_preserves_per_author_order():
from devplacepy.database import interleave_by_author
rows = [
{"uid": 1, "user_uid": "a"},
{"uid": 2, "user_uid": "a"},
{"uid": 3, "user_uid": "a"},
{"uid": 4, "user_uid": "b"},
]
spread = interleave_by_author(rows)
a_order = [r["uid"] for r in spread if r["user_uid"] == "a"]
assert a_order == [1, 2, 3]
def test_interleave_by_author_single_author_keeps_order():
from devplacepy.database import interleave_by_author
rows = [{"uid": i, "user_uid": "a"} for i in range(4)]
spread = interleave_by_author(rows)
assert [r["uid"] for r in spread] == [0, 1, 2, 3]
def _relation(actor_uid, target_uid, kind):
get_table("user_relations").insert(
{
"uid": generate_uid(),
"user_uid": actor_uid,
"target_uid": target_uid,
"kind": kind,
"created_at": _now_db_helpers(),
"deleted_at": None,
"deleted_by": None,
}
)
def test_get_user_relations_partitions_both_kinds(local_db):
from devplacepy.database import get_user_relations, invalidate_user_relations
actor = _user_db_helpers()
blocked = _user_db_helpers()
muted = _user_db_helpers()
_relation(actor, blocked, "block")
_relation(actor, muted, "mute")
invalidate_user_relations(actor)
relations = get_user_relations(actor)
assert relations["block"] == frozenset({blocked})
assert relations["mute"] == frozenset({muted})
def test_get_user_relations_anonymous_is_empty(local_db):
from devplacepy.database import get_user_relations
relations = get_user_relations(None)
assert relations["block"] == frozenset()
assert relations["mute"] == frozenset()
def test_get_silenced_uids_is_union(local_db):
from devplacepy.database import get_silenced_uids, invalidate_user_relations
actor = _user_db_helpers()
blocked = _user_db_helpers()
muted = _user_db_helpers()
_relation(actor, blocked, "block")
_relation(actor, muted, "mute")
invalidate_user_relations(actor)
assert get_silenced_uids(actor) == frozenset({blocked, muted})
def test_soft_deleted_relation_is_ignored(local_db):
from devplacepy.database import get_blocked_uids, invalidate_user_relations
actor = _user_db_helpers()
target = _user_db_helpers()
_relation(actor, target, "block")
invalidate_user_relations(actor)
assert get_blocked_uids(actor) == frozenset({target})
row = get_table("user_relations").find_one(user_uid=actor, target_uid=target)
get_table("user_relations").update(
{"id": row["id"], "deleted_at": _now_db_helpers()}, ["id"]
)
invalidate_user_relations(actor)
assert get_blocked_uids(actor) == frozenset()
def test_paginate_excludes_blocked_authors(local_db):
from devplacepy.database import paginate, invalidate_user_relations
viewer = _user_db_helpers()
blocked = _user_db_helpers()
visible = _user_db_helpers()
blocked_post = _post(blocked)
visible_post = _post(visible)
_relation(viewer, blocked, "block")
invalidate_user_relations(viewer)
rows, _ = paginate(get_table("posts"), viewer_uid=viewer)
uids = {r["uid"] for r in rows}
assert visible_post in uids
assert blocked_post not in uids
def test_paginate_without_viewer_keeps_all(local_db):
from devplacepy.database import paginate, invalidate_user_relations
viewer = _user_db_helpers()
blocked = _user_db_helpers()
blocked_post = _post(blocked)
_relation(viewer, blocked, "block")
invalidate_user_relations(viewer)
rows, _ = paginate(get_table("posts"), viewer_uid=None)
assert blocked_post in {r["uid"] for r in rows}
def test_load_comments_drops_blocked_author(local_db):
from devplacepy.database import load_comments, invalidate_user_relations
viewer = _user_db_helpers()
host = _user_db_helpers()
blocked = _user_db_helpers()
post_uid = _post(host)
_comment(blocked, post_uid)
_relation(viewer, blocked, "block")
invalidate_user_relations(viewer)
viewer_row = get_table("users").find_one(uid=viewer)
comments = load_comments("post", post_uid, viewer_row)
assert comments == []
others = load_comments("post", post_uid, None)
assert len(others) == 1
def test_get_uids_by_username_match_returns_matching_uids(local_db):
from devplacepy.database import get_uids_by_username_match
target = generate_uid()
get_table("users").insert(
{
"uid": target,
"username": "searchablehero",
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",
"created_at": _now_db_helpers(),
}
)
assert target in get_uids_by_username_match("searchablehero")
assert target in get_uids_by_username_match("hablehe")
assert get_uids_by_username_match("") == []
def test_text_search_clause_matches_author_username(local_db):
from devplacepy.database import text_search_clause
author = generate_uid()
get_table("users").insert(
{
"uid": author,
"username": "authorquery",
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",
"created_at": _now_db_helpers(),
}
)
post_uid = _post(author, title="unrelated title", content="unrelated body")
posts = get_table("posts")
clause = text_search_clause(
posts, "authorquery", ("title", "content"), author_field="user_uid"
)
assert clause is not None
rows = list(posts.find(clause, deleted_at=None))
assert post_uid in {r["uid"] for r in rows}
def test_text_search_clause_without_author_field_ignores_username(local_db):
from devplacepy.database import text_search_clause
author = generate_uid()
get_table("users").insert(
{
"uid": author,
"username": "lonelyauthor",
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",
"created_at": _now_db_helpers(),
}
)
_post(author, title="nothing here", content="nothing here either")
posts = get_table("posts")
clause = text_search_clause(posts, "lonelyauthor", ("title", "content"))
rows = list(posts.find(clause, deleted_at=None)) if clause is not None else []
assert all(r["user_uid"] != author for r in rows)
def test_text_search_clause_still_matches_text_fields(local_db):
from devplacepy.database import text_search_clause
author = _user_db_helpers()
post_uid = _post(author, title="distinctivekeyword", content="body text")
posts = get_table("posts")
clause = text_search_clause(
posts, "distinctivekeyword", ("title", "content"), author_field="user_uid"
)
assert clause is not None
rows = list(posts.find(clause, deleted_at=None))
assert post_uid in {r["uid"] for r in rows}
def _notification(user_uid, target_url, read=False):
uid = generate_uid()
get_table("notifications").insert(
{
"uid": uid,
"user_uid": user_uid,
"type": "comment",
"message": "someone replied",
"related_uid": generate_uid(),
"target_url": target_url,
"read": read,
"created_at": _now_db_helpers(),
}
)
return uid
def _is_read(uid):
return bool(get_table("notifications").find_one(uid=uid)["read"])
def test_mark_notifications_marks_exact_url(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
uid = _notification(user, "/posts/abc-post")
assert mark_notifications_read_by_target(user, "/posts/abc-post") == 1
assert _is_read(uid) is True
def test_mark_notifications_matches_anchor_prefix(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
uid = _notification(user, "/posts/abc-post#comment-5")
assert mark_notifications_read_by_target(user, "/posts/abc-post") == 1
assert _is_read(uid) is True
def test_mark_notifications_leaves_other_targets(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
keep = _notification(user, "/posts/other-post")
assert mark_notifications_read_by_target(user, "/posts/abc-post") == 0
assert _is_read(keep) is False
def test_mark_notifications_only_unread_counted(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
_notification(user, "/posts/abc-post", read=True)
assert mark_notifications_read_by_target(user, "/posts/abc-post") == 0
def test_mark_notifications_empty_args(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
assert mark_notifications_read_by_target("", "/posts/abc") == 0
assert mark_notifications_read_by_target(user, "") == 0
def test_mark_notifications_scoped_per_user(local_db):
from devplacepy.database import mark_notifications_read_by_target
owner = _user_db_helpers()
other = _user_db_helpers()
other_uid = _notification(other, "/posts/abc-post")
assert mark_notifications_read_by_target(owner, "/posts/abc-post") == 0
assert _is_read(other_uid) is False