197 lines
7.6 KiB
Python
Raw Normal View History

2026-07-26 14:57:18 +02:00
# retoor <retoor@molodetz.nl>
import requests
from tests.conftest import BASE_URL
JSON = {"Accept": "application/json"}
LAYOUT = ".feed-layout"
LEFT = ".feed-layout > .sidebar-card"
CENTRE = ".feed-layout > .feed-main"
RIGHT = ".feed-layout > .feed-right"
NAV_ENTRY = ".topnav-link[href='/quizzes']"
NEW_QUIZ_BUTTON = ".quiz-actions a[href='/quizzes/new']"
DEVII_BUTTON = ".quiz-actions button[data-devii-open]"
SCOREBOARD = ".quiz-scoreboard-card"
def _seed_quiz(title="Hub e2e quiz"):
session = requests.Session()
name = f"hub{abs(hash(title)) % 10**9}"
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",
2026-07-26 14:57:18 +02:00
},
allow_redirects=True,
)
slug = session.post(
f"{BASE_URL}/quizzes/create", data={"title": title, "description": "d"}, headers=JSON
).json()["data"]["slug"]
session.post(
f"{BASE_URL}/quizzes/{slug}/questions",
data={
"kind": "single_choice",
"prompt": "Which journal mode allows concurrent readers?",
"points": 2,
"options": "DELETE\nWAL",
"correct_indexes": "1",
},
headers=JSON,
)
session.post(f"{BASE_URL}/quizzes/{slug}/publish", headers=JSON)
return slug
def test_the_hub_renders_the_three_column_layout(page, app_server):
_seed_quiz("Layout e2e quiz")
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
page.locator(LAYOUT).wait_for(state="visible")
assert page.locator(LEFT).count() == 1
assert page.locator(CENTRE).count() == 1
assert page.locator(RIGHT).count() == 1
def test_the_hub_has_a_visible_heading(page, app_server):
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
heading = page.locator("h1.quiz-page-title")
heading.wait_for(state="visible")
assert heading.inner_text().strip() == "Quizzes"
def test_the_nav_entry_is_visible_to_a_guest_and_active(page, app_server):
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
entry = page.locator(NAV_ENTRY)
entry.wait_for(state="visible")
assert "active" in (entry.get_attribute("class") or "")
def test_a_guest_sees_disabled_create_controls(page, app_server):
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
page.locator(".quiz-actions").wait_for(state="visible")
assert page.locator(NEW_QUIZ_BUTTON).get_attribute("aria-disabled") == "true"
assert page.locator(DEVII_BUTTON).is_disabled()
def test_a_guest_still_sees_the_list_and_the_scoreboard(page, app_server):
_seed_quiz("Guest visible quiz")
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
page.locator(SCOREBOARD).wait_for(state="visible")
assert page.locator(".quiz-card").count() >= 1
def test_a_member_sees_enabled_create_controls(alice):
page, _ = alice
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
page.locator(NEW_QUIZ_BUTTON).wait_for(state="visible")
assert page.locator(NEW_QUIZ_BUTTON).get_attribute("aria-disabled") is None
assert page.locator(DEVII_BUTTON).is_enabled()
def test_a_member_sees_the_personal_filters(alice):
page, _ = alice
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
page.locator(".sidebar-nav").wait_for(state="visible")
for target in ("todo", "done", "mine", "drafts"):
assert page.locator(f".sidebar-link[href='/quizzes?filter={target}']").count() == 1
def test_a_guest_sees_only_the_all_filter(page, app_server):
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
page.locator(".sidebar-nav").wait_for(state="visible")
assert page.locator(".sidebar-link[href='/quizzes?filter=todo']").count() == 0
assert page.locator(".sidebar-link[href='/quizzes?filter=all']").count() == 1
def test_a_quiz_card_shows_its_state_badge(alice):
page, _ = alice
slug = _seed_quiz("Badge e2e quiz")
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
card = page.locator(f".quiz-card:has(a[href='/quizzes/{slug}'])").first
card.wait_for(state="visible")
assert card.locator(".quiz-state-todo").count() == 1
def test_a_quiz_card_shows_its_meta_chips(page, app_server):
slug = _seed_quiz("Meta chips e2e quiz")
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
card = page.locator(f".quiz-card:has(a[href='/quizzes/{slug}'])").first
card.wait_for(state="visible")
assert "1 questions" in card.inner_text()
assert "2 points" in card.inner_text()
def test_the_search_box_filters_the_list(page, app_server):
slug = _seed_quiz("Unmistakable pelican quiz")
page.goto(f"{BASE_URL}/quizzes?search=pelican", wait_until="domcontentloaded")
page.locator(".feed-posts").wait_for(state="visible")
assert page.locator(f".quiz-card:has(a[href='/quizzes/{slug}'])").count() == 1
def test_the_new_quiz_button_opens_the_create_form(alice):
page, _ = alice
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
page.locator(NEW_QUIZ_BUTTON).click()
page.wait_for_url("**/quizzes/new", wait_until="domcontentloaded")
page.locator("form.quiz-form").wait_for(state="visible")
def test_the_devii_button_seeds_the_terminal_without_sending(alice):
page, _ = alice
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
trigger = page.locator(DEVII_BUTTON)
trigger.wait_for(state="visible")
prompt = trigger.get_attribute("data-devii-prompt")
assert prompt and "import_quiz" in prompt
trigger.click()
terminal_input = page.locator("devii-terminal .devii-input")
terminal_input.wait_for(state="visible", timeout=10000)
assert terminal_input.input_value() == prompt
assert page.locator("devii-terminal .devii-line-user").count() == 0
page.evaluate("window.app && window.app.devii && window.app.devii.close()")
page.evaluate("window.localStorage.clear()")
def test_opening_a_card_reaches_the_detail_page(page, app_server):
slug = _seed_quiz("Card link e2e quiz")
page.goto(f"{BASE_URL}/quizzes", wait_until="domcontentloaded")
page.locator(f".quiz-card a.card-link[href='/quizzes/{slug}']").first.click()
page.wait_for_url(f"**/quizzes/{slug}", wait_until="domcontentloaded")
page.locator("h1.quiz-detail-title").wait_for(state="visible")
def test_the_detail_page_shows_the_stats_strip(page, app_server):
slug = _seed_quiz("Stats strip e2e quiz")
page.goto(f"{BASE_URL}/quizzes/{slug}", wait_until="domcontentloaded")
page.locator(".quiz-stats-strip").wait_for(state="visible")
assert page.locator(".quiz-stat").count() == 5
def test_the_detail_page_offers_a_start_button_to_a_member(alice):
page, _ = alice
slug = _seed_quiz("Start button e2e quiz")
page.goto(f"{BASE_URL}/quizzes/{slug}", wait_until="domcontentloaded")
button = page.locator(".quiz-detail-actions button:has-text('Start quiz')")
button.wait_for(state="visible")
assert button.is_enabled()
def test_the_detail_page_disables_start_for_a_guest(page, app_server):
slug = _seed_quiz("Guest start e2e quiz")
page.goto(f"{BASE_URL}/quizzes/{slug}", wait_until="domcontentloaded")
button = page.locator(".quiz-detail-actions button:has-text('Start quiz')")
button.wait_for(state="visible")
assert button.is_disabled()
def test_the_detail_page_carries_a_comment_section(page, app_server):
slug = _seed_quiz("Comment section e2e quiz")
page.goto(f"{BASE_URL}/quizzes/{slug}", wait_until="domcontentloaded")
page.locator(".comments-section").wait_for(state="visible")