From 5e0653d9f89016ca1f523b36d29e18ccc15f3984 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Fri, 18 Jun 2021 09:43:54 +0200
Subject: [PATCH] caluire-axel: set_activity_agenda_typical_week endpoint
 (#54383)

---
 passerelle/contrib/caluire_axel/models.py  | 181 ++++++---
 passerelle/contrib/caluire_axel/schemas.py |  32 ++
 tests/test_caluire_axel.py                 | 405 +++++++++++++++++++++
 3 files changed, 560 insertions(+), 58 deletions(-)

diff --git a/passerelle/contrib/caluire_axel/models.py b/passerelle/contrib/caluire_axel/models.py
index 22382bc2..a4b8a7e0 100644
--- a/passerelle/contrib/caluire_axel/models.py
+++ b/passerelle/contrib/caluire_axel/models.py
@@ -27,13 +27,22 @@ from django.utils.timezone import now
 from django.utils.translation import ugettext_lazy as _
 
 from passerelle.base.models import BaseResource
-from passerelle.compat import json_loads
 from passerelle.contrib.utils import axel
 from passerelle.utils.api import endpoint
 from passerelle.utils.jsonresponse import APIError
 
 from . import schemas, utils
 
+WEEKDAYS = {
+    0: 'monday',
+    1: 'tuesday',
+    2: 'wednesday',
+    3: 'thursday',
+    4: 'friday',
+    5: 'saturday',
+    6: 'sunday',
+}
+
 
 class CaluireAxel(BaseResource):
 
@@ -532,7 +541,7 @@ class CaluireAxel(BaseResource):
             },
         }
 
