Projet

Général

Profil

0001-misc-split-manager-tests.patch

Lauréline Guérin, 10 août 2021 14:59

Télécharger (145 ko)

Voir les différences:

Subject: [PATCH 1/2] misc: split manager tests

 tests/manager/test_all.py        | 1813 +++---------------------------
 tests/manager/test_exception.py  | 1316 ++++++++++++++++++++++
 tests/manager/test_timeperiod.py |  233 ++++
 3 files changed, 1707 insertions(+), 1655 deletions(-)
 create mode 100644 tests/manager/test_exception.py
 create mode 100644 tests/manager/test_timeperiod.py
tests/manager/test_all.py
1 1
import copy
2 2
import datetime
3 3
import json
4
import os
5 4
from unittest import mock
6 5

  
7 6
import freezegun
8 7
import pytest
9 8
import requests
10 9
from django.contrib.auth.models import Group
11
from django.core.files.base import ContentFile
12 10
from django.core.management import call_command
13 11
from django.db import connection
14 12
from django.test import override_settings
15 13
from django.test.utils import CaptureQueriesContext
16
from django.urls import reverse
17 14
from django.utils.encoding import force_text
18 15
from django.utils.timezone import localtime, make_aware, now
19 16
from webtest import Upload
......
32 29
    UnavailabilityCalendar,
33 30
    VirtualMember,
34 31
)
35
from chrono.manager.forms import TimePeriodExceptionForm
36 32
from chrono.utils.signature import check_query
37 33

  
38 34
pytestmark = pytest.mark.django_db
......
335 331
    assert agenda.desk_simple_management is False
336 332

  
337 333

  
338
@override_settings(
339
    EXCEPTIONS_SOURCES={
340
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
341
    }
342
)
343
def test_add_agenda_exceptions_from_settings(app, admin_user):
344
    app = login(app)
345
    resp = app.get('/manage/', status=200)
346
    resp = resp.click('New')
347
    resp.form['label'] = 'Foo bar'
348
    resp.form['kind'] = 'meetings'
349
    resp = resp.form.submit().follow()
350

  
351
    agenda = Agenda.objects.get(label='Foo bar')
352
    assert agenda.desk_set.count() == 1
353

  
354
    default_desk = agenda.desk_set.first()
355
    assert default_desk.timeperiodexception_set.exists()
356
    assert default_desk.timeperiodexceptionsource_set.count() == 1
357

  
358
    source = default_desk.timeperiodexceptionsource_set.first()
359
    assert source.enabled
360

  
361
    agenda.desk_simple_management = False
362
    agenda.save()
363
    resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
364
    resp.form['label'] = 'Desk A'
365
    resp = resp.form.submit().follow()
366

  
367
    desk = Desk.objects.get(slug='desk-a')
368
    assert desk.timeperiodexception_set.exists()
369
    assert desk.timeperiodexceptionsource_set.count() == 1
370

  
371
    source = desk.timeperiodexceptionsource_set.first()
372
    assert source.enabled
373

  
374

  
375 334
def test_add_agenda_as_manager(app, manager_user):
376 335
    # open /manage/ access to manager_user, and check agenda creation is not
377 336
    # allowed.
......
909 868
    meeting_type = MeetingType(agenda=agenda, label='Blah')
910 869
    meeting_type.save()
911 870

  
912
    app = login(app)
913
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
914
    resp = resp.click('Blah')
915
    resp = resp.click('Delete')
916
    assert 'Are you sure you want to delete this?' in resp.text
917
    assert 'disabled' not in resp.text
918
    resp = resp.form.submit()
919
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
920
    meeting_type.refresh_from_db()
921
    assert meeting_type.deleted is True
922
    assert '__deleted__' in meeting_type.slug
923

  
924
    # meeting type not showing up anymore
925
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
926
    assert 'Meeting Type Foo' not in resp.text
927

  
928
    # it is possible to add a new meeting type with the same slug
929
    new_meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
930
    assert new_meeting_type.slug == 'blah'
931

  
932

  
933
def test_meetings_agenda_add_time_period(app, admin_user):
934
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
935
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
936
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
937
    MeetingType.objects.create(agenda=agenda, label='Blah')
938
    app = login(app)
939
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
940
    resp = resp.click('Add a time period', index=0)
941
    resp.form.get('weekdays', index=2).checked = True
942
    resp.form['start_time'] = '10:00'
943
    resp.form['end_time'] = '17:00'
944
    resp = resp.form.submit()
945
    assert TimePeriod.objects.get(desk=desk).weekday == 2
946
    assert TimePeriod.objects.get(desk=desk).start_time.hour == 10
947
    assert TimePeriod.objects.get(desk=desk).start_time.minute == 0
948
    assert TimePeriod.objects.get(desk=desk).end_time.hour == 17
949
    assert TimePeriod.objects.get(desk=desk).end_time.minute == 0
950
    assert desk2.timeperiod_set.exists() is False
951
    resp = resp.follow()
952

  
953
    # add a second time period
954
    resp = resp.click('Add a time period', index=0)
955
    resp.form.get('weekdays', index=0).checked = True
956
    resp.form['start_time'] = '10:00'
957
    resp.form['end_time'] = '13:00'
958
    resp = resp.form.submit()
959
    resp = resp.follow()
960
    assert 'Monday / 10 a.m. → 1 p.m.' in resp.text
961
    assert 'Wednesday / 10 a.m. → 5 p.m.' in resp.text
962
    assert resp.text.index('Monday') < resp.text.index('Wednesday')
963

  
964
    # invert start and end
965
    resp2 = resp.click('Add a time period', index=0)
966
    resp2.form.get('weekdays', index=0).checked = True
967
    resp2.form['start_time'] = '13:00'
968
    resp2.form['end_time'] = '10:00'
969
    resp2 = resp2.form.submit()
970
    assert 'End time must come after start time.' in resp2.text
971

  
972
    # and add same time periods on multiple days
973
    resp = resp.click('Add a time period', index=0)
974
    resp.form.get('weekdays', index=4).checked = True
975
    resp.form.get('weekdays', index=5).checked = True
976
    resp.form['start_time'] = '10:00'
977
    resp.form['end_time'] = '13:00'
978
    resp = resp.form.submit()
979
    assert TimePeriod.objects.filter(desk=desk).count() == 4
980

  
981

  
982
def test_meetings_agenda_add_time_period_desk_simple_management(app, admin_user):
983
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
984
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
985
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
986
    assert agenda.is_available_for_simple_management() is True
987

  
988
    app = login(app)
989
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period' % (agenda.pk, desk.pk))
990
    resp.form.get('weekdays', index=0).checked = True
991
    resp.form['start_time'] = '10:00'
992
    resp.form['end_time'] = '13:00'
993
    resp.form.submit()
994

  
995
    assert TimePeriod.objects.filter(desk=desk).count() == 1
996
    assert TimePeriod.objects.filter(desk=desk2).count() == 1
997
    assert agenda.is_available_for_simple_management() is True
998

  
999

  
1000
def test_meetings_agenda_add_time_period_on_missing_desk(app, admin_user):
1001
    app = login(app)
1002
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1003
    app.get('/manage/agendas/%s/desk/0/add-time-period' % agenda.pk, status=404)
1004

  
1005

  
1006
def test_meetings_agenda_add_time_period_as_manager(app, manager_user):
1007
    agenda = Agenda(label='Foo bar', kind='meetings')
1008
    agenda.view_role = manager_user.groups.all()[0]
1009
    agenda.save()
1010
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1011
    app = login(app, username='manager', password='manager')
1012
    resp = app.get('/manage/agendas/%d/' % agenda.id)
1013
    assert not 'Settings' in resp.text
1014
    resp = app.get('/manage/agendas/%d/settings' % agenda.id, status=403)
1015
    MeetingType(agenda=agenda, label='Blah').save()
1016
    app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=403)
1017
    time_period = TimePeriod(
1018
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
1019
    )
1020
    time_period.save()
1021
    resp = app.get('/manage/agendas/%d/' % agenda.id)
1022
    app.get('/manage/timeperiods/%d/edit' % time_period.id, status=403)
1023
    app.get('/manage/timeperiods/%d/delete' % time_period.id, status=403)
1024
    # grant edit right to manager
1025
    agenda.edit_role = manager_user.groups.all()[0]
1026
    agenda.save()
1027

  
1028
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1029
    assert 'Add a time period' in resp.text
1030
    assert '/manage/timeperiods/%s/edit' % time_period.id in resp.text
1031
    assert '/manage/timeperiods/%s/delete' % time_period.id in resp.text
1032

  
1033
    app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=200)
1034
    app.get('/manage/timeperiods/%d/edit' % time_period.id, status=200)
1035

  
1036

  
1037
def test_meetings_agenda_edit_time_period(app, admin_user):
1038
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1039
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1040
    time_period = TimePeriod.objects.create(
1041
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
1042
    )
1043
    desk2 = desk.duplicate()
1044
    time_period2 = desk2.timeperiod_set.get()
1045

  
1046
    app = login(app)
1047
    # edit
1048
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1049
    resp = resp.click('Monday / 9 a.m. → noon', index=0)
1050
    assert 'Edit Time Period' in resp.text
1051
    resp.form['start_time'] = '10:00'
1052
    resp = resp.form.submit()
1053
    resp = resp.follow()
1054
    time_period.refresh_from_db()
1055
    assert time_period.start_time.hour == 10
1056
    time_period2.refresh_from_db()
1057
    assert time_period2.start_time.hour == 9
1058

  
1059
    # edit with inverted start/end
1060
    resp2 = resp.click('Monday / 10 a.m. → noon')
1061
    resp2.form['start_time'] = '18:00'
1062
    resp2 = resp2.form.submit()
1063
    assert 'End time must come after start time.' in resp2.text
1064

  
1065

  
1066
def test_meetings_agenda_edit_time_period_desk_simple_management(app, admin_user):
1067
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
1068
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1069
    time_period = TimePeriod.objects.create(
1070
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
1071
    )
1072
    desk2 = desk.duplicate()
1073
    time_period2 = desk2.timeperiod_set.get()
1074
    assert agenda.is_available_for_simple_management() is True
1075

  
1076
    app = login(app)
1077
    resp = app.get('/manage/timeperiods/%s/edit' % time_period.pk)
1078
    resp.form['weekday'] = 3
1079
    resp.form['start_time'] = '10:00'
1080
    resp.form['end_time'] = '11:00'
1081
    resp.form.submit()
1082
    time_period.refresh_from_db()
1083
    time_period2.refresh_from_db()
1084
    assert time_period.weekday == 3
1085
    assert time_period.start_time.hour == 10
1086
    assert time_period.end_time.hour == 11
1087
    assert time_period2.weekday == 3
1088
    assert time_period2.start_time.hour == 10
1089
    assert time_period2.end_time.hour == 11
1090
    assert agenda.is_available_for_simple_management() is True
1091

  
1092
    # should not happen: corresponding time period does not exist
1093
    time_period2.delete()
1094
    resp = app.get('/manage/timeperiods/%s/edit' % time_period.pk)
1095
    resp.form['weekday'] = 3
1096
    resp.form['start_time'] = '10:00'
1097
    resp.form['end_time'] = '11:00'
1098
    # no error
1099
    resp.form.submit()
1100
    time_period.refresh_from_db()
1101
    assert time_period.weekday == 3
1102
    assert time_period.start_time.hour == 10
1103
    assert time_period.end_time.hour == 11
1104

  
1105

  
1106
def test_meetings_agenda_delete_time_period(app, admin_user):
1107
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1108
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1109
    TimePeriod.objects.create(
1110
        desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
1111
    )
1112
    desk.duplicate()
1113
    assert TimePeriod.objects.count() == 2
1114

  
1115
    app = login(app)
1116
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
1117
    resp = resp.click('Wednesday', index=0)
1118
    resp = resp.click('Delete')
1119
    resp = resp.form.submit()
1120
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
1121
    assert TimePeriod.objects.count() == 1
1122

  
1123

  
1124
def test_meetings_agenda_delete_time_period_desk_simple_management(app, admin_user):
1125
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
1126
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1127
    time_period = TimePeriod.objects.create(
1128
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
1129
    )
1130
    desk.duplicate()
1131
    assert TimePeriod.objects.count() == 2
1132
    assert agenda.is_available_for_simple_management() is True
1133

  
1134
    app = login(app)
1135
    resp = app.get('/manage/timeperiods/%s/delete' % time_period.pk)
1136
    resp.form.submit()
1137
    assert TimePeriod.objects.count() == 0
1138
    assert agenda.is_available_for_simple_management() is True
1139

  
1140
    # should not happen: corresponding time period does not exist
1141
    time_period = TimePeriod.objects.create(
1142
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
1143
    )
1144
    resp = app.get('/manage/timeperiods/%s/delete' % time_period.pk)
1145
    resp.form.submit()
1146
    assert TimePeriod.objects.count() == 0
1147

  
1148

  
1149
def test_meetings_agenda_add_desk(app, admin_user):
1150
    app = login(app)
1151
    resp = app.get('/manage/', status=200)
1152
    resp = resp.click('New')
1153
    resp.form['label'] = 'Foo bar'
1154
    resp.form['kind'] = 'meetings'
1155
    resp = resp.form.submit()
1156
    assert Desk.objects.count() == 1
1157
    assert str(Desk.objects.first()) == 'Desk 1'
1158
    agenda = Agenda.objects.get(slug='foo-bar')
1159
    agenda.desk_simple_management = False
1160
    agenda.save()
1161

  
1162
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1163
    resp = resp.click('New Desk')
1164
    resp.form['label'] = 'Desk A'
1165
    resp = resp.form.submit().follow()
1166
    assert Desk.objects.count() == 2
1167
    desk = Desk.objects.latest('pk')
1168
    TimePeriod.objects.create(
1169
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1170
    )
1171
    resp = resp.click('New Desk')
1172
    resp.form['label'] = 'Desk A'
1173
    resp = resp.form.submit().follow()
1174
    assert Desk.objects.count() == 3
1175
    assert Desk.objects.filter(slug='desk-a-1').count() == 1
1176
    assert 'Desk A' in resp.text
1177
    new_desk = Desk.objects.latest('pk')
1178
    assert new_desk.timeperiod_set.count() == 0
1179

  
1180
    # unknown pk
1181
    app.get('/manage/agendas/0/add-desk', status=404)
1182

  
1183
    # wrong kind
1184
    agenda.kind = 'virtual'
1185
    agenda.save()
1186
    app.get('/manage/agendas/%s/add-desk' % agenda.pk, status=404)
1187
    agenda.kind = 'events'
1188
    agenda.save()
1189
    app.get('/manage/agendas/%s/add-desk' % agenda.pk, status=404)
1190

  
1191

  
1192
@override_settings(
1193
    EXCEPTIONS_SOURCES={
1194
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
1195
    }
1196
)
1197
def test_meetings_agenda_add_desk_from_another(app, admin_user):
1198
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1199
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1200
    TimePeriod.objects.create(
1201
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1202
    )
1203
    assert Desk.objects.count() == 1
1204
    assert desk.timeperiodexceptionsource_set.count() == 0
1205

  
1206
    app = login(app)
1207
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1208
    resp = resp.click('New Desk')
1209
    resp.form['label'] = 'Desk B'
1210
    resp.form['copy_from'] = desk.pk
1211
    resp = resp.form.submit().follow()
1212
    assert Desk.objects.count() == 2
1213
    new_desk = Desk.objects.latest('pk')
1214
    assert new_desk.label == 'Desk B'
1215
    assert new_desk.timeperiod_set.count() == 1
1216
    assert (
1217
        new_desk.timeperiodexceptionsource_set.count() == 0
1218
    )  # holidays not automatically added via duplication
1219

  
1220

  
1221
@override_settings(
1222
    EXCEPTIONS_SOURCES={
1223
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
1224
    }
1225
)
1226
def test_meetings_agenda_add_desk_simple_management(app, admin_user):
1227
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
1228
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1229
    TimePeriod.objects.create(
1230
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1231
    )
1232
    assert Desk.objects.count() == 1
1233
    assert desk.timeperiodexceptionsource_set.count() == 0
1234

  
1235
    app = login(app)
1236
    resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
1237
    resp.form['label'] = 'Desk B'
1238
    assert 'copy_from' not in resp.context['form'].fields
1239
    resp = resp.form.submit().follow()
1240
    assert Desk.objects.count() == 2
1241
    new_desk = Desk.objects.latest('pk')
1242
    assert new_desk.label == 'Desk B'
1243
    assert new_desk.timeperiod_set.count() == 1
1244
    assert (
1245
        new_desk.timeperiodexceptionsource_set.count() == 0
1246
    )  # holidays not automatically added via duplication
1247

  
1248
    # ok if no desks (should not happen)
1249
    Desk.objects.all().delete()
1250
    resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
1251
    resp.form['label'] = 'Desk'
1252
    assert 'copy_from' not in resp.context['form'].fields
1253
    resp = resp.form.submit().follow()
1254
    assert Desk.objects.count() == 1
1255
    new_desk = Desk.objects.latest('pk')
1256
    assert new_desk.timeperiodexceptionsource_set.count() == 1
1257

  
1258

  
1259
def test_meetings_agenda_edit_desk(app, admin_user):
1260
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1261
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1262
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
1263
    other_agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1264
    other_desk = Desk.objects.create(agenda=other_agenda, label='Desk A')
1265
    assert other_desk.slug == desk.slug
1266

  
1267
    app = login(app)
1268
    resp = app.get('/manage/desks/%s/edit' % desk.pk)
1269
    resp.form['label'] = 'Desk C'
1270
    resp.form['slug'] = desk.slug
1271
    resp = resp.form.submit().follow()
1272
    assert 'Desk A' not in resp.text
1273
    assert 'Desk B' in resp.text
1274
    assert 'Desk C' in resp.text
1275
    desk.refresh_from_db()
1276
    assert desk.label == 'Desk C'
