smoke test the rest of the apps

This commit is contained in:
Ross
2026-06-10 22:05:12 +01:00
parent e670adf2d0
commit 6bb9441748
55 changed files with 1454 additions and 109 deletions
+5 -4
View File
@@ -86,10 +86,11 @@ class QuestionUserAnswerFilter(django_filters.FilterSet):
user=None,
request=None,
):
if not request.user.groups.filter(name="shorts_checker").exists():
queryset = queryset.filter(open_access=True) | queryset.filter(
author__id=request.user.id
)
if not request.user.is_superuser:
if not request.user.groups.filter(name="shorts_checker").exists():
queryset = queryset.filter(exam__open_access=True) | queryset.filter(
exam__author__id=request.user.id
)
super(QuestionUserAnswerFilter, self).__init__(
data=data, queryset=queryset, prefix=prefix, request=request
)
@@ -0,0 +1,7 @@
{% extends 'shorts/base.html' %}
{% block content %}
<form method="post">{% csrf_token %}
<p>Are you sure you want to delete "{{ object }}"?</p>
<input type="submit" value="Confirm" class="btn btn-danger">
</form>
{% endblock %}
@@ -0,0 +1,8 @@
<div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''>
</div>
<button class="previous-btn">Previous</button>
<button class="next-btn">Next</button>
{% for id in ids %}
{{id}}
{% endfor %}
@@ -0,0 +1,31 @@
{% extends 'shorts/base.html' %}
{% load render_table from django_tables2 %}
{% block css %}
{% endblock %}
{% block content %}
<div id="view-filter-options">
<h3>Shorts User Answers</h3>
<form action="" method="get">
{{ filter.form }}
<input class="btn btn-primary btn-sm mt-1 mb-1" type="submit" />
</form>
</div>
{% render_table table %}
<button id="delete-selected-button">Delete selected answers</button>
{% endblock %}
{% block js %}
<script>
$(document).ready(function () {
$("#delete-selected-button").on("click", () => {
window.delete_multiple(
"{% url 'shorts:user_answer_delete_multiple' %}", "{{ csrf_token }}"
)
})
});
</script>
{% endblock %}
@@ -0,0 +1,12 @@
{% extends 'shorts/base.html' %}
{% block content %}
CID: {{useranswer.cid}}<br/>
User: {{useranswer.user}}<br/>
Exam: <a href="{% url 'shorts:exam_overview' useranswer.exam.pk %}">{{useranswer.exam}}</a><br />
Question: <a href="{% url 'shorts:question_detail' useranswer.question.pk %}">{{useranswer.question}}</a><br />
Answer: <pre>{{useranswer.answer}}</pre>
Score: {{useranswer.get_answer_score}}<br/>
<a href="{% url 'shorts:user_answer_delete' useranswer.id %}">Delete answer</a>
<a href="{% url 'admin:shorts_useranswer_change' useranswer.id %}">Admin Edit</a>
{% endblock content %}
-3
View File
@@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.
+1
View File
@@ -0,0 +1 @@
# Package marker for tests
+151
View File
@@ -0,0 +1,151 @@
import pytest
import tempfile
from django.urls import reverse
from django.contrib.auth.models import Group, User
from django.test import Client
SAFE_STATUS_CODES = {200, 301, 302, 303, 403, 404, 405}
@pytest.fixture
def candidate_client(db):
user = User.objects.create_user(username="candidate", password="password", email="candidate@test.com")
client = Client()
client.force_login(user)
return client
@pytest.fixture
def superuser_client(db):
user = User.objects.create_superuser(username="superuser", password="password", email="superuser@test.com")
for group_name in ["shorts_checker", "cid_user_manager"]:
g, _ = Group.objects.get_or_create(name=group_name)
user.groups.add(g)
client = Client()
client.force_login(user)
return client
@pytest.fixture
def anonymous_client(db):
return Client()
@pytest.fixture
def smoke_data(db, settings, tmp_path):
from shorts.models import Question, QuestionImage, Exam, UserAnswer, QuestionFinding
from rapids.models import Examination
settings.MEDIA_ROOT = str(tmp_path)
create_user = User.objects.create_user(username="smoke_user", email="smoke_user@test.com")
exm_name, _ = Examination.objects.get_or_create(examination="Test Examination")
question = Question.objects.create(
history="Test History",
marking_guidance="Test Guidance",
)
question.examination.add(exm_name)
# Mocking image
image = tempfile.NamedTemporaryFile(
dir=settings.MEDIA_ROOT, suffix=".jpg", delete=False
)
image.flush()
image._committed = True
image.write(b"12345ABCD")
image.seek(0)
q_image = QuestionImage(image=image.name, question=question)
q_image.save()
exam = Exam.objects.create(
name="Test Shorts Exam",
exam_mode=True,
active=True
)
exam.author.add(create_user)
from shorts.models import ExamQuestionDetail
ExamQuestionDetail.objects.create(exam=exam, question=question, sort_order=1)
user_answer = UserAnswer.objects.create(
user=create_user,
exam=exam,
question=question,
answer="testanswer",
score=2,
)
finding = QuestionFinding.objects.create(
question=question,
description="Test Finding",
)
return {
"question": question,
"exam": exam,
"user_answer": user_answer,
"finding": finding,
}
URL_SPECS = [
("shorts:question_viewer", None),
("shorts:question_view", None),
("shorts:question_json", lambda d: {"pk": d["question"].pk}),
("shorts:question_json_unbased", lambda d: {"pk": d["question"].pk}),
("shorts:question_save_annotation", lambda d: {"pk": d["question"].pk}),
("shorts:question_split", lambda d: {"pk": d["question"].pk}),
("shorts:question_clone", lambda d: {"pk": d["question"].pk}),
("shorts:question_delete", lambda d: {"pk": d["question"].pk}),
("shorts:question_anonymise_dicom", lambda d: {"pk": d["question"].pk}),
("shorts:question_add_exam", lambda d: {"question_id": d["question"].pk}),
("shorts:combine_images_side_by_side", lambda d: {"question_id": d["question"].pk}),
("shorts:mark_answer", lambda d: {"exam_id": d["exam"].pk, "question_number": 0, "answer_id": d["user_answer"].pk}),
("shorts:mark_answer_override", lambda d: {"exam_id": d["exam"].pk, "question_number": 0, "answer_id": d["user_answer"].pk}),
("shorts:mark", lambda d: {"exam_pk": d["exam"].pk, "sk": 0}),
("shorts:mark_all", lambda d: {"exam_pk": d["exam"].pk, "sk": 0}),
("shorts:mark_review", lambda d: {"exam_pk": d["exam"].pk, "sk": 0}),
("shorts:exam_markers", lambda d: {"pk": d["exam"].pk}),
("shorts:exam_groups_edit", lambda d: {"pk": d["exam"].pk}),
("shorts:exam_authors", lambda d: {"pk": d["exam"].pk}),
("shorts:exam_create", None),
("shorts:exam_clone", lambda d: {"exam_id": d["exam"].pk}),
("shorts:exam_update", lambda d: {"pk": d["exam"].pk}),
("shorts:exam_delete", lambda d: {"pk": d["exam"].pk}),
("shorts:question_create", None),
("shorts:question_create_exam", lambda d: {"pk": d["exam"].pk}),
("shorts:question_update", lambda d: {"pk": d["question"].pk}),
("shorts:question_sample_answers", lambda d: {"pk": d["question"].pk}),
("shorts:user_answer_table_view", None),
("shorts:user_answer_view", lambda d: {"pk": d["user_answer"].pk}),
("shorts:user_answer_delete", lambda d: {"pk": d["user_answer"].pk}),
("shorts:question_findings", lambda d: {"question_id": d["question"].pk}),
("shorts:add_finding", None),
("shorts:delete_finding", lambda d: {"pk": d["finding"].pk}),
("shorts:question_edit_finding", lambda d: {"question_id": d["question"].pk, "finding_pk": d["finding"].pk}),
("shorts:index", None),
("shorts:help", None),
]
@pytest.mark.django_db
@pytest.mark.parametrize("url_name, kwargs_lambda", URL_SPECS)
def test_url_smoke_anonymous(anonymous_client, smoke_data, url_name, kwargs_lambda):
kwargs = kwargs_lambda(smoke_data) if kwargs_lambda else None
url = reverse(url_name, kwargs=kwargs)
response = anonymous_client.get(url, follow=False)
assert response.status_code != 500, f"500 Server Error on {url_name} ({url}) for Anonymous"
assert response.status_code in {200, 302, 400, 403, 404, 405}, f"Unexpected status code {response.status_code} on {url_name} for Anonymous"
@pytest.mark.django_db
@pytest.mark.parametrize("url_name, kwargs_lambda", URL_SPECS)
def test_url_smoke_candidate(candidate_client, smoke_data, url_name, kwargs_lambda):
kwargs = kwargs_lambda(smoke_data) if kwargs_lambda else None
url = reverse(url_name, kwargs=kwargs)
response = candidate_client.get(url, follow=False)
assert response.status_code != 500, f"500 Server Error on {url_name} ({url}) for Candidate"
assert response.status_code in SAFE_STATUS_CODES or response.status_code == 400, f"Unexpected status code {response.status_code} on {url_name} for Candidate"
@pytest.mark.django_db
@pytest.mark.parametrize("url_name, kwargs_lambda", URL_SPECS)
def test_url_smoke_superuser(superuser_client, smoke_data, url_name, kwargs_lambda):
kwargs = kwargs_lambda(smoke_data) if kwargs_lambda else None
url = reverse(url_name, kwargs=kwargs)
response = superuser_client.get(url, follow=False)
assert response.status_code != 500, f"500 Server Error on {url_name} ({url}) for Superuser"
assert response.status_code in {200, 301, 302, 303, 400, 403, 404, 405}, f"Unexpected status code {response.status_code} on {url_name} for Superuser"
+9 -5
View File
@@ -107,6 +107,8 @@ def normaliseScore(score):
class AuthorOrCheckerRequiredMixin(object):
def get_object(self, *args, **kwargs):
obj = super().get_object(*args, **kwargs)
if self.request.user.is_superuser:
return obj
if self.request.user.groups.filter(name="shorts_checker").exists():
return obj
if self.request.user not in obj.author.all():
@@ -351,8 +353,8 @@ def mark_all(request, exam_pk, sk):
def mark_review(request, exam_pk, sk):
return mark(request, exam_pk, sk, unmarked_exam_answers_only=False, review=True)
def mark_answer_override(request, exam_id, question_number, cid):
return mark_answer(request, exam_id, question_number, cid, override=True)
def mark_answer_override(request, exam_id, question_number, answer_id):
return mark_answer(request, exam_id, question_number, answer_id, override=True)
def mark_answer(request, exam_id, question_number, answer_id, override=False):
@@ -548,9 +550,8 @@ def mark_answer(request, exam_id, question_number, answer_id, override=False):
},
)
# @user_passes_test(user_is_admin, login_url="/accounts/login")
@login_required
def mark(request, exam_pk, sk, unmarked_exam_answers_only=True):
def mark(request, exam_pk, sk, unmarked_exam_answers_only=True, review=False):
exam = get_object_or_404(Exam, pk=exam_pk)
if not GenericExamViews.check_user_marker_access(request.user, exam_pk):
@@ -591,6 +592,7 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True):
"user_answers": user_answers,
"unmarked_count": unmarked_count,
"marker_unmarked_count": marker_unmarked_count,
"review": review,
},
)
else:
@@ -603,6 +605,7 @@ def mark(request, exam_pk, sk, unmarked_exam_answers_only=True):
"question_details": question_details,
"user_answers": user_answers,
"unmarked_count": unmarked_count,
"review": review,
},
)
@@ -756,7 +759,8 @@ def question_json(request, pk):
def question_viewer(request):
# question = get_object_or_404(Question, pk=pk)
ids = request.GET.get("ids").split(",")
ids_param = request.GET.get("ids")
ids = ids_param.split(",") if ids_param else []
return render(
request,