From 3aa2e87fd90abc97b8c9b6aa636a75f6f5c2e796 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 13 Feb 2023 11:07:42 +0000 Subject: [PATCH] start longs tests --- longs/tests/test_longs_exams.py | 505 ++++++++++++++++++++++++++++++++ 1 file changed, 505 insertions(+) create mode 100644 longs/tests/test_longs_exams.py diff --git a/longs/tests/test_longs_exams.py b/longs/tests/test_longs_exams.py new file mode 100644 index 00000000..b08f3fdb --- /dev/null +++ b/longs/tests/test_longs_exams.py @@ -0,0 +1,505 @@ +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, CidUserAnswer, 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.long_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_image(question): + image = tempfile.NamedTemporaryFile( + dir=settings.MEDIA_ROOT, suffix=".jpg", delete=False + ) + + long_image = LongImage(image=image, long=question) + + long_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_long_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"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 = CidUserAnswer.objects.filter(cid=cid_user.cid) + else: + answers = CidUserAnswer.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