Files
penracourses/rapids/tests/test_rapids_exams.py
T
2023-11-27 12:08:45 +00:00

511 lines
16 KiB
Python

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.tests.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
)
image.flush()
image._committed = True
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)
print(exam_json["questions"].values())
print("----")
print(question1.get_question_json(answers=False))
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"] is True
assert json_res["question_count"] == 2 * 3
# 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("li", attrs={"data-exam-id": exam.pk})
)
assert search_exam
assert exam.name in str(search_exam)
assert len(search_exam.find("button", {"class": "start-button"})) > 0
# 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