chrono/tests/api/test_agenda.py

605 lines
23 KiB
Python

import datetime
import pytest
from django.contrib.auth.models import Group
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, now
from chrono.agendas.models import (
Agenda,
Booking,
Category,
Desk,
Event,
EventsType,
Resource,
TimePeriodException,
)
pytestmark = pytest.mark.django_db
def test_agendas_api(app):
edit_group = Group.objects.create(name='Edit')
view_group = Group.objects.create(name='View')
category_a = Category.objects.create(label='Category A')
category_b = Category.objects.create(label='Category B')
events_type = EventsType.objects.create(label='Type A')
events_type2 = EventsType.objects.create(label='Type B')
event_agenda = Agenda.objects.create(
label='Foo bar',
category=category_a,
events_type=events_type,
edit_role=edit_group,
)
Desk.objects.create(agenda=event_agenda, slug='_exceptions_holder')
event_agenda2 = Agenda.objects.create(label='Foo bar 2', category=category_a, events_type=events_type2)
Desk.objects.create(agenda=event_agenda2, slug='_exceptions_holder')
event_agenda3 = Agenda.objects.create(label='Foo bar 3')
Desk.objects.create(agenda=event_agenda3, slug='_exceptions_holder')
meetings_agenda1 = Agenda.objects.create(
label='Foo bar Meeting', kind='meetings', category=category_b, view_role=view_group
)
meetings_agenda2 = 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)
virtual_agenda = Agenda.objects.create(
label='Virtual Agenda',
kind='virtual',
minimal_booking_delay=1,
maximal_booking_delay=56,
edit_role=edit_group,
view_role=view_group,
)
resp = app.get('/api/agenda/')
assert resp.json == {
'err': 0,
'data': [
{
'text': 'Foo bar',
'id': 'foo-bar',
'slug': 'foo-bar',
'kind': 'events',
'minimal_booking_delay': 1,
'minimal_booking_delay_in_working_days': False,
'maximal_booking_delay': 56,
'edit_role': 'Edit',
'view_role': None,
'category': 'category-a',
'category_label': 'Category A',
'events_type': 'type-a',
'api': {
'datetimes_url': 'http://testserver/api/agenda/foo-bar/datetimes/',
'fillslots_url': 'http://testserver/api/agenda/foo-bar/fillslots/',
'backoffice_url': 'http://testserver/manage/agendas/%s/' % event_agenda.pk,
},
},
{
'text': 'Foo bar 2',
'id': 'foo-bar-2',
'kind': 'events',
'slug': 'foo-bar-2',
'minimal_booking_delay': 1,
'minimal_booking_delay_in_working_days': False,
'maximal_booking_delay': 56,
'edit_role': None,
'view_role': None,
'category': 'category-a',
'category_label': 'Category A',
'events_type': 'type-b',
'api': {
'datetimes_url': 'http://testserver/api/agenda/foo-bar-2/datetimes/',
'fillslots_url': 'http://testserver/api/agenda/foo-bar-2/fillslots/',
'backoffice_url': 'http://testserver/manage/agendas/%s/' % event_agenda2.pk,
},
},
{
'text': 'Foo bar 3',
'id': 'foo-bar-3',
'kind': 'events',
'slug': 'foo-bar-3',
'minimal_booking_delay': 1,
'minimal_booking_delay_in_working_days': False,
'maximal_booking_delay': 56,
'edit_role': None,
'view_role': None,
'category': None,
'category_label': None,
'events_type': None,
'api': {
'datetimes_url': 'http://testserver/api/agenda/foo-bar-3/datetimes/',
'fillslots_url': 'http://testserver/api/agenda/foo-bar-3/fillslots/',
'backoffice_url': 'http://testserver/manage/agendas/%s/' % event_agenda3.pk,
},
},
{
'text': 'Foo bar Meeting',
'id': 'foo-bar-meeting',
'slug': 'foo-bar-meeting',
'minimal_booking_delay': 1,
'maximal_booking_delay': 56,
'edit_role': None,
'view_role': 'View',
'category': 'category-b',
'category_label': 'Category B',
'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/',
'resources_url': 'http://testserver/api/agenda/foo-bar-meeting/resources/',
'fillslots_url': 'http://testserver/api/agenda/foo-bar-meeting/fillslots/',
'backoffice_url': 'http://testserver/manage/agendas/%s/' % meetings_agenda1.pk,
},
},
{
'text': 'Foo bar Meeting 2',
'id': 'foo-bar-meeting-2',
'slug': 'foo-bar-meeting-2',
'minimal_booking_delay': 1,
'maximal_booking_delay': 56,
'edit_role': None,
'view_role': None,
'category': None,
'category_label': None,
'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/',
'resources_url': 'http://testserver/api/agenda/foo-bar-meeting-2/resources/',
'fillslots_url': 'http://testserver/api/agenda/foo-bar-meeting-2/fillslots/',
'backoffice_url': 'http://testserver/manage/agendas/%s/' % meetings_agenda2.pk,
},
},
{
'text': 'Virtual Agenda',
'id': 'virtual-agenda',
'slug': 'virtual-agenda',
'minimal_booking_delay': 1,
'maximal_booking_delay': 56,
'edit_role': 'Edit',
'view_role': 'View',
'category': None,
'category_label': None,
'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/',
'backoffice_url': 'http://testserver/manage/agendas/%s/' % virtual_agenda.pk,
},
},
],
}
resp = app.get('/api/agenda/', params={'q': 'foo'})
assert len(resp.json['data']) == 5
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']) == 6
resp = app.get('/api/agenda/', params={'category': '__none__'})
assert len(resp.json['data']) == 3
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=1,
agenda=event_agenda,
)
event2 = Event.objects.create(
start_datetime=(localtime() + datetime.timedelta(days=10)).replace(hour=10, minute=0),
places=1,
agenda=event_agenda,
)
event3 = Event.objects.create(
start_datetime=(localtime() + datetime.timedelta(days=15)).replace(hour=10, minute=0),
places=1,
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
event1.booking_set.create()
event1.refresh_from_db()
assert event1.full is True
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
assert len(resp.json['data']) == 1
# all events are full
event2.booking_set.create()
event2.refresh_from_db()
assert event2.full is True
event3.booking_set.create()
event3.refresh_from_db()
assert event3.full is True
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
assert len(resp.json['data']) == 0
# event1 is not full but too soon
event1.booking_set.all().delete()
event1.refresh_from_db()
assert event1.full is 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
event3.booking_set.all().delete()
event3.refresh_from_db()
assert event3.full is False
event_agenda.maximal_booking_delay = 12
event_agenda.save()
del event_agenda.max_booking_datetime
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
event2.booking_set.all().delete()
event2.refresh_from_db()
assert event2.full is False
event_agenda.event_set.update(publication_datetime=now() + 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,
recurrence_days=list(range(7)),
recurrence_end_date=now() + datetime.timedelta(days=15),
)
event.create_all_recurrences()
assert len(event_agenda.get_open_events()) == 2
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
assert len(resp.json['data']) == 1
for _ in range(10):
event_agenda = Agenda.objects.create(label='Foo bar', category=category_a)
Desk.objects.create(agenda=event_agenda, slug='_exceptions_holder')
event = Event.objects.create(
start_datetime=now(),
places=10,
agenda=event_agenda,
recurrence_days=[now().weekday()],
recurrence_end_date=now() + datetime.timedelta(days=15),
)
event.create_all_recurrences()
TimePeriodException.objects.create(
desk=event_agenda.desk_set.get(),
start_datetime=now(),
end_datetime=now() + datetime.timedelta(hours=1),
)
with CaptureQueriesContext(connection) as ctx:
resp = app.get('/api/agenda/', params={'with_open_events': '1'})
assert len(ctx.captured_queries) == 3
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=1,
agenda=agenda,
)
event2 = Event.objects.create(
start_datetime=(localtime() + datetime.timedelta(days=10)).replace(hour=10, minute=0),
places=1,
agenda=agenda,
)
event3 = Event.objects.create(
start_datetime=(localtime() + datetime.timedelta(days=15)).replace(hour=10, minute=0),
places=1,
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
event1.booking_set.create()
event1.refresh_from_db()
assert event1.full is True
resp = app.get('/api/agenda/%s/' % agenda.slug)
assert resp.json['data']['opened_events_available'] is True
# all events are full
event2.booking_set.create()
event2.refresh_from_db()
assert event2.full is True
event3.booking_set.create()
event3.refresh_from_db()
assert event3.full is 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
event1.booking_set.all().delete()
event1.refresh_from_db()
assert event1.full is 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
event3.booking_set.all().delete()
event3.refresh_from_db()
assert event3.full is False
agenda.maximal_booking_delay = 12
agenda.save()
del agenda.max_booking_datetime
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
event2.booking_set.all().delete()
event2.refresh_from_db()
assert event2.full is False
agenda.event_set.update(publication_datetime=now() + 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_detail_routing(app, meetings_agenda):
api_url = '/api/agenda/%s/' % meetings_agenda.slug
resp = app.get(api_url)
assert isinstance(resp.json['data'], dict)
# check it doesn't get confused with an agenda with "agenda" in its slug
agenda = Agenda(
label='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 isinstance(resp.json['data'], 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,
'edit_role': None,
'view_role': None,
'category': None,
'category_label': 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,
'backoffice_url': 'http://testserver/manage/agendas/%s/' % virtual_meetings_agenda.pk,
},
},
}
def test_agenda_api_delete(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
# unauthenticated
resp = app.delete('/api/agenda/%s/' % agenda.slug, status=401)
assert Agenda.objects.count() == 1
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.delete('/api/agenda/%s/' % agenda.slug)
assert resp.json['err'] == 0
assert not Agenda.objects.exists()
def test_agenda_api_delete_busy(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
Desk.objects.create(agenda=agenda, slug='_exceptions_holder')
event = Event.objects.create(start_datetime=now() + datetime.timedelta(days=10), places=10, agenda=agenda)
booking = Booking.objects.create(event=event)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.delete('/api/agenda/%s/' % agenda.slug)
assert resp.json['err'] == 1
assert 'This cannot be removed' in resp.json['err_desc']
booking.cancellation_datetime = now()
booking.save()
resp = app.delete('/api/agenda/%s/' % agenda.slug)
assert resp.json['err'] == 0
assert not Agenda.objects.exists()
@pytest.mark.freeze_time('2021-07-09')
def test_add_agenda(app, user, settings):
events_type = EventsType.objects.create(label='Type A')
category_a = Category.objects.create(label='Category A')
api_url = '/api/agenda/'
# no authentication
resp = app.post(api_url, status=401)
assert resp.json['detail'] == 'Authentication credentials were not provided.'
# wrong password
app.authorization = ('Basic', ('john.doe', 'wrong'))
resp = app.post(api_url, status=401)
assert resp.json['detail'] == 'Invalid username/password.'
app.authorization = ('Basic', ('john.doe', 'password'))
# missing fields
resp = app.post(api_url, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'label': ['This field is required.'], 'slug': ['This field is required.']}
# wrong contents
params = {
'label': 'foo',
'slug': 'foo',
'kind': 'oups',
'minimal_booking_delay': 'oups',
'minimal_booking_delay_in_working_days': 'oups',
'anonymize_delay': 'oups',
'edit_role': 'oups',
'view_role': 'plop',
'category': 'oups',
'events_type': 'oups',
}
resp = app.post(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'kind': ['"oups" is not a valid choice.'],
'minimal_booking_delay': ['A valid integer is required.'],
'minimal_booking_delay_in_working_days': ['Must be a valid boolean.'],
'anonymize_delay': ['A valid integer is required.'],
'edit_role': ['unknown role: oups'],
'view_role': ['unknown role: plop'],
'category': ['unknown category: oups'],
'events_type': ['unknown events type: oups'],
}
# slug already used
meeting_agenda = Agenda(label='Foo bar Meeting', kind='meetings')
meeting_agenda.save()
params = {
'label': 'foo',
'slug': meeting_agenda.slug,
}
resp = app.post(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'slug': ['agenda with this Identifier already exists.']}
# option only available on events agenda
params = {
'label': 'foo',
'slug': 'foo',
'kind': 'meetings',
'minimal_booking_delay_in_working_days': True,
}
resp = app.post(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'minimal_booking_delay_in_working_days': ['Option not available on meetings agenda']
}
params = {
'label': 'foo',
'slug': 'foo',
'kind': 'meetings',
'events_type': 'type-a',
}
resp = app.post(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['errors'] == {'events_type': ['Option not available on meetings agenda']}
# add an agenda using only required fields
params = {
'label': 'My Agenda',
'slug': 'my-agenda',
}
resp = app.post(api_url, params=params)
assert not resp.json['err']
assert len(resp.json['data']) == 1
agenda = Agenda.objects.get(slug='my-agenda')
assert agenda.kind == 'events'
settings.WORKING_DAY_CALENDAR = 'workalendar.europe.France'
edit_group = Group.objects.create(name='Edit')
view_group = Group.objects.create(name='View')
# add a meetings agenda
params = {
'label': 'foo Meetings',
'slug': 'foo-meetings',
'kind': 'meetings',
'minimal_booking_delay': 1,
'maximal_booking_delay': 3,
'anonymize_delay': 30,
'edit_role': 'Edit',
'view_role': 'View',
'category': 'category-a',
}
resp = app.post(api_url, params=params)
assert not resp.json['err']
assert len(resp.json['data']) == 1
agenda = Agenda.objects.get(slug='foo-meetings')
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 10)
assert agenda.edit_role == edit_group
assert agenda.view_role == view_group
assert agenda.category == category_a
assert not Desk.objects.filter(agenda=agenda, slug='_exceptions_holder').exists()
# add an events agenda
params = {
'label': 'foo Events',
'slug': 'foo-events',
'kind': 'events',
'minimal_booking_delay': 1,
'minimal_booking_delay_in_working_days': True,
'maximal_booking_delay': 3,
'anonymize_delay': 30,
'edit_role': 'Edit',
'view_role': 'View',
'category': 'category-a',
'events_type': 'type-a',
}
resp = app.post(api_url, params=params)
assert not resp.json['err']
assert len(resp.json['data']) == 1
agenda = Agenda.objects.get(slug='foo-events')
assert agenda.edit_role == edit_group
assert agenda.view_role == view_group
assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 12)
assert agenda.category == category_a
assert agenda.events_type == events_type
assert Desk.objects.filter(agenda=agenda, slug='_exceptions_holder').exists()
resp = app.get('/api/agendas/datetimes/?agendas=%s' % agenda.slug)
assert 'data' in resp.json