1277
    assert desk.slug == other_desk.slug
1278

  
1279
    # check slug edition
1280
    resp = app.get('/manage/desks/%s/edit' % desk.pk)
1281
    resp.form['slug'] = desk2.slug
1282
    resp = resp.form.submit()
1283
    assert resp.context['form'].errors['slug'] == ['Another desk exists with the same identifier.']
1284

  
1285
    # unknown pk
1286
    app.get('/manage/desks/0/edit', status=404)
1287

  
1288
    # wrong kind
1289
    agenda.kind = 'virtual'
1290
    agenda.save()
1291
    app.get('/manage/desks/%s/edit' % desk.pk, status=404)
1292
    agenda.kind = 'events'
1293
    agenda.save()
1294
    app.get('/manage/desks/%s/edit' % desk.pk, status=404)
1295

  
1296

  
1297
def test_meetings_agenda_delete_desk(app, admin_user):
1298
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1299
    Desk.objects.create(agenda=agenda, label='Desk A')
1300
    desk_b = Desk.objects.create(agenda=agenda, label='Desk B')
1301

  
1302
    app = login(app)
1303

  
1304
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1305
    resp = resp.click('Desk A')
1306
    resp = resp.click('Delete')
1307
    resp = resp.form.submit()
1308
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
1309
    assert Desk.objects.count() == 1
1310

  
1311
    # only one desk
1312
    app.get('/manage/desks/%s/delete' % desk_b.pk, status=404)
1313

  
1314
    desk_a = Desk.objects.create(agenda=agenda, label='Desk A')
1315

  
1316
    # unknown pk
1317
    app.get('/manage/desks/0/delete', status=404)
1318

  
1319
    # wrong kind
1320
    agenda.kind = 'virtual'
1321
    agenda.save()
1322
    app.get('/manage/desks/%s/delete' % desk_a.pk, status=404)
1323
    agenda.kind = 'events'
1324
    agenda.save()
1325
    app.get('/manage/desks/%s/delete' % desk_a.pk, status=404)
1326

  
1327

  
1328
def test_meetings_agenda_add_time_period_exception(app, admin_user):
1329
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1330
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1331
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
1332

  
1333
    app = login(app)
1334
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1335
    resp = resp.click('Add a time period exception', index=0)
1336
    today = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
1337
    tomorrow = make_aware(today + datetime.timedelta(days=1))
1338
    dt_format = '%Y-%m-%d %H:%M'
1339
    resp.form['label'] = 'Exception 1'
1340
    resp.form['start_datetime_0'] = tomorrow.strftime('%Y-%m-%d')
1341
    resp.form['start_datetime_1'] = '08:00'
1342
    resp.form['end_datetime_0'] = tomorrow.strftime('%Y-%m-%d')
1343
    resp.form['end_datetime_1'] = '16:00'
1344
    resp = resp.form.submit().follow()
1345
    assert TimePeriodException.objects.count() == 1
1346
    assert desk2.timeperiodexception_set.exists() is False
1347
    time_period_exception = TimePeriodException.objects.first()
1348
    assert localtime(time_period_exception.start_datetime).strftime(dt_format) == tomorrow.replace(
1349
        hour=8
1350
    ).strftime(dt_format)
1351
    assert localtime(time_period_exception.end_datetime).strftime(dt_format) == tomorrow.replace(
1352
        hour=16
1353
    ).strftime(dt_format)
1354
    # add an exception beyond 2 weeks and make sure it isn't listed
1355
    resp = resp.click('Add a time period exception', index=1)
1356
    future = tomorrow + datetime.timedelta(days=15)
1357
    resp.form['label'] = 'Exception 2'
1358
    resp.form['start_datetime_0'] = future.strftime('%Y-%m-%d')
1359
    resp.form['start_datetime_1'] = '00:00'
1360
    resp.form['end_datetime_0'] = future.strftime('%Y-%m-%d')
1361
    resp.form['end_datetime_1'] = '16:00'
1362
    resp = resp.form.submit().follow()
1363
    assert TimePeriodException.objects.count() == 2
1364
    assert 'Exception 1' in resp.text
1365
    assert 'Exception 2' not in resp.text
1366
    resp = resp.click(href="/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk)
1367
    assert 'Exception 1' in resp.text
1368
    assert 'Exception 2' in resp.text
1369

  
1370

  
1371
def test_meetings_agenda_add_time_period_exception_booking_overlaps(app, admin_user):
1372
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1373
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1374
    meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
1375

  
1376
    # different type of overlap
1377
    # event.start_datetime <= exception.start_datetime < event.start_datetime + meeting_type.duration
1378
    event = Event.objects.create(
1379
        agenda=agenda,
1380
        places=1,
1381
        desk=desk,
1382
        meeting_type=meeting_type,
1383
        start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30)),
1384
    )
1385
    Booking.objects.create(event=event)
1386
    app = login(app)
1387
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
1388
    resp.form['label'] = 'Exception'
1389
    resp.form['start_datetime_0'] = '2017-05-22'
1390
    resp.form['start_datetime_1'] = '10:45'
1391
    resp.form['end_datetime_0'] = '2017-05-22'
1392
    resp.form['end_datetime_1'] = '17:30'
1393
    resp = resp.form.submit().follow()
1394
    assert TimePeriodException.objects.count() == 1
1395
    assert 'Exception added.' in resp.text
1396
    assert 'One or several bookings exists within this time slot.' in resp.text
1397

  
1398

  
1399
def test_meetings_agenda_add_time_period_exception_all_desks(app, admin_user):
1400
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1401
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1402

  
1403
    # add global time exception
1404
    # only one desk: no option to apply to all desks
1405
    app = login(app)
1406
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
1407
    assert 'all_desks' not in resp.context['form'].fields
1408
    # more than one desk
1409
    Desk.objects.create(agenda=agenda, label='Desk B')
1410
    agenda2 = Agenda.objects.create(label='Foo bar', kind='meetings')
1411
    Desk.objects.create(agenda=agenda2, label='Other Desk')  # check exception is not created for this one
1412
    event = Event.objects.create(
1413
        agenda=agenda, places=1, desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
1414
    )
1415
    Booking.objects.create(event=event)
1416
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
1417
    resp.form['label'] = 'Exception'
1418
    resp.form['start_datetime_0'] = '2017-05-22'
1419
    resp.form['start_datetime_1'] = '08:00'
1420
    resp.form['end_datetime_0'] = '2017-05-26'
1421
    resp.form['end_datetime_1'] = '17:30'
1422
    resp.form['all_desks'] = True
1423
    resp = resp.form.submit().follow()
1424
    assert TimePeriodException.objects.count() == 2
1425
    assert 'Exceptions added.' in resp.text
1426
    assert 'One or several bookings exists within this time slot.' in resp.text
1427

  
1428
    exception = TimePeriodException.objects.first()
1429
    resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
1430
    assert 'all_desks' not in resp.context['form'].fields
1431

  
1432

  
1433
def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admin_user):
1434
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1435
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1436
    MeetingType(agenda=agenda, label='Blah').save()
1437
    TimePeriod.objects.create(
1438
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1439
    )
1440
    event = Event.objects.create(
1441
        agenda=agenda, places=1, desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
1442
    )
1443
    Booking.objects.create(event=event)
1444
    login(app)
1445
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1446
    resp = resp.click('Add a time period exception')
1447
    resp = resp.form.submit()  # submit empty form
1448
    # fields should be marked with errors
1449
    assert resp.text.count('This field is required.') == 2
1450
    # try again with data in fields
1451
    resp.form['start_datetime_0'] = '2017-05-22'
1452
    resp.form['start_datetime_1'] = '08:00'
1453
    resp.form['end_datetime_0'] = '2017-05-26'
1454
    resp.form['end_datetime_1'] = '17:30'
1455
    resp = resp.form.submit().follow()
1456
    assert 'Exception added.' in resp.text
1457
    assert 'One or several bookings exists within this time slot.' in resp.text
1458
    assert TimePeriodException.objects.count() == 1
1459

  
1460

  
1461
def test_meetings_agenda_add_time_period_exception_when_cancelled_booking_exists(app, admin_user):
1462
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1463
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1464
    MeetingType(agenda=agenda, label='Blah').save()
1465
    TimePeriod.objects.create(
1466
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1467
    )
1468
    event = Event.objects.create(
1469
        agenda=agenda, places=1, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
1470
    )
1471
    Booking.objects.create(
1472
        event=event, cancellation_datetime=make_aware(datetime.datetime(2017, 5, 20, 10, 30))
1473
    )
1474
    login(app)
1475
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1476
    resp = resp.click('Add a time period exception')
1477
    resp.form['start_datetime_0'] = '2017-05-22'
1478
    resp.form['start_datetime_1'] = '08:00'
1479
    resp.form['end_datetime_0'] = '2017-05-26'
1480
    resp.form['end_datetime_1'] = '17:30'
1481
    resp = resp.form.submit().follow()
1482
    assert 'Exception added' in resp.text
1483
    assert 'One or several bookings exists within this time slot.' not in resp.text
1484
    assert TimePeriodException.objects.count() == 1
1485

  
1486

  
1487
def test_meetings_agenda_add_time_period_exception_desk_simple_management(app, admin_user):
1488
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
1489
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1490
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
1491
    event = Event.objects.create(
1492
        agenda=agenda, places=1, desk=desk2, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
1493
    )
1494
    Booking.objects.create(event=event)
1495
    assert agenda.is_available_for_simple_management() is True
1496

  
1497
    login(app)
1498
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
1499
    assert 'all_desks' not in resp.form.fields
1500
    resp.form['label'] = 'Exception'
1501
    resp.form['start_datetime_0'] = '2017-05-22'
1502
    resp.form['start_datetime_1'] = '8:00'
1503
    resp.form['end_datetime_0'] = '2017-05-22'
1504
    resp.form['end_datetime_1'] = '17:30'
1505
    resp = resp.form.submit().follow()
1506
    assert 'Exception added' in resp.text
1507
    assert 'One or several bookings exists within this time slot.' in resp.text
1508
    assert TimePeriodException.objects.count() == 2
1509
    assert agenda.is_available_for_simple_management() is True
1510

  
1511

  
1512
def test_meetings_agenda_edit_time_period_exception(app, admin_user):
1513
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1514
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1515
    exception = TimePeriodException.objects.create(
1516
        label='Exception',
1517
        desk=desk,
1518
        start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
1519
        end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
1520
    )
1521
    desk2 = desk.duplicate()
1522
    exception2 = desk2.timeperiodexception_set.get()
1523

  
1524
    login(app)
1525
    resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
1526
    resp.form['start_datetime_1'] = '8:00'
1527
    resp.form.submit()
1528
    exception.refresh_from_db()
1529
    assert exception.start_datetime == make_aware(datetime.datetime(2018, 12, 16, 8, 0))
1530
    exception2.refresh_from_db()
1531
    assert exception2.start_datetime == make_aware(datetime.datetime(2018, 12, 16, 5, 0))
1532

  
1533

  
1534
def test_meetings_agenda_edit_time_period_exception_overlaps(app, admin_user):
1535
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1536
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1537
    calendar = UnavailabilityCalendar.objects.create(label='foo')
1538
    calendar.desks.add(desk)
1539
    time_period_exception_desk = TimePeriodException.objects.create(
1540
        label='Exception',
1541
        desk=desk,
1542
        start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
1543
        end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
1544
    )
1545
    time_period_exception_calendar = TimePeriodException.objects.create(
1546
        label='Exception',
1547
        unavailability_calendar=calendar,
1548
        start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
1549
        end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
1550
    )
1551
    login(app)
1552
    for time_period_exception in (time_period_exception_desk, time_period_exception_calendar):
1553
        event = Event.objects.create(
1554
            agenda=agenda,
1555
            places=1,
1556
            desk=desk,
1557
            start_datetime=make_aware(datetime.datetime(2018, 12, 16, 10, 30)),
1558
        )
1559

  
1560
        resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
1561
        resp = resp.form.submit().follow()
1562
        assert 'One or several bookings exists within this time slot.' not in resp.text
1563

  
1564
        booking = Booking.objects.create(event=event)
1565
        resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
1566
        resp = resp.form.submit().follow()
1567
        assert 'One or several bookings exists within this time slot.' in resp.text
1568

  
1569
        booking.cancel()
1570
        resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
1571
        resp = resp.form.submit().follow()
1572
        assert 'One or several bookings exists within this time slot.' not in resp.text
1573

  
1574
        booking.delete()
1575
        event.delete()
1576

  
1577

  
1578
def test_meetings_agenda_edit_time_period_exception_desk_simple_management(app, admin_user):
1579
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
1580
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1581
    exception = TimePeriodException.objects.create(
1582
        label='Exception',
1583
        desk=desk,
1584
        start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
1585
        end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
1586
    )
1587
    desk2 = desk.duplicate()
1588
    event = Event.objects.create(
1589
        agenda=agenda, places=1, desk=desk2, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
1590
    )
1591
    Booking.objects.create(event=event)
1592
    exception2 = desk2.timeperiodexception_set.get()
1593
    assert agenda.is_available_for_simple_management() is True
1594

  
1595
    login(app)
1596
    resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
1597
    resp.form['label'] = 'Exception foo bar'
1598
    resp.form['start_datetime_0'] = '2017-05-22'
1599
    resp.form['start_datetime_1'] = '8:00'
1600
    resp.form['end_datetime_0'] = '2017-05-22'
1601
    resp.form['end_datetime_1'] = '17:30'
1602
    resp = resp.form.submit().follow()
1603
    assert 'One or several bookings exists within this time slot.' in resp.text
1604
    exception.refresh_from_db()
1605
    exception2.refresh_from_db()
1606
    assert exception.label == 'Exception foo bar'
1607
    assert exception.start_datetime == make_aware(datetime.datetime(2017, 5, 22, 8, 0))
1608
    assert exception.end_datetime == make_aware(datetime.datetime(2017, 5, 22, 17, 30))
1609
    assert exception2.label == 'Exception foo bar'
1610
    assert exception2.start_datetime == make_aware(datetime.datetime(2017, 5, 22, 8, 0))
1611
    assert exception2.end_datetime == make_aware(datetime.datetime(2017, 5, 22, 17, 30))
1612
    assert agenda.is_available_for_simple_management() is True
1613

  
1614
    # should not happen: corresponding exception does not exist
1615
    exception2.delete()
1616
    resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
1617
    resp.form['label'] = 'Exception'
1618
    resp.form['start_datetime_0'] = '2017-05-21'
1619
    resp.form['start_datetime_1'] = '9:00'
1620
    resp.form['end_datetime_0'] = '2017-05-21'
1621
    resp.form['end_datetime_1'] = '18:30'
1622
    # no error
1623
    resp.form.submit()
1624
    exception.refresh_from_db()
1625
    assert exception.label == 'Exception'
1626
    assert exception.start_datetime == make_aware(datetime.datetime(2017, 5, 21, 9, 0))
1627
    assert exception.end_datetime == make_aware(datetime.datetime(2017, 5, 21, 18, 30))
1628

  
1629

  
1630
def test_meetings_agenda_add_invalid_time_period_exception():
1631
    form = TimePeriodExceptionForm(
1632
        data={
1633
            'start_datetime_0': '2017-05-26',
1634
            'start_datetime_1': '17:30',
1635
            'end_datetime_0': '2017-05-22',
1636
            'end_datetime_1': '08:00',
1637
        }
1638
    )
1639
    assert form.is_valid() is False
1640
    assert form.errors['end_datetime'] == ['End datetime must be greater than start datetime.']
1641

  
1642
    # start_datetime is invalid
1643
    form = TimePeriodExceptionForm(
1644
        data={
1645
            'start_datetime_0': '2017-05-26',
1646
            'start_datetime_1': 'foo',
1647
            'end_datetime_0': '2017-05-22',
1648
            'end_datetime_1': '08:00',
1649
        }
1650
    )
1651
    assert form.is_valid() is False
1652

  
1653
    # end_datetime is invalid
1654
    form = TimePeriodExceptionForm(
1655
        data={
1656
            'start_datetime_0': '2017-05-26',
1657
            'start_datetime_1': '17:30',
1658
            'end_datetime_0': 'bar',
1659
            'end_datetime_1': '08:00',
1660
        }
1661
    )
1662
    assert form.is_valid() is False
1663

  
1664

  
1665
def test_meetings_agenda_delete_time_period_exception(app, admin_user):
1666
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1667
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1668
    TimePeriodException.objects.create(
1669
        label='Exception Foo',
1670
        desk=desk,
1671
        start_datetime=now() + datetime.timedelta(days=1),
1672
        end_datetime=now() + datetime.timedelta(days=2),
1673
    )
1674
    desk.duplicate()
1675
    assert TimePeriodException.objects.count() == 2
1676

  
1677
    login(app)
1678
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1679
    resp = resp.click('Exception Foo', index=0)
1680
    resp = resp.click('Delete')
1681
    resp = resp.form.submit().follow()
1682
    assert TimePeriodException.objects.count() == 1
1683
    assert resp.request.url.endswith('/manage/agendas/%d/settings' % agenda.pk)
1684

  
1685
    # stay on exception list
1686
    time_period_exception = TimePeriodException.objects.create(
1687
        label='Future Exception',
1688
        desk=desk,
1689
        start_datetime=now() + datetime.timedelta(days=1),
1690
        end_datetime=now() + datetime.timedelta(days=2),
1691
    )
1692
    resp = app.get('/manage/time-period-exceptions/%d/exception-list' % desk.pk)
1693
    resp = resp.click(href='/manage/time-period-exceptions/%d/delete' % time_period_exception.pk)
1694
    resp = resp.form.submit(
1695
        extra_environ={'HTTP_REFERER': str('/manage/time-period-exceptions/%d/exception-list' % desk.pk)}
1696
    ).follow()
1697
    assert resp.request.url.endswith('/manage/time-period-exceptions/%d/exception-list' % desk.pk)
