5427 lines
221 KiB
Python
5427 lines
221 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import datetime
|
|
import mock
|
|
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,
|
|
Booking,
|
|
Category,
|
|
Desk,
|
|
Event,
|
|
MeetingType,
|
|
Resource,
|
|
TimePeriod,
|
|
TimePeriodException,
|
|
UnavailabilityCalendar,
|
|
VirtualMember,
|
|
BookingColor,
|
|
)
|
|
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(year=2017, month=5, day=20, hour=1, minute=12),
|
|
datetime.datetime(year=2017, month=5, day=20, hour=11, minute=42),
|
|
datetime.datetime(year=2017, month=5, day=20, hour=23, minute=17),
|
|
]
|
|
)
|
|
def mock_now(request, freezer, time_zone):
|
|
aware_datetime = make_aware(request.param)
|
|
freezer.move_to(aware_datetime)
|
|
return aware_datetime
|
|
|
|
|
|
@pytest.fixture
|
|
def some_data(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(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):
|
|
category_a = Category.objects.create(label='Category A')
|
|
category_b = Category.objects.create(label='Category B')
|
|
event_agenda = Agenda.objects.create(label='Foo bar', category=category_a)
|
|
Agenda.objects.create(label='Foo bar 2', category=category_a)
|
|
meetings_agenda1 = Agenda.objects.create(label='Foo bar Meeting', kind='meetings', category=category_b)
|
|
Agenda.objects.create(label='Foo bar Meeting 2', kind='meetings')
|
|
resource1 = Resource.objects.create(label='Resource 1', description='Foo bar Resource 1')
|
|
resource2 = Resource.objects.create(label='Resource 2', description='Foo bar Resource 2')
|
|
Resource.objects.create(label='Resource 3')
|
|
meetings_agenda1.resources.add(resource1, resource2)
|
|
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': 'foo-bar',
|
|
'slug': 'foo-bar',
|
|
'kind': 'events',
|
|
'minimal_booking_delay': 1,
|
|
'maximal_booking_delay': 56,
|
|
'api': {
|
|
'datetimes_url': 'http://testserver/api/agenda/foo-bar/datetimes/',
|
|
'fillslots_url': 'http://testserver/api/agenda/foo-bar/fillslots/',
|
|
},
|
|
},
|
|
{
|
|
'text': 'Foo bar 2',
|
|
'id': 'foo-bar-2',
|
|
'kind': 'events',
|
|
'slug': 'foo-bar-2',
|
|
'minimal_booking_delay': 1,
|
|
'maximal_booking_delay': 56,
|
|
'api': {
|
|
'datetimes_url': 'http://testserver/api/agenda/foo-bar-2/datetimes/',
|
|
'fillslots_url': 'http://testserver/api/agenda/foo-bar-2/fillslots/',
|
|
},
|
|
},
|
|
{
|
|
'text': 'Foo bar Meeting',
|
|
'id': 'foo-bar-meeting',
|
|
'slug': 'foo-bar-meeting',
|
|
'minimal_booking_delay': 1,
|
|
'maximal_booking_delay': 56,
|
|
'kind': 'meetings',
|
|
'resources': [
|
|
{'id': 'resource-1', 'text': 'Resource 1', 'description': 'Foo bar Resource 1'},
|
|
{'id': 'resource-2', 'text': 'Resource 2', 'description': 'Foo bar Resource 2'},
|
|
],
|
|
'api': {
|
|
'meetings_url': 'http://testserver/api/agenda/foo-bar-meeting/meetings/',
|
|
'desks_url': 'http://testserver/api/agenda/foo-bar-meeting/desks/',
|
|
'fillslots_url': 'http://testserver/api/agenda/foo-bar-meeting/fillslots/',
|
|
},
|
|
},
|
|
{
|
|
'text': 'Foo bar Meeting 2',
|
|
'id': 'foo-bar-meeting-2',
|
|
'slug': 'foo-bar-meeting-2',
|
|
'minimal_booking_delay': 1,
|
|
'maximal_booking_delay': 56,
|
|
'kind': 'meetings',
|
|
'resources': [],
|
|
'api': {
|
|
'meetings_url': 'http://testserver/api/agenda/foo-bar-meeting-2/meetings/',
|
|
'desks_url': 'http://testserver/api/agenda/foo-bar-meeting-2/desks/',
|
|
'fillslots_url': 'http://testserver/api/agenda/foo-bar-meeting-2/fillslots/',
|
|
},
|
|
},
|
|
{
|
|
'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/virtual-agenda/meetings/',
|
|
'desks_url': 'http://testserver/api/agenda/virtual-agenda/desks/',
|
|
'fillslots_url': 'http://testserver/api/agenda/virtual-agenda/fillslots/',
|
|
},
|
|
},
|
|
]
|
|
}
|
|
|
|
resp = app.get('/api/agenda/', params={'q': 'foo'})
|
|
assert len(resp.json['data']) == 4
|
|
resp = app.get('/api/agenda/', params={'q': 'MEET'})
|
|
assert len(resp.json['data']) == 2
|
|
resp = app.get('/api/agenda/', params={'q': ''})
|
|
assert len(resp.json['data']) == 0
|
|
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
resp = app.get('/api/agenda/')
|
|
assert len(ctx.captured_queries) == 3
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
resp = app.get('/api/agenda/', params={'q': 'MEET'})
|
|
assert len(ctx.captured_queries) == 2
|
|
|
|
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
|
assert len(resp.json['data']) == 0
|
|
|
|
resp = app.get('/api/agenda/', params={'category': ''})
|
|
assert len(resp.json['data']) == 5
|
|
resp = app.get('/api/agenda/', params={'category': '__none__'})
|
|
assert len(resp.json['data']) == 2
|
|
resp = app.get('/api/agenda/', params={'category': 'category-a'})
|
|
assert len(resp.json['data']) == 2
|
|
resp = app.get('/api/agenda/', params={'category': 'category-b'})
|
|
assert len(resp.json['data']) == 1
|
|
resp = app.get('/api/agenda/', params={'category': 'unknown'})
|
|
assert len(resp.json['data']) == 0
|
|
|
|
event1 = Event.objects.create(
|
|
start_datetime=(localtime() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
|
places=20,
|
|
agenda=event_agenda,
|
|
)
|
|
event2 = Event.objects.create(
|
|
start_datetime=(localtime() + datetime.timedelta(days=10)).replace(hour=10, minute=0),
|
|
places=20,
|
|
agenda=event_agenda,
|
|
)
|
|
event3 = Event.objects.create(
|
|
start_datetime=(localtime() + datetime.timedelta(days=15)).replace(hour=10, minute=0),
|
|
places=20,
|
|
agenda=event_agenda,
|
|
)
|
|
|
|
# all events are free
|
|
resp = app.get('/api/agenda/', params={'with_open_events': 'true'})
|
|
assert len(resp.json['data']) == 1
|
|
|
|
# one event is full
|
|
Event.objects.filter(pk=event1.pk).update(full=True)
|
|
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
|
assert len(resp.json['data']) == 1
|
|
|
|
# all events are full
|
|
Event.objects.update(full=True)
|
|
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
|
assert len(resp.json['data']) == 0
|
|
|
|
# event1 is not full but too soon
|
|
Event.objects.filter(pk=event1.pk).update(full=False)
|
|
event_agenda.minimal_booking_delay = 10
|
|
event_agenda.save()
|
|
assert list(event_agenda.get_open_events()) == [event2, event3]
|
|
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
|
assert len(resp.json['data']) == 0
|
|
|
|
# event3 is not full but too late
|
|
Event.objects.filter(pk=event3.pk).update(full=False)
|
|
event_agenda.maximal_booking_delay = 12
|
|
event_agenda.save()
|
|
assert list(event_agenda.get_open_events()) == [event2]
|
|
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
|
assert len(resp.json['data']) == 0
|
|
|
|
# events are not full but not published
|
|
Event.objects.update(full=False)
|
|
event_agenda.event_set.update(publication_date=now().date() + datetime.timedelta(days=20))
|
|
assert list(event_agenda.get_open_events()) == []
|
|
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
|
assert len(resp.json['data']) == 0
|
|
|
|
# event recurrences are available
|
|
event = Event.objects.create(
|
|
start_datetime=now(),
|
|
places=10,
|
|
agenda=event_agenda,
|
|
repeat='daily',
|
|
)
|
|
assert len(event_agenda.get_open_events()) == 2
|
|
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
|
assert len(resp.json['data']) == 1
|
|
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
|
|
assert len(ctx.captured_queries) == 4
|
|
|
|
|
|
def test_agendas_meetingtypes_api(app, some_data, meetings_agenda):
|
|
resp = app.get('/api/agenda/%s/meetings/' % meetings_agenda.slug)
|
|
expected_resp = {
|
|
'data': [
|
|
{
|
|
'text': 'Blah',
|
|
'id': 'blah',
|
|
'duration': 30,
|
|
'api': {
|
|
'datetimes_url': 'http://testserver/api/agenda/foo-bar-meeting/meetings/blah/datetimes/',
|
|
},
|
|
}
|
|
]
|
|
}
|
|
assert resp.json == expected_resp
|
|
|
|
# deleted meeting type does not show up
|
|
MeetingType.objects.create(agenda=meetings_agenda, slug='deleted-meeting-type', duration=43, deleted=True)
|
|
resp = app.get('/api/agenda/%s/meetings/' % meetings_agenda.slug)
|
|
assert resp.json == expected_resp
|
|
|
|
# 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_meetingtype_api(app, some_data, meetings_agenda):
|
|
resp = app.get('/api/agenda/%s/meetings/blah/' % 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)
|
|
resp = app.get('/api/agenda/%s/meetings/xxxx/' % agenda1.slug, 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(slug=event['id']).in_bookable_period()
|
|
for event in agenda.event_set.all():
|
|
if not event.in_bookable_period():
|
|
assert event.slug not 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
|
|
|
|
agenda.event_set.update(publication_date=localtime(now()).date() + datetime.timedelta(days=1))
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
assert len(resp.json['data']) == 0
|
|
check_bookability(resp.json['data'])
|
|
|
|
agenda.event_set.update(publication_date=localtime(now()).date())
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
assert len(resp.json['data']) == 2
|
|
check_bookability(resp.json['data'])
|
|
|
|
# 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_urls(app):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
|
|
event = Event.objects.create(
|
|
slug='event-slug',
|
|
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
for datum in resp.json['data']:
|
|
assert urlparse.urlparse(datum['api']['bookings_url']).path == '/api/agenda/%s/bookings/%s/' % (
|
|
agenda.slug,
|
|
event.slug,
|
|
)
|
|
assert urlparse.urlparse(datum['api']['fillslot_url']).path == '/api/agenda/%s/fillslot/%s/' % (
|
|
agenda.slug,
|
|
event.slug,
|
|
)
|
|
assert urlparse.urlparse(datum['api']['status_url']).path == '/api/agenda/%s/status/%s/' % (
|
|
agenda.slug,
|
|
event.slug,
|
|
)
|
|
|
|
|
|
def test_datetimes_api_meetings_agenda(app, meetings_agenda):
|
|
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
|
|
|
|
resp = app.get('/api/agenda/%s/meetings/xxx/datetimes/' % meeting_type.agenda.slug, status=404)
|
|
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 144
|
|
assert resp.json == app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id).json
|
|
|
|
meetings_agenda.minimal_booking_delay = 7
|
|
meetings_agenda.maximal_booking_delay = 28
|
|
meetings_agenda.save()
|
|
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp.json['data']) == 54
|
|
|
|
meetings_agenda.minimal_booking_delay = 1
|
|
meetings_agenda.maximal_booking_delay = 56
|
|
meetings_agenda.save()
|
|
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp.json['data']) == 144
|
|
|
|
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
|
|
ev = Event(
|
|
agenda=meetings_agenda,
|
|
meeting_type=meeting_type,
|
|
places=1,
|
|
full=False,
|
|
start_datetime=make_aware(dt),
|
|
desk=Desk.objects.first(),
|
|
)
|
|
ev.save()
|
|
booking = Booking(event=ev)
|
|
booking.save()
|
|
|
|
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp2.json['data']) == 144
|
|
assert resp.json['data'][0] == resp2.json['data'][0]
|
|
assert resp.json['data'][1] == resp2.json['data'][1]
|
|
assert resp.json['data'][2] != resp2.json['data'][2]
|
|
assert resp.json['data'][2]['disabled'] is False
|
|
assert resp2.json['data'][2]['disabled'] is True
|
|
assert resp.json['data'][3] == resp2.json['data'][3]
|
|
|
|
# test with a timeperiod overlapping current moment, it should get one
|
|
# datetime for the current timeperiod + two from the next week.
|
|
if localtime(now()).time().hour == 23:
|
|
# skip this part of the test as it would require support for events
|
|
# crossing midnight
|
|
return
|
|
default_desk, _ = Desk.objects.get_or_create(agenda=meetings_agenda, slug='desk-1')
|
|
TimePeriod.objects.filter(desk=default_desk).delete()
|
|
start_time = localtime(now()) - datetime.timedelta(minutes=10)
|
|
time_period = TimePeriod(
|
|
weekday=localtime(now()).weekday(),
|
|
start_time=start_time,
|
|
end_time=start_time + datetime.timedelta(hours=1),
|
|
desk=default_desk,
|
|
)
|
|
time_period.save()
|
|
meetings_agenda.minimal_booking_delay = 0
|
|
meetings_agenda.maximal_booking_delay = 10
|
|
meetings_agenda.save()
|
|
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp.json['data']) == 3
|
|
|
|
|
|
@pytest.mark.freeze_time('2020-10-24') # tomorrow is time change
|
|
def test_datetimes_api_meetings_agenda_time_change(app):
|
|
agenda = Agenda.objects.create(
|
|
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=3
|
|
)
|
|
desk = Desk.objects.create(agenda=agenda, slug='desk')
|
|
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo', duration=60)
|
|
for weekday in [0, 5, 6]: # monday, saturday, sunday
|
|
TimePeriod.objects.create(
|
|
weekday=weekday,
|
|
start_time=datetime.time(9, 0),
|
|
end_time=datetime.time(10, 00),
|
|
desk=desk,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=weekday,
|
|
start_time=datetime.time(14, 0),
|
|
end_time=datetime.time(15, 00),
|
|
desk=desk,
|
|
)
|
|
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
|
|
resp = app.get(api_url)
|
|
assert resp.json['data'] == [
|
|
{
|
|
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-24-0900/'},
|
|
'datetime': '2020-10-24 09:00:00',
|
|
'disabled': False,
|
|
'id': 'foo:2020-10-24-0900',
|
|
'text': 'Oct. 24, 2020, 9 a.m.',
|
|
},
|
|
{
|
|
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-24-1400/'},
|
|
'datetime': '2020-10-24 14:00:00',
|
|
'disabled': False,
|
|
'id': 'foo:2020-10-24-1400',
|
|
'text': 'Oct. 24, 2020, 2 p.m.',
|
|
},
|
|
{
|
|
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-25-0900/'},
|
|
'datetime': '2020-10-25 09:00:00',
|
|
'disabled': False,
|
|
'id': 'foo:2020-10-25-0900',
|
|
'text': 'Oct. 25, 2020, 9 a.m.',
|
|
},
|
|
{
|
|
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-25-1400/'},
|
|
'datetime': '2020-10-25 14:00:00',
|
|
'disabled': False,
|
|
'id': 'foo:2020-10-25-1400',
|
|
'text': 'Oct. 25, 2020, 2 p.m.',
|
|
},
|
|
{
|
|
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-26-0900/'},
|
|
'datetime': '2020-10-26 09:00:00',
|
|
'disabled': False,
|
|
'id': 'foo:2020-10-26-0900',
|
|
'text': 'Oct. 26, 2020, 9 a.m.',
|
|
},
|
|
{
|
|
'api': {'fillslot_url': 'http://testserver/api/agenda/agenda/fillslot/foo:2020-10-26-1400/'},
|
|
'datetime': '2020-10-26 14:00:00',
|
|
'disabled': False,
|
|
'id': 'foo:2020-10-26-1400',
|
|
'text': 'Oct. 26, 2020, 2 p.m.',
|
|
},
|
|
]
|
|
|
|
|
|
def test_datetimes_api_meetings_agenda_with_resources(app):
|
|
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
|
|
tomorrow_str = tomorrow.isoformat()
|
|
agenda = Agenda.objects.create(
|
|
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
|
|
)
|
|
other_agenda = Agenda.objects.create(label='Other', kind='meetings')
|
|
other_desk = Desk.objects.create(agenda=other_agenda, slug='desk-1')
|
|
other_meeting_type = MeetingType.objects.create(agenda=other_agenda, slug='foo-bar', duration=90)
|
|
resource1 = Resource.objects.create(label='Resource 1')
|
|
resource2 = Resource.objects.create(label='Resource 2')
|
|
resource3 = Resource.objects.create(label='Resource 3')
|
|
agenda.resources.add(resource1, resource2, resource3)
|
|
desk = Desk.objects.create(agenda=agenda, slug='desk-1')
|
|
desk2 = Desk.objects.create(agenda=agenda, slug='desk-2')
|
|
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
|
|
TimePeriod.objects.create(
|
|
weekday=tomorrow.weekday(),
|
|
start_time=datetime.time(9, 0),
|
|
end_time=datetime.time(17, 00),
|
|
desk=desk,
|
|
)
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda.slug, meeting_type.slug)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
|
|
|
|
# all resources are free
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s,%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource1.slug,
|
|
resource2.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
|
|
for slot in resp.json['data']:
|
|
assert slot['api']['fillslot_url'].endswith('/?resources=%s,%s' % (resource1.slug, resource2.slug))
|
|
|
|
# resource 1 is not available from 10h to 11h30 in another agenda
|
|
dt = make_aware(datetime.datetime.combine(tomorrow, datetime.time(10, 0)))
|
|
event1 = Event.objects.create(
|
|
agenda=other_agenda,
|
|
meeting_type=other_meeting_type,
|
|
places=1,
|
|
full=False,
|
|
start_datetime=dt,
|
|
desk=other_desk,
|
|
)
|
|
event1.resources.add(resource1)
|
|
booking_r1 = Booking.objects.create(event=event1)
|
|
# resource 3 is not available from 9h to 10h in this agenda (but desk-1 is free)
|
|
dt = make_aware(datetime.datetime.combine(tomorrow, datetime.time(9, 0)))
|
|
event2 = Event.objects.create(
|
|
agenda=agenda,
|
|
meeting_type=meeting_type,
|
|
places=1,
|
|
full=False,
|
|
start_datetime=dt,
|
|
desk=desk2,
|
|
)
|
|
event2.resources.add(resource3)
|
|
Booking.objects.create(event=event2)
|
|
dt = make_aware(datetime.datetime.combine(tomorrow, datetime.time(9, 30)))
|
|
event3 = Event.objects.create(
|
|
agenda=agenda,
|
|
meeting_type=meeting_type,
|
|
places=1,
|
|
full=False,
|
|
start_datetime=dt,
|
|
desk=desk2,
|
|
)
|
|
event3.resources.add(resource3)
|
|
Booking.objects.create(event=event3)
|
|
|
|
# check for resource 1 and resource 2: not available from 10H to 11H30
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s,%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource1.slug,
|
|
resource2.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
|
'%s 10:00:00' % tomorrow_str,
|
|
'%s 10:30:00' % tomorrow_str,
|
|
'%s 11:00:00' % tomorrow_str,
|
|
]
|
|
|
|
# check for resource 2 only ? it's free
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource2.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
|
|
for slot in resp.json['data']:
|
|
assert slot['api']['fillslot_url'].endswith('/?resources=%s' % resource2.slug)
|
|
|
|
# check for resource 3: not available from 9H to 10H
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource3.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
|
'%s 09:00:00' % tomorrow_str,
|
|
'%s 09:30:00' % tomorrow_str,
|
|
]
|
|
|
|
# check for resource 1 and resource 3
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s,%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource1.slug,
|
|
resource3.slug,
|
|
)
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
resp = app.get(api_url)
|
|
assert len(ctx.captured_queries) == 10
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
|
'%s 09:00:00' % tomorrow_str,
|
|
'%s 09:30:00' % tomorrow_str,
|
|
'%s 10:00:00' % tomorrow_str,
|
|
'%s 10:30:00' % tomorrow_str,
|
|
'%s 11:00:00' % tomorrow_str,
|
|
]
|
|
|
|
# no resources to book
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == []
|
|
|
|
# event 3 is booked without resource, only one desk
|
|
event3.desk = desk
|
|
event3.save()
|
|
event3.resources.clear()
|
|
desk2.delete()
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
|
'%s 09:30:00' % tomorrow_str
|
|
]
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource1.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
|
'%s 09:30:00' % tomorrow_str,
|
|
'%s 10:00:00' % tomorrow_str,
|
|
'%s 10:30:00' % tomorrow_str,
|
|
'%s 11:00:00' % tomorrow_str,
|
|
]
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource3.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
|
'%s 09:30:00' % tomorrow_str
|
|
]
|
|
|
|
# resource is unknown or not valid for this agenda
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=foobarbaz' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
)
|
|
resp = app.get(api_url, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid resource: foobarbaz' # legacy
|
|
assert resp.json['err_class'] == 'invalid resource: foobarbaz'
|
|
assert resp.json['err_desc'] == 'invalid resource: foobarbaz'
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s,foobarbaz' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource3.slug,
|
|
)
|
|
resp = app.get(api_url, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid resource: foobarbaz' # legacy
|
|
assert resp.json['err_class'] == 'invalid resource: foobarbaz'
|
|
assert resp.json['err_desc'] == 'invalid resource: foobarbaz'
|
|
agenda.resources.remove(resource3)
|
|
resp = app.get(api_url, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid resource: foobarbaz, resource-3' # legacy
|
|
assert resp.json['err_class'] == 'invalid resource: foobarbaz, resource-3'
|
|
assert resp.json['err_desc'] == 'invalid resource: foobarbaz, resource-3'
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource3.slug,
|
|
)
|
|
resp = app.get(api_url, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid resource: resource-3' # legacy
|
|
assert resp.json['err_class'] == 'invalid resource: resource-3'
|
|
assert resp.json['err_desc'] == 'invalid resource: resource-3'
|
|
|
|
# if booking is canceled the resource is free
|
|
booking_r1.cancel()
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/?resources=%s' % (
|
|
agenda.slug,
|
|
meeting_type.slug,
|
|
resource1.slug,
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 32
|
|
assert [s['datetime'] for s in resp.json['data'] if s['disabled'] is True] == [
|
|
'%s 09:30:00' % tomorrow_str
|
|
]
|
|
|
|
|
|
def test_datetimes_api_meetings_agenda_short_time_periods(app, meetings_agenda, user):
|
|
meetings_agenda.minimal_booking_delay = 0
|
|
meetings_agenda.maximal_booking_delay = 10
|
|
meetings_agenda.save()
|
|
|
|
default_desk, _ = Desk.objects.get_or_create(agenda=meetings_agenda, slug='desk-1')
|
|
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
|
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.slug][0]['api'][
|
|
'fillslot_url'
|
|
]
|
|
assert urlparse.urlparse(event_fillslot_url).path == '/api/agenda/%s/fillslot/%s/' % (
|
|
agenda.slug,
|
|
event.slug,
|
|
)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, event.id))
|
|
Booking.objects.get(id=resp.json['booking_id'])
|
|
assert resp.json['datetime'] == localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
|
|
assert 'booking_url' in resp.json['api']
|
|
assert 'accept_url' in resp.json['api']
|
|
assert 'suspend_url' in resp.json['api']
|
|
assert 'cancel_url' in resp.json['api']
|
|
assert 'ics_url' in resp.json['api']
|
|
assert 'anonymize_url' in resp.json['api']
|
|
assert urlparse.urlparse(resp.json['api']['booking_url']).netloc
|
|
assert urlparse.urlparse(resp.json['api']['accept_url']).netloc
|
|
assert urlparse.urlparse(resp.json['api']['suspend_url']).netloc
|
|
assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
|
|
assert urlparse.urlparse(resp.json['api']['ics_url']).netloc
|
|
assert urlparse.urlparse(resp.json['api']['anonymize_url']).netloc
|
|
assert Booking.objects.count() == 1
|
|
|
|
# access by slug
|
|
event.slug = 'bar'
|
|
event.save()
|
|
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.id, event.slug))
|
|
assert Booking.objects.count() == 2
|
|
assert Booking.objects.filter(event__agenda=agenda).count() == 2
|
|
|
|
# test with additional data
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
|
|
params={
|
|
'label': 'foo',
|
|
'user_name': 'bar',
|
|
'backoffice_url': 'http://example.net/',
|
|
'cancel_callback_url': 'http://example.net/jump/trigger/',
|
|
'user_email': 'bar@bar.com',
|
|
'user_phone_number': '+33123456789',
|
|
'form_url': 'http://example.net/',
|
|
},
|
|
)
|
|
booking = Booking.objects.get(id=resp.json['booking_id'])
|
|
assert booking.label == 'foo'
|
|
assert booking.user_name == 'bar'
|
|
assert booking.backoffice_url == 'http://example.net/'
|
|
assert booking.cancel_callback_url == 'http://example.net/jump/trigger/'
|
|
assert booking.user_email == 'bar@bar.com'
|
|
assert booking.user_phone_number == '+33123456789'
|
|
assert booking.form_url == 'http://example.net/'
|
|
|
|
# blank data are OK
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
|
|
params={'label': '', 'user_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'}
|
|
|
|
# anonymize
|
|
booking_id = resp.json['booking_id']
|
|
resp = app.post(resp.json['api']['anonymize_url'])
|
|
assert Booking.objects.get(id=booking_id).anonymization_datetime is not None
|
|
|
|
# test invalid data are refused
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event.id),
|
|
params={'user_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 'DTEND:' 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()]
|
|
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_slugs = [x['id'] for x in resp_datetimes.json['data']]
|
|
assert api_event_slugs == events_slugs
|
|
|
|
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 'booking_url' in resp.json['api']
|
|
assert 'accept_url' in resp.json['api']
|
|
assert 'suspend_url' in resp.json['api']
|
|
assert 'cancel_url' in resp.json['api']
|
|
assert urlparse.urlparse(resp.json['api']['booking_url']).netloc
|
|
assert urlparse.urlparse(resp.json['api']['accept_url']).netloc
|
|
assert urlparse.urlparse(resp.json['api']['suspend_url']).netloc
|
|
assert urlparse.urlparse(resp.json['api']['cancel_url']).netloc
|
|
assert 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_external_id': 'some_external_id',
|
|
'user_name': 'bar',
|
|
'user_display_label': 'foo',
|
|
'backoffice_url': 'http://example.net/',
|
|
},
|
|
)
|
|
booking_id = resp.json['booking_id']
|
|
booking = Booking.objects.get(pk=booking_id)
|
|
assert booking.label == 'foo'
|
|
assert booking.user_external_id == 'some_external_id'
|
|
assert booking.user_name == 'bar'
|
|
assert booking.user_display_label == 'foo'
|
|
assert booking.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)
|
|
|
|
# check bookable period
|
|
with mock.patch('chrono.agendas.models.Event.in_bookable_period') as in_bookable_period:
|
|
in_bookable_period.return_value = True
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslots/' % agenda.id,
|
|
params={'slots': events_ids, 'label': 'l', 'user_name': 'u', 'backoffice_url': '', 'foo': 'bar'},
|
|
)
|
|
assert resp.json['err'] == 0
|
|
in_bookable_period.return_value = False
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslots/' % agenda.id,
|
|
params={'slots': events_ids, 'label': 'l', 'user_name': 'u', 'backoffice_url': '', 'foo': 'bar'},
|
|
)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'event not bookable' # legacy
|
|
assert resp.json['err_class'] == 'event not bookable'
|
|
assert resp.json['err_desc'] == 'event not bookable'
|
|
|
|
|
|
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
|
|
|
|
start = now() + datetime.timedelta(days=2)
|
|
Event.objects.create(label='Long Slug', slug='a' * 100, start_datetime=start, places=2, agenda=agenda)
|
|
Event.objects.create(label='Long Slug', slug='b' * 100, start_datetime=start, places=2, agenda=agenda)
|
|
events_ids = [x.id for x in Event.objects.filter(label='Long Slug')]
|
|
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})
|
|
assert Booking.objects.count() == 5
|
|
|
|
|
|
def test_booking_api_meeting(app, meetings_agenda, user):
|
|
agenda_id = meetings_agenda.slug
|
|
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
|
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
event_id = resp.json['data'][2]['id']
|
|
assert urlparse.urlparse(
|
|
resp.json['data'][2]['api']['fillslot_url']
|
|
).path == '/api/agenda/%s/fillslot/%s/' % (meetings_agenda.slug, event_id)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# verify malformed event_pk returns a 400
|
|
resp_booking = app.post('/api/agenda/%s/fillslot/None/' % agenda_id, status=400)
|
|
assert resp_booking.json['err'] == 1
|
|
|
|
# make a booking
|
|
resp_booking = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
|
|
assert Booking.objects.count() == 1
|
|
assert resp_booking.json['datetime'] == localtime(Booking.objects.all()[0].event.start_datetime).strftime(
|
|
'%Y-%m-%d %H:%M:%S'
|
|
)
|
|
assert resp_booking.json['end_datetime'] == localtime(
|
|
Booking.objects.all()[0].event.end_datetime
|
|
).strftime('%Y-%m-%d %H:%M:%S')
|
|
assert resp_booking.json['duration'] == 30
|
|
|
|
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp.json['data']) == len([x for x in resp2.json['data'] if not x.get('disabled')]) + 1
|
|
|
|
# try booking the same timeslot
|
|
resp2 = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
|
|
assert resp2.json['err'] == 1
|
|
assert resp2.json['reason'] == 'no more desk available' # legacy
|
|
assert resp2.json['err_class'] == 'no more desk available'
|
|
assert resp2.json['err_desc'] == 'no more desk available'
|
|
|
|
# try booking another timeslot
|
|
event_id = resp.json['data'][3]['id']
|
|
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda_id, event_id))
|
|
assert resp.json['err'] == 0
|
|
assert Booking.objects.count() == 2
|
|
|
|
|
|
def test_booking_api_meeting_colors(app, user):
|
|
agenda = Agenda.objects.create(
|
|
label='foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=56
|
|
)
|
|
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
|
|
default_desk = Desk.objects.create(agenda=agenda, label='Desk 1')
|
|
time_period = TimePeriod.objects.create(
|
|
weekday=0, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=default_desk
|
|
)
|
|
datetimes_resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
event_id = datetimes_resp.json['data'][2]['id']
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id),
|
|
params={
|
|
'use_color_for': 'Cooking',
|
|
},
|
|
)
|
|
booking = Booking.objects.get(id=resp.json['booking_id'])
|
|
assert booking.color.label == 'Cooking'
|
|
assert booking.color.index == 0
|
|
|
|
event_id = datetimes_resp.json['data'][3]['id']
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id),
|
|
params={
|
|
'use_color_for': 'Cooking',
|
|
},
|
|
)
|
|
new_booking = Booking.objects.get(id=resp.json['booking_id'])
|
|
assert new_booking.color.index == 0
|
|
|
|
event_id = datetimes_resp.json['data'][4]['id']
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id),
|
|
params={
|
|
'use_color_for': 'Swimming',
|
|
},
|
|
)
|
|
new_booking = Booking.objects.get(id=resp.json['booking_id'])
|
|
assert new_booking.color.label == 'Swimming'
|
|
assert new_booking.color.index == 1
|
|
|
|
for i in range((BookingColor.COLOR_COUNT * 2) - 2):
|
|
event_id = datetimes_resp.json['data'][i]['id']
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event_id),
|
|
params={
|
|
'use_color_for': str(i),
|
|
},
|
|
)
|
|
assert BookingColor.objects.count() == BookingColor.COLOR_COUNT * 2
|
|
assert BookingColor.objects.distinct('index').order_by().count() == BookingColor.COLOR_COUNT
|
|
|
|
|
|
def test_booking_api_meeting_with_resources(app, user):
|
|
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
|
|
tomorrow_str = tomorrow.isoformat()
|
|
agenda = Agenda.objects.create(
|
|
label='Agenda', kind='meetings', minimal_booking_delay=0, maximal_booking_delay=10
|
|
)
|
|
resource1 = Resource.objects.create(label='Resource 1')
|
|
resource2 = Resource.objects.create(label='Resource 2')
|
|
resource3 = Resource.objects.create(label='Resource 3')
|
|
agenda.resources.add(resource1, resource2, resource3)
|
|
desk = Desk.objects.create(agenda=agenda, slug='desk')
|
|
meeting_type = MeetingType.objects.create(agenda=agenda, slug='foo-bar')
|
|
TimePeriod.objects.create(
|
|
weekday=tomorrow.weekday(),
|
|
start_time=datetime.time(9, 0),
|
|
end_time=datetime.time(17, 00),
|
|
desk=desk,
|
|
)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# make a booking without resource
|
|
resp = app.post('/api/agenda/%s/fillslot/%s:%s-1000/' % (agenda.pk, meeting_type.pk, tomorrow_str))
|
|
assert resp.json['datetime'] == '%s 10:00:00' % tomorrow_str
|
|
assert resp.json['end_datetime'] == '%s 10:30:00' % tomorrow_str
|
|
assert resp.json['duration'] == 30
|
|
assert resp.json['resources'] == []
|
|
booking = Booking.objects.latest('pk')
|
|
assert list(booking.event.resources.all()) == []
|
|
|
|
# now try to book also a resource - slot not free
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s:%s-1000/?resources=%s'
|
|
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug)
|
|
)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'no more desk available' # legacy
|
|
assert resp.json['err_class'] == 'no more desk available'
|
|
assert resp.json['err_desc'] == 'no more desk available'
|
|
|
|
# slot is free
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s'
|
|
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug)
|
|
)
|
|
assert resp.json['datetime'] == '%s 09:00:00' % tomorrow_str
|
|
assert resp.json['end_datetime'] == '%s 09:30:00' % tomorrow_str
|
|
assert resp.json['duration'] == 30
|
|
assert resp.json['resources'] == [resource1.slug]
|
|
booking = Booking.objects.latest('pk')
|
|
assert list(booking.event.resources.all()) == [resource1]
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s:%s-0930/?resources=%s,%s'
|
|
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug, resource2.slug)
|
|
)
|
|
assert resp.json['datetime'] == '%s 09:30:00' % tomorrow_str
|
|
assert resp.json['end_datetime'] == '%s 10:00:00' % tomorrow_str
|
|
assert resp.json['duration'] == 30
|
|
assert resp.json['resources'] == [resource1.slug, resource2.slug]
|
|
booking = Booking.objects.latest('pk')
|
|
assert list(booking.event.resources.all()) == [resource1, resource2]
|
|
|
|
# resource is unknown or not valid for this agenda
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=foobarblah'
|
|
% (agenda.pk, meeting_type.pk, tomorrow_str),
|
|
status=400,
|
|
)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid resource: foobarblah' # legacy
|
|
assert resp.json['err_class'] == 'invalid resource: foobarblah'
|
|
assert resp.json['err_desc'] == 'invalid resource: foobarblah'
|
|
agenda.resources.remove(resource3)
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s'
|
|
% (agenda.pk, meeting_type.pk, tomorrow_str, resource3.slug),
|
|
status=400,
|
|
)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid resource: resource-3' # legacy
|
|
assert resp.json['err_class'] == 'invalid resource: resource-3'
|
|
assert resp.json['err_desc'] == 'invalid resource: resource-3'
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s,foobarblah'
|
|
% (agenda.pk, meeting_type.pk, tomorrow_str, resource3.slug),
|
|
status=400,
|
|
)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid resource: foobarblah, resource-3' # legacy
|
|
assert resp.json['err_class'] == 'invalid resource: foobarblah, resource-3'
|
|
assert resp.json['err_desc'] == 'invalid resource: foobarblah, resource-3'
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s:%s-0900/?resources=%s,foobarblah'
|
|
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug),
|
|
status=400,
|
|
)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid resource: foobarblah' # legacy
|
|
assert resp.json['err_class'] == 'invalid resource: foobarblah'
|
|
assert resp.json['err_desc'] == 'invalid resource: foobarblah'
|
|
|
|
# booking is canceled: slot is free
|
|
booking.cancel()
|
|
resp = app.post(
|
|
'/api/agenda/%s/fillslot/%s:%s-0930/?resources=%s,%s'
|
|
% (agenda.pk, meeting_type.pk, tomorrow_str, resource1.slug, resource2.slug)
|
|
)
|
|
assert resp.json['datetime'] == '%s 09:30:00' % tomorrow_str
|
|
assert resp.json['end_datetime'] == '%s 10:00:00' % tomorrow_str
|
|
assert resp.json['duration'] == 30
|
|
assert resp.json['resources'] == [resource1.slug, resource2.slug]
|
|
booking = Booking.objects.latest('pk')
|
|
assert list(booking.event.resources.all()) == [resource1, resource2]
|
|
|
|
|
|
def test_booking_api_meeting_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
|
|
|
|
|
|
def test_booking_api_meeting_fillslots_wrong_slot(app, user):
|
|
agenda = Agenda.objects.create(label='Foo', kind='meetings')
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
impossible_slots = ['1:2017-05-22-1130', '2:2017-05-22-1100']
|
|
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, 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)'
|
|
|
|
unknown_slots = ['0:2017-05-22-1130']
|
|
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': unknown_slots}, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid meeting type id: 0' # legacy
|
|
assert resp.json['err_class'] == 'invalid meeting type id: 0'
|
|
assert resp.json['err_desc'] == 'invalid meeting type id: 0'
|
|
unknown_slots = ['foobar:2017-05-22-1130']
|
|
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': unknown_slots}, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'invalid meeting type id: foobar' # legacy
|
|
assert resp.json['err_class'] == 'invalid meeting type id: foobar'
|
|
assert resp.json['err_desc'] == 'invalid meeting type id: foobar'
|
|
|
|
badformat_slots = ['foo:2020-10-28-14h00']
|
|
resp = app.post('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': badformat_slots}, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'bad datetime format: 2020-10-28-14h00' # legacy
|
|
assert resp.json['err_class'] == 'bad datetime format: 2020-10-28-14h00'
|
|
assert resp.json['err_desc'] == 'bad datetime format: 2020-10-28-14h00'
|
|
|
|
|
|
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 = 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'))
|
|
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, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
|
|
for i in range(0, 10):
|
|
event = Event.objects.create(
|
|
slug='event-slug%i' % i,
|
|
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=i),
|
|
places=20,
|
|
agenda=agenda,
|
|
)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
assert len(ctx.captured_queries) == 4
|
|
assert resp.json['data'][-1]['places']['total'] == 20
|
|
assert resp.json['data'][-1]['places']['available'] == 20
|
|
assert resp.json['data'][-1]['places']['reserved'] == 0
|
|
assert resp.json['data'][-1]['places']['full'] is False
|
|
assert 'waiting_list_total' not in resp.json['data'][-1]['places']
|
|
|
|
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk))
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['places']['total'] == 20
|
|
assert resp.json['places']['available'] == 19
|
|
assert resp.json['places']['reserved'] == 1
|
|
assert resp.json['places']['full'] is False
|
|
assert 'waiting_list_total' not in resp.json['places']
|
|
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
assert resp.json['data'][-1]['places']['total'] == 20
|
|
assert resp.json['data'][-1]['places']['available'] == 19
|
|
assert resp.json['data'][-1]['places']['reserved'] == 1
|
|
assert resp.json['data'][-1]['places']['full'] is False
|
|
assert 'waiting_list_total' not in resp.json['data'][-1]['places']
|
|
|
|
Booking.objects.create(event=event, in_waiting_list=True)
|
|
event.waiting_list_places = 5
|
|
event.save()
|
|
|
|
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk))
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['places']['total'] == 20
|
|
assert resp.json['places']['available'] == 19
|
|
assert resp.json['places']['reserved'] == 1
|
|
assert resp.json['places']['full'] is False
|
|
assert resp.json['places']['waiting_list_total'] == 5
|
|
assert resp.json['places']['waiting_list_available'] == 3
|
|
assert resp.json['places']['waiting_list_reserved'] == 2
|
|
assert resp.json['places']['waiting_list_activated'] is True
|
|
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
assert resp.json['data'][-1]['places']['total'] == 20
|
|
assert resp.json['data'][-1]['places']['available'] == 19
|
|
assert resp.json['data'][-1]['places']['reserved'] == 1
|
|
assert resp.json['data'][-1]['places']['full'] is False
|
|
assert resp.json['data'][-1]['places']['waiting_list_total'] == 5
|
|
assert resp.json['data'][-1]['places']['waiting_list_available'] == 3
|
|
assert resp.json['data'][-1]['places']['waiting_list_reserved'] == 2
|
|
assert resp.json['data'][-1]['places']['waiting_list_activated'] is True
|
|
|
|
# not for mettings agenda
|
|
meetings_agenda = Agenda.objects.create(label='meetings', kind='meetings')
|
|
desk = Desk.objects.create(label='foo', agenda=meetings_agenda)
|
|
meeting_type = MeetingType.objects.create(agenda=meetings_agenda, label='Blah', duration=30)
|
|
TimePeriod.objects.create(
|
|
weekday=3, start_time=datetime.time(11, 0), end_time=datetime.time(12, 30), desk=desk
|
|
)
|
|
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug))
|
|
assert 'places' not in resp.json['data'][0]
|
|
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (meetings_agenda.pk, resp.json['data'][0]['id']))
|
|
assert resp.json['err'] == 0
|
|
assert 'places' not in resp.json
|
|
|
|
# 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 = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][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'] == 20
|
|
assert resp.json['places']['available'] == 19
|
|
assert resp.json['places']['reserved'] == 1
|
|
assert resp.json['places']['full'] is False
|
|
assert resp.json['places']['waiting_list_total'] == 2
|
|
assert resp.json['places']['waiting_list_available'] == 2
|
|
assert resp.json['places']['waiting_list_reserved'] == 0
|
|
assert resp.json['places']['waiting_list_activated'] is False
|
|
|
|
# add another booking
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk), params={'force_waiting_list': False}
|
|
)
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['places']['total'] == 20
|
|
assert resp.json['places']['available'] == 18
|
|
assert resp.json['places']['reserved'] == 2
|
|
assert resp.json['places']['full'] is False
|
|
assert resp.json['places']['waiting_list_total'] == 2
|
|
assert resp.json['places']['waiting_list_available'] == 2
|
|
assert resp.json['places']['waiting_list_reserved'] == 0
|
|
assert resp.json['places']['waiting_list_activated'] is False
|
|
|
|
# add a booking, but in waiting list
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk), params={'force_waiting_list': True}
|
|
)
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['places']['total'] == 20
|
|
assert resp.json['places']['available'] == 18
|
|
assert resp.json['places']['reserved'] == 2
|
|
assert resp.json['places']['full'] is False
|
|
assert resp.json['places']['waiting_list_total'] == 2
|
|
assert resp.json['places']['waiting_list_available'] == 1
|
|
assert resp.json['places']['waiting_list_reserved'] == 1
|
|
assert resp.json['places']['waiting_list_activated'] is True
|
|
|
|
# add a booking => booked in waiting list
|
|
resp = app.post_json('/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk))
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['places']['total'] == 20
|
|
assert resp.json['places']['available'] == 18
|
|
assert resp.json['places']['reserved'] == 2
|
|
assert resp.json['places']['full'] is True
|
|
assert resp.json['places']['waiting_list_total'] == 2
|
|
assert resp.json['places']['waiting_list_available'] == 0
|
|
assert resp.json['places']['waiting_list_reserved'] == 2
|
|
assert resp.json['places']['waiting_list_activated'] is True
|
|
|
|
# waiting list is full
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.pk, event.pk), params={'force_waiting_list': True}
|
|
)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'sold out' # legacy
|
|
assert resp.json['err_class'] == 'sold out'
|
|
assert resp.json['err_desc'] == 'sold out'
|
|
|
|
|
|
def test_booking_api_with_cancel_booking(app, some_data, user):
|
|
agenda = Agenda.objects.filter(label=u'Foo bar')[0]
|
|
event_0, event_1, event_2 = [x for x in Event.objects.filter(agenda=agenda) if x.in_bookable_period()][
|
|
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_1.id),
|
|
params={'cancel_booking_id': booking_id, 'count': 1},
|
|
)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'cancel booking: count is different' # legacy
|
|
assert resp.json['err_class'] == 'cancel booking: count is different'
|
|
assert resp.json['err_desc'] == 'cancel booking: count is different'
|
|
assert Booking.objects.count() == 4
|
|
|
|
# cancel_booking_id must be an integer
|
|
app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_0.id),
|
|
params={'cancel_booking_id': 'no an integer'},
|
|
status=400,
|
|
)
|
|
|
|
# cancel_booking_id can be empty or null
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_0.id), params={'cancel_booking_id': ''}
|
|
)
|
|
assert resp.json['err'] == 0
|
|
assert 'cancelled_booking_id' not in resp.json
|
|
assert Booking.objects.count() == 5
|
|
|
|
resp = app.post_json(
|
|
'/api/agenda/%s/fillslot/%s/' % (agenda.id, event_0.id), params={'cancel_booking_id': None}
|
|
)
|
|
assert resp.json['err'] == 0
|
|
assert 'cancelled_booking_id' not in resp.json
|
|
assert Booking.objects.count() == 6
|
|
|
|
|
|
@pytest.mark.parametrize('flag', [True, False, None])
|
|
def test_booking_api_present(app, user, flag):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
|
booking = Booking.objects.create(event=event, user_was_present=flag, user_absence_reason='foobar')
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
resp = app.get('/api/booking/%s/' % booking.pk)
|
|
assert resp.json['booking_id'] == booking.pk
|
|
assert resp.json['user_was_present'] == flag
|
|
assert resp.json['user_absence_reason'] == 'foobar'
|
|
|
|
|
|
@pytest.mark.parametrize('flag', [True, False])
|
|
def test_booking_api_waiting_list(app, user, flag):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
|
booking = Booking.objects.create(event=event, in_waiting_list=flag)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
resp = app.get('/api/booking/%s/' % booking.pk)
|
|
assert resp.json['booking_id'] == booking.pk
|
|
assert resp.json['in_waiting_list'] == flag
|
|
|
|
|
|
def test_booking_api_error(app, user):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
|
booking = Booking.objects.create(event=event)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# unknown
|
|
booking.cancel()
|
|
for method in ['get', 'delete', 'patch']:
|
|
getattr(app, method)('/api/booking/0/', status=404)
|
|
|
|
# cancelled
|
|
booking.cancel()
|
|
for method in ['get', 'delete', 'patch']:
|
|
resp = getattr(app, method)('/api/booking/%s/' % booking.pk)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['err_desc'] == 'booking is cancelled'
|
|
|
|
# not a primary booking
|
|
secondary = Booking.objects.create(event=event, primary_booking=booking)
|
|
for method in ['get', 'delete', 'patch']:
|
|
resp = getattr(app, method)('/api/booking/%s/' % secondary.pk)
|
|
assert resp.json['err'] == 2
|
|
assert resp.json['err_desc'] == 'secondary booking'
|
|
|
|
# in waiting list
|
|
booking.cancellation_datetime = None
|
|
booking.in_waiting_list = True
|
|
booking.save()
|
|
resp = app.get('/api/booking/%s/' % booking.pk)
|
|
assert resp.json['err'] == 0
|
|
resp = app.patch('/api/booking/%s/' % booking.pk)
|
|
assert resp.json['err'] == 3
|
|
assert resp.json['err_desc'] == 'booking is in waiting list'
|
|
resp = app.delete('/api/booking/%s/' % booking.pk)
|
|
assert resp.json['err'] == 0
|
|
|
|
|
|
def test_booking_patch_api(app, user):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
|
booking = Booking.objects.create(event=event)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
resp = app.patch('/api/booking/%s/' % booking.pk)
|
|
assert resp.json['err'] == 0
|
|
resp = app.patch('/api/booking/%s/' % booking.pk, params={'user_was_present': 'foobar'}, status=400)
|
|
assert resp.json['err'] == 4
|
|
assert resp.json['err_desc'] == 'invalid payload'
|
|
|
|
|
|
@pytest.mark.parametrize('flag', [True, False, None])
|
|
def test_booking_patch_api_present(app, user, flag):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
|
booking = Booking.objects.create(event=event)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# set flag
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_was_present': flag})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present == flag
|
|
|
|
# reset
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_was_present': None})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is None
|
|
|
|
# make secondary bookings
|
|
Booking.objects.create(event=event, primary_booking=booking)
|
|
Booking.objects.create(event=event, primary_booking=booking)
|
|
# and other booking
|
|
other_booking = Booking.objects.create(event=event)
|
|
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_was_present': flag})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present == flag
|
|
# all secondary bookings are upadted
|
|
assert list(booking.secondary_booking_set.values_list('user_was_present', flat=True)) == [flag, flag]
|
|
other_booking.refresh_from_db()
|
|
assert other_booking.user_was_present is None # not changed
|
|
|
|
|
|
def test_booking_patch_api_absence_reason(app, user):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
|
booking = Booking.objects.create(event=event, user_was_present=True)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# set reason
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.user_absence_reason == 'foobar'
|
|
|
|
# reset
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': ''})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.user_absence_reason == ''
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': None})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.user_absence_reason == ''
|
|
|
|
# make secondary bookings
|
|
Booking.objects.create(event=event, primary_booking=booking, user_was_present=False)
|
|
Booking.objects.create(event=event, primary_booking=booking, user_was_present=False)
|
|
# and other booking
|
|
other_booking = Booking.objects.create(event=event)
|
|
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_absence_reason': 'foobar'})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.user_absence_reason == 'foobar'
|
|
# all secondary bookings are upadted
|
|
assert list(booking.secondary_booking_set.values_list('user_absence_reason', flat=True)) == [
|
|
'foobar',
|
|
'foobar',
|
|
]
|
|
assert list(booking.secondary_booking_set.values_list('user_was_present', flat=True)) == [
|
|
False,
|
|
False,
|
|
] # not changed
|
|
other_booking.refresh_from_db()
|
|
assert other_booking.user_absence_reason == '' # not changed
|
|
|
|
|
|
def test_booking_patch_api_extra_data(app, user):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
|
booking = Booking.objects.create(event=event, user_was_present=True)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'extra_data': {'foo': 'bar'}})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.extra_data == {'foo': 'bar'}
|
|
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'extra_data': {'foo': 'baz'}})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.extra_data == {'foo': 'baz'}
|
|
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'extra_data': {'foooo': 'bar'}})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.extra_data == {'foo': 'baz', 'foooo': 'bar'}
|
|
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'extra_data': {'foooo': 'baz', 'foo': None}})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.extra_data == {'foo': None, 'foooo': 'baz'}
|
|
|
|
# make secondary bookings
|
|
Booking.objects.create(event=event, primary_booking=booking, user_was_present=False)
|
|
Booking.objects.create(event=event, primary_booking=booking, user_was_present=False)
|
|
# and other booking
|
|
other_booking = Booking.objects.create(event=event)
|
|
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'extra_data': {'foooo': 'baz', 'foo': None}})
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.extra_data == {'foo': None, 'foooo': 'baz'}
|
|
# all secondary bookings are upadted
|
|
assert list(booking.secondary_booking_set.values_list('extra_data', flat=True)) == [
|
|
{'foo': None, 'foooo': 'baz'},
|
|
{'foo': None, 'foooo': 'baz'},
|
|
]
|
|
assert list(booking.secondary_booking_set.values_list('user_was_present', flat=True)) == [
|
|
False,
|
|
False,
|
|
] # not changed
|
|
other_booking.refresh_from_db()
|
|
assert other_booking.extra_data is None # not changed
|
|
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'extra_data': None})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.extra_data == {'foo': None, 'foooo': 'baz'} # not changed
|
|
app.patch_json('/api/booking/%s/' % booking.pk, params={'extra_data': {}})
|
|
booking.refresh_from_db()
|
|
assert booking.user_was_present is True # not changed
|
|
assert booking.extra_data == {'foo': None, 'foooo': 'baz'} # not changed
|
|
|
|
|
|
def test_booking_cancellation_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 = 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 = 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 = 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),
|
|
params={
|
|
'cancel_callback_url': 'http://example.net/jump/trigger/',
|
|
},
|
|
)
|
|
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
|
|
with mock.patch('chrono.agendas.models.requests.post') as mocked_post:
|
|
resp = app.post('/api/booking/%s/cancel/' % booking_id)
|
|
assert not mocked_post.called
|
|
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.slug 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 event.slug not in [x['id'] for x in resp.json['data'] if not x.get('disabled')]
|
|
assert event.slug 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, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
|
|
event = Event.objects.create(
|
|
slug='event-slug',
|
|
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
|
places=10,
|
|
agenda=agenda,
|
|
)
|
|
agenda2 = Agenda.objects.create(label='Foo bar2', kind='events', minimal_booking_delay=0)
|
|
# other event with the same slug but in another agenda
|
|
Event.objects.create(
|
|
slug='event-slug',
|
|
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
|
places=5,
|
|
agenda=agenda2,
|
|
)
|
|
Booking.objects.create(event=event)
|
|
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.slug, event.slug), status=401)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
resp = app.get('/api/agenda/%s/status/%s/' % (agenda.slug, event.slug))
|
|
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 == {
|
|
'err': 0,
|
|
'id': 'event-slug',
|
|
'slug': 'event-slug',
|
|
'text': str(event),
|
|
'datetime': localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
|
|
'description': None,
|
|
'pricing': None,
|
|
'url': None,
|
|
'disabled': False,
|
|
'api': {
|
|
'bookings_url': 'http://testserver/api/agenda/foo-bar/bookings/event-slug/',
|
|
'fillslot_url': 'http://testserver/api/agenda/foo-bar/fillslot/event-slug/',
|
|
'status_url': 'http://testserver/api/agenda/foo-bar/status/event-slug/',
|
|
},
|
|
'places': {'available': 9, 'reserved': 1, 'total': 10, 'full': False, 'has_waiting_list': False},
|
|
}
|
|
assert 'waiting_list_total' not 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.slug, event.slug))
|
|
assert resp.json['places']['waiting_list_total'] == 5
|
|
assert resp.json['places']['waiting_list_available'] == 4
|
|
assert resp.json['places']['waiting_list_reserved'] == 1
|
|
|
|
# wrong kind
|
|
for kind in ['meetings', 'virtual']:
|
|
agenda.kind = kind
|
|
agenda.save()
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.pk, event.slug), status=404)
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.slug, event.slug), status=404)
|
|
|
|
|
|
def test_status_url(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
|
|
event = Event.objects.create(
|
|
slug='event-slug',
|
|
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
|
places=10,
|
|
agenda=agenda,
|
|
)
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.pk, event.pk))
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.slug, event.slug))
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.pk, event.slug))
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.slug, event.pk))
|
|
|
|
# unknown event
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.pk, 0), status=404)
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.slug, 0), status=404)
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.pk, 'foobar'), status=404)
|
|
app.get('/api/agenda/%s/status/%s/' % (agenda.slug, 'foobar'), status=404)
|
|
|
|
# unknown agenda
|
|
app.get('/api/agenda/%s/status/%s/' % (0, event.pk), status=404)
|
|
app.get('/api/agenda/%s/status/%s/' % (0, event.slug), status=404)
|
|
app.get('/api/agenda/%s/status/%s/' % ('foobar', event.pk), status=404)
|
|
app.get('/api/agenda/%s/status/%s/' % ('foobar', event.slug), status=404)
|
|
|
|
|
|
def test_bookings(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
|
|
event = Event.objects.create(
|
|
slug='event-slug',
|
|
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
agenda2 = Agenda.objects.create(label='Foo bar2', kind='events', minimal_booking_delay=0)
|
|
# other event with the same slug but in another agenda
|
|
Event.objects.create(
|
|
slug='event-slug',
|
|
start_datetime=(now() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
|
places=5,
|
|
agenda=agenda2,
|
|
)
|
|
|
|
# create a booking not on the waiting list
|
|
primary1 = Booking.objects.create(event=event, in_waiting_list=False, user_external_id='42')
|
|
Booking.objects.create(
|
|
event=event, in_waiting_list=False, primary_booking=primary1, user_external_id='42'
|
|
)
|
|
primary2 = Booking.objects.create(event=event, in_waiting_list=False, user_external_id='foobar')
|
|
Booking.objects.create(event=event, in_waiting_list=False, user_external_id='')
|
|
primary4 = Booking.objects.create(event=event, in_waiting_list=True, user_external_id='42')
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# unknown agenda
|
|
app.get('/api/agenda/foobar/bookings/%s/' % (event.pk), params={'user_external_id': '42'}, status=404)
|
|
# unknown event
|
|
app.get('/api/agenda/%s/bookings/0/' % (agenda.slug), params={'user_external_id': '42'}, status=404)
|
|
app.get('/api/agenda/%s/bookings/foobar/' % (agenda.slug), params={'user_external_id': '42'}, status=404)
|
|
|
|
# search for '42' external user
|
|
resp = app.get(
|
|
'/api/agenda/%s/bookings/%s/' % (agenda.slug, event.slug), params={'user_external_id': '42'}
|
|
)
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['data'] == [
|
|
{'booking_id': primary1.pk, 'in_waiting_list': False},
|
|
{'booking_id': primary4.pk, 'in_waiting_list': True},
|
|
]
|
|
# primary1 is cancelled
|
|
primary1.cancel()
|
|
resp = app.get(
|
|
'/api/agenda/%s/bookings/%s/' % (agenda.slug, event.slug), params={'user_external_id': '42'}
|
|
)
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['data'] == [
|
|
{'booking_id': primary4.pk, 'in_waiting_list': True},
|
|
]
|
|
|
|
# search for 'foobar' external user
|
|
resp = app.get(
|
|
'/api/agenda/%s/bookings/%s/' % (agenda.slug, event.slug), params={'user_external_id': 'foobar'}
|
|
)
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['data'] == [
|
|
{'booking_id': primary2.pk, 'in_waiting_list': False},
|
|
]
|
|
|
|
# no bookings for this external user
|
|
resp = app.get(
|
|
'/api/agenda/%s/bookings/%s/' % (agenda.slug, event.slug), params={'user_external_id': '35'}
|
|
)
|
|
assert resp.json['err'] == 0
|
|
assert resp.json['data'] == []
|
|
# no user_external_id in params
|
|
resp = app.get('/api/agenda/%s/bookings/%s/' % (agenda.slug, event.slug), params={'user_external_id': ''})
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['reason'] == 'missing param user_external_id' # legacy
|
|
assert resp.json['err_class'] == 'missing param user_external_id'
|
|
assert resp.json['err_desc'] == 'missing param user_external_id'
|
|
resp = app.get('/api/agenda/%s/bookings/%s/' % (agenda.slug, event.slug))
|
|
assert resp.json['err'] == 1
|
|
|
|
# wrong kind
|
|
agenda.kind = 'meetings'
|
|
agenda.save()
|
|
app.get(
|
|
'/api/agenda/%s/bookings/%s/' % (agenda.slug, event.slug),
|
|
params={'user_external_id': '42'},
|
|
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.slug 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 event.slug not in [x['id'] for x in resp.json['data'] if not x.get('disabled')]
|
|
assert event.slug 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' 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, user):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=20, waiting_list_places=5)
|
|
|
|
# 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_accept_booking_non_event_agenda(app, user):
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
agenda = Agenda.objects.create(kind='meetings')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=1)
|
|
booking = Booking.objects.create(event=event)
|
|
|
|
app.post('/api/booking/%s/accept/' % booking.pk, status=404)
|
|
agenda.kind = 'virtual'
|
|
agenda.save()
|
|
app.post('/api/booking/%s/accept/' % booking.pk, status=404)
|
|
|
|
|
|
def test_suspend_booking(app, user):
|
|
agenda = Agenda.objects.create(kind='events')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=20, waiting_list_places=5)
|
|
|
|
# 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_suspend_booking_non_event_agenda(app, user):
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
agenda = Agenda.objects.create(kind='meetings')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=1)
|
|
booking = Booking.objects.create(event=event)
|
|
|
|
app.post('/api/booking/%s/suspend/' % booking.pk, status=404)
|
|
agenda.kind = 'virtual'
|
|
agenda.save()
|
|
app.post('/api/booking/%s/suspend/' % booking.pk, status=404)
|
|
|
|
|
|
def test_resize_booking_invalid_payload(app, user):
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
# decrease a booking to 0
|
|
resp = app.post('/api/booking/0/resize/', params={'count': 0}, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['err_desc'] == 'invalid payload'
|
|
|
|
|
|
def test_resize_booking_not_found(app, user):
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
app.post('/api/booking/0/resize/', params={'count': 42}, status=404)
|
|
|
|
|
|
def test_resize_booking_not_primary(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
|
event = Event.objects.create(
|
|
slug='event',
|
|
start_datetime=now(),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
primary = Booking.objects.create(event=event)
|
|
secondary = Booking.objects.create(event=event, primary_booking=primary)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# resize a booking that is not primary
|
|
resp = app.post('/api/booking/%s/resize/' % secondary.pk, params={'count': 42})
|
|
assert resp.json['err'] == 2
|
|
assert resp.json['err_desc'] == 'secondary booking'
|
|
|
|
|
|
def test_resize_booking_cancelled(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
|
event = Event.objects.create(
|
|
slug='event',
|
|
start_datetime=now(),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
primary = Booking.objects.create(event=event)
|
|
primary.cancel()
|
|
|
|
# resize a booking that was cancelled before
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['err_desc'] == 'booking is cancelled'
|
|
|
|
|
|
def test_resize_booking_multi_events(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
|
event1 = Event.objects.create(
|
|
slug='event1',
|
|
start_datetime=now(),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
event2 = Event.objects.create(
|
|
slug='event2',
|
|
start_datetime=now(),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
primary = Booking.objects.create(event=event1, in_waiting_list=False)
|
|
Booking.objects.create(event=event2, in_waiting_list=False, primary_booking=primary)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# resize a booking that is on multi events
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
|
|
assert resp.json['err'] == 4
|
|
assert resp.json['err_desc'] == 'can not resize multi event booking'
|
|
|
|
|
|
def test_resize_booking_mixed_waiting_list(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
|
event = Event.objects.create(
|
|
slug='event',
|
|
start_datetime=now(),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
primary = Booking.objects.create(event=event, in_waiting_list=False)
|
|
Booking.objects.create(event=event, primary_booking=primary, in_waiting_list=True)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# booking is in main list and waiting list in the same time
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 42})
|
|
assert resp.json['err'] == 5
|
|
assert resp.json['err_desc'] == 'can not resize booking: waiting list inconsistency'
|
|
|
|
|
|
def test_resize_booking(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
|
event = Event.objects.create(
|
|
slug='event',
|
|
start_datetime=now(),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
primary = Booking.objects.create(event=event, in_waiting_list=False)
|
|
# there is other bookings
|
|
Booking.objects.create(event=event, in_waiting_list=False)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# increase
|
|
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() == 3
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 3
|
|
assert primary.secondary_booking_set.count() == 1
|
|
# too much
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
|
|
assert resp.json['err'] == 3
|
|
assert resp.json['err_desc'] == 'sold out'
|
|
# max
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 4})
|
|
assert resp.json['err'] == 0
|
|
assert Booking.objects.filter(in_waiting_list=False).count() == 5
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 5
|
|
assert primary.secondary_booking_set.count() == 3
|
|
|
|
# booking is overbooked - it is ok to increase
|
|
event.places = 4
|
|
event.save()
|
|
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() == 6
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 6
|
|
assert primary.secondary_booking_set.count() == 4
|
|
|
|
# decrease
|
|
# booking is overbooked, but it is always ok to do nothing
|
|
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() == 6
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 6
|
|
assert primary.secondary_booking_set.count() == 4
|
|
# or to decrease
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 4})
|
|
assert resp.json['err'] == 0
|
|
assert Booking.objects.filter(in_waiting_list=False).count() == 5
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 5
|
|
assert primary.secondary_booking_set.count() == 3
|
|
# again
|
|
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() == 2
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 2
|
|
assert primary.secondary_booking_set.count() == 0
|
|
|
|
# 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() == 2
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 2
|
|
assert primary.secondary_booking_set.count() == 0
|
|
|
|
|
|
def test_resize_booking_in_waiting_list(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events')
|
|
event = Event.objects.create(
|
|
slug='event',
|
|
start_datetime=now(),
|
|
places=5,
|
|
waiting_list_places=5,
|
|
agenda=agenda,
|
|
)
|
|
primary = Booking.objects.create(event=event, in_waiting_list=True)
|
|
# there is other bookings
|
|
Booking.objects.create(event=event, in_waiting_list=True)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# increase
|
|
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() == 3
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 3
|
|
assert primary.secondary_booking_set.count() == 1
|
|
# too much
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
|
|
assert resp.json['err'] == 3
|
|
assert resp.json['err_desc'] == 'sold out'
|
|
# max
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 4})
|
|
assert resp.json['err'] == 0
|
|
assert Booking.objects.filter(in_waiting_list=True).count() == 5
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 5
|
|
assert primary.secondary_booking_set.count() == 3
|
|
|
|
# booking is overbooked (on waiting list, that shoud not happen)
|
|
event.waiting_list_places = 4
|
|
event.save()
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 5})
|
|
assert resp.json['err'] == 3
|
|
assert resp.json['err_desc'] == 'sold out'
|
|
|
|
# decrease
|
|
# booking is overbooked, but it is always ok to do nothing
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 4})
|
|
assert resp.json['err'] == 0
|
|
assert Booking.objects.filter(in_waiting_list=True).count() == 5
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 5
|
|
assert primary.secondary_booking_set.count() == 3
|
|
# or to decrease
|
|
event.waiting_list_places = 3
|
|
event.save()
|
|
resp = app.post('/api/booking/%s/resize/' % primary.pk, params={'count': 3})
|
|
assert resp.json['err'] == 0
|
|
assert Booking.objects.filter(in_waiting_list=True).count() == 4
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 4
|
|
assert primary.secondary_booking_set.count() == 2
|
|
# again
|
|
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() == 2
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 2
|
|
assert primary.secondary_booking_set.count() == 0
|
|
|
|
# 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() == 2
|
|
assert Booking.objects.filter(primary_booking__isnull=True).count() == 2
|
|
assert Booking.objects.count() == 2
|
|
assert primary.secondary_booking_set.count() == 0
|
|
|
|
|
|
def test_resize_booking_non_event_agenda(app, user):
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
agenda = Agenda.objects.create(kind='meetings')
|
|
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=1)
|
|
booking = Booking.objects.create(event=event)
|
|
|
|
app.post('/api/booking/%s/resize/' % booking.pk, params={'count': 42}, status=404)
|
|
agenda.kind = 'virtual'
|
|
agenda.save()
|
|
app.post('/api/booking/%s/resize/' % booking.pk, params={'count': 42}, status=404)
|
|
|
|
|
|
def test_anonymize_booking(app, user):
|
|
agenda = Agenda.objects.create(label='Foo bar')
|
|
event = Event.objects.create(start_datetime=now(), places=5, agenda=agenda)
|
|
Booking.objects.create(event=event, label='not to anonymize')
|
|
booking = Booking.objects.create(event=event, label='test')
|
|
secondary_booking = Booking.objects.create(event=event, label='test secondary', primary_booking=booking)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
resp = app.post('/api/booking/%s/anonymize/' % booking.pk)
|
|
assert resp.json['err'] == 0
|
|
assert Booking.objects.filter(anonymization_datetime__isnull=False).count() == 2
|
|
assert Booking.objects.filter(label='').count() == 2
|
|
|
|
|
|
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.slug][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' 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_slugs = [x.slug 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_slugs]
|
|
|
|
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' 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_slugs = [x.slug 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_slugs]
|
|
|
|
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_slugs = [x.slug 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_slugs]
|
|
|
|
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):
|
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
|
|
event1 = Event.objects.create(
|
|
start_datetime=(localtime() + datetime.timedelta(days=5)).replace(hour=10, minute=0),
|
|
places=20,
|
|
agenda=agenda,
|
|
)
|
|
event2 = Event.objects.create(
|
|
start_datetime=(localtime() + datetime.timedelta(days=10)).replace(hour=10, minute=0),
|
|
places=20,
|
|
agenda=agenda,
|
|
)
|
|
event3 = Event.objects.create(
|
|
start_datetime=(localtime() + datetime.timedelta(days=15)).replace(hour=10, minute=0),
|
|
places=20,
|
|
agenda=agenda,
|
|
)
|
|
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['opened_events_available'] is True
|
|
assert data['api']['datetimes_url'] == 'http://testserver/api/agenda/foo-bar/datetimes/'
|
|
|
|
# one event is full
|
|
Event.objects.filter(pk=event1.pk).update(full=True)
|
|
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
|
assert resp.json['data']['opened_events_available'] is True
|
|
|
|
# all events are full
|
|
Event.objects.update(full=True)
|
|
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
|
assert resp.json['data']['opened_events_available'] is False
|
|
|
|
# event1 is not full but too soon
|
|
Event.objects.filter(pk=event1.pk).update(full=False)
|
|
agenda.minimal_booking_delay = 10
|
|
agenda.save()
|
|
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
|
assert list(agenda.get_open_events()) == [event2, event3]
|
|
assert resp.json['data']['opened_events_available'] is False
|
|
|
|
# event3 is not full but too late
|
|
Event.objects.filter(pk=event3.pk).update(full=False)
|
|
agenda.maximal_booking_delay = 12
|
|
agenda.save()
|
|
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
|
assert list(agenda.get_open_events()) == [event2]
|
|
assert resp.json['data']['opened_events_available'] is False
|
|
|
|
# events are not full but not published
|
|
Event.objects.update(full=False)
|
|
agenda.event_set.update(publication_date=now().date() + datetime.timedelta(days=20))
|
|
resp = app.get('/api/agenda/%s/' % agenda.slug)
|
|
assert list(agenda.get_open_events()) == []
|
|
assert resp.json['data']['opened_events_available'] is False
|
|
|
|
# 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': 'foo'}
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD'
|
|
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD'
|
|
|
|
params = {'date_end': 'foo'}
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD'
|
|
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD'
|
|
|
|
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
|
|
|
|
# cover completely to test limit condition in get_all_slots()
|
|
full_coverage = TimePeriodException.objects.create(
|
|
desk=excp1.desk,
|
|
start_datetime=make_aware(datetime.datetime(2017, 1, 1, 0, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2018, 1, 1, 0, 0)),
|
|
)
|
|
resp21 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp21.json['data']) == 0
|
|
full_coverage.delete()
|
|
|
|
# with a second desk
|
|
desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda)
|
|
time_period = desk.timeperiod_set.first()
|
|
TimePeriod.objects.create(
|
|
desk=desk2,
|
|
start_time=time_period.start_time,
|
|
end_time=time_period.end_time,
|
|
weekday=time_period.weekday,
|
|
)
|
|
resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp.json['data']) == len(resp3.json['data']) + 2 # +2 because excp1 changed
|
|
|
|
# try to booking just after an exception is set
|
|
TimePeriodException.objects.create(
|
|
desk=desk2,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 9, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
|
|
)
|
|
booking_url = resp3.json['data'][0]['api']['fillslot_url']
|
|
resp = app.post(booking_url)
|
|
assert resp.json['err'] == 1
|
|
|
|
|
|
def test_agenda_meeting_api_in_between_exceptions(app, meetings_agenda, user):
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
|
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
desk = meetings_agenda.desk_set.first()
|
|
# test exception at the lowest limit
|
|
TimePeriodException.objects.create(
|
|
desk=desk,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
|
|
)
|
|
resp2 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp.json['data']) == len(resp2.json['data']) + 4
|
|
# exclude slots on 2017-05-30 and 2017-07-10
|
|
date_2017_05_30 = datetime.datetime(2017, 5, 30).date()
|
|
date_2017_07_10 = datetime.datetime(2017, 7, 10).date()
|
|
count_on_2017_05_30 = len(
|
|
[
|
|
datum
|
|
for datum in resp.json['data']
|
|
if datetime_from_str(datum['datetime']).date() == date_2017_05_30
|
|
]
|
|
)
|
|
count_on_2017_07_10 = len(
|
|
[
|
|
datum
|
|
for datum in resp.json['data']
|
|
if datetime_from_str(datum['datetime']).date() == date_2017_07_10
|
|
]
|
|
)
|
|
TimePeriodException.objects.create(
|
|
desk=desk,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 30, 8, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 30, 18, 0)),
|
|
)
|
|
TimePeriodException.objects.create(
|
|
desk=desk,
|
|
start_datetime=make_aware(datetime.datetime(2017, 7, 10, 8, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 7, 10, 18, 0)),
|
|
)
|
|
resp3 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp2.json['data']) == len(resp3.json['data']) + count_on_2017_05_30 + count_on_2017_07_10
|
|
assert (
|
|
len(
|
|
[
|
|
datum
|
|
for datum in resp3.json['data']
|
|
if datetime_from_str(datum['datetime']).date() == date_2017_05_30
|
|
]
|
|
)
|
|
== 0
|
|
)
|
|
assert (
|
|
len(
|
|
[
|
|
datum
|
|
for datum in resp3.json['data']
|
|
if datetime_from_str(datum['datetime']).date() == date_2017_07_10
|
|
]
|
|
)
|
|
== 0
|
|
)
|
|
# with a second desk with the same time periods
|
|
desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda)
|
|
for time_period in desk.timeperiod_set.all():
|
|
TimePeriod.objects.create(
|
|
desk=desk2,
|
|
start_time=time_period.start_time,
|
|
end_time=time_period.end_time,
|
|
weekday=time_period.weekday,
|
|
)
|
|
resp4 = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
assert len(resp.json['data']) == len(resp4.json['data'])
|
|
|
|
|
|
def test_agenda_meeting_api_desk_info(app, meetings_agenda, user):
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
|
desk = meetings_agenda.desk_set.get(slug='desk-1')
|
|
desk2 = Desk.objects.create(label='Desk 2', agenda=meetings_agenda)
|
|
for time_period in desk.timeperiod_set.all():
|
|
TimePeriod.objects.create(
|
|
desk=desk2,
|
|
start_time=time_period.start_time,
|
|
end_time=time_period.end_time,
|
|
weekday=time_period.weekday,
|
|
)
|
|
resp = app.get('/api/agenda/meetings/%s/datetimes/' % meeting_type.id)
|
|
booking_url = resp.json['data'][0]['api']['fillslot_url']
|
|
booking_url2 = resp.json['data'][3]['api']['fillslot_url']
|
|
resp = app.post(booking_url)
|
|
assert resp.json['desk']['label'] == desk.label
|
|
assert resp.json['desk']['slug'] == desk.slug
|
|
# book the same slot and make sure desk 2 info are returned
|
|
resp = app.post(booking_url)
|
|
assert resp.json['desk']['label'] == desk2.label
|
|
assert resp.json['desk']['slug'] == desk2.slug
|
|
# booking slot 3 and make sure desk 1 info are returned
|
|
resp = app.post(booking_url2)
|
|
assert resp.json['desk']['label'] == desk.label
|
|
assert resp.json['desk']['slug'] == desk.slug
|
|
|
|
|
|
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 = 3
|
|
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_agenda_meeting_deleted_meetingtype(app, meetings_agenda, user):
|
|
MeetingType.objects.all().delete()
|
|
meeting_type = MeetingType.objects.create(
|
|
agenda=meetings_agenda, label='Blah 20', duration=20, deleted=True
|
|
)
|
|
resp = app.get(
|
|
'/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug), status=404
|
|
)
|
|
|
|
meeting_type.deleted = False
|
|
meeting_type.save()
|
|
resp = app.get('/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug))
|
|
data = resp.json['data']
|
|
assert len(data) == 216
|
|
|
|
# try to book if disabled
|
|
meeting_type.deleted = True
|
|
meeting_type.save()
|
|
|
|
fillslot_url = data[0]['api']['fillslot_url']
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
resp_booking = app.post(fillslot_url, status=400)
|
|
assert 'invalid meeting type id' in resp_booking.json['err_desc']
|
|
|
|
|
|
def test_datetimes_api_meetings_agenda_start_hour_change(app, meetings_agenda):
|
|
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
|
|
|
|
resp = app.get(api_url)
|
|
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
|
|
ev = Event(
|
|
agenda=meetings_agenda,
|
|
meeting_type=meeting_type,
|
|
places=1,
|
|
full=False,
|
|
start_datetime=make_aware(dt),
|
|
desk=Desk.objects.first(),
|
|
)
|
|
ev.save()
|
|
booking = Booking(event=ev)
|
|
booking.save()
|
|
|
|
resp = app.get(api_url)
|
|
assert len([x for x in resp.json['data'] if x['disabled']]) == 1
|
|
|
|
desk = Desk.objects.get(label='Desk 1')
|
|
# shift opening times by 15 minutes
|
|
for timeperiod in desk.timeperiod_set.all():
|
|
timeperiod.start_time = timeperiod.start_time.replace(minute=15)
|
|
timeperiod.end_time = timeperiod.end_time.replace(minute=15)
|
|
timeperiod.save()
|
|
|
|
# two slots should now be marked as disabled as the previous booking spans
|
|
# them.
|
|
resp = app.get(api_url)
|
|
assert len([x for x in resp.json['data'] if x['disabled']]) == 2
|
|
|
|
|
|
def test_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
|
|
|
|
# cover completely to test limit condition in get_all_slots()
|
|
full_coverage = TimePeriodException.objects.create(
|
|
desk=default_desk,
|
|
start_datetime=make_aware(datetime.datetime(2017, 1, 1, 0, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2018, 1, 1, 0, 0)),
|
|
)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 0
|
|
full_coverage.delete()
|
|
|
|
virtual_meetings_agenda.minimal_booking_delay = 7
|
|
virtual_meetings_agenda.maximal_booking_delay = 28
|
|
virtual_meetings_agenda.save()
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 54
|
|
|
|
virtual_meetings_agenda.minimal_booking_delay = 1
|
|
virtual_meetings_agenda.maximal_booking_delay = 56
|
|
virtual_meetings_agenda.save()
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 144
|
|
|
|
resp = app.get(api_url)
|
|
dt = datetime.datetime.strptime(resp.json['data'][2]['id'].split(':')[1], '%Y-%m-%d-%H%M')
|
|
ev = Event(
|
|
agenda=real_agenda,
|
|
meeting_type=meeting_type,
|
|
places=1,
|
|
full=False,
|
|
start_datetime=make_aware(dt),
|
|
desk=default_desk,
|
|
)
|
|
ev.save()
|
|
booking = Booking(event=ev)
|
|
booking.save()
|
|
resp2 = app.get(api_url)
|
|
assert len(resp2.json['data']) == 144
|
|
assert resp.json['data'][0] == resp2.json['data'][0]
|
|
assert resp.json['data'][1] == resp2.json['data'][1]
|
|
assert resp.json['data'][2] != resp2.json['data'][2]
|
|
assert resp.json['data'][2]['disabled'] is False
|
|
assert resp2.json['data'][2]['disabled'] is True
|
|
assert resp.json['data'][3] == resp2.json['data'][3]
|
|
|
|
# test with a timeperiod overlapping current moment, it should get one
|
|
# datetime for the current timeperiod + two from the next week.
|
|
if localtime(now()).time().hour == 23:
|
|
# skip this part of the test as it would require support for events
|
|
# crossing midnight
|
|
return
|
|
|
|
TimePeriod.objects.filter(desk=default_desk).delete()
|
|
start_time = localtime(now()) - datetime.timedelta(minutes=10)
|
|
time_period = TimePeriod(
|
|
weekday=localtime(now()).weekday(),
|
|
start_time=start_time,
|
|
end_time=start_time + datetime.timedelta(hours=1),
|
|
desk=default_desk,
|
|
)
|
|
time_period.save()
|
|
virtual_meetings_agenda.minimal_booking_delay = 0
|
|
virtual_meetings_agenda.maximal_booking_delay = 10
|
|
virtual_meetings_agenda.save()
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 3
|
|
|
|
|
|
def test_virtual_agendas_meetings_datetimes_api_with_similar_desk(app):
|
|
agenda_foo = Agenda.objects.create(
|
|
label=u'Agenda Foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=4
|
|
)
|
|
meeting_type_foo = MeetingType.objects.create(agenda=agenda_foo, label='Meeting Type', duration=30)
|
|
test_1st_weekday = (localtime(now()).weekday() + 1) % 7
|
|
test_2nd_weekday = (localtime(now()).weekday() + 2) % 7
|
|
test_3rd_weekday = (localtime(now()).weekday() + 3) % 7
|
|
|
|
desk_foo = Desk.objects.create(agenda=agenda_foo, label='Desk 1')
|
|
TimePeriod.objects.create(
|
|
weekday=test_1st_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk_foo,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=test_2nd_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk_foo,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=test_3rd_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk_foo,
|
|
)
|
|
|
|
agenda_bar = Agenda.objects.create(
|
|
label=u'Agenda Bar', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=4
|
|
)
|
|
meeting_type_bar = MeetingType.objects.create(agenda=agenda_bar, label='Meeting Type', duration=30)
|
|
desk_bar = Desk.objects.create(agenda=agenda_bar, label='Desk 1')
|
|
TimePeriod.objects.create(
|
|
weekday=test_1st_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk_bar,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=test_2nd_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk_bar,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=test_3rd_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk_bar,
|
|
)
|
|
|
|
virtual_agenda = Agenda.objects.create(
|
|
label=u'Agenda Virtual', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=4
|
|
)
|
|
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=agenda_foo)
|
|
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=agenda_bar)
|
|
|
|
# 4 slots each day * 3 days
|
|
foo_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda_foo.slug, meeting_type_bar.slug)
|
|
resp = app.get(foo_api_url)
|
|
assert len(resp.json['data']) == 12
|
|
# same thing bar agenda
|
|
bar_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda_foo.slug, meeting_type_bar.slug)
|
|
resp = app.get(bar_api_url)
|
|
assert len(resp.json['data']) == 12
|
|
# same thing on the virtual agenda
|
|
virtual_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, meeting_type_bar.slug)
|
|
resp = app.get(virtual_api_url)
|
|
assert len(resp.json['data']) == 12
|
|
|
|
# exclude first day
|
|
start = (localtime(now()) + datetime.timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
|
end = (localtime(now()) + datetime.timedelta(days=1)).replace(
|
|
hour=23, minute=59, second=59, microsecond=0
|
|
)
|
|
TimePeriodException.objects.create(start_datetime=start, end_datetime=end, desk=desk_foo)
|
|
TimePeriodException.objects.create(start_datetime=start, end_datetime=end, desk=desk_bar)
|
|
# exclude second day
|
|
start = (localtime(now()) + datetime.timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)
|
|
end = (localtime(now()) + datetime.timedelta(days=2)).replace(
|
|
hour=23, minute=59, second=59, microsecond=0
|
|
)
|
|
TimePeriodException.objects.create(start_datetime=start, end_datetime=end, desk=desk_foo)
|
|
TimePeriodException.objects.create(start_datetime=start, end_datetime=end, desk=desk_bar)
|
|
|
|
# 4 slots each day * 1 day
|
|
resp = app.get(foo_api_url)
|
|
assert len(resp.json['data']) == 4
|
|
# same thing bar agenda
|
|
resp = app.get(bar_api_url)
|
|
assert len(resp.json['data']) == 4
|
|
# same thing on the virtual agenda
|
|
resp = app.get(virtual_api_url)
|
|
assert len(resp.json['data']) == 4
|
|
|
|
|
|
def test_virtual_agendas_meetings_datetimes_delays_api(app, mock_now):
|
|
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
|
|
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
|
|
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
|
|
TimePeriod.objects.create(
|
|
weekday=0,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=foo_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=1,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=foo_desk_1,
|
|
)
|
|
|
|
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
|
|
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
|
|
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
|
|
TimePeriod.objects.create(
|
|
weekday=2,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=bar_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=3,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=bar_desk_1,
|
|
)
|
|
|
|
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
|
|
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
|
|
virt_meeting_type = virt_agenda.iter_meetingtypes()[0]
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug)
|
|
resp = app.get(api_url)
|
|
# 8 slots for m each agenda
|
|
assert len(resp.json['data']) == 16
|
|
|
|
# restrict foo's minimal_booking_delay : only bar's slots are left
|
|
foo_agenda.minimal_booking_delay = 6
|
|
foo_agenda.save()
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 8
|
|
|
|
# restrict bar's maximal_booking_delay : only half of bar's slots are left
|
|
bar_agenda.maximal_booking_delay = 5
|
|
bar_agenda.save()
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
|
|
# put back very slots from foo
|
|
foo_agenda.minimal_booking_delay = 1
|
|
foo_agenda.maximal_booking_delay = 7
|
|
foo_agenda.save()
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 12
|
|
|
|
|
|
def test_virtual_agendas_meetings_datetimes_exluded_periods(app, mock_now):
|
|
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
|
|
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
|
|
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
|
|
TimePeriod.objects.create(
|
|
weekday=0,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=foo_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=1,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=foo_desk_1,
|
|
)
|
|
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
|
|
|
|
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
|
|
resp = app.get(api_url)
|
|
# 8 slots
|
|
data = resp.json['data']
|
|
assert len(data) == 8
|
|
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
|
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
|
assert data[2]['datetime'] == '2017-05-22 11:00:00'
|
|
|
|
# exclude one hour the first day
|
|
tp1 = TimePeriod.objects.create(
|
|
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
|
|
)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 6
|
|
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
|
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
|
# no more slots the 22 thanks to the exclusion period
|
|
assert data[2]['datetime'] == '2017-05-23 10:00:00'
|
|
|
|
# exclude the second day
|
|
tp2 = TimePeriod.objects.create(
|
|
weekday=1, start_time=datetime.time(9, 0), end_time=datetime.time(18, 0), agenda=virt_agenda
|
|
)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 2
|
|
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
|
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
|
|
|
# go back to no restriction
|
|
tp1.delete()
|
|
tp2.delete()
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 8
|
|
|
|
# excluded period applies to every desk
|
|
foo_desk_2 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 2')
|
|
TimePeriod.objects.create(
|
|
weekday=3,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=foo_desk_2,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=4,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=foo_desk_2,
|
|
)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 16
|
|
|
|
# exclude one hour the first day
|
|
tp1 = TimePeriod.objects.create(
|
|
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
|
|
)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 14
|
|
|
|
# exclude one hour the last day
|
|
tp2 = TimePeriod.objects.create(
|
|
weekday=4, start_time=datetime.time(11, 0), end_time=datetime.time(12, 0), agenda=virt_agenda
|
|
)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 12
|
|
|
|
# go back to no restriction
|
|
tp1.delete()
|
|
tp2.delete()
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 16
|
|
|
|
# add a second real agenda
|
|
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
|
|
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
|
|
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
|
|
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
|
|
TimePeriod.objects.create(
|
|
weekday=0,
|
|
start_time=datetime.time(14, 0),
|
|
end_time=datetime.time(16, 0),
|
|
desk=bar_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=1,
|
|
start_time=datetime.time(14, 0),
|
|
end_time=datetime.time(16, 0),
|
|
desk=bar_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=2,
|
|
start_time=datetime.time(14, 0),
|
|
end_time=datetime.time(16, 0),
|
|
desk=bar_desk_2,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=3,
|
|
start_time=datetime.time(14, 0),
|
|
end_time=datetime.time(16, 0),
|
|
desk=bar_desk_2,
|
|
)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 32
|
|
|
|
# exclude the first day, 11 to 15 : 4 slots
|
|
tp1 = TimePeriod.objects.create(
|
|
weekday=0, start_time=datetime.time(11, 0), end_time=datetime.time(15, 0), agenda=virt_agenda
|
|
)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 28
|
|
|
|
|
|
def test_virtual_agendas_meetings_exception(app, user, virtual_meetings_agenda):
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
real_agenda = virtual_meetings_agenda.real_agendas.first()
|
|
desk = real_agenda.desk_set.first()
|
|
virt_meeting_type = virtual_meetings_agenda.iter_meetingtypes()[0]
|
|
datetimes_url = '/api/agenda/%s/meetings/%s/datetimes/' % (
|
|
virtual_meetings_agenda.slug,
|
|
virt_meeting_type.slug,
|
|
)
|
|
resp = app.get(datetimes_url)
|
|
|
|
# test exception at the lowest limit
|
|
excp1 = TimePeriodException.objects.create(
|
|
desk=desk,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
|
|
)
|
|
resp2 = app.get(datetimes_url)
|
|
assert len(resp.json['data']) == len(resp2.json['data']) + 4
|
|
|
|
# test exception at the highest limit
|
|
excp1.end_datetime = make_aware(datetime.datetime(2017, 5, 22, 11, 0))
|
|
excp1.save()
|
|
resp2 = app.get(datetimes_url)
|
|
assert len(resp.json['data']) == len(resp2.json['data']) + 2
|
|
|
|
# add an exception with an end datetime less than excp1 end datetime
|
|
# and make sure that excp1 end datetime preveil
|
|
excp1.end_datetime = make_aware(datetime.datetime(2017, 5, 23, 11, 0))
|
|
excp1.save()
|
|
|
|
TimePeriodException.objects.create(
|
|
desk=excp1.desk,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 15, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0)),
|
|
)
|
|
|
|
resp2 = app.get(datetimes_url)
|
|
assert len(resp.json['data']) == len(resp2.json['data']) + 6
|
|
|
|
# with a second desk
|
|
desk2 = Desk.objects.create(label='Desk 2', agenda=real_agenda)
|
|
time_period = desk.timeperiod_set.first()
|
|
TimePeriod.objects.create(
|
|
desk=desk2,
|
|
start_time=time_period.start_time,
|
|
end_time=time_period.end_time,
|
|
weekday=time_period.weekday,
|
|
)
|
|
resp3 = app.get(datetimes_url)
|
|
assert len(resp.json['data']) == len(resp3.json['data']) + 2 # +2 because excp1 changed
|
|
|
|
|
|
def test_virtual_agendas_meetings_datetimes_multiple_agendas(app, mock_now):
|
|
foo_agenda = Agenda.objects.create(
|
|
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
|
|
)
|
|
foo_meeting_type = MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
|
|
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
|
|
|
|
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
|
|
test_2nd_weekday = (localtime(now()).weekday() + 3) % 7
|
|
test_3rd_weekday = (localtime(now()).weekday() + 4) % 7
|
|
test_4th_weekday = (localtime(now()).weekday() + 5) % 7
|
|
|
|
def create_time_perdiods(desk, end=12):
|
|
TimePeriod.objects.create(
|
|
weekday=test_1st_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(end, 0),
|
|
desk=desk,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=test_2nd_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(end, 0),
|
|
desk=desk,
|
|
)
|
|
|
|
create_time_perdiods(foo_desk_1)
|
|
virt_agenda = Agenda.objects.create(
|
|
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=6
|
|
)
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
|
|
virt_meeting_type = virt_agenda.iter_meetingtypes()[0]
|
|
|
|
# We are saturday and we can book for next monday and tuesday, 4 slots available each day
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 8
|
|
assert resp.json['data'][0]['id'] == 'meeting-type:2017-05-22-1000'
|
|
|
|
virt_agenda.maximal_booking_delay = 10 # another monday comes in
|
|
virt_agenda.save()
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 12
|
|
|
|
# Back to next monday and tuesday restriction
|
|
virt_agenda.maximal_booking_delay = 6
|
|
virt_agenda.save()
|
|
|
|
# Add another agenda
|
|
bar_agenda = Agenda.objects.create(
|
|
label='Bar Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=5
|
|
)
|
|
bar_meeting_type = MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
|
|
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
|
|
create_time_perdiods(bar_desk_1, end=13) # bar_agenda has two more slots each day
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 12
|
|
assert len(ctx.captured_queries) == 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=bar_agenda, label='Bar desk 1')
|
|
|
|
TimePeriod.objects.create(
|
|
weekday=0,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=bar_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=1,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=bar_desk_1,
|
|
)
|
|
|
|
virt_agenda = Agenda.objects.create(
|
|
label='Virtual Agenda', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=5
|
|
)
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
|
|
virt_meeting_type = virt_agenda.iter_meetingtypes()[0]
|
|
# We are saturday and we can book for next monday and tuesday, 4 slots available each day
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virt_agenda.slug, virt_meeting_type.slug)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 8
|
|
|
|
# make a booking
|
|
fillslot_url = resp.json['data'][0]['api']['fillslot_url']
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
resp_booking = app.post(fillslot_url)
|
|
assert Booking.objects.count() == 1
|
|
booking = Booking.objects.get(pk=resp_booking.json['booking_id'])
|
|
assert (
|
|
resp_booking.json['datetime']
|
|
== localtime(booking.event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
|
|
== resp.json['data'][0]['datetime']
|
|
)
|
|
first_booking_agenda = resp_booking.json['agenda']['slug']
|
|
|
|
assert resp_booking.json['end_datetime'] == localtime(
|
|
Booking.objects.all()[0].event.end_datetime
|
|
).strftime('%Y-%m-%d %H:%M:%S')
|
|
assert resp_booking.json['duration'] == 30
|
|
|
|
# second booking on the same slot (available on the second real agenda)
|
|
resp_booking = app.post(fillslot_url)
|
|
assert Booking.objects.count() == 2
|
|
booking = Booking.objects.get(pk=resp_booking.json['booking_id'])
|
|
assert (
|
|
resp_booking.json['datetime']
|
|
== localtime(booking.event.start_datetime).strftime('%Y-%m-%d %H:%M:%S')
|
|
== resp.json['data'][0]['datetime']
|
|
)
|
|
second_booking_agenda = resp_booking.json['agenda']['slug']
|
|
assert set([first_booking_agenda, second_booking_agenda]) == {'foo-meeting', 'bar-meeting'}
|
|
|
|
# try booking the same timeslot a third time: full
|
|
resp_booking = app.post(fillslot_url)
|
|
assert resp_booking.json['err'] == 1
|
|
assert resp_booking.json['err_class'] == 'no more desk available'
|
|
assert resp_booking.json['err_desc'] == 'no more desk available'
|
|
|
|
|
|
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
|
|
|
|
|
|
@pytest.mark.freeze_time('2017-04-01')
|
|
def test_duration_on_booking_api_fillslot_response(app, user):
|
|
agenda = Agenda(label=u'Foo bar')
|
|
agenda.save()
|
|
first_date = datetime.datetime(2017, 5, 20, 1, 12)
|
|
durations = [None, 0, 45]
|
|
evt = []
|
|
for i in range(3):
|
|
evt.append(
|
|
Event(
|
|
start_datetime=first_date + datetime.timedelta(days=i),
|
|
duration=durations[i],
|
|
places=20,
|
|
agenda=agenda,
|
|
)
|
|
)
|
|
evt[i].save()
|
|
|
|
assert evt[0].end_datetime is None
|
|
assert evt[1].end_datetime == evt[1].start_datetime
|
|
assert evt[2].end_datetime == evt[2].start_datetime + datetime.timedelta(minutes=45)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[0].id))
|
|
assert resp.json['datetime'] == '2017-05-20 01:12:00'
|
|
assert resp.json['end_datetime'] is None
|
|
assert 'ics_url' in resp.json['api']
|
|
ics = app.get(resp.json['api']['ics_url']).text
|
|
assert 'DTSTART:20170519T231200Z' in ics
|
|
assert 'DTEND:' not in ics
|
|
|
|
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[1].id))
|
|
assert resp.json['datetime'] == '2017-05-21 01:12:00'
|
|
assert resp.json['end_datetime'] == resp.json['datetime']
|
|
assert 'ics_url' in resp.json['api']
|
|
ics = app.get(resp.json['api']['ics_url']).text
|
|
assert 'DTSTART:20170520T231200Z' in ics
|
|
assert 'DTEND:20170520T231200Z' in ics
|
|
|
|
resp = app.post('/api/agenda/%s/fillslot/%s/' % (agenda.slug, evt[2].id))
|
|
assert resp.json['datetime'] == '2017-05-22 01:12:00'
|
|
assert resp.json['end_datetime'] == '2017-05-22 01:57:00'
|
|
assert 'ics_url' in resp.json['api']
|
|
ics = app.get(resp.json['api']['ics_url']).text
|
|
assert 'DTSTART:20170521T231200Z' in ics
|
|
assert 'DTEND:20170521T235700Z' in ics
|
|
|
|
|
|
@pytest.mark.freeze_time('2017-04-01')
|
|
def test_duration_on_booking_api_fillslots_response(app, user):
|
|
agenda = Agenda(label=u'Foo bar')
|
|
agenda.save()
|
|
first_date = datetime.datetime(2017, 5, 20, 1, 12)
|
|
durations = [None, 0, 45]
|
|
evt = []
|
|
for i in range(3):
|
|
evt.append(
|
|
Event(
|
|
start_datetime=first_date + datetime.timedelta(days=i),
|
|
duration=durations[i],
|
|
places=20,
|
|
agenda=agenda,
|
|
)
|
|
)
|
|
evt[i].save()
|
|
|
|
assert evt[0].end_datetime is None
|
|
assert evt[1].end_datetime == evt[1].start_datetime
|
|
assert evt[2].end_datetime == evt[2].start_datetime + datetime.timedelta(minutes=45)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# first event having null duration
|
|
string_param = ','.join([str(e.id) for e in evt[::-1]]) # unordered parameters
|
|
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': string_param})
|
|
r_evt = resp.json['events']
|
|
|
|
assert r_evt[0]['datetime'] == '2017-05-20 01:12:00'
|
|
assert r_evt[0]['end_datetime'] is None
|
|
assert r_evt[1]['datetime'] == '2017-05-21 01:12:00'
|
|
assert r_evt[1]['end_datetime'] == r_evt[1]['datetime']
|
|
assert r_evt[2]['datetime'] == '2017-05-22 01:12:00'
|
|
assert r_evt[2]['end_datetime'] == '2017-05-22 01:57:00'
|
|
assert 'ics_url' in resp.json['api']
|
|
ics = app.get(resp.json['api']['ics_url']).text
|
|
assert 'DTSTART:20170519T231200Z' in ics
|
|
assert 'DTEND:' not in ics
|
|
|
|
# first event having duration
|
|
evt[0].duration = 90
|
|
evt[0].save()
|
|
resp = app.post_json('/api/agenda/%s/fillslots/' % agenda.slug, params={'slots': string_param})
|
|
r_evt = resp.json['events']
|
|
|
|
assert r_evt[0]['datetime'] == '2017-05-20 01:12:00'
|
|
assert r_evt[0]['end_datetime'] == '2017-05-20 02:42:00'
|
|
assert 'ics_url' in resp.json['api']
|
|
ics = app.get(resp.json['api']['ics_url']).text
|
|
assert 'DTSTART:20170519T231200Z' in ics
|
|
assert 'DTEND:20170520T004200Z' in ics
|
|
|
|
|
|
@pytest.mark.freeze_time('2017-05-21')
|
|
def test_unavailabilitycalendar_meetings_datetimes(app, user):
|
|
meetings_agenda = Agenda.objects.create(label='Meeting', kind='meetings', maximal_booking_delay=7)
|
|
desk = Desk.objects.create(agenda=meetings_agenda, label='desk 1')
|
|
meeting_type = MeetingType.objects.create(agenda=meetings_agenda, label='Meeting Type', duration=30)
|
|
TimePeriod.objects.create(
|
|
weekday=0,
|
|
start_time=datetime.time(9, 0),
|
|
end_time=datetime.time(18, 0),
|
|
desk=desk,
|
|
)
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
datetimes_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meetings_agenda.slug, meeting_type.slug)
|
|
resp = app.get(datetimes_url)
|
|
assert len(resp.json['data']) == 18
|
|
|
|
# create an unvalailability calendar
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='foo holydays')
|
|
TimePeriodException.objects.create(
|
|
unavailability_calendar=unavailability_calendar,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 10, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
|
|
)
|
|
unavailability_calendar.desks.add(desk)
|
|
# link unavailability calendar to a desk of another agenda
|
|
other_agenda = Agenda.objects.create(label='Meeting 2', kind='meetings', maximal_booking_delay=7)
|
|
other_desk = Desk.objects.create(agenda=other_agenda, label='desk 1')
|
|
unavailability_calendar.desks.add(other_desk)
|
|
unavailability_calendar2 = UnavailabilityCalendar.objects.create(label='bar holydays')
|
|
unavailability_calendar2.desks.add(desk)
|
|
|
|
# 2 slots are gone
|
|
with CaptureQueriesContext(connection) as ctx:
|
|
resp2 = app.get(datetimes_url)
|
|
assert len(ctx.captured_queries) == 10
|
|
assert len(resp.json['data']) == len(resp2.json['data']) + 2
|
|
|
|
# add a standard desk exception
|
|
TimePeriodException.objects.create(
|
|
desk=desk,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
|
|
)
|
|
# 4 slots are gone
|
|
resp3 = app.get(datetimes_url)
|
|
assert len(resp.json['data']) == len(resp3.json['data']) + 4
|
|
|
|
|
|
def test_unavailabilitycalendar_on_virtual_datetimes(app, user, mock_now):
|
|
foo_agenda = Agenda.objects.create(label='Foo Meeting', kind='meetings', maximal_booking_delay=7)
|
|
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
|
|
foo_desk_1 = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
|
|
TimePeriod.objects.create(
|
|
weekday=0,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=foo_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=1,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=foo_desk_1,
|
|
)
|
|
virt_agenda = Agenda.objects.create(label='Virtual Agenda', kind='virtual')
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=foo_agenda)
|
|
|
|
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (virt_agenda.slug)
|
|
resp = app.get(api_url)
|
|
# 8 slots
|
|
data = resp.json['data']
|
|
assert len(data) == 8
|
|
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
|
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
|
assert data[2]['datetime'] == '2017-05-22 11:00:00'
|
|
|
|
# exclude one hour the first day through an unvalailability calendar on the foo agenda
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='foo holydays')
|
|
TimePeriodException.objects.create(
|
|
unavailability_calendar=unavailability_calendar,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 22, 11, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 22, 12, 0)),
|
|
)
|
|
unavailability_calendar.desks.add(foo_desk_1)
|
|
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 6
|
|
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
|
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
|
# no more slots the 22 thanks to the unavailability calendar
|
|
assert data[2]['datetime'] == '2017-05-23 10:00:00'
|
|
|
|
# exclude the second day
|
|
TimePeriodException.objects.create(
|
|
unavailability_calendar=unavailability_calendar,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 23, 9, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 18, 0)),
|
|
)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 2
|
|
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
|
assert data[1]['datetime'] == '2017-05-22 10:30:00'
|
|
|
|
# add a second real agenda
|
|
bar_agenda = Agenda.objects.create(label='Bar Meeting', kind='meetings', maximal_booking_delay=7)
|
|
VirtualMember.objects.create(virtual_agenda=virt_agenda, real_agenda=bar_agenda)
|
|
MeetingType.objects.create(agenda=bar_agenda, label='Meeting Type', duration=30)
|
|
bar_desk_1 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 1')
|
|
bar_desk_2 = Desk.objects.create(agenda=bar_agenda, label='Bar desk 2')
|
|
TimePeriod.objects.create(
|
|
weekday=0,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=bar_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=1,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=bar_desk_1,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=0,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=bar_desk_2,
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=1,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=bar_desk_2,
|
|
)
|
|
|
|
# bar_agenda has the same time periods than foo_agenda, but no unavailability calendar
|
|
# so we are back at the start : 8 slots
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 8
|
|
|
|
# exclude one hour the second day through another unvalailability calendar on the bar agenda
|
|
unavailability_calendar = UnavailabilityCalendar.objects.create(label='bar holydays')
|
|
TimePeriodException.objects.create(
|
|
unavailability_calendar=unavailability_calendar,
|
|
start_datetime=make_aware(datetime.datetime(2017, 5, 23, 11, 0)),
|
|
end_datetime=make_aware(datetime.datetime(2017, 5, 23, 12, 0)),
|
|
)
|
|
unavailability_calendar.desks.add(bar_desk_1, bar_desk_2)
|
|
|
|
# 2 slots are gone
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 6
|
|
assert data[0]['datetime'] == '2017-05-22 10:00:00'
|
|
assert data[-1]['datetime'] == '2017-05-23 10:30:00'
|
|
|
|
|
|
@pytest.mark.parametrize('test_datetime', (None, '2020-11-11 23:50', '2020-12-06 10:14'))
|
|
def test_datetimes_maximal_booking_delay(app, user, freezer, test_datetime):
|
|
if test_datetime:
|
|
freezer.move_to(test_datetime)
|
|
foo_agenda = Agenda.objects.create(
|
|
label='Foo Meeting', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=1
|
|
)
|
|
MeetingType.objects.create(agenda=foo_agenda, label='Meeting Type', duration=30)
|
|
foo_desk = Desk.objects.create(agenda=foo_agenda, label='Foo desk 1')
|
|
weekday1 = ((localtime(now())).weekday() + 1) % 7
|
|
weekday2 = ((localtime(now())).weekday() + 2) % 7
|
|
TimePeriod.objects.create(
|
|
weekday=weekday1, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk
|
|
)
|
|
TimePeriod.objects.create(
|
|
weekday=weekday2, start_time=datetime.time(10, 0), end_time=datetime.time(12, 0), desk=foo_desk
|
|
)
|
|
|
|
api_url = '/api/agenda/%s/meetings/meeting-type/datetimes/' % (foo_agenda.slug)
|
|
resp = app.get(api_url)
|
|
# minimal_booking_delay=1 and maximal_booking_delay=1 so no slots
|
|
data = resp.json['data']
|
|
assert len(data) == 0
|
|
|
|
foo_agenda.maximal_booking_delay = 2
|
|
foo_agenda.save()
|
|
# 4 slots each day * 1 day (because minimal_booking_delay=1 and maximal_booking_delay=2)
|
|
resp = app.get(api_url)
|
|
data = resp.json['data']
|
|
assert len(data) == 4
|
|
|
|
|
|
def test_meetings_and_virtual_datetimes_date_filter(app):
|
|
agenda_foo = Agenda.objects.create(
|
|
label=u'Agenda Foo', kind='meetings', minimal_booking_delay=1, maximal_booking_delay=7
|
|
)
|
|
meeting_type = MeetingType.objects.create(agenda=agenda_foo, label='Meeting Type', duration=30)
|
|
desk_foo = Desk.objects.create(agenda=agenda_foo, label='Desk 1')
|
|
weekday1 = ((localtime(now())).weekday() + 1) % 7
|
|
weekday2 = ((localtime(now())).weekday() + 2) % 7
|
|
weekday3 = ((localtime(now())).weekday() + 3) % 7
|
|
weekday4 = ((localtime(now())).weekday() + 4) % 7
|
|
weekday5 = ((localtime(now())).weekday() + 5) % 7
|
|
weekday6 = ((localtime(now())).weekday() + 6) % 7
|
|
for weekday in (weekday1, weekday2, weekday3, weekday4, weekday5, weekday6):
|
|
TimePeriod.objects.create(
|
|
weekday=weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk_foo,
|
|
)
|
|
|
|
virtual_agenda = Agenda.objects.create(
|
|
label=u'Agenda Virtual', kind='virtual', minimal_booking_delay=1, maximal_booking_delay=7
|
|
)
|
|
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=agenda_foo)
|
|
|
|
# 4 slots each day * 6 days
|
|
foo_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (agenda_foo.slug, meeting_type.slug)
|
|
resp = app.get(foo_api_url)
|
|
assert len(resp.json['data']) == 24
|
|
virtual_api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, meeting_type.slug)
|
|
resp = app.get(virtual_api_url)
|
|
assert len(resp.json['data']) == 24
|
|
|
|
params = {'date_start': 'foo'}
|
|
resp = app.get(foo_api_url, params=params, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD'
|
|
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD'
|
|
|
|
params = {'date_end': 'foo'}
|
|
resp = app.get(foo_api_url, params=params, status=400)
|
|
assert resp.json['err'] == 1
|
|
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD'
|
|
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD'
|
|
|
|
# exclude weekday1 through date_start, 4 slots each day * 5 days
|
|
params = {'date_start': (localtime(now()) + datetime.timedelta(days=2)).date().isoformat()}
|
|
resp = app.get(foo_api_url, params=params)
|
|
assert len(resp.json['data']) == 20
|
|
resp = app.get(virtual_api_url, params=params)
|
|
assert len(resp.json['data']) == 20
|
|
|
|
# minimal_booking_delay (which exclude weekday1 and wekkday2 ) takes precedence
|
|
# 4 slots each day * 4 days
|
|
agenda_foo.minimal_booking_delay = 3
|
|
agenda_foo.save()
|
|
resp = app.get(foo_api_url, params=params)
|
|
assert len(resp.json['data']) == 16
|
|
# also on virtual agenda
|
|
virtual_agenda.minimal_booking_delay = 3
|
|
virtual_agenda.save()
|
|
resp = app.get(virtual_api_url, params=params)
|
|
assert len(resp.json['data']) == 16
|
|
|
|
# reset
|
|
agenda_foo.minimal_booking_delay = 1
|
|
virtual_agenda.minimal_booking_delay = 1
|
|
agenda_foo.save()
|
|
virtual_agenda.save()
|
|
|
|
# exclude weekday6 through date_end, 4 slots each day * 5 days
|
|
params = {'date_end': (localtime(now()) + datetime.timedelta(days=6)).date().isoformat()}
|
|
resp = app.get(foo_api_url, params=params)
|
|
assert len(resp.json['data']) == 20
|
|
resp = app.get(virtual_api_url, params=params)
|
|
assert len(resp.json['data']) == 20
|
|
|
|
# maximal_booking_delay (which exclude weekday5 and weekday6 ) takes precedence
|
|
# 4 slots each day * 4 days
|
|
agenda_foo.maximal_booking_delay = 5
|
|
agenda_foo.save()
|
|
resp = app.get(foo_api_url, params=params)
|
|
assert len(resp.json['data']) == 16
|
|
# also on virtual agenda
|
|
virtual_agenda.maximal_booking_delay = 5
|
|
virtual_agenda.save()
|
|
resp = app.get(virtual_api_url, params=params)
|
|
assert len(resp.json['data']) == 16
|
|
|
|
|
|
def test_datetimes_api_meta(app, freezer):
|
|
# 2017-05-20 -> saturday
|
|
freezer.move_to(make_aware(datetime.datetime(year=2017, month=5, day=20, hour=1, minute=12)))
|
|
|
|
agenda = Agenda.objects.create(label=u'Foo bar')
|
|
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()
|
|
# a date in the past
|
|
event = Event(start_datetime=first_date - datetime.timedelta(days=10), places=10, agenda=agenda)
|
|
event.save()
|
|
|
|
events = Event.objects.filter(agenda_id=agenda.id).exclude(start_datetime__lt=now())
|
|
assert len(events) == 3
|
|
api_url = '/api/agenda/%s/datetimes/' % agenda.slug
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 3
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 3,
|
|
'bookable_datetimes_number_available': 3,
|
|
'first_bookable_slot': resp.json['data'][0],
|
|
}
|
|
|
|
def simulate_booking(event, nb_places):
|
|
for i in range(nb_places):
|
|
Booking(event=event).save()
|
|
|
|
simulate_booking(events[0], 10)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 3
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 3,
|
|
'bookable_datetimes_number_available': 3,
|
|
'first_bookable_slot': resp.json['data'][0],
|
|
}
|
|
|
|
simulate_booking(events[0], 10)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 3
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 3,
|
|
'bookable_datetimes_number_available': 2,
|
|
'first_bookable_slot': resp.json['data'][1],
|
|
}
|
|
|
|
simulate_booking(events[1], 20)
|
|
simulate_booking(events[2], 20)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 3
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': True,
|
|
'bookable_datetimes_number_total': 3,
|
|
'bookable_datetimes_number_available': 0,
|
|
'first_bookable_slot': None,
|
|
}
|
|
|
|
|
|
def test_datetimes_api_meetings_agenda_meta(app, freezer):
|
|
# 2017-05-20 -> saturday
|
|
freezer.move_to(make_aware(datetime.datetime(year=2017, month=5, day=20, hour=1, minute=12)))
|
|
meetings_agenda = Agenda.objects.create(
|
|
label=u'Foo bar Meeting', kind='meetings', maximal_booking_delay=3
|
|
)
|
|
meeting_type = MeetingType(agenda=meetings_agenda, label='Blah', duration=30)
|
|
meeting_type.save()
|
|
|
|
desk1 = Desk.objects.create(agenda=meetings_agenda, label='Desk 1')
|
|
desk2 = Desk.objects.create(agenda=meetings_agenda, label='Desk 2')
|
|
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
|
|
for desk in desk1, desk2:
|
|
TimePeriod(
|
|
weekday=test_1st_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk,
|
|
).save()
|
|
|
|
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
assert resp.json['data'][2]['disabled'] is False
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 4,
|
|
'bookable_datetimes_number_available': 4,
|
|
'first_bookable_slot': resp.json['data'][0],
|
|
}
|
|
|
|
def simulate_booking(slot, desk):
|
|
dt = datetime.datetime.strptime(slot['id'].split(':')[1], '%Y-%m-%d-%H%M')
|
|
ev = Event(
|
|
agenda=meetings_agenda,
|
|
meeting_type=meeting_type,
|
|
places=1,
|
|
full=False,
|
|
start_datetime=make_aware(dt),
|
|
desk=desk,
|
|
)
|
|
ev.save()
|
|
booking = Booking(event=ev)
|
|
booking.save()
|
|
|
|
simulate_booking(resp.json['meta']['first_bookable_slot'], desk1)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
assert resp.json['data'][0]['disabled'] is False
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 4,
|
|
'bookable_datetimes_number_available': 4,
|
|
'first_bookable_slot': resp.json['data'][0],
|
|
}
|
|
|
|
simulate_booking(resp.json['meta']['first_bookable_slot'], desk2)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
assert resp.json['data'][0]['disabled'] is True
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 4,
|
|
'bookable_datetimes_number_available': 3,
|
|
'first_bookable_slot': resp.json['data'][1],
|
|
}
|
|
|
|
for idx in range(1, 4):
|
|
simulate_booking(resp.json['data'][idx], desk1)
|
|
simulate_booking(resp.json['data'][idx], desk2)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': True,
|
|
'bookable_datetimes_number_total': 4,
|
|
'bookable_datetimes_number_available': 0,
|
|
'first_bookable_slot': None,
|
|
}
|
|
|
|
|
|
def test_datetimes_api_virtual_meetings_agenda_meta(app, freezer):
|
|
# 2017-05-20 -> saturday
|
|
freezer.move_to(make_aware(datetime.datetime(year=2017, month=5, day=20, hour=1, minute=12)))
|
|
meetings_agenda1 = Agenda.objects.create(label=u'Foo Meeting', kind='meetings', maximal_booking_delay=3)
|
|
meetings_agenda2 = Agenda.objects.create(label=u'Bar Meeting', kind='meetings', maximal_booking_delay=3)
|
|
virtual_agenda = Agenda.objects.create(label=u'Agenda Virtual', kind='virtual', maximal_booking_delay=3)
|
|
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda1)
|
|
VirtualMember.objects.create(virtual_agenda=virtual_agenda, real_agenda=meetings_agenda2)
|
|
desk1 = Desk.objects.create(agenda=meetings_agenda1, label='Desk 1')
|
|
desk2 = Desk.objects.create(agenda=meetings_agenda2, label='Desk 2')
|
|
|
|
test_1st_weekday = (localtime(now()).weekday() + 2) % 7
|
|
for agenda, desk in zip((meetings_agenda1, meetings_agenda2), (desk1, desk2)):
|
|
meeting_type = MeetingType.objects.create(agenda=agenda, label='Blah', duration=30)
|
|
TimePeriod(
|
|
weekday=test_1st_weekday,
|
|
start_time=datetime.time(10, 0),
|
|
end_time=datetime.time(12, 0),
|
|
desk=desk,
|
|
).save()
|
|
|
|
virt_meeting_type = virtual_agenda.iter_meetingtypes()[0]
|
|
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (virtual_agenda.slug, virt_meeting_type.slug)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
assert resp.json['data'][2]['disabled'] is False
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 4,
|
|
'bookable_datetimes_number_available': 4,
|
|
'first_bookable_slot': resp.json['data'][0],
|
|
}
|
|
|
|
def simulate_booking(slot, agenda, desk):
|
|
dt = datetime.datetime.strptime(slot['id'].split(':')[1], '%Y-%m-%d-%H%M')
|
|
ev = Event(
|
|
agenda=agenda,
|
|
meeting_type=meeting_type,
|
|
places=1,
|
|
full=False,
|
|
start_datetime=make_aware(dt),
|
|
desk=desk,
|
|
)
|
|
ev.save()
|
|
booking = Booking(event=ev)
|
|
booking.save()
|
|
|
|
simulate_booking(resp.json['data'][0], meetings_agenda1, desk1)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
assert resp.json['data'][0]['disabled'] is False
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 4,
|
|
'bookable_datetimes_number_available': 4,
|
|
'first_bookable_slot': resp.json['data'][0],
|
|
}
|
|
|
|
simulate_booking(resp.json['data'][0], meetings_agenda2, desk2)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
assert resp.json['data'][0]['disabled'] is True
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': False,
|
|
'bookable_datetimes_number_total': 4,
|
|
'bookable_datetimes_number_available': 3,
|
|
'first_bookable_slot': resp.json['data'][1],
|
|
}
|
|
|
|
for idx in range(1, 4):
|
|
simulate_booking(resp.json['data'][idx], meetings_agenda1, desk1)
|
|
simulate_booking(resp.json['data'][idx], meetings_agenda2, desk2)
|
|
resp = app.get(api_url)
|
|
assert len(resp.json['data']) == 4
|
|
assert resp.json['meta'] == {
|
|
'no_bookable_datetimes': True,
|
|
'bookable_datetimes_number_total': 4,
|
|
'bookable_datetimes_number_available': 0,
|
|
'first_bookable_slot': None,
|
|
}
|
|
|
|
|
|
def test_recurring_events_api(app, user, freezer):
|
|
freezer.move_to('2021-01-12 12:05') # Tuesday
|
|
agenda = Agenda.objects.create(
|
|
label='Foo bar', kind='events', minimal_booking_delay=1, maximal_booking_delay=30
|
|
)
|
|
base_event = Event.objects.create(
|
|
slug='abc', label='Test', start_datetime=localtime(), repeat='weekly', places=5, agenda=agenda
|
|
)
|
|
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
data = resp.json['data']
|
|
assert len(data) == 4
|
|
assert data[0]['id'] == 'abc:2021-01-19-1305'
|
|
assert data[0]['datetime'] == '2021-01-19 13:05:00'
|
|
assert data[0]['text'] == 'Test (Jan. 19, 2021, 1:05 p.m.)'
|
|
assert data[3]['id'] == 'abc:2021-02-09-1305'
|
|
assert Event.objects.count() == 1
|
|
|
|
fillslot_url = data[0]['api']['fillslot_url']
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
|
|
# book first event
|
|
resp = app.post(fillslot_url)
|
|
assert resp.json['err'] == 0
|
|
assert Event.objects.count() == 2
|
|
event = Booking.objects.get(pk=resp.json['booking_id']).event
|
|
assert event.slug == 'abc--2021-01-19-1305'
|
|
|
|
# first event is now a real event in datetimes
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
data = resp.json['data']
|
|
assert len(data) == 4
|
|
assert data[0]['id'] == event.slug
|
|
new_fillslot_url = data[0]['api']['fillslot_url']
|
|
|
|
# booking again with both old and new urls works
|
|
resp = app.post(fillslot_url)
|
|
assert resp.json['err'] == 0
|
|
resp = app.post(new_fillslot_url)
|
|
assert resp.json['err'] == 0
|
|
assert Event.objects.count() == 2
|
|
assert event.booking_set.count() == 3
|
|
|
|
# status and bookings api also create a real event
|
|
status_url = data[1]['api']['status_url']
|
|
resp = app.get(status_url)
|
|
assert resp.json['places']['total'] == 5
|
|
assert Event.objects.count() == 3
|
|
|
|
bookings_url = data[2]['api']['bookings_url']
|
|
resp = app.get(bookings_url, params={'user_external_id': '42'})
|
|
assert resp.json['data'] == []
|
|
assert Event.objects.count() == 4
|
|
|
|
# cancelled recurrences do not appear
|
|
event.cancel()
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
assert len(resp.json['data']) == 3
|
|
assert resp.json['data'][0]['id'] == 'abc--2021-01-26-1305'
|
|
|
|
# publication date is accounted for
|
|
Event.objects.filter(primary_event=base_event).delete()
|
|
base_event.publication_date = now().replace(day=27)
|
|
base_event.save()
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
assert len(resp.json['data']) == 2
|
|
assert resp.json['data'][0]['id'] == 'abc:2021-02-02-1305'
|
|
|
|
|
|
def test_recurring_events_api_various_times(app, user, mock_now):
|
|
agenda = Agenda.objects.create(
|
|
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=30
|
|
)
|
|
event = Event.objects.create(
|
|
slug='abc', start_datetime=localtime(), repeat='weekly', places=5, agenda=agenda
|
|
)
|
|
event.refresh_from_db()
|
|
|
|
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
|
|
assert len(resp.json['data']) == 5
|
|
fillslot_url = resp.json['data'][0]['api']['fillslot_url']
|
|
|
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
|
resp = app.post(fillslot_url)
|
|
assert resp.json['err'] == 0
|
|
|
|
new_event = Booking.objects.get(pk=resp.json['booking_id']).event
|
|
assert event.start_datetime == new_event.start_datetime
|