237 lines
7.9 KiB
Python
Raw Normal View History

2026-07-26 14:57:18 +02:00
# retoor <retoor@molodetz.nl>
from datetime import datetime, timezone
import pytest
from pydantic import ValidationError
from devplacepy.config import QUIZ_MAX_OPTIONS, QUIZ_MAX_QUESTIONS
from devplacepy.database import get_table
from devplacepy.models import QuizDocument, QuizImportForm
from devplacepy.services.quiz import store
from devplacepy.services.quiz.store import QuizError
from devplacepy.utils import generate_uid, make_combined_slug
_counter = [0]
DOCUMENT = {
"title": "SQLite fundamentals",
"description": "Questions on WAL and indexing.",
"settings": {"shuffle_questions": True, "reveal_answers": True, "pass_percent": 70},
"questions": [
{
"kind": "single_choice",
"prompt": "Which journal mode allows concurrent readers?",
"points": 2,
"explanation": "WAL keeps readers off the writer's lock.",
"options": [{"label": "DELETE"}, {"label": "WAL", "is_correct": True}],
},
{
"kind": "free_text",
"prompt": "Why does a partial index help?",
"points": 3,
"expected_answer": "It keeps the live query off a one bucket index.",
"grading_criteria": "Accept any answer mentioning the planner.",
},
],
}
def _user(prefix):
_counter[0] += 1
uid = generate_uid()
get_table("users").insert(
{
"uid": uid,
"username": f"{prefix}{_counter[0]}",
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",
2026-07-26 14:57:18 +02:00
"email": f"{prefix}{_counter[0]}@t.dev",
"role": "Member",
"api_key": generate_uid(),
"created_at": datetime.now(timezone.utc).isoformat(),
}
)
return get_table("users").find_one(uid=uid)
def _quiz(owner, document):
uid = generate_uid()
get_table("quizzes").insert(
store.born_live(
{
"uid": uid,
"user_uid": owner["uid"],
"slug": make_combined_slug(document.title, uid),
"title": document.title,
"description": document.description,
"status": "draft",
"published_at": "",
"question_count": 0,
"total_points": 0,
"attempt_count": 0,
"stars": 0,
**store.document_settings(document),
}
)
)
store.import_questions(uid, document)
return store.get_quiz(uid)
def test_parse_document_accepts_the_reference_shape():
document = store.parse_document(DOCUMENT)
assert document.title == "SQLite fundamentals"
assert len(document.questions) == 2
def test_parse_document_rejects_junk():
with pytest.raises(QuizError):
store.parse_document({"title": "x"})
def test_the_import_form_parses_a_json_string():
import json
form = QuizImportForm(document=json.dumps(DOCUMENT))
assert form.document.title == "SQLite fundamentals"
def test_the_import_form_rejects_invalid_json():
with pytest.raises(ValidationError):
QuizImportForm(document="{not json")
def test_a_document_needs_at_least_one_question():
with pytest.raises(ValidationError):
QuizDocument(title="Empty quiz", questions=[])
def test_a_document_is_capped_at_the_question_limit():
question = {"kind": "numeric", "prompt": "q", "numeric_value": 1}
with pytest.raises(ValidationError):
QuizDocument(title="Too big", questions=[question] * (QUIZ_MAX_QUESTIONS + 1))
def test_a_question_is_capped_at_the_option_limit():
options = [{"label": f"L{index}"} for index in range(QUIZ_MAX_OPTIONS + 1)]
options[0]["is_correct"] = True
with pytest.raises(ValidationError):
QuizDocument(
title="Too many options",
questions=[{"kind": "single_choice", "prompt": "q", "options": options}],
)
def test_a_single_choice_needs_exactly_one_correct_option():
with pytest.raises(ValidationError):
QuizDocument(
title="Bad choice",
questions=[
{
"kind": "single_choice",
"prompt": "q",
"options": [{"label": "a"}, {"label": "b"}],
}
],
)
def test_a_free_text_needs_a_reference_or_criteria():
with pytest.raises(ValidationError):
QuizDocument(
title="Bad free text",
questions=[{"kind": "free_text", "prompt": "explain"}],
)
def test_a_matching_needs_a_right_hand_value_on_every_pair():
with pytest.raises(ValidationError):
QuizDocument(
title="Bad matching",
questions=[
{
"kind": "matching",
"prompt": "q",
"options": [{"label": "a", "match_value": "A"}, {"label": "b"}],
}
],
)
def test_document_settings_map_onto_the_quiz_columns():
settings = store.document_settings(store.parse_document(DOCUMENT))
assert settings["shuffle_questions"] == 1
assert settings["reveal_answers"] == 1
assert settings["pass_percent"] == 70
def test_importing_creates_every_question_in_order(local_db):
owner = _user("qd")
quiz = _quiz(owner, store.parse_document(DOCUMENT))
questions = store.list_questions(quiz["uid"])
assert [q["kind"] for q in questions] == ["single_choice", "free_text"]
assert [q["position"] for q in questions] == [0, 1]
def test_importing_recomputes_the_totals(local_db):
owner = _user("qd")
quiz = _quiz(owner, store.parse_document(DOCUMENT))
assert quiz["question_count"] == 2
assert quiz["total_points"] == 5
def test_importing_stores_the_kind_specific_fields(local_db):
owner = _user("qd")
quiz = _quiz(owner, store.parse_document(DOCUMENT))
free_text = store.list_questions(quiz["uid"])[1]
assert free_text["expected_answer"].startswith("It keeps")
assert free_text["grading_criteria"].startswith("Accept")
def test_the_owner_export_round_trips_through_import(local_db):
owner = _user("qd")
quiz = _quiz(owner, store.parse_document(DOCUMENT))
exported = store.export_document(quiz["uid"], True)
reimported = _quiz(owner, store.parse_document(exported))
assert reimported["question_count"] == quiz["question_count"]
assert reimported["total_points"] == quiz["total_points"]
assert store.export_document(reimported["uid"], True) == exported
def test_the_owner_export_carries_the_answer_key(local_db):
owner = _user("qd")
quiz = _quiz(owner, store.parse_document(DOCUMENT))
exported = store.export_document(quiz["uid"], True)
assert any(option.get("is_correct") for option in exported["questions"][0]["options"])
assert exported["questions"][1]["expected_answer"]
def test_the_public_export_omits_the_answer_key(local_db):
owner = _user("qd")
quiz = _quiz(owner, store.parse_document(DOCUMENT))
exported = store.export_document(quiz["uid"], False)
assert all("is_correct" not in option for option in exported["questions"][0]["options"])
assert "expected_answer" not in exported["questions"][1]
assert "correct_boolean" not in exported["questions"][0]
def test_the_public_export_keeps_the_prompts_and_labels(local_db):
owner = _user("qd")
quiz = _quiz(owner, store.parse_document(DOCUMENT))
exported = store.export_document(quiz["uid"], False)
assert exported["questions"][0]["prompt"].startswith("Which journal mode")
assert [option["label"] for option in exported["questions"][0]["options"]] == ["DELETE", "WAL"]
def test_the_export_carries_the_settings(local_db):
owner = _user("qd")
quiz = _quiz(owner, store.parse_document(DOCUMENT))
exported = store.export_document(quiz["uid"], False)
assert exported["settings"]["pass_percent"] == 70
assert exported["settings"]["reveal_answers"] is True
def test_exporting_an_unknown_quiz_raises(local_db):
with pytest.raises(QuizError):
store.export_document("does-not-exist", True)