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

478 lines
15 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 anatomy.views import GenericExamViews as AnatomyExamViews
from anatomy.models import (
Answer,
Exam,
AnatomyQuestion as Question,
QuestionType,
Structure,
)
from generic.models import CidUser, CidUserGroup, UserUserGroup
from rad.tests.test_helpers import AssertNotFound, create_cid_user_and_groups
from django.contrib.auth.models import User
APP_NAME = "anatomy"
def create_exam(db):
exam: Exam = AnatomyExamViews.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.anatomy_cid_user_groups.all()
assert exam
return exam
def create_structures(db):
structure = Structure.objects.create(structure="test structure")
structure.save()
structure = Structure.objects.create(structure="test structure 2")
structure.save()
def create_question_types(db):
question_type = QuestionType.objects.create(text="test question type")
question_type.save()
question_type = QuestionType.objects.create(text="test question type 2")
question_type.save()
def create_question(exam, answer=None):
# Just assign random stuff
image = tempfile.NamedTemporaryFile(
dir=settings.MEDIA_ROOT, suffix=".jpg", delete=False
)
question: Question = Question.objects.create(
question_type=QuestionType.objects.first(),
structure=Structure.objects.first(),
image=image.name,
)
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)
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)
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
# Check that we can access the exam json
res = client.get(exam_metadata["url"])
assert res.status_code == 200
exam_json = json.loads(res.content)
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"anatomy/{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"anatomy/{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"anatomy/{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"
valid_answers = []
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"answer {qid}",
}
if testing_cid_user:
post_data["passcode"] = cid_user.passcode
valid_answers.append(post_data)
post_json = {
"eid": exam_json["eid"],
"cid": post_cid,
"start_time": "1659618141.731",
"answers": json.dumps(valid_answers),
}
# 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"] == 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():
try:
new_ans = Answer(
question=question,
answer=question.get_unmarked_user_answers().pop(),
status=Answer.MarkOptions.CORRECT,
)
new_ans.save()
except KeyError: # 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