import json import os from re import A from django.conf import settings import pytest import tempfile # from django.contrib.auth.models import User from django.urls import reverse from rich.pretty import pprint from bs4 import BeautifulSoup from rapids.views import GenericExamViews as RapidExamViews from rapids.models import Answer, UserAnswer, Exam, Examination, Rapid as Question, RapidImage from generic.models import CidUser, CidUserGroup, UserUserGroup from django.contrib.auth.models import User from rad.test_helpers import AssertNotFound, create_cid_user_and_groups APP_NAME = "rapids" def create_exam(db): exam: Exam = RapidExamViews.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(*usergroup1.users.all()) assert exam in group1.rapid_cid_user_groups.all() assert exam return exam def create_examination(db): examination = Examination.objects.create(examination="test examination") examination.save() examination = Examination.objects.create(examination="test examination 2") examination.save() def create_rapid_image(question): image = tempfile.NamedTemporaryFile( dir=settings.MEDIA_ROOT, suffix=".jpg", delete=False ) rapid_image = RapidImage(image=image, rapid=question) rapid_image.save() def create_question(exam, answer=None, normal=False): # Just assign random stuff question: Question = Question.objects.create( laterality=Question.NONE, # examination=Examination.objects.get(pk=1), # image=imagohe.name, history="TEST history", normal=normal, ) create_rapid_image(question=question) question.exams.set([exam]) # Create an answer for the question if answer is None: answer_text = "testanswer" else: answer_text = answer answer = Answer( question=question, answer=answer_text, answer_compare=answer_text, status=Answer.MarkOptions.CORRECT, ) answer.save() return question def test_exams(db, client): create_cid_user_and_groups(db) exam = create_exam(db) # create_structures(db) # create_question_types(db) create_examination(db) question1 = create_question(exam) generated_questions = [question1, create_question(exam), create_question(exam)] 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_1000 = CidUser.objects.get(cid=1000) cid_user_1001 = CidUser.objects.get(cid=1001) cid_user_2001 = CidUser.objects.get(cid=2001) user1 = User.objects.get(username="user1") # Test exam access urls # Check that we can't access without logging in assert client.get(exam.get_json_url()).status_code == 404 assert ( client.get( exam.get_json_url(cid=cid_user_2001.cid, passcode=cid_user_2001.passcode) ).status_code == 404 ) # Valid user but incorrect passcode assert ( client.get( exam.get_json_url(cid=cid_user_1001.cid, passcode=cid_user_2001.passcode) ).status_code == 404 ) users_tested = 1 for cid_user in [cid_user_1001, cid_user_1000, user1]: if isinstance(cid_user, CidUser): testing_cid_user = True else: testing_cid_user = False current_user = cid_user client.force_login(cid_user) exam.active = True exam.publish_results = False exam.save() # Test active exams sections works as intended if testing_cid_user: active_exams_url = reverse( f"active_exams_cid", kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode}, ) else: active_exams_url = reverse( f"active_exams", ) active_exams_cid = client.get(active_exams_url) print(cid_user) assert active_exams_cid.status_code == 200 assert len(json.loads(active_exams_cid.content)["exams"]) == 1 if testing_cid_user: no_active_exams_cid = client.get( reverse(f"active_exams_cid", kwargs={"cid": 2001, "passcode": "EGFH"}) ) assert no_active_exams_cid.status_code == 401 assert json.loads(no_active_exams_cid.content)["status"] == "invalid" invalid_passcode = client.get( reverse( f"active_exams_cid", kwargs={"cid": cid_user.cid, "passcode": "AAAA"}, ) ) assert invalid_passcode.status_code == 401 assert json.loads(invalid_passcode.content)["status"] == "invalid" exam_metadata = json.loads(active_exams_cid.content)["exams"][0] assert exam_metadata["name"] == "Cid Exam" assert exam_metadata["exam_active"] == True assert exam_metadata["exam_mode"] == True res = client.get(exam_metadata["url"]) assert res.status_code == 200 exam_json = json.loads(res.content) print(exam_json) assert ( question1.get_question_json(answers=False) in exam_json["questions"].values() ) assert len(exam_json["questions"]) == 3 exam.active = False exam.save() res = client.get(exam_metadata["url"]) assert res.status_code == 404 exam.active = True exam.save() if testing_cid_user: post_cid = cid_user.cid else: post_cid = f"u-{current_user.pk}" post_json = { "eid": f"rapid/{exam.pk}", "cid": post_cid, "start_time": "1659618141.731", "answers": [[]], } # Test submission # Start with 0 answers res = client.post(reverse("global_exam_answers_submit"), data=post_json) json_res = json.loads(res.content) assert json_res["success"] == True assert json_res["question_count"] == 0 post_json = { "eid": f"rapid/{exam.pk+10}", "cid": post_cid, "start_time": "1659618141.731", "answers": [[]], } res = client.post(reverse("global_exam_answers_submit"), data=post_json) json_res = json.loads(res.content) assert json_res["success"] == False # Test invalid cid post_json = { "eid": f"rapid/{exam.pk+10}", "cid": 1, "start_time": "1659618141.731", "answers": [[]], } res = client.post(reverse("global_exam_answers_submit"), data=post_json) json_res = json.loads(res.content) assert json_res["success"] == False assert json_res["error"] == "Invalid data" # Rapid answers currently send a seperate object for each component valid_answers = [] valid_answers_extra = [] for questions in exam_json["questions"]: for qid in questions: post_data = { "eid": exam_json["eid"], "cid": post_cid, "qid": qid, "qidn": "1", "ans": f"Abnormal", } post_data2 = { "eid": exam_json["eid"], "cid": post_cid, "qid": qid, "qidn": "2", "ans": f"answer {qid}", } if testing_cid_user: post_data["passcode"] = cid_user.passcode post_data2["passcode"] = cid_user.passcode valid_answers_extra.append(post_data) valid_answers.append(post_data2) post_json = { "eid": exam_json["eid"], "cid": post_cid, "start_time": "1659618141.731", "answers": json.dumps([*valid_answers, *valid_answers_extra]), } # Test submission # give three answers res = client.post(reverse("global_exam_answers_submit"), data=post_json) json_res = json.loads(res.content) assert json_res["success"] == True assert json_res["question_count"] == 6 # Check the answers now exist in the database if testing_cid_user: answers = UserAnswer.objects.filter(cid=cid_user.cid) else: answers = UserAnswer.objects.filter(user=cid_user) assert answers.count() == 3 # We can then check the answers have been submitted (and can be viewed by the user) 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) cid_scores_soup = BeautifulSoup(cid_scores_res.content, "html.parser") search_exam = ( cid_scores_soup.find("div", {"id": "exam-assigned"}) .find("ul", {"class": exam.app_name}) .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) # Load the exam results page 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) answer_lis = cid_exam_scores_soup.find( "ul", {"class": "score-answer-list"} ).find_all("li") assert len(answer_lis) == len(valid_answers) for n in range( len(valid_answers) ): # Check no answers are visible if results not published answer = valid_answers[n] li = answer_lis[n] assert answer["ans"] in str(li) # Check that the saved answer is shown assert not li.find( "span", {"class": "correct-answer"} ) # and that the correct answer isn't assert ( cid_exam_scores_soup.find("div", {"class": "score-overview"}) is None ) # check we hide the score overview if not published # Add another question to the exam create_question(exam) # 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( "ul", {"class": "score-answer-list"} ).find_all("li") assert len(answer_lis) == len(valid_answers) + 1 assert "Not answered" in str(answer_lis[-1]) # 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 answer_lis = cid_exam_scores_soup.find( "ul", {"class": "score-answer-list"} ).find_all("li") assert len(answer_lis) == len(valid_answers) + 1 assert "Not answered" in str(answer_lis[-1]) # Repetition.... for n in range( len(valid_answers) ): # Check answers are visible if results published answer = valid_answers[n] li = answer_lis[n] assert answer["ans"] in str(li) # Check that the saved answer is shown assert li.find("span", {"class": "correct-answer"}).string == "testanswer" if users_tested == 1: # First user the answers will not have been saved assert ( cid_exam_scores_soup.find("div", {"class": "score-overview"}) .find("span", {"id": "total-score"}) .string == "0 (3 unmarked)" ) else: # Subsequent users should already have answers marked (apart from the last question which we have deleted) assert ( cid_exam_scores_soup.find("div", {"class": "score-overview"}) .find("span", {"id": "total-score"}) .string == "4 (1 unmarked)" ) for question in exam.exam_questions.all(): print(question) print(question.get_compare_answers()) try: new_ans = Answer( question=question, answer=question.get_unmarked_user_answers().pop(), status=Answer.MarkOptions.CORRECT, ) new_ans.save() except IndexError: # Final question does not have an answer pass # 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": "score-overview"}) .find("span", {"id": "total-score"}) .string == "6" ) generated_questions.pop(-1).delete() users_tested = users_tested + 1