Files
guilan-ace-backend/apps/gamification/tests/integration/test_events.py

117 lines
4.3 KiB
Python

from io import StringIO
from types import SimpleNamespace
import pytest
from django.contrib import admin
from django.core.management import call_command
from apps.events.admin import RegistrationAdmin
from apps.events.models import Registration
from apps.events.services import verify_registration_attendance
from apps.gamification.models import ContributionLedger
from apps.gamification.services import summary_for_user
pytestmark = [pytest.mark.integration, pytest.mark.django_db]
def test_attendee_cannot_mark_their_own_registration_as_attended(
client, event_factory, user_factory, auth_headers,
):
attendee = user_factory()
event = event_factory()
registration = Registration.objects.create(
event=event,
user=attendee,
status=Registration.StatusChoices.CONFIRMED,
)
response = client.put(
f"/api/events/registrations/{registration.id}",
data={"status": "attended"},
content_type="application/json",
**auth_headers(attendee),
)
registration.refresh_from_db()
assert response.status_code == 403
assert registration.status == Registration.StatusChoices.CONFIRMED
assert summary_for_user(attendee)["points_total"] == 0
def test_staff_attendance_action_records_verifier_and_awards_once(
event_factory, user_factory, mocker,
):
staff = user_factory(is_staff=True)
attendee = user_factory()
registration = Registration.objects.create(
event=event_factory(),
user=attendee,
status=Registration.StatusChoices.CONFIRMED,
)
mocker.patch.object(RegistrationAdmin, "message_user")
registration_admin = RegistrationAdmin(Registration, admin.site)
request = SimpleNamespace(user=staff)
registration_admin.mark_attended(request, Registration.objects.filter(pk=registration.pk))
registration.refresh_from_db()
registration_admin.mark_attended(request, Registration.objects.filter(pk=registration.pk))
assert registration.status == Registration.StatusChoices.ATTENDED
assert registration.attendance_verified_by_id == staff.id
assert registration.attendance_verification_source == Registration.AttendanceVerificationSource.STAFF_ADMIN
assert summary_for_user(attendee)["points_total"] == 30
assert ContributionLedger.objects.filter(user=attendee).count() == 1
def test_attendance_backfill_is_read_only_by_default_and_safe_to_repeat(
event_factory, user_factory,
):
attendee = user_factory()
event = event_factory()
registration = Registration.objects.create(
event=event,
user=attendee,
status=Registration.StatusChoices.ATTENDED,
)
Registration.objects.create(
event=event_factory(),
user=user_factory(),
status=Registration.StatusChoices.CONFIRMED,
)
output = StringIO()
call_command("backfill_event_attendance", stdout=output)
assert summary_for_user(attendee)["points_total"] == 0
registration.refresh_from_db()
assert registration.attendance_verification_source == ""
assert "Dry run only" in output.getvalue()
call_command("backfill_event_attendance", apply=True, stdout=StringIO())
call_command("backfill_event_attendance", apply=True, stdout=StringIO())
registration.refresh_from_db()
assert registration.attendance_verification_source == Registration.AttendanceVerificationSource.HISTORICAL
assert registration.attendance_verified_at is not None
assert summary_for_user(attendee)["points_total"] == 30
assert ContributionLedger.objects.filter(user=attendee).count() == 1
def test_removing_verified_attendance_reverses_points_and_badges(event_factory, user_factory):
attendee = user_factory()
registration = Registration.objects.create(
event=event_factory(),
user=attendee,
status=Registration.StatusChoices.CONFIRMED,
)
verify_registration_attendance(registration)
assert summary_for_user(attendee)["points_total"] == 30
registration.status = Registration.StatusChoices.CANCELLED
registration.save(update_fields=["status", "updated_at"])
summary = summary_for_user(attendee)
assert summary["points_total"] == 0
assert summary["activity_counts"]["attendance"] == 0
assert not any(badge["category"] == "attendance" for badge in summary["badges"])
assert ContributionLedger.objects.filter(user=attendee).count() == 2