Split backend tests into unit and integration suites, add isolated fixtures and coverage enforcement, and update CI and test documentation. Cover current authentication, content, notification, payment, media, and admin flows; fix behaviors revealed by the new tests. Validate both suites and the per-module gate against local PostgreSQL.
152 lines
7.1 KiB
Python
152 lines
7.1 KiB
Python
import pytest
|
|
import json
|
|
from io import BytesIO
|
|
from django.core.exceptions import ValidationError
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
from PIL import Image
|
|
|
|
from apps.certificates.models import CertificateTemplate, Skill, UserCertificate
|
|
from apps.events.models import Registration
|
|
|
|
|
|
pytestmark = [pytest.mark.integration, pytest.mark.django_db]
|
|
|
|
|
|
def test_certificate_requires_confirmed_registration(user_factory, event_factory):
|
|
user = user_factory()
|
|
event = event_factory()
|
|
template = CertificateTemplate.objects.create(event=event, image="")
|
|
with pytest.raises(ValidationError):
|
|
template.award_certificate(user=user, title="Award", score=80)
|
|
assert not UserCertificate.objects.exists()
|
|
|
|
|
|
def test_confirmed_attendee_receives_certificate(user_factory, event_factory, mocker):
|
|
mocker.patch("apps.events.tasks.send_registration_confirmation_notification.delay")
|
|
user = user_factory()
|
|
event = event_factory()
|
|
template = CertificateTemplate.objects.create(event=event, image="")
|
|
Registration.objects.create(event=event, user=user, status=Registration.StatusChoices.CONFIRMED)
|
|
certificate = template.award_certificate(user=user, title="Award", score=80)
|
|
assert certificate.score_label == "Perfect"
|
|
assert UserCertificate.objects.filter(user=user, event=event).count() == 1
|
|
assert len(certificate.code) == 10
|
|
|
|
|
|
def test_certificate_skill_ids_are_deduplicated_and_validated(user_factory, event_factory):
|
|
template = CertificateTemplate.objects.create(event=event_factory(), image="")
|
|
skill = Skill.objects.create(name="Python")
|
|
template.skills.add(skill)
|
|
assert template._resolve_skill_ids(None) == [skill.id]
|
|
assert template._resolve_skill_ids([skill.id, str(skill.id), None, "bad"]) == [skill.id]
|
|
assert template._resolve_skill_ids([]) == []
|
|
with pytest.raises(ValidationError):
|
|
template._resolve_skill_ids([999999])
|
|
|
|
|
|
def test_certificate_batch_rejects_empty_or_missing_users(event_factory):
|
|
template = CertificateTemplate.objects.create(event=event_factory(), image="")
|
|
with pytest.raises(ValidationError):
|
|
template.generate_certificates([])
|
|
with pytest.raises(ValidationError):
|
|
template.generate_certificates([{"score": 80}])
|
|
with pytest.raises(ValidationError):
|
|
template.generate_certificates([{"user_id": 999999, "score": 80}])
|
|
|
|
|
|
def test_certificate_api_staff_generation_and_public_verification(client, user_factory, event_factory, auth_headers, mocker):
|
|
mocker.patch("apps.events.tasks.send_registration_confirmation_notification.delay")
|
|
staff = user_factory(is_staff=True)
|
|
attendee = user_factory()
|
|
event = event_factory()
|
|
template = CertificateTemplate.objects.create(event=event, image="")
|
|
Registration.objects.create(event=event, user=attendee, status=Registration.StatusChoices.CONFIRMED)
|
|
path = f"/api/certificates/templates/{event.pk}"
|
|
assert client.get(path).status_code == 401
|
|
assert client.get(path, **auth_headers(attendee)).status_code == 403
|
|
response = client.get(path, **auth_headers(staff))
|
|
assert response.status_code == 200
|
|
assert response.json()["event_id"] == event.pk
|
|
|
|
generation = client.post(
|
|
f"{path}/generate",
|
|
data=json.dumps({"entries": [{"user_id": attendee.pk, "score": 84}]}),
|
|
content_type="application/json",
|
|
**auth_headers(staff),
|
|
)
|
|
assert generation.status_code == 200
|
|
assert generation.json()["certificates"][0]["user_name"] == attendee.username
|
|
certificate = UserCertificate.objects.get(user=attendee, event=event)
|
|
verified = client.get(f"/api/certificates/verify/{certificate.code}")
|
|
assert verified.status_code == 200
|
|
assert verified.json()["user_name"] == attendee.username
|
|
assert verified.json()["score"] == 84
|
|
|
|
|
|
def test_certificate_api_rejects_unregistered_attendee(client, user_factory, event_factory, auth_headers):
|
|
staff = user_factory(is_staff=True)
|
|
attendee = user_factory()
|
|
event = event_factory()
|
|
CertificateTemplate.objects.create(event=event, image="")
|
|
response = client.post(
|
|
f"/api/certificates/templates/{event.pk}/generate",
|
|
data=json.dumps({"entries": [{"user_id": attendee.pk, "score": 75}]}),
|
|
content_type="application/json",
|
|
**auth_headers(staff),
|
|
)
|
|
assert response.status_code == 400
|
|
assert not UserCertificate.objects.exists()
|
|
|
|
|
|
def test_batch_generation_updates_existing_awards_and_skills(user_factory, event_factory, mocker):
|
|
mocker.patch("apps.events.tasks.send_registration_confirmation_notification.delay")
|
|
user = user_factory()
|
|
event = event_factory()
|
|
template = CertificateTemplate.objects.create(event=event, image="")
|
|
skill = Skill.objects.create(name="Testing")
|
|
template.skills.add(skill)
|
|
Registration.objects.create(event=event, user=user, status=Registration.StatusChoices.ATTENDED)
|
|
first = template.generate_certificates([{"user_id": user.pk, "score": 24}], default_title="Initial")[0]
|
|
assert first.title == "Initial" and first.score_label == "Fair"
|
|
assert list(first.skills.all()) == [skill]
|
|
second = template.generate_certificates([{"user_id": user.pk, "score": 49, "skill_ids": []}],
|
|
default_title="Updated")[0]
|
|
assert second.pk == first.pk and second.title == "Updated" and second.score_label == "Good"
|
|
assert not second.skills.exists()
|
|
assert UserCertificate.objects.filter(user=user, event=event).count() == 1
|
|
|
|
|
|
def test_certificate_duplicate_code_is_regenerated(user_factory, event_factory, mocker):
|
|
mocker.patch("apps.events.tasks.send_registration_confirmation_notification.delay")
|
|
user = user_factory()
|
|
another = user_factory()
|
|
event = event_factory()
|
|
template = CertificateTemplate.objects.create(event=event, image="")
|
|
for attendee in (user, another):
|
|
Registration.objects.create(event=event, user=attendee, status=Registration.StatusChoices.CONFIRMED)
|
|
first = template.award_certificate(user=user, title="First", score=55)
|
|
second = template.award_certificate(user=another, title="Second", score=60)
|
|
second.code = first.code
|
|
second.save()
|
|
assert second.code != first.code
|
|
assert second.score_label == "Very Good"
|
|
|
|
|
|
def test_certificate_template_renders_award_image(user_factory, event_factory, mocker, tmp_path, settings):
|
|
settings.MEDIA_ROOT = tmp_path
|
|
mocker.patch("apps.events.tasks.send_registration_confirmation_notification.delay")
|
|
user = user_factory(first_name="Alice", last_name="Smith")
|
|
event = event_factory(title="Workshop")
|
|
buffer = BytesIO()
|
|
Image.new("RGB", (800, 500), "white").save(buffer, format="PNG")
|
|
template = CertificateTemplate.objects.create(
|
|
event=event,
|
|
image=SimpleUploadedFile("template.png", buffer.getvalue(), content_type="image/png"),
|
|
)
|
|
Registration.objects.create(event=event, user=user, status=Registration.StatusChoices.CONFIRMED)
|
|
certificate = template.award_certificate(user=user, title="Award", score=99)
|
|
certificate.refresh_from_db()
|
|
assert certificate.image and certificate.image.storage.exists(certificate.image.name)
|
|
with Image.open(certificate.image.path) as rendered:
|
|
assert rendered.size == (800, 500)
|