from datetime import timedelta from io import BytesIO from tempfile import TemporaryDirectory from unittest.mock import patch from django.contrib.auth.models import User from django.db import IntegrityError, transaction from django.core.exceptions import ValidationError from django.core.management import call_command from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase, override_settings from django.urls import reverse from django.utils import timezone from django.template import Context, Template from accounts.models import PremiumEntitlement from .models import ExamSet, Question, QuestionGroup, Section, StudentAnswer, StudentAttempt from .html_importer import create_exam_from_payload, parse_ielts_html from .excel_importer import build_excel_template, parse_excel_test from .views import SUBMISSION_GRACE_SECONDS from .grading import academic_reading_band IMPORT_HTML = b'''

Music history

This is the reading passage.

Choose the correct answer.

1. Which option is correct?
2. Complete this ____ .
''' class HtmlImportTests(TestCase): def setUp(self): self.admin_user = User.objects.create_superuser( username="admin", email="admin@example.com", password="admin-pass-123" ) def test_parser_extracts_real_question_types_and_answers(self): section = parse_ielts_html(IMPORT_HTML, "reading.html") self.assertEqual(section.passage_text, "Music history\n\nThis is the reading passage.") self.assertEqual(len(section.questions), 2) self.assertEqual(section.questions[0].question_type, "mcq") self.assertEqual(section.questions[0].options, ["A. First choice", "B. Second choice"]) self.assertEqual(section.questions[0].correct_answer, "B. Second choice") self.assertEqual(section.questions[1].question_type, "gap") self.assertEqual(section.questions[1].correct_answer, "answer") def test_listening_import_does_not_require_a_reading_passage(self): listening_html = b'''
1. Choose one.
''' section = parse_ielts_html(listening_html, "listening.html", section_type="listening") exam = create_exam_from_payload({"title":"Listening import", "description":"", "category":"listening", "section_type":"listening", "time_limit_minutes":10, "publish":False, "sections":[section.as_payload()]}) self.assertEqual(exam.sections.get().section_type, "listening") self.assertEqual(exam.sections.get().questions.get().correct_answer, "B") def test_payload_creates_a_publishable_exam(self): section = parse_ielts_html(IMPORT_HTML, "reading.html") exam = create_exam_from_payload( { "title": "Imported reading test", "description": "Imported from HTML", "category": "reading", "section_type": "reading", "time_limit_minutes": 20, "publish": True, "sections": [section.as_payload()], } ) self.assertTrue(exam.is_published) self.assertEqual(exam.sections.count(), 1) self.assertEqual(exam.sections.get().questions.count(), 2) def test_admin_home_uses_clean_unfold_dashboard(self): self.client.force_login(self.admin_user) response = self.client.get(reverse("admin:index")) self.assertEqual(response.status_code, 200) self.assertContains(response, "Testpoint") self.assertContains(response, "Tests") self.assertContains(response, "/static/unfold/css/styles.css") def test_category_quick_create_prefills_the_exam_category(self): self.client.force_login(self.admin_user) response = self.client.get(reverse("admin:exams_examset_add") + "?category=listening") self.assertEqual(response.status_code, 200) self.assertContains(response, '