1698

  
1699

  
1700
def test_meetings_agenda_delete_time_period_exception_desk_simple_management(app, admin_user):
1701
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
1702
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1703
    exception = TimePeriodException.objects.create(
1704
        label='Exception',
1705
        desk=desk,
1706
        start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
1707
        end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
1708
    )
1709
    desk.duplicate()
1710
    assert TimePeriodException.objects.count() == 2
1711
    assert agenda.is_available_for_simple_management() is True
1712

  
1713
    login(app)
1714
    resp = app.get('/manage/time-period-exceptions/%d/delete' % exception.pk)
1715
    resp.form.submit()
1716
    assert TimePeriodException.objects.count() == 0
1717
    assert agenda.is_available_for_simple_management() is True
1718

  
1719
    # should not happen: corresponding exception does not exist
1720
    exception = TimePeriodException.objects.create(
1721
        label='Exception',
1722
        desk=desk,
1723
        start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
1724
        end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
1725
    )
1726
    assert TimePeriodException.objects.count() == 1
1727
    resp = app.get('/manage/time-period-exceptions/%d/delete' % exception.pk)
1728
    resp.form.submit()
1729
    assert TimePeriodException.objects.count() == 0
1730

  
1731

  
1732
@override_settings(
1733
    EXCEPTIONS_SOURCES={
1734
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
1735
    }
1736
)
1737
def test_exception_list(app, admin_user):
1738
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1739
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1740
    MeetingType(agenda=agenda, label='Blah').save()
1741
    TimePeriod.objects.create(
1742
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1743
    )
1744
    past_exception = TimePeriodException.objects.create(
1745
        label='Past Exception',
1746
        desk=desk,
1747
        start_datetime=now() - datetime.timedelta(days=2),
1748
        end_datetime=now() - datetime.timedelta(days=1),
1749
    )
1750
    current_exception = TimePeriodException.objects.create(
1751
        label='Current Exception',
1752
        desk=desk,
1753
        start_datetime=now() - datetime.timedelta(days=1),
1754
        end_datetime=now() + datetime.timedelta(days=1),
1755
    )
1756
    future_exception = TimePeriodException.objects.create(
1757
        label='Future Exception',
1758
        desk=desk,
1759
        start_datetime=now() + datetime.timedelta(days=1),
1760
        end_datetime=now() + datetime.timedelta(days=2),
1761
    )
1762

  
1763
    login(app)
1764
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
1765
    assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
1766
    assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
1767
    assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
1768

  
1769
    resp = resp.click(href="/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk)
1770
    assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
1771
    assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
1772
    assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
1773

  
1774
    resp = resp.click(href="/manage/time-period-exceptions/%d/exception-list" % desk.pk)
1775
    assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
1776
    assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
1777
    assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
1778

  
1779
    with CaptureQueriesContext(connection) as ctx:
1780
        app.get("/manage/time-period-exceptions/%d/exception-list" % desk.pk)
1781
        assert len(ctx.captured_queries) == 6
1782

  
1783
    desk.import_timeperiod_exceptions_from_settings(enable=True)
1784
    with CaptureQueriesContext(connection) as ctx:
1785
        app.get("/manage/time-period-exceptions/%d/exception-list" % desk.pk)
1786
        assert len(ctx.captured_queries) == 6
1787

  
1788
    # add an unavailability calendar
1789
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar')
1790
    past_exception = TimePeriodException.objects.create(
1791
        label='Calendar Past Exception',
1792
        unavailability_calendar=unavailability_calendar,
1793
        start_datetime=now() - datetime.timedelta(days=2),
1794
        end_datetime=now() - datetime.timedelta(days=1),
1795
    )
1796
    current_exception = TimePeriodException.objects.create(
1797
        label='Calendar Current Exception',
1798
        unavailability_calendar=unavailability_calendar,
1799
        start_datetime=now() - datetime.timedelta(days=1),
1800
        end_datetime=now() + datetime.timedelta(days=1),
1801
    )
1802
    future_exception = TimePeriodException.objects.create(
1803
        label='Calendar Future Exception',
1804
        unavailability_calendar=unavailability_calendar,
1805
        start_datetime=now() + datetime.timedelta(days=1),
1806
        end_datetime=now() + datetime.timedelta(days=2),
1807
    )
1808
    unavailability_calendar.desks.add(desk)
1809

  
1810
    for url in (
1811
        "/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk,
1812
        "/manage/time-period-exceptions/%d/exception-list" % desk.pk,
1813
    ):
1814
        resp = app.get(url)
1815
        assert 'Calendar Past Exception' not in resp.text
1816
        assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
1817
        assert 'Calendar Current Exception' in resp.text
1818
        assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk not in resp.text
1819
        assert 'Calendar Future Exception' in resp.text
1820
        assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk not in resp.text
1821

  
1822

  
1823
def test_agenda_import_time_period_exception_from_ics(app, admin_user):
1824
    agenda = Agenda.objects.create(label='Example', kind='meetings')
1825
    desk = Desk.objects.create(agenda=agenda, label='Test Desk')
1826
    desk.duplicate()
1827
    login(app)
1828
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
1829
    assert 'Manage exception sources' in resp.text
1830
    resp = resp.click('manage exceptions', index=0)
1831
    assert "To add new exceptions, you can upload a file or specify an address to a remote calendar." in resp
1832
    resp = resp.form.submit(status=200)
1833
    assert 'Please provide an ICS File or an URL.' in resp.text
1834
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
1835
    resp.form['ics_file'] = Upload('exceptions.ics', b'invalid content', 'text/calendar')
1836
    resp = resp.form.submit(status=200)
1837
    assert 'File format is invalid' in resp.text
1838
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
1839
    ics_with_no_start_date = b"""BEGIN:VCALENDAR
1840
VERSION:2.0
1841
PRODID:-//foo.bar//EN
1842
BEGIN:VEVENT
1843
DTEND:20180101
1844
SUMMARY:New Year's Eve
1845
END:VEVENT
1846
END:VCALENDAR"""
1847
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_start_date, 'text/calendar')
1848
    resp = resp.form.submit(status=200)
1849
    assert 'Event &quot;New Year&#39;s Eve&quot; has no start date.' in resp.text
1850
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
1851
    ics_with_no_events = b"""BEGIN:VCALENDAR
1852
VERSION:2.0
1853
PRODID:-//foo.bar//EN
1854
END:VCALENDAR"""
1855
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_events, 'text/calendar')
1856
    resp = resp.form.submit(status=200)
1857
    assert "The file doesn&#39;t contain any events." in resp.text
1858
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
1859

  
1860
    ics_with_exceptions = b"""BEGIN:VCALENDAR
1861
VERSION:2.0
1862
PRODID:-//foo.bar//EN
1863
BEGIN:VEVENT
1864
DTSTART:20180101
1865
DTEND:20180101
1866
SUMMARY:New Year's Eve
1867
END:VEVENT
1868
END:VCALENDAR"""
1869
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
1870
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_exceptions, 'text/calendar')
1871
    resp = resp.form.submit(status=302)
1872
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
1873
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
1874
    source = desk.timeperiodexceptionsource_set.get()
1875
    exception = desk.timeperiodexception_set.get()
1876
    assert exception.source == source
1877
    assert source.ics_filename == 'exceptions.ics'
1878
    assert 'exceptions.ics' in source.ics_file.name
1879
    assert source.ics_url is None
1880
    resp = resp.follow()
1881
    assert 'Exceptions will be imported in a few minutes.' in resp.text
1882

  
1883

  
1884
@pytest.mark.freeze_time('2017-12-01')
1885
def test_agenda_import_time_period_exception_from_ics_recurrent(app, admin_user):
1886
    agenda = Agenda.objects.create(label='Example', kind='meetings')
1887
    desk = Desk.objects.create(agenda=agenda, label='Test Desk')
1888
    MeetingType(agenda=agenda, label='Foo').save()
1889
    TimePeriod.objects.create(
1890
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1891
    )
1892
    login(app)
1893
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1894
    resp = resp.click('manage exceptions')
1895
    ics_with_recurrent_exceptions = b"""BEGIN:VCALENDAR
1896
VERSION:2.0
1897
PRODID:-//foo.bar//EN
1898
BEGIN:VEVENT
1899
DTSTART:20180101
1900
DTEND:20180101
1901
SUMMARY:New Year's Eve
1902
RRULE:FREQ=YEARLY
1903
END:VEVENT
1904
END:VCALENDAR"""
1905
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_recurrent_exceptions, 'text/calendar')
1906
    resp = resp.form.submit(status=302).follow()
1907
    assert TimePeriodException.objects.filter(desk=desk).count() == 2
1908

  
1909

  
1910
@mock.patch('chrono.agendas.models.requests.get')
1911
def test_agenda_import_time_period_exception_with_remote_ics(mocked_get, app, admin_user):
1912
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1913
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
1914
    desk.duplicate()
1915
    login(app)
1916
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
1917

  
1918
    assert 'ics_file' in resp.form.fields
1919
    assert 'ics_url' in resp.form.fields
1920
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1921
    mocked_response = mock.Mock()
1922
    mocked_response.text = """BEGIN:VCALENDAR
1923
VERSION:2.0
1924
PRODID:-//foo.bar//EN
1925
BEGIN:VEVENT
1926
DTSTART:20180101
1927
DTEND:20180101
1928
SUMMARY:New Year's Eve
1929
END:VEVENT
1930
END:VCALENDAR"""
1931
    mocked_get.return_value = mocked_response
1932
    resp = resp.form.submit(status=302)
1933
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
1934
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
1935
    source = desk.timeperiodexceptionsource_set.get()
1936
    exception = desk.timeperiodexception_set.get()
1937
    assert exception.source == source
1938
    assert source.ics_filename is None
1939
    assert source.ics_file.name == ''
1940
    assert source.ics_url == 'http://example.com/foo.ics'
1941

  
1942

  
1943
@mock.patch('chrono.agendas.models.requests.get')
1944
def test_agenda_import_time_period_exception_with_remote_ics_no_events(mocked_get, app, admin_user):
1945
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1946
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
1947
    MeetingType.objects.create(agenda=agenda, label='Bar')
1948
    login(app)
1949
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
1950
    resp = resp.click('manage exceptions')
1951
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1952
    mocked_response = mock.Mock()
1953
    mocked_response.text = """BEGIN:VCALENDAR
1954
VERSION:2.0
1955
PRODID:-//foo.bar//EN
1956
BEGIN:VEVENT
1957
DTSTART:20180101
1958
DTEND:20180101
1959
SUMMARY:New Year's Eve
1960
END:VEVENT
1961
END:VCALENDAR"""
1962
    mocked_get.return_value = mocked_response
1963
    resp = resp.form.submit(status=302)
1964
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
1965

  
1966

  
1967
@mock.patch('chrono.agendas.models.requests.get')
1968
def test_agenda_import_time_period_exception_from_remote_ics_with_connection_error(
1969
    mocked_get, app, admin_user
1970
):
1971
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1972
    Desk.objects.create(agenda=agenda, label='New Desk')
1973
    MeetingType.objects.create(agenda=agenda, label='Bar')
1974
    login(app)
1975
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
1976
    resp = resp.click('manage exceptions')
1977

  
1978
    assert 'ics_file' in resp.form.fields
1979
    assert 'ics_url' in resp.form.fields
1980
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1981
    mocked_response = mock.Mock()
1982
    mocked_get.return_value = mocked_response
1983

  
1984
    def mocked_requests_connection_error(*args, **kwargs):
1985
        raise requests.exceptions.ConnectionError('unreachable')
1986

  
1987
    mocked_get.side_effect = mocked_requests_connection_error
1988
    resp = resp.form.submit(status=200)
1989
    assert 'Failed to retrieve remote calendar (http://example.com/foo.ics, unreachable).' in resp.text
1990

  
1991

  
1992
@mock.patch('chrono.agendas.models.requests.get')
1993
def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_get, app, admin_user):
1994
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
1995
    Desk.objects.create(agenda=agenda, label='New Desk')
1996
    MeetingType.objects.create(agenda=agenda, label='Bar')
1997
    login(app)
1998
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
1999
    resp = resp.click('manage exceptions')
2000
    resp.form['ics_url'] = 'http://example.com/foo.ics'
2001
    mocked_response = mock.Mock()
2002
    mocked_response.status_code = 403
2003
    mocked_get.return_value = mocked_response
2004

  
2005
    def mocked_requests_http_forbidden_error(*args, **kwargs):
2006
        raise requests.exceptions.HTTPError(response=mocked_response)
2007

  
2008
    mocked_get.side_effect = mocked_requests_http_forbidden_error
2009
    resp = resp.form.submit(status=200)
2010
    assert 'Failed to retrieve remote calendar (http://example.com/foo.ics, HTTP error 403).' in resp.text
2011

  
2012

  
2013
@mock.patch('chrono.agendas.models.requests.get')
2014
def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mocked_get, app, admin_user):
2015
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
2016
    Desk.objects.create(agenda=agenda, label='New Desk')
2017
    MeetingType.objects.create(agenda=agenda, label='Bar')
2018
    login(app)
2019
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
2020
    resp = resp.click('manage exceptions')
2021
    resp.form['ics_url'] = 'https://example.com/foo.ics'
2022
    mocked_response = mock.Mock()
2023
    mocked_get.return_value = mocked_response
2024

  
2025
    def mocked_requests_http_ssl_error(*args, **kwargs):
2026
        raise requests.exceptions.SSLError('SSL error')
2027

  
2028
    mocked_get.side_effect = mocked_requests_http_ssl_error
2029
    resp = resp.form.submit(status=200)
2030
    assert 'Failed to retrieve remote calendar (https://example.com/foo.ics, SSL error).' in resp.text
2031

  
2032

  
2033
@mock.patch('chrono.agendas.models.requests.get')
2034
def test_agenda_import_time_period_exception_url_desk_simple_management(mocked_get, app, admin_user):
2035
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
2036
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
2037
    desk.duplicate()
2038
    assert agenda.is_available_for_simple_management() is True
2039

  
2040
    login(app)
2041
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
2042
    resp.form['ics_url'] = 'http://example.com/foo.ics'
2043
    mocked_response = mock.Mock()
2044
    mocked_response.text = """BEGIN:VCALENDAR
2045
VERSION:2.0
2046
PRODID:-//foo.bar//EN
2047
BEGIN:VEVENT
2048
DTSTART:20180101
2049
DTEND:20180101
2050
SUMMARY:New Year's Eve
2051
END:VEVENT
2052
END:VCALENDAR"""
2053
    mocked_get.return_value = mocked_response
2054
    resp = resp.form.submit(status=302)
2055
    assert TimePeriodException.objects.count() == 2
2056
    assert agenda.is_available_for_simple_management() is True
2057

  
2058

  
2059
def test_agenda_import_time_period_exception_file_desk_simple_management(app, admin_user):
2060
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
2061
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
2062
    desk.duplicate()
2063
    assert agenda.is_available_for_simple_management() is True
2064

  
2065
    login(app)
2066
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
2067
    ics_exceptions = b"""BEGIN:VCALENDAR
2068
VERSION:2.0
2069
PRODID:-//foo.bar//EN
2070
BEGIN:VEVENT
2071
DTSTART:20180101
2072
DTEND:20180101
2073
SUMMARY:New Year's Eve
2074
END:VEVENT
2075
END:VCALENDAR"""
2076
    resp.form['ics_file'] = Upload('exceptions.ics', ics_exceptions, 'text/calendar')
2077
    resp = resp.form.submit(status=302)
2078
    assert TimePeriodException.objects.count() == 2
2079
    assert agenda.is_available_for_simple_management() is True
2080

  
2081

  
2082
def test_meetings_agenda_delete_time_period_exception_source(app, admin_user):
2083
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
2084
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2085
    source1 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
2086
    TimePeriodException.objects.create(
2087
        desk=desk,
2088
        source=source1,
2089
        start_datetime=now() - datetime.timedelta(days=1),
2090
        end_datetime=now() + datetime.timedelta(days=1),
2091
    )
2092
    source2 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
2093
    TimePeriodException.objects.create(
2094
        desk=desk,
2095
        source=source2,
2096
        start_datetime=now() - datetime.timedelta(days=1),
2097
        end_datetime=now() + datetime.timedelta(days=1),
2098
    )
2099
    desk.duplicate()
2100
    assert TimePeriodException.objects.count() == 4
2101
    assert TimePeriodExceptionSource.objects.count() == 4
2102

  
2103
    login(app)
2104
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source2.pk)
2105
    resp = resp.form.submit()
2106
    assert TimePeriodException.objects.count() == 3
2107
    assert TimePeriodExceptionSource.objects.count() == 3
2108
    assert source1.timeperiodexception_set.count() == 1
2109
    assert TimePeriodExceptionSource.objects.filter(pk=source2.pk).exists() is False
2110

  
2111

  
2112
def test_meetings_agenda_delete_time_period_exception_source_desk_simple_management(app, admin_user):
2113
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
2114
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
2115
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
2116
    desk.duplicate()
2117
    assert TimePeriodExceptionSource.objects.count() == 2
2118
    assert agenda.is_available_for_simple_management() is True
2119

  
2120
    login(app)
2121
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source.pk)
2122
    resp.form.submit()
2123
    assert TimePeriodExceptionSource.objects.count() == 0
2124
    assert agenda.is_available_for_simple_management() is True
2125

  
2126
    # should not happen: corresponding source does not exist
2127
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
2128
    assert TimePeriodExceptionSource.objects.count() == 1
2129
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source.pk)
2130
    resp.form.submit()
2131
    assert TimePeriodExceptionSource.objects.count() == 0
2132

  
2133

  
2134
def test_meetings_agenda_replace_time_period_exception_source(app, admin_user, freezer):
2135
    freezer.move_to('2019-12-01')
