pdf-quiz-generator/backend/tests
Daniel d591813f02 fix: an upload is what its bytes say, not what its name claims
The document matcher decided by extension and fell through to "decode whatever
this is as UTF-8" for everything that was not a .pdf. A shell script, an HTML
page, a CSV or a JPEG all became a search query, and a file called report.pdf
holding something else was read as a PDF.

Three questions now, in order, before anything else touches the file: is it
under 2 MB (read one byte past the cap, so a huge file is never held in memory
to be measured); what is it, sniffed from the leading bytes; and what text is
in it. PDF, DOCX and images, and nothing else — 415 with a sentence naming what
to upload instead. DOCX is parsed from the zip with no new dependency and is
checked against the one attack that shape allows, a member that unpacks to far
more than the file's size suggests. An image is read by the tool model, and
where no tool model is configured it says so rather than 500ing.

On injection, since that is the question people mean: there is no path from an
uploaded file to code that runs. The extracted text is a search query — bound
parameter to Postgres, never concatenated into SQL — and the content of a
message to a model. It is never rendered as HTML, never written to disk, never
passed to a shell. Control characters are stripped because they make queries
that match nothing, not because anything would interpret them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN
2026-09-12 19:05:12 +02:00
..
__init__.py
test_access.py
test_ai_mode.py fix: a greeting is not a query, so nothing is searched for one 2026-09-12 19:03:30 +02:00
test_ai_mode_matching.py fix: an upload is what its bytes say, not what its name claims 2026-09-12 19:05:12 +02:00
test_ai_practice.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_article_ai.py fix: WebP figures, the openai SDK removed, and a voice a site can add to 2026-09-12 17:13:05 +02:00
test_article_notes.py feat: question folders, per-section notes, and two feedback paths 2026-09-12 18:37:43 +02:00
test_article_search_index.py feat: three answers, chosen by a number rather than by the model 2026-09-12 16:15:05 +02:00
test_articles_cards.py feat: link a question to its reading from either end, and a player that reads like a Qbank 2026-09-12 15:17:04 +02:00
test_articles_migration.py
test_bank_cleanup.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_blueprint_plans.py feat: study plans built to the board's content outline 2026-09-11 21:40:17 +02:00
test_captcha.py feat: Cap replaces hCaptcha, self-hosted beside the app 2026-09-12 06:14:14 +02:00
test_category_grants.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_category_migration.py feat: retire the tags, and stop settings from hiding from the page that 2026-09-12 05:06:58 +02:00
test_collections.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_draft_questions.py fix: the other registration form, our own transcriber first, and a rail 2026-09-12 04:41:27 +02:00
test_exam_admin.py feat: exams are a group you can build, with the board's own blueprint 2026-09-11 20:27:48 +02:00
test_exam_blueprint.py feat: adaptive weighs the paper as well as the learner, and Session 2026-09-12 05:54:09 +02:00
test_exam_scoped_tags.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_exams.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_feedback.py feat: question folders, per-section notes, and two feedback paths 2026-09-12 18:37:43 +02:00
test_file_intake.py fix: an upload is what its bytes say, not what its name claims 2026-09-12 19:05:12 +02:00
test_global_search.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_hybrid_search.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_invites_and_flags.py feat: the tutor is an administrator's to allow, and a handbook explains the rest 2026-09-11 21:13:38 +02:00
test_login_codes.py feat: sign in with a code sent by email 2026-09-12 17:29:28 +02:00
test_login_without_captcha.py feat: Cap replaces hCaptcha, self-hosted beside the app 2026-09-12 06:14:14 +02:00
test_media_library.py
test_multi_category.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_option_explanations.py
test_prepared_session.py feat: a session prepared for you, and a model that can see when the one on the job cannot 2026-09-12 15:46:04 +02:00
test_question_folders.py feat: question folders, per-section notes, and two feedback paths 2026-09-12 18:37:43 +02:00
test_question_trash.py feat: questions are soft-deleted, and the trash holds them 2026-09-11 20:12:58 +02:00
test_question_versions.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_quiz_builder.py feat: a session prepared for you, and a model that can see when the one on the job cannot 2026-09-12 15:46:04 +02:00
test_quiz_sessions.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_related_privacy.py fix: WebP figures, the openai SDK removed, and a voice a site can add to 2026-09-12 17:13:05 +02:00
test_rerank.py feat: rerank what a learner is shown, with Cohere through the proxy 2026-09-12 18:23:06 +02:00
test_session_lifecycle.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_share_public.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_shared_category.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_study_plan_editing.py
test_study_plan_sessions.py
test_study_tools.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_study_tools_migration.py
test_tag_hierarchy.py refactor: remove per-question sharing 2026-09-12 08:42:51 +02:00
test_thumbnails.py feat: thumbnails for uploaded images, at two widths and no others 2026-09-12 08:49:20 +02:00
test_vision_fallback.py fix: WebP figures, the openai SDK removed, and a voice a site can add to 2026-09-12 17:13:05 +02:00