chrono/tests/test_api.py

3126 lines
130 KiB
Python

# -*- coding: utf-8 -*-
import datetime
import urllib.parse as urlparse
import pytest
import sys
from django.contrib.auth import get_user_model
from django.db import connection
from django.test import override_settings
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import now, make_aware, localtime
from chrono.agendas.models import (
Agenda,
Event,
Booking,
MeetingType,
TimePeriod,
Desk,
TimePeriodException,
VirtualMember,
)
import chrono.api.views
pytestmark = pytest.mark.django_db
def datetime_from_str(dt_str):
return datetime.datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')
@pytest.fixture
def user():
User = get_user_model()
user = User.objects.create(
username='john.doe', first_name=u'John', last_name=u'Doe', email='john.doe@example.net'
)
user.set_password('password')
user.save()
return user
@pytest.fixture(params=['Europe/Brussels', 'Asia/Kolkata', 'Brazil/East'])
def time_zone(request, settings):
settings.TIME_ZONE = request.param
# 2017-05-20 -> saturday
@pytest.fixture(
params=[
datetime.datetime(2017, 5, 20, 1, 12),
datetime.datetime(2017, 5, 20, 11, 42),
datetime.datetime(2017, 5, 20, 23, 17),
]
)
def mock_now(request, monkeypatch):
def mockreturn():
return make_aware(request.param)
monkeypatch.setattr(chrono.api.views, 'now', mockreturn)
monkeypatch.setattr(chrono.agendas.models, 'now', mockreturn)
monkeypatch.setattr(sys.modules[__name__], 'now', mockreturn)
return mockreturn()
@pytest.fixture
def some_data(time_zone, mock_now):
agenda = Agenda(label=u'Foo bar')
agenda.save()
first_date = localtime(now()).replace(hour=17, minute=0, second=0, microsecond=0)
first_date += datetime.timedelta(days=1)
for i in range(3):
event = Event(start_datetime=first_date + datetime.timedelta(days=i), places=20, agenda=agenda)
event.save()
agenda2 = Agenda(label=u'Foo bar 2')
agenda2.save()
first_date = localtime(now()).replace(hour=20, minute=0, second=0, microsecond=0)
first_date += datetime.timedelta(days=1)
for i in range(2):
event = Event(start_datetime=first_date + datetime.timedelta(days=i), places=20, agenda=agenda2)
event.save()
# a date in the past
event = Event(start_datetime=first_date - datetime.timedelta(days=10), places=10, agenda=agenda)
event.save()
@pytest.fixture
def meetings_agenda(time_zone, mock_now):
agenda = Agenda(
label=u'Foo bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56
)
agenda.save()
meeting_type = MeetingType(agenda=agenda, label='Blah', duration=30)
meeting_type.save()
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
test_2nd_weekday = (localtime(now()).weekday() + 3) % 7
default_desk, created = Desk.objects.get_or_create(agenda=agenda, label='Desk 1')
time_period = TimePeriod(
weekday=test_1st_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(12, 0),
desk=default_desk,
)
time_period.save()
time_period = TimePeriod(
weekday=test_2nd_weekday,
start_time=datetime.time(10, 0),
end_time=datetime.time(17, 0),
desk=default_desk,
)
time_period.save()
return agenda
@pytest.fixture
def virtual_meetings_agenda(meetings_agenda):
agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
VirtualMember.objects.create(virtual_agenda=agenda, real_agenda=meetings_agenda)
return agenda
def test_agendas_api(app, some_data, meetings_agenda):
agenda1 = Agenda.objects.filter(label=u'Foo bar')[0]
agenda2 = Agenda.objects.filter(label=u'Foo bar 2')[0]
virtual_agenda = Agenda.objects.create(
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=56
)
resp = app.get('/api/agenda/')
assert resp.json == {
'data': [
{
'text': 'Foo bar',
'id': u'foo-bar',
'slug': 'foo-bar',
'kind': 'events',
'minimal_booking_delay': 1,
'maximal_booking_delay': 56,
'api': {
'datetimes_url': 'http://testserver/api/agenda/%s/datetimes/' % agenda1.slug,
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % agenda1.slug,
},
},
{
'text': 'Foo bar 2',
'id': u'foo-bar-2',
'kind': 'events',
'slug': 'foo-bar-2',
'minimal_booking_delay': 1,
'maximal_booking_delay': 56,
'api': {
'datetimes_url': 'http://testserver/api/agenda/%s/datetimes/' % agenda2.slug,
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % agenda2.slug,
},
},
{
'text': 'Foo bar Meeting',
'id': u'foo-bar-meeting',
'slug': 'foo-bar-meeting',
'minimal_booking_delay': 1,
'maximal_booking_delay': 56,
'kind': 'meetings',
'api': {
'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % meetings_agenda.slug,
'desks_url': 'http://testserver/api/agenda/%s/desks/' % meetings_agenda.slug,
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % meetings_agenda.slug,
},
},
{
'text': 'Virtual Agenda',
'id': 'virtual-agenda',
'slug': 'virtual-agenda',
'minimal_booking_delay': 1,
'maximal_booking_delay': 56,
'kind': 'virtual',
'api': {
'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % virtual_agenda.slug,
'desks_url': 'http://testserver/api/agenda/%s/desks/' % virtual_agenda.slug,
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % virtual_agenda.slug,
},
},
]
}
resp = app.get('/api/agenda/', params={'q': 'foo'})
assert len(resp.json['data']) == 3
resp = app.get('/api/agenda/', params={'q': 'MEET'})
assert len(resp.json['data']) == 1
resp = app.get('/api/agenda/', params={'q': ''})
assert len(resp.json['data']) == 0
def test_agendas_meetingtypes_api(app, some_data, meetings_agenda):
resp = app.get('/api/agenda/%s/meetings/' % meetings_agenda.slug)
assert resp.json == {
'data': [
{
'text': 'Blah',
'id': 'blah',
'duration': 30,
'api': {
'datetimes_url': 'http://testserver/api/agenda/foo-bar-meeting/meetings/blah/datetimes/',
},
}
]
}
# wrong kind
agenda1 = Agenda.objects.filter(label=u'Foo bar')[0]
resp = app.get('/api/agenda/%s/meetings/' % agenda1.slug, status=404)
# unknown
resp = app.get('/api/agenda/xxxx/meetings/', status=404)
def test_agendas_desks_api(app, some_data, meetings_agenda):
resp = app.get('/api/agenda/%s/desks/' % meetings_agenda.slug)
assert resp.json == {'data': [{'text': 'Desk 1', 'id': 'desk-1',}]}
# wrong kind
agenda1 = Agenda.objects.filter(label=u'Foo bar')[0]
resp = app.get('/api/agenda/%s/desks/' % agenda1.slug, status=404)
# unknown
resp = app.get('/api/agenda/xxxx/desks/', status=404)
def test_datetimes_api(app, some_data):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
def check_bookability(data):
for event in data:
assert Event.objects.get(id=event['id']).in_bookable_period()
for event in agenda.event_set.all():
if not event.in_bookable_period():
assert not event.id in [x['id'] for x in data]
resp = app.get('/api/agenda/xxx/datetimes/', status=404)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert 'data' in resp.json
assert len(resp.json['data']) == 3
check_bookability(resp.json['data'])
assert app.get('/api/agenda/%s/datetimes/' % agenda.id).json == resp.json
agenda.minimal_booking_delay = 5
agenda.save()
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert len(resp.json['data']) == 0
check_bookability(resp.json['data'])
agenda.minimal_booking_delay = 2
agenda.save()
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert len(resp.json['data']) == 2
check_bookability(resp.json['data'])
agenda.minimal_booking_delay = 0
agenda.maximal_booking_delay = 3
agenda.save()
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert len(resp.json['data']) == 2
check_bookability(resp.json['data'])
assert resp.json['data'][0]['description'] is None
# add description, URL and pricing to events
for i, event in enumerate(agenda.event_set.all()):
event.description = 'Description %s' % i
event.url = 'https://www.example.net/%s' % i
event.pricing = '%s' % i
event.save()
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert resp.json['data'][0]['description']
def test_datetimes_api_wrong_kind(app, some_data):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
agenda.kind = 'meetings'
agenda.save()
resp = app.get('/api/agenda/%s/datetimes/' % agenda.id, status=404)
def test_datetime_api_fr(app, some_data):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
with override_settings(LANGUAGE_CODE='fr-fr'):
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
# no seconds, hh:mm in 24-hour formats
assert resp.json['data'][0]['text'].endswith(' 17:00')
assert resp.json['data'][0]['datetime'].endswith(' 17:00:00')
assert 'data' in resp.json
def test_datetime_api_label(app, some_data):
agenda_id = Agenda.objects.filter(label=u'Foo bar 2')[0].id
event = Event.objects.filter(agenda=agenda_id)[0]
event.label = 'Hello world'
event.save()
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
assert 'Hello world' in [x['text'] for x in resp.json['data']]
def test_datetime_api_status_url(app, some_data):
agenda = Agenda.objects.get(label=u'Foo bar 2')
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
for datum in resp.json['data']:
assert urlparse.urlparse(datum['api']['status_url']).path == '/api/agenda/%s/status/%s/' % (
agenda.slug,
datum['slug'] or datum['id'],
)
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 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
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)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
# 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'
def test_booking_api(app, some_data, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]
# 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.id][0]['api'][
'fillslot_url'
]
assert urlparse.urlparse(event_fillslot_url).path == '/api/agenda/%s/fillslot/%s/' % (
agenda.slug,
event.slug or event.id,
)
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 'accept_url' not 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 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 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/'},
)
assert Booking.objects.get(id=resp.json['booking_id']).label == 'foo'
assert Booking.objects.get(id=resp.json['booking_id']).user_name == 'bar'
assert Booking.objects.get(id=resp.json['booking_id']).backoffice_url == 'http://example.net/'
# blank data are OK
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'label': '', 'user_name': '', 'backoffice_url': ''},
)
assert Booking.objects.get(id=resp.json['booking_id']).label == ''
assert Booking.objects.get(id=resp.json['booking_id']).user_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_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_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'}
# test invalid data are refused
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={'user_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_name' in resp.json['errors']
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_ics(app, some_data, meetings_agenda, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id))
assert Booking.objects.count() == 1
assert 'ics_url' in resp.json['api']
assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
assert urlparse.urlparse(resp.json['api']['ics_url']).netloc
formatted_start_date = event.start_datetime.strftime('%Y%m%dT%H%M%S')
booking_ics = Booking.objects.get(id=resp.json['booking_id']).get_ics()
assert (
'UID:%s-%s-%s\r\n' % (event.start_datetime.isoformat(), agenda.pk, resp.json['booking_id'])
in booking_ics
)
assert 'SUMMARY:\r\n' in booking_ics
assert 'DTSTART:%sZ\r\n' % formatted_start_date in booking_ics
assert 'DTEDND:' not in booking_ics
# 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/',
'url': 'http://example.com/booking',
},
)
assert Booking.objects.count() == 2
booking_ics = Booking.objects.get(id=resp.json['booking_id']).get_ics()
assert 'SUMMARY:foo\r\n' in booking_ics
assert 'ATTENDEE:bar\r\n' in booking_ics
assert 'URL:http://example.com/booking\r\n' in booking_ics
# test with user_label in additionnal 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/',
'url': 'http://example.com/booking',
'user_display_label': 'your booking',
},
)
assert Booking.objects.count() == 3
booking_ics = Booking.objects.get(id=resp.json['booking_id']).get_ics()
assert 'SUMMARY:your booking\r\n' in booking_ics
assert 'ATTENDEE:bar\r\n' in booking_ics
assert 'URL:http://example.com/booking\r\n' in booking_ics
# extra data stored in extra_data field
resp = app.post_json(
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
params={
'label': 'l',
'user_name': 'u',
'backoffice_url': '',
'location': 'bar',
'comment': 'booking comment',
'description': 'booking description',
},
)
assert Booking.objects.count() == 4
booking_id = resp.json['booking_id']
booking = Booking.objects.get(id=booking_id)
booking_ics = booking.get_ics()
assert 'COMMENT:booking comment\r\n' in booking_ics
assert 'LOCATION:bar\r\n' in booking_ics
assert 'DESCRIPTION:booking description\r\n' in booking_ics
# unauthenticated
app.authorization = None
app.get('/api/booking/%s/ics/' % resp.json['booking_id'], status=401)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.get('/api/booking/%s/ics/' % resp.json['booking_id'])
assert resp.headers['Content-Type'] == 'text/calendar'
params = {
'description': 'custom booking description',
'location': 'custom booking location',
'comment': 'custom comment',
'url': 'http://example.com/custom',
}
resp = app.get('/api/booking/%s/ics/' % booking_id, params=params)
assert 'DESCRIPTION:custom booking description\r\n' in resp.text
assert 'LOCATION:custom booking location\r\n' in resp.text
assert 'COMMENT:custom comment\r\n' in resp.text
assert 'URL:http://example.com/custom\r\n' in resp.text
meetings_agenda_id = Agenda.objects.filter(label=u'Foo bar Meeting')[0].id
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
event = resp.json['data'][2]
resp = app.post('/api/agenda/%s/fillslot/%s/' % (meetings_agenda_id, event['id']))
assert Booking.objects.count() == 5
assert 'ics_url' in resp.json['api']
booking = Booking.objects.get(id=resp.json['booking_id'])
booking_ics = booking.get_ics()
start = booking.event.start_datetime.strftime('%Y%m%dT%H%M%S')
end = (
booking.event.start_datetime + datetime.timedelta(minutes=booking.event.meeting_type.duration)
).strftime('%Y%m%dT%H%M%S')
assert "DTSTART:%sZ\r\n" % start in booking_ics
assert "DTEND:%sZ\r\n" % end in booking_ics
def test_booking_api_fillslots(app, some_data, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
events_ids = [x.id for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()]
for i, event in enumerate([x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()]):
event.slug = 'event-%s' % i
event.save()
events_slugs = [x.slug for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()]
assert len(events_ids) == 3
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0] # first event
# unauthenticated
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, 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)
api_event_ids = [x['id'] for x in resp_datetimes.json['data']]
assert api_event_ids == events_ids
assert Booking.objects.count() == 0
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': events_ids})
primary_booking_id = resp.json['booking_id']
Booking.objects.get(id=primary_booking_id)
assert resp.json['datetime'] == localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
assert 'accept_url' not in resp.json['api']
assert 'suspend_url' in resp.json['api']
assert 'cancel_url' in resp.json['api']
assert urlparse.urlparse(resp.json['api']['suspend_url']).netloc
assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
assert Booking.objects.count() == 3
# these 3 bookings are related, the first is the primary one
bookings = Booking.objects.all().order_by('pk')
assert bookings[0].primary_booking is None
assert bookings[1].primary_booking.id == bookings[0].id == primary_booking_id
assert bookings[2].primary_booking.id == bookings[0].id == primary_booking_id
# access by slug
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': events_slugs})
primary_booking_id_2 = resp.json['booking_id']
assert Booking.objects.count() == 6
assert Booking.objects.filter(event__agenda=agenda).count() == 6
# 6 = 2 primary + 2*2 secondary
assert Booking.objects.filter(event__agenda=agenda, primary_booking__isnull=True).count() == 2
assert Booking.objects.filter(event__agenda=agenda, primary_booking=primary_booking_id).count() == 2
assert Booking.objects.filter(event__agenda=agenda, primary_booking=primary_booking_id_2).count() == 2
# test with additional data
resp = app.post_json(
'/api/agenda/%s/fillslots/' % agenda.id,
params={
'slots': events_ids,
'label': 'foo',
'user_name': 'bar',
'backoffice_url': 'http://example.net/',
},
)
booking_id = resp.json['booking_id']
assert Booking.objects.get(id=booking_id).label == 'foo'
assert Booking.objects.get(id=booking_id).user_name == 'bar'
assert Booking.objects.get(id=booking_id).backoffice_url == 'http://example.net/'
assert Booking.objects.filter(primary_booking=booking_id, label='foo').count() == 2
# cancel
cancel_url = resp.json['api']['cancel_url']
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 0
assert Booking.objects.get(id=booking_id).cancellation_datetime is None
resp_cancel = app.post(cancel_url)
assert resp_cancel.json['err'] == 0
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 3
assert Booking.objects.get(id=booking_id).cancellation_datetime is not None
# extra data stored in extra_data field
resp = app.post_json(
'/api/agenda/%s/fillslots/' % agenda.id,
params={'slots': events_ids, 'label': 'l', 'user_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_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'}
for booking in Booking.objects.filter(primary_booking=resp.json['booking_id']):
assert booking.extra_data == {'foo': 'bar'}
# test invalid data are refused
resp = app.post_json(
'/api/agenda/%s/fillslots/' % agenda.id,
params={'slots': events_ids, 'user_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_name' in resp.json['errors']
# empty or missing slots
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, params={'slots': []}, status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'slots list cannot be empty' # legacy
assert resp.json['err_class'] == 'slots list cannot be empty'
assert resp.json['err_desc'] == 'slots list cannot be empty'
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'slots list cannot be empty' # legacy
assert resp.json['err_class'] == 'slots list cannot be empty'
assert resp.json['err_desc'] == 'slots list cannot be empty'
# invalid slots format
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.id, params={'slots': 'foobar'}, status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'unknown event identifiers or slugs' # legacy
assert resp.json['err_class'] == 'unknown event identifiers or slugs'
assert resp.json['err_desc'] == 'unknown event identifiers or slugs'
# unknown agendas
resp = app.post('/api/agenda/foobar/fillslots/', status=404)
resp = app.post('/api/agenda/0/fillslots/', status=404)
def test_booking_api_fillslots_slots_string_param(app, some_data, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
events_ids = [x.id for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()]
assert len(events_ids) == 3
app.authorization = ('Basic', ('john.doe', 'password'))
# empty string
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': ''}, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'invalid payload'
assert resp.json['err_desc'] == 'invalid payload'
slots_string_param = ','.join([str(e) for e in events_ids])
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots_string_param})
primary_booking_id = resp.json['booking_id']
Booking.objects.get(id=primary_booking_id)
assert Booking.objects.count() == 3
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_fillslots(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)
slots = [resp.json['data'][0]['id'], resp.json['data'][1]['id']]
app.authorization = ('Basic', ('john.doe', 'password'))
resp_booking = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': slots})
assert Booking.objects.count() == 2
primary_booking = Booking.objects.filter(primary_booking__isnull=True).first()
secondary_booking = Booking.objects.filter(primary_booking=primary_booking.id).first()
assert resp_booking.json['datetime'] == localtime(primary_booking.event.start_datetime).strftime(
'%Y-%m-%d %H:%M:%S'
)
assert resp_booking.json['end_datetime'] == localtime(secondary_booking.event.end_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.get('disabled')]) + 2
# try booking the same timeslots
resp2 = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': slots})
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 partially free timeslots (one free, one busy)
nonfree_slots = [resp.json['data'][0]['id'], resp.json['data'][2]['id']]
resp2 = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': nonfree_slots})
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'
# booking other free timeslots
free_slots = [resp.json['data'][3]['id'], resp.json['data'][2]['id']]
resp2 = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': free_slots})
assert resp2.json['err'] == 0
cancel_url = resp2.json['api']['cancel_url']
assert Booking.objects.count() == 4
# 4 = 2 primary + 2 secondary
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
assert Booking.objects.filter(primary_booking__isnull=False).count() == 2
# cancel
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 0
resp_cancel = app.post(cancel_url)
assert resp_cancel.json['err'] == 0
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 2
impossible_slots = ['1:2017-05-22-1130', '2:2017-05-22-1100']
resp = app.post('/api/agenda/%s/fillslots/' % agenda_id, params={'slots': impossible_slots}, status=400)
assert resp.json['err'] == 1
assert resp.json['reason'] == 'all slots must have the same meeting type id (1)' # legacy
assert resp.json['err_class'] == 'all slots must have the same meeting type id (1)'
assert resp.json['err_desc'] == 'all slots must have the same meeting type id (1)'
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']
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_booking_api_with_data(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id)[0]
app.authorization = ('Basic', ('john.doe', 'password'))
resp = 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'}
def test_booking_api_available(app, some_data, meetings_agenda, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
event = Event.objects.filter(agenda=agenda)[0]
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk))
assert resp.json['err'] == 0
assert resp.json['places']['total'] == 10
assert resp.json['places']['available'] == 9
assert resp.json['places']['reserved'] == 1
assert 'waiting_list_total' not in resp.json['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'] == 10
assert resp.json['places']['available'] == 9
assert resp.json['places']['reserved'] == 1
assert resp.json['places']['waiting_list_total'] == 5
assert resp.json['places']['waiting_list_available'] == 3
assert resp.json['places']['waiting_list_reserved'] == 2
# not for mettings agenda
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.pk)
event_id = resp.json['data'][2]['id']
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.pk, event_id))
assert resp.json['err'] == 0
assert 'places' not in resp.json
# not for multiple booking
events = [
x for x in Event.objects.filter(agenda=agenda).order_by('start_datetime') if x.in_bookable_period()
][:2]
slots = [x.pk for x in events]
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': '3'})
assert resp.json['err'] == 0
assert 'places' not in resp.json
def test_booking_api_force_waiting_list(app, some_data, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
event = Event.objects.filter(agenda=agenda)[0]
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'] == 10
assert resp.json['places']['available'] == 9
assert resp.json['places']['reserved'] == 1
assert resp.json['places']['waiting_list_total'] == 2
assert resp.json['places']['waiting_list_available'] == 2
assert resp.json['places']['waiting_list_reserved'] == 0
# 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'] == 10
assert resp.json['places']['available'] == 8
assert resp.json['places']['reserved'] == 2
assert resp.json['places']['waiting_list_total'] == 2
assert resp.json['places']['waiting_list_available'] == 2
assert resp.json['places']['waiting_list_reserved'] == 0
# 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'] == 10
assert resp.json['places']['available'] == 8
assert resp.json['places']['reserved'] == 2
assert resp.json['places']['waiting_list_total'] == 2
assert resp.json['places']['waiting_list_available'] == 1
assert resp.json['places']['waiting_list_reserved'] == 1
# 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'] == 10
assert resp.json['places']['available'] == 8
assert resp.json['places']['reserved'] == 2
assert resp.json['places']['waiting_list_total'] == 2
assert resp.json['places']['waiting_list_available'] == 0
assert resp.json['places']['waiting_list_reserved'] == 2
# 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, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event_0, event_1, event_2, event_3 = Event.objects.filter(agenda_id=agenda_id)[0:4]
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_3.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_booking_cancellation_api(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id)[0]
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id), status=401)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id))
booking_id = resp.json['booking_id']
assert Booking.objects.count() == 1
resp = app.delete('/api/booking/%s/' % booking_id)
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 1
def test_booking_cancellation_post_api(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id)[0]
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id), status=401)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id))
booking_id = resp.json['booking_id']
assert Booking.objects.count() == 1
assert urlparse.urlparse(resp.json['api']['cancel_url']).path == '/api/booking/%s/cancel/' % booking_id
resp = app.post('/api/booking/%s/cancel/' % booking_id)
assert Booking.objects.filter(cancellation_datetime__isnull=False).count() == 1
# cancel an object that doesn't exist
resp = app.post('/api/booking/%s/cancel/' % 0, status=404)
# cancel an event that was already cancelled
resp = app.post('/api/booking/%s/cancel/' % booking_id, status=200)
assert resp.json['err'] == 1
def test_booking_cancellation_post_meeting_api(app, meetings_agenda, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar Meeting')[0].id
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
nb_events = len(resp.json['data'])
event_id = resp.json['data'][2]['id']
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert Booking.objects.count() == 1
booking_id = resp.json['booking_id']
assert Booking.objects.count() == 1
resp = app.post('/api/booking/%s/cancel/' % booking_id)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len(resp.json['data']) == nb_events
# book the same time slot
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
assert resp.json['err'] == 0
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == nb_events - 1
def test_soldout(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 3
assert event.id in [x['id'] for x in resp.json['data']]
for i in range(event.places):
Booking(event=event).save()
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 2
assert not event.id in [x['id'] for x in resp.json['data'] if not x.get('disabled')]
assert event.id in [x['id'] for x in resp.json['data'] if x.get('disabled')]
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, 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_status(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id)[0]
Booking(event=event).save()
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, event.id), status=401)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, 0), status=404)
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, 'xx'), status=404)
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, event.id))
assert resp.json['err'] == 0
assert resp.json['places']['total'] == 10
assert resp.json['places']['available'] == 9
assert resp.json['places']['reserved'] == 1
assert not 'waiting_list_total' in resp.json['places']
Booking(event=event, in_waiting_list=True).save()
event.waiting_list_places = 5
event.save()
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, event.id))
assert resp.json['places']['waiting_list_total'] == 5
assert resp.json['places']['waiting_list_available'] == 4
assert resp.json['places']['waiting_list_reserved'] == 1
# access by slug
event.slug = 'bar'
event.save()
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, event.slug))
# not found event
resp = app.get('/api/agenda/%s/status/%s/' % (agenda_id, 'unknown'), status=404)
def test_waiting_list_datetimes(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
event.waiting_list_places = 5
event.save()
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 3
assert event.id in [x['id'] for x in resp.json['data']]
for i in range(event.places):
Booking(event=event).save()
# all places are booked but all the dates are still displayed as there is a
# waiting list.
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 3
# fill the waiting list
for i in range(event.waiting_list_places):
Booking(event=event, in_waiting_list=True).save()
# the event datetime should no longer be returned
resp = app.get('/api/agenda/%s/datetimes/' % agenda_id)
assert len([x for x in resp.json['data'] if not x.get('disabled')]) == 2
assert not event.id in [x['id'] for x in resp.json['data'] if not x.get('disabled')]
assert event.id in [x['id'] for x in resp.json['data'] if x.get('disabled')]
def test_waiting_list_booking(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
event.waiting_list_places = 5
event.save()
for i in range(event.places):
Booking(event=event).save()
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event.id), status=200)
assert resp.json['err'] == 0
assert resp.json['in_waiting_list'] is True
assert 'accept_url' in resp.json['api']
assert 'suspend_url' not in resp.json['api']
assert 'cancel_url' in resp.json['api']
assert 'ics_url' in resp.json['api']
assert urlparse.urlparse(resp.json['api']['accept_url']).netloc
assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
assert urlparse.urlparse(resp.json['api']['ics_url']).netloc
# 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_id, event.id), status=200)
assert resp.json['err'] == 0
assert resp.json['in_waiting_list'] is True
# fill the waiting list
for i in range(event.waiting_list_places):
Booking(event=event, in_waiting_list=True).save()
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, 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_cancel_booking(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].pk
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
primary = Booking.objects.create(event=event)
secondary = Booking.objects.create(event=event, primary_booking=primary)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/booking/%s/cancel/' % secondary.pk)
assert resp.json['err'] == 2
assert resp.json['reason'] == 'secondary booking' # legacy
assert resp.json['err_class'] == 'secondary booking'
assert resp.json['err_desc'] == 'secondary booking'
resp = app.post('/api/booking/%s/cancel/' % primary.pk)
assert resp.json['err'] == 0
primary.refresh_from_db()
secondary.refresh_from_db()
assert primary.cancellation_datetime is not None
assert secondary.cancellation_datetime is not None
def test_accept_booking(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
event.waiting_list_places = 5
event.save()
# create a booking on the waiting list
primary = Booking.objects.create(event=event, in_waiting_list=True)
secondary = Booking.objects.create(event=event, in_waiting_list=True, primary_booking=primary)
assert Booking.objects.filter(in_waiting_list=True).count() == 2
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/booking/%s/accept/' % secondary.id)
assert resp.json['err'] == 2
assert resp.json['reason'] == 'secondary booking' # legacy
assert resp.json['err_class'] == 'secondary booking'
assert resp.json['err_desc'] == 'secondary booking'
resp = app.post('/api/booking/%s/accept/' % primary.id)
assert resp.json['err'] == 0
assert resp.json['overbooked_places'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 2
primary.refresh_from_db()
secondary.refresh_from_db()
assert primary.in_waiting_list is False
assert secondary.in_waiting_list is False
# accept a booking that doesn't exist
resp = app.post('/api/booking/0/accept/', status=404)
# accept a booking that was not in the waiting list
resp = app.post('/api/booking/%s/accept/' % primary.id, status=200)
assert resp.json['err'] == 3
# accept a booking that was cancelled before
primary.suspend()
primary.cancel()
resp = app.post('/api/booking/%s/accept/' % primary.id, status=200)
assert resp.json['err'] == 1
assert Booking.objects.filter(in_waiting_list=True).count() == 2
assert Booking.objects.filter(in_waiting_list=False).count() == 0
# accept a booking with overbooking
event.places = 1
event.save()
Booking.objects.update(cancellation_datetime=None)
primary.refresh_from_db()
primary.suspend()
resp = app.post('/api/booking/%s/accept/' % primary.pk)
assert resp.json['err'] == 0
assert resp.json['overbooked_places'] == 1
def test_suspend_booking(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
event.waiting_list_places = 5
event.save()
# create a booking not on the waiting list
primary = Booking.objects.create(event=event, in_waiting_list=False)
secondary = Booking.objects.create(event=event, in_waiting_list=False, primary_booking=primary)
assert Booking.objects.filter(in_waiting_list=False).count() == 2
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/booking/%s/suspend/' % secondary.id)
assert resp.json['err'] == 2
assert resp.json['reason'] == 'secondary booking' # legacy
assert resp.json['err_class'] == 'secondary booking'
assert resp.json['err_desc'] == 'secondary booking'
resp = app.post('/api/booking/%s/suspend/' % primary.pk)
primary.refresh_from_db()
secondary.refresh_from_db()
assert primary.in_waiting_list is True
assert secondary.in_waiting_list is True
# suspend a booking that doesn't exist
resp = app.post('/api/booking/0/suspend/', status=404)
# suspend a booking that is in the waiting list
resp = app.post('/api/booking/%s/suspend/' % primary.pk, status=200)
assert resp.json['err'] == 3
# suspend a booking that was cancelled before
primary.accept()
primary.cancel()
resp = app.post('/api/booking/%s/suspend/' % primary.pk, status=200)
assert resp.json['err'] == 1
assert primary.in_waiting_list is False
def test_resize_booking(app, some_data, user):
agenda_id = Agenda.objects.filter(label=u'Foo bar')[0].id
event = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[0]
event.places = 5
event.waiting_list_places = 5
event.save()
event2 = Event.objects.filter(agenda_id=agenda_id).exclude(start_datetime__lt=now())[1]
# create a booking not on the waiting list
primary = Booking.objects.create(event=event, in_waiting_list=False)
secondary = Booking.objects.create(event=event, in_waiting_list=False, primary_booking=primary)
assert Booking.objects.filter(in_waiting_list=False).count() == 2
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/booking/%s/resize/' % secondary.pk, params={'count': 42})
assert resp.json['err'] == 2
assert resp.json['reason'] == 'secondary booking' # legacy
assert resp.json['err_class'] == 'secondary booking'
assert resp.json['err_desc'] == 'secondary booking'
# resize a booking that doesn't exist
resp = app.post('/api/booking/0/resize/', params={'count': 42}, status=404)
# decrease a booking to 0
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 0}, status=400)
assert resp.json['err'] == 1
# decrease a booking not in waiting list
assert Booking.objects.filter(in_waiting_list=False).count() == 2
assert Booking.objects.filter().count() == 2
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 1
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 1
# no changes
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 1
assert Booking.objects.filter().count() == 1
# increase a booking not in waiting list
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 2})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 2
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 2
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
assert resp.json['err'] == 3
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/booking/%s/resize/' % primary.pk, params={'count': 5})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=False).count() == 5
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 5
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
assert resp.json['err'] == 3
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
# decrease a booking in waiting list
primary.suspend()
assert Booking.objects.filter(in_waiting_list=True).count() == 5
assert Booking.objects.filter().count() == 5
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 1
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 1
# no changes
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 1})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 1
assert Booking.objects.filter().count() == 1
# increase a booking in waiting list
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 2})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 2
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 2
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
assert resp.json['err'] == 3
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/booking/%s/resize/' % primary.pk, params={'count': 5})
assert resp.json['err'] == 0
assert Booking.objects.filter(in_waiting_list=True).count() == 5
assert Booking.objects.filter(primary_booking__isnull=True).count() == 1
assert Booking.objects.filter().count() == 5
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 6})
assert resp.json['err'] == 3
assert resp.json['reason'] == 'sold out' # legacy
assert resp.json['err_class'] == 'sold out'
assert resp.json['err_desc'] == 'sold out'
# resize a booking that is on multi events
secondary = Booking.objects.create(event=event2, in_waiting_list=False, primary_booking=primary)
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
assert resp.json['err'] == 4
assert resp.json['reason'] == 'can not resize multi event booking' # legacy
assert resp.json['err_class'] == 'can not resize multi event booking'
assert resp.json['err_desc'] == 'can not resize multi event booking'
# resize a booking that was cancelled before
primary.cancel()
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
assert resp.json['err'] == 1
def test_multiple_booking_api(app, some_data, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
event = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][0]
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id)
event_fillslot_url = [x for x in resp_datetimes.json['data'] if x['id'] == event.id][0]['api'][
'fillslot_url'
]
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)"
app.authorization = ('Basic', ('john.doe', 'password'))
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"
app.authorization = ('Basic', ('john.doe', 'password'))
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' not in resp.json['api']
assert 'cancel_url' in resp.json['api']
assert Event.objects.get(id=event.id).booked_places == 3
resp2 = 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).waiting_list == 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).waiting_list == 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).waiting_list == 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).waiting_list == 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).waiting_list == 2
def test_multiple_booking_api_fillslots(app, some_data, user):
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
# get slots of first 2 events
events = [
x for x in Event.objects.filter(agenda=agenda).order_by('start_datetime') if x.in_bookable_period()
][:2]
events_ids = [x.id for x in events]
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id)
slots = [x['id'] for x in resp_datetimes.json['data'] if x['id'] in events_ids]
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.post('/api/agenda/%s/fillslots/?count=NaN' % agenda.slug, params={'slots': slots}, 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/fillslots/' % agenda.slug, params={'slots': slots, 'count': 'NaN'}, 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 'count' in resp.json['errors']
# get 3 places on 2 slots
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': '3'})
# one booking with 5 children
booking = Booking.objects.get(id=resp.json['booking_id'])
cancel_url = resp.json['api']['cancel_url']
assert Booking.objects.filter(primary_booking=booking).count() == 5
assert resp.json['datetime'] == localtime(events[0].start_datetime).strftime('%Y-%m-%d %H:%M:%S')
assert 'accept_url' not in resp.json['api']
assert 'cancel_url' in resp.json['api']
assert 'ics_url' in resp.json['api']
resp_events = resp.json['events']
assert len(resp_events) == len(events)
for (e, resp_e) in zip(events, resp_events):
assert e.slug == resp_e['slug']
assert e.description == resp_e['description']
assert str(e) == resp_e['text']
assert localtime(e.start_datetime).strftime('%Y-%m-%d %H:%M:%S') == resp_e['datetime']
for event in events:
assert Event.objects.get(id=event.id).booked_places == 3
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 2})
for event in events:
assert Event.objects.get(id=event.id).booked_places == 5
resp = app.post(cancel_url)
for event in events:
assert Event.objects.get(id=event.id).booked_places == 2
# check available places overflow
# NB: limit only the first event !
events[0].places = 3
events[0].waiting_list_places = 8
events[0].save()
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5})
for event in events:
assert Event.objects.get(id=event.id).booked_places == 2
assert Event.objects.get(id=event.id).waiting_list == 5
accept_url = resp.json['api']['accept_url']
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5})
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'
for event in events:
assert Event.objects.get(id=event.id).booked_places == 2
assert Event.objects.get(id=event.id).waiting_list == 5
# accept the waiting list
resp = app.post(accept_url)
for event in events:
assert Event.objects.get(id=event.id).booked_places == 7
assert Event.objects.get(id=event.id).waiting_list == 0
# check with a short waiting list
Booking.objects.all().delete()
# NB: limit only the first event !
events[0].places = 4
events[0].waiting_list_places = 2
events[0].save()
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 5})
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/fillslots/' % agenda.slug, params={'slots': slots, 'count': 3})
assert resp.json['err'] == 0
for event in events:
assert Event.objects.get(id=event.id).booked_places == 3
assert Event.objects.get(id=event.id).waiting_list == 0
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'count': 3})
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/fillslots/' % agenda.slug, params={'slots': slots, 'count': '2'})
assert resp.json['err'] == 0
for event in events:
assert Event.objects.get(id=event.id).booked_places == 3
assert Event.objects.get(id=event.id).waiting_list == 2
def test_multiple_booking_move_booking(app, user):
agenda = Agenda(label=u'Foo bar')
agenda.save()
first_date = localtime(now()).replace(hour=17, minute=0, second=0, microsecond=0)
first_date += datetime.timedelta(days=1)
events = []
for i in range(10):
event = Event(start_datetime=first_date + datetime.timedelta(days=i), places=20, agenda=agenda)
event.save()
events.append(event)
first_two_events = events[:2]
events_ids = [x.id for x in first_two_events]
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id)
slots = [x['id'] for x in resp_datetimes.json['data'] if x['id'] in events_ids]
app.authorization = ('Basic', ('john.doe', 'password'))
# get 1 place on 2 slots
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots})
booking = Booking.objects.get(id=resp.json['booking_id'])
assert Booking.objects.filter(primary_booking=booking).count() == 1
for event in first_two_events:
assert Event.objects.get(id=event.id).booked_places == 1
# change, 1 place on 2 other slots
last_two_events = events[-2:]
events_ids = [x.id for x in last_two_events]
resp_datetimes = app.get('/api/agenda/%s/datetimes/' % agenda.id)
slots = [x['id'] for x in resp_datetimes.json['data'] if x['id'] in events_ids]
resp = app.post(
'/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': slots, 'cancel_booking_id': booking.pk}
)
booking = Booking.objects.get(id=resp.json['booking_id'])
assert Booking.objects.filter(primary_booking=booking).count() == 1
for event in first_two_events:
assert Event.objects.get(id=event.id).booked_places == 0
for event in last_two_events:
assert Event.objects.get(id=event.id).booked_places == 1
def test_agenda_detail_api(app, some_data):
agenda = Agenda.objects.get(slug='foo-bar')
resp = app.get('/api/agenda/%s/' % agenda.slug)
data = resp.json['data']
assert data['id'] == 'foo-bar'
assert data['slug'] == 'foo-bar'
assert data['text'] == 'Foo bar'
assert data['kind'] == 'events'
assert data['api']['datetimes_url'] == 'http://testserver/api/agenda/foo-bar/datetimes/'
# unknown
app.get('/api/agenda/whatever/', status=404)
def test_agenda_api_date_range(app, some_data):
# test range limitation
agenda2 = Agenda.objects.get(slug='foo-bar-2')
base_date = agenda2.event_set.last().start_datetime.date()
base_date = base_date + datetime.timedelta(days=1)
for idx in range(7, 10):
if idx == 7:
day_events = ['9:00', '10:00', '11:00']
elif idx == 8:
day_events = ['13:00', '14:00']
else:
day_events = ['8:00']
day = base_date + datetime.timedelta(days=idx)
for event in day_events:
event_dt = datetime.datetime.combine(day, datetime.datetime.strptime(event, '%H:%M').time())
Event.objects.create(agenda=agenda2, start_datetime=make_aware(event_dt), places=2)
params = {'date_start': base_date.isoformat()}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
assert len(resp.json['data']) == 6
date_end = base_date + datetime.timedelta(days=7)
params = {'date_end': date_end.isoformat()}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
assert len(resp.json['data']) == 2
assert resp.json['data'][0]['datetime'] == '2017-05-21 20:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-22 20:00:00'
params = {
'date_start': base_date + datetime.timedelta(days=8),
'date_end': base_date + datetime.timedelta(days=10),
}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['datetime'] == '2017-05-31 13:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-06-01 08:00:00'
# with minimal booking delay changed
agenda2.minimal_booking_delay = 3
agenda2.save()
params = {'date_start': '2017-05-21'}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
assert len(resp.json['data']) == 6
assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-06-01 08:00:00'
# with maximal booking delay changed
agenda2.maximal_booking_delay = 11
agenda2.save()
params = {'date_end': '2017-06-01'}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-30 11:00:00'
def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda_id = meetings_agenda.slug
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
# add booking of another meeting type
meeting_type2 = MeetingType.objects.create(agenda=meetings_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_id, event_id))
cancel_url = resp.json['api']['cancel_url']
# add a second desk
time_period = meetings_agenda.desk_set.first().timeperiod_set.first()
desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_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_id, 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_id, 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)
# capture number of queries made for datetime endpoint with few bookings
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
queries_count_datetime1 = len(ctx.captured_queries)
assert len(resp2.json['data']) == len([x for x in resp.json['data'] if not x['disabled']])
# capture number of queries made for fillslot endpoint with few bookings
with CaptureQueriesContext(connection) as ctx:
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
queries_count_fillslot1 = len(ctx.captured_queries)
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_id, 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_id, 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 idx, event_data in enumerate(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) == queries_count_fillslot1
with CaptureQueriesContext(connection) as ctx:
app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
assert queries_count_datetime1 == len(ctx.captured_queries)
def test_agenda_meeting_api_fillslots_multiple_desks(app, meetings_agenda, user):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda_id = meetings_agenda.slug
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
# add a second desk, same timeperiods
time_period = meetings_agenda.desk_set.first().timeperiod_set.first()
desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_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)
slots = [x['id'] for x in resp.json['data'][:3]]
def get_free_places():
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
return len([x for x in resp.json['data'] if not x['disabled']])
start_free_places = get_free_places()
# booking 3 slots on desk 1
fillslots_url = '/api/agenda/%s/fillslots/' % agenda_id
resp = app.post(fillslots_url, params={'slots': slots})
assert resp.json['err'] == 0
desk1 = resp.json['desk']['slug']
cancel_url = resp.json['api']['cancel_url']
assert get_free_places() == start_free_places
# booking same slots again, will be on desk 2
resp = app.post(fillslots_url, params={'slots': slots})
assert resp.json['err'] == 0
assert resp.json['desk']['slug'] != desk2
# 3 places are disabled in datetimes list
assert get_free_places() == start_free_places - len(slots)
# try booking again: no desk available
resp = app.post(fillslots_url, params={'slots': 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'
assert get_free_places() == start_free_places - len(slots)
# cancel desk 1 booking
resp = app.post(cancel_url)
assert resp.json['err'] == 0
# all places are free again
assert get_free_places() == start_free_places
# booking a single slot (must be on desk 1)
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, slots[1]))
assert resp.json['err'] == 0
assert resp.json['desk']['slug'] == desk1
cancel_url = resp.json['api']['cancel_url']
assert get_free_places() == start_free_places - 1
# try booking the 3 slots again: no desk available, one slot is not fully available
resp = app.post(fillslots_url, params={'slots': 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'
# cancel last signel slot booking, desk1 will be free
resp = app.post(cancel_url)
assert resp.json['err'] == 0
assert get_free_places() == start_free_places
# booking again is ok, on desk 1
resp = app.post(fillslots_url, params={'slots': slots})
assert resp.json['err'] == 0
assert resp.json['desk']['slug'] == desk1
assert get_free_places() == start_free_places - len(slots)
def test_agenda_meeting_same_day(app, meetings_agenda, 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']
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):
time_period = 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
# 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
def test_agenda_meeting_gcd_durations(app, meetings_agenda, user):
meetings_agenda.maximal_booking_delay = 8
meetings_agenda.save()
time_period = TimePeriod.objects.get(end_time=datetime.time(12, 0))
time_period.end_time = datetime.time(13, 0)
time_period.save()
meeting_type_30 = MeetingType.objects.get(duration=30)
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type_30.id)
assert len(resp.json['data']) == 20
meeting_type_20 = MeetingType(agenda=meetings_agenda, label='Lorem', duration=20)
meeting_type_20.save()
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'
)
def test_agenda_meeting_gcd_durations_and_exceptions(app, meetings_agenda, user):
meetings_agenda.maximal_booking_delay = 2
meetings_agenda.save()
MeetingType.objects.all().delete()
meeting_type_20 = MeetingType(agenda=meetings_agenda, label='Blah 20', duration=20)
meeting_type_20.save()
meeting_type_40 = MeetingType(agenda=meetings_agenda, label='Blah 40', duration=40)
meeting_type_40.save()
desk = meetings_agenda.desk_set.all()[0]
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_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_agenda_detail_routing(app, meetings_agenda):
api_url = '/api/agenda/%s/' % meetings_agenda.slug
resp = app.get(api_url)
assert type(resp.json['data']) is dict
# check it doesn't get confused with an agenda with "agenda" in its slug
agenda = Agenda(
label=u'Foo bar Agenda', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56
)
agenda.save()
api_url = '/api/agenda/%s/' % agenda.slug
resp = app.get(api_url)
assert type(resp.json['data']) is dict
def test_virtual_agenda_detail(app, virtual_meetings_agenda):
resp = app.get('/api/agenda/%s/' % virtual_meetings_agenda.slug)
assert resp.json == {
'data': {
'text': 'Virtual Agenda',
'id': 'virtual-agenda',
'slug': 'virtual-agenda',
'minimal_booking_delay': None,
'maximal_booking_delay': None,
'kind': 'virtual',
'api': {
'meetings_url': 'http://testserver/api/agenda/%s/meetings/' % virtual_meetings_agenda.slug,
'desks_url': 'http://testserver/api/agenda/%s/desks/' % virtual_meetings_agenda.slug,
'fillslots_url': 'http://testserver/api/agenda/%s/fillslots/' % virtual_meetings_agenda.slug,
},
},
}
def test_virtual_agendas_meetingtypes_api(app):
virt_agenda = Agenda.objects.create(label=u'Virtual agenda', kind='virtual')
# No meetings because no real agenda
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug)
assert resp.json == {'data': []}
# One real agenda : every meetings exposed
foo_agenda = Agenda.objects.create(label=u'Foo', kind='meetings')
MeetingType.objects.create(agenda=foo_agenda, label='Meeting1', duration=30)
MeetingType.objects.create(agenda=foo_agenda, label='Meeting2', duration=15)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug)
assert resp.json == {
'data': [
{
'text': 'Meeting1',
'id': 'meeting1',
'duration': 30,
'api': {
'datetimes_url': 'http://testserver/api/agenda/virtual-agenda/meetings/meeting1/datetimes/',
},
},
{
'text': 'Meeting2',
'id': 'meeting2',
'duration': 15,
'api': {
'datetimes_url': 'http://testserver/api/agenda/virtual-agenda/meetings/meeting2/datetimes/',
},
},
]
}
# Several real agendas
bar_agenda = Agenda.objects.create(label=u'Bar', kind='meetings')
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Bar', duration=30)
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
# Bar agenda has no meeting type: no meetings exposed
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug)
assert resp.json == {'data': []}
# Bar agenda has a meetings wih different label, slug, duration: no meetings exposed
mt = MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type Bar', duration=15)
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug)
assert resp.json == {'data': []}
# Bar agenda has a meetings wih same label, but different slug and duration: no meetings exposed
mt.label = 'Meeting1'
mt.save()
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug)
assert resp.json == {'data': []}
# Bar agenda has a meetings wih same label and slug, but different duration: no meetings exposed
mt.slug = 'meeting1'
mt.save()
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug)
assert resp.json == {'data': []}
# Bar agenda has a meetings wih same label, slug and duration: only this meeting exposed
mt.duration = 30
mt.save()
resp = app.get('/api/agenda/%s/meetings/' % virt_agenda.slug)
assert resp.json == {
'data': [
{
'text': 'Meeting1',
'id': 'meeting1',
'duration': 30,
'api': {
'datetimes_url': 'http://testserver/api/agenda/virtual-agenda/meetings/meeting1/datetimes/',
},
},
]
}
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
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 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_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 = 4
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, time_zone, 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=5
)
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 = 9 # 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 = 5
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)
resp = app.get(api_url)
assert len(resp.json['data']) == 12
# 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
def test_virtual_agendas_meetings_booking(app, mock_now, 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=foo_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']
)
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']
)
# 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'
def test_virtual_agendas_meetings_booking_default_policy(app, mock_now, 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