2136
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
2137
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2138
    ics_file_content = b"""BEGIN:VCALENDAR
2139
VERSION:2.0
2140
PRODID:-//foo.bar//EN
2141
BEGIN:VEVENT
2142
DTSTART:20180101
2143
DTEND:20180101
2144
SUMMARY:New Year's Eve
2145
RRULE:FREQ=YEARLY
2146
END:VEVENT
2147
END:VCALENDAR"""
2148

  
2149
    login(app)
2150
    # import a source from a file
2151
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
2152
    resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
2153
    resp = resp.form.submit(status=302).follow()
2154
    assert TimePeriodException.objects.filter(desk=desk).count() == 2
2155
    source = TimePeriodExceptionSource.objects.latest('pk')
2156
    assert source.timeperiodexception_set.count() == 2
2157
    exceptions = list(source.timeperiodexception_set.order_by('pk'))
2158
    old_ics_file_path = source.ics_file.path
2159

  
2160
    desk2 = desk.duplicate()
2161
    source2 = desk2.timeperiodexceptionsource_set.get()
2162
    exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
2163
    assert TimePeriodException.objects.count() == 4
2164
    old_ics_file_path2 = source2.ics_file.path
2165

  
2166
    # replace the source
2167
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
2168
    resp.form['ics_newfile'] = Upload('exceptions-bis.ics', ics_file_content, 'text/calendar')
2169
    resp = resp.form.submit().follow()
2170
    source.refresh_from_db()
2171
    assert source.ics_file.path != old_ics_file_path
2172
    assert source.ics_filename == 'exceptions-bis.ics'
2173
    assert os.path.exists(old_ics_file_path) is False
2174
    assert TimePeriodException.objects.count() == 4
2175
    assert source.timeperiodexception_set.count() == 2
2176
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
2177
    assert exceptions[0].pk != new_exceptions[0].pk
2178
    assert exceptions[1].pk != new_exceptions[1].pk
2179
    source2.refresh_from_db()
2180
    assert source2.ics_file.path == old_ics_file_path2
2181
    assert source2.ics_filename == 'exceptions.ics'
2182
    new_exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
2183
    assert exceptions2[0].pk == new_exceptions2[0].pk
2184
    assert exceptions2[1].pk == new_exceptions2[1].pk
2185

  
2186

  
2187
def test_meetings_agenda_replace_time_period_exception_source_desk_simple_management(app, admin_user):
2188
    ics_file_content = b"""BEGIN:VCALENDAR
2189
VERSION:2.0
2190
PRODID:-//foo.bar//EN
2191
BEGIN:VEVENT
2192
DTSTART:20180101
2193
DTEND:20180101
2194
SUMMARY:New Year's Eve
2195
END:VEVENT
2196
END:VCALENDAR"""
2197

  
2198
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
2199
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2200
    source = TimePeriodExceptionSource.objects.create(
2201
        desk=desk,
2202
        ics_filename='sample.ics',
2203
        ics_file=ContentFile(ics_file_content, name='sample.ics'),
2204
    )
2205
    desk2 = desk.duplicate()
2206
    source2 = desk2.timeperiodexceptionsource_set.get()
2207
    assert TimePeriodExceptionSource.objects.count() == 2
2208
    assert TimePeriodException.objects.count() == 0  # not imported yet
2209
    assert agenda.is_available_for_simple_management() is True
2210
    old_ics_file_path = source.ics_file.path
2211
    old_ics_file_path2 = source2.ics_file.path
2212

  
2213
    login(app)
2214
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
2215
    resp.form['ics_newfile'] = Upload('exceptions-bis.ics', ics_file_content, 'text/calendar')
2216
    resp.form.submit()
2217
    assert TimePeriodExceptionSource.objects.count() == 2
2218
    assert TimePeriodException.objects.count() == 2
2219
    assert desk.timeperiodexception_set.count() == 1
2220
    assert desk2.timeperiodexception_set.count() == 1
2221
    source.refresh_from_db()
2222
    assert source.ics_file.path != old_ics_file_path
2223
    assert source.ics_filename == 'exceptions-bis.ics'
2224
    assert os.path.exists(old_ics_file_path) is False
2225
    source2.refresh_from_db()
2226
    assert source2.ics_file.path != old_ics_file_path2
2227
    assert source2.ics_filename == 'exceptions-bis.ics'
2228
    assert os.path.exists(old_ics_file_path2) is False
2229
    assert agenda.is_available_for_simple_management() is True
2230

  
2231
    # should not happen: corresponding source does not exist
2232
    source2.delete()
2233
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
2234
    resp.form['ics_newfile'] = Upload('exceptions-ter.ics', ics_file_content, 'text/calendar')
2235
    resp.form.submit()
2236
    assert TimePeriodExceptionSource.objects.count() == 1
2237
    assert TimePeriodException.objects.count() == 1
2238
    assert desk.timeperiodexception_set.count() == 1
2239
    assert desk2.timeperiodexception_set.count() == 0
2240
    assert desk.timeperiodexceptionsource_set.get().ics_filename == 'exceptions-ter.ics'
2241

  
2242

  
2243
@mock.patch('chrono.agendas.models.requests.get')
2244
def test_meetings_agenda_refresh_time_period_exception_source(mocked_get, app, admin_user):
2245
    mocked_response = mock.Mock()
2246
    mocked_response.text = """BEGIN:VCALENDAR
2247
VERSION:2.0
2248
PRODID:-//foo.bar//EN
2249
BEGIN:VEVENT
2250
DTSTART:20180101
2251
DTEND:20180101
2252
SUMMARY:New Year's Eve
2253
END:VEVENT
2254
END:VCALENDAR"""
2255
    mocked_get.return_value = mocked_response
2256

  
2257
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
2258
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
871
    app = login(app)
872
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
873
    resp = resp.click('Blah')
874
    resp = resp.click('Delete')
875
    assert 'Are you sure you want to delete this?' in resp.text
876
    assert 'disabled' not in resp.text
877
    resp = resp.form.submit()
878
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
879
    meeting_type.refresh_from_db()
880
    assert meeting_type.deleted is True
881
    assert '__deleted__' in meeting_type.slug
2259 882

  
2260
    login(app)
2261
    # import a source from an url
883
    # meeting type not showing up anymore
2262 884
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
2263
    resp = resp.click('manage exceptions')
2264
    resp.form['ics_url'] = 'http://example.com/foo.ics'
2265
    resp = resp.form.submit(status=302).follow()
2266
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
2267
    source = TimePeriodExceptionSource.objects.latest('pk')
2268
    assert source.timeperiodexception_set.count() == 1
2269
    exceptions = list(source.timeperiodexception_set.order_by('pk'))
885
    assert 'Meeting Type Foo' not in resp.text
2270 886

  
2271
    desk2 = desk.duplicate()
2272
    source2 = desk2.timeperiodexceptionsource_set.get()
2273
    exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
2274
    assert TimePeriodException.objects.count() == 2
887
    # it is possible to add a new meeting type with the same slug
888
    new_meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
889
    assert new_meeting_type.slug == 'blah'
2275 890

  
2276
    # refresh the source
2277
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
2278
    resp = resp.click('manage exceptions', index=0)
2279
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk)
2280
    assert TimePeriodException.objects.count() == 2
2281
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
2282
    assert exceptions[0].pk != new_exceptions[0].pk
2283
    new_exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
2284
    assert exceptions2[0].pk == new_exceptions2[0].pk
2285

  
2286

  
2287
@mock.patch('chrono.agendas.models.requests.get')
2288
def test_meetings_agenda_refresh_time_period_exception_source_desk_simple_management(
2289
    mocked_get, app, admin_user
2290
):
2291
    mocked_response = mock.Mock()
2292
    mocked_response.text = """BEGIN:VCALENDAR
2293
VERSION:2.0
2294
PRODID:-//foo.bar//EN
2295
BEGIN:VEVENT
2296
DTSTART:20180101
2297
DTEND:20180101
2298
SUMMARY:New Year's Eve
2299
END:VEVENT
2300
END:VCALENDAR"""
2301
    mocked_get.return_value = mocked_response
2302 891

  
2303
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
2304
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2305
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
2306
    desk2 = desk.duplicate()
2307
    source2 = desk2.timeperiodexceptionsource_set.get()
2308
    assert TimePeriodExceptionSource.objects.count() == 2
2309
    assert TimePeriodException.objects.count() == 0  # not imported yet
2310
    assert agenda.is_available_for_simple_management() is True
892
def test_meetings_agenda_add_desk(app, admin_user):
893
    app = login(app)
894
    resp = app.get('/manage/', status=200)
895
    resp = resp.click('New')
896
    resp.form['label'] = 'Foo bar'
897
    resp.form['kind'] = 'meetings'
898
    resp = resp.form.submit()
899
    assert Desk.objects.count() == 1
900
    assert str(Desk.objects.first()) == 'Desk 1'
901
    agenda = Agenda.objects.get(slug='foo-bar')
902
    agenda.desk_simple_management = False
903
    agenda.save()
2311 904

  
2312
    login(app)
2313
    app.get('/manage/time-period-exceptions-source/%d/refresh' % source.pk)
2314
    assert TimePeriodExceptionSource.objects.count() == 2
2315
    assert TimePeriodException.objects.count() == 2
2316
    assert source.timeperiodexception_set.count() == 1
2317
    assert source2.timeperiodexception_set.count() == 1
2318
    assert agenda.is_available_for_simple_management() is True
905
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
906
    resp = resp.click('New Desk')
907
    resp.form['label'] = 'Desk A'
908
    resp = resp.form.submit().follow()
909
    assert Desk.objects.count() == 2
910
    desk = Desk.objects.latest('pk')
911
    TimePeriod.objects.create(
912
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
913
    )
914
    resp = resp.click('New Desk')
915
    resp.form['label'] = 'Desk A'
916
    resp = resp.form.submit().follow()
917
    assert Desk.objects.count() == 3
918
    assert Desk.objects.filter(slug='desk-a-1').count() == 1
919
    assert 'Desk A' in resp.text
920
    new_desk = Desk.objects.latest('pk')
921
    assert new_desk.timeperiod_set.count() == 0
922

  
923
    # unknown pk
924
    app.get('/manage/agendas/0/add-desk', status=404)
2319 925

  
2320
    # should not happen: corresponding source does not exist
2321
    source2.delete()
2322
    app.get('/manage/time-period-exceptions-source/%d/refresh' % source.pk)
2323
    assert TimePeriodExceptionSource.objects.count() == 1
2324
    assert TimePeriodException.objects.count() == 1
2325
    assert source.timeperiodexception_set.count() == 1
926
    # wrong kind
927
    agenda.kind = 'virtual'
928
    agenda.save()
929
    app.get('/manage/agendas/%s/add-desk' % agenda.pk, status=404)
930
    agenda.kind = 'events'
931
    agenda.save()
932
    app.get('/manage/agendas/%s/add-desk' % agenda.pk, status=404)
2326 933

  
2327 934

  
2328 935
@override_settings(
......
2330 937
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
2331 938
    }
2332 939
)
2333
def test_meetings_agenda_time_period_exception_source_from_settings_toggle(app, admin_user):
940
def test_meetings_agenda_add_desk_from_another(app, admin_user):
2334 941
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
2335 942
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2336
    desk.import_timeperiod_exceptions_from_settings(enable=True)
2337
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
2338
    desk2.import_timeperiod_exceptions_from_settings(enable=True)
2339
    assert desk.timeperiodexception_set.exists()
2340
    assert desk2.timeperiodexception_set.exists()
2341

  
2342
    login(app)
2343
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
2344
    assert 'Holidays' in resp.text
2345
    assert 'disabled' not in resp.text
2346
    assert 'refresh' not in resp.text
2347

  
2348
    resp = resp.click('disable').follow()
2349
    assert not desk.timeperiodexception_set.exists()
2350
    assert desk2.timeperiodexception_set.exists()
2351

  
2352
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
2353
    assert 'Holidays' in resp.text
2354
    assert 'disabled' in resp.text
2355

  
2356
    resp = resp.click('enable').follow()
2357
    assert desk.timeperiodexception_set.exists()
2358
    assert desk2.timeperiodexception_set.exists()
943
    TimePeriod.objects.create(
944
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
945
    )
946
    assert Desk.objects.count() == 1
947
    assert desk.timeperiodexceptionsource_set.count() == 0
2359 948

  
2360
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
2361
    assert 'disabled' not in resp.text
949
    app = login(app)
950
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
951
    resp = resp.click('New Desk')
952
    resp.form['label'] = 'Desk B'
953
    resp.form['copy_from'] = desk.pk
954
    resp = resp.form.submit().follow()
955
    assert Desk.objects.count() == 2
956
    new_desk = Desk.objects.latest('pk')
957
    assert new_desk.label == 'Desk B'
958
    assert new_desk.timeperiod_set.count() == 1
959
    assert (
960
        new_desk.timeperiodexceptionsource_set.count() == 0
961
    )  # holidays not automatically added via duplication
2362 962

  
2363 963

  
2364 964
@override_settings(
......
2366 966
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
2367 967
    }
2368 968
)
2369
def test_meetings_agenda_time_period_exception_source_from_settings_toggle_desk_simple_management(
2370
    app, admin_user
2371
):
969
def test_meetings_agenda_add_desk_simple_management(app, admin_user):
2372 970
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
2373 971
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2374
    desk.import_timeperiod_exceptions_from_settings(enable=True)
2375
    source = desk.timeperiodexceptionsource_set.get()
2376
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
2377
    desk2.import_timeperiod_exceptions_from_settings(enable=True)
2378
    source2 = desk2.timeperiodexceptionsource_set.get()
2379
    assert desk.timeperiodexception_set.exists()
2380
    assert desk2.timeperiodexception_set.exists()
2381
    assert agenda.is_available_for_simple_management() is True
2382

  
2383
    login(app)
2384

  
2385
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
2386
    source.refresh_from_db()
2387
    source2.refresh_from_db()
2388
    assert not source.enabled
2389
    assert not source2.enabled
2390
    assert not desk.timeperiodexception_set.exists()
2391
    assert not desk2.timeperiodexception_set.exists()
2392
    assert agenda.is_available_for_simple_management() is True
972
    TimePeriod.objects.create(
973
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
974
    )
975
    assert Desk.objects.count() == 1
976
    assert desk.timeperiodexceptionsource_set.count() == 0
2393 977

  
2394
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
2395
    source.refresh_from_db()
2396
    source2.refresh_from_db()
2397
    assert source.enabled
2398
    assert source2.enabled
2399
    assert desk.timeperiodexception_set.exists()
2400
    assert desk2.timeperiodexception_set.exists()
2401
    assert agenda.is_available_for_simple_management() is True
978
    app = login(app)
979
    resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
980
    resp.form['label'] = 'Desk B'
981
    assert 'copy_from' not in resp.context['form'].fields
982
    resp = resp.form.submit().follow()
983
    assert Desk.objects.count() == 2
984
    new_desk = Desk.objects.latest('pk')
985
    assert new_desk.label == 'Desk B'
986
    assert new_desk.timeperiod_set.count() == 1
987
    assert (
988
        new_desk.timeperiodexceptionsource_set.count() == 0
989
    )  # holidays not automatically added via duplication
2402 990

  
2403
    # should not happen: corresponding source does not exist
2404
    source2.delete()
2405
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
2406
    source.refresh_from_db()
2407
    assert not source.enabled
991
    # ok if no desks (should not happen)
992
    Desk.objects.all().delete()
993
    resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
994
    resp.form['label'] = 'Desk'
995
    assert 'copy_from' not in resp.context['form'].fields
996
    resp = resp.form.submit().follow()
997
    assert Desk.objects.count() == 1
998
    new_desk = Desk.objects.latest('pk')
999
    assert new_desk.timeperiodexceptionsource_set.count() == 1
2408 1000

  
2409 1001

  
2410
def test_meetings_agenda_time_period_exception_source_try_disable_ics(app, admin_user):
1002
def test_meetings_agenda_edit_desk(app, admin_user):
2411 1003
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
2412 1004
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2413
    MeetingType(agenda=agenda, label='Blah').save()
2414
    TimePeriod.objects.create(
2415
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
2416
    )
2417
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
1005
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
1006
    other_agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1007
    other_desk = Desk.objects.create(agenda=other_agenda, label='Desk A')
1008
    assert other_desk.slug == desk.slug
2418 1009

  
2419
    login(app)
2420
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow().follow()
2421
    resp = resp.click('Settings')
2422
    resp = resp.click('manage exceptions')
2423
    assert 'test.ics' in resp.text
1010
    app = login(app)
1011
    resp = app.get('/manage/desks/%s/edit' % desk.pk)
1012
    resp.form['label'] = 'Desk C'
1013
    resp.form['slug'] = desk.slug
1014
    resp = resp.form.submit().follow()
1015
    assert 'Desk A' not in resp.text
1016
    assert 'Desk B' in resp.text
1017
    assert 'Desk C' in resp.text
1018
    desk.refresh_from_db()
1019
    assert desk.label == 'Desk C'
1020
    assert desk.slug == other_desk.slug
2424 1021

  
2425
    assert app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk, status=404)
1022
    # check slug edition
1023
    resp = app.get('/manage/desks/%s/edit' % desk.pk)
1024
    resp.form['slug'] = desk2.slug
1025
    resp = resp.form.submit()
1026
    assert resp.context['form'].errors['slug'] == ['Another desk exists with the same identifier.']
2426 1027

  
1028
    # unknown pk
1029
    app.get('/manage/desks/0/edit', status=404)
2427 1030

  
2428
@override_settings(
2429
    EXCEPTIONS_SOURCES={
2430
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
2431
    }
2432
)
2433
def test_meetings_agenda_time_period_exception_source_from_settings(app, admin_user, freezer):
2434
    freezer.move_to('2020-01-01')
1031
    # wrong kind
1032
    agenda.kind = 'virtual'
1033
    agenda.save()
1034
    app.get('/manage/desks/%s/edit' % desk.pk, status=404)
