Basic anatomy user integration testing
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
<pre>{{ans}}</pre>
|
<pre>{{ans}}</pre>
|
||||||
</span>
|
</span>
|
||||||
{% if exam.publish_results %}
|
{% if exam.publish_results %}
|
||||||
{{score}}
|
<span class="answer-score">{{score}}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<span class="view-question-link" data-qn={{forloop.counter0}}>View</span>
|
<span class="view-question-link" data-qn={{forloop.counter0}}>View</span>
|
||||||
|
|||||||
@@ -15,36 +15,37 @@ from bs4 import BeautifulSoup
|
|||||||
|
|
||||||
from anatomy.views import GenericExamViews as AnatomyExamViews
|
from anatomy.views import GenericExamViews as AnatomyExamViews
|
||||||
|
|
||||||
from anatomy.models import Answer, Exam, AnatomyQuestion as Question, QuestionType, Structure
|
from anatomy.models import (
|
||||||
|
Answer,
|
||||||
|
Exam,
|
||||||
|
AnatomyQuestion as Question,
|
||||||
|
QuestionType,
|
||||||
|
Structure,
|
||||||
|
)
|
||||||
|
|
||||||
from generic.models import CidUser, CidUserGroup
|
from generic.models import CidUser, CidUserGroup, UserUserGroup
|
||||||
|
|
||||||
|
from rad.test_helpers import AssertNotFound, create_cid_user_and_groups
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
APP_NAME = "anatomy"
|
APP_NAME = "anatomy"
|
||||||
|
|
||||||
|
|
||||||
def create_cid_user_and_groups(db):
|
|
||||||
group1 = CidUserGroup.objects.create(name="Group1")
|
|
||||||
group2 = CidUserGroup.objects.create(name="Group2")
|
|
||||||
|
|
||||||
cid_user_1000 = CidUser.objects.create(cid=1000, passcode="ABCD", group=group1)
|
|
||||||
cid_user_1001 = CidUser.objects.create(cid=1001, passcode="EFGH", group=group1)
|
|
||||||
cid_user_2001 = CidUser.objects.create(cid=2001, passcode="EFGH", group=group2)
|
|
||||||
|
|
||||||
assert cid_user_1000.cid == 1000
|
|
||||||
assert cid_user_1001.cid == 1001
|
|
||||||
|
|
||||||
|
|
||||||
def create_exam(db):
|
def create_exam(db):
|
||||||
exam: Exam = AnatomyExamViews.Exam.objects.create(
|
exam: Exam = AnatomyExamViews.Exam.objects.create(
|
||||||
name="Cid Exam", exam_mode=True, active=True
|
name="Cid Exam", exam_mode=True, active=True
|
||||||
)
|
)
|
||||||
|
|
||||||
group1: CidUserGroup = CidUserGroup.objects.get(name="Group1")
|
group1: CidUserGroup = CidUserGroup.objects.get(name="Group1")
|
||||||
|
usergroup1: UserUserGroup = UserUserGroup.objects.get(name="Group1")
|
||||||
|
|
||||||
exam.cid_user_groups.add(group1)
|
exam.cid_user_groups.add(group1)
|
||||||
|
|
||||||
exam.valid_cid_users.add(*group1.ciduser_set.all())
|
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 in group1.anatomy_cid_user_groups.all()
|
||||||
|
|
||||||
assert exam
|
assert exam
|
||||||
@@ -85,7 +86,12 @@ def create_question(exam, answer=None):
|
|||||||
answer_text = "testanswer"
|
answer_text = "testanswer"
|
||||||
else:
|
else:
|
||||||
answer_text = answer
|
answer_text = answer
|
||||||
answer = Answer(question=question, answer=answer_text, answer_compare=answer_text, status=Answer.MarkOptions.CORRECT)
|
answer = Answer(
|
||||||
|
question=question,
|
||||||
|
answer=answer_text,
|
||||||
|
answer_compare=answer_text,
|
||||||
|
status=Answer.MarkOptions.CORRECT,
|
||||||
|
)
|
||||||
answer.save()
|
answer.save()
|
||||||
|
|
||||||
return question
|
return question
|
||||||
@@ -99,34 +105,47 @@ def test_exams(db, client):
|
|||||||
create_question_types(db)
|
create_question_types(db)
|
||||||
|
|
||||||
question1 = create_question(exam)
|
question1 = create_question(exam)
|
||||||
create_question(exam)
|
generated_questions = [question1, create_question(exam), create_question(exam)]
|
||||||
create_question(exam)
|
|
||||||
|
|
||||||
active_exams = client.get(reverse(f"{APP_NAME}:active_exams"))
|
active_exams = client.get(reverse(f"{APP_NAME}:active_exams"))
|
||||||
assert active_exams.status_code == 200
|
assert active_exams.status_code == 200
|
||||||
assert len(json.loads(active_exams.content)["exams"]) == 0
|
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_1001 = CidUser.objects.get(cid=1001)
|
||||||
|
cid_user_2001 = CidUser.objects.get(cid=2001)
|
||||||
|
user1 = User.objects.get(username="user1")
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
for cid_user in [cid_user_1001]:
|
|
||||||
# Test active exams sections works as intended
|
# Test active exams sections works as intended
|
||||||
active_exams_cid = client.get(
|
if testing_cid_user:
|
||||||
reverse(
|
active_exams_url = reverse(
|
||||||
f"active_exams_cid",
|
f"active_exams_cid",
|
||||||
kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode},
|
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 active_exams_cid.status_code == 200
|
||||||
assert len(json.loads(active_exams_cid.content)["exams"]) == 1
|
assert len(json.loads(active_exams_cid.content)["exams"]) == 1
|
||||||
|
|
||||||
active_exams_cid = client.get(
|
if testing_cid_user:
|
||||||
reverse(f"active_exams_cid", kwargs={"cid": 1001, "passcode": "EFGH"})
|
|
||||||
)
|
|
||||||
|
|
||||||
assert active_exams_cid.status_code == 200
|
|
||||||
assert len(json.loads(active_exams_cid.content)["exams"]) == 1
|
|
||||||
|
|
||||||
no_active_exams_cid = client.get(
|
no_active_exams_cid = client.get(
|
||||||
reverse(f"active_exams_cid", kwargs={"cid": 2001, "passcode": "EGFH"})
|
reverse(f"active_exams_cid", kwargs={"cid": 2001, "passcode": "EGFH"})
|
||||||
)
|
)
|
||||||
@@ -135,7 +154,10 @@ def test_exams(db, client):
|
|||||||
assert json.loads(no_active_exams_cid.content)["status"] == "invalid"
|
assert json.loads(no_active_exams_cid.content)["status"] == "invalid"
|
||||||
|
|
||||||
invalid_passcode = client.get(
|
invalid_passcode = client.get(
|
||||||
reverse(f"active_exams_cid", kwargs={"cid": 1001, "passcode": "ABCD"})
|
reverse(
|
||||||
|
f"active_exams_cid",
|
||||||
|
kwargs={"cid": cid_user.cid, "passcode": "AAAA"},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
assert invalid_passcode.status_code == 401
|
assert invalid_passcode.status_code == 401
|
||||||
assert json.loads(invalid_passcode.content)["status"] == "invalid"
|
assert json.loads(invalid_passcode.content)["status"] == "invalid"
|
||||||
@@ -166,9 +188,14 @@ def test_exams(db, client):
|
|||||||
exam.active = True
|
exam.active = True
|
||||||
exam.save()
|
exam.save()
|
||||||
|
|
||||||
|
if testing_cid_user:
|
||||||
|
post_cid = cid_user.cid
|
||||||
|
else:
|
||||||
|
post_cid = f"u-{current_user.pk}"
|
||||||
|
|
||||||
post_json = {
|
post_json = {
|
||||||
"eid": f"anatomy/{exam.pk}",
|
"eid": f"anatomy/{exam.pk}",
|
||||||
"cid": cid_user_1001.cid,
|
"cid": post_cid,
|
||||||
"start_time": "1659618141.731",
|
"start_time": "1659618141.731",
|
||||||
"answers": [[]],
|
"answers": [[]],
|
||||||
}
|
}
|
||||||
@@ -184,7 +211,7 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
post_json = {
|
post_json = {
|
||||||
"eid": f"anatomy/{exam.pk+10}",
|
"eid": f"anatomy/{exam.pk+10}",
|
||||||
"cid": cid_user_1001.cid,
|
"cid": post_cid,
|
||||||
"start_time": "1659618141.731",
|
"start_time": "1659618141.731",
|
||||||
"answers": [[]],
|
"answers": [[]],
|
||||||
}
|
}
|
||||||
@@ -208,20 +235,21 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
for questions in exam_json["questions"]:
|
for questions in exam_json["questions"]:
|
||||||
for qid in questions:
|
for qid in questions:
|
||||||
valid_answers.append(
|
post_data = {
|
||||||
{
|
|
||||||
"eid": exam_json["eid"],
|
"eid": exam_json["eid"],
|
||||||
"cid": cid_user_1001.cid,
|
"cid": post_cid,
|
||||||
"passcode": cid_user_1001.passcode,
|
|
||||||
"qid": qid,
|
"qid": qid,
|
||||||
"qidn": "1",
|
"qidn": "1",
|
||||||
"ans": f"answer {qid}",
|
"ans": f"answer {qid}",
|
||||||
}
|
}
|
||||||
)
|
if testing_cid_user:
|
||||||
|
post_data["passcode"] = cid_user.passcode
|
||||||
|
|
||||||
|
valid_answers.append(post_data)
|
||||||
|
|
||||||
post_json = {
|
post_json = {
|
||||||
"eid": exam_json["eid"],
|
"eid": exam_json["eid"],
|
||||||
"cid": cid_user_1001.cid,
|
"cid": post_cid,
|
||||||
"start_time": "1659618141.731",
|
"start_time": "1659618141.731",
|
||||||
"answers": json.dumps(valid_answers),
|
"answers": json.dumps(valid_answers),
|
||||||
}
|
}
|
||||||
@@ -237,66 +265,130 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
# We can then check the answers have been submitted (and can be viewed by the user)
|
# We can then check the answers have been submitted (and can be viewed by the user)
|
||||||
|
|
||||||
# Get overview scores page
|
if testing_cid_user:
|
||||||
cid_scores_res = client.get(
|
# Get overview scores page (invalid passcode)
|
||||||
reverse(f"cid_scores", kwargs={"cid": 1001, "passcode": "EFGH"})
|
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")
|
cid_scores_soup = BeautifulSoup(cid_scores_res.content, "html.parser")
|
||||||
|
|
||||||
print(cid_scores_soup.find("div", {"id" : "exam-assigned"}))
|
search_exam = (
|
||||||
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})
|
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 search_exam
|
||||||
assert exam.name in str(search_exam)
|
assert exam.name in str(search_exam)
|
||||||
assert "Active" in str(search_exam)
|
assert "Active" in str(search_exam)
|
||||||
#assert "Active" in assigned_exams # check it is active
|
# assert "Active" in assigned_exams # check it is active
|
||||||
|
|
||||||
invalid_exam = cid_scores_soup.find_all("li", attrs={"data-exam-id": exam.pk+5})
|
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
|
assert not invalid_exam # Check we can't find an invalid exam
|
||||||
|
|
||||||
# Check that we have a results link
|
# 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})
|
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 results_exam
|
||||||
assert exam.name in str(results_exam)
|
assert exam.name in str(results_exam)
|
||||||
assert exam.name+" test" not 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
|
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
|
# Load the exam results page
|
||||||
cid_exam_scores_res = client.get(
|
cid_exam_scores_res = client.get(exam_scores_url)
|
||||||
reverse(f"{exam.app_name}:exam_scores_cid_user", kwargs={"pk": exam.pk, "cid": 1001, "passcode": "EFGH"})
|
# Load the exam results page
|
||||||
)
|
|
||||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
||||||
|
|
||||||
# Check that we have an alert that exams are not published
|
# Check that we have an alert that exams are not published
|
||||||
alert = cid_exam_scores_soup.find("div", {"class": "alert"})
|
alert = cid_exam_scores_soup.find("div", {"class": "alert"})
|
||||||
assert "Results are not currently published." in str(alert)
|
assert "Results are not currently published." in str(alert)
|
||||||
|
|
||||||
|
answer_lis = cid_exam_scores_soup.find(
|
||||||
answer_lis = cid_exam_scores_soup.find("ul", {"class": "score-answer-list"}).find_all("li")
|
"ul", {"class": "score-answer-list"}
|
||||||
|
).find_all("li")
|
||||||
|
|
||||||
assert len(answer_lis) == len(valid_answers)
|
assert len(answer_lis) == len(valid_answers)
|
||||||
|
|
||||||
for n in range(len(valid_answers)): # Check no answers are visible if results not published
|
for n in range(
|
||||||
|
len(valid_answers)
|
||||||
|
): # Check no answers are visible if results not published
|
||||||
answer = valid_answers[n]
|
answer = valid_answers[n]
|
||||||
li = answer_lis[n]
|
li = answer_lis[n]
|
||||||
|
|
||||||
assert answer["ans"] in str(li) # Check that the saved answer is shown
|
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 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
|
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
|
# Add another question to the exam
|
||||||
create_question(exam)
|
create_question(exam)
|
||||||
|
|
||||||
# Load the exam results page (again)
|
# Load the exam results page (again)
|
||||||
cid_exam_scores_res = client.get(
|
cid_exam_scores_res = client.get(exam_scores_url)
|
||||||
reverse(f"{exam.app_name}:exam_scores_cid_user", kwargs={"pk": exam.pk, "cid": 1001, "passcode": "EFGH"})
|
|
||||||
)
|
|
||||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
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")
|
answer_lis = cid_exam_scores_soup.find(
|
||||||
|
"ul", {"class": "score-answer-list"}
|
||||||
|
).find_all("li")
|
||||||
assert len(answer_lis) == len(valid_answers) + 1
|
assert len(answer_lis) == len(valid_answers) + 1
|
||||||
assert "Not answered" in str(answer_lis[-1])
|
assert "Not answered" in str(answer_lis[-1])
|
||||||
|
|
||||||
@@ -305,20 +397,24 @@ def test_exams(db, client):
|
|||||||
exam.save()
|
exam.save()
|
||||||
|
|
||||||
# Load the exam results page (again)
|
# Load the exam results page (again)
|
||||||
cid_exam_scores_res = client.get(
|
cid_exam_scores_res = client.get(exam_scores_url)
|
||||||
reverse(f"{exam.app_name}:exam_scores_cid_user", kwargs={"pk": exam.pk, "cid": 1001, "passcode": "EFGH"})
|
|
||||||
)
|
|
||||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
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
|
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")
|
answer_lis = cid_exam_scores_soup.find(
|
||||||
|
"ul", {"class": "score-answer-list"}
|
||||||
|
).find_all("li")
|
||||||
|
|
||||||
assert len(answer_lis) == len(valid_answers) + 1
|
assert len(answer_lis) == len(valid_answers) + 1
|
||||||
assert "Not answered" in str(answer_lis[-1])
|
assert "Not answered" in str(answer_lis[-1])
|
||||||
|
|
||||||
# Repetition....
|
# Repetition....
|
||||||
for n in range(len(valid_answers)): # Check answers are visible if results published
|
for n in range(
|
||||||
|
len(valid_answers)
|
||||||
|
): # Check answers are visible if results published
|
||||||
answer = valid_answers[n]
|
answer = valid_answers[n]
|
||||||
li = answer_lis[n]
|
li = answer_lis[n]
|
||||||
|
|
||||||
@@ -326,20 +422,45 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
assert li.find("span", {"class": "correct-answer"}).string == "testanswer"
|
assert li.find("span", {"class": "correct-answer"}).string == "testanswer"
|
||||||
|
|
||||||
assert cid_exam_scores_soup.find("div", {"class": "score-overview"}).find("span", {"id": "total-score"}).string == "0 (3 unmarked)"
|
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():
|
for question in exam.exam_questions.all():
|
||||||
try:
|
try:
|
||||||
new_ans = Answer(question=question, answer=question.get_unmarked_user_answers().pop(), status=Answer.MarkOptions.CORRECT)
|
new_ans = Answer(
|
||||||
|
question=question,
|
||||||
|
answer=question.get_unmarked_user_answers().pop(),
|
||||||
|
status=Answer.MarkOptions.CORRECT,
|
||||||
|
)
|
||||||
new_ans.save()
|
new_ans.save()
|
||||||
except KeyError: # Final question does not have an answer
|
except KeyError: # Final question does not have an answer
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Load the exam results page (again)
|
# Load the exam results page (again)
|
||||||
cid_exam_scores_res = client.get(
|
cid_exam_scores_res = client.get(exam_scores_url)
|
||||||
reverse(f"{exam.app_name}:exam_scores_cid_user", kwargs={"pk": exam.pk, "cid": 1001, "passcode": "EFGH"})
|
|
||||||
)
|
|
||||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
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"
|
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
|
||||||
|
|||||||
+2
-1
@@ -2,6 +2,7 @@ from django.urls import path, include
|
|||||||
|
|
||||||
from generic.models import Examination
|
from generic.models import Examination
|
||||||
from . import views
|
from . import views
|
||||||
|
from generic.views import ExamViews as GenericExamViews
|
||||||
|
|
||||||
|
|
||||||
app_name = "generic"
|
app_name = "generic"
|
||||||
@@ -131,7 +132,7 @@ def generic_view_urls(generic_views):
|
|||||||
return urlpatterns
|
return urlpatterns
|
||||||
|
|
||||||
|
|
||||||
def generic_exam_urls(generic_exam_view):
|
def generic_exam_urls(generic_exam_view: GenericExamViews):
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", generic_exam_view.index, name="index"),
|
path("", generic_exam_view.index, name="index"),
|
||||||
path("exam/<int:pk>/", generic_exam_view.exam_overview, name="exam_overview"),
|
path("exam/<int:pk>/", generic_exam_view.exam_overview, name="exam_overview"),
|
||||||
|
|||||||
+7
-4
@@ -58,7 +58,7 @@ from .forms import (
|
|||||||
UserUserGroupForm,
|
UserUserGroupForm,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .models import CidUser, CidUserGroup, Examination, QuestionNote, Supervisor, UserGrades, UserProfile, UserUserGroup, get_next_cid
|
from .models import CidUser, CidUserGroup, ExamBase, Examination, QuestionNote, Supervisor, UserGrades, UserProfile, UserUserGroup, get_next_cid
|
||||||
|
|
||||||
from rapids.models import Rapid as RapidQuestion
|
from rapids.models import Rapid as RapidQuestion
|
||||||
from rapids.models import Exam as RapidExam
|
from rapids.models import Exam as RapidExam
|
||||||
@@ -1319,9 +1319,11 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
|
|
||||||
eid = int(answer["eid"].split("/")[1])
|
eid = int(answer["eid"].split("/")[1])
|
||||||
uid = False
|
uid = False
|
||||||
|
passcode = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cid = int(answer["cid"])
|
cid = int(answer["cid"])
|
||||||
|
passcode = answer["passcode"]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if answer["cid"].startswith("u-"):
|
if answer["cid"].startswith("u-"):
|
||||||
cid = False
|
cid = False
|
||||||
@@ -1352,9 +1354,9 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
exam = get_object_or_404(self.Exam, pk=eid)
|
exam: ExamBase = get_object_or_404(self.Exam, pk=eid)
|
||||||
|
|
||||||
if not exam.check_cid_user(cid, answer["passcode"], user_id=uid):
|
if not exam.check_cid_user(cid, passcode, user_id=uid):
|
||||||
return JsonResponse(
|
return JsonResponse(
|
||||||
{"success": False, "error": "invalid cid / passcode"}
|
{"success": False, "error": "invalid cid / passcode"}
|
||||||
)
|
)
|
||||||
@@ -1597,7 +1599,8 @@ class ExamViews(View, LoginRequiredMixin):
|
|||||||
if not exam.exam_mode:
|
if not exam.exam_mode:
|
||||||
raise Http404("Packet not in exam mode")
|
raise Http404("Packet not in exam mode")
|
||||||
|
|
||||||
# TODO:Need some kind of test for cid
|
if cid is not None and not exam.check_cid_user(cid, passcode, request):
|
||||||
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
questions = (
|
questions = (
|
||||||
exam.exam_questions.all()
|
exam.exam_questions.all()
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from physics.views import GenericExamViews as PhysicsExamViews
|
|||||||
from physics.models import Category, CidUserAnswer, Exam, Question
|
from physics.models import Category, CidUserAnswer, Exam, Question
|
||||||
|
|
||||||
from generic.models import CidUser, CidUserGroup, UserUserGroup
|
from generic.models import CidUser, CidUserGroup, UserUserGroup
|
||||||
from rad.test_helpers import AssertNotFound
|
from rad.test_helpers import AssertNotFound, create_cid_user_and_groups
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
@@ -29,23 +29,6 @@ APP_NAME = "physics"
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_cid_user_and_groups(db):
|
|
||||||
group1 = CidUserGroup.objects.create(name="Group1")
|
|
||||||
group2 = CidUserGroup.objects.create(name="Group2")
|
|
||||||
|
|
||||||
cid_user_1000 = CidUser.objects.create(cid=1000, passcode="ABCD", group=group1)
|
|
||||||
cid_user_1001 = CidUser.objects.create(cid=1001, passcode="EFGH", group=group1)
|
|
||||||
cid_user_2001 = CidUser.objects.create(cid=2001, passcode="EFGH", group=group2)
|
|
||||||
|
|
||||||
assert cid_user_1000.cid == 1000
|
|
||||||
assert cid_user_1001.cid == 1001
|
|
||||||
|
|
||||||
# Create UserUsers
|
|
||||||
user1 = User.objects.create_user(username="user1", email="test@email.com", password="test1234")
|
|
||||||
|
|
||||||
usergroup1 = UserUserGroup.objects.create(name="Group1")
|
|
||||||
usergroup1.users.add(user1)
|
|
||||||
usergroup2 = UserUserGroup.objects.create(name="Group2")
|
|
||||||
|
|
||||||
|
|
||||||
def create_exam(db):
|
def create_exam(db):
|
||||||
@@ -122,7 +105,7 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
create_category(db)
|
create_category(db)
|
||||||
|
|
||||||
questions = [create_question(exam), create_question(exam), create_question(exam)]
|
generated_questions = [create_question(exam), create_question(exam), create_question(exam)]
|
||||||
|
|
||||||
active_exams = client.get(reverse(f"{APP_NAME}:active_exams"))
|
active_exams = client.get(reverse(f"{APP_NAME}:active_exams"))
|
||||||
assert active_exams.status_code == 200
|
assert active_exams.status_code == 200
|
||||||
@@ -150,7 +133,6 @@ def test_exams(db, client):
|
|||||||
exam.active = True
|
exam.active = True
|
||||||
exam.publish_results = False
|
exam.publish_results = False
|
||||||
exam.save()
|
exam.save()
|
||||||
# Test active exams sections works as intended
|
|
||||||
start_page_res = client.get(
|
start_page_res = client.get(
|
||||||
reverse(f"physics:exam_start", kwargs={"pk": exam.pk})
|
reverse(f"physics:exam_start", kwargs={"pk": exam.pk})
|
||||||
)
|
)
|
||||||
@@ -161,7 +143,7 @@ def test_exams(db, client):
|
|||||||
else:
|
else:
|
||||||
assert "Start Exam" in str(start_page_soup)
|
assert "Start Exam" in str(start_page_soup)
|
||||||
|
|
||||||
for n, question in enumerate(questions):
|
for n, question in enumerate(generated_questions):
|
||||||
if testing_cid_user:
|
if testing_cid_user:
|
||||||
take_url = reverse(
|
take_url = reverse(
|
||||||
f"physics:exam_take",
|
f"physics:exam_take",
|
||||||
@@ -203,7 +185,7 @@ def test_exams(db, client):
|
|||||||
# Check that relevant buttons are being displayed
|
# Check that relevant buttons are being displayed
|
||||||
assert cid_take_soup.find("button", {"name": "finish"}) is not None
|
assert cid_take_soup.find("button", {"name": "finish"}) is not None
|
||||||
|
|
||||||
if n < len(questions) - 1:
|
if n < len(generated_questions) - 1:
|
||||||
assert cid_take_soup.find("button", {"name": "next"}) is not None
|
assert cid_take_soup.find("button", {"name": "next"}) is not None
|
||||||
|
|
||||||
if n > 0:
|
if n > 0:
|
||||||
@@ -213,7 +195,7 @@ def test_exams(db, client):
|
|||||||
n + 1
|
n + 1
|
||||||
) # + 1 for 0 offset
|
) # + 1 for 0 offset
|
||||||
assert cid_take_soup.find("span", {"id": "exam-length"}).string == str(
|
assert cid_take_soup.find("span", {"id": "exam-length"}).string == str(
|
||||||
len(questions)
|
len(generated_questions)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check that stem text is displayed
|
# Check that stem text is displayed
|
||||||
@@ -276,8 +258,8 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
# Loop through questions again to test submissions
|
# Loop through questions again to test submissions
|
||||||
# All physics questions have 5 parts (and require a csrf token)
|
# All physics questions have 5 parts (and require a csrf token)
|
||||||
questions_len = len(questions) - 1
|
questions_len = len(generated_questions) - 1
|
||||||
for n, question in enumerate(questions):
|
for n, question in enumerate(generated_questions):
|
||||||
if testing_cid_user:
|
if testing_cid_user:
|
||||||
question_url = reverse(
|
question_url = reverse(
|
||||||
f"physics:exam_take",
|
f"physics:exam_take",
|
||||||
@@ -388,8 +370,8 @@ def test_exams(db, client):
|
|||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get overview scores page (invalid passcode)
|
|
||||||
if testing_cid_user:
|
if testing_cid_user:
|
||||||
|
# Get overview scores page (invalid passcode)
|
||||||
AssertNotFound(client,
|
AssertNotFound(client,
|
||||||
reverse(f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": "AAAA"})
|
reverse(f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": "AAAA"})
|
||||||
)
|
)
|
||||||
@@ -468,12 +450,12 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
# Check that we are showing enough questions / answers
|
# Check that we are showing enough questions / answers
|
||||||
answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"})
|
answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"})
|
||||||
assert len(answer_lis) == 5 * len(questions)
|
assert len(answer_lis) == 5 * len(generated_questions)
|
||||||
|
|
||||||
submitted_answers = cid_exam_scores_soup.find_all(
|
submitted_answers = cid_exam_scores_soup.find_all(
|
||||||
"span", {"class": "submitted-user-answer"}
|
"span", {"class": "submitted-user-answer"}
|
||||||
)
|
)
|
||||||
assert len(submitted_answers) == 5 * len(questions)
|
assert len(submitted_answers) == 5 * len(generated_questions)
|
||||||
|
|
||||||
for ans in submitted_answers:
|
for ans in submitted_answers:
|
||||||
assert ans.text.strip() == "True"
|
assert ans.text.strip() == "True"
|
||||||
@@ -483,7 +465,7 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
# Add another question to the exam
|
# Add another question to the exam
|
||||||
extra_question = create_question(exam)
|
extra_question = create_question(exam)
|
||||||
questions.append(extra_question)
|
generated_questions.append(extra_question)
|
||||||
|
|
||||||
# Load the exam results page (again)
|
# Load the exam results page (again)
|
||||||
cid_exam_scores_res = client.get(
|
cid_exam_scores_res = client.get(
|
||||||
@@ -492,12 +474,12 @@ def test_exams(db, client):
|
|||||||
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
||||||
|
|
||||||
answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"})
|
answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"})
|
||||||
assert len(answer_lis) == 5 * len(questions)
|
assert len(answer_lis) == 5 * len(generated_questions)
|
||||||
|
|
||||||
submitted_answers = cid_exam_scores_soup.find_all(
|
submitted_answers = cid_exam_scores_soup.find_all(
|
||||||
"span", {"class": "submitted-user-answer"}
|
"span", {"class": "submitted-user-answer"}
|
||||||
)
|
)
|
||||||
assert len(submitted_answers) == 5 * len(questions)
|
assert len(submitted_answers) == 5 * len(generated_questions)
|
||||||
|
|
||||||
for ans in submitted_answers[:-5]:
|
for ans in submitted_answers[:-5]:
|
||||||
assert ans.text.strip() == "True"
|
assert ans.text.strip() == "True"
|
||||||
@@ -524,10 +506,10 @@ def test_exams(db, client):
|
|||||||
correct_answers = cid_exam_scores_soup.find_all(
|
correct_answers = cid_exam_scores_soup.find_all(
|
||||||
"span", {"class": "correct-answer"}
|
"span", {"class": "correct-answer"}
|
||||||
)
|
)
|
||||||
assert len(correct_answers) == 5 * len(questions)
|
assert len(correct_answers) == 5 * len(generated_questions)
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
for question in questions:
|
for question in generated_questions:
|
||||||
for ans in question.get_answers():
|
for ans in question.get_answers():
|
||||||
assert correct_answers[n].text.strip() == f"Correct answer: {ans}"
|
assert correct_answers[n].text.strip() == f"Correct answer: {ans}"
|
||||||
n = n + 1
|
n = n + 1
|
||||||
@@ -535,7 +517,7 @@ def test_exams(db, client):
|
|||||||
submitted_answers = cid_exam_scores_soup.find_all(
|
submitted_answers = cid_exam_scores_soup.find_all(
|
||||||
"span", {"class": "submitted-user-answer"}
|
"span", {"class": "submitted-user-answer"}
|
||||||
)
|
)
|
||||||
assert len(submitted_answers) == 5 * len(questions)
|
assert len(submitted_answers) == 5 * len(generated_questions)
|
||||||
|
|
||||||
for ans in submitted_answers[:-5]:
|
for ans in submitted_answers[:-5]:
|
||||||
assert ans.text.strip() == "True"
|
assert ans.text.strip() == "True"
|
||||||
@@ -613,5 +595,5 @@ def test_exams(db, client):
|
|||||||
cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser")
|
cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser")
|
||||||
assert "Exam is currently inactive" in cid_take_soup.text
|
assert "Exam is currently inactive" in cid_take_soup.text
|
||||||
|
|
||||||
questions.pop(-1).delete()
|
generated_questions.pop(-1).delete()
|
||||||
users_tested = users_tested + 1
|
users_tested = users_tested + 1
|
||||||
@@ -102,6 +102,9 @@ def active_exams(request):
|
|||||||
def exam_scores_cid_user(request, pk, cid=None, passcode=None):
|
def exam_scores_cid_user(request, pk, cid=None, passcode=None):
|
||||||
exam = get_object_or_404(Exam, pk=pk)
|
exam = get_object_or_404(Exam, pk=pk)
|
||||||
|
|
||||||
|
if not exam.exam_mode:
|
||||||
|
raise Http404("Packet not in exam mode")
|
||||||
|
|
||||||
if cid is not None and not exam.check_cid_user(cid, passcode, request):
|
if cid is not None and not exam.check_cid_user(cid, passcode, request):
|
||||||
raise Http404("Error accessing exam")
|
raise Http404("Error accessing exam")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,27 @@
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from generic.models import CidUser, CidUserGroup, UserUserGroup
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
def AssertNotFound(client, url):
|
def AssertNotFound(client, url):
|
||||||
"""Helper to quickly test for urls that should return a 404 (NOT FOUND) error"""
|
"""Helper to quickly test for urls that should return a 404 (NOT FOUND) error"""
|
||||||
response = client.get(url)
|
response = client.get(url)
|
||||||
assert response.status_code == HTTPStatus.NOT_FOUND, f"Response content: {response.content}"
|
assert response.status_code == HTTPStatus.NOT_FOUND, f"Response content: {response.content}"
|
||||||
|
|
||||||
|
|
||||||
|
def create_cid_user_and_groups(db):
|
||||||
|
group1 = CidUserGroup.objects.create(name="Group1")
|
||||||
|
group2 = CidUserGroup.objects.create(name="Group2")
|
||||||
|
|
||||||
|
cid_user_1000 = CidUser.objects.create(cid=1000, passcode="ABCD", group=group1)
|
||||||
|
cid_user_1001 = CidUser.objects.create(cid=1001, passcode="EFGH", group=group1)
|
||||||
|
cid_user_2001 = CidUser.objects.create(cid=2001, passcode="EFGH", group=group2)
|
||||||
|
|
||||||
|
assert cid_user_1000.cid == 1000
|
||||||
|
assert cid_user_1001.cid == 1001
|
||||||
|
|
||||||
|
# Create UserUsers
|
||||||
|
user1 = User.objects.create_user(username="user1", email="test@email.com", password="test1234")
|
||||||
|
usergroup1 = UserUserGroup.objects.create(name="Group1")
|
||||||
|
usergroup1.users.add(user1)
|
||||||
|
usergroup2 = UserUserGroup.objects.create(name="Group2")
|
||||||
Reference in New Issue
Block a user