from django.test import TestCase, Client from django.urls import reverse from quiz.models import Question, QuizUser, QuizSession, QuizResult, Course, Exam, Tag, Option class QuizViewsTestCase(TestCase): """Comprehensive tests for all quiz endpoints""" def setUp(self): """Set up test data""" self.client = Client() # Ensure session exists and get its key s = self.client.session s.save() session_key = s.session_key # Create test user with the same session key self.user = QuizUser.objects.create(session_key=session_key) # Create test course self.course = Course.objects.create(name="Test Course") # Create test exam self.exam = Exam.objects.create( name="Test Exam", course=self.course, date="2025-01-01" # Required field ) # Create test tags self.tag1 = Tag.objects.create(name="Tag 1", slug="tag-1") self.tag2 = Tag.objects.create(name="Tag 2", slug="tag-2") # Create test questions self.question1 = Question.objects.create( text="Test question 1?", correct_answer="A", exam=self.exam, file_path="test1.md" ) self.question1.tags.add(self.tag1) Option.objects.create(question=self.question1, letter="A", text="Answer A") Option.objects.create(question=self.question1, letter="B", text="Answer B") self.question2 = Question.objects.create( text="Test question 2 (multi)?", correct_answer="A,B", exam=self.exam, file_path="test2.md" ) self.question2.tags.add(self.tag1, self.tag2) Option.objects.create(question=self.question2, letter="A", text="Answer A") Option.objects.create(question=self.question2, letter="B", text="Answer B") Option.objects.create(question=self.question2, letter="C", text="Answer C") # Set user in session session = self.client.session session['quiz_user_id'] = self.user.id session.save() def test_index_view(self): """Test dashboard index view""" response = self.client.get(reverse('quiz:index')) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Snabbstart') self.assertIn('active_sessions', response.context) self.assertIn('form', response.context) def test_create_quiz(self): """Test quiz creation""" response = self.client.post(reverse('quiz:create_quiz'), { 'course': self.course.id, 'tags': [self.tag1.id], }) self.assertEqual(response.status_code, 302) # Redirect # Verify session was created session = QuizSession.objects.filter(user=self.user).first() self.assertIsNotNone(session) self.assertEqual(session.course, self.course) self.assertTrue(session.is_active) def test_quiz_mode_view(self): """Test dedicated quiz mode view""" session = QuizSession.objects.create( user=self.user, course=self.course ) response = self.client.get(reverse('quiz:quiz_mode', args=[session.id])) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Test Course') self.assertIn('session', response.context) def test_quiz_question_view(self): """Test quiz question endpoint""" session = QuizSession.objects.create( user=self.user, course=self.course ) response = self.client.get(reverse('quiz:quiz_question', args=[session.id])) self.assertEqual(response.status_code, 200) self.assertIn('question', response.context) self.assertIn('session', response.context) def test_navigate_question(self): """Test question navigation""" session = QuizSession.objects.create( user=self.user, course=self.course ) # Test next navigation response = self.client.get( reverse('quiz:navigate_question', args=[session.id, 'next']), {'q': self.question1.id} ) self.assertEqual(response.status_code, 200) # Test previous navigation response = self.client.get( reverse('quiz:navigate_question', args=[session.id, 'previous']), {'q': self.question2.id} ) self.assertEqual(response.status_code, 200) def test_submit_single_answer(self): """Test submitting a single-choice answer""" session = QuizSession.objects.create( user=self.user, course=self.course ) response = self.client.post( reverse('quiz:submit_answer', args=[session.id]), { 'question_id': self.question1.id, 'answer': 'A' } ) self.assertEqual(response.status_code, 200) # Verify result was created result = QuizResult.objects.get( user=self.user, question=self.question1, quiz_session=session ) self.assertEqual(result.selected_answer, 'A') self.assertTrue(result.is_correct) def test_submit_multi_answer(self): """Test submitting a multi-choice answer""" session = QuizSession.objects.create( user=self.user, course=self.course ) # Test correct multi-answer (order shouldn't matter) response = self.client.post( reverse('quiz:submit_answer', args=[session.id]), { 'question_id': self.question2.id, 'answer': 'B,A' # Reversed order } ) self.assertEqual(response.status_code, 200) result = QuizResult.objects.get( user=self.user, question=self.question2, quiz_session=session ) self.assertTrue(result.is_correct) # Should be correct despite order def test_submit_difficulty(self): """Test submitting FSRS difficulty rating""" session = QuizSession.objects.create( user=self.user, course=self.course ) # First submit an answer QuizResult.objects.create( user=self.user, question=self.question1, quiz_session=session, selected_answer='A', is_correct=True ) # Then submit difficulty response = self.client.post( reverse('quiz:submit_difficulty', args=[session.id]), { 'question_id': self.question1.id, 'difficulty': 'easy' } ) self.assertEqual(response.status_code, 200) # Verify difficulty was saved result = QuizResult.objects.get( user=self.user, question=self.question1, quiz_session=session ) self.assertEqual(result.difficulty, 'easy') def test_close_quiz(self): """Test closing a quiz session""" session = QuizSession.objects.create( user=self.user, course=self.course, is_active=True ) response = self.client.post(reverse('quiz:close_quiz', args=[session.id])) self.assertEqual(response.status_code, 302) # Redirect to index # Verify session was deactivated session.refresh_from_db() self.assertFalse(session.is_active) def test_invalid_session_access(self): """Test accessing another user's session""" other_user = QuizUser.objects.create(session_key="other_session_key") session = QuizSession.objects.create( user=other_user, course=self.course ) # Try to access it response = self.client.get(reverse('quiz:quiz_mode', args=[session.id])) self.assertEqual(response.status_code, 404) def test_answer_without_question_id(self): """Test error handling for missing question_id""" session = QuizSession.objects.create( user=self.user, course=self.course ) response = self.client.post( reverse('quiz:submit_answer', args=[session.id]), {'answer': 'A'} # Missing question_id ) self.assertEqual(response.status_code, 400) def test_difficulty_without_result(self): """Test error handling for difficulty without existing result""" session = QuizSession.objects.create( user=self.user, course=self.course ) response = self.client.post( reverse('quiz:submit_difficulty', args=[session.id]), { 'question_id': self.question1.id, 'difficulty': 'easy' } ) self.assertEqual(response.status_code, 404) def test_answer_normalization(self): """Test that multi-choice answers are normalized correctly""" session = QuizSession.objects.create( user=self.user, course=self.course ) # Submit answers in different orders test_cases = [ ('A,B', True), # Correct order ('B,A', True), # Reversed order ('A,C', False), # Wrong answer ('A', False), # Incomplete answer ] for answer, expected_correct in test_cases: result = QuizResult.objects.filter( user=self.user, question=self.question2, quiz_session=session ).delete() # Clean up response = self.client.post( reverse('quiz:submit_answer', args=[session.id]), { 'question_id': self.question2.id, 'answer': answer } ) self.assertEqual(response.status_code, 200) result = QuizResult.objects.get( user=self.user, question=self.question2, quiz_session=session ) self.assertEqual(result.is_correct, expected_correct, f"Answer '{answer}' should be {'correct' if expected_correct else 'incorrect'}")