-    def get_bookings(self, child_id, activity_id, activity_label, start_date, end_date):
+    def get_bookings(self, child_id, activity_id, start_date, end_date, activity_label=None):
         data = {
             'IDENTINDIVIDU': child_id,
             'IDENTACTIVITE': activity_id,
@@ -620,7 +629,8 @@ class CaluireAxel(BaseResource):
                 booking['disabled'] = True if day_date < datetime.date.today() or color == 'grey' else False
             booking['details']['activity_id'] = activity_id
             booking['details']['activity_type'] = activity_type
-            booking['details']['activity_label'] = activity_label
+            if activity_label:
+                booking['details']['activity_label'] = activity_label
             booking['details']['child_id'] = child_id
             bookings.append(booking)
 
@@ -651,7 +661,7 @@ class CaluireAxel(BaseResource):
         if activity_data is None:
             raise APIError('Activity not found', err_code='not-found')
         bookings = self.get_bookings(
-            idpersonne, activity_id, activity_data['LIBELLEACTIVITE'], start_date, end_date
+            idpersonne, activity_id, start_date, end_date, activity_label=activity_data['LIBELLEACTIVITE']
         )
         return {'data': bookings}
 
@@ -683,7 +693,9 @@ class CaluireAxel(BaseResource):
             if activity_id.startswith('CJ') and not activity_id.startswith('CJ MER'):
                 # vacances: ignore id
                 continue
-            bookings += self.get_bookings(idpersonne, activity_id, activity_label, start_date, end_date)
+            bookings += self.get_bookings(
+                idpersonne, activity_id, start_date, end_date, activity_label=activity_label
+            )
 
         # sort bookings
         activity_types = ['matin', 'midi', 'soir', 'mercredi']
@@ -701,6 +713,63 @@ class CaluireAxel(BaseResource):
 
         return {'data': bookings}
 
+    def set_bookings(self, child_id, activity_id, start_date, end_date, booking_list):
+        agenda = self.get_bookings(child_id, activity_id, start_date, end_date)
+        legacy_days = [b['id'] for b in agenda if b['prefill'] is True]
+        available_days = [b['id'] for b in agenda if b['disabled'] is False]
+        booking_date = start_date
+        bookings = []
+        while booking_date <= end_date:
+            day = booking_date.strftime(axel.json_date_format)
+            day_id = '%s:%s:%s' % (child_id, activity_id, day)
+            booked = None
+            if day_id not in available_days:
+                # disabled or not available: not bookable
+                booked = None
+            elif day_id not in legacy_days and day_id in booking_list:
+                booked = 'X'
+            elif day_id in legacy_days and day_id not in booking_list:
+                booked = 'D'
+            if booked is not None:
+                # no changes, don't send the day
+                bookings.append(
+                    {
+                        'JOURDATE': day,
+                        'MATIN': booked,
+                        'MIDI': None,
+                        'APRESMIDI': None,
+                    }
+                )
+            booking_date += datetime.timedelta(days=1)
+        if not bookings:
+            # don't call axel if no changes
+            return 0
+        try:
+            data = {
+                'PORTAIL': {
+                    'SETAGENDA': {
+                        'IDENTINDIVIDU': child_id,
+                        'ACTIVITE': {
+                            'IDENTACTIVITE': activity_id,
+                            'JOUR': bookings,
+                        },
+                    }
+                }
+            }
+            result = schemas.set_agenda(self, data)
+        except axel.AxelError as e:
+            raise APIError(
+                'Axel error: %s' % e,
+                err_code='error',
+                data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
+            )
+
+        code = result.json_response['DATA']['PORTAIL']['SETAGENDA']['CODE']
+        if code != 0:
+            raise APIError('Wrong agenda status', err_code='agenda-code-error-%s' % code)
+
+        return len(bookings)
+
     @endpoint(
         display_category=_('Schooling'),
         display_order=7,
@@ -746,65 +815,61 @@ class CaluireAxel(BaseResource):
         updated = 0
         for activity in activities_data.get('ACTIVITE', []):
             activity_id = activity['IDENTACTIVITE']
-            activity_label = activity['LIBELLEACTIVITE']
             if activity_id.startswith('CJ'):
                 # mercredi or vacances: not bookable
                 continue
-            agenda = self.get_bookings(child_id, activity_id, activity_label, start_date, end_date)
-            legacy_days = [b['id'] for b in agenda if b['prefill'] is True]
-            available_days = [b['id'] for b in agenda if b['disabled'] is False]
-            booking_date = start_date
-            bookings = []
-            while booking_date <= end_date:
-                day = booking_date.strftime(axel.json_date_format)
-                day_id = '%s:%s:%s' % (child_id, activity_id, day)
-                booked = None
-                if day_id not in available_days:
-                    # disabled or not available: not bookable
-                    booked = None
-                elif day_id not in legacy_days and day_id in post_data['booking_list']:
-                    booked = 'X'
-                elif day_id in legacy_days and day_id not in post_data['booking_list']:
-                    booked = 'D'
-                if booked is not None:
-                    # no changes, don't send the day
-                    bookings.append(
-                        {
-                            'JOURDATE': day,
-                            'MATIN': booked,
-                            'MIDI': None,
-                            'APRESMIDI': None,
-                        }
-                    )
-                booking_date += datetime.timedelta(days=1)
-            if not bookings:
-                # don't call axel if no changes
-                continue
-            try:
-                data = {
-                    'PORTAIL': {
-                        'SETAGENDA': {
-                            'IDENTINDIVIDU': child_id,
-                            'ACTIVITE': {
-                                'IDENTACTIVITE': activity_id,
-                                'JOUR': bookings,
-                            },
-                        }
-                    }
+            updated += self.set_bookings(
+                child_id, activity_id, start_date, end_date, post_data['booking_list']
+            )
+
+        return {
+            'updated': True,
+            'count': updated,
+        }
+
+    @endpoint(
+        display_category=_('Schooling'),
+        display_order=7,
+        description=_("Set activity agenda for a child with a typical week"),
+        perm='can_access',
+        parameters={
+            'NameID': {'description': _('Publik ID')},
+        },
+        post={
+            'request_body': {
+                'schema': {
+                    'application/json': schemas.TYPICAL_WEEK_BOOKING_SCHEMA,
                 }
-                result = schemas.set_agenda(self, data)
-            except axel.AxelError as e:
-                raise APIError(
-                    'Axel error: %s' % e,
-                    err_code='error',
-                    data={'xml_request': e.xml_request, 'xml_response': e.xml_response},
-                )
+            }
+        },
+    )
+    def set_activity_agenda_typical_week(self, request, NameID, post_data):
+        link = self.get_link(NameID)
+        child_id = post_data['child_id']
+        child_data = self.get_child_data(link.family_id, child_id)
+        if child_data is None:
+            raise APIError('Child not found', err_code='not-found')
+        start_date, end_date, reference_year = self.get_start_and_end_dates(
+            post_data['start_date'], post_data['end_date']
+        )
+        activity_id = post_data['activity_id']
+        activity_data = self.get_child_activity(child_id, activity_id, reference_year)
+        if activity_data is None:
+            raise APIError('Activity not found', err_code='not-found')
 
-            code = result.json_response['DATA']['PORTAIL']['SETAGENDA']['CODE']
-            if code != 0:
-                raise APIError('Wrong agenda status', err_code='agenda-code-error-%s' % code)
+        if activity_id.startswith('CJ'):
+            raise APIError('Not available for this activity', err_code='bad-request', http_status=400)
+
+        booking_date = start_date
+        bookings = []
+        while booking_date <= end_date:
+            if WEEKDAYS[booking_date.weekday()] in post_data['booking_list']:
+                bookings.append(
+                    '%s:%s:%s' % (child_id, activity_id, booking_date.strftime(axel.json_date_format))
+                )
+            booking_date += datetime.timedelta(days=1)
 
-            updated += len(bookings)
+        updated = self.set_bookings(child_id, activity_id, start_date, end_date, bookings)
 
         return {
             'updated': True,
diff --git a/passerelle/contrib/caluire_axel/schemas.py b/passerelle/contrib/caluire_axel/schemas.py
index 0e13d74d..5568c0f3 100644
--- a/passerelle/contrib/caluire_axel/schemas.py
+++ b/passerelle/contrib/caluire_axel/schemas.py
@@ -147,6 +147,38 @@ BOOKING_SCHEMA = {
     ],
 }
 
+TYPICAL_WEEK_BOOKING_SCHEMA = {
+    'type': 'object',
+    'properties': {
+        'child_id': {
+            'type': 'string',
+            'minLength': 1,
+            'maxLength': 8,
+        },
+        'activity_id': {
+            'type': 'string',
+            'minLength': 1,
+            'maxLength': 10,
+        },
+        'booking_list': {
+            'type': 'array',
+            'items': {
+                'type': 'string',
+                'pattern': '(monday|tuesday|thursday|friday)',
+            },
+        },
+        'start_date': copy.deepcopy(axel.date_type),
+        'end_date': copy.deepcopy(axel.date_type),
+    },
+    'required': [
+        'child_id',
+        'activity_id',
+        'booking_list',
+        'start_date',
+        'end_date',
+    ],
+}
+
 
 PAYMENT_SCHEMA = {
     'type': 'object',
diff --git a/tests/test_caluire_axel.py b/tests/test_caluire_axel.py
index deaef1e6..04b742a7 100644
--- a/tests/test_caluire_axel.py
+++ b/tests/test_caluire_axel.py
@@ -147,6 +147,17 @@ def booking_params():
     }
 
 
+@pytest.fixture
+def week_booking_params():
+    return {
+        'child_id': '50632',
+        'activity_id': 'ELEM',
+        'booking_list': [],
+        'start_date': '2020-09-01',
+        'end_date': '2021-08-31',
+    }
+
+
 @pytest.fixture
 def upload_attachments_params():
     return {
@@ -2020,6 +2031,400 @@ def test_set_agenda_endpoint_wrong_code(app, resource, family_data, activities,
     assert resp.json['err'] == 'agenda-code-error-%s' % code
 
 
+@freezegun.freeze_time('2020-08-01')
+def test_set_activity_agenda_typical_week_endpoint_axel_error(
+    app, resource, family_data, activities, week_booking_params
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    week_booking_params['booking_list'] = ['monday']
+    with mock.patch('passerelle.contrib.caluire_axel.schemas.get_famille_individus') as operation:
+        operation.side_effect = AxelError('FooBar')
+        resp = app.post_json(
+            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy', params=week_booking_params
+        )
+    assert resp.json['err_desc'] == "Axel error: FooBar"
+    assert resp.json['err'] == 'error'
+
+    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
+    with open(filepath) as xml:
+        content = xml.read()
+    with mock_data(content, 'GetFamilleIndividus'):
+        with mock.patch('passerelle.contrib.caluire_axel.schemas.get_list_activites') as operation:
+            operation.side_effect = AxelError('FooBar')
+            resp = app.post_json(
+                '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy', params=week_booking_params
+            )
+    assert resp.json['err_desc'] == "Axel error: FooBar"
+    assert resp.json['err'] == 'error'
+
+    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/activities_info.xml')
+    with open(filepath) as xml:
+        content = xml.read()
+    with mock_data(content, 'GetListActivites'):
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+            return_value=family_data,
+        ):
+            with mock.patch('passerelle.contrib.caluire_axel.schemas.get_agenda') as operation:
+                operation.side_effect = AxelError('FooBar')
+                resp = app.post_json(
+                    '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                    params=week_booking_params,
+                )
+    assert resp.json['err_desc'] == "Axel error: FooBar"
+    assert resp.json['err'] == 'error'
+
+    content = '''<PORTAIL>
+    <GETAGENDA>
+        <CODE>1</CODE>
+        <JOUR>
+          <JOURDATE>07/09/2020</JOURDATE>
+          <MATIN>.</MATIN>
+          <MIDI></MIDI>
+          <APRESMIDI></APRESMIDI>
+          <FERME>N</FERME>
+        </JOUR>
+    </GETAGENDA>
+</PORTAIL>'''
+    with mock_data(content, 'GetAgenda'):
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+            return_value=family_data,
+        ):
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+                return_value=activities,
+            ):
+                with mock.patch('passerelle.contrib.caluire_axel.schemas.set_agenda') as operation:
+                    operation.side_effect = AxelError('FooBar')
+                    resp = app.post_json(
+                        '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                        params=week_booking_params,
+                    )
+    assert resp.json['err_desc'] == "Axel error: FooBar"
+    assert resp.json['err'] == 'error'
+
+
+def test_set_activity_agenda_typical_week_endpoint_no_result(app, resource, week_booking_params):
+    week_booking_params['child_id'] = 'zzz'
+    resp = app.post_json(
+        '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy', params=week_booking_params
+    )
+    assert resp.json['err_desc'] == "Person not found"
+    assert resp.json['err'] == 'not-found'
+
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
+    with open(filepath) as xml:
+        content = xml.read()
+    with mock_data(content, 'GetFamilleIndividus'):
+        resp = app.post_json(
+            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy', params=week_booking_params
+        )
+        assert resp.json['err_desc'] == "Child not found"
+        assert resp.json['err'] == 'not-found'
+
+
+def test_set_activity_agenda_typical_week_endpoint_date_error(app, resource, week_booking_params):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    filepath = os.path.join(os.path.dirname(__file__), 'data/caluire_axel/family_info.xml')
+    with open(filepath) as xml:
+        content = xml.read()
+    with mock_data(content, 'GetFamilleIndividus'):
+        week_booking_params['start_date'] = '2021-09-01'
+        week_booking_params['end_date'] = '2021-08-31'
+        resp = app.post_json(
+            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+            params=week_booking_params,
+            status=400,
+        )
+        assert resp.json['err_desc'] == "start_date should be before end_date"
+        assert resp.json['err'] == 'bad-request'
+
+        week_booking_params['end_date'] = '2022-09-01'
+        resp = app.post_json(
+            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+            params=week_booking_params,
+            status=400,
+        )
+        assert (
+            resp.json['err_desc'] == "start_date and end_date are in different reference year (2021 != 2022)"
+        )
+        assert resp.json['err'] == 'bad-request'
+
+
+def test_set_activity_agenda_typical_week_endpoint(
+    app, resource, family_data, activities, week_booking_params
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    with mock.patch(
+        'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+        return_value=family_data,
+    ):
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+            return_value=activities,
+        ):
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_bookings',
+                return_value=[],
+            ):
+                resp = app.post_json(
+                    '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                    params=week_booking_params,
+                )
+    assert resp.json['err'] == 0
+    assert resp.json['updated'] is True
+    assert resp.json['count'] == 0
+
+
+@pytest.mark.parametrize('prefill', [True, False])
+@freezegun.freeze_time('2020-08-01')
+def test_set_activity_agenda_typical_week_endpoint_prefill_periscolaire(
+    app, resource, family_data, week_booking_params, prefill
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    for activity_id in ['ACCMAT', 'GARDERIES', 'ETUDES', 'CANTINE', 'FOOBAR']:
+        activity = {
+            'ENTREE': '2020-09-02',
+            'SORTIE': '2021-08-31',
+            'IDENTACTIVITE': activity_id,
+            'LIBELLEACTIVITE': 'FOOBAR',
+        }
+        week_booking_params['booking_list'] = ['monday']
+        week_booking_params['activity_id'] = activity_id
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+            return_value=family_data,
+        ):
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+                return_value={'ACTIVITE': [activity]},
+            ):
+                bookings = [
+                    {
+                        'id': '50632:%s:2020-09-07' % activity_id,
+                        'disabled': False,
+                        'prefill': prefill,
+                    },
+                    {
+                        'id': '50632:%s:2020-09-08' % activity_id,
+                        'disabled': False,
+                        'prefill': prefill,
+                    },
+                    {
+                        'id': '50632:%s:2020-09-10' % activity_id,
+                        'disabled': False,
+                        'prefill': prefill,
+                    },
+                    {
+                        'id': '50632:%s:2020-09-11' % activity_id,
+                        'disabled': False,
+                        'prefill': prefill,
+                    },
+                ]
+                with mock.patch(
+                    'passerelle.contrib.caluire_axel.models.CaluireAxel.get_bookings',
+                    return_value=bookings,
+                ):
+                    with mock.patch('passerelle.contrib.caluire_axel.schemas.set_agenda') as operation:
+                        operation.return_value = OperationResult(
+                            json_response={'DATA': {'PORTAIL': {'SETAGENDA': {'CODE': 0}}}},
+                            xml_request='',
+                            xml_response='',
+                        )
+                        resp = app.post_json(
+                            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                            params=week_booking_params,
+                        )
+        assert resp.json['err'] == 0
+        assert resp.json['updated'] is True
+        assert operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['IDENTINDIVIDU'] == '50632'
+        assert (
+            operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['IDENTACTIVITE']
+            == activity_id
+        )
+        if prefill is True:
+            assert len(operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR']) == 3
+            for i, day in enumerate(['2020-09-08', '2020-09-10', '2020-09-11']):
+                assert (
+                    operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][i][
+                        'JOURDATE'
+                    ]
+                    == day
+                )
+                assert (
+                    operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][i]['MATIN']
+                    == 'D'
+                )
+        else:
+            assert len(operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR']) == 1
+            assert (
+                operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][0]['JOURDATE']
+                == '2020-09-07'
+            )
+            assert (
+                operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][0]['MATIN']
+                == 'X'
+            )
+
+
+@freezegun.freeze_time('2020-08-01')
+def test_set_activity_agenda_typical_week_endpoint_prefill_extrascolaire(
+    app,
+    resource,
+    family_data,
+    week_booking_params,
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    for activity_id in ['CJ MER', 'CJVACANCES']:
+        activity = {
+            'ENTREE': '2020-09-02',
+            'SORTIE': '2021-08-31',
+            'IDENTACTIVITE': activity_id,
+            'LIBELLEACTIVITE': 'FOOBAR',
+        }
+        week_booking_params['booking_list'] = ['monday']
+        week_booking_params['activity_id'] = activity_id
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+            return_value=family_data,
+        ):
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+                return_value={'ACTIVITE': [activity]},
+            ):
+                bookings = [
+                    {
+                        'id': '50632:%s:2020-09-07' % activity_id,
+                        'disabled': False,
+                        'prefill': False,
+                    },
+                ]
+                with mock.patch(
+                    'passerelle.contrib.caluire_axel.models.CaluireAxel.get_bookings',
+                    return_value=bookings,
+                ):
+                    with mock.patch('passerelle.contrib.caluire_axel.schemas.set_agenda') as operation:
+                        operation.return_value = OperationResult(
+                            json_response={'DATA': {'PORTAIL': {'SETAGENDA': {'CODE': 0}}}},
+                            xml_request='',
+                            xml_response='',
+                        )
+                        resp = app.post_json(
+                            '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                            params=week_booking_params,
+                            status=400,
+                        )
+    assert resp.json['err_desc'] == "Not available for this activity"
+    assert resp.json['err'] == 'bad-request'
+
+
+@pytest.mark.parametrize('disabled', [True, False, None])
+@freezegun.freeze_time('2020-08-01')
+def test_set_activity_agenda_typical_week_endpoint_disabled(
+    app, resource, family_data, week_booking_params, disabled
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    activity = {
+        'ENTREE': '2020-09-02',
+        'SORTIE': '2021-08-31',
+        'IDENTACTIVITE': 'FOOBAR',
+        'LIBELLEACTIVITE': 'FOOBAR',
+    }
+    week_booking_params['booking_list'] = ['monday']
+    week_booking_params['activity_id'] = 'FOOBAR'
+    with mock.patch(
+        'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+        return_value=family_data,
+    ):
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+            return_value={'ACTIVITE': [activity]},
+        ):
+            bookings = []
+            if disabled is not None:
+                bookings = [
+                    {
+                        'id': '50632:FOOBAR:2020-09-07',
+                        'disabled': disabled,
+                        'prefill': False,
+                    },
+                ]
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_bookings',
+                return_value=bookings,
+            ):
+                with mock.patch('passerelle.contrib.caluire_axel.schemas.set_agenda') as operation:
+                    operation.return_value = OperationResult(
+                        json_response={'DATA': {'PORTAIL': {'SETAGENDA': {'CODE': 0}}}},
+                        xml_request='',
+                        xml_response='',
+                    )
+                    resp = app.post_json(
+                        '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                        params=week_booking_params,
+                    )
+    assert resp.json['err'] == 0
+    assert resp.json['updated'] is True
+    if disabled is False:
+        assert operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['IDENTINDIVIDU'] == '50632'
+        assert (
+            operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['IDENTACTIVITE'] == 'FOOBAR'
+        )
+        assert len(operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR']) == 1
+        assert (
+            operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][0]['JOURDATE']
+            == '2020-09-07'
+        )
+        assert (
+            operation.call_args_list[0][0][1]['PORTAIL']['SETAGENDA']['ACTIVITE']['JOUR'][0]['MATIN'] == 'X'
+        )
+    else:
+        assert len(operation.call_args_list) == 0
+
+
+@pytest.mark.parametrize('code', [-1, -2, -3, -4])
+def test_set_activity_agenda_typical_week_endpoint_wrong_code(
+    app, resource, family_data, activities, week_booking_params, code
+):
+    Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
+    bookings = [
+        {
+            'id': '50632:ELEM:2020-09-07',
+            'disabled': False,
+            'prefill': True,
+        },
+    ]
+    content = (
+        '''<PORTAIL>
+    <SETAGENDA>
+        <CODE>%s</CODE>
+    </SETAGENDA>
+</PORTAIL>'''
+        % code
+    )
+    with mock_data(content, 'SetAgenda', data_method='setData'):
+        with mock.patch(
+            'passerelle.contrib.caluire_axel.models.CaluireAxel.get_family_data',
+            return_value=family_data,
+        ):
+            with mock.patch(
+                'passerelle.contrib.caluire_axel.models.CaluireAxel.get_child_activities',
+                return_value=activities,
+            ):
+                with mock.patch(
+                    'passerelle.contrib.caluire_axel.models.CaluireAxel.get_bookings',
+                    return_value=bookings,
+                ):
+                    resp = app.post_json(
+                        '/caluire-axel/test/set_activity_agenda_typical_week?NameID=yyy',
+                        params=week_booking_params,
+                    )
+    assert resp.json['err_desc'] == "Wrong agenda status"
+    assert resp.json['err'] == 'agenda-code-error-%s' % code
+
+
 def test_invoices_endpoint_axel_error(app, resource):
     Link.objects.create(resource=resource, name_id='yyy', family_id='XXX', person_id='42')
     with mock.patch('passerelle.contrib.caluire_axel.schemas.get_factures_a_payer') as operation:
-- 
2.30.2

