chrono/tests/api/fillslot/test_all.py

2029 lines
84 KiB
Python

import datetime
import random
import urllib.parse as urlparse
from unittest import mock
import pytest
from django.db import IntegrityError, connection
from django.test.utils import CaptureQueriesContext
from chrono.agendas.models import (
Agenda,
Booking,
BookingColor,
Desk,
Event,
MeetingType,
Resource,
TimePeriod,
VirtualMember,
)
from chrono.utils.timezone import localtime, now
pytestmark = pytest.mark.django_db
def test_booking_api(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=10, agenda=agenda
)
# unauthenticated
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id), status=401)
for agenda_key in (agenda.slug, agenda.id): # acces datetimes via agenda slug or id (legacy)
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda_key)
event_fillslot_url = [x for x in resp_datetimes.json['data'] if x['id'] == event.slug][0]['api'][
'fillslot_url'
]
assert urlparse.urlparse(event_fillslot_url).path == '/api/agenda/%s/fillslot/%s/' % (
agenda.slug,
event.slug,
)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id))
Booking.objects.get(id=resp.json['booking_id'])
assert resp.json['datetime'] == localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
assert 'booking_url' in resp.json['api']
assert 'accept_url' in resp.json['api']
assert 'suspend_url' in resp.json['api']
assert 'cancel_url' in resp.json['api']
assert 'ics_url' in resp.json['api']
assert 'anonymize_url' in resp.json['api']
assert urlparse.urlparse(resp.json['api']['booking_url']).netloc
assert urlparse.urlparse(resp.json['api']['accept_url']).netloc
assert urlparse.urlparse(resp.json['api']['suspend_url']).netloc
assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
assert urlparse.urlparse(resp.json['api']['ics_url']).netloc
assert urlparse.urlparse(resp.json['api']['anonymize_url']).netloc
assert Booking.objects.count() == 1
# access by slug
event.slug = 'bar'
event.save()
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.slug))
assert Booking.objects.count() == 2
assert Booking.objects.filter(event__agenda=agenda).count() == 2
# test with additional data
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={
'label': 'foo',
'user_name': 'bar',
'backoffice_url': 'http://example.net/',
'cancel_callback_url': 'http://example.net/jump/trigger/',
'user_email': 'bar@bar.com',
'user_phone_number': '+33 (0) 6 12 34 56 78', # long phone number
'form_url': 'http://example.net/',
'extra_emails': ['baz@baz.com', 'hop@hop.com'],
'extra_phone_numbers': ['+33123456789', '+33123456789'],
'presence_callback_url': 'http://example.net/jump/trigger2/',
'absence_callback_url': 'http://example.net/jump/trigger3/',
},
)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert booking.label == 'foo'
assert booking.user_first_name == ''
assert booking.user_last_name == 'bar'
assert booking.backoffice_url == 'http://example.net/'
assert booking.cancel_callback_url == 'http://example.net/jump/trigger/'
assert booking.presence_callback_url == 'http://example.net/jump/trigger2/'
assert booking.absence_callback_url == 'http://example.net/jump/trigger3/'
assert booking.user_email == 'bar@bar.com'
assert booking.user_phone_number == '+33 (0) 6 12 34 56 78'
assert booking.form_url == 'http://example.net/'
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={
'label': 'foo',
'user_first_name': 'foo',
'user_last_name': 'bar',
'backoffice_url': 'http://example.net/',
'cancel_callback_url': 'http://example.net/jump/trigger/',
'user_email': 'bar@bar.com',
'user_phone_number': '+33123456789',
'form_url': 'http://example.net/',
},
)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert booking.label == 'foo'
assert booking.user_first_name == 'foo'
assert booking.user_last_name == 'bar'
assert booking.backoffice_url == 'http://example.net/'
assert booking.cancel_callback_url == 'http://example.net/jump/trigger/'
assert booking.user_email == 'bar@bar.com'
assert booking.user_phone_number == '+33123456789'
assert booking.form_url == 'http://example.net/'
# blank data are OK
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'label': '', 'user_first_name': '', 'user_last_name': '', 'backoffice_url': ''},
)
assert Booking.objects.get(id=resp.json['booking_id']).label == ''
assert Booking.objects.get(id=resp.json['booking_id']).user_first_name == ''
assert Booking.objects.get(id=resp.json['booking_id']).user_last_name == ''
assert Booking.objects.get(id=resp.json['booking_id']).backoffice_url == ''
# extra data stored in extra_data field
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'label': 'l', 'user_last_name': 'u', 'backoffice_url': '', 'foo': 'bar'},
)
assert Booking.objects.get(id=resp.json['booking_id']).label == 'l'
assert Booking.objects.get(id=resp.json['booking_id']).user_last_name == 'u'
assert Booking.objects.get(id=resp.json['booking_id']).backoffice_url == ''
assert Booking.objects.get(id=resp.json['booking_id']).extra_data == {'foo': 'bar'}
# anonymize
booking_id = resp.json['booking_id']
resp = app.post(resp.json['api']['anonymize_url'])
assert Booking.objects.get(id=booking_id).anonymization_datetime is not None
# test invalid data are refused
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'user_last_name': {'foo': 'bar'}},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid payload' # legacy
assert resp.json['err_class'] == 'invalid payload'
assert resp.json['err_desc'] == 'invalid payload'
assert len(resp.json['errors']) == 1
assert 'user_last_name' in resp.json['errors']
# extra_data list/dict values are refused
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'foo': ['bar', 'baz']},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'foo': {'bar': 'baz'}},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'wrong type for extra_data foo value'
# test parameters wrongly passed in query are refused
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/?backoffice_url=https://example.com&label=test' % (agenda.id, event.id),
status=400,
)
assert resp.json['err'] == 1
assert (
resp.json['err_class']
== 'parameters "backoffice_url, label" must be included in request body, not query'
)
assert (
resp.json['err_desc']
== 'parameters "backoffice_url, label" must be included in request body, not query'
)
resp = app.post('/api/agenda/foobar/fillslot/%s/' % event.id, status=404)
resp = app.post('/api/agenda/0/fillslot/%s/' % event.id, status=404)
def test_booking_api_event_exclusion_constraint(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='meetings')
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
desk = Desk.objects.create(agenda=agenda, slug='desk')
tomorrow = now() + datetime.timedelta(days=1)
TimePeriod.objects.create(
weekday=tomorrow.date().weekday(),
start_time=datetime.time(9, 0),
end_time=datetime.time(17, 00),
desk=desk,
)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
event_id = resp.json['data'][2]['id']
app.authorization = ('Basic', ('john.doe', 'password'))
original_save = Event.save
global first_call # pylint: disable=global-variable-undefined
first_call = True
def wrapped(*args, **kwargs):
global first_call # pylint: disable=global-variable-undefined
if first_call is True:
first_call = False
raise IntegrityError('tstzrange_constraint')
return original_save(*args, **kwargs)
with mock.patch('chrono.agendas.models.Event.save', autospec=True) as mock_create:
mock_create.side_effect = wrapped
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_id),
)
assert len(mock_create.call_args_list) == 2
assert resp.json['err'] == 0
assert Event.objects.count() == 1
assert Booking.objects.count() == 1
def test_booking_api_extra_emails(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=10, agenda=agenda
)
app.authorization = ('Basic', ('john.doe', 'password'))
fillslot_url = '/api/agenda/%s/fillslot/%s/' % (agenda.id, event.slug)
resp = app.post_json(
fillslot_url,
params={
'extra_emails': ['foo@foo.com', 'bar@bar.com'],
'extra_phone_numbers': ['+33123456789', '+33123456790'],
},
)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert booking.extra_emails == ['foo@foo.com', 'bar@bar.com']
assert booking.extra_phone_numbers == ['+33123456789', '+33123456790']
resp = app.post_json(
fillslot_url,
params={
'extra_emails': 'foo@foo.com, bar@bar.com',
'extra_phone_numbers': '+33123456789,+33123456790',
},
)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert booking.extra_emails == ['foo@foo.com', 'bar@bar.com']
assert booking.extra_phone_numbers == ['+33123456789', '+33123456790']
resp = app.post_json(
fillslot_url,
params={
'extra_emails': 'foo@foo.com',
'extra_phone_numbers': '+33123456789',
},
)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert booking.extra_emails == ['foo@foo.com']
assert booking.extra_phone_numbers == ['+33123456789']
resp = app.post_json(
fillslot_url,
params={
'extra_emails': ', bar@bar.com',
'extra_phone_numbers': '',
},
)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert booking.extra_emails == ['bar@bar.com']
assert booking.extra_phone_numbers == []
resp = app.post(fillslot_url)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert booking.extra_emails == []
assert booking.extra_phone_numbers == []
resp = app.post_json(
fillslot_url,
params={
'extra_emails': 'bar.com',
'extra_phone_numbers': 'too loooooooooong',
},
status=400,
)
assert resp.json['errors']['extra_emails']['0'] == ['Enter a valid email address.']
assert resp.json['errors']['extra_phone_numbers']['0'] == [
'Ensure this field has no more than 16 characters.'
]
def test_booking_api_check_delays(app, user):
agenda = Agenda.objects.create(
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=7
)
event = Event.objects.create(
slug='event-slug',
start_datetime=localtime() + datetime.timedelta(days=5),
places=5,
agenda=agenda,
)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 0
booking = Booking.objects.latest('pk')
assert booking.out_of_min_delay is False
# test minimal_booking_delay
agenda.minimal_booking_delay = 6
agenda.save()
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'event not bookable'
agenda.save()
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug), params={'bypass_delays': True})
assert resp.json['err'] == 0
booking = Booking.objects.latest('pk')
assert booking.out_of_min_delay is True
# test maximal_booking_delay
agenda.minimal_booking_delay = 0
agenda.maximal_booking_delay = 3
agenda.save()
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'event not bookable'
agenda.save()
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug), params={'bypass_delays': True})
assert resp.json['err'] == 0
booking = Booking.objects.latest('pk')
assert booking.out_of_min_delay is False
@pytest.mark.freeze_time('2021-02-23')
def test_booking_api_exclude_slots(app, user):
agenda = Agenda.objects.create(
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=7
)
event = Event.objects.create(
slug='event-slug',
start_datetime=localtime().replace(hour=10, minute=0),
places=5,
agenda=agenda,
)
Booking.objects.create(event=event, user_external_id='42')
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug), params={'user_external_id': '42'}
)
assert resp.json['err'] == 0
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug),
params={'user_external_id': '42', 'exclude_user': True},
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'event is already booked by user'
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug), params={'exclude_user': True})
assert resp.json['err'] == 0 # ok, no user_external_id
Booking.objects.update(cancellation_datetime=now())
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug),
params={'user_external_id': '42', 'exclude_user': True},
)
assert resp.json['err'] == 0 # ok, existing bookings are cancelled
event.delete()
# recurrent event
start_datetime = localtime().replace(hour=12, minute=0)
event = Event.objects.create(
slug='recurrent',
start_datetime=start_datetime,
recurrence_days=[start_datetime.isoweekday()],
recurrence_end_date=start_datetime + datetime.timedelta(days=15),
places=3,
agenda=agenda,
)
event.create_all_recurrences()
first_recurrence = Event.objects.get(primary_event=event, start_datetime=event.start_datetime)
Booking.objects.create(event=first_recurrence, user_external_id='42')
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, first_recurrence.slug),
params={'user_external_id': '42'},
)
assert resp.json['err'] == 0
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, first_recurrence.slug),
params={'user_external_id': '42', 'exclude_user': True},
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'event is already booked by user'
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, first_recurrence.slug), params={'exclude_user': True}
)
assert resp.json['err'] == 0
@pytest.mark.freeze_time('2021-02-25')
def test_booking_api_meetings_agenda_exclude_slots(app, user):
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()
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')
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post(
'/api/agenda/%s/fillslot/%s:2021-02-26-0900/' % (agenda.slug, meeting_type.slug),
params={'user_external_id': '42'},
)
assert resp.json['err'] == 0
resp = app.post(
'/api/agenda/%s/fillslot/%s:2021-02-26-0900/' % (agenda.slug, meeting_type.slug),
params={'user_external_id': '42', 'exclude_user': True},
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'no more desk available'
resp = app.post(
'/api/agenda/%s/fillslot/%s:2021-02-26-0900/' % (agenda.slug, meeting_type.slug),
params={'exclude_user': True},
)
assert resp.json['err'] == 0
def test_booking_api_meeting(app, meetings_agenda, user):
agenda_id = meetings_agenda.slug
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
event_id = resp.json['data'][2]['id']
assert urlparse.urlparse(
resp.json['data'][2]['api']['fillslot_url']
).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id)
app.authorization = ('Basic', ('john.doe', 'password'))
# verify malformed event_pk returns a 400
resp_booking = app.post('/api/agenda/%s/fillslot/None/' % agenda_id, status=400)
assert resp_booking.json['err'] == 1
# make a booking
resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert Booking.objects.count() == 1
assert resp_booking.json['datetime'] == localtime(Booking.objects.all()[0].event.start_datetime).strftime(
'%Y-%m-%d %H:%M:%S'
)
assert resp_booking.json['end_datetime'] == localtime(
Booking.objects.all()[0].event.end_datetime
).strftime('%Y-%m-%d %H:%M:%S')
assert resp_booking.json['duration'] == 30
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len([x for x in resp2.json['data'] if not x.get('disabled')]) + 1
# try booking the same timeslot
resp2 = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert resp2.json['err'] == 1
assert resp2.json['reason'] == 'no more desk available' # legacy
assert resp2.json['err_class'] == 'no more desk available'
assert resp2.json['err_desc'] == 'no more desk available'
# try booking another timeslot
event_id = resp.json['data'][3]['id']
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert resp.json['err'] == 0
assert Booking.objects.count() == 2
def test_booking_api_meeting_colors(app, user):
agenda = Agenda.objects.create(
label='foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56
)
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
default_desk = Desk.objects.create(agenda=agenda, label='Desk 1')
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=default_desk
)
datetimes_resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
event_id = datetimes_resp.json['data'][2]['id']
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id),
params={
'use_color_for': 'Cooking',
},
)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert booking.color.label == 'Cooking'
assert booking.color.index == 0
event_id = datetimes_resp.json['data'][3]['id']
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id),
params={
'use_color_for': 'Cooking',
},
)
new_booking = Booking.objects.get(id=resp.json['booking_id'])
assert new_booking.color.index == 0
event_id = datetimes_resp.json['data'][4]['id']
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id),
params={
'use_color_for': 'Swimming',
},
)
new_booking = Booking.objects.get(id=resp.json['booking_id'])
assert new_booking.color.label == 'Swimming'
assert new_booking.color.index == 1
for i in range((BookingColor.COLOR_COUNT * 2) - 2):
event_id = datetimes_resp.json['data'][i]['id']
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id),
params={
'use_color_for': str(i),
},
)
assert BookingColor.objects.count() == BookingColor.COLOR_COUNT * 2
assert BookingColor.objects.distinct('index').order_by().count() == BookingColor.COLOR_COUNT
def test_booking_api_meeting_with_resources(app, user):
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
)
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')
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,
)
app.authorization = ('Basic', ('john.doe', 'password'))
# make a booking without resource
resp = app.post('/api/agenda/%s/fillslot/%s:%s-1000/' % (agenda.pk, meeting_type.pk, tomorrow_str))
assert resp.json['datetime'] == '%s 10:00:00' % tomorrow_str
assert resp.json['end_datetime'] == '%s 10:30:00' % tomorrow_str
assert resp.json['duration'] == 30
assert resp.json['resources'] == []
booking = Booking.objects.latest('pk')
assert list(booking.event.resources.all()) == []
# now try to book also a resource - slot not free
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-1000/?resources=%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug)
)
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'
# slot is free
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug)
)
assert resp.json['datetime'] == '%s 09:00:00' % tomorrow_str
assert resp.json['end_datetime'] == '%s 09:30:00' % tomorrow_str
assert resp.json['duration'] == 30
assert resp.json['resources'] == [resource1.slug]
booking = Booking.objects.latest('pk')
assert list(booking.event.resources.all()) == [resource1]
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0930/?resources=%s,%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug, resource2.slug)
)
assert resp.json['datetime'] == '%s 09:30:00' % tomorrow_str
assert resp.json['end_datetime'] == '%s 10:00:00' % tomorrow_str
assert resp.json['duration'] == 30
assert resp.json['resources'] == [resource1.slug, resource2.slug]
booking = Booking.objects.latest('pk')
assert list(booking.event.resources.all()) == [resource1, resource2]
# resource is unknown or not valid for this agenda
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=foobarblah'
% (agenda.pk, meeting_type.pk, tomorrow_str),
status=400,
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid slugs: foobarblah' # legacy
assert resp.json['err_class'] == 'invalid slugs: foobarblah'
assert resp.json['err_desc'] == 'invalid slugs: foobarblah'
agenda.resources.remove(resource3)
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource3.slug),
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'
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s,foobarblah'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource3.slug),
status=400,
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid slugs: foobarblah, resource-3' # legacy
assert resp.json['err_class'] == 'invalid slugs: foobarblah, resource-3'
assert resp.json['err_desc'] == 'invalid slugs: foobarblah, resource-3'
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s,foobarblah'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug),
status=400,
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid slugs: foobarblah' # legacy
assert resp.json['err_class'] == 'invalid slugs: foobarblah'
assert resp.json['err_desc'] == 'invalid slugs: foobarblah'
# booking is canceled: slot is free
booking.cancel()
resp = app.post(
'/api/agenda/%s/fillslot/%s:%s-0930/?resources=%s,%s'
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug, resource2.slug)
)
assert resp.json['datetime'] == '%s 09:30:00' % tomorrow_str
assert resp.json['end_datetime'] == '%s 10:00:00' % tomorrow_str
assert resp.json['duration'] == 30
assert resp.json['resources'] == [resource1.slug, resource2.slug]
booking = Booking.objects.latest('pk')
assert list(booking.event.resources.all()) == [resource1, resource2]
def test_booking_api_meeting_across_daylight_saving_time(app, meetings_agenda, user):
meetings_agenda.maximal_booking_delay = 365
meetings_agenda.save()
agenda_id = meetings_agenda.slug
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
event_index = 26 * 18
event_id = resp.json['data'][event_index]['id']
assert event_id[-4:] == resp.json['data'][2 * 18]['id'][-4:]
assert urlparse.urlparse(
resp.json['data'][event_index]['api']['fillslot_url']
).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id)
app.authorization = ('Basic', ('john.doe', 'password'))
resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert Booking.objects.count() == 1
assert resp_booking.json['datetime'] == localtime(Booking.objects.all()[0].event.start_datetime).strftime(
'%Y-%m-%d %H:%M:%S'
)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert resp.json['data'][event_index]['disabled']
@pytest.mark.freeze_time('2022-01-20 14:00') # Thursday
def test_booking_api_meeting_weekday_indexes(app, user):
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
weekday_indexes=[1, 3],
start_time=datetime.time(11, 0),
end_time=datetime.time(12, 0),
desk=desk,
)
datetimes_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
slot = datetimes_resp.json['data'][0]['id']
assert slot == 'plop:2022-02-03-1100'
app.authorization = ('Basic', ('john.doe', 'password'))
# single booking
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
assert Booking.objects.count() == 1
assert resp.json['duration'] == 30
# try to book slot on a skipped week
slot = datetimes_resp.json['data'][3]['id']
time_period.weekday_indexes = [1]
time_period.save()
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
assert slot not in {slot['id'] for slot in resp.json['data']}
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'no more desk available'
def test_booking_api_with_data(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=10, agenda=agenda
)
app.authorization = ('Basic', ('john.doe', 'password'))
app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id), params={'hello': 'world'})
assert Booking.objects.count() == 1
assert Booking.objects.all()[0].extra_data == {'hello': 'world'}
@pytest.mark.freeze_time('2022-10-24 10:00')
def test_booking_api_meeting_date_time_period(app, user):
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,
)
datetimes_resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug))
slot = datetimes_resp.json['data'][0]['id']
assert slot == 'plop:2022-10-24-1200'
app.authorization = ('Basic', ('john.doe', 'password'))
# single booking
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
assert Booking.objects.count() == 1
assert resp.json['duration'] == 30
# book another two slots
slots = [datetimes_resp.json['data'][1]['id'], datetimes_resp.json['data'][2]['id']]
for i, slot in enumerate(slots, 2):
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
assert Booking.objects.count() == i
assert resp.json['duration'] == 30
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, slot))
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'no more desk available'
def test_booking_api_available(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
events = []
for i in range(0, 10):
events.append(
Event.objects.create(
slug='event-slug%i' % i,
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=i),
places=20,
agenda=agenda,
)
)
event = random.choice(events)
app.authorization = ('Basic', ('john.doe', 'password'))
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert len(ctx.captured_queries) == 4
event_data = [d for d in resp.json['data'] if d['id'] == event.slug][0]
assert event_data['places']['total'] == 20
assert event_data['places']['available'] == 20
assert event_data['places']['reserved'] == 0
assert event_data['places']['full'] is False
assert 'waiting_list_total' not in event_data['places']
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk))
assert resp.json['err'] == 0
assert resp.json['places']['total'] == 20
assert resp.json['places']['available'] == 19
assert resp.json['places']['reserved'] == 1
assert resp.json['places']['full'] is False
assert 'waiting_list_total' not in resp.json['places']
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
event_data = [d for d in resp.json['data'] if d['id'] == event.slug][0]
assert event_data['places']['total'] == 20
assert event_data['places']['available'] == 19
assert event_data['places']['reserved'] == 1
assert event_data['places']['full'] is False
assert 'waiting_list_total' not in event_data['places']
Booking.objects.create(event=event, in_waiting_list=True)
event.waiting_list_places = 5
event.save()
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk))
assert resp.json['err'] == 0
assert resp.json['places']['total'] == 20
assert resp.json['places']['available'] == 19
assert resp.json['places']['reserved'] == 1
assert resp.json['places']['full'] is False
assert resp.json['places']['waiting_list_total'] == 5
assert resp.json['places']['waiting_list_available'] == 3
assert resp.json['places']['waiting_list_reserved'] == 2
assert resp.json['places']['waiting_list_activated'] is True
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
event_data = [d for d in resp.json['data'] if d['id'] == event.slug][0]
assert event_data['places']['total'] == 20
assert event_data['places']['available'] == 19
assert event_data['places']['reserved'] == 1
assert event_data['places']['full'] is False
assert event_data['places']['waiting_list_total'] == 5
assert event_data['places']['waiting_list_available'] == 3
assert event_data['places']['waiting_list_reserved'] == 2
assert event_data['places']['waiting_list_activated'] is True
# not for mettings agenda
meetings_agenda = Agenda.objects.create(label='meetings', kind='meetings')
desk = Desk.objects.create(label='foo', agenda=meetings_agenda)
meeting_type = MeetingType.objects.create(agenda=meetings_agenda, label='Blah', duration=30)
TimePeriod.objects.create(
weekday=3, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), desk=desk
)
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug))
assert 'places' not in resp.json['data'][0]
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.pk, resp.json['data'][0]['id']))
assert resp.json['err'] == 0
assert 'places' not in resp.json
def test_booking_api_force_waiting_list(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=20, agenda=agenda
)
app.authorization = ('Basic', ('john.doe', 'password'))
# no waiting list
assert event.waiting_list_places == 0
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk), params={'force_waiting_list': True}
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'no waiting list' # legacy
assert resp.json['err_class'] == 'no waiting list'
assert resp.json['err_desc'] == 'no waiting list'
event.waiting_list_places = 2
event.save()
# add a booking
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk))
assert resp.json['err'] == 0
assert resp.json['places']['total'] == 20
assert resp.json['places']['available'] == 19
assert resp.json['places']['reserved'] == 1
assert resp.json['places']['full'] is False
assert resp.json['places']['waiting_list_total'] == 2
assert resp.json['places']['waiting_list_available'] == 2
assert resp.json['places']['waiting_list_reserved'] == 0
assert resp.json['places']['waiting_list_activated'] is False
# add another booking
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk), params={'force_waiting_list': False}
)
assert resp.json['err'] == 0
assert resp.json['places']['total'] == 20
assert resp.json['places']['available'] == 18
assert resp.json['places']['reserved'] == 2
assert resp.json['places']['full'] is False
assert resp.json['places']['waiting_list_total'] == 2
assert resp.json['places']['waiting_list_available'] == 2
assert resp.json['places']['waiting_list_reserved'] == 0
assert resp.json['places']['waiting_list_activated'] is False
# add a booking, but in waiting list
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk), params={'force_waiting_list': True}
)
assert resp.json['err'] == 0
assert resp.json['places']['total'] == 20
assert resp.json['places']['available'] == 18
assert resp.json['places']['reserved'] == 2
assert resp.json['places']['full'] is False
assert resp.json['places']['waiting_list_total'] == 2
assert resp.json['places']['waiting_list_available'] == 1
assert resp.json['places']['waiting_list_reserved'] == 1
assert resp.json['places']['waiting_list_activated'] is True
# add a booking => booked in waiting list
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk))
assert resp.json['err'] == 0
assert resp.json['places']['total'] == 20
assert resp.json['places']['available'] == 18
assert resp.json['places']['reserved'] == 2
assert resp.json['places']['full'] is True
assert resp.json['places']['waiting_list_total'] == 2
assert resp.json['places']['waiting_list_available'] == 0
assert resp.json['places']['waiting_list_reserved'] == 2
assert resp.json['places']['waiting_list_activated'] is True
# waiting list is full
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk), params={'force_waiting_list': True}
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
def test_booking_api_with_cancel_booking(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
events = []
for _ in range(3):
events.append(
Event.objects.create(
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=20, agenda=agenda
)
)
event_0, event_1, event_2 = events[0], events[1], events[2]
app.authorization = ('Basic', ('john.doe', 'password'))
app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event_0.id))
assert Booking.objects.count() == 1
first_booking = Booking.objects.first()
# Book a new event and cancel previous booking
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_1.id),
params={'cancel_booking_id': first_booking.pk},
)
assert resp.json['err'] == 0
assert resp.json['cancelled_booking_id'] == first_booking.pk
assert Booking.objects.count() == 2
first_booking = Booking.objects.get(pk=first_booking.pk)
assert first_booking.cancellation_datetime
# Cancelling an already cancelled booking returns an error
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_1.id),
params={'cancel_booking_id': first_booking.pk},
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'cancel booking: booking already cancelled' # legacy
assert resp.json['err_class'] == 'cancel booking: booking already cancelled'
assert resp.json['err_desc'] == 'cancel booking: booking already cancelled'
assert Booking.objects.count() == 2
# Cancelling a non existent booking returns an error
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_1.id), params={'cancel_booking_id': '-1'}
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'cancel booking: booking does no exist' # legacy
assert resp.json['err_class'] == 'cancel booking: booking does no exist'
assert resp.json['err_desc'] == 'cancel booking: booking does no exist'
assert Booking.objects.count() == 2
# Cancelling booking with different count than new booking
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.id, event_2.id), params={'count': 2})
assert resp.json['err'] == 0
assert Booking.objects.count() == 4
booking_id = resp.json['booking_id']
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_1.id),
params={'cancel_booking_id': booking_id, 'count': 1},
)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'cancel booking: count is different' # legacy
assert resp.json['err_class'] == 'cancel booking: count is different'
assert resp.json['err_desc'] == 'cancel booking: count is different'
assert Booking.objects.count() == 4
# cancel_booking_id must be an integer
app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_0.id),
params={'cancel_booking_id': 'no an integer'},
status=400,
)
# cancel_booking_id can be empty or null
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_0.id), params={'cancel_booking_id': ''}
)
assert resp.json['err'] == 0
assert 'cancelled_booking_id' not in resp.json
assert Booking.objects.count() == 5
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_0.id), params={'cancel_booking_id': None}
)
assert resp.json['err'] == 0
assert 'cancelled_booking_id' not in resp.json
assert Booking.objects.count() == 6
def test_soldout(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=2, agenda=agenda
)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.pk)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 1
assert event.slug in [x['id'] for x in resp.json['data']]
for _ in range(2):
Booking.objects.create(event=event)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.pk)
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['id'] == event.slug
assert resp.json['data'][0]['disabled'] is True
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.id), status=200)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
def test_waiting_list_booking(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event',
start_datetime=now() + datetime.timedelta(days=5),
places=2,
waiting_list_places=2,
agenda=agenda,
)
for _ in range(2):
Booking.objects.create(event=event)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.id), status=200)
assert resp.json['err'] == 0
assert resp.json['in_waiting_list'] is True
# cancel a booking that was not on the waiting list
booking = Booking.objects.filter(event=event, in_waiting_list=False)[0]
booking.cancel()
# check new booking is still done on the waiting list
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.id), status=200)
assert resp.json['err'] == 0
assert resp.json['in_waiting_list'] is True
# fill the waiting list
for _ in range(2):
Booking.objects.create(event=event, in_waiting_list=True)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.id), status=200)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
def test_multiple_booking_api(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event',
start_datetime=now() + datetime.timedelta(days=5),
places=10,
agenda=agenda,
)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/?count=NaN' % (agenda.slug, event.id), status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'invalid value for count (NaN)' # legacy
assert resp.json['err_class'] == 'invalid value for count (NaN)'
assert resp.json['err_desc'] == 'invalid value for count (NaN)'
resp = app.post('/api/agenda/%s/fillslot/%s/?count=0' % (agenda.slug, event.id), status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'count cannot be less than or equal to zero' # legacy
assert resp.json['err_class'] == 'count cannot be less than or equal to zero'
assert resp.json['err_desc'] == 'count cannot be less than or equal to zero'
resp = app.post('/api/agenda/%s/fillslot/%s/?count=-3' % (agenda.slug, event.id), status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'count cannot be less than or equal to zero' # legacy
assert resp.json['err_class'] == 'count cannot be less than or equal to zero'
assert resp.json['err_desc'] == 'count cannot be less than or equal to zero'
resp = app.post('/api/agenda/%s/fillslot/%s/?count=3' % (agenda.slug, event.id))
Booking.objects.get(id=resp.json['booking_id'])
assert resp.json['datetime'] == localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
assert 'accept_url' in resp.json['api']
assert 'cancel_url' in resp.json['api']
assert Event.objects.get(id=event.id).booked_places == 3
app.post('/api/agenda/%s/fillslot/%s/?count=2' % (agenda.slug, event.id))
assert Event.objects.get(id=event.id).booked_places == 5
resp = app.post(resp.json['api']['cancel_url'])
assert Event.objects.get(id=event.id).booked_places == 2
# check available places overflow
event.places = 3
event.waiting_list_places = 8
event.save()
resp3 = app.post('/api/agenda/%s/fillslot/%s/?count=5' % (agenda.slug, event.id))
assert Event.objects.get(id=event.id).booked_places == 2
assert Event.objects.get(id=event.id).booked_waiting_list_places == 5
# check waiting list overflow
resp = app.post('/api/agenda/%s/fillslot/%s/?count=5' % (agenda.slug, event.id))
assert resp.json['err'] == 1
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
assert Event.objects.get(id=event.id).booked_places == 2
assert Event.objects.get(id=event.id).booked_waiting_list_places == 5
# accept the waiting list
resp = app.post(resp3.json['api']['accept_url'])
assert Event.objects.get(id=event.id).booked_places == 7
assert Event.objects.get(id=event.id).booked_waiting_list_places == 0
# check with a short waiting list
Booking.objects.all().delete()
event.places = 4
event.waiting_list_places = 2
event.save()
resp = app.post('/api/agenda/%s/fillslot/%s/?count=5' % (agenda.slug, event.id))
assert resp.json['err'] == 1
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
resp = app.post('/api/agenda/%s/fillslot/%s/?count=3' % (agenda.slug, event.id))
assert resp.json['err'] == 0
assert Event.objects.get(id=event.id).booked_places == 3
assert Event.objects.get(id=event.id).booked_waiting_list_places == 0
resp = app.post('/api/agenda/%s/fillslot/%s/?count=3' % (agenda.slug, event.id))
assert resp.json['err'] == 1
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
resp = app.post('/api/agenda/%s/fillslot/%s/?count=2' % (agenda.slug, event.id))
assert resp.json['err'] == 0
assert Event.objects.get(id=event.id).booked_places == 3
assert Event.objects.get(id=event.id).booked_waiting_list_places == 2
def test_agenda_meeting_api_multiple_desk(app, user):
agenda = Agenda.objects.create(
label='foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56
)
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
default_desk = Desk.objects.create(agenda=agenda, label='Desk 1')
time_period = TimePeriod.objects.create(
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=default_desk
)
app.authorization = ('Basic', ('john.doe', 'password'))
# add booking of another meeting type
meeting_type2 = MeetingType.objects.create(agenda=agenda, label='Tux kart', duration=60)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type2.id)
event_id = resp.json['data'][0]['id']
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id))
cancel_url = resp.json['api']['cancel_url']
# add a second desk
desk2 = Desk.objects.create(label='Desk 2', agenda=agenda)
TimePeriod.objects.create(
start_time=time_period.start_time,
end_time=time_period.end_time,
weekday=time_period.weekday,
desk=desk2,
)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
event_id = resp.json['data'][1]['id']
resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id))
assert Booking.objects.count() == 2
assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime).strftime(
'%Y-%m-%d %H:%M:%S'
)
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len([x for x in resp2.json['data'] if not x['disabled']]) + 1
# try booking the same timeslot and fail
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id))
assert Booking.objects.count() == 2
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'
# cancel first booking and retry
resp = app.post(cancel_url)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(ctx.captured_queries) == 10
assert len(resp2.json['data']) == len([x for x in resp.json['data'] if not x['disabled']])
with CaptureQueriesContext(connection) as ctx:
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id))
assert len(ctx.captured_queries) == 19
assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime).strftime(
'%Y-%m-%d %H:%M:%S'
)
cancel_url = resp.json['api']['cancel_url']
resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp2.json['data']) == len([x for x in resp3.json['data'] if not x['disabled']]) + 1
# cancel a booking
resp = app.post(cancel_url)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == len(resp2.json['data'])
# try booking the same slot to make sure that cancelled booking has freed the slot
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id))
assert Booking.objects.count() == 4
assert Booking.objects.exclude(cancellation_datetime__isnull=True).count() == 2
assert resp_booking.json['datetime'] == localtime(Booking.objects.last().event.start_datetime).strftime(
'%Y-%m-%d %H:%M:%S'
)
# try booking the same timeslot again and fail
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id))
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'
# fill the agenda and make sure big O is O(1)
for event_data in resp2.json['data'][2:10]:
booking_url = event_data['api']['fillslot_url']
with CaptureQueriesContext(connection) as ctx:
app.post(booking_url)
assert len(ctx.captured_queries) == 18
with CaptureQueriesContext(connection) as ctx:
app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(ctx.captured_queries) == 10
def test_agenda_meeting_same_day(app, mock_now, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda(label='Foo', kind='meetings')
agenda.minimal_booking_delay = 0
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
desk1 = Desk.objects.create(label='foo', agenda=agenda)
desk2 = Desk.objects.create(label='bar', 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=desk1
)
TimePeriod.objects.create(
weekday=weekday, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), desk=desk2
)
resp = app.get(datetime_url)
event_data = resp.json['data'][0]
# check first proposed date is on the same day unless we're past the last
# open timeperiod.
event_datetime = datetime.datetime.strptime(event_data['datetime'], '%Y-%m-%d %H:%M:%S').timetuple()
assert (
event_datetime[:3] == mock_now.timetuple()[:3] and event_datetime[3:5] >= mock_now.timetuple()[3:5]
) or (event_datetime[:3] > mock_now.timetuple()[:3] and event_datetime[3:5] < mock_now.timetuple()[3:5])
# 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'] == 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'] == 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']
@pytest.mark.freeze_time('2022-02-05')
def test_virtual_agendas_meetings_booking(app, user):
foo_agenda = Agenda.objects.create(
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
)
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', minimal_booking_delay=1, maximal_booking_delay=5
)
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=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,
)
virt_agenda = Agenda.objects.create(
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=5
)
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]
# 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
# make a booking
fillslot_url = resp.json['data'][0]['api']['fillslot_url']
app.authorization = ('Basic', ('john.doe', 'password'))
resp_booking = app.post(fillslot_url)
assert Booking.objects.count() == 1
booking = Booking.objects.get(pk=resp_booking.json['booking_id'])
assert (
resp_booking.json['datetime']
== localtime(booking.event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
== resp.json['data'][0]['datetime']
)
first_booking_agenda = resp_booking.json['agenda']['slug']
assert resp_booking.json['end_datetime'] == localtime(
Booking.objects.all()[0].event.end_datetime
).strftime('%Y-%m-%d %H:%M:%S')
assert resp_booking.json['duration'] == 30
# second booking on the same slot (available on the second real agenda)
resp_booking = app.post(fillslot_url)
assert Booking.objects.count() == 2
booking = Booking.objects.get(pk=resp_booking.json['booking_id'])
assert (
resp_booking.json['datetime']
== localtime(booking.event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
== resp.json['data'][0]['datetime']
)
second_booking_agenda = resp_booking.json['agenda']['slug']
assert {first_booking_agenda, second_booking_agenda} == {'foo-meeting', 'bar-meeting'}
# try booking the same timeslot a third time: full
resp_booking = app.post(fillslot_url)
assert resp_booking.json['err'] == 1
assert resp_booking.json['err_class'] == 'no more desk available'
assert resp_booking.json['err_desc'] == 'no more desk available'
@pytest.mark.freeze_time('2021-02-25')
def test_virtual_agendas_meetings_booking_exclude_slots(app, user):
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()
agenda3 = 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)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=agenda3)
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')
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post(
'/api/agenda/%s/fillslot/%s:2021-02-26-0900/' % (virt_agenda.slug, meeting_type.slug),
params={'user_external_id': '42'},
)
assert resp.json['err'] == 0
resp = app.post(
'/api/agenda/%s/fillslot/%s:2021-02-26-0900/' % (virt_agenda.slug, meeting_type.slug),
params={'user_external_id': '42', 'exclude_user': True},
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'no more desk available'
resp = app.post(
'/api/agenda/%s/fillslot/%s:2021-02-26-0900/' % (virt_agenda.slug, meeting_type.slug),
params={'exclude_user': True},
)
assert resp.json['err'] == 0
virt_agenda.minimal_booking_delay = None
virt_agenda.maximal_booking_delay = None
virt_agenda.save()
resp = app.post(
'/api/agenda/%s/fillslot/%s:2021-02-26-0900/' % (virt_agenda.slug, meeting_type.slug),
params={'user_external_id': '42', 'exclude_user': True},
)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'no more desk available'
@pytest.mark.freeze_time('2022-02-05')
def test_virtual_agendas_meetings_booking_default_policy(app, user):
foo_agenda = Agenda.objects.create(
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
)
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
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=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=foo_desk_2,
)
bar_agenda = Agenda.objects.create(
label='Bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
)
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')
bar_desk_3 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 3')
bar_desk_4 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 3')
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=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_2,
)
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_3,
)
TimePeriod.objects.create(
weekday=0,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=bar_desk_4,
)
virt_agenda = Agenda.objects.create(
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=5
)
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]
# 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)
# We are saturday and we can book for next monday, 4 slots available each day
assert len(resp.json['data']) == 4
# there are 6 desks so we can make 6 bookings on the same slot
fillslot_url = resp.json['data'][0]['api']['fillslot_url']
app.authorization = ('Basic', ('john.doe', 'password'))
for i in range(1, 7):
foo_num_bookings = Booking.objects.filter(event__desk__agenda=foo_agenda).count()
bar_num_bookings = Booking.objects.filter(event__desk__agenda=bar_agenda).count()
foo_fill_rate = foo_num_bookings / 2
bar_fill_rate = bar_num_bookings / 4
next_agenda = None
if i != 1:
if foo_fill_rate < bar_fill_rate:
next_agenda = foo_agenda
elif foo_fill_rate > bar_fill_rate:
next_agenda = bar_agenda
elif foo_fill_rate == bar_fill_rate:
next_agenda = None
resp_booking = app.post(fillslot_url)
assert Booking.objects.count() == i
booking = Booking.objects.get(pk=resp_booking.json['booking_id'])
assert (
resp_booking.json['datetime']
== localtime(booking.event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
== resp.json['data'][0]['datetime']
)
if next_agenda:
assert booking.event.agenda == next_agenda
foo_num_bookings = Booking.objects.filter(event__desk__agenda=foo_agenda).count()
bar_num_bookings = Booking.objects.filter(event__desk__agenda=bar_agenda).count()
assert foo_num_bookings == 2
assert bar_num_bookings == 4
@pytest.mark.freeze_time('2022-03-30 08:00') # wednesday
def test_virtual_honor_real_agenda_booking_delay(app, user):
foo_agenda = Agenda.objects.create(
label='Foo', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=1
)
foo_meeting_type = MeetingType.objects.create(agenda=foo_agenda, label='Plop', duration=60)
foo_desk = Desk.objects.create(agenda=foo_agenda, label='desk')
bar_agenda = Agenda.objects.create(
label='Foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=2
)
MeetingType.objects.create(agenda=bar_agenda, label='Plop', duration=60)
bar_desk = Desk.objects.create(agenda=bar_agenda, label='desk')
for desk in (foo_desk, bar_desk):
TimePeriod.objects.create(
weekday=2, # wednesday
start_time=datetime.time(11, 0),
end_time=datetime.time(12, 0),
desk=desk,
)
TimePeriod.objects.create(
weekday=3, # thursday
start_time=datetime.time(11, 0),
end_time=datetime.time(12, 0),
desk=desk,
)
virtual_agenda = Agenda.objects.create(
label='Agenda Virtual', kind='virtual', minimal_booking_delay=None, maximal_booking_delay=None
)
virtual_agenda.real_agendas.add(foo_agenda, bar_agenda)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, foo_meeting_type.slug)
datetimes_resp = app.get(api_url)
assert len(datetimes_resp.json['data']) == 2
assert datetimes_resp.json['data'][0]['datetime'] == '2022-03-30 11:00:00' # comes from foo_agenda
assert datetimes_resp.json['data'][1]['datetime'] == '2022-03-31 11:00:00' # comes from bar_agenda
assert Booking.objects.count() == 0
app.authorization = ('Basic', ('john.doe', 'password'))
# minimal_booking_delay of bar_agenda must be honored
fillslot_url = datetimes_resp.json['data'][0]['api']['fillslot_url']
# book, this must end up in foo_agenda
resp = app.post(fillslot_url)
assert resp.json['err'] == 0
assert Booking.objects.count() == 1
booking = Booking.objects.first()
assert booking.event.agenda == foo_agenda
# try to book again, this must fail
resp = app.post(fillslot_url)
assert resp.json['err'] == 1
booking.delete()
assert Booking.objects.count() == 0
# maximal_booking_delay of foo_agenda must be honored
fillslot_url = datetimes_resp.json['data'][1]['api']['fillslot_url']
# book, this must end up in bar_agenda
resp = app.post(fillslot_url)
assert resp.json['err'] == 0
assert Booking.objects.count() == 1
booking = Booking.objects.first()
assert booking.event.agenda == bar_agenda
# try to book again, this must fail
resp = app.post(fillslot_url)
assert resp.json['err'] == 1
@pytest.mark.freeze_time('2017-04-01')
def test_duration_on_booking_api_fillslot_response(app, user):
agenda = Agenda(label='Foo bar')
agenda.save()
first_date = datetime.datetime(2017, 5, 20, 1, 12)
durations = [None, 0, 45]
evt = []
for i in range(3):
evt.append(
Event(
start_datetime=first_date + datetime.timedelta(days=i),
duration=durations[i],
places=20,
agenda=agenda,
)
)
evt[i].save()
assert evt[0].end_datetime is None
assert evt[1].end_datetime == evt[1].start_datetime
assert evt[2].end_datetime == evt[2].start_datetime + datetime.timedelta(minutes=45)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[0].id))
assert resp.json['datetime'] == '2017-05-20 01:12:00'
assert resp.json['end_datetime'] is None
assert 'ics_url' in resp.json['api']
ics = app.get(resp.json['api']['ics_url']).text
assert 'DTSTART:20170519T231200Z' in ics
assert 'DTEND:' not in ics
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[1].id))
assert resp.json['datetime'] == '2017-05-21 01:12:00'
assert resp.json['end_datetime'] == resp.json['datetime']
assert 'ics_url' in resp.json['api']
ics = app.get(resp.json['api']['ics_url']).text
assert 'DTSTART:20170520T231200Z' in ics
assert 'DTEND:20170520T231200Z' in ics
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[2].id))
assert resp.json['datetime'] == '2017-05-22 01:12:00'
assert resp.json['end_datetime'] == '2017-05-22 01:57:00'
assert 'ics_url' in resp.json['api']
ics = app.get(resp.json['api']['ics_url']).text
assert 'DTSTART:20170521T231200Z' in ics
assert 'DTEND:20170521T235700Z' in ics
def test_fillslot_past_event(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda.objects.create(
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
)
event1 = Event.objects.create(
label='Today before now',
start_datetime=localtime(now() - datetime.timedelta(hours=1)),
places=5,
agenda=agenda,
)
event2 = Event.objects.create(
label='Today after now',
start_datetime=localtime(now() + datetime.timedelta(hours=1)),
places=5,
agenda=agenda,
)
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event1.slug))
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is not bookable' % event1.slug
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event1.slug), params={'events': 'future'})
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is not bookable' % event1.slug
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event1.slug), params={'events': 'past'})
assert resp.json['err'] == 0
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event1.slug), params={'events': 'all'})
assert resp.json['err'] == 0
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event2.slug))
assert resp.json['err'] == 0
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event2.slug), params={'events': 'future'})
assert resp.json['err'] == 0
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event2.slug), params={'events': 'past'})
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is not bookable' % event2.slug
resp = app.post('/api/agenda/%s/fillslot/%s/?events=past' % (agenda.slug, event2.slug))
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is not bookable' % event2.slug
resp = app.post(
'/api/agenda/%s/fillslot/%s/?events=all' % (agenda.slug, event2.slug), params={'events': 'past'}
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is not bookable' % event2.slug
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event2.slug), params={'events': 'all'})
assert resp.json['err'] == 0
# check canceled
event1.cancel()
event2.cancel()
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event1.slug), params={'events': 'all'})
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is cancelled' % event1.slug
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event2.slug), params={'events': 'all'})
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is cancelled' % event2.slug
def test_fillslot_past_event_places(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda.objects.create(
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
)
event = Event.objects.create(
label='Today before now',
start_datetime=localtime(now() - datetime.timedelta(hours=1)),
places=1,
agenda=agenda,
)
Booking.objects.create(event=event)
# always bookable if past
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug), params={'events': 'past'})
assert resp.json['err'] == 0
event.waiting_list_places = 1
event.save()
Booking.objects.create(event=event, in_waiting_list=True)
# always bookable if past
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug), params={'events': 'past'})
assert resp.json['err'] == 0
def test_fillslot_past_events_min_places(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda.objects.create(
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
)
event = Event.objects.create(
label='Today before now',
start_datetime=localtime(now() - datetime.timedelta(hours=1)),
places=1,
agenda=agenda,
)
# always bookable if past
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug), params={'events': 'past', 'count': 2}
)
assert resp.json['err'] == 0
def test_fillslot_past_events_exclude_slots(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda.objects.create(
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
)
event = Event.objects.create(
label='Today before now',
start_datetime=localtime(now() - datetime.timedelta(hours=1)),
places=5,
agenda=agenda,
)
Booking.objects.create(event=event, user_external_id='42')
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug),
params={'events': 'past', 'user_external_id': '35', 'exclude_user': True},
)
assert resp.json['err'] == 0
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug),
params={'events': 'past', 'user_external_id': '35', 'exclude_user': True},
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is already booked by user' % event.slug
def test_fillslot_past_events_recurring_event(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda.objects.create(
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
)
start_datetime = localtime(now() - datetime.timedelta(days=3 * 7))
event = Event.objects.create(
label='Recurring',
start_datetime=start_datetime,
recurrence_days=[start_datetime.isoweekday()],
recurrence_end_date=now() + datetime.timedelta(days=30),
places=5,
agenda=agenda,
)
event.create_all_recurrences()
event_slug = '%s--%s' % (
event.slug,
(event.start_datetime - datetime.timedelta(days=7)).strftime('%Y-%m-%d-%H%M'),
) # too soon
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_slug), params={'events': 'all'}, status=400
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid slugs: %s' % event_slug
event_slug = '%s--%s' % (
event.slug,
(event.start_datetime + datetime.timedelta(days=7)).strftime('%Y-%m-%d-%H%M'),
)
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_slug), params={'events': 'past'})
assert resp.json['err'] == 0
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_slug), params={'events': 'all'})
assert resp.json['err'] == 0
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_slug), params={'events': 'future'})
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is not bookable' % event_slug.replace(':', '--')
event_slug = '%s--%s' % (
event.slug,
(event.start_datetime + datetime.timedelta(days=50 * 7)).strftime('%Y-%m-%d-%H%M'),
) # too late
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_slug), params={'events': 'all'}, status=400
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid slugs: %s' % event_slug
event_slug = '%s--%s' % (
event.slug,
(event.start_datetime + datetime.timedelta(days=4 * 7)).strftime('%Y-%m-%d-%H%M'),
)
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_slug), params={'events': 'past'})
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is not bookable' % event_slug.replace(':', '--')
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_slug), params={'events': 'all'})
assert resp.json['err'] == 0
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event_slug), params={'events': 'future'})
assert resp.json['err'] == 0
# check exclude_user_external_id
first_recurrence = Event.objects.get(primary_event=event, start_datetime=event.start_datetime)
Booking.objects.create(event=first_recurrence, user_external_id='42')
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, first_recurrence.slug),
params={'events': 'past', 'user_external_id': '42', 'exclude_user': True},
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is already booked by user' % first_recurrence.slug
# check canceled
first_recurrence.cancel()
resp = app.post(
'/api/agenda/%s/fillslot/%s/' % (agenda.slug, first_recurrence.slug), params={'events': 'past'}
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'event %s is cancelled' % first_recurrence.slug
def test_url_translation(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
Event.objects.create(
label='Event', start_datetime=now() + datetime.timedelta(days=5), places=10, agenda=agenda
)
app.authorization = ('Basic', ('john.doe', 'password'))
assert Booking.objects.count() == 0
resp = app.get('/api/agenda/%s/datetimes/' % agenda.pk)
fillslot_url = resp.json['data'][0]['api']['fillslot_url']
params = {
'backoffice_url': 'https://demarches.example.com/backoffice/foo/bar',
'cancel_callback_url': 'https://demarches.example.com/foo/bar/jump',
'form_url': 'https://demarches.example.com/foo/bar',
}
# https://demarches.example.com not in KNOWN_SERVICES, no URL translation
resp = app.post_json(fillslot_url, params=params)
booking = Booking.objects.get(pk=resp.json['booking_id'])
assert booking.backoffice_url == 'https://demarches.example.com/backoffice/foo/bar'
assert booking.cancel_callback_url == 'https://demarches.example.com/foo/bar/jump'
assert booking.form_url == 'https://demarches.example.com/foo/bar'
# http://example.org/ is in KNOWN_SERVICES, translation happens
params = {
'backoffice_url': 'http://example.org/backoffice/foo/bar',
'cancel_callback_url': 'http://example.org/foo/bar/jump',
'form_url': 'http://example.org/foo/bar',
}
resp = app.post_json(fillslot_url, params=params)
booking = Booking.objects.get(pk=resp.json['booking_id'])
assert booking.backoffice_url == 'publik://default/backoffice/foo/bar'
assert booking.cancel_callback_url == 'publik://default/foo/bar/jump'
assert booking.form_url == 'publik://default/foo/bar'
def test_fillslot_recurring_event_booking_forbidden(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
label='Event',
start_datetime=now() + datetime.timedelta(days=7),
recurrence_days=[now().isoweekday()],
places=2,
agenda=agenda,
)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'event is recurrent'
# monday 15th march at 12:00 CEST
@pytest.mark.freeze_time('2021-03-15T12:00:00+01:00')
def test_user_external_id(app, user):
user_external_id = 'foobar'
meeting_agenda_1 = Agenda.objects.create(
label='Meeting agenda', slug='mt1', kind='meetings', minimal_booking_delay=0
)
desk = Desk.objects.create(agenda=meeting_agenda_1, slug='desk')
meeting_type = MeetingType.objects.create(agenda=meeting_agenda_1, slug='mtt')
TimePeriod.objects.create(
weekday=0, start_time=datetime.time(9, 0), end_time=datetime.time(17, 00), desk=desk
)
meeting_agenda_2 = meeting_agenda_1.duplicate()
meeting_agenda_2.maximal_booking_delay = 20
meeting_agenda_2.save()
meeting_agenda_3 = meeting_agenda_1.duplicate()
meeting_agenda_3.maximal_booking_delay = 30
meeting_agenda_3.save()
virtual_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
virtual_agenda.real_agendas.add(meeting_agenda_1, meeting_agenda_2, meeting_agenda_3)
meeting_agenda_1_datetimes_url = f'/api/agenda/{meeting_agenda_1.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
meeting_agenda_2_datetimes_url = f'/api/agenda/{meeting_agenda_2.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
meeting_agenda_3_datetimes_url = f'/api/agenda/{meeting_agenda_3.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
virtual_agenda_datetimes_url = f'/api/agenda/{virtual_agenda.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
today_at_15 = localtime(now()).replace(hour=15, minute=0)
# check all slots are free
resp = app.get(virtual_agenda_datetimes_url)
assert not any(x['disabled'] for x in resp.json['data'])
resp = app.get(meeting_agenda_1_datetimes_url)
assert not any(x['disabled'] for x in resp.json['data'])
resp = app.get(meeting_agenda_2_datetimes_url)
assert not any(x['disabled'] for x in resp.json['data'])
resp = app.get(meeting_agenda_3_datetimes_url)
assert not any(x['disabled'] for x in resp.json['data'])
for agenda in virtual_agenda.get_real_agendas():
meeting_event = Event.objects.create(
label='Event',
slug='meeting1',
start_datetime=today_at_15,
agenda=agenda,
places=1,
meeting_type=agenda.meetingtype_set.all()[0],
desk=agenda.desk_set.all()[0],
)
Booking.objects.create(event=meeting_event, user_external_id=user_external_id)
# now some slots are disabled
resp = app.get(virtual_agenda_datetimes_url)
assert any(x['disabled'] for x in resp.json['data'])
agenda_datetimes_url = f'/api/agenda/{agenda.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
resp = app.get(agenda_datetimes_url)
assert any(x['disabled'] for x in resp.json['data'])
for other_agenda in virtual_agenda.get_real_agendas():
if other_agenda == agenda:
continue
other_agenda_datetimes_url = f'/api/agenda/{other_agenda.slug}/meetings/{meeting_type.slug}/datetimes/?user_external_id={user_external_id}'
resp = app.get(other_agenda_datetimes_url)
assert not any(x['disabled'] for x in resp.json['data'])
meeting_event.delete()