chrono/tests/api/test_event.py

1403 lines
49 KiB
Python

import datetime
import pytest
from django.db import connection
from django.test.utils import CaptureQueriesContext
from django.utils.timezone import localtime, make_aware, now
from chrono.agendas.models import Agenda, Booking, CheckType, CheckTypeGroup, Event, EventsType, Subscription
pytestmark = pytest.mark.django_db
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,
custom_fields={
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
},
)
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 == {
'err': 0,
'id': 'event-slug',
'slug': 'event-slug',
'text': str(event),
'label': '',
'agenda_label': 'Foo bar',
'date': localtime(event.start_datetime).strftime('%Y-%m-%d'),
'datetime': localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
'description': None,
'pricing': None,
'url': None,
'disabled': False,
'duration': None,
'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/',
'check_url': 'http://testserver/api/agenda/foo-bar/check/event-slug/',
'backoffice_url': 'http://testserver/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk),
},
'places': {'available': 9, 'reserved': 1, 'total': 10, 'full': False, 'has_waiting_list': False},
}
assert 'waiting_list_total' not in resp.json['places']
events_type = EventsType.objects.create(
label='Foo',
custom_fields=[
{'varname': 'text', 'label': 'Text', 'field_type': 'text'},
{'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'},
{'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'},
],
)
agenda.events_type = events_type
agenda.save()
resp = app.get('/api/agenda/%s/status/%s/' % (agenda.slug, event.slug))
assert resp.json == {
'err': 0,
'id': 'event-slug',
'slug': 'event-slug',
'text': str(event),
'label': '',
'agenda_label': 'Foo bar',
'date': localtime(event.start_datetime).strftime('%Y-%m-%d'),
'datetime': localtime(event.start_datetime).strftime('%Y-%m-%d %H:%M:%S'),
'description': None,
'pricing': None,
'url': None,
'disabled': False,
'duration': None,
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
'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/',
'check_url': 'http://testserver/api/agenda/foo-bar/check/event-slug/',
'backoffice_url': 'http://testserver/manage/agendas/%s/events/%s/' % (agenda.pk, event.pk),
},
'places': {'available': 9, 'reserved': 1, 'total': 10, 'full': False, 'has_waiting_list': False},
}
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)
@pytest.mark.freeze_time('2021-02-23')
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)
@pytest.mark.freeze_time('2021-02-23')
def test_event_checked(app, user):
agenda = Agenda.objects.create(label='Events', kind='events')
event = Event.objects.create(
label='xyz',
start_datetime=now() - datetime.timedelta(days=1),
places=10,
agenda=agenda,
)
assert event.checked is False
app.authorization = ('Basic', ('john.doe', 'password'))
app.post('/api/agenda/%s/check/%s/' % (agenda.slug, event.slug))
event.refresh_from_db()
assert event.checked is True
# already checked
app.post('/api/agenda/%s/check/%s/' % (agenda.slug, event.slug))
event.refresh_from_db()
assert event.checked is True
# wrong kind
agenda.kind = 'meetings'
agenda.save()
app.post('/api/agenda/%s/check/%s/' % (agenda.slug, event.slug), status=404)
agenda.kind = 'virtual'
agenda.save()
app.post('/api/agenda/%s/check/%s/' % (agenda.slug, event.slug), status=404)
@pytest.mark.parametrize(
'days_in, days_out, err_msg',
[
(1, None, 'Expected a list of items but got type "int".'),
('2', [2], None),
([3], [3], None),
(['4'], [4], None),
([1, 2], [1, 2], None),
(['2', '3'], [2, 3], None),
('4, 5', [4, 5], None),
],
)
def test_string_or_list_serialiser(app, user, days_in, days_out, err_msg):
app.authorization = ('Basic', ('john.doe', 'password'))
agenda = Agenda(label='Foo bar')
agenda.maximal_booking_delay = 0
agenda.save()
api_url = '/api/agenda/%s/event/' % (agenda.slug)
params = {
'start_datetime': '2022-02-03 16:00',
'places': '1',
'recurrence_days': days_in,
'recurrence_end_date': '2022-02-13',
}
if not err_msg:
resp = app.post_json(api_url, params=params)
assert not resp.json['err']
assert Event.objects.get(primary_event__isnull=True).recurrence_days == days_out
else:
resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['recurrence_days'][0] == err_msg
@pytest.mark.freeze_time('2021-11-01')
def test_add_event(app, user):
api_url = '/api/agenda/%s/event/' % ('999')
# 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 agenda
resp = app.post(api_url, status=404)
assert resp.json['detail'] == 'Not found.'
# using meeting agenda
meeting_agenda = Agenda.objects.create(label='Foo bar Meeting', kind='meetings')
api_url = '/api/agenda/%s/event/' % (meeting_agenda.slug)
resp = app.post(api_url, status=404)
assert resp.json['detail'] == 'Not found.'
agenda = Agenda.objects.create(label='Foo bar', maximal_booking_delay=0)
api_url = '/api/agenda/%s/event/' % (agenda.slug)
# missing fields
resp = app.post(api_url, status=400)
assert resp.json['err']
assert resp.json['errors'] == {
'start_datetime': ['This field is required.'],
'places': ['This field is required.'],
}
# add with errors in datetime parts
params = {
'start_datetime': '2021-11-15 minuit',
'places': 10,
}
resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert 'Datetime has wrong format' in resp.json['errors']['start_datetime'][0]
# add an event
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
}
resp = app.post_json(api_url, params=params)
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-bar-event'
assert {'api', 'disabled', 'places'}.issubset(resp.json['data'].keys())
assert {'recurrence_days', 'recurrence_week_interval', 'recurrence_end_date'}.isdisjoint(
resp.json['data'].keys()
)
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event')
assert str(event.start_datetime) == '2021-11-15 14:38:00+00:00'
assert str(event.start_datetime.tzinfo) == 'UTC'
assert event.places == 10
assert event.publication_datetime is None
assert event.custom_fields == {}
# add an event without custom fields
events_type = EventsType.objects.create(
label='Foo',
custom_fields=[
{'varname': 'text', 'label': 'Text', 'field_type': 'text'},
{'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'},
{'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'},
],
)
agenda.events_type = events_type
agenda.save()
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
}
resp = app.post_json(api_url, params=params)
assert not resp.json['err']
event = Event.objects.latest('pk')
assert event.custom_fields == {
'text': '',
'textarea': '',
'bool': None,
}
# add with almost all optional managed fields
params = {
'start_datetime': '2021-11-15 15:38',
'duration': 42,
'publication_datetime': '2021-09-20 10:00',
'places': 11,
'waiting_list_places': 3,
'label': 'FOO camp',
'description': 'An event',
'pricing': 'free',
'url': 'http://example.org/foo/bar/?',
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
}
resp = app.post_json(api_url, params=params)
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-camp'
assert {'api', 'disabled', 'places'}.issubset(resp.json['data'].keys())
assert {'recurrence_days', 'recurrence_week_interval', 'recurrence_end_date'}.isdisjoint(
resp.json['data'].keys()
)
event = Event.objects.filter(agenda=agenda).get(slug='foo-camp')
assert event.duration == 42
assert event.waiting_list_places == 3
assert event.label == 'FOO camp'
assert event.description == 'An event'
assert event.pricing == 'free'
assert event.url == 'http://example.org/foo/bar/?'
assert str(event.publication_datetime) == '2021-09-20 08:00:00+00:00'
assert str(event.publication_datetime.tzinfo) == 'UTC'
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
# add with errors in bool custom field
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
'custom_field_bool': 'foobar',
}
resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['custom_field_bool'][0] == 'Must be a valid boolean.'
# add with errors in recurrence_days list
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
'recurrence_days': 'oups',
}
resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['recurrence_days']['0'][0] == 'A valid integer is required.'
params = {
'start_datetime': '2021-11-15 15:38',
'places': 10,
'recurrence_days': '7',
}
resp = app.post_json(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['recurrence_days']['0'][0] == 'Ensure this value is less than or equal to 6.'
# add a recurrent event
params = {
'start_datetime': '2021-11-15 15:38',
'places': 12,
'recurrence_days': '3',
'recurrence_week_interval': '2',
'description': 'A recurrent event',
}
assert Event.objects.filter(agenda=agenda).count() == 3
resp = app.post_json(api_url, params=params)
assert Event.objects.filter(agenda=agenda).count() == 29
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-bar-event-2'
assert {'api', 'disabled', 'places'}.isdisjoint(resp.json['data'].keys())
assert {'recurrence_days', 'recurrence_week_interval', 'recurrence_end_date'}.issubset(
resp.json['data'].keys()
)
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event-2')
assert event.description == 'A recurrent event'
assert event.recurrence_days == [3]
assert event.recurrence_week_interval == 2
assert event.recurrence_end_date is None
# some occurrences created
assert event.recurrences.count() == 25
# add a recurrent event with end recurrence date creates 9 recurrences
params = {
'start_datetime': '2021-11-15 15:38',
'places': 13,
'recurrence_days': '0,3,5', # Monday, Tuesday, Saturday
'recurrence_week_interval': '2',
'recurrence_end_date': '2021-12-27',
'description': 'A recurrent event having recurrences',
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
}
resp = app.post_json(api_url, params=params)
assert not resp.json['err']
assert resp.json['data']['id'] == 'foo-bar-event-3'
assert {'api', 'disabled', 'places'}.isdisjoint(resp.json['data'].keys())
assert {'recurrence_days', 'recurrence_week_interval', 'recurrence_end_date'}.issubset(
resp.json['data'].keys()
)
event = Event.objects.filter(agenda=agenda).get(slug='foo-bar-event-3')
assert Event.objects.filter(agenda=agenda).count() == 39
assert event.description == 'A recurrent event having recurrences'
assert event.recurrence_days == [0, 3, 5]
assert event.recurrence_week_interval == 2
assert event.recurrence_end_date == datetime.date(2021, 12, 27)
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
assert event.recurrences.count() == 9
assert sorted(
str(x.start_datetime.date()) for x in Event.objects.all() if 'foo-bar-event-3--' in x.slug
) == [
'2021-11-15',
'2021-11-18',
'2021-11-20',
'2021-11-29',
'2021-12-02',
'2021-12-04',
'2021-12-13',
'2021-12-16',
'2021-12-18',
]
for ev in event.recurrences.all():
assert ev.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
app.delete('/api/agenda/%s/event/' % agenda.slug, status=405) # forbidden
@pytest.mark.freeze_time('2021-11-01')
def test_update_event(app, user):
api_url = '/api/agenda/%s/event/%s/' % ('nop', 'nop')
# no authentication
resp = app.patch(api_url, status=401)
assert resp.json['detail'] == 'Authentication credentials were not provided.'
# wrong password
app.authorization = ('Basic', ('john.doe', 'wrong'))
resp = app.patch(api_url, status=401)
assert resp.json['detail'] == 'Invalid username/password.'
app.authorization = ('Basic', ('john.doe', 'password'))
# missing agenda
resp = app.patch(api_url, status=404)
assert resp.json['detail'] == 'Not found.'
meeting_agenda = Agenda.objects.create(label='Foo bar Meeting', kind='meetings')
# using meeting agenda
api_url = '/api/agenda/%s/event/%s/' % (meeting_agenda.slug, 'nop')
resp = app.patch(api_url, status=404)
assert resp.json['detail'] == 'Not found.'
events_type = EventsType.objects.create(
label='Foo',
custom_fields=[
{'varname': 'text', 'label': 'Text', 'field_type': 'text'},
{'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'},
{'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'},
],
)
agenda = Agenda.objects.create(label='Foo bar', events_type=events_type)
# missing event
api_url = '/api/agenda/%s/event/%s/' % (agenda.slug, 'nop')
resp = app.patch(api_url, status=404)
assert resp.json['detail'] == 'Not found.'
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10, waiting_list_places=5)
api_url = '/api/agenda/%s/event/%s/' % (agenda.slug, event.slug)
# update with errors in datetime parts
params = {
'start_datetime': '2021-11-15 minuit',
'recurrence_days': '7, 8',
}
resp = app.patch_json(api_url, params=params, status=400)
assert resp.json['err']
assert resp.json['err_desc'] == 'invalid payload'
assert 'Datetime has wrong format' in resp.json['errors']['start_datetime'][0]
assert resp.json['errors']['recurrence_days']['0'][0] == 'Ensure this value is less than or equal to 6.'
# update with almost all optional managed fields
params = {
'start_datetime': '2021-11-15 15:38',
'duration': 42,
'publication_datetime': '2021-09-20 12:00',
'places': 8,
'waiting_list_places': 3,
'label': 'FOO camp',
'description': 'An event',
'pricing': 'free',
'url': 'http://example.org/foo/bar/?',
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
assert event.duration == 42
assert event.places == 8
assert event.waiting_list_places == 3
assert event.label == 'FOO camp'
assert event.description == 'An event'
assert event.pricing == 'free'
assert event.url == 'http://example.org/foo/bar/?'
assert str(event.publication_datetime) == '2021-09-20 10:00:00+00:00'
assert str(event.publication_datetime.tzinfo) == 'UTC'
assert event.custom_fields == {
'text': '',
'textarea': '',
'bool': None,
}
# update event as a recurring event
params = {
'recurrence_days': '0,3,5',
'recurrence_week_interval': 2,
'recurrence_end_date': '2021-12-27',
# with custom fields
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
assert event.recurrences.count() == 9
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
assert [x.places for x in event.recurrences.all()] == [8] * 9
for ev in event.recurrences.all():
assert ev.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
recurrence_slug = event.recurrences.first().slug
assert recurrence_slug == 'foo-bar-event--2021-11-15-1538'
recurrence_url = '/api/agenda/%s/event/%s/' % (agenda.slug, 'foo-bar-event--2021-11-15-1538')
# update unprotected fields of one of the event recurrencies
params = {
'start_datetime': '2021-11-14 14:00',
'duration': 43,
'places': 9,
'waiting_list_places': 4,
'label': 'BAR camp',
'description': 'An occurence of an event recurrence',
'pricing': '5€',
'url': 'http://example.org/bar/bar/',
}
resp = app.patch_json(recurrence_url, params=params)
assert not resp.json['err']
recurrence = Event.objects.filter(agenda=agenda).get(slug=recurrence_slug)
assert recurrence.duration == 43
assert recurrence.places == 9
assert recurrence.waiting_list_places == 4
assert recurrence.label == 'BAR camp'
assert recurrence.description == 'An occurence of an event recurrence'
assert recurrence.pricing == '5€'
assert recurrence.url == 'http://example.org/bar/bar/'
# try to update protected fields of one of the event recurrencies
params = {
'publication_datetime': '2021-11-15 12:00',
}
resp = app.patch_json(recurrence_url, params=params, status=400)
assert resp.json['err']
assert 'cannot be modified' in resp.json['err_desc']
# update protected fields of one of the event recurrencies providing same value
params = {
'recurrence_week_interval': 1,
}
resp = app.patch_json(recurrence_url, params=params)
assert not resp.json['err']
# update of custom field of one of event recurrences is ignored
params = {
'custom_field_text': 'foo bar baz',
}
resp = app.patch_json(recurrence_url, params=params)
assert not resp.json['err']
recurrence.refresh_from_db()
assert recurrence.custom_fields == {
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
}
booking = Booking.objects.create(event=event.recurrences.all()[2])
# update unprotected fields
params = {
'places': 7,
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
assert [x.places for x in event.recurrences.all()] == [7] * 9
# try to update recurring event protected fields
params = {
'recurrence_days': '1,2',
}
resp = app.patch_json(api_url, params=params, status=400)
assert resp.json['err']
assert 'cannot be modified' in resp.json['err_desc']
assert booking.event.start_datetime.date() == datetime.date(2021, 11, 20)
# try to reduce recurrence end date before booked event
params = {
'recurrence_end_date': '2021-11-20',
}
resp = app.patch_json(api_url, params=params, status=400)
assert resp.json['err']
assert 'bookings exist after this date.' in resp.json['err_desc']
# reduce recurrence end date after booked event
params = {
'recurrence_end_date': '2021-11-21',
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
assert event.recurrences.count() == 3
booking.cancel()
# update no more protected fields
params = {
'recurrence_days': '1,2,3,4,5',
'recurrence_week_interval': 1,
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event = Event.objects.filter(agenda=agenda).get(slug=event.slug)
assert event.recurrences.count() == 5
# update just one custom field
params = {
'custom_field_textarea': 'foo bar baz',
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event.refresh_from_db()
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar baz',
'bool': True,
}
params = {
'custom_field_bool': False,
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event.refresh_from_db()
assert event.custom_fields == {
'text': 'foo',
'textarea': 'foo bar baz',
'bool': False,
}
# reset custom fields
params = {
'custom_field_text': '',
'custom_field_textarea': '',
'custom_field_bool': None,
}
resp = app.patch_json(api_url, params=params)
assert not resp.json['err']
event.refresh_from_db()
assert event.custom_fields == {
'text': '',
'textarea': '',
'bool': None,
}
def test_event_read_only_fields(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
agenda2 = Agenda.objects.create(label='Foo bar 2', kind='events')
event = Event.objects.create(
slug='event', start_datetime=now() + datetime.timedelta(days=5), places=1, agenda=agenda
)
app.authorization = ('Basic', ('john.doe', 'password'))
api_url = '/api/agenda/%s/event/' % agenda.slug
params = {
'slug': 'slug',
'agenda': agenda2.slug,
'primary_event': event.slug,
'start_datetime': now().isoformat(),
'places': 42,
}
resp = app.post(api_url, params=params)
assert resp.json['err'] == 0
new_event = Event.objects.latest('pk')
assert new_event.slug == 'foo-bar-event'
assert new_event.agenda == agenda
assert new_event.primary_event is None
api_url = '/api/agenda/%s/event/%s/' % (agenda.slug, new_event.slug)
params = {
'slug': 'slug',
'agenda': agenda2.slug,
'primary_event': event.slug,
}
resp = app.patch(api_url, params=params)
assert resp.json['err'] == 0
new_event.refresh_from_db()
assert new_event.slug == 'foo-bar-event'
assert new_event.agenda == agenda
assert new_event.primary_event is None
@pytest.mark.freeze_time('2021-11-01 10:00')
def test_delete_event(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
slug='event', start_datetime=now() + datetime.timedelta(days=5), places=1, agenda=agenda
)
# no authentication
resp = app.delete('/api/agenda/%s/event/%s/' % (agenda.slug, event.slug), status=401)
assert Event.objects.count() == 1
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.delete('/api/agenda/%s/event/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 0
assert not Event.objects.exists()
@pytest.mark.freeze_time('2021-11-01 10:00')
def test_delete_event_forbidden(app, user, freezer):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
event = Event.objects.create(
slug='event', start_datetime=now() + datetime.timedelta(days=5), places=1, agenda=agenda
)
booking = Booking.objects.create(event=event)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.delete('/api/agenda/%s/event/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 1
assert 'This cannot be removed' in resp.json['err_desc']
freezer.move_to('2021-11-10 10:00')
resp = app.delete('/api/agenda/%s/event/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 0
assert not Event.objects.exists()
event = Event.objects.create(
slug='event', start_datetime=now() + datetime.timedelta(days=5), places=1, agenda=agenda
)
booking = Booking.objects.create(event=event)
resp = app.delete('/api/agenda/%s/event/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 1
booking.cancellation_datetime = now()
booking.save()
resp = app.delete('/api/agenda/%s/event/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 0
assert not Event.objects.exists()
@pytest.mark.freeze_time('2021-11-01 10:00')
def test_delete_recurring_event_forbidden(app, user):
agenda = Agenda.objects.create(label='Foo bar', kind='events')
start_datetime = now() + datetime.timedelta(days=10)
event = Event.objects.create(
start_datetime=start_datetime,
places=10,
agenda=agenda,
recurrence_days=[start_datetime.weekday()],
recurrence_end_date=start_datetime + datetime.timedelta(days=7),
)
event.create_all_recurrences()
event_recurrence = Event.objects.get(primary_event=event, start_datetime=event.start_datetime)
booking = Booking.objects.create(event=event_recurrence)
app.authorization = ('Basic', ('john.doe', 'password'))
resp = app.delete('/api/agenda/%s/event/%s/' % (agenda.slug, event.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/event/%s/' % (agenda.slug, event.slug))
assert resp.json['err'] == 0
assert not Event.objects.exists()
def test_events_check_status_params(app, user):
app.authorization = ('Basic', ('john.doe', 'password'))
# missing user_external_id
resp = app.get(
'/api/agendas/events/check-status/',
params={'agendas': 'foo', 'date_start': '2022-05-01', 'date_end': '2022-06-01'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['user_external_id'] == ['This field is required.']
# missing agendas
resp = app.get(
'/api/agendas/events/check-status/',
params={'user_external_id': 'child:42', 'date_start': '2022-05-01', 'date_end': '2022-06-01'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['agendas'] == ['This field is required.']
# unknown agenda
resp = app.get(
'/api/agendas/events/check-status/',
params={
'user_external_id': 'child:42',
'agendas': 'foo, bar',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['agendas'] == ['invalid slugs: bar, foo']
Agenda.objects.create(label='Foo')
resp = app.get(
'/api/agendas/events/check-status/',
params={
'user_external_id': 'child:42',
'agendas': 'foo, bar',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['agendas'] == ['invalid slugs: bar']
# wrong kind
wrong_agenda = Agenda.objects.create(label='Bar')
for kind in ['meetings', 'virtual']:
wrong_agenda.kind = kind
wrong_agenda.save()
resp = app.get(
'/api/agendas/events/check-status/',
params={
'user_external_id': 'child:42',
'agendas': 'foo, bar',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['agendas'] == ['invalid slugs: bar']
# missing date_start
resp = app.get(
'/api/agendas/events/check-status/',
params={'user_external_id': 'child:42', 'agendas': 'foo', 'date_end': '2022-06-01'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['date_start'] == ['This field is required.']
# missing date_end
resp = app.get(
'/api/agendas/events/check-status/',
params={'user_external_id': 'child:42', 'agendas': 'foo', 'date_start': '2022-05-01'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert resp.json['errors']['date_end'] == ['This field is required.']
# bad date format
resp = app.get(
'/api/agendas/events/check-status/',
params={'user_external_id': 'child:42', 'agendas': 'foo', 'date_start': 'wrong', 'date_end': 'wrong'},
status=400,
)
assert resp.json['err'] == 1
assert resp.json['err_desc'] == 'invalid payload'
assert 'wrong format' in resp.json['errors']['date_start'][0]
assert 'wrong format' in resp.json['errors']['date_end'][0]
@pytest.mark.freeze_time('2022-05-30 14:00')
def test_events_check_status(app, user):
agenda = Agenda.objects.create(label='Foo')
event = Event.objects.create(
slug='event-slug',
label='Event Label',
start_datetime=now(),
places=10,
agenda=agenda,
checked=True,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='child:42',
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2022, month=9, day=1),
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-status/'
params = {
'user_external_id': 'child:42',
'agendas': 'foo',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
}
# not booked
resp = app.get(url, params=params)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['check_status'] == {
'status': 'not-booked',
}
assert resp.json['data'][0]['booking'] == {}
# 2 bookings found, error
booking = Booking.objects.create(event=event, user_external_id='child:42')
booking2 = Booking.objects.create(event=event, user_external_id='child:42')
Booking.objects.create(event=event, user_external_id='other')
resp = app.get(url, params=params)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['check_status'] == {
'status': 'error',
'error_reason': 'too-many-bookings-found',
}
assert resp.json['data'][0]['booking'] == {}
# booking cancelled
booking2.delete()
booking.cancellation_datetime = now()
booking.save()
resp = app.get(url, params=params)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['check_status'] == {
'status': 'cancelled',
}
assert list(resp.json['data'][0]['booking'].keys()) == [
'id',
'in_waiting_list',
'user_first_name',
'user_last_name',
'user_email',
'user_phone_number',
'user_was_present',
'user_absence_reason',
'user_presence_reason',
'use_color_for',
'extra_data',
'creation_datetime',
'cancellation_datetime',
]
assert resp.json['data'][0]['booking']['cancellation_datetime'] == localtime(now()).isoformat()
# booking not checked
booking.cancellation_datetime = None
booking.save()
resp = app.get(url, params=params)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['check_status'] == {
'status': 'error',
'error_reason': 'booking-not-checked',
}
assert resp.json['data'][0]['booking']['cancellation_datetime'] is None
# absence
booking.user_was_present = False
booking.save()
resp = app.get(url, params=params)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['check_status'] == {
'status': 'absence',
'check_type': '',
}
assert resp.json['data'][0]['booking']['user_was_present'] is False
assert resp.json['data'][0]['booking']['user_absence_reason'] == ''
assert resp.json['data'][0]['booking']['user_presence_reason'] == ''
# absence with check type
group = CheckTypeGroup.objects.create(label='Foo bar')
check_type = CheckType.objects.create(label='Foo reason', group=group, kind='absence')
booking.user_check_type = check_type
booking.save()
resp = app.get(url, params=params)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['check_status'] == {
'status': 'absence',
'check_type': 'foo-reason',
}
assert resp.json['data'][0]['booking']['user_was_present'] is False
assert resp.json['data'][0]['booking']['user_absence_reason'] == 'foo-reason'
assert resp.json['data'][0]['booking']['user_presence_reason'] == ''
# presence
booking.user_check_type = None
booking.user_was_present = True
booking.save()
resp = app.get(url, params=params)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['check_status'] == {
'status': 'presence',
'check_type': '',
}
assert resp.json['data'][0]['booking']['user_was_present'] is True
assert resp.json['data'][0]['booking']['user_absence_reason'] == ''
assert resp.json['data'][0]['booking']['user_presence_reason'] == ''
# presence with check type
check_type.kind = 'presence'
check_type.save()
booking.user_check_type = check_type
booking.save()
resp = app.get(url, params=params)
assert resp.json['err'] == 0
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['check_status'] == {
'status': 'presence',
'check_type': 'foo-reason',
}
assert resp.json['data'][0]['booking']['user_was_present'] is True
assert resp.json['data'][0]['booking']['user_absence_reason'] == ''
assert resp.json['data'][0]['booking']['user_presence_reason'] == 'foo-reason'
@pytest.mark.freeze_time('2022-05-30 14:00')
def test_events_check_status_events(app, user):
events_type = EventsType.objects.create(
label='Foo',
custom_fields=[
{'varname': 'text', 'label': 'Text', 'field_type': 'text'},
{'varname': 'textarea', 'label': 'TextArea', 'field_type': 'textarea'},
{'varname': 'bool', 'label': 'Bool', 'field_type': 'bool'},
],
)
group = CheckTypeGroup.objects.create(label='Foo bar')
check_type = CheckType.objects.create(label='Foo reason', group=group, kind='absence')
agenda = Agenda.objects.create(label='Foo', events_type=events_type)
start_datetime = now()
# recurring event
recurring_event = Event.objects.create(
slug='recurring-event-slug',
label='Recurring Event Label',
start_datetime=start_datetime,
recurrence_days=[start_datetime.weekday()],
recurrence_end_date=start_datetime + datetime.timedelta(days=7),
places=10,
agenda=agenda,
custom_fields={
'text': 'foo',
'textarea': 'foo bar',
'bool': True,
},
)
recurring_event.create_all_recurrences()
first_event = recurring_event.recurrences.get()
first_event.checked = True
first_event.save()
event = Event.objects.create(
slug='event-slug',
label='Event Label',
start_datetime=start_datetime - datetime.timedelta(days=1),
places=10,
agenda=agenda,
checked=True,
)
# not checked event
notchecked_event = Event.objects.create(
slug='notchecked-event-slug',
label='Not Checked Event Label',
start_datetime=start_datetime - datetime.timedelta(days=2),
places=10,
agenda=agenda,
checked=False,
)
# cancelled event, not returned
Event.objects.create(
slug='cancelled',
label='Cancelled',
start_datetime=start_datetime,
places=10,
agenda=agenda,
cancelled=True,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='child:42',
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2022, month=9, day=1),
)
booking1 = Booking.objects.create(
event=first_event, user_external_id='child:42', user_was_present=True, user_check_type=check_type
)
booking2 = Booking.objects.create(
event=event, user_external_id='child:42', user_was_present=True, user_check_type=check_type
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-status/'
params = {
'user_external_id': 'child:42',
'agendas': 'foo',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
}
with CaptureQueriesContext(connection) as ctx:
resp = app.get(url, params=params)
assert len(ctx.captured_queries) == 5
assert resp.json['err'] == 0
assert resp.json['data'] == [
{
'event': {
'description': None,
'duration': None,
'label': 'Not Checked Event Label',
'slug': 'notchecked-event-slug',
'places': 10,
'pricing': None,
'publication_datetime': None,
'recurrence_days': None,
'recurrence_end_date': None,
'recurrence_week_interval': 1,
'start_datetime': localtime(notchecked_event.start_datetime).isoformat(),
'url': None,
'waiting_list_places': 0,
'agenda': agenda.slug,
'primary_event': None,
'custom_field_bool': None,
'custom_field_text': '',
'custom_field_textarea': '',
},
'check_status': {'error_reason': 'event-not-checked', 'status': 'error'},
'booking': {},
},
{
'event': {
'description': None,
'duration': None,
'label': 'Event Label',
'slug': 'event-slug',
'places': 10,
'pricing': None,
'publication_datetime': None,
'recurrence_days': None,
'recurrence_end_date': None,
'recurrence_week_interval': 1,
'start_datetime': localtime(event.start_datetime).isoformat(),
'url': None,
'waiting_list_places': 0,
'agenda': agenda.slug,
'primary_event': None,
'custom_field_bool': None,
'custom_field_text': '',
'custom_field_textarea': '',
},
'check_status': {'check_type': 'foo-reason', 'status': 'presence'},
'booking': {
'cancellation_datetime': None,
'use_color_for': None,
'creation_datetime': localtime(now()).isoformat(),
'extra_data': None,
'id': booking2.pk,
'in_waiting_list': False,
'user_absence_reason': '',
'user_email': '',
'user_first_name': '',
'user_last_name': '',
'user_phone_number': '',
'user_presence_reason': check_type.slug,
'user_was_present': True,
},
},
{
'event': {
'description': None,
'duration': None,
'label': 'Recurring Event Label',
'slug': 'recurring-event-slug--2022-05-30-1600',
'places': 10,
'pricing': None,
'publication_datetime': None,
'recurrence_days': None,
'recurrence_end_date': None,
'recurrence_week_interval': 1,
'start_datetime': localtime(first_event.start_datetime).isoformat(),
'url': None,
'waiting_list_places': 0,
'agenda': agenda.slug,
'primary_event': recurring_event.slug,
'custom_field_text': 'foo',
'custom_field_textarea': 'foo bar',
'custom_field_bool': True,
},
'check_status': {'check_type': 'foo-reason', 'status': 'presence'},
'booking': {
'cancellation_datetime': None,
'use_color_for': None,
'creation_datetime': localtime(now()).isoformat(),
'extra_data': None,
'id': booking1.pk,
'in_waiting_list': False,
'user_absence_reason': '',
'user_email': '',
'user_first_name': '',
'user_last_name': '',
'user_phone_number': '',
'user_presence_reason': check_type.slug,
'user_was_present': True,
},
},
]
@pytest.mark.freeze_time('2022-05-30 14:00')
def test_events_check_status_agendas_filter(app, user):
agenda1 = Agenda.objects.create(label='Foo')
agenda2 = Agenda.objects.create(label='Foo 2')
Event.objects.create(
slug='event-1',
label='Event 1',
start_datetime=now(),
places=10,
agenda=agenda1,
)
Event.objects.create(
slug='event-2',
label='Event 2',
start_datetime=now(),
places=10,
agenda=agenda2,
)
Subscription.objects.create(
agenda=agenda1,
user_external_id='child:42',
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2022, month=9, day=1),
)
Subscription.objects.create(
agenda=agenda2,
user_external_id='child:42',
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2022, month=9, day=1),
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-status/'
params = {
'user_external_id': 'child:42',
'agendas': 'foo, foo-2',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
}
resp = app.get(url, params=params)
assert len(resp.json['data']) == 2
assert resp.json['data'][0]['event']['slug'] == 'event-1'
assert resp.json['data'][1]['event']['slug'] == 'event-2'
params['agendas'] = 'foo'
resp = app.get(url, params=params)
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['event']['slug'] == 'event-1'
params['agendas'] = 'foo-2'
resp = app.get(url, params=params)
assert len(resp.json['data']) == 1
assert resp.json['data'][0]['event']['slug'] == 'event-2'
@pytest.mark.parametrize(
'event_date, expected',
[
# just before first day
((2022, 4, 30, 12, 0), False),
# first day
((2022, 5, 1, 12, 0), True),
# last day
((2022, 5, 31, 12, 0), True),
# just after last day
((2022, 6, 1, 12, 0), False),
],
)
def test_events_check_status_date_filter(app, user, event_date, expected):
agenda = Agenda.objects.create(label='Foo')
Event.objects.create(
slug='event',
label='Event',
start_datetime=make_aware(datetime.datetime(*event_date)),
places=10,
agenda=agenda,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='child:42',
date_start=datetime.date(year=2021, month=9, day=1),
date_end=datetime.date(year=2022, month=9, day=1),
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-status/'
params = {
'user_external_id': 'child:42',
'agendas': 'foo',
'date_start': '2022-05-01',
'date_end': '2022-06-01',
}
resp = app.get(url, params=params)
assert len(resp.json['data']) == int(expected)
@pytest.mark.parametrize(
'event_date, expected',
[
# just before first day
((2022, 4, 30, 12, 0), False),
# first day
((2022, 5, 1, 12, 0), True),
# last day
((2022, 5, 31, 12, 0), True),
# just after last day
((2022, 6, 1, 12, 0), False),
],
)
def test_events_check_status_subscription_filter(app, user, freezer, event_date, expected):
agenda = Agenda.objects.create(label='Foo')
Event.objects.create(
slug='event',
label='Event',
start_datetime=make_aware(datetime.datetime(*event_date)),
places=10,
agenda=agenda,
)
Subscription.objects.create(
agenda=agenda,
user_external_id='child:42',
date_start=datetime.date(year=2022, month=5, day=1),
date_end=datetime.date(year=2022, month=6, day=1),
)
Subscription.objects.create(
agenda=agenda,
user_external_id='other',
date_start=datetime.date(year=2022, month=4, day=1),
date_end=datetime.date(year=2022, month=7, day=1),
)
app.authorization = ('Basic', ('john.doe', 'password'))
url = '/api/agendas/events/check-status/'
params = {
'user_external_id': 'child:42',
'agendas': 'foo',
'date_start': '2022-04-01',
'date_end': '2022-07-01',
}
resp = app.get(url, params=params)
assert len(resp.json['data']) == int(expected)