1035
    agenda.kind = 'events'
1036
    agenda.save()
1037
    app.get('/manage/desks/%s/edit' % desk.pk, status=404)
1038

  
1039

  
1040
def test_meetings_agenda_delete_desk(app, admin_user):
2435 1041
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
2436
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
2437
    desk.import_timeperiod_exceptions_from_settings(enable=True)
2438
    new_year = desk.timeperiodexception_set.filter(label='New year').first()
2439
    remove_url = reverse('chrono-manager-time-period-exception-delete', kwargs={'pk': new_year.pk})
2440
    edit_url = reverse('chrono-manager-time-period-exception-edit', kwargs={'pk': new_year.pk})
1042
    Desk.objects.create(agenda=agenda, label='Desk A')
1043
    desk_b = Desk.objects.create(agenda=agenda, label='Desk B')
2441 1044

  
2442
    login(app)
2443
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
2444
    assert 'New year' in resp.text
2445
    assert remove_url not in resp.text and edit_url not in resp.text
1045
    app = login(app)
1046

  
1047
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1048
    resp = resp.click('Desk A')
1049
    resp = resp.click('Delete')
1050
    resp = resp.form.submit()
1051
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.pk)
1052
    assert Desk.objects.count() == 1
1053

  
1054
    # only one desk
1055
    app.get('/manage/desks/%s/delete' % desk_b.pk, status=404)
2446 1056

  
2447
    resp = resp.click('see all')
2448
    assert 'New year' in resp.text
2449
    assert remove_url not in resp.text and edit_url not in resp.text
1057
    desk_a = Desk.objects.create(agenda=agenda, label='Desk A')
1058

  
1059
    # unknown pk
1060
    app.get('/manage/desks/0/delete', status=404)
2450 1061

  
2451
    app.get(remove_url, status=404)
1062
    # wrong kind
1063
    agenda.kind = 'virtual'
1064
    agenda.save()
1065
    app.get('/manage/desks/%s/delete' % desk_a.pk, status=404)
1066
    agenda.kind = 'events'
1067
    agenda.save()
1068
    app.get('/manage/desks/%s/delete' % desk_a.pk, status=404)
2452 1069

  
2453 1070

  
2454 1071
def test_agenda_day_view(app, admin_user, manager_user, api_user):
......
4455 3072
    assert resp.text.count('Swimming') == 2  # 1 booking + legend
4456 3073
    assert 'Booking colors:' in resp.text
4457 3074
    assert len(resp.pyquery.find('div.booking-colors span.booking-color-label')) == 2
4458

  
4459

  
4460
@override_settings(
4461
    EXCEPTIONS_SOURCES={
4462
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
4463
    }
4464
)
4465
def test_recurring_events_manage_exceptions(settings, app, admin_user, freezer):
4466
    freezer.move_to('2021-07-01 12:10')
4467

  
4468
    app = login(app)
4469
    resp = app.get('/manage/')
4470
    resp = resp.click('New')
4471
    resp.form['label'] = 'Foo bar'
4472
    resp.form['kind'] = 'events'
4473
    resp = resp.form.submit().follow()
4474

  
4475
    agenda = Agenda.objects.get(label='Foo bar')
4476
    assert agenda.desk_set.count() == 1
4477
    desk = agenda.desk_set.get(slug='_exceptions_holder')
4478

  
4479
    event = Event.objects.create(start_datetime=now(), places=10, agenda=agenda)
4480
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
4481
    assert not 'Recurrence exceptions' in resp.text
4482

  
4483
    event.recurrence_days = list(range(7))
4484
    event.save()
4485

  
4486
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
4487
    assert len(resp.pyquery.find('.event-info')) == 31
4488

  
4489
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
4490
    assert 'Recurrence exceptions' in resp.text
4491

  
4492
    resp = resp.click('Add a time period exception')
4493
    resp.form['start_datetime_0'] = now().strftime('%Y-%m-%d')
4494
    resp.form['start_datetime_1'] = now().strftime('%H:%M')
4495
    resp.form['end_datetime_0'] = (now() + datetime.timedelta(days=7)).strftime('%Y-%m-%d')
4496
    resp.form['end_datetime_1'] = (now() + datetime.timedelta(days=7)).strftime('%H:%M')
4497
    resp = resp.form.submit().follow()
4498
    assert desk.timeperiodexception_set.count() == 1
4499

  
4500
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
4501
    assert len(resp.pyquery.find('.event-info')) == 24
4502

  
4503
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
4504
    resp = resp.click('Configure', href='exceptions')
4505
    resp = resp.click('enable').follow()
4506
    assert TimePeriodException.objects.count() > 1
4507
    assert 'Bastille Day' in resp.text
4508

  
4509
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
4510
    assert len(resp.pyquery.find('.event-info')) == 23
4511

  
4512
    # add recurrence end date, which lead to recurrences creation
4513
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
4514
    resp.form['recurrence_end_date'] = (now() + datetime.timedelta(days=31)).strftime('%Y-%m-%d')
4515
    resp = resp.form.submit()
4516

  
4517
    # recurrences corresponding to exceptions have not been created
4518
    assert Event.objects.count() == 24
4519

  
4520

  
4521
def test_recurring_events_exceptions_report(settings, app, admin_user, freezer):
4522
    freezer.move_to('2021-07-01 12:10')
4523
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
4524
    Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
4525
    event = Event.objects.create(
4526
        start_datetime=now(),
4527
        places=10,
4528
        recurrence_days=list(range(7)),
4529
        recurrence_end_date=now() + datetime.timedelta(days=30),
4530
        agenda=agenda,
4531
    )
4532
    event.create_all_recurrences()
4533

  
4534
    app = login(app)
4535
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
4536
    assert len(resp.pyquery.find('.event-info')) == 30
4537

  
4538
    time_period_exception = TimePeriodException.objects.create(
4539
        desk=agenda.desk_set.get(),
4540
        start_datetime=datetime.date(year=2021, month=7, day=5),
4541
        end_datetime=datetime.date(year=2021, month=7, day=10),
4542
    )
4543
    call_command('update_event_recurrences')
4544

  
4545
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
4546
    assert len(resp.pyquery.find('.event-info')) == 25
4547

  
4548
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
4549
    assert not 'warningnotice' in resp.text
4550

  
4551
    event = Event.objects.get(start_datetime__day=11)
4552
    booking = Booking.objects.create(event=event)
4553
    time_period_exception.end_datetime = datetime.date(year=2021, month=7, day=12)
4554
    time_period_exception.save()
4555
    call_command('update_event_recurrences')
4556

  
4557
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
4558
    assert len(resp.pyquery.find('.event-info')) == 24
4559

  
4560
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
4561
    assert 'warningnotice' in resp.text
4562
    assert 'July 11, 2021, 2:10 p.m.' in resp.text
4563

  
4564
    booking.cancel()
4565
    call_command('update_event_recurrences')
4566

  
4567
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
4568
    assert len(resp.pyquery.find('.event-info')) == 23
4569

  
4570
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
4571
    assert not 'warningnotice' in resp.text
tests/manager/test_exception.py
1
import datetime
2
import os
3
from unittest import mock
4

  
5
import pytest
6
import requests
7
from django.core.files.base import ContentFile
8
from django.core.management import call_command
9
from django.db import connection
10
from django.test import override_settings
11
from django.test.utils import CaptureQueriesContext
12
from django.urls import reverse
13
from django.utils.timezone import localtime, make_aware, now
14
from webtest import Upload
15

  
16
from chrono.agendas.models import (
17
    Agenda,
18
    Booking,
19
    Desk,
20
    Event,
21
    MeetingType,
22
    TimePeriod,
23
    TimePeriodException,
24
    TimePeriodExceptionSource,
25
    UnavailabilityCalendar,
26
)
27
from chrono.manager.forms import TimePeriodExceptionForm
28

  
29
pytestmark = pytest.mark.django_db
30

  
31

  
32
def login(app, username='admin', password='admin'):
33
    login_page = app.get('/login/')
34
    login_form = login_page.forms[0]
35
    login_form['username'] = username
36
    login_form['password'] = password
37
    resp = login_form.submit()
38
    assert resp.status_int == 302
39
    return app
40

  
41

  
42
@override_settings(
43
    EXCEPTIONS_SOURCES={
44
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
45
    }
46
)
47
def test_add_agenda_exceptions_from_settings(app, admin_user):
48
    app = login(app)
49
    resp = app.get('/manage/', status=200)
50
    resp = resp.click('New')
51
    resp.form['label'] = 'Foo bar'
52
    resp.form['kind'] = 'meetings'
53
    resp = resp.form.submit().follow()
54

  
55
    agenda = Agenda.objects.get(label='Foo bar')
56
    assert agenda.desk_set.count() == 1
57

  
58
    default_desk = agenda.desk_set.first()
59
    assert default_desk.timeperiodexception_set.exists()
60
    assert default_desk.timeperiodexceptionsource_set.count() == 1
61

  
62
    source = default_desk.timeperiodexceptionsource_set.first()
63
    assert source.enabled
64

  
65
    agenda.desk_simple_management = False
66
    agenda.save()
67
    resp = app.get('/manage/agendas/%s/add-desk' % agenda.pk)
68
    resp.form['label'] = 'Desk A'
69
    resp = resp.form.submit().follow()
70

  
71
    desk = Desk.objects.get(slug='desk-a')
72
    assert desk.timeperiodexception_set.exists()
73
    assert desk.timeperiodexceptionsource_set.count() == 1
74

  
75
    source = desk.timeperiodexceptionsource_set.first()
76
    assert source.enabled
77

  
78

  
79
def test_meetings_agenda_add_time_period_exception(app, admin_user):
80
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
81
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
82
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
83

  
84
    app = login(app)
85
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
86
    resp = resp.click('Add a time period exception', index=0)
87
    today = datetime.datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
88
    tomorrow = make_aware(today + datetime.timedelta(days=1))
89
    dt_format = '%Y-%m-%d %H:%M'
90
    resp.form['label'] = 'Exception 1'
91
    resp.form['start_datetime_0'] = tomorrow.strftime('%Y-%m-%d')
92
    resp.form['start_datetime_1'] = '08:00'
93
    resp.form['end_datetime_0'] = tomorrow.strftime('%Y-%m-%d')
94
    resp.form['end_datetime_1'] = '16:00'
95
    resp = resp.form.submit().follow()
96
    assert TimePeriodException.objects.count() == 1
97
    assert desk2.timeperiodexception_set.exists() is False
98
    time_period_exception = TimePeriodException.objects.first()
99
    assert localtime(time_period_exception.start_datetime).strftime(dt_format) == tomorrow.replace(
100
        hour=8
101
    ).strftime(dt_format)
102
    assert localtime(time_period_exception.end_datetime).strftime(dt_format) == tomorrow.replace(
103
        hour=16
104
    ).strftime(dt_format)
105
    # add an exception beyond 2 weeks and make sure it isn't listed
106
    resp = resp.click('Add a time period exception', index=1)
107
    future = tomorrow + datetime.timedelta(days=15)
108
    resp.form['label'] = 'Exception 2'
109
    resp.form['start_datetime_0'] = future.strftime('%Y-%m-%d')
110
    resp.form['start_datetime_1'] = '00:00'
111
    resp.form['end_datetime_0'] = future.strftime('%Y-%m-%d')
112
    resp.form['end_datetime_1'] = '16:00'
113
    resp = resp.form.submit().follow()
114
    assert TimePeriodException.objects.count() == 2
115
    assert 'Exception 1' in resp.text
116
    assert 'Exception 2' not in resp.text
117
    resp = resp.click(href="/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk)
118
    assert 'Exception 1' in resp.text
119
    assert 'Exception 2' in resp.text
120

  
121

  
122
def test_meetings_agenda_add_time_period_exception_booking_overlaps(app, admin_user):
123
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
124
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
125
    meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah')
126

  
127
    # different type of overlap
128
    # event.start_datetime <= exception.start_datetime < event.start_datetime + meeting_type.duration
129
    event = Event.objects.create(
130
        agenda=agenda,
131
        places=1,
132
        desk=desk,
133
        meeting_type=meeting_type,
134
        start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30)),
135
    )
136
    Booking.objects.create(event=event)
137
    app = login(app)
138
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
139
    resp.form['label'] = 'Exception'
140
    resp.form['start_datetime_0'] = '2017-05-22'
141
    resp.form['start_datetime_1'] = '10:45'
142
    resp.form['end_datetime_0'] = '2017-05-22'
143
    resp.form['end_datetime_1'] = '17:30'
144
    resp = resp.form.submit().follow()
145
    assert TimePeriodException.objects.count() == 1
146
    assert 'Exception added.' in resp.text
147
    assert 'One or several bookings exists within this time slot.' in resp.text
148

  
149

  
150
def test_meetings_agenda_add_time_period_exception_all_desks(app, admin_user):
151
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
152
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
153

  
154
    # add global time exception
155
    # only one desk: no option to apply to all desks
156
    app = login(app)
157
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
158
    assert 'all_desks' not in resp.context['form'].fields
159
    # more than one desk
160
    Desk.objects.create(agenda=agenda, label='Desk B')
161
    agenda2 = Agenda.objects.create(label='Foo bar', kind='meetings')
162
    Desk.objects.create(agenda=agenda2, label='Other Desk')  # check exception is not created for this one
163
    event = Event.objects.create(
164
        agenda=agenda, places=1, desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
165
    )
166
    Booking.objects.create(event=event)
167
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
168
    resp.form['label'] = 'Exception'
169
    resp.form['start_datetime_0'] = '2017-05-22'
170
    resp.form['start_datetime_1'] = '08:00'
171
    resp.form['end_datetime_0'] = '2017-05-26'
172
    resp.form['end_datetime_1'] = '17:30'
173
    resp.form['all_desks'] = True
174
    resp = resp.form.submit().follow()
175
    assert TimePeriodException.objects.count() == 2
176
    assert 'Exceptions added.' in resp.text
177
    assert 'One or several bookings exists within this time slot.' in resp.text
178

  
179
    exception = TimePeriodException.objects.first()
180
    resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
181
    assert 'all_desks' not in resp.context['form'].fields
182

  
183

  
184
def test_meetings_agenda_add_time_period_exception_when_booking_exists(app, admin_user):
185
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
186
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
187
    MeetingType(agenda=agenda, label='Blah').save()
188
    TimePeriod.objects.create(
189
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
190
    )
191
    event = Event.objects.create(
192
        agenda=agenda, places=1, desk=desk, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
193
    )
194
    Booking.objects.create(event=event)
195
    login(app)
196
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
197
    resp = resp.click('Add a time period exception')
198
    resp = resp.form.submit()  # submit empty form
199
    # fields should be marked with errors
200
    assert resp.text.count('This field is required.') == 2
201
    # try again with data in fields
202
    resp.form['start_datetime_0'] = '2017-05-22'
203
    resp.form['start_datetime_1'] = '08:00'
204
    resp.form['end_datetime_0'] = '2017-05-26'
205
    resp.form['end_datetime_1'] = '17:30'
206
    resp = resp.form.submit().follow()
207
    assert 'Exception added.' in resp.text
208
    assert 'One or several bookings exists within this time slot.' in resp.text
209
    assert TimePeriodException.objects.count() == 1
210

  
211

  
212
def test_meetings_agenda_add_time_period_exception_when_cancelled_booking_exists(app, admin_user):
213
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
214
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
215
    MeetingType(agenda=agenda, label='Blah').save()
216
    TimePeriod.objects.create(
217
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
218
    )
219
    event = Event.objects.create(
220
        agenda=agenda, places=1, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
221
    )
222
    Booking.objects.create(
223
        event=event, cancellation_datetime=make_aware(datetime.datetime(2017, 5, 20, 10, 30))
224
    )
225
    login(app)
226
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
227
    resp = resp.click('Add a time period exception')
228
    resp.form['start_datetime_0'] = '2017-05-22'
229
    resp.form['start_datetime_1'] = '08:00'
230
    resp.form['end_datetime_0'] = '2017-05-26'
231
    resp.form['end_datetime_1'] = '17:30'
232
    resp = resp.form.submit().follow()
233
    assert 'Exception added' in resp.text
234
    assert 'One or several bookings exists within this time slot.' not in resp.text
235
    assert TimePeriodException.objects.count() == 1
236

  
237

  
238
def test_meetings_agenda_add_time_period_exception_desk_simple_management(app, admin_user):
239
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
240
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
241
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
242
    event = Event.objects.create(
243
        agenda=agenda, places=1, desk=desk2, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
244
    )
245
    Booking.objects.create(event=event)
246
    assert agenda.is_available_for_simple_management() is True
247

  
248
    login(app)
249
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period-exception' % (agenda.pk, desk.pk))
250
    assert 'all_desks' not in resp.form.fields
251
    resp.form['label'] = 'Exception'
252
    resp.form['start_datetime_0'] = '2017-05-22'
253
    resp.form['start_datetime_1'] = '8:00'
254
    resp.form['end_datetime_0'] = '2017-05-22'
255
    resp.form['end_datetime_1'] = '17:30'
256
    resp = resp.form.submit().follow()
257
    assert 'Exception added' in resp.text
258
    assert 'One or several bookings exists within this time slot.' in resp.text
259
    assert TimePeriodException.objects.count() == 2
260
    assert agenda.is_available_for_simple_management() is True
261

  
262

  
263
def test_meetings_agenda_edit_time_period_exception(app, admin_user):
264
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
265
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
266
    exception = TimePeriodException.objects.create(
267
        label='Exception',
268
        desk=desk,
269
        start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
270
        end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
271
    )
272
    desk2 = desk.duplicate()
273
    exception2 = desk2.timeperiodexception_set.get()
274

  
275
    login(app)
276
    resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
277
    resp.form['start_datetime_1'] = '8:00'
278
    resp.form.submit()
279
    exception.refresh_from_db()
280
    assert exception.start_datetime == make_aware(datetime.datetime(2018, 12, 16, 8, 0))
