chrono/tests/api/datetimes/test_meetings.py

2810 lines
109 KiB
Python

import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from chrono.agendas.models import (
Agenda,
Booking,
Desk,
Event,
MeetingType,
Resource,
TimePeriod,
TimePeriodException,
UnavailabilityCalendar,
VirtualMember,
)
from chrono.utils.timezone import localtime, make_aware, now
pytestmark = pytest.mark.django_db
def datetime_from_str(dt_str):
return datetime.datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')
def test_datetimes_api_meetings_agenda(app, meetings_agenda):
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
resp = app.get('/api/agenda/%s/meetings/xxx/datetimes/' % meeting_type.agenda.slug, status=404)
resp = app.get(api_url)
assert len(resp.json['data']) == 144
assert resp.json == app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id).json
meetings_agenda.minimal_booking_delay = 7
meetings_agenda.maximal_booking_delay = 28
meetings_agenda.save()
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == 54
meetings_agenda.minimal_booking_delay = 1
meetings_agenda.maximal_booking_delay = 56
meetings_agenda.save()
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == 144
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event(
agenda=meetings_agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=Desk.objects.first(),
)
ev.save()
booking = Booking(event=ev)
booking.save()
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp2.json['data']) == 144
assert resp.json['data'][0] == resp2.json['data'][0]
assert resp.json['data'][1] == resp2.json['data'][1]
assert resp.json['data'][2] != resp2.json['data'][2]
assert resp.json['data'][2]['disabled'] is False
assert resp2.json['data'][2]['disabled'] is True
assert resp.json['data'][3] == resp2.json['data'][3]
# test with a timeperiod overlapping current moment, it should get one
# datetime for the current timeperiod + two from the next week.
if localtime(now()).time().hour == 23:
# skip this part of the test as it would require support for events
# crossing midnight
return
default_desk, _ = Desk.objects.get_or_create(agenda=meetings_agenda, slug='desk-1')
TimePeriod.objects.filter(desk=default_desk).delete()
start_time = localtime(now()) - datetime.timedelta(minutes=10)
time_period = TimePeriod(
weekday=localtime(now()).weekday(),
start_time=start_time,
end_time=start_time + datetime.timedelta(hours=1),
desk=default_desk,
)
time_period.save()
meetings_agenda.minimal_booking_delay = 0
meetings_agenda.maximal_booking_delay = 10
meetings_agenda.save()
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == 3
@pytest.mark.freeze_time('2020-10-24') # tomorrow is time change
def test_datetimes_api_meetings_agenda_time_change(app):
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=3
)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo', duration=60)
for weekday in [0, 5, 6]: # monday, saturday, sunday
TimePeriod.objects.create(
weekday=weekday,
start_time=datetime.time(9, 0),
end_time=datetime.time(10, 00),
desk=desk,
)
TimePeriod.objects.create(
weekday=weekday,
start_time=datetime.time(14, 0),
end_time=datetime.time(15, 00),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert resp.json['data'] == [
{
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-24-0900/'},
'date': '2020-10-24',
'datetime': '2020-10-24 09:00:00',
'end_datetime': '2020-10-24 10:00:00',
'disabled': False,
'id': 'foo:2020-10-24-0900',
'text': 'Oct. 24, 2020, 9 a.m.',
},
{
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-24-1400/'},
'date': '2020-10-24',
'datetime': '2020-10-24 14:00:00',
'end_datetime': '2020-10-24 15:00:00',
'disabled': False,
'id': 'foo:2020-10-24-1400',
'text': 'Oct. 24, 2020, 2 p.m.',
},
{
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-25-0900/'},
'date': '2020-10-25',
'datetime': '2020-10-25 09:00:00',
'end_datetime': '2020-10-25 10:00:00',
'disabled': False,
'id': 'foo:2020-10-25-0900',
'text': 'Oct. 25, 2020, 9 a.m.',
},
{
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-25-1400/'},
'date': '2020-10-25',
'datetime': '2020-10-25 14:00:00',
'end_datetime': '2020-10-25 15:00:00',
'disabled': False,
'id': 'foo:2020-10-25-1400',
'text': 'Oct. 25, 2020, 2 p.m.',
},
{
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-26-0900/'},
'date': '2020-10-26',
'datetime': '2020-10-26 09:00:00',
'end_datetime': '2020-10-26 10:00:00',
'disabled': False,
'id': 'foo:2020-10-26-0900',
'text': 'Oct. 26, 2020, 9 a.m.',
},
{
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-26-1400/'},
'date': '2020-10-26',
'datetime': '2020-10-26 14:00:00',
'end_datetime': '2020-10-26 15:00:00',
'disabled': False,
'id': 'foo:2020-10-26-1400',
'text': 'Oct. 26, 2020, 2 p.m.',
},
]
def test_datetimes_api_meetings_agenda_with_resources(app):
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
tomorrow_str = tomorrow.isoformat()
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
)
other_agenda = Agenda.objects.create(label='Other', kind='meetings')
other_desk = Desk.objects.create(agenda=other_agenda, slug='desk-1')
other_meeting_type = MeetingType.objects.create(agenda=other_agenda, slug='foo-bar', duration=90)
resource1 = Resource.objects.create(label='Resource 1')
resource2 = Resource.objects.create(label='Resource 2')
resource3 = Resource.objects.create(label='Resource 3')
agenda.resources.add(resource1, resource2, resource3)
desk = Desk.objects.create(agenda=agenda, slug='desk-1')
desk2 = Desk.objects.create(agenda=agenda, slug='desk-2')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
TimePeriod.objects.create(
weekday=tomorrow.weekday(),
start_time=datetime.time(9, 0),
end_time=datetime.time(17, 00),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
# all resources are free
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s,%s' % (
agenda.slug,
meeting_type.slug,
resource1.slug,
resource2.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
for slot in resp.json['data']:
assert slot['api']['fillslot_url'].endswith('/?resources=%s,%s' % (resource1.slug, resource2.slug))
# resource 1 is not available from 10h to 11h30 in another agenda
dt = make_aware(datetime.datetime.combine(tomorrow, datetime.time(10, 0)))
event1 = Event.objects.create(
agenda=other_agenda,
meeting_type=other_meeting_type,
places=1,
full=False,
start_datetime=dt,
desk=other_desk,
)
event1.resources.add(resource1)
booking_r1 = Booking.objects.create(event=event1)
# resource 3 is not available from 9h to 10h in this agenda (but desk-1 is free)
dt = make_aware(datetime.datetime.combine(tomorrow, datetime.time(9, 0)))
event2 = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=dt,
desk=desk2,
)
event2.resources.add(resource3)
Booking.objects.create(event=event2)
dt = make_aware(datetime.datetime.combine(tomorrow, datetime.time(9, 30)))
event3 = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=dt,
desk=desk2,
)
event3.resources.add(resource3)
Booking.objects.create(event=event3)
# check for resource 1 and resource 2: not available from 10H to 11H30
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s,%s' % (
agenda.slug,
meeting_type.slug,
resource1.slug,
resource2.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
'%s 10:00:00' % tomorrow_str,
'%s 10:30:00' % tomorrow_str,
'%s 11:00:00' % tomorrow_str,
]
# check for resource 2 only ? it's free
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
agenda.slug,
meeting_type.slug,
resource2.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
for slot in resp.json['data']:
assert slot['api']['fillslot_url'].endswith('/?resources=%s' % resource2.slug)
# check for resource 3: not available from 9H to 10H
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
agenda.slug,
meeting_type.slug,
resource3.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
'%s 09:00:00' % tomorrow_str,
'%s 09:30:00' % tomorrow_str,
]
# check for resource 1 and resource 3
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s,%s' % (
agenda.slug,
meeting_type.slug,
resource1.slug,
resource3.slug,
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get(api_url)
assert len(ctx.captured_queries) == 10
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
'%s 09:00:00' % tomorrow_str,
'%s 09:30:00' % tomorrow_str,
'%s 10:00:00' % tomorrow_str,
'%s 10:30:00' % tomorrow_str,
'%s 11:00:00' % tomorrow_str,
]
# no resources to book
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=' % (
agenda.slug,
meeting_type.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (
agenda.slug,
meeting_type.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
# event 3 is booked without resource, only one desk
event3.desk = desk
event3.save()
event3.resources.clear()
desk2.delete()
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (
agenda.slug,
meeting_type.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
'%s 09:30:00' % tomorrow_str
]
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
agenda.slug,
meeting_type.slug,
resource1.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
'%s 09:30:00' % tomorrow_str,
'%s 10:00:00' % tomorrow_str,
'%s 10:30:00' % tomorrow_str,
'%s 11:00:00' % tomorrow_str,
]
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
agenda.slug,
meeting_type.slug,
resource3.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
'%s 09:30:00' % tomorrow_str
]
# resource is unknown or not valid for this agenda
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=foobarbaz' % (
agenda.slug,
meeting_type.slug,
)
resp = app.get(api_url, status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid slugs: foobarbaz' # legacy
assert resp.json['err_class'] == 'invalid slugs: foobarbaz'
assert resp.json['err_desc'] == 'invalid slugs: foobarbaz'
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s,foobarbaz' % (
agenda.slug,
meeting_type.slug,
resource3.slug,
)
resp = app.get(api_url, status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid slugs: foobarbaz' # legacy
assert resp.json['err_class'] == 'invalid slugs: foobarbaz'
assert resp.json['err_desc'] == 'invalid slugs: foobarbaz'
agenda.resources.remove(resource3)
resp = app.get(api_url, status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid slugs: foobarbaz, resource-3' # legacy
assert resp.json['err_class'] == 'invalid slugs: foobarbaz, resource-3'
assert resp.json['err_desc'] == 'invalid slugs: foobarbaz, resource-3'
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
agenda.slug,
meeting_type.slug,
resource3.slug,
)
resp = app.get(api_url, status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid slugs: resource-3' # legacy
assert resp.json['err_class'] == 'invalid slugs: resource-3'
assert resp.json['err_desc'] == 'invalid slugs: resource-3'
# if booking is canceled the resource is free
booking_r1.cancel()
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
agenda.slug,
meeting_type.slug,
resource1.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 32
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
'%s 09:30:00' % tomorrow_str
]
def test_datetimes_api_meetings_agenda_short_time_periods(app, meetings_agenda, user):
meetings_agenda.minimal_booking_delay = 0
meetings_agenda.maximal_booking_delay = 10
meetings_agenda.save()
default_desk, _ = Desk.objects.get_or_create(agenda=meetings_agenda, slug='desk-1')
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
# test with short time periods
TimePeriod.objects.filter(desk=default_desk).delete()
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
time_period = TimePeriod(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(10, 30),
desk=default_desk,
)
time_period.save()
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == 2
fillslot_url = resp.json['data'][0]['api']['fillslot_url']
two_slots = [resp.json['data'][0]['id'], resp.json['data'][1]['id']]
time_period.end_time = datetime.time(10, 15)
time_period.save()
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == 0
# check booking is not possible
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post(fillslot_url)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'no more desk available' # legacy
assert resp.json['err_class'] == 'no more desk available'
assert resp.json['err_desc'] == 'no more desk available'
# booking the two slots fails too
fillslots_url = '/api/agenda/%s/fillslots/' % meeting_type.agenda.slug
resp = app.post(fillslots_url, params={'slots': two_slots})
assert resp.json['err'] == 1
assert resp.json['reason'] == 'no more desk available' # legacy
assert resp.json['err_class'] == 'no more desk available'
assert resp.json['err_desc'] == 'no more desk available'
@pytest.mark.freeze_time('2021-02-25')
def test_datetimes_api_meetings_agenda_exclude_slots(app):
tomorrow = now() + datetime.timedelta(days=1)
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
TimePeriod.objects.create(
weekday=tomorrow.date().weekday(),
start_time=datetime.time(9, 0),
end_time=datetime.time(17, 00),
desk=desk,
)
desk.duplicate()
event = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
start_datetime=localtime(tomorrow).replace(hour=9, minute=0),
desk=desk,
)
Booking.objects.create(event=event, user_external_id='42')
event2 = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
start_datetime=localtime(tomorrow).replace(hour=10, minute=0),
desk=desk,
)
cancelled = Booking.objects.create(event=event2, user_external_id='35')
cancelled.cancel()
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
assert resp.json['data'][0]['disabled'] is False
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
assert resp.json['data'][2]['disabled'] is False
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
params={'exclude_user_external_id': '35'},
)
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
assert resp.json['data'][0]['disabled'] is False
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
assert resp.json['data'][2]['disabled'] is False
with CaptureQueriesContext(connection) as ctx:
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
params={'exclude_user_external_id': '42'},
)
assert len(ctx.captured_queries) == 9
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
assert resp.json['data'][0]['disabled'] is True
assert 'booked_for_external_user' not in resp.json['data'][0]
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
assert resp.json['data'][2]['disabled'] is False
assert resp.json['meta']['first_bookable_slot']['id'] == 'foo-bar:2021-02-26-0930'
@pytest.mark.freeze_time('2021-02-25')
def test_datetimes_api_meetings_agenda_user_external_id(app):
tomorrow = now() + datetime.timedelta(days=1)
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
TimePeriod.objects.create(
weekday=tomorrow.date().weekday(),
start_time=datetime.time(9, 0),
end_time=datetime.time(17, 00),
desk=desk,
)
desk.duplicate()
event = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
start_datetime=localtime(tomorrow).replace(hour=9, minute=0),
desk=desk,
)
Booking.objects.create(event=event, user_external_id='42')
event2 = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
start_datetime=localtime(tomorrow).replace(hour=10, minute=0),
desk=desk,
)
cancelled = Booking.objects.create(event=event2, user_external_id='35')
cancelled.cancel()
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
assert 'booked_for_external_user' not in resp.json['data'][0]
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
params={'exclude_user_external_id': '35'},
)
assert 'booked_for_external_user' not in resp.json['data'][0]
with CaptureQueriesContext(connection) as ctx:
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
params={'user_external_id': '42'},
)
assert len(ctx.captured_queries) == 9
assert resp.json['data'][0]['booked_for_external_user'] is True
# mix with exclude_user_external_id
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
params={'user_external_id': '42', 'exclude_user_external_id': '35'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'user_external_id and exclude_user_external_id have different values'
@pytest.mark.freeze_time('2021-03-15')
def test_datetimes_api_meetings_agenda_hide_disabled(app):
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar', duration=60)
start_date = now() + datetime.timedelta(days=3)
TimePeriod.objects.create(
weekday=start_date.weekday(),
start_time=datetime.time(9, 0),
end_time=datetime.time(10, 00),
desk=desk,
)
event = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
start_datetime=localtime(start_date).replace(hour=9, minute=0),
desk=desk,
)
Booking.objects.create(event=event)
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
assert resp.json['data'][0]['id'] == 'foo-bar:2021-03-18-0900'
assert resp.json['data'][0]['disabled'] is True
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug),
params={'hide_disabled': True},
)
assert resp.json['data'] == []
def test_booking_api_meeting_different_durations_book_short(app, meetings_agenda, user):
agenda_id = meetings_agenda.id
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
meeting_type_2 = MeetingType(agenda=meetings_agenda, label='Shorter', duration=15)
meeting_type_2.save()
# get long events
resp_long = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
# book a short event
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_2.id)
event_id = resp.json['data'][0]['id']
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert Booking.objects.count() == 1
# the longer event at the same time shouldn't be available anymore
resp_long2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert (
len(resp_long.json['data']) == len([x for x in resp_long2.json['data'] if not x.get('disabled')]) + 1
)
assert resp_long.json['data'][1:] == [x for x in resp_long2.json['data'] if not x.get('disabled')]
def test_booking_api_meeting_different_durations_book_long(app, meetings_agenda, user):
agenda_id = meetings_agenda.id
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
meeting_type_2 = MeetingType(agenda=meetings_agenda, label='Shorter', duration=15)
meeting_type_2.save()
# get short events
resp_short = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_2.id)
# book a long event
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
event_id = resp.json['data'][0]['id']
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert Booking.objects.count() == 1
# this should have removed two short events
resp_short2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_2.id)
assert (
len(resp_short.json['data'])
== len([x for x in resp_short2.json['data'] if not x.get('disabled')]) + 2
)
# book another long event
event_id = resp.json['data'][10]['id']
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert Booking.objects.count() == 2
resp_short2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_2.id)
assert (
len(resp_short.json['data'])
== len([x for x in resp_short2.json['data'] if not x.get('disabled')]) + 4
)
def test_agenda_meeting_next_day(app, meetings_agenda, mock_now, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda(label='Foo', kind='meetings')
agenda.minimal_booking_delay = 1
agenda.maximal_booking_delay = 15
agenda.save()
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
datetime_url = '/api/agenda/meetings/%s/datetimes/' % meeting_type.id
desk = Desk.objects.create(label='foo', agenda=agenda)
for weekday in range(7):
TimePeriod.objects.create(
weekday=weekday, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), desk=desk
)
resp = app.get(datetime_url)
event_data = resp.json['data'][0]
# check all proposed dates are on the next day
tomorrow = mock_now + datetime.timedelta(days=1)
event_datetime = datetime.datetime.strptime(event_data['datetime'], '%Y-%m-%d %H:%M:%S').timetuple()
assert event_datetime[:3] == tomorrow.timetuple()[:3]
# check booking works
first_booking_url = resp.json['data'][0]['api']['fillslot_url']
assert app.post(first_booking_url).json['err'] == 0
assert app.post(first_booking_url).json['err'] == 1
last_booking_url = resp.json['data'][-1]['api']['fillslot_url']
assert app.post(last_booking_url).json['err'] == 0
assert app.post(last_booking_url).json['err'] == 1
# check full datetimes are marked as disabled
resp = app.get(datetime_url)
assert resp.json['data'][0]['disabled']
assert not resp.json['data'][1]['disabled']
assert resp.json['data'][-1]['disabled']
assert not resp.json['data'][-2]['disabled']
def test_agenda_meeting_api_exception(app, meetings_agenda, user):
app.authorization = ('Basic', ('john.doe', 'password'))
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
desk = meetings_agenda.desk_set.first()
# test exception at the lowest limit
excp1 = TimePeriodException.objects.create(
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
)
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len(resp2.json['data']) + 4
# test exception at the highest limit
excp1.end_datetime = make_aware(datetime.datetime(2017, 5, 22, 11, 0))
excp1.save()
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len(resp2.json['data']) + 2
# add an exception with an end datetime less than excp1 end datetime
# and make sure that excp1 end datetime preveil
excp1.end_datetime = make_aware(datetime.datetime(2017, 5, 23, 11, 0))
excp1.save()
TimePeriodException.objects.create(
desk=excp1.desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 15, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0)),
)
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len(resp2.json['data']) + 6
# cover completely to test limit condition in get_all_slots()
full_coverage = TimePeriodException.objects.create(
desk=excp1.desk,
start_datetime=make_aware(datetime.datetime(2017, 1, 1, 0, 0)),
end_datetime=make_aware(datetime.datetime(2018, 1, 1, 0, 0)),
)
resp21 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp21.json['data']) == 0
full_coverage.delete()
# with a second desk
desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda)
time_period = desk.timeperiod_set.first()
TimePeriod.objects.create(
desk=desk2,
start_time=time_period.start_time,
end_time=time_period.end_time,
weekday=time_period.weekday,
)
resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len(resp3.json['data']) + 2 # +2 because excp1 changed
# try to booking just after an exception is set
TimePeriodException.objects.create(
desk=desk2,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 9, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
)
booking_url = resp3.json['data'][0]['api']['fillslot_url']
resp = app.post(booking_url)
assert resp.json['err'] == 1
def test_agenda_meeting_api_in_between_exceptions(app, meetings_agenda, user):
app.authorization = ('Basic', ('john.doe', 'password'))
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
desk = meetings_agenda.desk_set.first()
# test exception at the lowest limit
TimePeriodException.objects.create(
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
)
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len(resp2.json['data']) + 4
# exclude slots on 2017-05-30 and 2017-07-10
date_2017_05_30 = datetime.datetime(2017, 5, 30).date()
date_2017_07_10 = datetime.datetime(2017, 7, 10).date()
count_on_2017_05_30 = len(
[
datum
for datum in resp.json['data']
if datetime_from_str(datum['datetime']).date() == date_2017_05_30
]
)
count_on_2017_07_10 = len(
[
datum
for datum in resp.json['data']
if datetime_from_str(datum['datetime']).date() == date_2017_07_10
]
)
TimePeriodException.objects.create(
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 30, 8, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 30, 18, 0)),
)
TimePeriodException.objects.create(
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 7, 10, 8, 0)),
end_datetime=make_aware(datetime.datetime(2017, 7, 10, 18, 0)),
)
resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp2.json['data']) == len(resp3.json['data']) + count_on_2017_05_30 + count_on_2017_07_10
assert (
len(
[
datum
for datum in resp3.json['data']
if datetime_from_str(datum['datetime']).date() == date_2017_05_30
]
)
== 0
)
assert (
len(
[
datum
for datum in resp3.json['data']
if datetime_from_str(datum['datetime']).date() == date_2017_07_10
]
)
== 0
)
# with a second desk with the same time periods
desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda)
for time_period in desk.timeperiod_set.all():
TimePeriod.objects.create(
desk=desk2,
start_time=time_period.start_time,
end_time=time_period.end_time,
weekday=time_period.weekday,
)
resp4 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len(resp4.json['data'])
def test_agenda_meeting_api_desk_info(app, meetings_agenda, user):
app.authorization = ('Basic', ('john.doe', 'password'))
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
desk = meetings_agenda.desk_set.get(slug='desk-1')
desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda)
for time_period in desk.timeperiod_set.all():
TimePeriod.objects.create(
desk=desk2,
start_time=time_period.start_time,
end_time=time_period.end_time,
weekday=time_period.weekday,
)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
booking_url = resp.json['data'][0]['api']['fillslot_url']
booking_url2 = resp.json['data'][3]['api']['fillslot_url']
resp = app.post(booking_url)
assert resp.json['desk']['label'] == desk.label
assert resp.json['desk']['slug'] == desk.slug
# book the same slot and make sure desk 2 info are returned
resp = app.post(booking_url)
assert resp.json['desk']['label'] == desk2.label
assert resp.json['desk']['slug'] == desk2.slug
# booking slot 3 and make sure desk 1 info are returned
resp = app.post(booking_url2)
assert resp.json['desk']['label'] == desk.label
assert resp.json['desk']['slug'] == desk.slug
@pytest.mark.freeze_time('2017-05-20')
def test_agenda_meeting_gcd_durations(app, user):
meetings_agenda = Agenda.objects.create(
label='Foo bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=8
)
meeting_type_30 = MeetingType.objects.create(agenda=meetings_agenda, label='Blah', duration=30)
desk = Desk.objects.create(agenda=meetings_agenda, label='Desk 1')
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(13, 0),
desk=desk,
)
TimePeriod.objects.create(
weekday=1,
start_time=datetime.time(10, 0),
end_time=datetime.time(17, 0),
desk=desk,
)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.pk)
assert len(resp.json['data']) == 20
meeting_type_20 = MeetingType.objects.create(agenda=meetings_agenda, label='Lorem', duration=20)
assert meetings_agenda.get_base_meeting_duration() == 10
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
assert len(resp.json['data']) == 56
# 16:30 is time period end time (17:00) minus meeting type duration
assert resp.json['data'][-1]['datetime'] == '2017-05-23 16:30:00'
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
assert len(resp.json['data']) == 58
assert resp.json['data'][-1]['datetime'] == '2017-05-23 16:40:00'
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
event_id = resp.json['data'][0]['id']
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
assert Booking.objects.count() == 1
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 55
event_id = [x for x in resp.json['data'] if not x.get('disabled')][0]['id']
resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
assert resp.json['datetime'] == '2017-05-22 10:30:00'
assert Booking.objects.count() == 2
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
event_id = [x for x in resp.json['data'] if not x.get('disabled')][0]['id']
resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
assert resp.json['datetime'] == '2017-05-22 10:50:00'
assert Booking.objects.count() == 3
# create a gap
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
event_id = [x for x in resp.json['data'] if not x.get('disabled')][1]['id']
resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.id, event_id))
assert resp.json['datetime'] == '2017-05-22 11:30:00'
assert Booking.objects.count() == 4
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith(
'2017-05-22 12:00:00'
)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
assert [x for x in resp.json['data'] if not x.get('disabled')][0]['datetime'].startswith(
'2017-05-22 12:00:00'
)
@pytest.mark.freeze_time('2017-05-20')
def test_agenda_meeting_gcd_durations_and_exceptions(app, user):
meetings_agenda = Agenda.objects.create(
label='Foo bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=3
)
desk = Desk.objects.create(agenda=meetings_agenda, label='Desk 1')
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk,
)
TimePeriod.objects.create(
weekday=1,
start_time=datetime.time(10, 0),
end_time=datetime.time(17, 0),
desk=desk,
)
meeting_type_20 = MeetingType.objects.create(agenda=meetings_agenda, label='Blah 20', duration=20)
meeting_type_40 = MeetingType.objects.create(agenda=meetings_agenda, label='Blah 40', duration=40)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
assert len(resp.json['data']) == 6
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_40.id)
assert len(resp.json['data']) == 5
# exception to just leave enough place for a single 20-minutes meeting.
TimePeriodException.objects.create(
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 20)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_20.id)
assert len(resp.json['data']) == 1
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_40.id)
assert len(resp.json['data']) == 0
def test_agenda_meeting_deleted_meetingtype(app, meetings_agenda, user):
MeetingType.objects.all().delete()
meeting_type = MeetingType.objects.create(
agenda=meetings_agenda, label='Blah 20', duration=20, deleted=True
)
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug), status=404
)
meeting_type.deleted = False
meeting_type.save()
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug))
data = resp.json['data']
assert len(data) == 216
# try to book if disabled
meeting_type.deleted = True
meeting_type.save()
fillslot_url = data[0]['api']['fillslot_url']
app.authorization = ('Basic', ('john.doe', 'password'))
resp_booking = app.post(fillslot_url, status=400)
assert 'invalid meeting type id' in resp_booking.json['err_desc']
def test_datetimes_api_meetings_agenda_start_hour_change(app, meetings_agenda):
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
resp = app.get(api_url)
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event(
agenda=meetings_agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=Desk.objects.first(),
)
ev.save()
booking = Booking(event=ev)
booking.save()
resp = app.get(api_url)
assert len([x for x in resp.json['data'] if x['disabled']]) == 1
desk = Desk.objects.get(label='Desk 1')
# shift opening times by 15 minutes
for timeperiod in desk.timeperiod_set.all():
timeperiod.start_time = timeperiod.start_time.replace(minute=15)
timeperiod.end_time = timeperiod.end_time.replace(minute=15)
timeperiod.save()
# two slots should now be marked as disabled as the previous booking spans
# them.
resp = app.get(api_url)
assert len([x for x in resp.json['data'] if x['disabled']]) == 2
def test_virtual_agendas_meetings_datetimes_api(app, virtual_meetings_agenda):
real_agenda = virtual_meetings_agenda.real_agendas.first()
meeting_type = real_agenda.meetingtype_set.first()
default_desk = real_agenda.desk_set.first()
# Unkown meeting
app.get('/api/agenda/%s/meetings/xxx/datetimes/' % virtual_meetings_agenda.slug, status=404)
virt_meeting_type = virtual_meetings_agenda.iter_meetingtypes()[0]
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_meetings_agenda.slug, virt_meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 144
# cover completely to test limit condition in get_all_slots()
full_coverage = TimePeriodException.objects.create(
desk=default_desk,
start_datetime=make_aware(datetime.datetime(2017, 1, 1, 0, 0)),
end_datetime=make_aware(datetime.datetime(2018, 1, 1, 0, 0)),
)
resp = app.get(api_url)
assert len(resp.json['data']) == 0
full_coverage.delete()
virtual_meetings_agenda.minimal_booking_delay = 7
virtual_meetings_agenda.maximal_booking_delay = 28
virtual_meetings_agenda.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 54
virtual_meetings_agenda.minimal_booking_delay = 1
virtual_meetings_agenda.maximal_booking_delay = 56
virtual_meetings_agenda.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 144
resp = app.get(api_url)
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event(
agenda=real_agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=default_desk,
)
ev.save()
booking = Booking(event=ev)
booking.save()
resp2 = app.get(api_url)
assert len(resp2.json['data']) == 144
assert resp.json['data'][0] == resp2.json['data'][0]
assert resp.json['data'][1] == resp2.json['data'][1]
assert resp.json['data'][2] != resp2.json['data'][2]
assert resp.json['data'][2]['disabled'] is False
assert resp2.json['data'][2]['disabled'] is True
assert resp.json['data'][3] == resp2.json['data'][3]
# test with a timeperiod overlapping current moment, it should get one
# datetime for the current timeperiod + two from the next week.
if localtime(now()).time().hour == 23:
# skip this part of the test as it would require support for events
# crossing midnight
return
TimePeriod.objects.filter(desk=default_desk).delete()
start_time = localtime(now()) - datetime.timedelta(minutes=10)
time_period = TimePeriod(
weekday=localtime(now()).weekday(),
start_time=start_time,
end_time=start_time + datetime.timedelta(hours=1),
desk=default_desk,
)
time_period.save()
virtual_meetings_agenda.minimal_booking_delay = 0
virtual_meetings_agenda.maximal_booking_delay = 10
virtual_meetings_agenda.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 3
def test_virtual_agendas_meetings_datetimes_api_with_similar_desk(app):
agenda_foo = Agenda.objects.create(
label='Agenda Foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=4
)
MeetingType.objects.create(agenda=agenda_foo, label='Meeting Type', duration=30)
test_1st_weekday = (localtime(now()).weekday() + 1) % 7
test_2nd_weekday = (localtime(now()).weekday() + 2) % 7
test_3rd_weekday = (localtime(now()).weekday() + 3) % 7
desk_foo = Desk.objects.create(agenda=agenda_foo, label='Desk 1')
TimePeriod.objects.create(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk_foo,
)
TimePeriod.objects.create(
weekday=test_2nd_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk_foo,
)
TimePeriod.objects.create(
weekday=test_3rd_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk_foo,
)
agenda_bar = Agenda.objects.create(
label='Agenda Bar', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=4
)
meeting_type_bar = MeetingType.objects.create(agenda=agenda_bar, label='Meeting Type', duration=30)
desk_bar = Desk.objects.create(agenda=agenda_bar, label='Desk 1')
TimePeriod.objects.create(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk_bar,
)
TimePeriod.objects.create(
weekday=test_2nd_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk_bar,
)
TimePeriod.objects.create(
weekday=test_3rd_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk_bar,
)
virtual_agenda = Agenda.objects.create(
label='Agenda Virtual', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=4
)
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=agenda_foo)
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=agenda_bar)
# 4 slots each day * 3 days
foo_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda_foo.slug, meeting_type_bar.slug)
resp = app.get(foo_api_url)
assert len(resp.json['data']) == 12
# same thing bar agenda
bar_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda_foo.slug, meeting_type_bar.slug)
resp = app.get(bar_api_url)
assert len(resp.json['data']) == 12
# same thing on the virtual agenda
virtual_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, meeting_type_bar.slug)
resp = app.get(virtual_api_url)
assert len(resp.json['data']) == 12
# exclude first day
start = (localtime(now()) + datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
end = (localtime(now()) + datetime.timedelta(days=1)).replace(
hour=23, minute=59, second=59, microsecond=0
)
TimePeriodException.objects.create(start_datetime=start, end_datetime=end, desk=desk_foo)
TimePeriodException.objects.create(start_datetime=start, end_datetime=end, desk=desk_bar)
# exclude second day
start = (localtime(now()) + datetime.timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)
end = (localtime(now()) + datetime.timedelta(days=2)).replace(
hour=23, minute=59, second=59, microsecond=0
)
TimePeriodException.objects.create(start_datetime=start, end_datetime=end, desk=desk_foo)
TimePeriodException.objects.create(start_datetime=start, end_datetime=end, desk=desk_bar)
# 4 slots each day * 1 day
resp = app.get(foo_api_url)
assert len(resp.json['data']) == 4
# same thing bar agenda
resp = app.get(bar_api_url)
assert len(resp.json['data']) == 4
# same thing on the virtual agenda
resp = app.get(virtual_api_url)
assert len(resp.json['data']) == 4
def test_virtual_agendas_meetings_datetimes_delays_api(app, mock_now):
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_1,
)
TimePeriod.objects.create(
weekday=1,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_1,
)
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
TimePeriod.objects.create(
weekday=2,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_1,
)
TimePeriod.objects.create(
weekday=3,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_1,
)
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
virt_meeting_type = virt_agenda.iter_meetingtypes()[0]
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug)
resp = app.get(api_url)
# 8 slots for m each agenda
assert len(resp.json['data']) == 16
# restrict foo's minimal_booking_delay : only bar's slots are left
foo_agenda.minimal_booking_delay = 6
foo_agenda.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 8
# restrict bar's maximal_booking_delay : only half of bar's slots are left
bar_agenda.maximal_booking_delay = 5
bar_agenda.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 4
# put back very slots from foo
foo_agenda.minimal_booking_delay = 1
foo_agenda.maximal_booking_delay = 7
foo_agenda.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 12
def test_virtual_agendas_meetings_datetimes_exluded_periods(app, mock_now):
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_1,
)
TimePeriod.objects.create(
weekday=1,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_1,
)
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
resp = app.get(api_url)
# 8 slots
data = resp.json['data']
assert len(data) == 8
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
assert data[2]['datetime'] == '2017-05-22 11:00:00'
# exclude one hour the first day
tp1 = TimePeriod.objects.create(
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 6
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
# no more slots the 22 thanks to the exclusion period
assert data[2]['datetime'] == '2017-05-23 10:00:00'
# exclude the second day
tp2 = TimePeriod.objects.create(
weekday=1, start_time=datetime.time(9, 0), end_time=datetime.time(18, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 2
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
# go back to no restriction
tp1.delete()
tp2.delete()
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 8
# excluded period applies to every desk
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
TimePeriod.objects.create(
weekday=3,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_2,
)
TimePeriod.objects.create(
weekday=4,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_2,
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 16
# exclude one hour the first day
tp1 = TimePeriod.objects.create(
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 14
# exclude one hour the last day
tp2 = TimePeriod.objects.create(
weekday=4, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 12
# go back to no restriction
tp1.delete()
tp2.delete()
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 16
# add a second real agenda
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(14, 0),
end_time=datetime.time(16, 0),
desk=bar_desk_1,
)
TimePeriod.objects.create(
weekday=1,
start_time=datetime.time(14, 0),
end_time=datetime.time(16, 0),
desk=bar_desk_1,
)
TimePeriod.objects.create(
weekday=2,
start_time=datetime.time(14, 0),
end_time=datetime.time(16, 0),
desk=bar_desk_2,
)
TimePeriod.objects.create(
weekday=3,
start_time=datetime.time(14, 0),
end_time=datetime.time(16, 0),
desk=bar_desk_2,
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 32
# exclude the first day, 11 to 15 : 4 slots
tp1 = TimePeriod.objects.create(
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(15, 0), agenda=virt_agenda
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 28
def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda):
app.authorization = ('Basic', ('john.doe', 'password'))
real_agenda = virtual_meetings_agenda.real_agendas.first()
desk = real_agenda.desk_set.first()
virt_meeting_type = virtual_meetings_agenda.iter_meetingtypes()[0]
datetimes_url = '/api/agenda/%s/meetings/%s/datetimes/' % (
virtual_meetings_agenda.slug,
virt_meeting_type.slug,
)
resp = app.get(datetimes_url)
# test exception at the lowest limit
excp1 = TimePeriodException.objects.create(
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
)
resp2 = app.get(datetimes_url)
assert len(resp.json['data']) == len(resp2.json['data']) + 4
# test exception at the highest limit
excp1.end_datetime = make_aware(datetime.datetime(2017, 5, 22, 11, 0))
excp1.save()
resp2 = app.get(datetimes_url)
assert len(resp.json['data']) == len(resp2.json['data']) + 2
# add an exception with an end datetime less than excp1 end datetime
# and make sure that excp1 end datetime preveil
excp1.end_datetime = make_aware(datetime.datetime(2017, 5, 23, 11, 0))
excp1.save()
TimePeriodException.objects.create(
desk=excp1.desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 15, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0)),
)
resp2 = app.get(datetimes_url)
assert len(resp.json['data']) == len(resp2.json['data']) + 6
# with a second desk
desk2 = Desk.objects.create(label='Desk 2', agenda=real_agenda)
time_period = desk.timeperiod_set.first()
TimePeriod.objects.create(
desk=desk2,
start_time=time_period.start_time,
end_time=time_period.end_time,
weekday=time_period.weekday,
)
resp3 = app.get(datetimes_url)
assert len(resp.json['data']) == len(resp3.json['data']) + 2 # +2 because excp1 changed
def test_virtual_agendas_meetings_datetimes_multiple_agendas(app, mock_now):
foo_agenda = Agenda.objects.create(
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
)
foo_meeting_type = MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
test_2nd_weekday = (localtime(now()).weekday() + 3) % 7
test_3rd_weekday = (localtime(now()).weekday() + 4) % 7
test_4th_weekday = (localtime(now()).weekday() + 5) % 7
def create_time_perdiods(desk, end=12):
TimePeriod.objects.create(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(end, 0),
desk=desk,
)
TimePeriod.objects.create(
weekday=test_2nd_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(end, 0),
desk=desk,
)
create_time_perdiods(foo_desk_1)
virt_agenda = Agenda.objects.create(
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=6
)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
virt_meeting_type = virt_agenda.iter_meetingtypes()[0]
# We are saturday and we can book for next monday and tuesday, 4 slots available each day
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 8
assert resp.json['data'][0]['id'] == 'meeting-type:2017-05-22-1000'
virt_agenda.maximal_booking_delay = 10 # another monday comes in
virt_agenda.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 12
# Back to next monday and tuesday restriction
virt_agenda.maximal_booking_delay = 6
virt_agenda.save()
# Add another agenda
bar_agenda = Agenda.objects.create(
label='Bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
)
bar_meeting_type = MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
create_time_perdiods(bar_desk_1, end=13) # bar_agenda has two more slots each day
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
with CaptureQueriesContext(connection) as ctx:
resp = app.get(api_url)
assert len(resp.json['data']) == 12
assert len(ctx.captured_queries) == 10
# simulate booking
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event.objects.create(
agenda=foo_agenda,
meeting_type=foo_meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=foo_desk_1,
)
booking1 = Booking.objects.create(event=ev)
resp = app.get(api_url)
assert len(resp.json['data']) == 12
# No disabled slot, because the booked slot is still available in second agenda
for slot in resp.json['data']:
assert slot['disabled'] is False
ev = Event.objects.create(
agenda=bar_agenda,
meeting_type=bar_meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=bar_desk_1,
)
booking2 = Booking.objects.create(event=ev)
resp = app.get(api_url)
assert len(resp.json['data']) == 12
# now one slot is disabled
for i, slot in enumerate(resp.json['data']):
if i == 2:
assert slot['disabled']
else:
assert slot['disabled'] is False
# Cancel booking, every slot available
booking1.cancel()
booking2.cancel()
resp = app.get(api_url)
assert len(resp.json['data']) == 12
for slot in resp.json['data']:
assert slot['disabled'] is False
# Add new desk on foo_agenda, open on wednesday
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
TimePeriod.objects.create(
weekday=test_3rd_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_2,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 16
# Add new desk on bar_agenda, open on thursday
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
TimePeriod.objects.create(
weekday=test_4th_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_2,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 20
@pytest.mark.freeze_time('2021-02-25')
def test_virtual_agendas_meetings_datetimes_exclude_slots(app):
tomorrow = now() + datetime.timedelta(days=1)
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
TimePeriod.objects.create(
weekday=tomorrow.date().weekday(),
start_time=datetime.time(9, 0),
end_time=datetime.time(17, 00),
desk=desk,
)
agenda2 = agenda.duplicate()
virt_agenda = Agenda.objects.create(
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=10
)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda2)
event = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
start_datetime=localtime(tomorrow).replace(hour=9, minute=0),
desk=desk,
)
Booking.objects.create(event=event, user_external_id='42')
event2 = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
start_datetime=localtime(tomorrow).replace(hour=10, minute=0),
desk=desk,
)
cancelled = Booking.objects.create(event=event2, user_external_id='35')
cancelled.cancel()
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug))
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
assert resp.json['data'][0]['disabled'] is False
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
assert resp.json['data'][2]['disabled'] is False
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
params={'exclude_user_external_id': '35'},
)
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
assert resp.json['data'][0]['disabled'] is False
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
assert resp.json['data'][2]['disabled'] is False
with CaptureQueriesContext(connection) as ctx:
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
params={'exclude_user_external_id': '42'},
)
assert len(ctx.captured_queries) == 11
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
assert resp.json['data'][0]['disabled'] is True
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
assert resp.json['data'][2]['disabled'] is False
virt_agenda.minimal_booking_delay = None
virt_agenda.maximal_booking_delay = None
virt_agenda.save()
resp = app.get(
'/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, meeting_type.slug),
params={'exclude_user_external_id': '42'},
)
assert resp.json['data'][0]['id'] == 'foo-bar:2021-02-26-0900'
assert resp.json['data'][0]['disabled'] is True
assert resp.json['data'][2]['id'] == 'foo-bar:2021-02-26-1000'
assert resp.json['data'][2]['disabled'] is False
@pytest.mark.freeze_time('2017-05-21')
def test_unavailabilitycalendar_meetings_datetimes(app, user):
meetings_agenda = Agenda.objects.create(label='Meeting', kind='meetings', maximal_booking_delay=7)
desk = Desk.objects.create(agenda=meetings_agenda, label='desk 1')
meeting_type = MeetingType.objects.create(agenda=meetings_agenda, label='Meeting Type', duration=30)
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(9, 0),
end_time=datetime.time(18, 0),
desk=desk,
)
app.authorization = ('Basic', ('john.doe', 'password'))
datetimes_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug)
resp = app.get(datetimes_url)
assert len(resp.json['data']) == 18
# create an unvalailability calendar
unavailability_calendar = UnavailabilityCalendar.objects.create(label='foo holydays')
TimePeriodException.objects.create(
unavailability_calendar=unavailability_calendar,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
)
unavailability_calendar.desks.add(desk)
# link unavailability calendar to a desk of another agenda
other_agenda = Agenda.objects.create(label='Meeting 2', kind='meetings', maximal_booking_delay=7)
other_desk = Desk.objects.create(agenda=other_agenda, label='desk 1')
unavailability_calendar.desks.add(other_desk)
unavailability_calendar2 = UnavailabilityCalendar.objects.create(label='bar holydays')
unavailability_calendar2.desks.add(desk)
# 2 slots are gone
with CaptureQueriesContext(connection) as ctx:
resp2 = app.get(datetimes_url)
assert len(ctx.captured_queries) == 10
assert len(resp.json['data']) == len(resp2.json['data']) + 2
# add a standard desk exception
TimePeriodException.objects.create(
desk=desk,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
)
# 4 slots are gone
resp3 = app.get(datetimes_url)
assert len(resp.json['data']) == len(resp3.json['data']) + 4
def test_unavailabilitycalendar_on_virtual_datetimes(app, user, mock_now):
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_1,
)
TimePeriod.objects.create(
weekday=1,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_1,
)
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
resp = app.get(api_url)
# 8 slots
data = resp.json['data']
assert len(data) == 8
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
assert data[2]['datetime'] == '2017-05-22 11:00:00'
# exclude one hour the first day through an unvalailability calendar on the foo agenda
unavailability_calendar = UnavailabilityCalendar.objects.create(label='foo holydays')
TimePeriodException.objects.create(
unavailability_calendar=unavailability_calendar,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
)
unavailability_calendar.desks.add(foo_desk_1)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 6
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
# no more slots the 22 thanks to the unavailability calendar
assert data[2]['datetime'] == '2017-05-23 10:00:00'
# exclude the second day
TimePeriodException.objects.create(
unavailability_calendar=unavailability_calendar,
start_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 18, 0)),
)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 2
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[1]['datetime'] == '2017-05-22 10:30:00'
# add a second real agenda
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_1,
)
TimePeriod.objects.create(
weekday=1,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_1,
)
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_2,
)
TimePeriod.objects.create(
weekday=1,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_2,
)
# bar_agenda has the same time periods than foo_agenda, but no unavailability calendar
# so we are back at the start : 8 slots
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 8
# exclude one hour the second day through another unvalailability calendar on the bar agenda
unavailability_calendar = UnavailabilityCalendar.objects.create(label='bar holydays')
TimePeriodException.objects.create(
unavailability_calendar=unavailability_calendar,
start_datetime=make_aware(datetime.datetime(2017, 5, 23, 11, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 12, 0)),
)
unavailability_calendar.desks.add(bar_desk_1, bar_desk_2)
# 2 slots are gone
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 6
assert data[0]['datetime'] == '2017-05-22 10:00:00'
assert data[-1]['datetime'] == '2017-05-23 10:30:00'
@pytest.mark.parametrize('test_datetime', (None, '2020-11-11 23:50', '2020-12-06 10:14'))
def test_datetimes_maximal_booking_delay(app, user, freezer, test_datetime):
if test_datetime:
freezer.move_to(test_datetime)
foo_agenda = Agenda.objects.create(
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=1
)
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
foo_desk = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
weekday1 = ((localtime(now())).weekday() + 1) % 7
weekday2 = ((localtime(now())).weekday() + 2) % 7
TimePeriod.objects.create(
weekday=weekday1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk
)
TimePeriod.objects.create(
weekday=weekday2, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk
)
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (foo_agenda.slug)
resp = app.get(api_url)
# minimal_booking_delay=1 and maximal_booking_delay=1 so no slots
data = resp.json['data']
assert len(data) == 0
foo_agenda.maximal_booking_delay = 2
foo_agenda.save()
# 4 slots each day * 1 day (because minimal_booking_delay=1 and maximal_booking_delay=2)
resp = app.get(api_url)
data = resp.json['data']
assert len(data) == 4
def test_meetings_and_virtual_datetimes_date_filter(app):
agenda_foo = Agenda.objects.create(
label='Agenda Foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=7
)
meeting_type = MeetingType.objects.create(agenda=agenda_foo, label='Meeting Type', duration=30)
desk_foo = Desk.objects.create(agenda=agenda_foo, label='Desk 1')
weekday1 = ((localtime(now())).weekday() + 1) % 7
weekday2 = ((localtime(now())).weekday() + 2) % 7
weekday3 = ((localtime(now())).weekday() + 3) % 7
weekday4 = ((localtime(now())).weekday() + 4) % 7
weekday5 = ((localtime(now())).weekday() + 5) % 7
weekday6 = ((localtime(now())).weekday() + 6) % 7
for weekday in (weekday1, weekday2, weekday3, weekday4, weekday5, weekday6):
TimePeriod.objects.create(
weekday=weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk_foo,
)
virtual_agenda = Agenda.objects.create(
label='Agenda Virtual', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=7
)
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=agenda_foo)
# 4 slots each day * 6 days
foo_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda_foo.slug, meeting_type.slug)
resp = app.get(foo_api_url)
assert len(resp.json['data']) == 24
virtual_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, meeting_type.slug)
resp = app.get(virtual_api_url)
assert len(resp.json['data']) == 24
for value in ['foo', '2017-05-42']:
params = {'date_start': value}
resp = app.get(foo_api_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['date_start'] == [
'Datetime has wrong format. Use one of these formats instead: YYYY-MM-DD, YYYY-MM-DD hh:mm, YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'
]
for value in ['foo', '2017-05-42']:
params = {'date_end': value}
resp = app.get(foo_api_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['date_end'] == [
'Datetime has wrong format. Use one of these formats instead: YYYY-MM-DD, YYYY-MM-DD hh:mm, YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z].'
]
# exclude weekday1 through date_start, 4 slots each day * 5 days
params = {'date_start': localtime(now() + datetime.timedelta(days=2)).date().isoformat()}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 20
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 20
params = {
'date_start': localtime(now() + datetime.timedelta(days=2))
.replace(hour=12, minute=0, second=0, microsecond=0)
.isoformat()
}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 16
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 16
params = {
'date_start': localtime(now() + datetime.timedelta(days=2))
.replace(hour=11, minute=0, second=0, microsecond=0)
.isoformat()
}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 18
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 18
# minimal_booking_delay (which exclude weekday1 and wekkday2 ) takes precedence
# 4 slots each day * 4 days
agenda_foo.minimal_booking_delay = 3
agenda_foo.save()
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 16
# also on virtual agenda
virtual_agenda.minimal_booking_delay = 3
virtual_agenda.save()
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 16
# reset
agenda_foo.minimal_booking_delay = 1
virtual_agenda.minimal_booking_delay = 1
agenda_foo.save()
virtual_agenda.save()
# exclude weekday6 through date_end, 4 slots each day * 5 days
params = {'date_end': localtime(now() + datetime.timedelta(days=6)).date().isoformat()}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 20
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 20
params = {
'date_end': localtime(now() + datetime.timedelta(days=6))
.replace(hour=11, minute=0, second=0, microsecond=0)
.isoformat()
}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 22
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 22
# maximal_booking_delay (which exclude weekday5 and weekday6 ) takes precedence
# 4 slots each day * 4 days
agenda_foo.maximal_booking_delay = 5
agenda_foo.save()
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 16
# also on virtual agenda
virtual_agenda.maximal_booking_delay = 5
virtual_agenda.save()
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 16
# now check with exceptions in DB
TimePeriodException.objects.create(
desk=desk_foo,
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
)
params = {
'date_start': localtime(now() + datetime.timedelta(days=2))
.replace(hour=12, minute=0, second=0, microsecond=0)
.isoformat()
}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 8
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 8
params = {
'date_end': localtime(now() + datetime.timedelta(days=2))
.replace(hour=11, minute=0, second=0, microsecond=0)
.isoformat()
}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 6
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 6
def test_datetimes_api_meetings_agenda_meta(app, freezer):
# 2017-05-20 -> saturday
freezer.move_to(make_aware(datetime.datetime(year=2017, month=5, day=20, hour=1, minute=12)))
meetings_agenda = Agenda.objects.create(label='Foo bar Meeting', kind='meetings', maximal_booking_delay=3)
meeting_type = MeetingType(agenda=meetings_agenda, label='Blah', duration=30)
meeting_type.save()
desk1 = Desk.objects.create(agenda=meetings_agenda, label='Desk 1')
desk2 = Desk.objects.create(agenda=meetings_agenda, label='Desk 2')
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
for desk in desk1, desk2:
TimePeriod(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk,
).save()
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][2]['disabled'] is False
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 4,
'first_bookable_slot': resp.json['data'][0],
}
def simulate_booking(slot, desk):
dt = datetime.datetime.strptime(slot['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event(
agenda=meetings_agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=desk,
)
ev.save()
booking = Booking(event=ev, user_external_id='42')
booking.save()
simulate_booking(resp.json['meta']['first_bookable_slot'], desk1)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is False
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 4,
'first_bookable_slot': resp.json['data'][0],
}
resp = app.get(api_url, params={'exclude_user_external_id': '42'})
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is True
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 3,
'first_bookable_slot': resp.json['data'][1],
}
simulate_booking(resp.json['data'][0], desk2)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is True
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 3,
'first_bookable_slot': resp.json['data'][1],
}
for idx in range(1, 4):
simulate_booking(resp.json['data'][idx], desk1)
simulate_booking(resp.json['data'][idx], desk2)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['meta'] == {
'no_bookable_datetimes': True,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 0,
'first_bookable_slot': None,
}
def test_datetimes_api_virtual_meetings_agenda_meta(app, freezer):
# 2017-05-20 -> saturday
freezer.move_to(make_aware(datetime.datetime(year=2017, month=5, day=20, hour=1, minute=12)))
meetings_agenda1 = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=3)
meetings_agenda2 = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=3)
virtual_agenda = Agenda.objects.create(label='Agenda Virtual', kind='virtual', maximal_booking_delay=3)
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda1)
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda2)
desk1 = Desk.objects.create(agenda=meetings_agenda1, label='Desk 1')
desk2 = Desk.objects.create(agenda=meetings_agenda2, label='Desk 2')
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
for agenda, desk in zip((meetings_agenda1, meetings_agenda2), (desk1, desk2)):
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
TimePeriod(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk,
).save()
virt_meeting_type = virtual_agenda.iter_meetingtypes()[0]
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, virt_meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][2]['disabled'] is False
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 4,
'first_bookable_slot': resp.json['data'][0],
}
def simulate_booking(slot, agenda, desk):
dt = datetime.datetime.strptime(slot['id'].split(':')[1], '%Y-%m-%d-%H%M')
ev = Event(
agenda=agenda,
meeting_type=meeting_type,
places=1,
full=False,
start_datetime=make_aware(dt),
desk=desk,
)
ev.save()
booking = Booking(event=ev, user_external_id='42')
booking.save()
simulate_booking(resp.json['data'][0], meetings_agenda1, desk1)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is False
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 4,
'first_bookable_slot': resp.json['data'][0],
}
resp = app.get(api_url, params={'exclude_user_external_id': '42'})
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is True
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 3,
'first_bookable_slot': resp.json['data'][1],
}
simulate_booking(resp.json['data'][0], meetings_agenda2, desk2)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['data'][0]['disabled'] is True
assert resp.json['meta'] == {
'no_bookable_datetimes': False,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 3,
'first_bookable_slot': resp.json['data'][1],
}
for idx in range(1, 4):
simulate_booking(resp.json['data'][idx], meetings_agenda1, desk1)
simulate_booking(resp.json['data'][idx], meetings_agenda2, desk2)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert resp.json['meta'] == {
'no_bookable_datetimes': True,
'bookable_datetimes_number_total': 4,
'bookable_datetimes_number_available': 0,
'first_bookable_slot': None,
}
def test_date_filter_overlapping_events(app):
# Create a meeting of 30 minutes from 09:45 to 10:15 and look for available
# 5 minutes meeting between 10:00 and 10:30, there should be only 3 if the
# exclusion from 30 minutes event is enforced.
agenda = Agenda.objects.create(label='foo', kind='meetings', minimal_booking_delay=0)
mt30 = MeetingType.objects.create(agenda=agenda, label='mt30', duration=30)
MeetingType.objects.create(agenda=agenda, label='mt5', duration=5)
desk = Desk.objects.create(agenda=agenda, label='desk')
for weekday in range(7):
TimePeriod.objects.create(
weekday=weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=desk,
)
# base_date, today at midnight
base_date = localtime(now() + datetime.timedelta(days=1))
base_date = base_date.replace(hour=0, minute=0, second=0, microsecond=0)
def make_date(hour, minute):
return base_date.replace(hour=hour, minute=minute).strftime('%Y-%m-%d %H:%M:00')
def make_date_filters(h1, m1, h2, m2):
return {'hide_disabled': 'true', 'date_start': make_date(h1, m1), 'date_end': make_date(h2, m2)}
Event.objects.create(
agenda=agenda,
slug='wtf',
meeting_type=mt30,
start_datetime=base_date.replace(hour=9, minute=45),
full=False,
places=1,
desk=desk,
)
resp = app.get('/api/agenda/foo/meetings/mt5/datetimes/', params=make_date_filters(10, 0, 10, 30))
assert len(resp.json['data']) == 3
def test_virtual_agendas_time_change(app, freezer):
agenda = Agenda.objects.create(
label='Foo bar Meeting', kind='meetings', minimal_booking_delay=10, maximal_booking_delay=20
)
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=15)
desk, _ = Desk.objects.get_or_create(agenda=agenda, label='Desk 1')
TimePeriodException.objects.create(
desk=desk,
start_datetime=make_aware(datetime.datetime(2022, 4, 4, 8, 0)),
end_datetime=make_aware(datetime.datetime(2022, 4, 4, 12, 0)),
)
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(8, 30),
end_time=datetime.time(12, 0),
desk=desk,
)
dt = datetime.datetime.strptime('2022-03-28-08:30', '%Y-%m-%d-%H:%M')
ev = Event.objects.create(
agenda=agenda,
meeting_type=meeting_type,
places=1,
start_datetime=make_aware(dt),
desk=desk,
)
Booking.objects.create(event=ev)
freezer.move_to(make_aware(datetime.datetime(2022, 3, 8, 7, 0)))
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
for slot in resp.json['data']:
if slot['datetime'] == '2022-03-28 08:30:00':
assert False, 'slot should not appear due to maximal_booking_delay'
# now got through virtual agenda
virtual_agenda = Agenda.objects.create(label='Foo bar Meeting', kind='virtual')
virtual_agenda.real_agendas.add(agenda)
assert virtual_agenda.minimal_booking_delay is None
assert virtual_agenda.maximal_booking_delay is None
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, meeting_type.slug)
resp = app.get(api_url)
for slot in resp.json['data']:
if slot['datetime'] == '2022-03-28 08:30:00':
assert (
False
), 'slot should not appear due to maximal_booking_delay of the real agenda (and no maximal_booking_delay) is defined on the real agenda'
@pytest.mark.freeze_time('2022-01-20 14:00') # Thursday
def test_datetimes_api_meetings_agenda_weekday_indexes(app):
agenda = Agenda.objects.create(
label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60
)
meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30)
desk = Desk.objects.create(agenda=agenda, label='desk')
time_period = TimePeriod.objects.create(
weekday=3, # Thursday
start_time=datetime.time(11, 0),
end_time=datetime.time(12, 0),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 16
assert [x['datetime'] for x in resp.json['data']][:6] == [
'2022-01-27 11:00:00',
'2022-01-27 11:30:00',
'2022-02-03 11:00:00',
'2022-02-03 11:30:00',
'2022-02-10 11:00:00',
'2022-02-10 11:30:00',
]
every_weeks_resp = resp
time_period.weekday_indexes = [1]
time_period.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert [x['datetime'] for x in resp.json['data']] == [
'2022-02-03 11:00:00',
'2022-02-03 11:30:00',
'2022-03-03 11:00:00',
'2022-03-03 11:30:00',
]
time_period.weekday_indexes = [1, 3]
time_period.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 8
assert [x['datetime'] for x in resp.json['data']] == [
'2022-02-03 11:00:00',
'2022-02-03 11:30:00',
'2022-02-17 11:00:00',
'2022-02-17 11:30:00',
'2022-03-03 11:00:00',
'2022-03-03 11:30:00',
'2022-03-17 11:00:00',
'2022-03-17 11:30:00',
]
time_period.weekday_indexes = [1, 2, 3, 4, 5]
time_period.save()
resp = app.get(api_url)
assert resp.json == every_weeks_resp.json
# there are five Mondays this month
time_period.weekday = 0
time_period.weekday_indexes = [5]
time_period.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 2
assert [x['datetime'] for x in resp.json['data']] == ['2022-01-31 11:00:00', '2022-01-31 11:30:00']
@pytest.mark.freeze_time('2022-01-20 14:00') # Thursday
def test_datetimes_api_meetings_virtual_agenda_weekday_indexes(app):
agenda = Agenda.objects.create(
label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60
)
desk = Desk.objects.create(agenda=agenda, label='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30)
virtual_agenda = Agenda.objects.create(label='Foo bar Meeting', kind='virtual')
virtual_agenda.real_agendas.add(agenda)
TimePeriod.objects.create(
weekday=0,
weekday_indexes=[1, 2],
start_time=datetime.time(11, 0),
end_time=datetime.time(12, 30),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 12
assert [x['datetime'] for x in resp.json['data']] == [
'2022-02-07 11:00:00',
'2022-02-07 11:30:00',
'2022-02-07 12:00:00',
'2022-02-14 11:00:00',
'2022-02-14 11:30:00',
'2022-02-14 12:00:00',
'2022-03-07 11:00:00',
'2022-03-07 11:30:00',
'2022-03-07 12:00:00',
'2022-03-14 11:00:00',
'2022-03-14 11:30:00',
'2022-03-14 12:00:00',
]
# add exclusion period on virtual agenda
exclusion_period = TimePeriod.objects.create(
weekday=0, start_time=datetime.time(11, 30), end_time=datetime.time(12, 30), agenda=virtual_agenda
)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert [x['datetime'] for x in resp.json['data']] == [
'2022-02-07 11:00:00',
'2022-02-14 11:00:00',
'2022-03-07 11:00:00',
'2022-03-14 11:00:00',
]
exclusion_period.start_time = datetime.time(10, 30)
exclusion_period.end_time = datetime.time(11, 30)
exclusion_period.save()
resp = app.get(api_url)
assert len(resp.json['data']) == 8
assert [x['datetime'] for x in resp.json['data']] == [
'2022-02-07 11:30:00',
'2022-02-07 12:00:00',
'2022-02-14 11:30:00',
'2022-02-14 12:00:00',
'2022-03-07 11:30:00',
'2022-03-07 12:00:00',
'2022-03-14 11:30:00',
'2022-03-14 12:00:00',
]
# add second exclusion period on virtual agenda
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(12, 00), end_time=datetime.time(12, 30), agenda=virtual_agenda
)
resp = app.get(api_url)
assert [x['datetime'] for x in resp.json['data']] == [
'2022-02-07 11:30:00',
'2022-02-14 11:30:00',
'2022-03-07 11:30:00',
'2022-03-14 11:30:00',
]
@pytest.mark.freeze_time('2022-10-24 10:00')
def test_datetimes_api_meetings_agenda_date_time_period(app):
agenda = Agenda.objects.create(
label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=8
)
meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30)
desk = Desk.objects.create(agenda=agenda, label='desk')
TimePeriod.objects.create(
date=datetime.date(2022, 10, 24),
start_time=datetime.time(12, 0),
end_time=datetime.time(14, 0),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert [x['datetime'] for x in resp.json['data']] == [
'2022-10-24 12:00:00',
'2022-10-24 12:30:00',
'2022-10-24 13:00:00',
'2022-10-24 13:30:00',
]
resp = app.get(api_url, params={'date_start': '2022-10-25'})
assert resp.json['data'] == []
# mix with repeating period
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(13, 0),
end_time=datetime.time(15, 0),
desk=desk,
)
resp = app.get(api_url)
assert [x['datetime'] for x in resp.json['data']] == [
'2022-10-24 12:00:00',
'2022-10-24 12:30:00',
'2022-10-24 13:00:00',
'2022-10-24 13:30:00',
'2022-10-24 14:00:00',
'2022-10-24 14:30:00',
'2022-10-31 13:00:00',
'2022-10-31 13:30:00',
'2022-10-31 14:00:00',
'2022-10-31 14:30:00',
]
@pytest.mark.freeze_time('2022-10-24 10:00')
def test_datetimes_api_meetings_virtual_agenda_date_time_period(app):
agenda = Agenda.objects.create(
label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=30
)
desk = Desk.objects.create(agenda=agenda, label='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30)
virtual_agenda = Agenda.objects.create(label='Foo bar Meeting', kind='virtual')
virtual_agenda.real_agendas.add(agenda)
TimePeriod.objects.create(
date=datetime.date(2022, 10, 24),
start_time=datetime.time(12, 0),
end_time=datetime.time(14, 0),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert [x['datetime'] for x in resp.json['data']] == [
'2022-10-24 12:00:00',
'2022-10-24 12:30:00',
'2022-10-24 13:00:00',
'2022-10-24 13:30:00',
]
# add exclusion period on virtual agenda
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(12, 00), end_time=datetime.time(13, 00), agenda=virtual_agenda
)
resp = app.get(api_url)
assert [x['datetime'] for x in resp.json['data']] == [
'2022-10-24 13:00:00',
'2022-10-24 13:30:00',
]
# add second exclusion period on virtual agenda
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(13, 30), end_time=datetime.time(14, 00), agenda=virtual_agenda
)
resp = app.get(api_url)
assert [x['datetime'] for x in resp.json['data']] == [
'2022-10-24 13:00:00',
]
@pytest.mark.freeze_time('2023-03-01 01:00')
def test_datetimes_api_meetings_agenda_date_time_period_dst_change(app):
agenda = Agenda.objects.create(
label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60
)
meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30)
desk = Desk.objects.create(agenda=agenda, label='desk')
# DST change happens on 26/03
TimePeriod.objects.create(
date=datetime.date(2023, 3, 21),
start_time=datetime.time(10, 0),
end_time=datetime.time(10, 30),
desk=desk,
)
TimePeriod.objects.create(
date=datetime.date(2023, 3, 28),
start_time=datetime.time(10, 0),
end_time=datetime.time(10, 30),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert [x['datetime'] for x in resp.json['data']] == [
'2023-03-21 10:00:00',
'2023-03-28 10:00:00',
]
@pytest.mark.freeze_time('2023-03-09 08:00')
def test_datetimes_end_datetime(app):
agenda = Agenda.objects.create(
label='Foo bar', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=60
)
meeting_type = MeetingType.objects.create(agenda=agenda, label='Plop', duration=30)
desk = Desk.objects.create(agenda=agenda, label='desk')
# DST change happens on 26/03
TimePeriod.objects.create(
date=datetime.date(2023, 3, 10),
start_time=datetime.time(10, 0),
end_time=datetime.time(10, 30),
desk=desk,
)
TimePeriod.objects.create(
date=datetime.date(2023, 3, 10),
start_time=datetime.time(10, 30),
end_time=datetime.time(11, 0),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert [(x['datetime'], x['end_datetime']) for x in resp.json['data']] == [
('2023-03-10 10:00:00', '2023-03-10 10:30:00'),
('2023-03-10 10:30:00', '2023-03-10 11:00:00'),
]
def test_datetimes_api_meetings_agenda_filter_minutes(app):
agenda = Agenda(
label='Foo bar Meeting', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=2
)
agenda.save()
meeting_type = MeetingType(agenda=agenda, label='Blah', duration=15)
meeting_type.save()
tomorrow_weekday = (localtime(now()).weekday() + 1) % 7
default_desk, _ = Desk.objects.get_or_create(agenda=agenda, label='Desk 1')
time_period = TimePeriod(
weekday=tomorrow_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=default_desk,
)
time_period.save()
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert len(resp.json['data']) == 8
assert datetime_from_str(resp.json['data'][0]['datetime']).minute == 0
assert datetime_from_str(resp.json['data'][1]['datetime']).minute == 15
assert datetime_from_str(resp.json['data'][2]['datetime']).minute == 30
assert datetime_from_str(resp.json['data'][3]['datetime']).minute == 45
assert datetime_from_str(resp.json['data'][4]['datetime']).minute == 0
assert datetime_from_str(resp.json['data'][5]['datetime']).minute == 15
assert datetime_from_str(resp.json['data'][6]['datetime']).minute == 30
assert datetime_from_str(resp.json['data'][7]['datetime']).minute == 45
# filter on minutes
api_url = '/api/agenda/%s/meetings/%s/datetimes/?minutes=0' % (
meeting_type.agenda.slug,
meeting_type.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 2
assert datetime_from_str(resp.json['data'][0]['datetime']).minute == 0
assert datetime_from_str(resp.json['data'][1]['datetime']).minute == 0
# filter on minutes, with more choices
api_url = '/api/agenda/%s/meetings/%s/datetimes/?minutes=0,30' % (
meeting_type.agenda.slug,
meeting_type.slug,
)
resp = app.get(api_url)
assert len(resp.json['data']) == 4
assert datetime_from_str(resp.json['data'][0]['datetime']).minute == 0
assert datetime_from_str(resp.json['data'][1]['datetime']).minute == 30
assert datetime_from_str(resp.json['data'][2]['datetime']).minute == 0
assert datetime_from_str(resp.json['data'][3]['datetime']).minute == 30
@pytest.mark.freeze_time('2023-04-03')
def test_datetimes_api_meetings_min_booking_datetime_with_minimal_booking_time(app):
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=3
)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo', duration=30)
for weekday in [0, 1, 2]: # monday, tuesday, wednesday
TimePeriod.objects.create(
weekday=weekday,
start_time=datetime.time(9, 0),
end_time=datetime.time(10, 00),
desk=desk,
)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert resp.json['data'][0]['datetime'] == '2023-04-03 09:00:00'
# set a minimal minimal_booking_time and check that it has no impact
agenda.minimal_booking_time = datetime.time(10, 0, 0)
agenda.save()
resp = app.get(api_url)
assert resp.json['data'][0]['datetime'] == '2023-04-03 09:00:00'
# set a minimal minimal_booking_time to None check that it has no impact
agenda.minimal_booking_time = None
agenda.save()
resp = app.get(api_url)
assert resp.json['data'][0]['datetime'] == '2023-04-03 09:00:00'
def test_datetimes_api_meetings_max_booking_datetime_with_minimal_booking_time(app, freezer):
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=3
)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo', duration=30)
for weekday in [0, 1, 2, 3, 4, 5]:
TimePeriod.objects.create(
weekday=weekday,
start_time=datetime.time(9, 0),
end_time=datetime.time(10, 00),
desk=desk,
)
# last slots visible are the one on J + maximal_booking_delay (3) -1
freezer.move_to('2023-04-03T00:00:00+02:00') # 2023-04-03 is a monday
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00'
# move to noon, no changes
freezer.move_to('2023-04-03T12:00:00+02:00')
resp = app.get(api_url)
assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00'
# set a minimal minimal_booking_time earlier than current time, no changes
agenda.minimal_booking_time = datetime.time(10, 0, 0)
agenda.save()
resp = app.get(api_url)
assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00'
# set a minimal minimal_booking_time later than current time, slots of 2023-04-05 disappear
agenda.minimal_booking_time = datetime.time(14, 0, 0)
agenda.save()
resp = app.get(api_url)
assert resp.json['data'][-2]['datetime'] == '2023-04-04 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-04 09:30:00'
# move to a time superior to minimal_booking_time (14:00), slots of 2023-04-05 re-appear
freezer.move_to('2023-04-03T15:00:00+02:00')
resp = app.get(api_url)
assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00'
# move to the day after, prior to minimal_booking_time (14:00), no changes
freezer.move_to('2023-04-04T12:00:00+02:00')
resp = app.get(api_url)
assert resp.json['data'][-2]['datetime'] == '2023-04-05 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-05 09:30:00'
# move to the day after, after minimal_booking_time (14:00), new slots available
freezer.move_to('2023-04-04T15:00:00+02:00')
resp = app.get(api_url)
assert resp.json['data'][-2]['datetime'] == '2023-04-06 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-06 09:30:00'
def test_datetimes_api_meetings_max_booking_datetime_with_minimal_booking_time_to_none(app, freezer):
agenda = Agenda.objects.create(
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=3
)
desk = Desk.objects.create(agenda=agenda, slug='desk')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo', duration=30)
for weekday in [0, 1, 2, 3, 4, 5]:
TimePeriod.objects.create(
weekday=weekday,
start_time=datetime.time(11, 00),
end_time=datetime.time(14, 00),
desk=desk,
)
# last slots visible are the one on J + maximal_booking_delay (3) -1
freezer.move_to('2023-04-03T00:00:00+02:00') # 2023-04-03 is a monday
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
resp = app.get(api_url)
assert resp.json['data'][-6]['datetime'] == '2023-04-05 11:00:00'
assert resp.json['data'][-5]['datetime'] == '2023-04-05 11:30:00'
assert resp.json['data'][-4]['datetime'] == '2023-04-05 12:00:00'
assert resp.json['data'][-3]['datetime'] == '2023-04-05 12:30:00'
assert resp.json['data'][-2]['datetime'] == '2023-04-05 13:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-05 13:30:00'
# set a minimal minimal_booking_time to None, 2023-04-05 disappear
# because current time is 00:00 and slots starts later
agenda.minimal_booking_time = None
agenda.save()
resp = app.get(api_url)
assert resp.json['data'][-1]['datetime'] == '2023-04-04 13:30:00'
# move a few hours later, juste after the first slot time of a day
# a new slot becomes available
freezer.move_to('2023-04-03T11:01:00+02:00')
resp = app.get(api_url)
assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:00:00'
# move juste after the second slot time, one more slot availalbe
freezer.move_to('2023-04-03T11:31:00+02:00')
resp = app.get(api_url)
assert resp.json['data'][-2]['datetime'] == '2023-04-05 11:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-05 11:30:00'
# at 15:00, every slots are available
freezer.move_to('2023-04-03T15:00:00+02:00')
resp = app.get(api_url)
assert resp.json['data'][-6]['datetime'] == '2023-04-05 11:00:00'
assert resp.json['data'][-5]['datetime'] == '2023-04-05 11:30:00'
assert resp.json['data'][-4]['datetime'] == '2023-04-05 12:00:00'
assert resp.json['data'][-3]['datetime'] == '2023-04-05 12:30:00'
assert resp.json['data'][-2]['datetime'] == '2023-04-05 13:00:00'
assert resp.json['data'][-1]['datetime'] == '2023-04-05 13:30:00'