Further basic integration testing
This commit is contained in:
@@ -16,10 +16,15 @@
|
|||||||
{% include 'user_score_header.html' %}
|
{% include 'user_score_header.html' %}
|
||||||
<ul class="score-answer-list">
|
<ul class="score-answer-list">
|
||||||
{% for ans, score, correct_answer in answers_and_marks %}
|
{% for ans, score, correct_answer in answers_and_marks %}
|
||||||
<li class="user-answer-li" data-question-number="{{forloop.counter}}">Question {{forloop.counter}} - Correct answer: <span class="correct-answer">{{correct_answer}}</span>
|
<li class="user-answer-li" data-question-number="{{forloop.counter}}">Question {{forloop.counter}} - Correct answer: {% if exam.publish_results %}<span class="correct-answer">{{correct_answer}}</span>{% endif %}
|
||||||
<br/>
|
<br/>
|
||||||
<span class="user-answer-score user-answer-score-{{score}} physics-ans">
|
<span class="user-answer-score user-answer-score-{{score}}">
|
||||||
<pre>{{ans}}</pre> ({{score}})
|
<span class="submitted-user-answer">
|
||||||
|
<pre>{{ans}}</pre>
|
||||||
|
</span>
|
||||||
|
{% if exam.publish_results %}
|
||||||
|
{{score}}
|
||||||
|
{% 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>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ def test_exams(db, client):
|
|||||||
|
|
||||||
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 li.find("span", {"class": "correct-answer"}).string == "*****" # 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
|
||||||
|
|
||||||
@@ -5,13 +5,22 @@
|
|||||||
{% include 'user_score_header.html' %}
|
{% include 'user_score_header.html' %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for question, ans in answers_and_marks %}
|
{% for question, ans in answers_and_marks %}
|
||||||
<li class="user-answer-li"><span class="question-stem-text">Question {{forloop.counter}} - {{ question.stem|safe }}</span></li>
|
<li class="user-answer-li"><span class="question-stem-text">Question {{forloop.counter}} - {{ question.stem|safe }}</span>
|
||||||
<ol type="a">
|
<ol type="a">
|
||||||
{% for q, a, score, correct_answer in ans %}
|
{% for q, a, score, correct_answer in ans %}
|
||||||
<li><span class="question-text">{{q|safe}}:</span> Correct answer: {{correct_answer}} <br /><span class="submitted-user-answer">{{a}} <span class="answer-{{score}}">(Score:
|
<li class="question-part">
|
||||||
{{score}})</span></span></li>
|
<span class="question-text">{{q|safe}}:</span> {% if exam.publish_results %}<span class="correct-answer">Correct answer: {{correct_answer}}</span>{% endif %}
|
||||||
|
<br />
|
||||||
|
<span class="submitted-user-answer">{{a}}</span>
|
||||||
|
{% if exam.publish_results %}
|
||||||
|
<span class="answer-{{score}}">
|
||||||
|
(Score: {{score}})
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ol>
|
</ol>
|
||||||
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% include 'user_scores_footer.html' %}
|
{% include 'user_scores_footer.html' %}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<span id="user-id">
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
User: {{request.user}}
|
User: {{request.user}}
|
||||||
{% else %}
|
{% else %}
|
||||||
CID: {{cid}}
|
CID: {{cid}}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</span>
|
||||||
<div id="timeup">
|
<div id="timeup">
|
||||||
Allocated time over.
|
Allocated time over.
|
||||||
</div>
|
</div>
|
||||||
@@ -29,15 +31,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="no-select">
|
<div class="no-select">
|
||||||
<h2>{{exam.name}}: Question [{{pos|add:1}}/{{exam_length}}]</h2>
|
<h2>{{exam.name}}: Question [<span id="question-number">{{pos|add:1}}</span>/<span id="exam-length">{{exam_length}}</span>]</h2>
|
||||||
{% if exam.publish_results %}
|
{% if exam.publish_results %}
|
||||||
<div class="alert alert-primary" role="alert">
|
<div class="alert alert-primary review-mode-alert" role="alert">
|
||||||
Exam is in review mode. Add question feedback <a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='physics' pk=question.pk %}')">here</a>.
|
Exam is in review mode. Add question feedback <a href="#" onclick="return window.create_popup_window('{% url 'feedback_create' question_type='physics' pk=question.pk %}')">here</a>.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>{{question.stem|safe}}</p>
|
<p><span id="question-stem">{{question.stem|safe}}</span></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -50,35 +52,35 @@
|
|||||||
<li data-ans="a">
|
<li data-ans="a">
|
||||||
<div class="fieldWrapper flex-container">
|
<div class="fieldWrapper flex-container">
|
||||||
{{ form.a.errors }}
|
{{ form.a.errors }}
|
||||||
<label for="{{ form.a.id_for_label }}" class="flex-8">{{question.a|safe}}</label>
|
<label for="{{ form.a.id_for_label }}" class="flex-8 question-text">{{question.a|safe}}</label>
|
||||||
<span class="flex-1">{{ form.a }}<span class="postinput"></span></span>
|
<span class="flex-1">{{ form.a }}<span class="postinput"></span></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li data-ans="b">
|
<li data-ans="b">
|
||||||
<div class="fieldWrapper flex-container">
|
<div class="fieldWrapper flex-container">
|
||||||
{{ form.b.errors }}
|
{{ form.b.errors }}
|
||||||
<label for="{{ form.b.id_for_label }}" class="flex-8">{{question.b|safe}}</label>
|
<label for="{{ form.b.id_for_label }}" class="flex-8 question-text">{{question.b|safe}}</label>
|
||||||
<span class="flex-1">{{ form.b }}<span class="postinput"></span></span>
|
<span class="flex-1">{{ form.b }}<span class="postinput"></span></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li data-ans="c">
|
<li data-ans="c">
|
||||||
<div class="fieldWrapper flex-container">
|
<div class="fieldWrapper flex-container">
|
||||||
{{ form.c.errors }}
|
{{ form.c.errors }}
|
||||||
<label for="{{ form.c.id_for_label }}" class="flex-8">{{question.c|safe}}</label>
|
<label for="{{ form.c.id_for_label }}" class="flex-8 question-text">{{question.c|safe}}</label>
|
||||||
<span class="flex-1">{{ form.c }}<span class="postinput"></span></span>
|
<span class="flex-1">{{ form.c }}<span class="postinput"></span></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li data-ans="d">
|
<li data-ans="d">
|
||||||
<div class="fieldWrapper flex-container">
|
<div class="fieldWrapper flex-container">
|
||||||
{{ form.d.errors }}
|
{{ form.d.errors }}
|
||||||
<label for="{{ form.d.id_for_label }}" class="flex-8">{{question.d|safe}}</label>
|
<label for="{{ form.d.id_for_label }}" class="flex-8 question-text">{{question.d|safe}}</label>
|
||||||
<span class="flex-1">{{ form.d }}<span class="postinput"></span></span>
|
<span class="flex-1">{{ form.d }}<span class="postinput"></span></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li data-ans="e">
|
<li data-ans="e">
|
||||||
<div class="fieldWrapper flex-container">
|
<div class="fieldWrapper flex-container">
|
||||||
{{ form.e.errors }}
|
{{ form.e.errors }}
|
||||||
<label for="{{ form.e.id_for_label }}" class="flex-8">{{question.e|safe}}</label>
|
<label for="{{ form.e.id_for_label }}" class="flex-8 question-text">{{question.e|safe}}</label>
|
||||||
<span class="flex-1">{{ form.e }}<span class="postinput"></span></span>
|
<span class="flex-1">{{ form.e }}<span class="postinput"></span></span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -118,14 +120,6 @@
|
|||||||
$(document).ready(() => {
|
$(document).ready(() => {
|
||||||
/* beautify ignore:start */
|
/* beautify ignore:start */
|
||||||
{% if not exam.publish_results %}
|
{% if not exam.publish_results %}
|
||||||
//$("ul.physics-answer-list li").each((n, el) => {
|
|
||||||
// $(el).click((e) => {
|
|
||||||
// console.log(1, el.dataset.ans);
|
|
||||||
// $("#id_answer").val(el.dataset.ans);
|
|
||||||
// $("ul.physics-answer-list li").removeClass("selected");
|
|
||||||
// $(el).addClass("selected")
|
|
||||||
// })
|
|
||||||
//})
|
|
||||||
|
|
||||||
let time_limit = '{{exam.time_limit}}'
|
let time_limit = '{{exam.time_limit}}'
|
||||||
if (time_limit != "None") {
|
if (time_limit != "None") {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
{% endif %} {% endcomment %}
|
{% endif %} {% endcomment %}
|
||||||
|
|
||||||
<div><p>Questions</p></div>
|
<div><p>Questions</p></div>
|
||||||
{{answer_count}} out of {{exam_length}} questions answered. Click to go to question.
|
<div class="overview-text">{{answer_count}} out of {{exam_length}} questions answered. Click to go to question.</div>
|
||||||
<div class="physics-finish-list">
|
<div class="physics-finish-list">
|
||||||
{% for question, answer in question_answer_tuples %}
|
{% for question, answer in question_answer_tuples %}
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
|
|||||||
@@ -0,0 +1,513 @@
|
|||||||
|
from http import HTTPStatus
|
||||||
|
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 physics.views import GenericExamViews as PhysicsExamViews
|
||||||
|
|
||||||
|
from physics.models import Category, CidUserAnswer, Exam, Question
|
||||||
|
|
||||||
|
from generic.models import CidUser, CidUserGroup
|
||||||
|
from rad.test_helpers import AssertNotFound
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def create_exam(db):
|
||||||
|
exam: Exam = PhysicsExamViews.Exam.objects.create(
|
||||||
|
name="Cid Exam", exam_mode=True, active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
group1: CidUserGroup = CidUserGroup.objects.get(name="Group1")
|
||||||
|
|
||||||
|
exam.cid_user_groups.add(group1)
|
||||||
|
|
||||||
|
exam.valid_cid_users.add(*group1.ciduser_set.all())
|
||||||
|
|
||||||
|
assert exam in group1.physics_cid_user_groups.all()
|
||||||
|
|
||||||
|
assert exam
|
||||||
|
|
||||||
|
return exam
|
||||||
|
|
||||||
|
|
||||||
|
def create_category(db):
|
||||||
|
category = Category.objects.create(category="test category")
|
||||||
|
category.save()
|
||||||
|
category = Category.objects.create(category="test category 2")
|
||||||
|
category.save()
|
||||||
|
|
||||||
|
|
||||||
|
def create_question(exam, answer=None):
|
||||||
|
# Just assign random stuff
|
||||||
|
|
||||||
|
if answer is None:
|
||||||
|
answers = {
|
||||||
|
"a_answer": True,
|
||||||
|
"b_answer": False,
|
||||||
|
"c_answer": True,
|
||||||
|
"d_answer": False,
|
||||||
|
"e_answer": True,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
answers = {
|
||||||
|
"a_answer": answer[0],
|
||||||
|
"b_answer": answer[1],
|
||||||
|
"c_answer": answer[2],
|
||||||
|
"d_answer": answer[3],
|
||||||
|
"e_answer": answer[4],
|
||||||
|
}
|
||||||
|
|
||||||
|
question: Question = Question.objects.create(
|
||||||
|
stem="test question stem",
|
||||||
|
a="question a text",
|
||||||
|
a_feedback="question a feedback",
|
||||||
|
b="question b text",
|
||||||
|
b_feedback="question b feedback",
|
||||||
|
c="question c text",
|
||||||
|
c_feedback="question c feedback",
|
||||||
|
d="question d text",
|
||||||
|
d_feedback="question d feedback",
|
||||||
|
e="question e text",
|
||||||
|
e_feedback="question e feedback",
|
||||||
|
**answers,
|
||||||
|
)
|
||||||
|
question.exams.set([exam])
|
||||||
|
|
||||||
|
return question
|
||||||
|
|
||||||
|
|
||||||
|
def test_exams(db, client):
|
||||||
|
create_cid_user_and_groups(db)
|
||||||
|
exam = create_exam(db)
|
||||||
|
|
||||||
|
create_category(db)
|
||||||
|
|
||||||
|
questions = [create_question(exam), 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_1001 = CidUser.objects.get(cid=1001)
|
||||||
|
cid_user_1000 = CidUser.objects.get(cid=1000)
|
||||||
|
|
||||||
|
for cid_user in [cid_user_1001, cid_user_1000]:
|
||||||
|
exam.active = True
|
||||||
|
exam.publish_results = False
|
||||||
|
exam.save()
|
||||||
|
# Test active exams sections works as intended
|
||||||
|
start_page_res = client.get(
|
||||||
|
reverse(f"physics:exam_start", kwargs={"pk": exam.pk})
|
||||||
|
)
|
||||||
|
start_page_soup = BeautifulSoup(start_page_res.content, "html.parser")
|
||||||
|
assert "Enter your CID and passcode" in str(start_page_soup)
|
||||||
|
|
||||||
|
for n, question in enumerate(questions):
|
||||||
|
cid_take_res = client.get(
|
||||||
|
reverse(
|
||||||
|
f"physics:exam_take",
|
||||||
|
kwargs={
|
||||||
|
"pk": exam.pk,
|
||||||
|
"sk": n,
|
||||||
|
"cid": cid_user.cid,
|
||||||
|
"passcode": cid_user.passcode,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser")
|
||||||
|
|
||||||
|
# Check correct user is displayed
|
||||||
|
assert (
|
||||||
|
str(cid_user.cid)
|
||||||
|
in cid_take_soup.find("span", {"id": "user-id"}).string
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check review mode alert not visible
|
||||||
|
assert cid_take_soup.find("div", {"class": "review-mode-alert"}) is None
|
||||||
|
|
||||||
|
# Check that relevant buttons are being displayed
|
||||||
|
assert cid_take_soup.find("button", {"name": "finish"}) is not None
|
||||||
|
|
||||||
|
if n < len(questions) - 1:
|
||||||
|
assert cid_take_soup.find("button", {"name": "next"}) is not None
|
||||||
|
|
||||||
|
if n > 0:
|
||||||
|
assert cid_take_soup.find("button", {"name": "previous"}) is not None
|
||||||
|
|
||||||
|
assert cid_take_soup.find("span", {"id": "question-number"}).string == str(
|
||||||
|
n + 1
|
||||||
|
) # + 1 for 0 offset
|
||||||
|
assert cid_take_soup.find("span", {"id": "exam-length"}).string == str(
|
||||||
|
len(questions)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that stem text is displayed
|
||||||
|
assert (
|
||||||
|
cid_take_soup.find("span", {"id": "question-stem"}).string
|
||||||
|
== question.stem
|
||||||
|
)
|
||||||
|
|
||||||
|
assert cid_take_soup.find("label", {"for": "id_a"}).string == question.a
|
||||||
|
assert cid_take_soup.find("label", {"for": "id_b"}).string == question.b
|
||||||
|
assert cid_take_soup.find("label", {"for": "id_c"}).string == question.c
|
||||||
|
assert cid_take_soup.find("label", {"for": "id_d"}).string == question.d
|
||||||
|
assert cid_take_soup.find("label", {"for": "id_e"}).string == question.e
|
||||||
|
|
||||||
|
# As we haven't submitted any answers they should all be blank
|
||||||
|
assert (
|
||||||
|
cid_take_soup.find("input", {"type": "checkbox", "checked": True})
|
||||||
|
is None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
cid_take_overview_res = client.get(
|
||||||
|
reverse(
|
||||||
|
f"physics:exam_take_overview",
|
||||||
|
kwargs={
|
||||||
|
"pk": exam.pk,
|
||||||
|
"cid": cid_user.cid,
|
||||||
|
"passcode": cid_user.passcode,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cid_take_overview_soup = BeautifulSoup(
|
||||||
|
cid_take_overview_res.content, "html.parser"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"0 out of 3 questions answered"
|
||||||
|
in cid_take_overview_soup.find(
|
||||||
|
"div", attrs={"class": "overview-text"}
|
||||||
|
).string
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(
|
||||||
|
cid_take_overview_soup.find_all("button", attrs={"class": "unanswered"})
|
||||||
|
)
|
||||||
|
== 3
|
||||||
|
)
|
||||||
|
|
||||||
|
# Loop through questions again to test submissions
|
||||||
|
# All physics questions have 5 parts (and require a csrf token)
|
||||||
|
questions_len = len(questions) - 1
|
||||||
|
for n, question in enumerate(questions):
|
||||||
|
question_url = reverse(
|
||||||
|
f"physics:exam_take",
|
||||||
|
kwargs={
|
||||||
|
"pk": exam.pk,
|
||||||
|
"sk": n,
|
||||||
|
"cid": cid_user.cid,
|
||||||
|
"passcode": cid_user.passcode,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
cid_take_res = client.get(question_url)
|
||||||
|
cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser")
|
||||||
|
|
||||||
|
# Extract csrftoken
|
||||||
|
csrftoken = cid_take_soup.find(
|
||||||
|
"input", attrs={"name": "csrfmiddlewaretoken"}
|
||||||
|
)["value"]
|
||||||
|
# Send answers (all TRUE)
|
||||||
|
post_json = {
|
||||||
|
"csrfmiddlewaretoken": csrftoken,
|
||||||
|
"a": "on",
|
||||||
|
"b": "on",
|
||||||
|
"c": "on",
|
||||||
|
"d": "on",
|
||||||
|
"e": "on",
|
||||||
|
"next": "",
|
||||||
|
}
|
||||||
|
res = client.post(question_url, data=post_json)
|
||||||
|
|
||||||
|
next_button = cid_take_soup.find("button", attrs={"name": "next"})
|
||||||
|
|
||||||
|
# Our final question will trigger a bad request (it shouldn't show a next button)
|
||||||
|
# We currently do still save the form though....
|
||||||
|
if questions_len == n:
|
||||||
|
assert next_button == None
|
||||||
|
assert res.status_code == HTTPStatus.BAD_REQUEST
|
||||||
|
else:
|
||||||
|
assert next_button
|
||||||
|
assert res.status_code == HTTPStatus.FOUND
|
||||||
|
|
||||||
|
overview_button = cid_take_soup.find("button", attrs={"name": "finish"})
|
||||||
|
assert overview_button
|
||||||
|
|
||||||
|
previous_button = cid_take_soup.find("button", attrs={"name": "previous"})
|
||||||
|
|
||||||
|
if n > 0:
|
||||||
|
assert previous_button
|
||||||
|
else:
|
||||||
|
assert not previous_button
|
||||||
|
|
||||||
|
# Filter answer to the question we just submitted to
|
||||||
|
cid_user_answers = question.cid_user_answers.all()
|
||||||
|
|
||||||
|
assert len(cid_user_answers) in (1, 2)
|
||||||
|
|
||||||
|
cid_user_answer = cid_user_answers.filter(cid=cid_user.cid)[0]
|
||||||
|
|
||||||
|
assert all(cid_user_answer.get_answers())
|
||||||
|
|
||||||
|
|
||||||
|
# Recheck the overview page (now that we have submitted answers)
|
||||||
|
cid_take_overview_res = client.get(
|
||||||
|
reverse(
|
||||||
|
f"physics:exam_take_overview",
|
||||||
|
kwargs={
|
||||||
|
"pk": exam.pk,
|
||||||
|
"cid": cid_user.cid,
|
||||||
|
"passcode": cid_user.passcode,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cid_take_overview_soup = BeautifulSoup(
|
||||||
|
cid_take_overview_res.content, "html.parser"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"3 out of 3 questions answered"
|
||||||
|
in cid_take_overview_soup.find(
|
||||||
|
"div", attrs={"class": "overview-text"}
|
||||||
|
).string
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
len(
|
||||||
|
cid_take_overview_soup.find_all("button", attrs={"class": "unanswered"})
|
||||||
|
)
|
||||||
|
== 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get overview scores page (invalid passcode)
|
||||||
|
AssertNotFound(client,
|
||||||
|
reverse(f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": "AAAA"})
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get overview scores page
|
||||||
|
cid_scores_res = client.get(
|
||||||
|
reverse(f"cid_scores", kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode})
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
AssertNotFound(client,
|
||||||
|
reverse(
|
||||||
|
f"{exam.app_name}:exam_scores_cid_user",
|
||||||
|
kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": "AAAA"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load the exam results page
|
||||||
|
cid_exam_scores_res = client.get(
|
||||||
|
reverse(
|
||||||
|
f"{exam.app_name}:exam_scores_cid_user",
|
||||||
|
kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": cid_user.passcode},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Check that we are showing enough questions / answers
|
||||||
|
answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"})
|
||||||
|
assert len(answer_lis) == 5 * len(questions)
|
||||||
|
|
||||||
|
submitted_answers = cid_exam_scores_soup.find_all(
|
||||||
|
"span", {"class": "submitted-user-answer"}
|
||||||
|
)
|
||||||
|
assert len(submitted_answers) == 5 * len(questions)
|
||||||
|
|
||||||
|
for ans in submitted_answers:
|
||||||
|
assert ans.text.strip() == "True"
|
||||||
|
|
||||||
|
# Check no correct answers are shown
|
||||||
|
assert not cid_exam_scores_soup.find_all("span", {"class": "correct-answer"})
|
||||||
|
|
||||||
|
# Add another question to the exam
|
||||||
|
extra_question = create_question(exam)
|
||||||
|
questions.append(extra_question)
|
||||||
|
|
||||||
|
# Load the exam results page (again)
|
||||||
|
cid_exam_scores_res = client.get(
|
||||||
|
reverse(
|
||||||
|
f"{exam.app_name}:exam_scores_cid_user",
|
||||||
|
kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": cid_user.passcode},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
||||||
|
|
||||||
|
answer_lis = cid_exam_scores_soup.find_all("li", {"class": "question-part"})
|
||||||
|
assert len(answer_lis) == 5 * len(questions)
|
||||||
|
|
||||||
|
submitted_answers = cid_exam_scores_soup.find_all(
|
||||||
|
"span", {"class": "submitted-user-answer"}
|
||||||
|
)
|
||||||
|
assert len(submitted_answers) == 5 * len(questions)
|
||||||
|
|
||||||
|
for ans in submitted_answers[:-5]:
|
||||||
|
assert ans.text.strip() == "True"
|
||||||
|
|
||||||
|
# The new question will not have any answers
|
||||||
|
for ans in submitted_answers[-5:]:
|
||||||
|
assert ans.text.strip() == ""
|
||||||
|
|
||||||
|
# Publish the results!
|
||||||
|
exam.publish_results = True
|
||||||
|
exam.save()
|
||||||
|
|
||||||
|
# Load the exam results page (again)
|
||||||
|
cid_exam_scores_res = client.get(
|
||||||
|
reverse(
|
||||||
|
f"{exam.app_name}:exam_scores_cid_user",
|
||||||
|
kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": cid_user.passcode},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|
||||||
|
# Check that we are displaying the correct answers (correctly)
|
||||||
|
correct_answers = cid_exam_scores_soup.find_all(
|
||||||
|
"span", {"class": "correct-answer"}
|
||||||
|
)
|
||||||
|
assert len(correct_answers) == 5 * len(questions)
|
||||||
|
|
||||||
|
n = 0
|
||||||
|
for question in questions:
|
||||||
|
for ans in question.get_answers():
|
||||||
|
assert correct_answers[n].text.strip() == f"Correct answer: {ans}"
|
||||||
|
n = n + 1
|
||||||
|
|
||||||
|
submitted_answers = cid_exam_scores_soup.find_all(
|
||||||
|
"span", {"class": "submitted-user-answer"}
|
||||||
|
)
|
||||||
|
assert len(submitted_answers) == 5 * len(questions)
|
||||||
|
|
||||||
|
for ans in submitted_answers[:-5]:
|
||||||
|
assert ans.text.strip() == "True"
|
||||||
|
|
||||||
|
# The new question will not have any answers
|
||||||
|
for ans in submitted_answers[-5:]:
|
||||||
|
assert ans.text.strip() == ""
|
||||||
|
|
||||||
|
assert (
|
||||||
|
cid_exam_scores_soup.find("div", {"class": "score-overview"})
|
||||||
|
.find("span", {"id": "total-score"})
|
||||||
|
.string
|
||||||
|
== "9"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
cid_exam_scores_soup.find("div", {"class": "score-overview"})
|
||||||
|
.find("span", {"id": "max-score"})
|
||||||
|
.string
|
||||||
|
== "20"
|
||||||
|
)
|
||||||
|
|
||||||
|
# answer the final question
|
||||||
|
cid_user_answer = CidUserAnswer(question=extra_question, a=True, b=False, c=True, d=False, e=True, cid=cid_user.cid, exam=exam)
|
||||||
|
cid_user_answer.save()
|
||||||
|
|
||||||
|
cid_exam_scores_res = client.get(
|
||||||
|
reverse(
|
||||||
|
f"{exam.app_name}:exam_scores_cid_user",
|
||||||
|
kwargs={"pk": exam.pk, "cid": cid_user.cid, "passcode": cid_user.passcode},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cid_exam_scores_soup = BeautifulSoup(cid_exam_scores_res.content, "html.parser")
|
||||||
|
|
||||||
|
# As we have answered correctly the new score should be increased by 5
|
||||||
|
assert (
|
||||||
|
cid_exam_scores_soup.find("div", {"class": "score-overview"})
|
||||||
|
.find("span", {"id": "total-score"})
|
||||||
|
.string
|
||||||
|
== "14"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Check what happens when exam is not active
|
||||||
|
exam.active = False
|
||||||
|
exam.save()
|
||||||
|
|
||||||
|
start_page_res = client.get(
|
||||||
|
reverse(f"physics:exam_start", kwargs={"pk": exam.pk})
|
||||||
|
)
|
||||||
|
start_page_soup = BeautifulSoup(start_page_res.content, "html.parser")
|
||||||
|
|
||||||
|
assert "Exam is currently inactive" in start_page_soup.text
|
||||||
|
|
||||||
|
cid_take_res = client.get(
|
||||||
|
reverse(
|
||||||
|
f"physics:exam_take",
|
||||||
|
kwargs={
|
||||||
|
"pk": exam.pk,
|
||||||
|
"sk": 0,
|
||||||
|
"cid": cid_user.cid,
|
||||||
|
"passcode": cid_user.passcode,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cid_take_soup = BeautifulSoup(cid_take_res.content, "html.parser")
|
||||||
|
assert "Exam is currently inactive" in cid_take_soup.text
|
||||||
|
|
||||||
|
questions.pop(-1).delete()
|
||||||
+10
-7
@@ -25,7 +25,7 @@ from django.core.cache import cache
|
|||||||
|
|
||||||
from django.urls import reverse_lazy, reverse
|
from django.urls import reverse_lazy, reverse
|
||||||
|
|
||||||
from django.http import Http404, JsonResponse
|
from django.http import Http404, HttpResponseBadRequest, JsonResponse
|
||||||
|
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
from django_filters.views import FilterView
|
from django_filters.views import FilterView
|
||||||
@@ -277,6 +277,13 @@ def exam_take(request, pk, sk, cid=None, passcode=None):
|
|||||||
else:
|
else:
|
||||||
answer = question.cid_user_answers.filter(user=request.user, exam=exam).first()
|
answer = question.cid_user_answers.filter(user=request.user, exam=exam).first()
|
||||||
|
|
||||||
|
previous = -1
|
||||||
|
if sk > 0:
|
||||||
|
previous = sk - 1
|
||||||
|
next = sk + 1
|
||||||
|
if sk == exam_length - 1:
|
||||||
|
next = False
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if answer:
|
if answer:
|
||||||
form = CidUserAnswerForm(request.POST, instance=answer)
|
form = CidUserAnswerForm(request.POST, instance=answer)
|
||||||
@@ -307,6 +314,8 @@ def exam_take(request, pk, sk, cid=None, passcode=None):
|
|||||||
finish_url = "physics:exam_take_overview_user"
|
finish_url = "physics:exam_take_overview_user"
|
||||||
|
|
||||||
if "next" in request.POST:
|
if "next" in request.POST:
|
||||||
|
if not next:
|
||||||
|
return HttpResponseBadRequest("Invalid request")
|
||||||
return redirect(take_url, sk=pos + 1, **kwargs)
|
return redirect(take_url, sk=pos + 1, **kwargs)
|
||||||
elif "previous" in request.POST:
|
elif "previous" in request.POST:
|
||||||
return redirect(take_url, sk=pos - 1, **kwargs)
|
return redirect(take_url, sk=pos - 1, **kwargs)
|
||||||
@@ -317,12 +326,6 @@ def exam_take(request, pk, sk, cid=None, passcode=None):
|
|||||||
else:
|
else:
|
||||||
form = CidUserAnswerForm(instance=answer)
|
form = CidUserAnswerForm(instance=answer)
|
||||||
|
|
||||||
previous = -1
|
|
||||||
if sk > 0:
|
|
||||||
previous = sk - 1
|
|
||||||
next = sk + 1
|
|
||||||
if sk == exam_length - 1:
|
|
||||||
next = False
|
|
||||||
|
|
||||||
saved_answer = False
|
saved_answer = False
|
||||||
if answer is not None:
|
if answer is not None:
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
def AssertNotFound(client, url):
|
||||||
|
"""Helper to quickly test for urls that should return a 404 (NOT FOUND) error"""
|
||||||
|
response = client.get(url)
|
||||||
|
assert response.status_code == HTTPStatus.NOT_FOUND, f"Response content: {response.content}"
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
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, Exam, Examination, Rapid as Question
|
||||||
|
|
||||||
|
from generic.models import CidUser, CidUserGroup
|
||||||
|
|
||||||
|
APP_NAME = "rapids"
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
exam: Exam = RapidExamViews.Exam.objects.create(
|
||||||
|
name="Cid Exam", exam_mode=True, active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
group1: CidUserGroup = CidUserGroup.objects.get(name="Group1")
|
||||||
|
|
||||||
|
exam.cid_user_groups.add(group1)
|
||||||
|
|
||||||
|
exam.valid_cid_users.add(*group1.ciduser_set.all())
|
||||||
|
|
||||||
|
assert exam in group1.rapids_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(db, rapid):
|
||||||
|
rapid_image = RapidImage()
|
||||||
|
|
||||||
|
|
||||||
|
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(
|
||||||
|
laterality=Question.LATERALITY_CHOICES.NONE,
|
||||||
|
examination=Examination.objects.get(pk=1),
|
||||||
|
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):
|
||||||
|
return
|
||||||
|
create_cid_user_and_groups(db)
|
||||||
|
exam = create_exam(db)
|
||||||
|
|
||||||
|
|
||||||
|
create_structures(db)
|
||||||
|
create_question_types(db)
|
||||||
|
|
||||||
|
question1 = create_question(exam)
|
||||||
|
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_1001 = CidUser.objects.get(cid=1001)
|
||||||
|
|
||||||
|
for cid_user in [cid_user_1001]:
|
||||||
|
# Test active exams sections works as intended
|
||||||
|
active_exams_cid = client.get(
|
||||||
|
reverse(
|
||||||
|
f"active_exams_cid",
|
||||||
|
kwargs={"cid": cid_user.cid, "passcode": cid_user.passcode},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert active_exams_cid.status_code == 200
|
||||||
|
assert len(json.loads(active_exams_cid.content)["exams"]) == 1
|
||||||
|
|
||||||
|
active_exams_cid = client.get(
|
||||||
|
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(
|
||||||
|
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": 1001, "passcode": "ABCD"})
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
post_json = {
|
||||||
|
"eid": f"anatomy/{exam.pk}",
|
||||||
|
"cid": cid_user_1001.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": cid_user_1001.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:
|
||||||
|
valid_answers.append(
|
||||||
|
{
|
||||||
|
"eid": exam_json["eid"],
|
||||||
|
"cid": cid_user_1001.cid,
|
||||||
|
"passcode": cid_user_1001.passcode,
|
||||||
|
"qid": qid,
|
||||||
|
"qidn": "1",
|
||||||
|
"ans": f"answer {qid}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
post_json = {
|
||||||
|
"eid": exam_json["eid"],
|
||||||
|
"cid": cid_user_1001.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)
|
||||||
|
|
||||||
|
# Get overview scores page
|
||||||
|
cid_scores_res = client.get(
|
||||||
|
reverse(f"cid_scores", kwargs={"cid": 1001, "passcode": "EFGH"})
|
||||||
|
)
|
||||||
|
|
||||||
|
cid_scores_soup = BeautifulSoup(cid_scores_res.content, "html.parser")
|
||||||
|
|
||||||
|
print(cid_scores_soup.find("div", {"id" : "exam-assigned"}))
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# Load the exam results page
|
||||||
|
cid_exam_scores_res = client.get(
|
||||||
|
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")
|
||||||
|
|
||||||
|
# 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 li.find("span", {"class": "correct-answer"}).string == "*****" # 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(
|
||||||
|
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")
|
||||||
|
|
||||||
|
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(
|
||||||
|
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")
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
assert cid_exam_scores_soup.find("div", {"class": "score-overview"}).find("span", {"id": "total-score"}).string == "0 (3 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(
|
||||||
|
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")
|
||||||
|
|
||||||
|
assert cid_exam_scores_soup.find("div", {"class": "score-overview"}).find("span", {"id": "total-score"}).string == "6"
|
||||||
Reference in New Issue
Block a user