from django.test import TestCase, Client from django.urls import reverse from django.contrib.auth.models import User from generic.models import CidUserExam, CidUser from physics.models import Exam, Question, UserAnswer from atlas.models import Case from atlas.forms import CaseForm class ScoresStatsTests(TestCase): def setUp(self): self.admin = User.objects.create_superuser( username="admin", email="admin@example.com", password="adminpassword" ) self.client = Client() self.client.force_login(self.admin) def test_physics_stats_with_pass_mark_and_exclusion(self): # Create Exam exam = Exam.objects.create(name="Physics Test Exam", exam_mode=True) exam.author.add(self.admin) # Create Questions (max score will be 10 points) q1 = Question.objects.create( stem="stem1", a="a", a_answer=True, b="b", b_answer=False, c="c", c_answer=True, d="d", d_answer=False, e="e", e_answer=True, ) q1.exams.add(exam) q2 = Question.objects.create( stem="stem2", a="a", a_answer=True, b="b", b_answer=False, c="c", c_answer=True, d="d", d_answer=False, e="e", e_answer=True, ) q2.exams.add(exam) # Create two candidates: cid=1 (score=5/10), cid=2 (score=3/10) UserAnswer.objects.create( question=q1, exam=exam, cid=1, a=True, b=False, c=True, d=False, e=True ) UserAnswer.objects.create( question=q2, exam=exam, cid=1, a=False, b=True, c=False, d=True, e=False ) UserAnswer.objects.create( question=q1, exam=exam, cid=2, a=True, b=True, c=True, d=True, e=False ) # 1. Test stats calculations without custom pass mark (defaults to 50% = 5) url = reverse("physics:exam_scores_all", kwargs={"pk": exam.pk}) response = self.client.get(f"{url}?stats_only=true") self.assertEqual(response.status_code, 200) context = response.context self.assertEqual(context["pass_mark_threshold"], 5.0) self.assertEqual(context["passed_candidates_count"], 1) self.assertEqual(context["pass_rate"], 50.0) # 2. Update pass mark to 3.0 via HTMX update_pass_mark url update_url = reverse("physics:update_pass_mark", kwargs={"pk": exam.pk}) update_response = self.client.post(update_url, {"pass_mark": "3.0"}, HTTP_HX_REQUEST="true") self.assertEqual(update_response.status_code, 302) exam.refresh_from_db() self.assertEqual(exam.pass_mark, 3.0) # Fetch stats again and verify updated pass mark threshold and pass rate response = self.client.get(f"{url}?stats_only=true") context = response.context self.assertEqual(context["pass_mark_threshold"], 3.0) self.assertEqual(context["passed_candidates_count"], 2) self.assertEqual(context["pass_rate"], 100.0) # 3. Exclude cid=2 from stats exclude_url = reverse("physics:toggle_candidate_stats_exclusion", kwargs={"pk": exam.pk}) exclude_response = self.client.post(f"{exclude_url}?candidate=c/2") self.assertEqual(exclude_response.status_code, 200) # Check database status cid_user = CidUser.objects.get(cid=2) cux = CidUserExam.objects.get(object_id=exam.pk, cid_user=cid_user) self.assertTrue(cux.exclude_from_stats) # Fetch stats again and verify cid=2 is excluded response = self.client.get(f"{url}?stats_only=true") context = response.context self.assertEqual(len(context["cids"]), 1) self.assertIn("c/1", context["cids"]) self.assertNotIn("c/2", context["cids"]) self.assertEqual(context["mean"], 5.0) self.assertEqual(context["passed_candidates_count"], 1) self.assertEqual(context["pass_rate"], 100.0) def test_case_form_save_commit_false_does_not_persist(self): form_data = { "title": "Unsaved Case Form Test", "description": "Unsaved description", "diagnostic_certainty": 3, "open_access": True, } form = CaseForm(data=form_data, user=self.admin) self.assertTrue(form.is_valid()) case_inst = form.save(commit=False) self.assertIsNone(case_inst.pk) self.assertEqual(Case.objects.filter(title="Unsaved Case Form Test").count(), 0) def test_case_linked_series_info(self): # Create a chain of 3 cases: c1 -> c2 -> c3 c1 = Case.objects.create(title="Case 1") c2 = Case.objects.create(title="Case 2", previous_case=c1) c3 = Case.objects.create(title="Case 3", previous_case=c2) # Verify positions and totals info1 = c1.get_series_info() self.assertTrue(info1["in_series"]) self.assertEqual(info1["position"], 1) self.assertEqual(info1["total"], 3) info2 = c2.get_series_info() self.assertTrue(info2["in_series"]) self.assertEqual(info2["position"], 2) self.assertEqual(info2["total"], 3) info3 = c3.get_series_info() self.assertTrue(info3["in_series"]) self.assertEqual(info3["position"], 3) self.assertEqual(info3["total"], 3) c4 = Case.objects.create(title="Case 4") info4 = c4.get_series_info() self.assertFalse(info4["in_series"])