281
    exception2.refresh_from_db()
282
    assert exception2.start_datetime == make_aware(datetime.datetime(2018, 12, 16, 5, 0))
283

  
284

  
285
def test_meetings_agenda_edit_time_period_exception_overlaps(app, admin_user):
286
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
287
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
288
    calendar = UnavailabilityCalendar.objects.create(label='foo')
289
    calendar.desks.add(desk)
290
    time_period_exception_desk = TimePeriodException.objects.create(
291
        label='Exception',
292
        desk=desk,
293
        start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
294
        end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
295
    )
296
    time_period_exception_calendar = TimePeriodException.objects.create(
297
        label='Exception',
298
        unavailability_calendar=calendar,
299
        start_datetime=make_aware(datetime.datetime(2018, 12, 16, 5, 0)),
300
        end_datetime=make_aware(datetime.datetime(2018, 12, 16, 23, 0)),
301
    )
302
    login(app)
303
    for time_period_exception in (time_period_exception_desk, time_period_exception_calendar):
304
        event = Event.objects.create(
305
            agenda=agenda,
306
            places=1,
307
            desk=desk,
308
            start_datetime=make_aware(datetime.datetime(2018, 12, 16, 10, 30)),
309
        )
310

  
311
        resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
312
        resp = resp.form.submit().follow()
313
        assert 'One or several bookings exists within this time slot.' not in resp.text
314

  
315
        booking = Booking.objects.create(event=event)
316
        resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
317
        resp = resp.form.submit().follow()
318
        assert 'One or several bookings exists within this time slot.' in resp.text
319

  
320
        booking.cancel()
321
        resp = app.get('/manage/time-period-exceptions/%s/edit' % time_period_exception.pk)
322
        resp = resp.form.submit().follow()
323
        assert 'One or several bookings exists within this time slot.' not in resp.text
324

  
325
        booking.delete()
326
        event.delete()
327

  
328

  
329
def test_meetings_agenda_edit_time_period_exception_desk_simple_management(app, admin_user):
330
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
331
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
332
    exception = TimePeriodException.objects.create(
333
        label='Exception',
334
        desk=desk,
335
        start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
336
        end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
337
    )
338
    desk2 = desk.duplicate()
339
    event = Event.objects.create(
340
        agenda=agenda, places=1, desk=desk2, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 30))
341
    )
342
    Booking.objects.create(event=event)
343
    exception2 = desk2.timeperiodexception_set.get()
344
    assert agenda.is_available_for_simple_management() is True
345

  
346
    login(app)
347
    resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
348
    resp.form['label'] = 'Exception foo bar'
349
    resp.form['start_datetime_0'] = '2017-05-22'
350
    resp.form['start_datetime_1'] = '8:00'
351
    resp.form['end_datetime_0'] = '2017-05-22'
352
    resp.form['end_datetime_1'] = '17:30'
353
    resp = resp.form.submit().follow()
354
    assert 'One or several bookings exists within this time slot.' in resp.text
355
    exception.refresh_from_db()
356
    exception2.refresh_from_db()
357
    assert exception.label == 'Exception foo bar'
358
    assert exception.start_datetime == make_aware(datetime.datetime(2017, 5, 22, 8, 0))
359
    assert exception.end_datetime == make_aware(datetime.datetime(2017, 5, 22, 17, 30))
360
    assert exception2.label == 'Exception foo bar'
361
    assert exception2.start_datetime == make_aware(datetime.datetime(2017, 5, 22, 8, 0))
362
    assert exception2.end_datetime == make_aware(datetime.datetime(2017, 5, 22, 17, 30))
363
    assert agenda.is_available_for_simple_management() is True
364

  
365
    # should not happen: corresponding exception does not exist
366
    exception2.delete()
367
    resp = app.get('/manage/time-period-exceptions/%s/edit' % exception.pk)
368
    resp.form['label'] = 'Exception'
369
    resp.form['start_datetime_0'] = '2017-05-21'
370
    resp.form['start_datetime_1'] = '9:00'
371
    resp.form['end_datetime_0'] = '2017-05-21'
372
    resp.form['end_datetime_1'] = '18:30'
373
    # no error
374
    resp.form.submit()
375
    exception.refresh_from_db()
376
    assert exception.label == 'Exception'
377
    assert exception.start_datetime == make_aware(datetime.datetime(2017, 5, 21, 9, 0))
378
    assert exception.end_datetime == make_aware(datetime.datetime(2017, 5, 21, 18, 30))
379

  
380

  
381
def test_meetings_agenda_add_invalid_time_period_exception():
382
    form = TimePeriodExceptionForm(
383
        data={
384
            'start_datetime_0': '2017-05-26',
385
            'start_datetime_1': '17:30',
386
            'end_datetime_0': '2017-05-22',
387
            'end_datetime_1': '08:00',
388
        }
389
    )
390
    assert form.is_valid() is False
391
    assert form.errors['end_datetime'] == ['End datetime must be greater than start datetime.']
392

  
393
    # start_datetime is invalid
394
    form = TimePeriodExceptionForm(
395
        data={
396
            'start_datetime_0': '2017-05-26',
397
            'start_datetime_1': 'foo',
398
            'end_datetime_0': '2017-05-22',
399
            'end_datetime_1': '08:00',
400
        }
401
    )
402
    assert form.is_valid() is False
403

  
404
    # end_datetime is invalid
405
    form = TimePeriodExceptionForm(
406
        data={
407
            'start_datetime_0': '2017-05-26',
408
            'start_datetime_1': '17:30',
409
            'end_datetime_0': 'bar',
410
            'end_datetime_1': '08:00',
411
        }
412
    )
413
    assert form.is_valid() is False
414

  
415

  
416
def test_meetings_agenda_delete_time_period_exception(app, admin_user):
417
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
418
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
419
    TimePeriodException.objects.create(
420
        label='Exception Foo',
421
        desk=desk,
422
        start_datetime=now() + datetime.timedelta(days=1),
423
        end_datetime=now() + datetime.timedelta(days=2),
424
    )
425
    desk.duplicate()
426
    assert TimePeriodException.objects.count() == 2
427

  
428
    login(app)
429
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
430
    resp = resp.click('Exception Foo', index=0)
431
    resp = resp.click('Delete')
432
    resp = resp.form.submit().follow()
433
    assert TimePeriodException.objects.count() == 1
434
    assert resp.request.url.endswith('/manage/agendas/%d/settings' % agenda.pk)
435

  
436
    # stay on exception list
437
    time_period_exception = TimePeriodException.objects.create(
438
        label='Future Exception',
439
        desk=desk,
440
        start_datetime=now() + datetime.timedelta(days=1),
441
        end_datetime=now() + datetime.timedelta(days=2),
442
    )
443
    resp = app.get('/manage/time-period-exceptions/%d/exception-list' % desk.pk)
444
    resp = resp.click(href='/manage/time-period-exceptions/%d/delete' % time_period_exception.pk)
445
    resp = resp.form.submit(
446
        extra_environ={'HTTP_REFERER': str('/manage/time-period-exceptions/%d/exception-list' % desk.pk)}
447
    ).follow()
448
    assert resp.request.url.endswith('/manage/time-period-exceptions/%d/exception-list' % desk.pk)
449

  
450

  
451
def test_meetings_agenda_delete_time_period_exception_desk_simple_management(app, admin_user):
452
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
453
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
454
    exception = TimePeriodException.objects.create(
455
        label='Exception',
456
        desk=desk,
457
        start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
458
        end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
459
    )
460
    desk.duplicate()
461
    assert TimePeriodException.objects.count() == 2
462
    assert agenda.is_available_for_simple_management() is True
463

  
464
    login(app)
465
    resp = app.get('/manage/time-period-exceptions/%d/delete' % exception.pk)
466
    resp.form.submit()
467
    assert TimePeriodException.objects.count() == 0
468
    assert agenda.is_available_for_simple_management() is True
469

  
470
    # should not happen: corresponding exception does not exist
471
    exception = TimePeriodException.objects.create(
472
        label='Exception',
473
        desk=desk,
474
        start_datetime=make_aware(datetime.datetime(2017, 5, 21, 5, 0)),
475
        end_datetime=make_aware(datetime.datetime(2017, 5, 21, 23, 0)),
476
    )
477
    assert TimePeriodException.objects.count() == 1
478
    resp = app.get('/manage/time-period-exceptions/%d/delete' % exception.pk)
479
    resp.form.submit()
480
    assert TimePeriodException.objects.count() == 0
481

  
482

  
483
@override_settings(
484
    EXCEPTIONS_SOURCES={
485
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
486
    }
487
)
488
def test_exception_list(app, admin_user):
489
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
490
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
491
    MeetingType(agenda=agenda, label='Blah').save()
492
    TimePeriod.objects.create(
493
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
494
    )
495
    past_exception = TimePeriodException.objects.create(
496
        label='Past Exception',
497
        desk=desk,
498
        start_datetime=now() - datetime.timedelta(days=2),
499
        end_datetime=now() - datetime.timedelta(days=1),
500
    )
501
    current_exception = TimePeriodException.objects.create(
502
        label='Current Exception',
503
        desk=desk,
504
        start_datetime=now() - datetime.timedelta(days=1),
505
        end_datetime=now() + datetime.timedelta(days=1),
506
    )
507
    future_exception = TimePeriodException.objects.create(
508
        label='Future Exception',
509
        desk=desk,
510
        start_datetime=now() + datetime.timedelta(days=1),
511
        end_datetime=now() + datetime.timedelta(days=2),
512
    )
513

  
514
    login(app)
515
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
516
    assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
517
    assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
518
    assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
519

  
520
    resp = resp.click(href="/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk)
521
    assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
522
    assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
523
    assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
524

  
525
    resp = resp.click(href="/manage/time-period-exceptions/%d/exception-list" % desk.pk)
526
    assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
527
    assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk in resp.text
528
    assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk in resp.text
529

  
530
    with CaptureQueriesContext(connection) as ctx:
531
        app.get("/manage/time-period-exceptions/%d/exception-list" % desk.pk)
532
        assert len(ctx.captured_queries) == 6
533

  
534
    desk.import_timeperiod_exceptions_from_settings(enable=True)
535
    with CaptureQueriesContext(connection) as ctx:
536
        app.get("/manage/time-period-exceptions/%d/exception-list" % desk.pk)
537
        assert len(ctx.captured_queries) == 6
538

  
539
    # add an unavailability calendar
540
    unavailability_calendar = UnavailabilityCalendar.objects.create(label='calendar')
541
    past_exception = TimePeriodException.objects.create(
542
        label='Calendar Past Exception',
543
        unavailability_calendar=unavailability_calendar,
544
        start_datetime=now() - datetime.timedelta(days=2),
545
        end_datetime=now() - datetime.timedelta(days=1),
546
    )
547
    current_exception = TimePeriodException.objects.create(
548
        label='Calendar Current Exception',
549
        unavailability_calendar=unavailability_calendar,
550
        start_datetime=now() - datetime.timedelta(days=1),
551
        end_datetime=now() + datetime.timedelta(days=1),
552
    )
553
    future_exception = TimePeriodException.objects.create(
554
        label='Calendar Future Exception',
555
        unavailability_calendar=unavailability_calendar,
556
        start_datetime=now() + datetime.timedelta(days=1),
557
        end_datetime=now() + datetime.timedelta(days=2),
558
    )
559
    unavailability_calendar.desks.add(desk)
560

  
561
    for url in (
562
        "/manage/time-period-exceptions/%d/exception-extract-list" % desk.pk,
563
        "/manage/time-period-exceptions/%d/exception-list" % desk.pk,
564
    ):
565
        resp = app.get(url)
566
        assert 'Calendar Past Exception' not in resp.text
567
        assert '/manage/time-period-exceptions/%d/edit' % past_exception.pk not in resp.text
568
        assert 'Calendar Current Exception' in resp.text
569
        assert '/manage/time-period-exceptions/%d/edit' % current_exception.pk not in resp.text
570
        assert 'Calendar Future Exception' in resp.text
571
        assert '/manage/time-period-exceptions/%d/edit' % future_exception.pk not in resp.text
572

  
573

  
574
def test_agenda_import_time_period_exception_from_ics(app, admin_user):
575
    agenda = Agenda.objects.create(label='Example', kind='meetings')
576
    desk = Desk.objects.create(agenda=agenda, label='Test Desk')
577
    desk.duplicate()
578
    login(app)
579
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
580
    assert 'Manage exception sources' in resp.text
581
    resp = resp.click('manage exceptions', index=0)
582
    assert "To add new exceptions, you can upload a file or specify an address to a remote calendar." in resp
583
    resp = resp.form.submit(status=200)
584
    assert 'Please provide an ICS File or an URL.' in resp.text
585
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
586
    resp.form['ics_file'] = Upload('exceptions.ics', b'invalid content', 'text/calendar')
587
    resp = resp.form.submit(status=200)
588
    assert 'File format is invalid' in resp.text
589
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
590
    ics_with_no_start_date = b"""BEGIN:VCALENDAR
591
VERSION:2.0
592
PRODID:-//foo.bar//EN
593
BEGIN:VEVENT
594
DTEND:20180101
595
SUMMARY:New Year's Eve
596
END:VEVENT
597
END:VCALENDAR"""
598
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_start_date, 'text/calendar')
599
    resp = resp.form.submit(status=200)
600
    assert 'Event &quot;New Year&#39;s Eve&quot; has no start date.' in resp.text
601
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
602
    ics_with_no_events = b"""BEGIN:VCALENDAR
603
VERSION:2.0
604
PRODID:-//foo.bar//EN
605
END:VCALENDAR"""
606
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_no_events, 'text/calendar')
607
    resp = resp.form.submit(status=200)
608
    assert "The file doesn&#39;t contain any events." in resp.text
609
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 0
610

  
611
    ics_with_exceptions = b"""BEGIN:VCALENDAR
612
VERSION:2.0
613
PRODID:-//foo.bar//EN
614
BEGIN:VEVENT
615
DTSTART:20180101
616
DTEND:20180101
617
SUMMARY:New Year's Eve
618
END:VEVENT
619
END:VCALENDAR"""
620
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
621
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_exceptions, 'text/calendar')
622
    resp = resp.form.submit(status=302)
623
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
624
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
625
    source = desk.timeperiodexceptionsource_set.get()
626
    exception = desk.timeperiodexception_set.get()
627
    assert exception.source == source
628
    assert source.ics_filename == 'exceptions.ics'
629
    assert 'exceptions.ics' in source.ics_file.name
630
    assert source.ics_url is None
631
    resp = resp.follow()
632
    assert 'Exceptions will be imported in a few minutes.' in resp.text
633

  
634

  
635
@pytest.mark.freeze_time('2017-12-01')
636
def test_agenda_import_time_period_exception_from_ics_recurrent(app, admin_user):
637
    agenda = Agenda.objects.create(label='Example', kind='meetings')
638
    desk = Desk.objects.create(agenda=agenda, label='Test Desk')
639
    MeetingType(agenda=agenda, label='Foo').save()
640
    TimePeriod.objects.create(
641
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
642
    )
643
    login(app)
644
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
645
    resp = resp.click('manage exceptions')
646
    ics_with_recurrent_exceptions = b"""BEGIN:VCALENDAR
647
VERSION:2.0
648
PRODID:-//foo.bar//EN
649
BEGIN:VEVENT
650
DTSTART:20180101
651
DTEND:20180101
652
SUMMARY:New Year's Eve
653
RRULE:FREQ=YEARLY
654
END:VEVENT
655
END:VCALENDAR"""
656
    resp.form['ics_file'] = Upload('exceptions.ics', ics_with_recurrent_exceptions, 'text/calendar')
657
    resp = resp.form.submit(status=302).follow()
658
    assert TimePeriodException.objects.filter(desk=desk).count() == 2
659

  
660

  
661
@mock.patch('chrono.agendas.models.requests.get')
662
def test_agenda_import_time_period_exception_with_remote_ics(mocked_get, app, admin_user):
663
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
664
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
665
    desk.duplicate()
666
    login(app)
667
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
668

  
669
    assert 'ics_file' in resp.form.fields
670
    assert 'ics_url' in resp.form.fields
671
    resp.form['ics_url'] = 'http://example.com/foo.ics'
672
    mocked_response = mock.Mock()
673
    mocked_response.text = """BEGIN:VCALENDAR
674
VERSION:2.0
675
PRODID:-//foo.bar//EN
676
BEGIN:VEVENT
677
DTSTART:20180101
678
DTEND:20180101
679
SUMMARY:New Year's Eve
680
END:VEVENT
681
END:VCALENDAR"""
682
    mocked_get.return_value = mocked_response
683
    resp = resp.form.submit(status=302)
684
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
685
    assert TimePeriodExceptionSource.objects.filter(desk=desk).count() == 1
686
    source = desk.timeperiodexceptionsource_set.get()
687
    exception = desk.timeperiodexception_set.get()
688
    assert exception.source == source
689
    assert source.ics_filename is None
690
    assert source.ics_file.name == ''
691
    assert source.ics_url == 'http://example.com/foo.ics'
692

  
693

  
694
@mock.patch('chrono.agendas.models.requests.get')
695
def test_agenda_import_time_period_exception_with_remote_ics_no_events(mocked_get, app, admin_user):
696
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
697
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
698
    MeetingType.objects.create(agenda=agenda, label='Bar')
699
    login(app)
700
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
701
    resp = resp.click('manage exceptions')
702
    resp.form['ics_url'] = 'http://example.com/foo.ics'
703
    mocked_response = mock.Mock()
704
    mocked_response.text = """BEGIN:VCALENDAR
705
VERSION:2.0
706
PRODID:-//foo.bar//EN
707
BEGIN:VEVENT
708
DTSTART:20180101
709
DTEND:20180101
710
SUMMARY:New Year's Eve
711
END:VEVENT
712
END:VCALENDAR"""
713
    mocked_get.return_value = mocked_response
