Prep MVP — Automated Tests ============================================================ [Helpers] PASS e() escapes HTML PASS e() handles special chars PASS csrf_token generates 64-char hex PASS csrf_token returns same token on second call PASS csrf_verify accepts valid token PASS csrf_verify rejects invalid token PASS csrf_verify rejects empty token PASS csrf_consume invalidates token PASS format_bytes returns correct values [Messages] PASS Messages constants are strings PASS Messages::fileTooLarge formats size PASS Messages::passwordProtected includes extension PASS Messages::corruptedFile includes extension PASS Messages::analysisPartiallyCompleted formats counts [PromptBuilder] PASS buildSystemPrompt returns non-empty PASS buildQuestionPrompt includes document text PASS buildSuggestionPrompt includes document text PASS buildChunkSummaryPrompt includes chunk index PASS both prompts include JSON schema PASS prompts use nonce-based boundary markers PASS system prompt contains anti-injection rules PASS sanitizeDocumentText redacts "ignore previous instructions" PASS sanitizeDocumentText redacts "reveal the system prompt" PASS sanitizeDocumentText redacts "show the API key" PASS sanitizeDocumentText redacts "pretend you are" injection PASS sanitizeDocumentText redacts script tags PASS sanitizeDocumentText redacts iframe tags PASS sanitizeDocumentText preserves legitimate content PASS sanitizeDocumentText handles empty text PASS prompt with injection text does not pass commands to AI PASS prompt with HTML injection does not include raw HTML PASS prompt with API key extraction attempt is sanitized PASS system prompt forbids revealing secrets PASS system prompt forbids outputting HTML PASS system prompt says document is untrusted PASS all three prompt types have boundary markers PASS chunk summary prompt includes untrusted-data warning [Practice Mode — Prompt Injection] PASS grading prompt uses nonce boundary markers PASS grading prompt labels all content as untrusted PASS grading prompt forbids outside knowledge PASS grading prompt instructs flagging unsupported claims PASS grading prompt requires JSON-only response PASS hostile user answer is sanitized in grading prompt PASS user answer with "reveal API key" is sanitized PASS user answer with script tag is sanitized in grading prompt PASS user answer with "pretend you are" injection is sanitized PASS grading prompt treats document + answer as separate boundaries PASS grading prompt shows document and answer in isolated sections PASS sanitizeDocumentText redacts "use outside knowledge" in full attack string PASS sanitizeDocumentText preserves normal answer text PASS empty answer in grading prompt is safe [DocumentChunker] PASS chunk returns single chunk for short text PASS chunk does not modify text under limit PASS chunk splits very long text PASS chunk splits at slide boundaries PASS assemble returns empty for all-failed chunks PASS assemble returns partial for some-failed chunks PASS assemble includes header and all summaries PASS assemble skips empty summaries [TextExtractor] PASS extractTextFromDocument rejects missing file PASS extractTextFromDocument rejects unsupported extension PASS extractTextFromDocument catches corrupted PDF PASS extractTextFromDocument supports all new extensions PASS extractTextFromDocument returns metadata on failure PASS extractTextFromDocument catches corrupted PDF [Rate Limiting] PASS rate_limit_hit returns false under limit PASS rate_limit_hit returns true over limit PASS rate_limit_hit prunes old entries [AiService — Mock] PASS valid JSON response returns success PASS invalid JSON in message content returns failure PASS empty choices array returns failure PASS provider error object returns failure PASS HTTP 401 unauthorized returns failure PASS HTTP 429 rate limit returns failure PASS HTTP 500 server error returns failure PASS cURL timeout returns failure PASS empty response body returns failure PASS API key never appears in error messages PASS successful response has null error PASS content_filter finish reason returns failure PASS non-JSON API response body returns failure [Add More Questions] PASS normalize_question_text lowercases and collapses spaces PASS normalize_question_text handles unicode PASS hash_question_text produces consistent output PASS hash_question_text different texts produce different hashes PASS buildAdditionalQuestionsPrompt includes focus PASS buildAdditionalQuestionsPrompt includes existing questions PASS buildAdditionalQuestionsPrompt uses boundary markers PASS buildAdditionalQuestionsPrompt sanitizes injected questions PASS buildAdditionalQuestionsPrompt includes context note PASS buildAdditionalQuestionsPrompt handles empty existing list PASS buildAdditionalQuestionsPrompt includes duplicates_avoided in JSON schema PASS ALLOWED_FOCUSES contains all expected values PASS ALLOWED_COUNTS contains 3, 5, 10 PASS addqGenerated formats success message PASS addqGenerated formats partial message ============================================================ Results: 96 passed, 0 failed, 96 total All tests passed.