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 longs.views import GenericExamViews as LongExamViews from longs.models import ( AnswerMarks, UserAnswer, Exam, Examination, Long as Question, LongSeries, LongSeriesImage, ) 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 = "longs" def create_exam(db): exam: Exam = LongExamViews.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.longs_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_long_series(question, image_number=5): series = LongSeries( description="Test series", examination=Examination.objects.get(pk=1), ) series.save() for n in range(image_number): create_long_series_image(series) return series def create_long_series_image(series): image = tempfile.NamedTemporaryFile( dir=settings.MEDIA_ROOT, suffix=".jpg", delete=False ) long_series_image = LongSeriesImage.objects.create(image=image.name, series=series) # long_series_image.save() def create_question(exam, answer=None, normal=False): # Just assign random stuff question: Question = Question.objects.create( description="Test question", history="Test history", feedback="Test feedback", model_observations="Model obs", model_interpretation="Model interp", model_principle_diagnosis="Model pd", model_differential_diagnosis="Model dd", model_management="Model management", ) series = create_long_series(question=question) question.series.add(series) question.exams.set([exam]) 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 print(f"{exam_metadata=}") res = client.get(exam_metadata["url"]) assert res.status_code == 200 exam_json = json.loads(res.content) print(exam_json) for q in exam_json["question_requests"]: print("check question:", q) # check redirects to the json file work # TODO: sort out testing of the saved JSON files question_json_res = client.get( reverse( f"longs:exam_question_json_cid", kwargs={"pk": exam.pk, "sk": q, "cid": cid_user.cid, "passcode": cid_user.passcode}, ), follow=True ) #assert question_json_res.status_code == 200 print(question_json_res.redirect_chain) print(question_json_res.content) assert len(question_json_res.redirect_chain) == 2 assert question_json_res.redirect_chain[0][1] == 302 assert question_json_res.redirect_chain[1][1] == 302 assert question_json_res.redirect_chain[1][0].endswith(f"{q}.json") question_json_unbased_res = client.get( reverse( f"longs:exam_question_json_unbased_cid", kwargs={"pk": exam.pk, "sk": q, "cid": cid_user.cid, "passcode": cid_user.passcode}, ), follow=True ) assert question_json_unbased_res.status_code == 200 question_json = json.loads(question_json_unbased_res.content) #question_json_res = client.get(question1.get_question_json(answers=False, based=True)) #print(question_json) assert ( question_json 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"long/{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"long/{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"long/{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" # Long 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