714
    resp = resp.form.submit(status=302)
715
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
716

  
717

  
718
@mock.patch('chrono.agendas.models.requests.get')
719
def test_agenda_import_time_period_exception_from_remote_ics_with_connection_error(
720
    mocked_get, app, admin_user
721
):
722
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
723
    Desk.objects.create(agenda=agenda, label='New Desk')
724
    MeetingType.objects.create(agenda=agenda, label='Bar')
725
    login(app)
726
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
727
    resp = resp.click('manage exceptions')
728

  
729
    assert 'ics_file' in resp.form.fields
730
    assert 'ics_url' in resp.form.fields
731
    resp.form['ics_url'] = 'http://example.com/foo.ics'
732
    mocked_response = mock.Mock()
733
    mocked_get.return_value = mocked_response
734

  
735
    def mocked_requests_connection_error(*args, **kwargs):
736
        raise requests.exceptions.ConnectionError('unreachable')
737

  
738
    mocked_get.side_effect = mocked_requests_connection_error
739
    resp = resp.form.submit(status=200)
740
    assert 'Failed to retrieve remote calendar (http://example.com/foo.ics, unreachable).' in resp.text
741

  
742

  
743
@mock.patch('chrono.agendas.models.requests.get')
744
def test_agenda_import_time_period_exception_from_forbidden_remote_ics(mocked_get, app, admin_user):
745
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
746
    Desk.objects.create(agenda=agenda, label='New Desk')
747
    MeetingType.objects.create(agenda=agenda, label='Bar')
748
    login(app)
749
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
750
    resp = resp.click('manage exceptions')
751
    resp.form['ics_url'] = 'http://example.com/foo.ics'
752
    mocked_response = mock.Mock()
753
    mocked_response.status_code = 403
754
    mocked_get.return_value = mocked_response
755

  
756
    def mocked_requests_http_forbidden_error(*args, **kwargs):
757
        raise requests.exceptions.HTTPError(response=mocked_response)
758

  
759
    mocked_get.side_effect = mocked_requests_http_forbidden_error
760
    resp = resp.form.submit(status=200)
761
    assert 'Failed to retrieve remote calendar (http://example.com/foo.ics, HTTP error 403).' in resp.text
762

  
763

  
764
@mock.patch('chrono.agendas.models.requests.get')
765
def test_agenda_import_time_period_exception_from_remote_ics_with_ssl_error(mocked_get, app, admin_user):
766
    agenda = Agenda.objects.create(label='New Example', kind='meetings')
767
    Desk.objects.create(agenda=agenda, label='New Desk')
768
    MeetingType.objects.create(agenda=agenda, label='Bar')
769
    login(app)
770
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
771
    resp = resp.click('manage exceptions')
772
    resp.form['ics_url'] = 'https://example.com/foo.ics'
773
    mocked_response = mock.Mock()
774
    mocked_get.return_value = mocked_response
775

  
776
    def mocked_requests_http_ssl_error(*args, **kwargs):
777
        raise requests.exceptions.SSLError('SSL error')
778

  
779
    mocked_get.side_effect = mocked_requests_http_ssl_error
780
    resp = resp.form.submit(status=200)
781
    assert 'Failed to retrieve remote calendar (https://example.com/foo.ics, SSL error).' in resp.text
782

  
783

  
784
@mock.patch('chrono.agendas.models.requests.get')
785
def test_agenda_import_time_period_exception_url_desk_simple_management(mocked_get, app, admin_user):
786
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
787
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
788
    desk.duplicate()
789
    assert agenda.is_available_for_simple_management() is True
790

  
791
    login(app)
792
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
793
    resp.form['ics_url'] = 'http://example.com/foo.ics'
794
    mocked_response = mock.Mock()
795
    mocked_response.text = """BEGIN:VCALENDAR
796
VERSION:2.0
797
PRODID:-//foo.bar//EN
798
BEGIN:VEVENT
799
DTSTART:20180101
800
DTEND:20180101
801
SUMMARY:New Year's Eve
802
END:VEVENT
803
END:VCALENDAR"""
804
    mocked_get.return_value = mocked_response
805
    resp = resp.form.submit(status=302)
806
    assert TimePeriodException.objects.count() == 2
807
    assert agenda.is_available_for_simple_management() is True
808

  
809

  
810
def test_agenda_import_time_period_exception_file_desk_simple_management(app, admin_user):
811
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
812
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
813
    desk.duplicate()
814
    assert agenda.is_available_for_simple_management() is True
815

  
816
    login(app)
817
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
818
    ics_exceptions = b"""BEGIN:VCALENDAR
819
VERSION:2.0
820
PRODID:-//foo.bar//EN
821
BEGIN:VEVENT
822
DTSTART:20180101
823
DTEND:20180101
824
SUMMARY:New Year's Eve
825
END:VEVENT
826
END:VCALENDAR"""
827
    resp.form['ics_file'] = Upload('exceptions.ics', ics_exceptions, 'text/calendar')
828
    resp = resp.form.submit(status=302)
829
    assert TimePeriodException.objects.count() == 2
830
    assert agenda.is_available_for_simple_management() is True
831

  
832

  
833
def test_meetings_agenda_delete_time_period_exception_source(app, admin_user):
834
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
835
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
836
    source1 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
837
    TimePeriodException.objects.create(
838
        desk=desk,
839
        source=source1,
840
        start_datetime=now() - datetime.timedelta(days=1),
841
        end_datetime=now() + datetime.timedelta(days=1),
842
    )
843
    source2 = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
844
    TimePeriodException.objects.create(
845
        desk=desk,
846
        source=source2,
847
        start_datetime=now() - datetime.timedelta(days=1),
848
        end_datetime=now() + datetime.timedelta(days=1),
849
    )
850
    desk.duplicate()
851
    assert TimePeriodException.objects.count() == 4
852
    assert TimePeriodExceptionSource.objects.count() == 4
853

  
854
    login(app)
855
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source2.pk)
856
    resp = resp.form.submit()
857
    assert TimePeriodException.objects.count() == 3
858
    assert TimePeriodExceptionSource.objects.count() == 3
859
    assert source1.timeperiodexception_set.count() == 1
860
    assert TimePeriodExceptionSource.objects.filter(pk=source2.pk).exists() is False
861

  
862

  
863
def test_meetings_agenda_delete_time_period_exception_source_desk_simple_management(app, admin_user):
864
    agenda = Agenda.objects.create(label='New Example', kind='meetings', desk_simple_management=True)
865
    desk = Desk.objects.create(agenda=agenda, label='New Desk')
866
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
867
    desk.duplicate()
868
    assert TimePeriodExceptionSource.objects.count() == 2
869
    assert agenda.is_available_for_simple_management() is True
870

  
871
    login(app)
872
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source.pk)
873
    resp.form.submit()
874
    assert TimePeriodExceptionSource.objects.count() == 0
875
    assert agenda.is_available_for_simple_management() is True
876

  
877
    # should not happen: corresponding source does not exist
878
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
879
    assert TimePeriodExceptionSource.objects.count() == 1
880
    resp = app.get('/manage/time-period-exceptions-source/%d/delete' % source.pk)
881
    resp.form.submit()
882
    assert TimePeriodExceptionSource.objects.count() == 0
883

  
884

  
885
def test_meetings_agenda_replace_time_period_exception_source(app, admin_user, freezer):
886
    freezer.move_to('2019-12-01')
887
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
888
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
889
    ics_file_content = b"""BEGIN:VCALENDAR
890
VERSION:2.0
891
PRODID:-//foo.bar//EN
892
BEGIN:VEVENT
893
DTSTART:20180101
894
DTEND:20180101
895
SUMMARY:New Year's Eve
896
RRULE:FREQ=YEARLY
897
END:VEVENT
898
END:VCALENDAR"""
899

  
900
    login(app)
901
    # import a source from a file
902
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
903
    resp.form['ics_file'] = Upload('exceptions.ics', ics_file_content, 'text/calendar')
904
    resp = resp.form.submit(status=302).follow()
905
    assert TimePeriodException.objects.filter(desk=desk).count() == 2
906
    source = TimePeriodExceptionSource.objects.latest('pk')
907
    assert source.timeperiodexception_set.count() == 2
908
    exceptions = list(source.timeperiodexception_set.order_by('pk'))
909
    old_ics_file_path = source.ics_file.path
910

  
911
    desk2 = desk.duplicate()
912
    source2 = desk2.timeperiodexceptionsource_set.get()
913
    exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
914
    assert TimePeriodException.objects.count() == 4
915
    old_ics_file_path2 = source2.ics_file.path
916

  
917
    # replace the source
918
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
919
    resp.form['ics_newfile'] = Upload('exceptions-bis.ics', ics_file_content, 'text/calendar')
920
    resp = resp.form.submit().follow()
921
    source.refresh_from_db()
922
    assert source.ics_file.path != old_ics_file_path
923
    assert source.ics_filename == 'exceptions-bis.ics'
924
    assert os.path.exists(old_ics_file_path) is False
925
    assert TimePeriodException.objects.count() == 4
926
    assert source.timeperiodexception_set.count() == 2
927
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
928
    assert exceptions[0].pk != new_exceptions[0].pk
929
    assert exceptions[1].pk != new_exceptions[1].pk
930
    source2.refresh_from_db()
931
    assert source2.ics_file.path == old_ics_file_path2
932
    assert source2.ics_filename == 'exceptions.ics'
933
    new_exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
934
    assert exceptions2[0].pk == new_exceptions2[0].pk
935
    assert exceptions2[1].pk == new_exceptions2[1].pk
936

  
937

  
938
def test_meetings_agenda_replace_time_period_exception_source_desk_simple_management(app, admin_user):
939
    ics_file_content = b"""BEGIN:VCALENDAR
940
VERSION:2.0
941
PRODID:-//foo.bar//EN
942
BEGIN:VEVENT
943
DTSTART:20180101
944
DTEND:20180101
945
SUMMARY:New Year's Eve
946
END:VEVENT
947
END:VCALENDAR"""
948

  
949
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
950
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
951
    source = TimePeriodExceptionSource.objects.create(
952
        desk=desk,
953
        ics_filename='sample.ics',
954
        ics_file=ContentFile(ics_file_content, name='sample.ics'),
955
    )
956
    desk2 = desk.duplicate()
957
    source2 = desk2.timeperiodexceptionsource_set.get()
958
    assert TimePeriodExceptionSource.objects.count() == 2
959
    assert TimePeriodException.objects.count() == 0  # not imported yet
960
    assert agenda.is_available_for_simple_management() is True
961
    old_ics_file_path = source.ics_file.path
962
    old_ics_file_path2 = source2.ics_file.path
963

  
964
    login(app)
965
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
966
    resp.form['ics_newfile'] = Upload('exceptions-bis.ics', ics_file_content, 'text/calendar')
967
    resp.form.submit()
968
    assert TimePeriodExceptionSource.objects.count() == 2
969
    assert TimePeriodException.objects.count() == 2
970
    assert desk.timeperiodexception_set.count() == 1
971
    assert desk2.timeperiodexception_set.count() == 1
972
    source.refresh_from_db()
973
    assert source.ics_file.path != old_ics_file_path
974
    assert source.ics_filename == 'exceptions-bis.ics'
975
    assert os.path.exists(old_ics_file_path) is False
976
    source2.refresh_from_db()
977
    assert source2.ics_file.path != old_ics_file_path2
978
    assert source2.ics_filename == 'exceptions-bis.ics'
979
    assert os.path.exists(old_ics_file_path2) is False
980
    assert agenda.is_available_for_simple_management() is True
981

  
982
    # should not happen: corresponding source does not exist
983
    source2.delete()
984
    resp = app.get('/manage/time-period-exceptions-source/%d/replace' % source.pk)
985
    resp.form['ics_newfile'] = Upload('exceptions-ter.ics', ics_file_content, 'text/calendar')
986
    resp.form.submit()
987
    assert TimePeriodExceptionSource.objects.count() == 1
988
    assert TimePeriodException.objects.count() == 1
989
    assert desk.timeperiodexception_set.count() == 1
990
    assert desk2.timeperiodexception_set.count() == 0
991
    assert desk.timeperiodexceptionsource_set.get().ics_filename == 'exceptions-ter.ics'
992

  
993

  
994
@mock.patch('chrono.agendas.models.requests.get')
995
def test_meetings_agenda_refresh_time_period_exception_source(mocked_get, app, admin_user):
996
    mocked_response = mock.Mock()
997
    mocked_response.text = """BEGIN:VCALENDAR
998
VERSION:2.0
999
PRODID:-//foo.bar//EN
1000
BEGIN:VEVENT
1001
DTSTART:20180101
1002
DTEND:20180101
1003
SUMMARY:New Year's Eve
1004
END:VEVENT
1005
END:VCALENDAR"""
1006
    mocked_get.return_value = mocked_response
1007

  
1008
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1009
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1010

  
1011
    login(app)
1012
    # import a source from an url
1013
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1014
    resp = resp.click('manage exceptions')
1015
    resp.form['ics_url'] = 'http://example.com/foo.ics'
1016
    resp = resp.form.submit(status=302).follow()
1017
    assert TimePeriodException.objects.filter(desk=desk).count() == 1
1018
    source = TimePeriodExceptionSource.objects.latest('pk')
1019
    assert source.timeperiodexception_set.count() == 1
1020
    exceptions = list(source.timeperiodexception_set.order_by('pk'))
1021

  
1022
    desk2 = desk.duplicate()
1023
    source2 = desk2.timeperiodexceptionsource_set.get()
1024
    exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
1025
    assert TimePeriodException.objects.count() == 2
1026

  
1027
    # refresh the source
1028
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
1029
    resp = resp.click('manage exceptions', index=0)
1030
    resp = resp.click(href='/manage/time-period-exceptions-source/%d/refresh' % source.pk)
1031
    assert TimePeriodException.objects.count() == 2
1032
    new_exceptions = list(source.timeperiodexception_set.order_by('pk'))
1033
    assert exceptions[0].pk != new_exceptions[0].pk
1034
    new_exceptions2 = list(source2.timeperiodexception_set.order_by('pk'))
1035
    assert exceptions2[0].pk == new_exceptions2[0].pk
1036

  
1037

  
1038
@mock.patch('chrono.agendas.models.requests.get')
1039
def test_meetings_agenda_refresh_time_period_exception_source_desk_simple_management(
1040
    mocked_get, app, admin_user
1041
):
1042
    mocked_response = mock.Mock()
1043
    mocked_response.text = """BEGIN:VCALENDAR
1044
VERSION:2.0
1045
PRODID:-//foo.bar//EN
1046
BEGIN:VEVENT
1047
DTSTART:20180101
1048
DTEND:20180101
1049
SUMMARY:New Year's Eve
1050
END:VEVENT
1051
END:VCALENDAR"""
1052
    mocked_get.return_value = mocked_response
1053

  
1054
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
1055
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1056
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
1057
    desk2 = desk.duplicate()
1058
    source2 = desk2.timeperiodexceptionsource_set.get()
1059
    assert TimePeriodExceptionSource.objects.count() == 2
1060
    assert TimePeriodException.objects.count() == 0  # not imported yet
1061
    assert agenda.is_available_for_simple_management() is True
1062

  
1063
    login(app)
1064
    app.get('/manage/time-period-exceptions-source/%d/refresh' % source.pk)
1065
    assert TimePeriodExceptionSource.objects.count() == 2
1066
    assert TimePeriodException.objects.count() == 2
1067
    assert source.timeperiodexception_set.count() == 1
1068
    assert source2.timeperiodexception_set.count() == 1
1069
    assert agenda.is_available_for_simple_management() is True
1070

  
1071
    # should not happen: corresponding source does not exist
1072
    source2.delete()
1073
    app.get('/manage/time-period-exceptions-source/%d/refresh' % source.pk)
1074
    assert TimePeriodExceptionSource.objects.count() == 1
1075
    assert TimePeriodException.objects.count() == 1
1076
    assert source.timeperiodexception_set.count() == 1
1077

  
1078

  
1079
@override_settings(
1080
    EXCEPTIONS_SOURCES={
1081
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
1082
    }
1083
)
1084
def test_meetings_agenda_time_period_exception_source_from_settings_toggle(app, admin_user):
1085
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1086
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1087
    desk.import_timeperiod_exceptions_from_settings(enable=True)
1088
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
1089
    desk2.import_timeperiod_exceptions_from_settings(enable=True)
1090
    assert desk.timeperiodexception_set.exists()
1091
    assert desk2.timeperiodexception_set.exists()
1092

  
1093
    login(app)
1094
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
1095
    assert 'Holidays' in resp.text
1096
    assert 'disabled' not in resp.text
1097
    assert 'refresh' not in resp.text
1098

  
1099
    resp = resp.click('disable').follow()
1100
    assert not desk.timeperiodexception_set.exists()
1101
    assert desk2.timeperiodexception_set.exists()
1102

  
1103
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
1104
    assert 'Holidays' in resp.text
1105
    assert 'disabled' in resp.text
1106

  
1107
    resp = resp.click('enable').follow()
1108
    assert desk.timeperiodexception_set.exists()
1109
    assert desk2.timeperiodexception_set.exists()
1110

  
1111
    resp = app.get('/manage/agendas/desk/%s/import-exceptions-from-ics/' % desk.pk)
1112
    assert 'disabled' not in resp.text
1113

  
1114

  
1115
@override_settings(
1116
    EXCEPTIONS_SOURCES={
1117
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
1118
    }
1119
)
1120
def test_meetings_agenda_time_period_exception_source_from_settings_toggle_desk_simple_management(
1121
    app, admin_user
1122
):
1123
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
1124
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1125
    desk.import_timeperiod_exceptions_from_settings(enable=True)
1126
    source = desk.timeperiodexceptionsource_set.get()
1127
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
1128
    desk2.import_timeperiod_exceptions_from_settings(enable=True)
