507 lines
20 KiB
Python
507 lines
20 KiB
Python
import io
|
||
import json
|
||
import re
|
||
import tarfile
|
||
|
||
import pytest
|
||
from django.contrib.auth.models import Group
|
||
from django.contrib.contenttypes.models import ContentType
|
||
|
||
from chrono.agendas.models import Agenda, Category, Desk, EventsType, Resource, UnavailabilityCalendar
|
||
from chrono.apps.export_import.models import Application, ApplicationElement
|
||
|
||
pytestmark = pytest.mark.django_db
|
||
|
||
|
||
def test_object_types(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
resp = app.get('/api/export-import/')
|
||
assert resp.json == {
|
||
'data': [
|
||
{
|
||
'id': 'agendas',
|
||
'text': 'Agendas',
|
||
'singular': 'Agenda',
|
||
'urls': {'list': 'http://testserver/api/export-import/agendas/'},
|
||
},
|
||
{
|
||
'id': 'agendas_categories',
|
||
'minor': True,
|
||
'singular': 'Category (agendas)',
|
||
'text': 'Categories (agendas)',
|
||
'urls': {'list': 'http://testserver/api/export-import/agendas_categories/'},
|
||
},
|
||
{
|
||
'id': 'events_types',
|
||
'minor': True,
|
||
'singular': 'Type d’évènements',
|
||
'text': 'Types d’évènements',
|
||
'urls': {'list': 'http://testserver/api/export-import/events_types/'},
|
||
},
|
||
{
|
||
'id': 'resources',
|
||
'minor': True,
|
||
'singular': 'Ressource',
|
||
'text': 'Ressources',
|
||
'urls': {'list': 'http://testserver/api/export-import/resources/'},
|
||
},
|
||
{
|
||
'id': 'unavailability_calendars',
|
||
'minor': True,
|
||
'singular': 'Unavailability calendar',
|
||
'text': 'Calendrier d’indisponibilités',
|
||
'urls': {'list': 'http://testserver/api/export-import/unavailability_calendars/'},
|
||
},
|
||
]
|
||
}
|
||
|
||
|
||
def test_list_agendas(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||
Agenda.objects.create(label='Event', slug='event', kind='events')
|
||
resp = app.get('/api/export-import/agendas/')
|
||
assert resp.json == {
|
||
'data': [
|
||
{
|
||
'id': 'event',
|
||
'text': 'Event',
|
||
'type': 'agendas',
|
||
'urls': {
|
||
'export': 'http://testserver/api/export-import/agendas/event/',
|
||
'dependencies': 'http://testserver/api/export-import/agendas/event/dependencies/',
|
||
'redirect': 'http://testserver/api/export-import/agendas/event/redirect/',
|
||
},
|
||
},
|
||
{
|
||
'id': 'rdv',
|
||
'text': 'Rdv',
|
||
'type': 'agendas',
|
||
'urls': {
|
||
'export': 'http://testserver/api/export-import/agendas/rdv/',
|
||
'dependencies': 'http://testserver/api/export-import/agendas/rdv/dependencies/',
|
||
'redirect': 'http://testserver/api/export-import/agendas/rdv/redirect/',
|
||
},
|
||
},
|
||
]
|
||
}
|
||
|
||
|
||
def test_export_agenda(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
group1 = Group.objects.create(name='group1')
|
||
group2 = Group.objects.create(name='group2')
|
||
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', edit_role=group1, view_role=group2)
|
||
resp = app.get('/api/export-import/agendas/rdv/')
|
||
assert resp.json['data']['label'] == 'Rdv'
|
||
assert resp.json['data']['permissions'] == {'view': 'group2', 'edit': 'group1'}
|
||
|
||
|
||
def test_export_minor_components(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
Category.objects.create(slug='cat', label='Category')
|
||
Resource.objects.create(slug='foo', label='Foo')
|
||
EventsType.objects.create(slug='foo', label='Foo')
|
||
UnavailabilityCalendar.objects.create(slug='foo', label='Foo')
|
||
|
||
resp = app.get('/api/export-import/agendas_categories/cat/')
|
||
assert resp.json['data']['label'] == 'Category'
|
||
resp = app.get('/api/export-import/resources/foo/')
|
||
assert resp.json['data']['label'] == 'Foo'
|
||
resp = app.get('/api/export-import/events_types/foo/')
|
||
assert resp.json['data']['label'] == 'Foo'
|
||
resp = app.get('/api/export-import/unavailability_calendars/foo/')
|
||
assert resp.json['data']['label'] == 'Foo'
|
||
|
||
|
||
def test_agenda_dependencies_category(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
category = Category.objects.create(slug='cat', label='Category')
|
||
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', category=category)
|
||
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
|
||
assert resp.json == {
|
||
'data': [
|
||
{
|
||
'id': 'cat',
|
||
'text': 'Category',
|
||
'type': 'agendas_categories',
|
||
'urls': {
|
||
'dependencies': 'http://testserver/api/export-import/agendas_categories/cat/dependencies/',
|
||
'export': 'http://testserver/api/export-import/agendas_categories/cat/',
|
||
'redirect': 'http://testserver/api/export-import/agendas_categories/cat/redirect/',
|
||
},
|
||
}
|
||
],
|
||
'err': 0,
|
||
}
|
||
|
||
|
||
def test_agenda_dependencies_resources(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
meetings_agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||
meetings_agenda.resources.add(Resource.objects.create(slug='foo', label='Foo'))
|
||
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
|
||
assert resp.json == {
|
||
'data': [
|
||
{
|
||
'id': 'foo',
|
||
'text': 'Foo',
|
||
'type': 'resources',
|
||
'urls': {
|
||
'dependencies': 'http://testserver/api/export-import/resources/foo/dependencies/',
|
||
'export': 'http://testserver/api/export-import/resources/foo/',
|
||
'redirect': 'http://testserver/api/export-import/resources/foo/redirect/',
|
||
},
|
||
}
|
||
],
|
||
'err': 0,
|
||
}
|
||
|
||
|
||
def test_agenda_dependencies_unavailability_calendars(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
meetings_agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||
desk = Desk.objects.create(slug='foo', label='Foo', agenda=meetings_agenda)
|
||
desk.unavailability_calendars.add(UnavailabilityCalendar.objects.create(slug='foo', label='Foo'))
|
||
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
|
||
assert resp.json == {
|
||
'data': [
|
||
{
|
||
'id': 'foo',
|
||
'text': 'Foo',
|
||
'type': 'unavailability_calendars',
|
||
'urls': {
|
||
'dependencies': 'http://testserver/api/export-import/unavailability_calendars/foo/dependencies/',
|
||
'export': 'http://testserver/api/export-import/unavailability_calendars/foo/',
|
||
'redirect': 'http://testserver/api/export-import/unavailability_calendars/foo/redirect/',
|
||
},
|
||
}
|
||
],
|
||
'err': 0,
|
||
}
|
||
|
||
|
||
def test_agenda_dependencies_groups(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
group1 = Group.objects.create(name='group1')
|
||
group2 = Group.objects.create(name='group2')
|
||
Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings', edit_role=group1, view_role=group2)
|
||
resp = app.get('/api/export-import/agendas/rdv/dependencies/')
|
||
# note: with hobo.agent.common installed, 'groups' will contain group slugs,
|
||
# not group id
|
||
assert resp.json == {
|
||
'data': [
|
||
{'id': group2.id, 'text': group2.name, 'type': 'roles', 'urls': {}},
|
||
{'id': group1.id, 'text': group1.name, 'type': 'roles', 'urls': {}},
|
||
],
|
||
'err': 0,
|
||
}
|
||
|
||
|
||
def test_agenda_dependencies_virtual_agendas(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
rdv1 = Agenda.objects.create(label='Rdv1', slug='rdv1', kind='meetings')
|
||
rdv2 = Agenda.objects.create(label='Rdv2', slug='rdv2', kind='meetings')
|
||
virt = Agenda.objects.create(label='Virt', slug='virt', kind='virtual')
|
||
virt.real_agendas.add(rdv1)
|
||
virt.real_agendas.add(rdv2)
|
||
resp = app.get('/api/export-import/agendas/virt/dependencies/')
|
||
assert resp.json == {
|
||
'data': [
|
||
{
|
||
'id': 'rdv1',
|
||
'text': 'Rdv1',
|
||
'type': 'agendas',
|
||
'urls': {
|
||
'dependencies': 'http://testserver/api/export-import/agendas/rdv1/dependencies/',
|
||
'export': 'http://testserver/api/export-import/agendas/rdv1/',
|
||
'redirect': 'http://testserver/api/export-import/agendas/rdv1/redirect/',
|
||
},
|
||
},
|
||
{
|
||
'id': 'rdv2',
|
||
'text': 'Rdv2',
|
||
'type': 'agendas',
|
||
'urls': {
|
||
'dependencies': 'http://testserver/api/export-import/agendas/rdv2/dependencies/',
|
||
'export': 'http://testserver/api/export-import/agendas/rdv2/',
|
||
'redirect': 'http://testserver/api/export-import/agendas/rdv2/redirect/',
|
||
},
|
||
},
|
||
],
|
||
'err': 0,
|
||
}
|
||
|
||
|
||
def test_agenda_dependencies_events_type(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
events_type = EventsType.objects.create(slug='foo', label='Foo')
|
||
Agenda.objects.create(label='Evt', slug='evt', kind='events', events_type=events_type)
|
||
resp = app.get('/api/export-import/agendas/evt/dependencies/')
|
||
assert resp.json == {
|
||
'data': [
|
||
{
|
||
'id': 'foo',
|
||
'text': 'Foo',
|
||
'type': 'events_types',
|
||
'urls': {
|
||
'dependencies': 'http://testserver/api/export-import/events_types/foo/dependencies/',
|
||
'export': 'http://testserver/api/export-import/events_types/foo/',
|
||
'redirect': 'http://testserver/api/export-import/events_types/foo/redirect/',
|
||
},
|
||
}
|
||
],
|
||
'err': 0,
|
||
}
|
||
|
||
|
||
def test_agenda_redirect(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
agenda = Agenda.objects.create(label='Rdv', slug='rdv', kind='meetings')
|
||
redirect_url = '/api/export-import/agendas/rdv/redirect/'
|
||
resp = app.get(redirect_url, status=302)
|
||
assert resp.location == f'/manage/agendas/{agenda.pk}/'
|
||
|
||
|
||
def create_bundle(app, user, visible=True, version_number='42.0'):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
|
||
group, _ = Group.objects.get_or_create(name='plop')
|
||
category, _ = Category.objects.get_or_create(slug='foo', label='Foo')
|
||
|
||
meetings_agenda, _ = Agenda.objects.get_or_create(
|
||
slug='rdv', label='Rdv', kind='meetings', edit_role=group, category=category
|
||
)
|
||
resource, _ = Resource.objects.get_or_create(slug='foo', label='Foo')
|
||
meetings_agenda.resources.add(resource)
|
||
desk, _ = Desk.objects.get_or_create(slug='foo', label='Foo', agenda=meetings_agenda)
|
||
unavailability_calendar, _ = UnavailabilityCalendar.objects.get_or_create(slug='foo', label='Foo')
|
||
desk.unavailability_calendars.add(unavailability_calendar)
|
||
|
||
events_type, _ = EventsType.objects.get_or_create(slug='foo', label='Foo')
|
||
events_agenda, _ = Agenda.objects.get_or_create(
|
||
label='Evt', slug='evt', kind='events', events_type=events_type
|
||
)
|
||
Desk.objects.get_or_create(agenda=events_agenda, slug='_exceptions_holder')
|
||
|
||
sub_agenda, _ = Agenda.objects.get_or_create(slug='sub', label='Sub', kind='meetings')
|
||
virtual_agenda, _ = Agenda.objects.get_or_create(label='Virt', slug='virt', kind='virtual')
|
||
virtual_agenda.real_agendas.add(sub_agenda)
|
||
|
||
components = [
|
||
(meetings_agenda, False),
|
||
(category, True),
|
||
(resource, True),
|
||
(unavailability_calendar, True),
|
||
(events_agenda, False),
|
||
(events_type, True),
|
||
(virtual_agenda, False),
|
||
(sub_agenda, True),
|
||
]
|
||
|
||
tar_io = io.BytesIO()
|
||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||
manifest_json = {
|
||
'application': 'Test',
|
||
'slug': 'test',
|
||
'icon': 'foo.png',
|
||
'description': 'Foo Bar',
|
||
'documentation_url': 'http://foo.bar',
|
||
'visible': visible,
|
||
'version_number': version_number,
|
||
'version_notes': 'foo bar blah',
|
||
'elements': [],
|
||
}
|
||
for component, auto_dependency in components:
|
||
manifest_json['elements'].append(
|
||
{
|
||
'type': component.application_component_type,
|
||
'slug': component.slug,
|
||
'name': component.label,
|
||
'auto-dependency': auto_dependency,
|
||
}
|
||
)
|
||
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
|
||
tarinfo = tarfile.TarInfo('manifest.json')
|
||
tarinfo.size = len(manifest_fd.getvalue())
|
||
tar.addfile(tarinfo, fileobj=manifest_fd)
|
||
|
||
icon_fd = io.BytesIO(
|
||
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
|
||
)
|
||
tarinfo = tarfile.TarInfo('foo.png')
|
||
tarinfo.size = len(icon_fd.getvalue())
|
||
tar.addfile(tarinfo, fileobj=icon_fd)
|
||
|
||
for component, _ in components:
|
||
component_export = app.get(
|
||
'/api/export-import/%s/%s/' % (component.application_component_type, component.slug)
|
||
).content
|
||
tarinfo = tarfile.TarInfo('%s/%s' % (component.application_component_type, component.slug))
|
||
tarinfo.size = len(component_export)
|
||
tar.addfile(tarinfo, fileobj=io.BytesIO(component_export))
|
||
bundle = tar_io.getvalue()
|
||
return bundle
|
||
|
||
|
||
@pytest.fixture
|
||
def bundle(app, user):
|
||
return create_bundle(app, user)
|
||
|
||
|
||
def test_bundle_import(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
|
||
bundles = []
|
||
for version_number in ['42.0', '42.1']:
|
||
bundles.append(create_bundle(app, user, version_number=version_number))
|
||
|
||
Agenda.objects.all().delete()
|
||
Category.objects.all().delete()
|
||
Resource.objects.all().delete()
|
||
EventsType.objects.all().delete()
|
||
UnavailabilityCalendar.objects.all().delete()
|
||
|
||
resp = app.put('/api/export-import/bundle-import/', bundles[0])
|
||
assert Agenda.objects.all().count() == 4
|
||
assert resp.json['err'] == 0
|
||
assert Application.objects.count() == 1
|
||
application = Application.objects.latest('pk')
|
||
assert application.slug == 'test'
|
||
assert application.name == 'Test'
|
||
assert application.description == 'Foo Bar'
|
||
assert application.documentation_url == 'http://foo.bar'
|
||
assert application.version_number == '42.0'
|
||
assert application.version_notes == 'foo bar blah'
|
||
assert re.match(r'applications/icons/foo(_\w+)?.png', application.icon.name)
|
||
assert application.editable is False
|
||
assert application.visible is True
|
||
assert ApplicationElement.objects.count() == 8
|
||
|
||
# check editable flag is kept on install
|
||
application.editable = True
|
||
application.save()
|
||
|
||
# create link to element not present in manifest: it should be unlinked
|
||
last_agenda = Agenda.objects.latest('pk')
|
||
ApplicationElement.objects.create(
|
||
application=application,
|
||
content_type=ContentType.objects.get_for_model(Agenda),
|
||
object_id=last_agenda.pk + 1,
|
||
)
|
||
|
||
# check update
|
||
resp = app.put('/api/export-import/bundle-import/', bundles[1])
|
||
assert Agenda.objects.all().count() == 4
|
||
assert resp.json['err'] == 0
|
||
assert Application.objects.count() == 1
|
||
application = Application.objects.latest('pk')
|
||
assert application.editable is False
|
||
assert ApplicationElement.objects.count() == 8
|
||
assert (
|
||
ApplicationElement.objects.filter(
|
||
application=application,
|
||
content_type=ContentType.objects.get_for_model(Agenda),
|
||
object_id=last_agenda.pk + 1,
|
||
).exists()
|
||
is False
|
||
)
|
||
|
||
|
||
def test_bundle_declare(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
|
||
bundle = create_bundle(app, user, visible=False)
|
||
resp = app.put('/api/export-import/bundle-declare/', bundle)
|
||
assert Agenda.objects.all().count() == 4
|
||
assert resp.json['err'] == 0
|
||
assert Application.objects.count() == 1
|
||
application = Application.objects.latest('pk')
|
||
assert application.slug == 'test'
|
||
assert application.name == 'Test'
|
||
assert application.description == 'Foo Bar'
|
||
assert application.documentation_url == 'http://foo.bar'
|
||
assert application.version_number == '42.0'
|
||
assert application.version_notes == 'foo bar blah'
|
||
assert re.match(r'applications/icons/foo(_\w+)?.png', application.icon.name)
|
||
assert application.editable is True
|
||
assert application.visible is False
|
||
assert ApplicationElement.objects.count() == 8
|
||
|
||
bundle = create_bundle(app, user, visible=True)
|
||
# create link to element not present in manifest: it should be unlinked
|
||
last_page = Agenda.objects.latest('pk')
|
||
ApplicationElement.objects.create(
|
||
application=application,
|
||
content_type=ContentType.objects.get_for_model(Agenda),
|
||
object_id=last_page.pk + 1,
|
||
)
|
||
# and remove agendas to have unkown references in manifest
|
||
Agenda.objects.all().delete()
|
||
|
||
resp = app.put('/api/export-import/bundle-declare/', bundle)
|
||
assert Application.objects.count() == 1
|
||
application = Application.objects.latest('pk')
|
||
assert application.visible is True
|
||
assert ApplicationElement.objects.count() == 4 # category, events_type, unavailability_calendar, resource
|
||
|
||
|
||
def test_bundle_unlink(app, user, bundle):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
|
||
application = Application.objects.create(
|
||
name='Test',
|
||
slug='test',
|
||
version_number='42.0',
|
||
)
|
||
other_application = Application.objects.create(
|
||
name='Other Test',
|
||
slug='other-test',
|
||
version_number='42.0',
|
||
)
|
||
agenda = Agenda.objects.latest('pk')
|
||
ApplicationElement.objects.create(
|
||
application=application,
|
||
content_object=agenda,
|
||
)
|
||
ApplicationElement.objects.create(
|
||
application=application,
|
||
content_type=ContentType.objects.get_for_model(Agenda),
|
||
object_id=agenda.pk + 1,
|
||
)
|
||
ApplicationElement.objects.create(
|
||
application=other_application,
|
||
content_object=agenda,
|
||
)
|
||
ApplicationElement.objects.create(
|
||
application=other_application,
|
||
content_type=ContentType.objects.get_for_model(Agenda),
|
||
object_id=agenda.pk + 1,
|
||
)
|
||
|
||
assert Application.objects.count() == 2
|
||
assert ApplicationElement.objects.count() == 4
|
||
app.post('/api/export-import/unlink/', {'application': 'test'})
|
||
assert Application.objects.count() == 1
|
||
assert ApplicationElement.objects.count() == 2
|
||
assert ApplicationElement.objects.filter(
|
||
application=other_application,
|
||
content_type=ContentType.objects.get_for_model(Agenda),
|
||
object_id=agenda.pk,
|
||
).exists()
|
||
assert ApplicationElement.objects.filter(
|
||
application=other_application,
|
||
content_type=ContentType.objects.get_for_model(Agenda),
|
||
object_id=agenda.pk + 1,
|
||
).exists()
|
||
|
||
# again
|
||
app.post('/api/export-import/unlink/', {'application': 'test'})
|
||
assert Application.objects.count() == 1
|
||
assert ApplicationElement.objects.count() == 2
|
||
|
||
|
||
def test_bundle_check(app, user):
|
||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||
assert app.put('/api/export-import/bundle-check/').json == {'err': 0, 'data': {}}
|