From f5c23b69ab39a8013d47ee009da2eb6a74a53dae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 15 Apr 2021 14:34:42 +0200
Subject: [PATCH 2/3] api: check absence reason exists (#53147)

---
 chrono/api/views.py | 25 +++++++++++++++++++++++--
 tests/test_api.py   | 34 ++++++++++++++++++++++++++++------
 2 files changed, 51 insertions(+), 8 deletions(-)

diff --git a/chrono/api/views.py b/chrono/api/views.py
index e11bca7..f395707 100644
--- a/chrono/api/views.py
+++ b/chrono/api/views.py
@@ -38,7 +38,16 @@ from rest_framework.generics import ListAPIView
 from rest_framework.views import APIView
 
 from chrono.api.utils import Response, APIError
-from ..agendas.models import Agenda, Event, Booking, MeetingType, TimePeriodException, Desk, BookingColor
+from ..agendas.models import (
+    AbsenceReason,
+    Agenda,
+    Event,
+    Booking,
+    MeetingType,
+    TimePeriodException,
+    Desk,
+    BookingColor,
+)
 from ..interval import IntervalSet
 
 
@@ -1304,8 +1313,20 @@ class BookingSerializer(serializers.ModelSerializer):
         read_only_fields = ['id', 'in_waiting_list']
 
     def validate_user_absence_reason(self, value):
-        if value is None:
+        if not value:
             return ''
+
+        if not self.instance.event.agenda.absence_reasons_group:
+            raise serializers.ValidationError(_('unknown absence reason'))
+
+        reasons_qs = self.instance.event.agenda.absence_reasons_group.absence_reasons
+        try:
+            reason = reasons_qs.get(slug=value)
+            value = reason.label
+        except AbsenceReason.DoesNotExist:
+            if not reasons_qs.filter(label=value).exists():
+                raise serializers.ValidationError(_('unknown absence reason'))
+
         return value
 
 
diff --git a/tests/test_api.py b/tests/test_api.py
index bda0ffa..272a961 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -12,6 +12,8 @@ from django.test.utils import CaptureQueriesContext
 from django.utils.timezone import now, make_aware, localtime
 
 from chrono.agendas.models import (
+    AbsenceReason,
+    AbsenceReasonGroup,
     Agenda,
     Booking,
     Category,
@@ -2561,11 +2563,30 @@ def test_booking_patch_api_absence_reason(app, user):
 
     app.authorization = ('Basic', ('john.doe', 'password'))
 
+    # reasons not defined on agenda
+    resp = app.patch_json(
+        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'}, status=400
+    )
+    assert resp.json['err'] == 4
+    assert resp.json['err_desc'] == 'invalid payload'
+
+    group = AbsenceReasonGroup.objects.create(label='Foo')
+    reason = AbsenceReason.objects.create(group=group, label='Foo bar')
+
+    resp = app.patch_json(
+        '/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'}, status=400
+    )
+    assert resp.json['err'] == 4
+    assert resp.json['err_desc'] == 'invalid payload'
+
     # set reason
-    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'})
+    agenda.absence_reasons_group = group
+    agenda.save()
+    # it works with label
+    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'Foo bar'})
     booking.refresh_from_db()
     assert booking.user_was_present is True  # not changed
-    assert booking.user_absence_reason == 'foobar'
+    assert booking.user_absence_reason == 'Foo bar'
 
     # reset
     app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': ''})
@@ -2583,14 +2604,15 @@ def test_booking_patch_api_absence_reason(app, user):
     # and other booking
     other_booking = Booking.objects.create(event=event)
 
-    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'})
+    # it works also with slug
+    app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': reason.slug})
     booking.refresh_from_db()
     assert booking.user_was_present is True  # not changed
-    assert booking.user_absence_reason == 'foobar'
+    assert booking.user_absence_reason == 'Foo bar'
     # all secondary bookings are upadted
     assert list(booking.secondary_booking_set.values_list('user_absence_reason', flat=True)) == [
-        'foobar',
-        'foobar',
+        'Foo bar',
+        'Foo bar',
     ]
     assert list(booking.secondary_booking_set.values_list('user_was_present', flat=True)) == [
         False,
-- 
2.30.2