1129
    source2 = desk2.timeperiodexceptionsource_set.get()
1130
    assert desk.timeperiodexception_set.exists()
1131
    assert desk2.timeperiodexception_set.exists()
1132
    assert agenda.is_available_for_simple_management() is True
1133

  
1134
    login(app)
1135

  
1136
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
1137
    source.refresh_from_db()
1138
    source2.refresh_from_db()
1139
    assert not source.enabled
1140
    assert not source2.enabled
1141
    assert not desk.timeperiodexception_set.exists()
1142
    assert not desk2.timeperiodexception_set.exists()
1143
    assert agenda.is_available_for_simple_management() is True
1144

  
1145
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
1146
    source.refresh_from_db()
1147
    source2.refresh_from_db()
1148
    assert source.enabled
1149
    assert source2.enabled
1150
    assert desk.timeperiodexception_set.exists()
1151
    assert desk2.timeperiodexception_set.exists()
1152
    assert agenda.is_available_for_simple_management() is True
1153

  
1154
    # should not happen: corresponding source does not exist
1155
    source2.delete()
1156
    app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk)
1157
    source.refresh_from_db()
1158
    assert not source.enabled
1159

  
1160

  
1161
def test_meetings_agenda_time_period_exception_source_try_disable_ics(app, admin_user):
1162
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1163
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1164
    MeetingType(agenda=agenda, label='Blah').save()
1165
    TimePeriod.objects.create(
1166
        weekday=1, desk=desk, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0)
1167
    )
1168
    source = TimePeriodExceptionSource.objects.create(desk=desk, ics_url='https://example.com/test.ics')
1169

  
1170
    login(app)
1171
    resp = app.get('/manage/agendas/%d/' % agenda.pk).follow().follow()
1172
    resp = resp.click('Settings')
1173
    resp = resp.click('manage exceptions')
1174
    assert 'test.ics' in resp.text
1175

  
1176
    assert app.get('/manage/time-period-exceptions-source/%s/toggle' % source.pk, status=404)
1177

  
1178

  
1179
@override_settings(
1180
    EXCEPTIONS_SOURCES={
1181
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
1182
    }
1183
)
1184
def test_meetings_agenda_time_period_exception_source_from_settings(app, admin_user, freezer):
1185
    freezer.move_to('2020-01-01')
1186
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
1187
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
1188
    desk.import_timeperiod_exceptions_from_settings(enable=True)
1189
    new_year = desk.timeperiodexception_set.filter(label='New year').first()
1190
    remove_url = reverse('chrono-manager-time-period-exception-delete', kwargs={'pk': new_year.pk})
1191
    edit_url = reverse('chrono-manager-time-period-exception-edit', kwargs={'pk': new_year.pk})
1192

  
1193
    login(app)
1194
    resp = app.get('/manage/agendas/%d/settings' % agenda.pk)
1195
    assert 'New year' in resp.text
1196
    assert remove_url not in resp.text and edit_url not in resp.text
1197

  
1198
    resp = resp.click('see all')
1199
    assert 'New year' in resp.text
1200
    assert remove_url not in resp.text and edit_url not in resp.text
1201

  
1202
    app.get(remove_url, status=404)
1203

  
1204

  
1205
@override_settings(
1206
    EXCEPTIONS_SOURCES={
1207
        'holidays': {'class': 'workalendar.europe.France', 'label': 'Holidays'},
1208
    }
1209
)
1210
def test_recurring_events_manage_exceptions(settings, app, admin_user, freezer):
1211
    freezer.move_to('2021-07-01 12:10')
1212

  
1213
    app = login(app)
1214
    resp = app.get('/manage/')
1215
    resp = resp.click('New')
1216
    resp.form['label'] = 'Foo bar'
1217
    resp.form['kind'] = 'events'
1218
    resp = resp.form.submit().follow()
1219

  
1220
    agenda = Agenda.objects.get(label='Foo bar')
1221
    assert agenda.desk_set.count() == 1
1222
    desk = agenda.desk_set.get(slug='_exceptions_holder')
1223

  
1224
    event = Event.objects.create(start_datetime=now(), places=10, agenda=agenda)
1225
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
1226
    assert 'Recurrence exceptions' not in resp.text
1227

  
1228
    event.recurrence_days = list(range(7))
1229
    event.save()
1230

  
1231
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
1232
    assert len(resp.pyquery.find('.event-info')) == 31
1233

  
1234
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
1235
    assert 'Recurrence exceptions' in resp.text
1236

  
1237
    resp = resp.click('Add a time period exception')
1238
    resp.form['start_datetime_0'] = now().strftime('%Y-%m-%d')
1239
    resp.form['start_datetime_1'] = now().strftime('%H:%M')
1240
    resp.form['end_datetime_0'] = (now() + datetime.timedelta(days=7)).strftime('%Y-%m-%d')
1241
    resp.form['end_datetime_1'] = (now() + datetime.timedelta(days=7)).strftime('%H:%M')
1242
    resp = resp.form.submit().follow()
1243
    assert desk.timeperiodexception_set.count() == 1
1244

  
1245
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
1246
    assert len(resp.pyquery.find('.event-info')) == 24
1247

  
1248
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
1249
    resp = resp.click('Configure', href='exceptions')
1250
    resp = resp.click('enable').follow()
1251
    assert TimePeriodException.objects.count() > 1
1252
    assert 'Bastille Day' in resp.text
1253

  
1254
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.id, 2021, 7))
1255
    assert len(resp.pyquery.find('.event-info')) == 23
1256

  
1257
    # add recurrence end date, which lead to recurrences creation
1258
    resp = app.get('/manage/agendas/%s/events/%s/edit' % (agenda.id, event.id))
1259
    resp.form['recurrence_end_date'] = (now() + datetime.timedelta(days=31)).strftime('%Y-%m-%d')
1260
    resp = resp.form.submit()
1261

  
1262
    # recurrences corresponding to exceptions have not been created
1263
    assert Event.objects.count() == 24
1264

  
1265

  
1266
def test_recurring_events_exceptions_report(settings, app, admin_user, freezer):
1267
    freezer.move_to('2021-07-01 12:10')
1268
    agenda = Agenda.objects.create(label='Foo bar', kind='events')
1269
    Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
1270
    event = Event.objects.create(
1271
        start_datetime=now(),
1272
        places=10,
1273
        recurrence_days=list(range(7)),
1274
        recurrence_end_date=now() + datetime.timedelta(days=30),
1275
        agenda=agenda,
1276
    )
1277
    event.create_all_recurrences()
1278

  
1279
    app = login(app)
1280
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
1281
    assert len(resp.pyquery.find('.event-info')) == 30
1282

  
1283
    time_period_exception = TimePeriodException.objects.create(
1284
        desk=agenda.desk_set.get(),
1285
        start_datetime=datetime.date(year=2021, month=7, day=5),
1286
        end_datetime=datetime.date(year=2021, month=7, day=10),
1287
    )
1288
    call_command('update_event_recurrences')
1289

  
1290
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
1291
    assert len(resp.pyquery.find('.event-info')) == 25
1292

  
1293
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
1294
    assert 'warningnotice' not in resp.text
1295

  
1296
    event = Event.objects.get(start_datetime__day=11)
1297
    booking = Booking.objects.create(event=event)
1298
    time_period_exception.end_datetime = datetime.date(year=2021, month=7, day=12)
1299
    time_period_exception.save()
1300
    call_command('update_event_recurrences')
1301

  
1302
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
1303
    assert len(resp.pyquery.find('.event-info')) == 24
1304

  
1305
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
1306
    assert 'warningnotice' in resp.text
1307
    assert 'July 11, 2021, 2:10 p.m.' in resp.text
1308

  
1309
    booking.cancel()
1310
    call_command('update_event_recurrences')
1311

  
1312
    resp = app.get('/manage/agendas/%s/%s/%s/' % (agenda.pk, 2021, 7))
1313
    assert len(resp.pyquery.find('.event-info')) == 23
1314

  
1315
    resp = app.get('/manage/agendas/%s/settings' % agenda.id)
1316
    assert 'warningnotice' not in resp.text
tests/manager/test_timeperiod.py
1
import datetime
2

  
3
import pytest
4

  
5
from chrono.agendas.models import Agenda, Desk, MeetingType, TimePeriod
6

  
7
pytestmark = pytest.mark.django_db
8

  
9

  
10
def login(app, username='admin', password='admin'):
11
    login_page = app.get('/login/')
12
    login_form = login_page.forms[0]
13
    login_form['username'] = username
14
    login_form['password'] = password
15
    resp = login_form.submit()
16
    assert resp.status_int == 302
17
    return app
18

  
19

  
20
def test_meetings_agenda_add_time_period(app, admin_user):
21
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
22
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
23
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
24
    MeetingType.objects.create(agenda=agenda, label='Blah')
25
    app = login(app)
26
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
27
    resp = resp.click('Add a time period', index=0)
28
    resp.form.get('weekdays', index=2).checked = True
29
    resp.form['start_time'] = '10:00'
30
    resp.form['end_time'] = '17:00'
31
    resp = resp.form.submit()
32
    assert TimePeriod.objects.get(desk=desk).weekday == 2
33
    assert TimePeriod.objects.get(desk=desk).start_time.hour == 10
34
    assert TimePeriod.objects.get(desk=desk).start_time.minute == 0
35
    assert TimePeriod.objects.get(desk=desk).end_time.hour == 17
36
    assert TimePeriod.objects.get(desk=desk).end_time.minute == 0
37
    assert desk2.timeperiod_set.exists() is False
38
    resp = resp.follow()
39

  
40
    # add a second time period
41
    resp = resp.click('Add a time period', index=0)
42
    resp.form.get('weekdays', index=0).checked = True
43
    resp.form['start_time'] = '10:00'
44
    resp.form['end_time'] = '13:00'
45
    resp = resp.form.submit()
46
    resp = resp.follow()
47
    assert 'Monday / 10 a.m. → 1 p.m.' in resp.text
48
    assert 'Wednesday / 10 a.m. → 5 p.m.' in resp.text
49
    assert resp.text.index('Monday') < resp.text.index('Wednesday')
50

  
51
    # invert start and end
52
    resp2 = resp.click('Add a time period', index=0)
53
    resp2.form.get('weekdays', index=0).checked = True
54
    resp2.form['start_time'] = '13:00'
55
    resp2.form['end_time'] = '10:00'
56
    resp2 = resp2.form.submit()
57
    assert 'End time must come after start time.' in resp2.text
58

  
59
    # and add same time periods on multiple days
60
    resp = resp.click('Add a time period', index=0)
61
    resp.form.get('weekdays', index=4).checked = True
62
    resp.form.get('weekdays', index=5).checked = True
63
    resp.form['start_time'] = '10:00'
64
    resp.form['end_time'] = '13:00'
65
    resp = resp.form.submit()
66
    assert TimePeriod.objects.filter(desk=desk).count() == 4
67

  
68

  
69
def test_meetings_agenda_add_time_period_desk_simple_management(app, admin_user):
70
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
71
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
72
    desk2 = Desk.objects.create(agenda=agenda, label='Desk B')
73
    assert agenda.is_available_for_simple_management() is True
74

  
75
    app = login(app)
76
    resp = app.get('/manage/agendas/%s/desk/%s/add-time-period' % (agenda.pk, desk.pk))
77
    resp.form.get('weekdays', index=0).checked = True
78
    resp.form['start_time'] = '10:00'
79
    resp.form['end_time'] = '13:00'
80
    resp.form.submit()
81

  
82
    assert TimePeriod.objects.filter(desk=desk).count() == 1
83
    assert TimePeriod.objects.filter(desk=desk2).count() == 1
84
    assert agenda.is_available_for_simple_management() is True
85

  
86

  
87
def test_meetings_agenda_add_time_period_on_missing_desk(app, admin_user):
88
    app = login(app)
89
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
90
    app.get('/manage/agendas/%s/desk/0/add-time-period' % agenda.pk, status=404)
91

  
92

  
93
def test_meetings_agenda_add_time_period_as_manager(app, manager_user):
94
    agenda = Agenda(label='Foo bar', kind='meetings')
95
    agenda.view_role = manager_user.groups.all()[0]
96
    agenda.save()
97
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
98
    app = login(app, username='manager', password='manager')
99
    resp = app.get('/manage/agendas/%d/' % agenda.id)
100
    assert 'Settings' not in resp.text
101
    resp = app.get('/manage/agendas/%d/settings' % agenda.id, status=403)
102
    MeetingType(agenda=agenda, label='Blah').save()
103
    app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=403)
104
    time_period = TimePeriod(
105
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
106
    )
107
    time_period.save()
108
    resp = app.get('/manage/agendas/%d/' % agenda.id)
109
    app.get('/manage/timeperiods/%d/edit' % time_period.id, status=403)
110
    app.get('/manage/timeperiods/%d/delete' % time_period.id, status=403)
111
    # grant edit right to manager
112
    agenda.edit_role = manager_user.groups.all()[0]
113
    agenda.save()
114

  
115
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
116
    assert 'Add a time period' in resp.text
117
    assert '/manage/timeperiods/%s/edit' % time_period.id in resp.text
118
    assert '/manage/timeperiods/%s/delete' % time_period.id in resp.text
119

  
120
    app.get('/manage/agendas/%d/desk/%d/add-time-period' % (agenda.id, desk.id), status=200)
121
    app.get('/manage/timeperiods/%d/edit' % time_period.id, status=200)
122

  
123

  
124
def test_meetings_agenda_edit_time_period(app, admin_user):
125
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
126
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
127
    time_period = TimePeriod.objects.create(
128
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
129
    )
130
    desk2 = desk.duplicate()
131
    time_period2 = desk2.timeperiod_set.get()
132

  
133
    app = login(app)
134
    # edit
135
    resp = app.get('/manage/agendas/%s/settings' % agenda.pk)
136
    resp = resp.click('Monday / 9 a.m. → noon', index=0)
137
    assert 'Edit Time Period' in resp.text
138
    resp.form['start_time'] = '10:00'
139
    resp = resp.form.submit()
140
    resp = resp.follow()
141
    time_period.refresh_from_db()
142
    assert time_period.start_time.hour == 10
143
    time_period2.refresh_from_db()
144
    assert time_period2.start_time.hour == 9
145

  
146
    # edit with inverted start/end
147
    resp2 = resp.click('Monday / 10 a.m. → noon')
148
    resp2.form['start_time'] = '18:00'
149
    resp2 = resp2.form.submit()
150
    assert 'End time must come after start time.' in resp2.text
151

  
152

  
153
def test_meetings_agenda_edit_time_period_desk_simple_management(app, admin_user):
154
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
155
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
156
    time_period = TimePeriod.objects.create(
157
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
158
    )
159
    desk2 = desk.duplicate()
160
    time_period2 = desk2.timeperiod_set.get()
161
    assert agenda.is_available_for_simple_management() is True
162

  
163
    app = login(app)
164
    resp = app.get('/manage/timeperiods/%s/edit' % time_period.pk)
165
    resp.form['weekday'] = 3
166
    resp.form['start_time'] = '10:00'
167
    resp.form['end_time'] = '11:00'
168
    resp.form.submit()
169
    time_period.refresh_from_db()
170
    time_period2.refresh_from_db()
171
    assert time_period.weekday == 3
172
    assert time_period.start_time.hour == 10
173
    assert time_period.end_time.hour == 11
174
    assert time_period2.weekday == 3
175
    assert time_period2.start_time.hour == 10
176
    assert time_period2.end_time.hour == 11
177
    assert agenda.is_available_for_simple_management() is True
178

  
179
    # should not happen: corresponding time period does not exist
180
    time_period2.delete()
181
    resp = app.get('/manage/timeperiods/%s/edit' % time_period.pk)
182
    resp.form['weekday'] = 3
183
    resp.form['start_time'] = '10:00'
184
    resp.form['end_time'] = '11:00'
185
    # no error
186
    resp.form.submit()
187
    time_period.refresh_from_db()
188
    assert time_period.weekday == 3
189
    assert time_period.start_time.hour == 10
190
    assert time_period.end_time.hour == 11
191

  
192

  
193
def test_meetings_agenda_delete_time_period(app, admin_user):
194
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
195
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
196
    TimePeriod.objects.create(
197
        desk=desk, weekday=2, start_time=datetime.time(10, 0), end_time=datetime.time(18, 0)
198
    )
199
    desk.duplicate()
200
    assert TimePeriod.objects.count() == 2
201

  
202
    app = login(app)
203
    resp = app.get('/manage/agendas/%s/settings' % agenda.id, status=200)
204
    resp = resp.click('Wednesday', index=0)
205
    resp = resp.click('Delete')
206
    resp = resp.form.submit()
207
    assert resp.location.endswith('/manage/agendas/%s/settings' % agenda.id)
208
    assert TimePeriod.objects.count() == 1
209

  
210

  
211
def test_meetings_agenda_delete_time_period_desk_simple_management(app, admin_user):
212
    agenda = Agenda.objects.create(label='Foo bar', kind='meetings', desk_simple_management=True)
213
    desk = Desk.objects.create(agenda=agenda, label='Desk A')
214
    time_period = TimePeriod.objects.create(
215
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
216
    )
217
    desk.duplicate()
218
    assert TimePeriod.objects.count() == 2
219
    assert agenda.is_available_for_simple_management() is True
220

  
221
    app = login(app)
222
    resp = app.get('/manage/timeperiods/%s/delete' % time_period.pk)
223
    resp.form.submit()
224
    assert TimePeriod.objects.count() == 0
225
    assert agenda.is_available_for_simple_management() is True
226

  
227
    # should not happen: corresponding time period does not exist
228
    time_period = TimePeriod.objects.create(
229
        desk=desk, weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(12, 0)
230
    )
231
    resp = app.get('/manage/timeperiods/%s/delete' % time_period.pk)
232
    resp.form.submit()
233
    assert TimePeriod.objects.count() == 0
0
-