563 lines
18 KiB
Python
563 lines
18 KiB
Python
import json
|
|
import os
|
|
from re import A
|
|
import time
|
|
from django.conf import settings
|
|
from django.core.exceptions import ValidationError
|
|
from django.http import Http404
|
|
from django.utils import timezone
|
|
|
|
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 RapidsExamViews
|
|
|
|
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 = RapidsExamViews.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 str(exam) 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 str(exam) in str(results_exam)
|
|
assert str(exam) + " 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.get_questions():
|
|
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
|
|
|
|
|
|
def test_exams_with_dates(db, client):
|
|
create_cid_user_and_groups(db)
|
|
exam = create_exam(db)
|
|
|
|
|
|
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)
|
|
user1 = User.objects.get(username="user1")
|
|
exam.check_user_can_take(None, None, user1)
|
|
exam.check_user_can_take(cid_user_1000.cid, cid_user_1000.passcode)
|
|
|
|
exam.active = False
|
|
exam.save()
|
|
|
|
with pytest.raises(Http404):
|
|
exam.check_user_can_take(None, None, user1)
|
|
|
|
with pytest.raises(Http404):
|
|
exam.check_user_can_take(cid_user_1000.cid, cid_user_1000.passcode)
|
|
|
|
with pytest.raises(ValidationError):
|
|
exam.restrict_to_dates = True
|
|
exam.save()
|
|
with pytest.raises(ValidationError):
|
|
exam.restrict_to_dates = True
|
|
exam.end_date = timezone.now()
|
|
exam.start_date = timezone.now() + timezone.timedelta(days=1)
|
|
exam.save()
|
|
|
|
exam.start_date = timezone.now()
|
|
exam.end_date = None # The previously set value seems to remain...
|
|
|
|
exam.restrict_to_dates = True
|
|
exam.save()
|
|
exam.check_user_can_take(None, None, user1)
|
|
exam.check_user_can_take(cid_user_1000.cid, cid_user_1000.passcode)
|
|
|
|
exam.end_date = timezone.now()
|
|
exam.save()
|
|
|
|
with pytest.raises(Http404):
|
|
exam.check_user_can_take(None, None, user1)
|
|
|
|
with pytest.raises(Http404):
|
|
exam.check_user_can_take(cid_user_1000.cid, cid_user_1000.passcode) |