From 491bb3feafa835f53d0d2d40c39d8147c8149e0f Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Tue, 22 Mar 2022 16:30:55 +0100 Subject: [PATCH] add school holidays connector (#63013) --- debian/control | 1 + passerelle/apps/holidays/__init__.py | 0 passerelle/apps/holidays/forms.py | 32 ++ .../apps/holidays/migrations/0001_initial.py | 65 +++ .../apps/holidays/migrations/__init__.py | 0 passerelle/apps/holidays/models.py | 114 +++++ passerelle/settings.py | 1 + passerelle/urls.py | 2 +- setup.py | 1 + tests/data/holidays/Zone-A.ics | 416 ++++++++++++++++++ tests/data/holidays/Zone-B.ics | 416 ++++++++++++++++++ tests/test_holidays.py | 133 ++++++ 12 files changed, 1180 insertions(+), 1 deletion(-) create mode 100644 passerelle/apps/holidays/__init__.py create mode 100644 passerelle/apps/holidays/forms.py create mode 100644 passerelle/apps/holidays/migrations/0001_initial.py create mode 100644 passerelle/apps/holidays/migrations/__init__.py create mode 100644 passerelle/apps/holidays/models.py create mode 100644 tests/data/holidays/Zone-A.ics create mode 100644 tests/data/holidays/Zone-B.ics create mode 100644 tests/test_holidays.py diff --git a/debian/control b/debian/control index 9a30c88e..6944446e 100644 --- a/debian/control +++ b/debian/control @@ -37,6 +37,7 @@ Depends: ${python3:Depends}, python3-feedparser, python3-pdfrw, python3-httplib2, + python3-vobject, python3-xmlschema Description: Uniform access to multiple data sources and services (Python module) diff --git a/passerelle/apps/holidays/__init__.py b/passerelle/apps/holidays/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/passerelle/apps/holidays/forms.py b/passerelle/apps/holidays/forms.py new file mode 100644 index 00000000..8183a186 --- /dev/null +++ b/passerelle/apps/holidays/forms.py @@ -0,0 +1,32 @@ +# passerelle - uniform access to multiple data sources and services +# Copyright (C) 2022 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from passerelle.forms import GenericConnectorForm + + +class HolidaysConnectorForm(GenericConnectorForm): + def __init__(self, *args, **kwargs): + from .models import HOLIDAYS_CHOICES + + super().__init__(*args, **kwargs) + self.fields['holidays'] = forms.MultipleChoiceField( + choices=HOLIDAYS_CHOICES, + widget=forms.CheckboxSelectMultiple, + initial=[x[0] for x in HOLIDAYS_CHOICES], + label=_('Holidays'), + ) diff --git a/passerelle/apps/holidays/migrations/0001_initial.py b/passerelle/apps/holidays/migrations/0001_initial.py new file mode 100644 index 00000000..63064488 --- /dev/null +++ b/passerelle/apps/holidays/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 2.2.19 on 2022-03-23 12:09 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('base', '0029_auto_20210202_1627'), + ] + + operations = [ + migrations.CreateModel( + name='Holidays', + fields=[ + ( + 'id', + models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ('title', models.CharField(max_length=50, verbose_name='Title')), + ('slug', models.SlugField(unique=True, verbose_name='Identifier')), + ('description', models.TextField(verbose_name='Description')), + ( + 'zone', + models.CharField( + choices=[('a', 'A'), ('b', 'B'), ('c', 'C')], max_length=16, verbose_name='Zone' + ), + ), + ( + 'holidays', + django.contrib.postgres.fields.ArrayField( + base_field=models.CharField( + choices=[ + ('winter_holidays', 'Vacances d’Hiver'), + ('spring_holidays', 'Vacances de Pâques'), + ('summer_holidays', 'Vacances d’Été'), + ('all_saints_holidays', 'Vacances de la Toussaint'), + ('christmas_holidays', 'Vacances de Noël'), + ('fathers_day', 'Fête des Pères'), + ('mothers_day', 'Fête des Mères'), + ], + max_length=32, + ), + size=None, + verbose_name='Holidays', + ), + ), + ( + 'users', + models.ManyToManyField( + blank=True, + related_name='_holidays_users_+', + related_query_name='+', + to='base.ApiUser', + ), + ), + ], + options={ + 'verbose_name': 'School holidays', + }, + ), + ] diff --git a/passerelle/apps/holidays/migrations/__init__.py b/passerelle/apps/holidays/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/passerelle/apps/holidays/models.py b/passerelle/apps/holidays/models.py new file mode 100644 index 00000000..bd414c68 --- /dev/null +++ b/passerelle/apps/holidays/models.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2022 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import requests +import vobject +from django.contrib.postgres.fields import ArrayField +from django.db import models +from django.http import HttpResponse +from django.utils.translation import ugettext_lazy as _ + +from passerelle.base.models import BaseResource +from passerelle.utils.api import endpoint +from passerelle.utils.jsonresponse import APIError + +from .forms import HolidaysConnectorForm + +ZONE_CHOICES = ( + ('a', 'A'), + ('b', 'B'), + ('c', 'C'), +) + +HOLIDAYS_LABELS = { + 'winter_holidays': "Vacances d’Hiver", + 'spring_holidays': 'Vacances de Pâques', + 'summer_holidays': "Vacances d’Été", + 'all_saints_holidays': 'Vacances de la Toussaint', + 'christmas_holidays': 'Vacances de Noël', +} + + +HOLIDAYS_CHOICES = [(x[0], x[1]) for x in HOLIDAYS_LABELS.items()] + + +ZONE_URLS = { + 'a': 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-A.ics', + 'b': 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-B.ics', + 'c': 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-C.ics', +} + + +HOLIDAYS_MAPPING = { + "Vacances d'Hiver": 'winter_holidays', + 'Vacances de Printemps': 'spring_holidays', + "Vacances d'Été (Élèves)": 'summer_holidays', + "Vacances d'Été": 'summer_holidays', + 'Vacances de la Toussaint': 'all_saints_holidays', + 'Vacances de Noël': 'christmas_holidays', +} + + +class Holidays(BaseResource): + zone = models.CharField(_('Zone'), max_length=16, choices=ZONE_CHOICES) + holidays = ArrayField( + models.CharField(max_length=32, choices=HOLIDAYS_CHOICES), + verbose_name=_('Holidays'), + blank=False, + null=False, + ) + + category = _('Misc') + manager_form_base_class = HolidaysConnectorForm + + class Meta: + verbose_name = _('School holidays') + + def get_holidays_display(self): + return ', '.join(HOLIDAYS_LABELS[holiday] for holiday in self.holidays) + + @endpoint(name='holidays.ics', description=_('Get holidays ICS.')) + def holidays_ics(self, request): + try: + response = requests.get(ZONE_URLS[self.zone]) + response.raise_for_status() + except requests.exceptions.RequestException as e: + raise APIError('Error while getting ICS file: %s' % e) + + try: + parsed = vobject.readOne(response.text) + except vobject.base.ParseError as e: + raise APIError('Invalid ICS file: %s' % e) + + if not parsed.contents.get('vevent'): + raise APIError('ICS file does not contain events.') + + cal = vobject.iCalendar() + for vevent in parsed.contents.get('vevent'): + description = vevent.contents['description'][0].value + + holiday_id = HOLIDAYS_MAPPING.get(description) + if holiday_id not in self.holidays: + continue + + new_vevent = cal.add('vevent') + new_vevent.add('summary').value = HOLIDAYS_LABELS[holiday_id] + new_vevent.add('uid').value = '%s-%s' % (holiday_id, vevent.dtstart.value.year) + new_vevent.add('categories').value = [holiday_id] + new_vevent.add('dtstart').value = vevent.dtstart.value + new_vevent.add('dtend').value = vevent.dtend.value + + return HttpResponse(cal.serialize(), content_type='text/calendar') diff --git a/passerelle/settings.py b/passerelle/settings.py index 0152f331..35db8f76 100644 --- a/passerelle/settings.py +++ b/passerelle/settings.py @@ -146,6 +146,7 @@ INSTALLED_APPS = ( 'passerelle.apps.franceconnect_data', 'passerelle.apps.gdc', 'passerelle.apps.gesbac', + 'passerelle.apps.holidays', 'passerelle.apps.jsondatastore', 'passerelle.apps.maelis', 'passerelle.apps.mdel', diff --git a/passerelle/urls.py b/passerelle/urls.py index 347e84c1..4fe11fc1 100644 --- a/passerelle/urls.py +++ b/passerelle/urls.py @@ -125,7 +125,7 @@ urlpatterns += [ r'^(?P[\w,-]+)/(?P[\w,-]+)/$', GenericConnectorView.as_view(), name='view-connector' ), url( - r'^(?P[\w,-]+)/(?P[\w,-]+)/(?P[\w,-]+)(?:/(?P.*))?$', + r'^(?P[\w,-]+)/(?P[\w,-]+)/(?P[\w,-.]+)(?:/(?P.*))?$', GenericEndpointView.as_view(), name='generic-endpoint', ), diff --git a/setup.py b/setup.py index bb614f6e..b8042ee6 100755 --- a/setup.py +++ b/setup.py @@ -125,6 +125,7 @@ setup( 'httplib2', 'xmlschema', 'pytz', + 'vobject', ], cmdclass={ 'build': build, diff --git a/tests/data/holidays/Zone-A.ics b/tests/data/holidays/Zone-A.ics new file mode 100644 index 00000000..c8680a31 --- /dev/null +++ b/tests/data/holidays/Zone-A.ics @@ -0,0 +1,416 @@ +BEGIN:VCALENDAR +PRODID:-//MENJS//Calendrier//FR +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:Calendrier scolaire - Zone A +X-WR-TIMEZONE:Europe/Paris +X-WR-CALDESC:Congés scolaires de la zone A depuis l'année 2017-2018 +BEGIN:VTIMEZONE +TZID:Europe/Paris +X-LIC-LOCATION:Europe/Paris +BEGIN:DAYLIGHT +DTSTART:19700329T020000 +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=-1SU +TZNAME:CET +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19701025T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20171021 +DTEND;VALUE=DATE:20171106 +SUMMARY:Vacances de la Toussaint +UID:20220301T030012Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20171223 +DTEND;VALUE=DATE:20180108 +SUMMARY:Vacances de Noël +UID:20220301T030013Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20180210 +DTEND;VALUE=DATE:20180226 +SUMMARY:Vacances d'Hiver +UID:20220301T030014Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20180407 +DTEND;VALUE=DATE:20180423 +SUMMARY:Vacances de Printemps +UID:20220301T030015Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20180707 +DTEND;VALUE=DATE:20180831 +SUMMARY:Vacances d'Été +UID:20220301T030016Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Enseignants) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20180707 +DTEND;VALUE=DATE:20180903 +SUMMARY:Vacances d'Été +UID:20220301T030017Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Élèves) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20181020 +DTEND;VALUE=DATE:20181105 +SUMMARY:Vacances de la Toussaint +UID:20220301T030018Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20181222 +DTEND;VALUE=DATE:20190107 +SUMMARY:Vacances de Noël +UID:20220301T030019Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190216 +DTEND;VALUE=DATE:20190304 +SUMMARY:Vacances d'Hiver +UID:20220301T030020Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190413 +DTEND;VALUE=DATE:20190429 +SUMMARY:Vacances de Printemps +UID:20220301T030021Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190529 +DTEND;VALUE=DATE:20190603 +SUMMARY:Pont de l'Ascension +UID:20220301T030022Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190706 +DTEND;VALUE=DATE:20190830 +SUMMARY:Vacances d'Été +UID:20220301T030023Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Enseignants) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190706 +DTEND;VALUE=DATE:20190902 +SUMMARY:Vacances d'Été +UID:20220301T030024Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Élèves) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20191019 +DTEND;VALUE=DATE:20191104 +SUMMARY:Vacances de la Toussaint +UID:20220301T030025Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20191221 +DTEND;VALUE=DATE:20200106 +SUMMARY:Vacances de Noël +UID:20220301T030026Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20200222 +DTEND;VALUE=DATE:20200309 +SUMMARY:Vacances d'Hiver +UID:20220301T030027Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20200418 +DTEND;VALUE=DATE:20200504 +SUMMARY:Vacances de Printemps +UID:20220301T030028Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20200520 +DTEND;VALUE=DATE:20200525 +SUMMARY:Pont de l'Ascension +UID:20220301T030029Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20200704 +DTEND;VALUE=DATE:20200901 +SUMMARY:Vacances d'Été +UID:20220301T030030Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20201017 +DTEND;VALUE=DATE:20201102 +SUMMARY:Vacances de la Toussaint +UID:20220301T030031Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20201219 +DTEND;VALUE=DATE:20210104 +SUMMARY:Vacances de Noël +UID:20220301T030032Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210206 +DTEND;VALUE=DATE:20210222 +SUMMARY:Vacances d'Hiver +UID:20220301T030033Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210410 +DTEND;VALUE=DATE:20210426 +SUMMARY:Vacances de Printemps +UID:20220301T030034Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210513 +DTEND;VALUE=DATE:20210517 +SUMMARY:Pont de l'Ascension +UID:20220301T030035Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210706 +DTEND;VALUE=DATE:20210901 +SUMMARY:Vacances d'Été +UID:20220301T030036Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Enseignants) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210706 +DTEND;VALUE=DATE:20210902 +SUMMARY:Vacances d'Été +UID:20220301T030037Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Élèves) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20211023 +DTEND;VALUE=DATE:20211108 +SUMMARY:Vacances de la Toussaint +UID:20220301T030038Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20211218 +DTEND;VALUE=DATE:20220103 +SUMMARY:Vacances de Noël +UID:20220301T030039Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220212 +DTEND;VALUE=DATE:20220228 +SUMMARY:Vacances d'Hiver +UID:20220301T030040Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220416 +DTEND;VALUE=DATE:20220502 +SUMMARY:Vacances de Printemps +UID:20220301T030041Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220526 +DTEND;VALUE=DATE:20220528 +SUMMARY:Pont de l'Ascension +UID:20220301T030042Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220707 +DTEND;VALUE=DATE:20220831 +SUMMARY:Vacances d'Été +UID:20220301T030043Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Enseignants) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220707 +DTEND;VALUE=DATE:20220901 +SUMMARY:Vacances d'Été +UID:20220301T030044Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Élèves) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20221022 +DTEND;VALUE=DATE:20221107 +SUMMARY:Vacances de la Toussaint +UID:20220301T030045Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20221217 +DTEND;VALUE=DATE:20230103 +SUMMARY:Vacances de Noël +UID:20220301T030046Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20230204 +DTEND;VALUE=DATE:20230220 +SUMMARY:Vacances d'Hiver +UID:20220301T030047Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20230408 +DTEND;VALUE=DATE:20230424 +SUMMARY:Vacances de Printemps +UID:20220301T030048Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20230518 +DTEND;VALUE=DATE:20230522 +SUMMARY:Pont de l'Ascension +UID:20220301T030049Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20230708 +DTEND;VALUE=DATE:20230708 +SUMMARY:Début des Vacances d'Été +UID:20220301T030050Z-Zone-A@data.education.gouv.fr +LOCATION: +DESCRIPTION:Début des Vacances d'Été +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR diff --git a/tests/data/holidays/Zone-B.ics b/tests/data/holidays/Zone-B.ics new file mode 100644 index 00000000..bc13fefe --- /dev/null +++ b/tests/data/holidays/Zone-B.ics @@ -0,0 +1,416 @@ +BEGIN:VCALENDAR +PRODID:-//MENJS//Calendrier//FR +VERSION:2.0 +CALSCALE:GREGORIAN +X-WR-CALNAME:Calendrier scolaire - Zone B +X-WR-TIMEZONE:Europe/Paris +X-WR-CALDESC:Congés scolaires de la zone B depuis l'année 2017-2018 +BEGIN:VTIMEZONE +TZID:Europe/Paris +X-LIC-LOCATION:Europe/Paris +BEGIN:DAYLIGHT +DTSTART:19700329T020000 +TZNAME:CEST +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3;BYDAY=-1SU +TZNAME:CET +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19701025T030000 +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=-1SU +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20171021 +DTEND;VALUE=DATE:20171106 +SUMMARY:Vacances de la Toussaint +UID:20220301T030051Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20171223 +DTEND;VALUE=DATE:20180108 +SUMMARY:Vacances de Noël +UID:20220301T030052Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20180224 +DTEND;VALUE=DATE:20180312 +SUMMARY:Vacances d'Hiver +UID:20220301T030053Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20180421 +DTEND;VALUE=DATE:20180507 +SUMMARY:Vacances de Printemps +UID:20220301T030054Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20180707 +DTEND;VALUE=DATE:20180831 +SUMMARY:Vacances d'Été +UID:20220301T030055Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Enseignants) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20180707 +DTEND;VALUE=DATE:20180903 +SUMMARY:Vacances d'Été +UID:20220301T030056Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Élèves) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20181020 +DTEND;VALUE=DATE:20181105 +SUMMARY:Vacances de la Toussaint +UID:20220301T030057Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20181222 +DTEND;VALUE=DATE:20190107 +SUMMARY:Vacances de Noël +UID:20220301T030058Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190209 +DTEND;VALUE=DATE:20190225 +SUMMARY:Vacances d'Hiver +UID:20220301T030059Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190406 +DTEND;VALUE=DATE:20190423 +SUMMARY:Vacances de Printemps +UID:20220301T030100Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190529 +DTEND;VALUE=DATE:20190603 +SUMMARY:Pont de l'Ascension +UID:20220301T030101Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190706 +DTEND;VALUE=DATE:20190830 +SUMMARY:Vacances d'Été +UID:20220301T030102Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Enseignants) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20190706 +DTEND;VALUE=DATE:20190902 +SUMMARY:Vacances d'Été +UID:20220301T030103Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Élèves) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20191019 +DTEND;VALUE=DATE:20191104 +SUMMARY:Vacances de la Toussaint +UID:20220301T030104Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20191221 +DTEND;VALUE=DATE:20200106 +SUMMARY:Vacances de Noël +UID:20220301T030105Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20200215 +DTEND;VALUE=DATE:20200302 +SUMMARY:Vacances d'Hiver +UID:20220301T030106Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20200411 +DTEND;VALUE=DATE:20200427 +SUMMARY:Vacances de Printemps +UID:20220301T030107Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20200520 +DTEND;VALUE=DATE:20200525 +SUMMARY:Pont de l'Ascension +UID:20220301T030108Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20200704 +DTEND;VALUE=DATE:20200901 +SUMMARY:Vacances d'Été +UID:20220301T030109Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20201017 +DTEND;VALUE=DATE:20201102 +SUMMARY:Vacances de la Toussaint +UID:20220301T030110Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20201219 +DTEND;VALUE=DATE:20210104 +SUMMARY:Vacances de Noël +UID:20220301T030111Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210220 +DTEND;VALUE=DATE:20210308 +SUMMARY:Vacances d'Hiver +UID:20220301T030112Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210410 +DTEND;VALUE=DATE:20210426 +SUMMARY:Vacances de Printemps +UID:20220301T030113Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210513 +DTEND;VALUE=DATE:20210517 +SUMMARY:Pont de l'Ascension +UID:20220301T030114Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210706 +DTEND;VALUE=DATE:20210901 +SUMMARY:Vacances d'Été +UID:20220301T030115Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Enseignants) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20210706 +DTEND;VALUE=DATE:20210902 +SUMMARY:Vacances d'Été +UID:20220301T030116Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Élèves) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20211023 +DTEND;VALUE=DATE:20211108 +SUMMARY:Vacances de la Toussaint +UID:20220301T030117Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20211218 +DTEND;VALUE=DATE:20220103 +SUMMARY:Vacances de Noël +UID:20220301T030118Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220205 +DTEND;VALUE=DATE:20220221 +SUMMARY:Vacances d'Hiver +UID:20220301T030119Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220409 +DTEND;VALUE=DATE:20220425 +SUMMARY:Vacances de Printemps +UID:20220301T030120Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220526 +DTEND;VALUE=DATE:20220528 +SUMMARY:Pont de l'Ascension +UID:20220301T030121Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220707 +DTEND;VALUE=DATE:20220831 +SUMMARY:Vacances d'Été +UID:20220301T030122Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Enseignants) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20220707 +DTEND;VALUE=DATE:20220901 +SUMMARY:Vacances d'Été +UID:20220301T030123Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Été (Élèves) +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20221022 +DTEND;VALUE=DATE:20221107 +SUMMARY:Vacances de la Toussaint +UID:20220301T030124Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de la Toussaint +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20221217 +DTEND;VALUE=DATE:20230103 +SUMMARY:Vacances de Noël +UID:20220301T030125Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Noël +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20230211 +DTEND;VALUE=DATE:20230227 +SUMMARY:Vacances d'Hiver +UID:20220301T030126Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances d'Hiver +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20230415 +DTEND;VALUE=DATE:20230502 +SUMMARY:Vacances de Printemps +UID:20220301T030127Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Vacances de Printemps +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20230518 +DTEND;VALUE=DATE:20230522 +SUMMARY:Pont de l'Ascension +UID:20220301T030128Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Pont de l'Ascension +TRANSP:TRANSPARENT +END:VEVENT +BEGIN:VEVENT +DTSTAMP:20220301T030012Z +DTSTART;VALUE=DATE:20230708 +DTEND;VALUE=DATE:20230708 +SUMMARY:Début des Vacances d'Été +UID:20220301T030129Z-Zone-B@data.education.gouv.fr +LOCATION: +DESCRIPTION:Début des Vacances d'Été +TRANSP:TRANSPARENT +END:VEVENT +END:VCALENDAR diff --git a/tests/test_holidays.py b/tests/test_holidays.py new file mode 100644 index 00000000..de5bb50d --- /dev/null +++ b/tests/test_holidays.py @@ -0,0 +1,133 @@ +import os + +import pytest +import vobject +from httmock import HTTMock, with_httmock + +from passerelle.apps.holidays.models import Holidays + +from .test_manager import login + +pytestmark = pytest.mark.django_db + +with open(os.path.join(os.path.dirname(__file__), 'data/holidays/Zone-A.ics')) as fd: + RESPONSE_ZONE_A = fd.read() + +with open(os.path.join(os.path.dirname(__file__), 'data/holidays/Zone-B.ics')) as fd: + RESPONSE_ZONE_B = fd.read() + + +def ics_data(url, request): + if url.path == 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-A.ics': + return {'content': RESPONSE_ZONE_A, 'request': request, 'status_code': 200} + if url.path == 'https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-B.ics': + return {'content': RESPONSE_ZONE_B, 'request': request, 'status_code': 200} + + +@with_httmock(ics_data) +def test_holidays_ics(app): + connector = Holidays.objects.create(slug='test', zone='a', holidays=[]) + + resp = app.get('/holidays/test/holidays.ics') + calendar = vobject.readOne(resp.text) + assert not 'vevent' in calendar.contents + + connector.holidays.append('winter_holidays') + connector.save() + + resp = app.get('/holidays/test/holidays.ics') + calendar = vobject.readOne(resp.text) + first_event = calendar.contents['vevent'][0] + assert first_event.uid.value == 'winter_holidays-2018' + assert first_event.summary.value == 'Vacances d’Hiver' + assert first_event.categories.value == ['winter_holidays'] + assert str(first_event.dtstart.value) == '2018-02-10' + assert str(first_event.dtend.value) == '2018-02-26' + + for i, event in enumerate(calendar.contents['vevent'][1:], start=2019): + assert event.uid.value == 'winter_holidays-%s' % i + assert event.summary.value == 'Vacances d’Hiver' + assert event.categories.value == ['winter_holidays'] + + connector.zone = 'b' + connector.save() + + resp = app.get('/holidays/test/holidays.ics') + calendar = vobject.readOne(resp.text) + first_event = calendar.contents['vevent'][0] + assert first_event.uid.value == 'winter_holidays-2018' + assert str(first_event.dtstart.value) == '2018-02-24' + assert str(first_event.dtend.value) == '2018-03-12' + + connector.holidays.append('summer_holidays') + connector.save() + + resp = app.get('/holidays/test/holidays.ics') + calendar = vobject.readOne(resp.text) + assert calendar.contents['vevent'][0].uid.value == 'winter_holidays-2018' + first_summer_event = next(x for x in calendar.contents['vevent'] if x.uid.value.startswith('summer')) + assert first_summer_event.uid.value == 'summer_holidays-2018' + assert first_summer_event.summary.value == 'Vacances d’Été' + assert first_summer_event.categories.value == ['summer_holidays'] + assert str(first_summer_event.dtstart.value) == '2018-07-07' + assert str(first_summer_event.dtend.value) == '2018-09-03' + + connector.holidays = [ + 'winter_holidays', + 'spring_holidays', + 'summer_holidays', + 'all_saints_holidays', + 'christmas_holidays', + ] + connector.save() + + resp = app.get('/holidays/test/holidays.ics') + calendar = vobject.readOne(resp.text) + holiday_ids = {x.categories.value[0] for x in calendar.contents['vevent']} + assert all(holiday_id in holiday_ids for holiday_id in connector.holidays) + + +def test_holidays_ics_invalid_responses(app): + Holidays.objects.create(slug='test', zone='a', holidays=[]) + + def server_error(url, request): + return {'content': 'error', 'status_code': 500} + + with HTTMock(server_error): + resp = app.get('/holidays/test/holidays.ics') + assert resp.json['err'] == 1 + assert 'Error while getting ICS file' in resp.json['err_desc'] + + def invalid_file(url, request): + return {'content': 'invalid', 'status_code': 200} + + with HTTMock(invalid_file): + resp = app.get('/holidays/test/holidays.ics') + assert resp.json['err'] == 1 + assert 'Invalid ICS file' in resp.json['err_desc'] + + def empty_file(url, request): + return { + 'content': 'BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//PYVOBJECT//NONSGML Version 1//EN\r\nEND:VCALENDAR\r\n', + 'status_code': 200, + } + + with HTTMock(empty_file): + resp = app.get('/holidays/test/holidays.ics') + assert resp.json['err'] == 1 + assert 'ICS file does not contain events' in resp.json['err_desc'] + + +def test_holidays_manager(app, admin_user): + login(app) + + resp = app.get('/manage/holidays/add') + resp.form['title'] = 'Holidays' + resp.form['slug'] = 'test' + resp.form['description'] = 'test' + resp.form['zone'] = 'c' + resp.form['holidays'] = ['summer_holidays', 'christmas_holidays'] + + resp = resp.form.submit().follow() + assert 'Holidays:\n Vacances d’Été, Vacances de Noël' in resp.text + assert 'Zone:\n C' in resp.text -- 2.30.2