from django.test import TestCase from labs.exams.exceptions import ExamAnswerValidationError from labs.exams.section import get_section_handler from labs.models import ExamAttemptAnswer, ExamAttemptSection, ExamSection, ExamQuestion from labs.tests.exams._helpers import ( create_attempt, create_exam_with_variant, create_question, create_section, create_user, ) class TestSectionHandlers(TestCase): def test_instruction_detail_hides_content_when_locked(self): user = create_user(email="instruction@example.com") exam, variant = create_exam_with_variant() available_section = create_section( variant=variant, section_type=ExamSection.SectionType.INSTRUCTION, display_order=0, title="Available", content={"text": "available"}, ) locked_section = create_section( variant=variant, section_type=ExamSection.SectionType.INSTRUCTION, display_order=1, title="Locked", content={"text": "locked"}, ) attempt = create_attempt(user=user, exam=exam, variant=variant) sections = list( ExamAttemptSection.objects.select_related("section") .filter(attempt=attempt.db_exam_attempt) .order_by("display_order") ) self.assertEqual([s.section_id for s in sections], [available_section.id, locked_section.id]) available_handler = get_section_handler(sections[0]) locked_handler = get_section_handler(sections[1]) self.assertEqual(available_handler.detail()["content"], {"text": "available"}) self.assertIsNone(locked_handler.detail()["content"]) sections[0].status = sections[0].Status.COMPLETED sections[0].save(update_fields=["status"]) completed_handler = get_section_handler(sections[0]) self.assertEqual(completed_handler.detail()["content"], {"text": "available"}) def test_question_answer_validation_and_echo(self): user = create_user(email="question-validate@example.com") exam, variant = create_exam_with_variant() section = create_section( variant=variant, section_type=ExamSection.SectionType.QUESTION, display_order=0, title="Questions", content={}, ) q1 = create_question( variant=variant, section=section, question_type=ExamQuestion.QuestionType.SINGLE_CHOICE, display_order=0, options=[{"id": "A", "text": "A"}, {"id": "B", "text": "B"}], answer=["A"], ) q2 = create_question( variant=variant, section=section, question_type=ExamQuestion.QuestionType.MULTIPLE_CHOICE, display_order=1, options=[{"id": "A", "text": "A"}, {"id": "B", "text": "B"}], answer=["A", "B"], ) attempt = create_attempt(user=user, exam=exam, variant=variant) attempt_section = ExamAttemptSection.objects.select_related("section").get( attempt=attempt.db_exam_attempt, section=section ) handler = get_section_handler(attempt_section) with self.assertRaises(ExamAnswerValidationError): handler.submit_answer(q1.id, "A") with self.assertRaises(ExamAnswerValidationError): handler.submit_answer(q1.id, [True]) with self.assertRaises(ExamAnswerValidationError): handler.submit_answer(q1.id, ["A", "A"]) with self.assertRaises(ExamAnswerValidationError): handler.submit_answer(q1.id, ["Z"]) with self.assertRaises(ExamAnswerValidationError): handler.submit_answer(q1.id, ["A", "B"]) handler.submit_answer(q1.id, ["A"]) detail = handler.get_frontend_question(q1.id) self.assertEqual(detail["user_answer"], ["A"]) self.assertIsNone(handler.get_frontend_question(q2.id)["user_answer"]) qs = handler.get_frontend_questions() self.assertEqual([q["id"] for q in qs], [q1.id, q2.id]) self.assertTrue([q for q in qs if q["id"] == q1.id][0]["is_answered"]) self.assertFalse([q for q in qs if q["id"] == q2.id][0]["is_answered"]) handler.submit_answer(q1.id, []) qs = handler.get_frontend_questions() self.assertFalse([q for q in qs if q["id"] == q1.id][0]["is_answered"]) a = ExamAttemptAnswer.objects.get( attempt=attempt.db_exam_attempt, attempt_section=attempt_section, question=q1, ) self.assertEqual(a.answer, []) self.assertFalse(a.is_answered) def test_calculate_score(self): user = create_user(email="score@example.com") exam, variant = create_exam_with_variant() section = create_section( variant=variant, section_type=ExamSection.SectionType.QUESTION, display_order=0, title="Questions", content={}, ) q1 = create_question( variant=variant, section=section, question_type=ExamQuestion.QuestionType.SINGLE_CHOICE, display_order=0, score=2, options=[{"id": "A", "text": "A"}, {"id": "B", "text": "B"}], answer=["A"], ) q2 = create_question( variant=variant, section=section, question_type=ExamQuestion.QuestionType.MULTIPLE_CHOICE, display_order=1, score=3, options=[{"id": "A", "text": "A"}, {"id": "B", "text": "B"}, {"id": "C", "text": "C"}], answer=["A", "C"], ) attempt = create_attempt(user=user, exam=exam, variant=variant) attempt_section = ExamAttemptSection.objects.select_related("section").get( attempt=attempt.db_exam_attempt, section=section ) handler = get_section_handler(attempt_section) handler.submit_answer(q1.id, ["A"]) handler.submit_answer(q2.id, ["A"]) score, detail = handler.calculate_score() self.assertEqual(score, 2) self.assertEqual([d["question_id"] for d in detail], [q1.id, q2.id]) self.assertTrue(detail[0]["is_correct"]) self.assertEqual(detail[0]["gained_score"], 2) self.assertFalse(detail[1]["is_correct"]) self.assertEqual(detail[1]["gained_score"], 0) def test_calculate_score_multiple_choice_correct_order_independent(self): user = create_user(email="score-mc@example.com") exam, variant = create_exam_with_variant() section = create_section( variant=variant, section_type=ExamSection.SectionType.QUESTION, display_order=0, title="Questions", content={}, ) q = create_question( variant=variant, section=section, question_type=ExamQuestion.QuestionType.MULTIPLE_CHOICE, display_order=0, score=3, options=[ {"id": "A", "text": "A"}, {"id": "B", "text": "B"}, {"id": "C", "text": "C"}, ], answer=["A", "C"], ) attempt = create_attempt(user=user, exam=exam, variant=variant) attempt_section = ExamAttemptSection.objects.select_related("section").get( attempt=attempt.db_exam_attempt, section=section ) handler = get_section_handler(attempt_section) handler.submit_answer(q.id, ["C", "A"]) score, detail = handler.calculate_score() self.assertEqual(score, 3) self.assertEqual(detail[0]["question_id"], q.id) self.assertTrue(detail[0]["is_correct"]) self.assertEqual(detail[0]["gained_score"], 3)