diff --git a/physics/tests/test_physics_exams.py b/physics/tests/test_physics_exams.py index dacd3b99..95170a9d 100644 --- a/physics/tests/test_physics_exams.py +++ b/physics/tests/test_physics_exams.py @@ -19,7 +19,7 @@ from physics.views import GenericExamViews as PhysicsExamViews from physics.models import Category, UserAnswer, Exam, Question from generic.models import CidUser, CidUserGroup, UserUserGroup -from rad.test_helpers import AssertNotFound, create_cid_user_and_groups +from rad.test_helpers import AssertNotFound, ExamTester, create_cid_user_and_groups, create_exam from django.contrib.auth.models import User @@ -27,27 +27,6 @@ from django.contrib.auth.models import User APP_NAME = "physics" -def create_exam(db): - exam: Exam = PhysicsExamViews.Exam.objects.create( - name="Cid Exam", exam_mode=True, active=True - ) - - group1: CidUserGroup = CidUserGroup.objects.get(name="Group1") - usergroup1: UserUserGroup = UserUserGroup.objects.get(name="Group1") - - exam.cid_user_groups.add(group1) - - exam.valid_cid_users.add(*group1.ciduser_set.all()) - - # exam.valid_user_users.add(user1) - exam.valid_user_users.add(*usergroup1.users.all()) - - assert exam in group1.physics_cid_user_groups.all() - - assert exam - - return exam - def create_category(db): category = Category.objects.create(category="test category") @@ -96,193 +75,55 @@ def create_question(exam, answer=None): def test_exams(db, client): - create_cid_user_and_groups(db) - exam = create_exam(db) - create_category(db) - generated_questions = [ - create_question(exam), - create_question(exam), - create_question(exam), - ] + exam_tester = ExamTester(db, client, APP_NAME, PhysicsExamViews) - active_exams = client.get(reverse(f"{APP_NAME}:active_exams")) - assert active_exams.status_code == 200 - assert len(json.loads(active_exams.content)["exams"]) == 0 - - cid_user_1001 = CidUser.objects.get(cid=1001) - cid_user_1000 = CidUser.objects.get(cid=1000) - cid_user_2001 = CidUser.objects.get(cid=2001) - - user1 = User.objects.get(username="user1") - user2 = User.objects.get(username="user2") + exam_tester.create_questions(create_question) users_tested = 1 # for cid_user in [cid_user_1001, cid_user_1000, user1, user2]: - for cid_user in [cid_user_1001, cid_user_1000, user1, user2]: - if isinstance(cid_user, CidUser): - testing_cid_user = True - else: - testing_cid_user = False - current_user = cid_user - client.force_login(cid_user) + for cid_user in exam_tester.all_users: + + exam_tester.load_user(cid_user) + exam = exam_tester.exam + exam.active = False + exam.save() + + exam_tester.check_active_exams(0) + exam.active = True exam.publish_results = False exam.save() - start_page_res = client.get( - reverse(f"physics:exam_start", kwargs={"pk": exam.pk}) - ) - print(cid_user) + exam_tester.check_onsite_start_page() - # Check user access to the start page - assert start_page_res.status_code == HTTPStatus.OK - start_page_soup = BeautifulSoup(start_page_res.content, "html.parser") - print(start_page_soup) - if testing_cid_user or cid_user in [user2]: - assert "Enter your CID and passcode" in str(start_page_soup) - else: - assert "Start Exam" in str(start_page_soup) + #check_onsite_start_page - for n, question in enumerate(generated_questions): - if testing_cid_user: - take_url = reverse( - f"physics:exam_take", - kwargs={ - "pk": exam.pk, - "sk": n, - "cid": cid_user.cid, - "passcode": cid_user.passcode, - }, - ) - else: - take_url = reverse( - f"physics:exam_take_user", - kwargs={ - "pk": exam.pk, - "sk": n, - }, - ) + exam_tester.check_take_page() - cid_take_res = client.get(take_url) + exam_tester.check_take_overview(answered=0, total=3) - # Check that users that have not been added cannot access the take pages - if cid_user in [user2]: - assert cid_take_res.status_code == HTTPStatus.NOT_FOUND - continue - else: - assert cid_take_res.status_code == HTTPStatus.OK - - cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser") - - # Check correct user is displayed - if testing_cid_user: - display_user = str(cid_user.cid) - else: - display_user = current_user.username - assert display_user in cid_take_soup.find("span", {"id": "user-id"}).string - - # Check review mode alert not visible - assert cid_take_soup.find("div", {"class": "review-mode-alert"}) is None - - # Check that relevant buttons are being displayed - assert cid_take_soup.find("button", {"name": "finish"}) is not None - - if n < len(generated_questions) - 1: - assert cid_take_soup.find("button", {"name": "next"}) is not None - - if n > 0: - assert cid_take_soup.find("button", {"name": "previous"}) is not None - - assert cid_take_soup.find("span", {"id": "question-number"}).string == str( - n + 1 - ) # + 1 for 0 offset - assert cid_take_soup.find("span", {"id": "exam-length"}).string == str( - len(generated_questions) - ) - - # Check that stem text is displayed - assert ( - cid_take_soup.find("span", {"id": "question-stem"}).string - == question.stem - ) - - assert cid_take_soup.find("label", {"for": "id_a"}).string == question.a - assert cid_take_soup.find("label", {"for": "id_b"}).string == question.b - assert cid_take_soup.find("label", {"for": "id_c"}).string == question.c - assert cid_take_soup.find("label", {"for": "id_d"}).string == question.d - assert cid_take_soup.find("label", {"for": "id_e"}).string == question.e - - # As we haven't submitted any answers they should all be blank - assert ( - cid_take_soup.find("input", {"type": "checkbox", "checked": True}) - is None - ) - - if testing_cid_user: - take_overview_url = reverse( - f"physics:exam_take_overview", - kwargs={ - "pk": exam.pk, - "cid": cid_user.cid, - "passcode": cid_user.passcode, - }, - ) - else: - take_overview_url = reverse( - f"physics:exam_take_overview_user", - kwargs={ - "pk": exam.pk, - }, - ) - - cid_take_overview_res = client.get(take_overview_url) - - # Check that users that have not been added cannot access the take pages - if cid_user in [user2]: - assert cid_take_overview_res.status_code == HTTPStatus.NOT_FOUND - else: - assert cid_take_overview_res.status_code == HTTPStatus.OK - - cid_take_overview_soup = BeautifulSoup( - cid_take_overview_res.content, "html.parser" - ) - assert ( - "0 out of 3 questions answered" - in cid_take_overview_soup.find( - "div", attrs={"class": "overview-text"} - ).string - ) - - assert ( - len( - cid_take_overview_soup.find_all( - "button", attrs={"class": "unanswered"} - ) - ) - == 3 - ) # Loop through questions again to test submissions # All physics questions have 5 parts (and require a csrf token) - questions_len = len(generated_questions) - 1 - for n, question in enumerate(generated_questions): - if testing_cid_user: + questions_len = len(exam_tester.generated_questions) - 1 + for n, question in enumerate(exam_tester.generated_questions): + if exam_tester.testing_cid_user: question_url = reverse( - f"physics:exam_take", + f"{APP_NAME}:exam_take", kwargs={ "pk": exam.pk, "sk": n, - "cid": cid_user.cid, - "passcode": cid_user.passcode, + "cid": exam_tester.current_user.cid, + "passcode": exam_tester.current_user.passcode, }, ) else: question_url = reverse( - f"physics:exam_take_user", + f"{APP_NAME}:exam_take_user", kwargs={ "pk": exam.pk, "sk": n, @@ -291,7 +132,7 @@ def test_exams(db, client): cid_take_res = client.get(question_url) - if cid_user in [user2]: + if cid_user in exam_tester.invalid_users: assert cid_take_res.status_code == HTTPStatus.NOT_FOUND continue else: @@ -341,255 +182,29 @@ def test_exams(db, client): assert len(cid_user_answers) == users_tested - if testing_cid_user: - cid_user_answer = cid_user_answers.filter(cid=cid_user.cid)[0] + if exam_tester.testing_cid_user: + cid_user_answer = cid_user_answers.filter(cid=exam_tester.current_user.cid)[0] else: - cid_user_answer = cid_user_answers.filter(user=current_user)[0] + cid_user_answer = cid_user_answers.filter(user=exam_tester.current_user)[0] assert all(cid_user_answer.get_answers()) # Recheck the overview page (now that we have submitted answers) - if testing_cid_user: - take_overview_url = reverse( - f"physics:exam_take_overview", - kwargs={ - "pk": exam.pk, - "cid": cid_user.cid, - "passcode": cid_user.passcode, - }, - ) - else: - take_overview_url = reverse( - f"physics:exam_take_overview_user", - kwargs={ - "pk": exam.pk, - }, - ) + exam_tester.check_take_overview(3, 3) - cid_take_overview_res = client.get(take_overview_url) - if cid_user in [user2]: - assert cid_take_overview_res.status_code == HTTPStatus.NOT_FOUND - else: - assert cid_take_overview_res.status_code == HTTPStatus.OK + exam_tester.check_cid_scores_page() - cid_take_overview_soup = BeautifulSoup( - cid_take_overview_res.content, "html.parser" - ) - assert ( - "3 out of 3 questions answered" - in cid_take_overview_soup.find( - "div", attrs={"class": "overview-text"} - ).string - ) - assert ( - len( - cid_take_overview_soup.find_all( - "button", attrs={"class": "unanswered"} - ) - ) - == 0 - ) + exam_tester.check_cid_scores_user_page() - if testing_cid_user: - # Get overview scores page (invalid passcode) - AssertNotFound( - client, - reverse( - f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": "AAAA"} - ), - ) - - cid_scores_url = reverse( - f"cid_scores", - kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode}, - ) - else: - cid_scores_url = reverse(f"user_scores") - - # Get overview scores page - cid_scores_res = client.get(cid_scores_url) - - assert cid_scores_res.status_code == HTTPStatus.OK - - cid_scores_soup = BeautifulSoup(cid_scores_res.content, "html.parser") - - if cid_user in [user2]: - # page should load but we shouldn't be able to find the exam - with pytest.raises(AttributeError): - search_exam = ( - cid_scores_soup.find("div", {"id": "exam-assigned"}) - .find("ul", {"class": exam.app_name.lower()}) - .find_all("li", attrs={"data-exam-id": exam.pk}) - ) - else: - search_exam = ( - cid_scores_soup.find("div", {"id": "exam-assigned"}) - .find("ul", {"class": exam.app_name.lower()}) - .find_all("li", attrs={"data-exam-id": exam.pk}) - ) - assert search_exam - assert exam.name in str(search_exam) - assert "Active" in str(search_exam) - # assert "Active" in assigned_exams # check it is active - - invalid_exam = cid_scores_soup.find_all( - "li", attrs={"data-exam-id": exam.pk + 5} - ) - assert not invalid_exam # Check we can't find an invalid exam - - # Check that we have a results link - results_exam = ( - cid_scores_soup.find("div", {"id": "exam-results"}) - .find("ul", {"class": exam.app_name}) - .find_all("li", attrs={"data-exam-id": exam.pk}) - ) - assert results_exam - assert exam.name in str(results_exam) - assert exam.name + " test" not in str(results_exam) - assert "Results Published" not in str( - results_exam - ) # It should not be published yet - - if testing_cid_user: - AssertNotFound( - client, - reverse( - f"{exam.app_name}:exam_scores_cid_user", - kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": "AAAA"}, - ), - ) - AssertNotFound( - client, - reverse( - f"{exam.app_name}:exam_scores_cid_user", - kwargs={ - "pk": exam.pk, - "cid": cid_user_2001.cid, - "passcode": cid_user_2001.passcode, - }, - ), - ) - - exam_scores_url = reverse( - f"{exam.app_name}:exam_scores_cid_user", - kwargs={ - "pk": exam.pk, - "cid": cid_user.cid, - "passcode": cid_user.passcode, - }, - ) - else: - exam_scores_url = reverse( - f"{exam.app_name}:exam_scores_user", - kwargs={"pk": exam.pk}, - ) - - # Load the exam results page - cid_exam_scores_res = client.get(exam_scores_url) - - if cid_user in [user2]: - assert cid_exam_scores_res.status_code == HTTPStatus.NOT_FOUND + # The rest of the tests are for valid users + if exam_tester.current_user in exam_tester.invalid_users: continue - else: - assert cid_exam_scores_res.status_code == HTTPStatus.OK - - cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser") - - # Check that we have an alert that exams are not published - alert = cid_exam_scores_soup.find("div", {"class": "alert"}) - assert "Results are not currently published." in str(alert) - - # Check that we are showing enough questions / answers - answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"}) - assert len(answer_lis) == 5 * len(generated_questions) - - submitted_answers = cid_exam_scores_soup.find_all( - "span", {"class": "submitted-user-answer"} - ) - assert len(submitted_answers) == 5 * len(generated_questions) - - for ans in submitted_answers: - assert ans.text.strip() == "True" - - # Check no correct answers are shown - assert not cid_exam_scores_soup.find_all("span", {"class": "correct-answer"}) - - # Add another question to the exam - extra_question = create_question(exam) - generated_questions.append(extra_question) - - # Load the exam results page (again) - cid_exam_scores_res = client.get(exam_scores_url) - cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser") - - answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"}) - assert len(answer_lis) == 5 * len(generated_questions) - - submitted_answers = cid_exam_scores_soup.find_all( - "span", {"class": "submitted-user-answer"} - ) - assert len(submitted_answers) == 5 * len(generated_questions) - - for ans in submitted_answers[:-5]: - assert ans.text.strip() == "True" - - # The new question will not have any answers - for ans in submitted_answers[-5:]: - assert ans.text.strip() == "" - - # Publish the results! - exam.publish_results = True - exam.save() - - # Load the exam results page (again) - cid_exam_scores_res = client.get(exam_scores_url) - cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser") - - assert ( - cid_exam_scores_soup.find("div", {"class": "alert"}) is None - ) # Check our warning banner is gone - - # Check that we are displaying the correct answers (correctly) - correct_answers = cid_exam_scores_soup.find_all( - "span", {"class": "correct-answer"} - ) - assert len(correct_answers) == 5 * len(generated_questions) - - n = 0 - for question in generated_questions: - for ans in question.get_answers(): - assert correct_answers[n].text.strip() == f"Correct answer: {ans}" - n = n + 1 - - submitted_answers = cid_exam_scores_soup.find_all( - "span", {"class": "submitted-user-answer"} - ) - assert len(submitted_answers) == 5 * len(generated_questions) - - for ans in submitted_answers[:-5]: - assert ans.text.strip() == "True" - - # The new question will not have any answers - for ans in submitted_answers[-5:]: - assert ans.text.strip() == "" - - assert ( - cid_exam_scores_soup.find("div", {"class": "score-overview"}) - .find("span", {"id": "total-score"}) - .string - == "9" - ) - assert ( - cid_exam_scores_soup.find("div", {"class": "score-overview"}) - .find("span", {"id": "max-score"}) - .string - == "20" - ) # answer the final question - if testing_cid_user: + extra_question = exam_tester.generated_questions[-1] + if exam_tester.testing_cid_user: cid_user_answer = UserAnswer( question=extra_question, a=True, @@ -608,54 +223,12 @@ def test_exams(db, client): c=True, d=False, e=True, - user=current_user, + user=exam_tester.current_user, exam=exam, ) cid_user_answer.save() - cid_exam_scores_res = client.get(exam_scores_url) - cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser") + exam_tester.check_cid_scores_user_page2() - # As we have answered correctly the new score should be increased by 5 - assert ( - cid_exam_scores_soup.find("div", {"class": "score-overview"}) - .find("span", {"id": "total-score"}) - .string - == "14" - ) - # Check what happens when exam is not active - exam.active = False - exam.save() - - start_page_res = client.get( - reverse(f"physics:exam_start", kwargs={"pk": exam.pk}) - ) - start_page_soup = BeautifulSoup(start_page_res.content, "html.parser") - - assert "Exam is currently inactive" in start_page_soup.text - - if testing_cid_user: - cid_take_url = reverse( - f"physics:exam_take", - kwargs={ - "pk": exam.pk, - "sk": 0, - "cid": cid_user.cid, - "passcode": cid_user.passcode, - }, - ) - else: - cid_take_url = reverse( - f"physics:exam_take_user", - kwargs={ - "pk": exam.pk, - "sk": 0, - }, - ) - cid_take_res = client.get(cid_take_url) - cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser") - assert "Exam is currently inactive" in cid_take_soup.text - - generated_questions.pop(-1).delete() users_tested = users_tested + 1 diff --git a/rad/test_helpers.py b/rad/test_helpers.py index ccb29c69..4c202923 100644 --- a/rad/test_helpers.py +++ b/rad/test_helpers.py @@ -1,12 +1,22 @@ from http import HTTPStatus +import json +from typing import Callable -from generic.models import CidUser, CidUserGroup, UserUserGroup +from bs4 import BeautifulSoup +import pytest + +from generic.models import CidUser, CidUserGroup, UserUserGroup, ExamBase +from generic.views import ExamViews from django.contrib.auth.models import User +from django.urls import reverse + def AssertNotFound(client, url): """Helper to quickly test for urls that should return a 404 (NOT FOUND) error""" response = client.get(url) - assert response.status_code == HTTPStatus.NOT_FOUND, f"Response content: {response.content}" + assert ( + response.status_code == HTTPStatus.NOT_FOUND + ), f"Response content: {response.content}" def create_cid_user_and_groups(db): @@ -21,9 +31,492 @@ def create_cid_user_and_groups(db): assert cid_user_1001.cid == 1001 # Create UserUsers - user1 = User.objects.create_user(username="user1", email="test@email.com", password="test1234") - user2 = User.objects.create_user(username="user2", email="test2@email.com", password="test1234") + user1 = User.objects.create_user( + username="user1", email="test@email.com", password="test1234" + ) + user2 = User.objects.create_user( + username="user2", email="test2@email.com", password="test1234" + ) usergroup1 = UserUserGroup.objects.create(name="Group1") usergroup1.users.add(user1) usergroup2 = UserUserGroup.objects.create(name="Group2") - usergroup2.users.add(user2) \ No newline at end of file + usergroup2.users.add(user2) + + +def create_exam(db, ExamViews: ExamViews): + exam: ExamBase = ExamViews.Exam.objects.create( + name="Cid Exam", exam_mode=True, active=True + ) + + cidgroup1: CidUserGroup = CidUserGroup.objects.get(name="Group1") + usergroup1: UserUserGroup = UserUserGroup.objects.get(name="Group1") + + exam.cid_user_groups.add(cidgroup1) + + exam.valid_cid_users.add(*cidgroup1.ciduser_set.all()) + + # exam.valid_user_users.add(user1) + exam.valid_user_users.add(*usergroup1.users.all()) + + assert exam + + return exam + + +class ExamTester: + def __init__(self, db, client, app_name, ExamViews) -> None: + self.db = db + self.client = client + self.app_name = app_name + + create_cid_user_and_groups(db) + self.exam = exam = create_exam(db, ExamViews) + + cidgroup1: CidUserGroup = CidUserGroup.objects.get(name="Group1") + assert exam in cidgroup1.physics_cid_user_groups.all() + + cid_user_1001 = CidUser.objects.get(cid=1001) + cid_user_1000 = CidUser.objects.get(cid=1000) + self.cid_user_2001 = cid_user_2001 = CidUser.objects.get(cid=2001) + + user1 = User.objects.get(username="user1") + user2 = User.objects.get(username="user2") + + self.all_users = [cid_user_1000, cid_user_1001, cid_user_2001, user1, user2] + + self.cid_users = [cid_user_1000, cid_user_1001, cid_user_2001] + + self.invalid_users = [cid_user_2001, user2] + + self.current_user = None + self.generated_questions = [] + + def load_user(self, user): + self.client.logout() + + self.current_user = user + + if isinstance(self.current_user, CidUser): + self.testing_cid_user = True + else: + self.testing_cid_user = False + + self.client.force_login(user) + + def create_questions(self, create_question_func: Callable): + self.create_question_func = create_question_func + + self.generated_questions = [ + create_question_func(self.exam), + create_question_func(self.exam), + create_question_func(self.exam), + ] + + def add_question(self): + self.generated_questions.append(self.create_question_func(self.exam)) + + def check_active_exams(self, n): + active_exams = self.client.get(reverse(f"{self.app_name}:active_exams")) + assert active_exams.status_code == 200 + assert len(json.loads(active_exams.content)["exams"]) == n, f"Current user: {self.current_user}" + + def check_onsite_start_page(self): + start_page_res = self.client.get( + reverse(f"{self.app_name}:exam_start", kwargs={"pk": self.exam.pk}) + ) + + # Check user access to the start page + assert start_page_res.status_code == HTTPStatus.OK + start_page_soup = BeautifulSoup(start_page_res.content, "html.parser") + if ( + self.current_user in self.cid_users + or self.current_user in self.invalid_users + ): + assert "Enter your CID and passcode" in str(start_page_soup) + else: + assert "Start Exam" in str(start_page_soup) + + def check_take_page(self): + for n, question in enumerate(self.generated_questions): + if self.testing_cid_user: + take_url = reverse( + f"{self.app_name}:exam_take", + kwargs={ + "pk": self.exam.pk, + "sk": n, + "cid": self.current_user.cid, + "passcode": self.current_user.passcode, + }, + ) + else: + take_url = reverse( + f"{self.app_name}:exam_take_user", + kwargs={ + "pk": self.exam.pk, + "sk": n, + }, + ) + + cid_take_res = self.client.get(take_url) + + # Check that users that have not been added cannot access the take pages + if self.current_user in self.invalid_users: + assert cid_take_res.status_code == HTTPStatus.NOT_FOUND + continue + else: + assert cid_take_res.status_code == HTTPStatus.OK, f"Current user: {self.current_user}" + + cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser") + + # Check correct user is displayed + if self.testing_cid_user: + display_user = str(self.current_user.cid) + else: + display_user = self.current_user.username + assert display_user in cid_take_soup.find("span", {"id": "user-id"}).string + + # Check review mode alert not visible + assert cid_take_soup.find("div", {"class": "review-mode-alert"}) is None + + # Check that relevant buttons are being displayed + assert cid_take_soup.find("button", {"name": "finish"}) is not None + + if n < len(self.generated_questions) - 1: + assert cid_take_soup.find("button", {"name": "next"}) is not None + + if n > 0: + assert cid_take_soup.find("button", {"name": "previous"}) is not None + + assert cid_take_soup.find("span", {"id": "question-number"}).string == str( + n + 1 + ) # + 1 for 0 offset + assert cid_take_soup.find("span", {"id": "exam-length"}).string == str( + len(self.generated_questions) + ) + + # Check that stem text is displayed + assert ( + cid_take_soup.find("span", {"id": "question-stem"}).string + == question.stem + ) + + assert cid_take_soup.find("label", {"for": "id_a"}).string == question.a + assert cid_take_soup.find("label", {"for": "id_b"}).string == question.b + assert cid_take_soup.find("label", {"for": "id_c"}).string == question.c + assert cid_take_soup.find("label", {"for": "id_d"}).string == question.d + assert cid_take_soup.find("label", {"for": "id_e"}).string == question.e + + # As we haven't submitted any answers they should all be blank + assert ( + cid_take_soup.find("input", {"type": "checkbox", "checked": True}) + is None + ) + + def check_take_overview(self, answered, total): + if self.testing_cid_user: + take_overview_url = reverse( + f"{self.app_name}:exam_take_overview", + kwargs={ + "pk": self.exam.pk, + "cid": self.current_user.cid, + "passcode": self.current_user.passcode, + }, + ) + else: + take_overview_url = reverse( + f"{self.app_name}:exam_take_overview_user", + kwargs={ + "pk": self.exam.pk, + }, + ) + + cid_take_overview_res = self.client.get(take_overview_url) + + # Check that users that have not been added cannot access the take pages + if self.current_user in self.invalid_users: + assert cid_take_overview_res.status_code == HTTPStatus.NOT_FOUND + else: + assert cid_take_overview_res.status_code == HTTPStatus.OK + + cid_take_overview_soup = BeautifulSoup( + cid_take_overview_res.content, "html.parser" + ) + print(self.generated_questions) + assert ( + f"{answered} out of 3 questions answered" + in cid_take_overview_soup.find( + "div", attrs={"class": "overview-text"} + ).string + ) + + assert ( + len( + cid_take_overview_soup.find_all( + "button", attrs={"class": "unanswered"} + ) + ) + == total - answered + ) + + def check_cid_scores_page(self): + if self.testing_cid_user: + # Get overview scores page (invalid passcode) + AssertNotFound( + self.client, + reverse( + f"cid_scores", + kwargs={"cid": self.current_user.cid, "passcode": "AAAA"}, + ), + ) + + cid_scores_url = reverse( + f"cid_scores", + kwargs={ + "cid": self.current_user.cid, + "passcode": self.current_user.passcode, + }, + ) + else: + cid_scores_url = reverse(f"user_scores") + + # Get overview scores page + cid_scores_res = self.client.get(cid_scores_url) + + assert cid_scores_res.status_code == HTTPStatus.OK + + cid_scores_soup = BeautifulSoup(cid_scores_res.content, "html.parser") + + if self.current_user in self.invalid_users: + # page should load but we shouldn't be able to find the exam + with pytest.raises(AttributeError): + search_exam = ( + cid_scores_soup.find("div", {"id": "exam-assigned"}) + .find("ul", {"class": self.exam.app_name.lower()}) + .find_all("li", attrs={"data-exam-id": self.exam.pk}) + ) + else: + search_exam = ( + cid_scores_soup.find("div", {"id": "exam-assigned"}) + .find("ul", {"class": self.exam.app_name.lower()}) + .find_all("li", attrs={"data-exam-id": self.exam.pk}) + ) + assert search_exam + assert self.exam.name in str(search_exam) + assert "Active" in str(search_exam) + # assert "Active" in assigned_exams # check it is active + + invalid_exam = cid_scores_soup.find_all( + "li", attrs={"data-exam-id": self.exam.pk + 5} + ) + assert not invalid_exam # Check we can't find an invalid exam + + # Check that we have a results link + results_exam = ( + cid_scores_soup.find("div", {"id": "exam-results"}) + .find("ul", {"class": self.exam.app_name}) + .find_all("li", attrs={"data-exam-id": self.exam.pk}) + ) + assert results_exam + assert self.exam.name in str(results_exam) + assert self.exam.name + " test" not in str(results_exam) + assert "Results Published" not in str( + results_exam + ) # It should not be published yet + + def check_cid_scores_user_page(self): + if self.testing_cid_user: + AssertNotFound( + self.client, + reverse( + f"{self.exam.app_name}:exam_scores_cid_user", + kwargs={ + "pk": self.exam.pk, + "cid": self.current_user.cid, + "passcode": "AAAA", + }, + ), + ) + AssertNotFound( + self.client, + reverse( + f"{self.exam.app_name}:exam_scores_cid_user", + kwargs={ + "pk": self.exam.pk, + "cid": self.cid_user_2001.cid, + "passcode": self.cid_user_2001.passcode, + }, + ), + ) + + exam_scores_url = reverse( + f"{self.exam.app_name}:exam_scores_cid_user", + kwargs={ + "pk": self.exam.pk, + "cid": self.current_user.cid, + "passcode": self.current_user.passcode, + }, + ) + else: + exam_scores_url = reverse( + f"{self.exam.app_name}:exam_scores_user", + kwargs={"pk": self.exam.pk}, + ) + + self.exam_scores_url = exam_scores_url + + # Load the exam results page + cid_exam_scores_res = self.client.get(exam_scores_url) + + if self.current_user in self.invalid_users: + assert cid_exam_scores_res.status_code == HTTPStatus.NOT_FOUND + return + else: + assert cid_exam_scores_res.status_code == HTTPStatus.OK + + cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser") + + # Check that we have an alert that exams are not published + alert = cid_exam_scores_soup.find("div", {"class": "alert"}) + assert "Results are not currently published." in str(alert) + + # Check that we are showing enough questions / answers + answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"}) + assert len(answer_lis) == 5 * len(self.generated_questions) + + submitted_answers = cid_exam_scores_soup.find_all( + "span", {"class": "submitted-user-answer"} + ) + assert len(submitted_answers) == 5 * len(self.generated_questions) + + for ans in submitted_answers: + assert ans.text.strip() == "True" + + # Check no correct answers are shown + assert not cid_exam_scores_soup.find_all("span", {"class": "correct-answer"}) + + # Add another question to the exam + self.add_question() + + # Load the exam results page (again) + cid_exam_scores_res = self.client.get(exam_scores_url) + cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser") + + answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"}) + assert len(answer_lis) == 5 * len(self.generated_questions) + + submitted_answers = cid_exam_scores_soup.find_all( + "span", {"class": "submitted-user-answer"} + ) + assert len(submitted_answers) == 5 * len(self.generated_questions) + + for ans in submitted_answers[:-5]: + assert ans.text.strip() == "True" + + # The new question will not have any answers + for ans in submitted_answers[-5:]: + assert ans.text.strip() == "" + + # Publish the results! + self.exam.publish_results = True + self.exam.save() + + # Load the exam results page (again) + cid_exam_scores_res = self.client.get(exam_scores_url) + cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser") + + assert ( + cid_exam_scores_soup.find("div", {"class": "alert"}) is None + ) # Check our warning banner is gone + + # Check that we are displaying the correct answers (correctly) + correct_answers = cid_exam_scores_soup.find_all( + "span", {"class": "correct-answer"} + ) + assert len(correct_answers) == 5 * len(self.generated_questions) + + n = 0 + for question in self.generated_questions: + for ans in question.get_answers(): + assert correct_answers[n].text.strip() == f"Correct answer: {ans}" + n = n + 1 + + submitted_answers = cid_exam_scores_soup.find_all( + "span", {"class": "submitted-user-answer"} + ) + assert len(submitted_answers) == 5 * len(self.generated_questions) + + for ans in submitted_answers[:-5]: + assert ans.text.strip() == "True" + + # The new question will not have any answers + for ans in submitted_answers[-5:]: + assert ans.text.strip() == "" + + assert ( + cid_exam_scores_soup.find("div", {"class": "score-overview"}) + .find("span", {"id": "total-score"}) + .string + == "9" + ) + assert ( + cid_exam_scores_soup.find("div", {"class": "score-overview"}) + .find("span", {"id": "max-score"}) + .string + == "20" + ) + + + + def check_cid_scores_user_page2(self): + + cid_exam_scores_res = self.client.get(self.exam_scores_url) + + cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser") + + + + # As we have answered correctly the new score should be increased by 5 + assert ( + cid_exam_scores_soup.find("div", {"class": "score-overview"}) + .find("span", {"id": "total-score"}) + .string + == "14" + ), f"Current user: {self.current_user}" + + # Check what happens when exam is not active + self.exam.active = False + self.exam.save() + + start_page_res = self.client.get( + reverse(f"{self.app_name}:exam_start", kwargs={"pk": self.exam.pk}) + ) + start_page_soup = BeautifulSoup(start_page_res.content, "html.parser") + + assert "Exam is currently inactive" in start_page_soup.text + + if self.testing_cid_user: + cid_take_url = reverse( + f"{self.app_name}:exam_take", + kwargs={ + "pk": self.exam.pk, + "sk": 0, + "cid": self.current_user.cid, + "passcode": self.current_user.passcode, + }, + ) + else: + cid_take_url = reverse( + f"{self.app_name}:exam_take_user", + kwargs={ + "pk": self.exam.pk, + "sk": 0, + }, + ) + cid_take_res = self.client.get(cid_take_url) + cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser") + assert "Exam is currently inactive" in cid_take_soup.text + + # Delete the last question, from both the list + q= self.generated_questions.pop() + # and the database + q.delete() \ No newline at end of file