applification: bundle malformé (#88132) #246
|
@ -32,6 +32,7 @@ from rest_framework.response import Response
|
|||
from combo.apps.export_import.models import Application, ApplicationAsyncJob, ApplicationElement
|
||||
from combo.apps.wcs.utils import WCSError
|
||||
from combo.data.models import Page, PageSnapshot
|
||||
from combo.utils.api import APIErrorBadRequest
|
||||
from combo.utils.misc import is_portal_agent
|
||||
|
||||
klasses = {klass.application_component_type: klass for klass in [Page]}
|
||||
|
@ -39,7 +40,7 @@ klasses['roles'] = Group
|
|||
|
||||
|
||||
class Index(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if is_portal_agent():
|
||||
|
@ -127,7 +128,7 @@ def get_component_bundle_entry(request, component, order):
|
|||
|
||||
|
||||
class ListComponents(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
klass = klasses[kwargs['component_type']]
|
||||
|
@ -145,7 +146,7 @@ list_components = ListComponents.as_view()
|
|||
|
||||
|
||||
class ExportComponent(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, uuid, *args, **kwargs):
|
||||
serialisation = get_object_or_404(Page, uuid=uuid).get_serialized_page()
|
||||
|
@ -156,7 +157,7 @@ export_component = ExportComponent.as_view()
|
|||
|
||||
|
||||
class ComponentDependencies(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def get(self, request, uuid, *args, **kwargs):
|
||||
klass = klasses[kwargs['component_type']]
|
||||
|
@ -199,94 +200,100 @@ def component_redirect(request, component_type, uuid):
|
|||
|
||||
|
||||
class BundleCheck(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
tar_io = io.BytesIO(request.read())
|
||||
page_type = 'portal-agent-pages' if is_portal_agent() else 'pages'
|
||||
with tarfile.open(fileobj=tar_io) as tar:
|
||||
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
|
||||
application_slug = manifest.get('slug')
|
||||
application_version = manifest.get('version_number')
|
||||
if not application_slug or not application_version:
|
||||
return Response({'data': {}})
|
||||
|
||||
differences = []
|
||||
unknown_elements = []
|
||||
no_history_elements = []
|
||||
legacy_elements = []
|
||||
content_type = ContentType.objects.get_for_model(Page)
|
||||
for element in manifest.get('elements'):
|
||||
if element.get('type') != page_type:
|
||||
continue
|
||||
try:
|
||||
with tarfile.open(fileobj=tar_io) as tar:
|
||||
try:
|
||||
page = Page.objects.get(uuid=element['slug'])
|
||||
except Page.DoesNotExist:
|
||||
unknown_elements.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
}
|
||||
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
|
||||
except KeyError:
|
||||
raise APIErrorBadRequest(_('Invalid tar file, missing manifest'))
|
||||
|
||||
application_slug = manifest.get('slug')
|
||||
application_version = manifest.get('version_number')
|
||||
if not application_slug or not application_version:
|
||||
return Response({'data': {}})
|
||||
|
||||
differences = []
|
||||
unknown_elements = []
|
||||
no_history_elements = []
|
||||
legacy_elements = []
|
||||
content_type = ContentType.objects.get_for_model(Page)
|
||||
for element in manifest.get('elements'):
|
||||
if element.get('type') != page_type:
|
||||
continue
|
||||
try:
|
||||
page = Page.objects.get(uuid=element['slug'])
|
||||
pmarillonnet
commented
Pareil ici, ce n’est pas la PR qui introduit ça et de toute façon l’API est déjà écrite, mais pour un relecteur peu éclairé comme moi cette confusion entre uuid et slug n’aide pas trop :/ Pareil ici, ce n’est pas la PR qui introduit ça et de toute façon l’API est déjà écrite, mais pour un relecteur peu éclairé comme moi cette confusion entre uuid et slug n’aide pas trop :/
lguerin
commented
yes, contrainte hobo yes, contrainte hobo
|
||||
except Page.DoesNotExist:
|
||||
unknown_elements.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
}
|
||||
)
|
||||
continue
|
||||
elements_qs = ApplicationElement.objects.filter(
|
||||
application__slug=application_slug,
|
||||
content_type=content_type,
|
||||
object_id=page.pk,
|
||||
)
|
||||
continue
|
||||
elements_qs = ApplicationElement.objects.filter(
|
||||
application__slug=application_slug,
|
||||
content_type=content_type,
|
||||
object_id=page.pk,
|
||||
)
|
||||
if not elements_qs.exists():
|
||||
# object exists, but not linked to the application
|
||||
legacy_elements.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
# information needed here, Relation objects may not exist yet in hobo
|
||||
'text': page.title,
|
||||
'url': request.build_absolute_uri(
|
||||
reverse(
|
||||
'api-export-import-component-redirect',
|
||||
kwargs={
|
||||
'uuid': str(page.uuid),
|
||||
'component_type': page.application_component_type,
|
||||
},
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
continue
|
||||
snapshot_for_app = (
|
||||
PageSnapshot.objects.filter(
|
||||
page=page,
|
||||
application_slug=application_slug,
|
||||
application_version=application_version,
|
||||
)
|
||||
.order_by('timestamp')
|
||||
.last()
|
||||
)
|
||||
if not snapshot_for_app:
|
||||
# no snapshot for this bundle
|
||||
no_history_elements.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
}
|
||||
)
|
||||
continue
|
||||
last_snapshot = PageSnapshot.objects.filter(page=page).latest('timestamp')
|
||||
if snapshot_for_app.pk != last_snapshot.pk:
|
||||
differences.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
'url': '%s%s?version1=%s&version2=%s'
|
||||
% (
|
||||
request.build_absolute_uri('/')[:-1],
|
||||
reverse('combo-manager-page-history-compare', args=[page.pk]),
|
||||
snapshot_for_app.pk,
|
||||
last_snapshot.pk,
|
||||
),
|
||||
}
|
||||
if not elements_qs.exists():
|
||||
# object exists, but not linked to the application
|
||||
legacy_elements.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
# information needed here, Relation objects may not exist yet in hobo
|
||||
'text': page.title,
|
||||
'url': request.build_absolute_uri(
|
||||
reverse(
|
||||
'api-export-import-component-redirect',
|
||||
kwargs={
|
||||
'uuid': str(page.uuid),
|
||||
'component_type': page.application_component_type,
|
||||
},
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
continue
|
||||
snapshot_for_app = (
|
||||
PageSnapshot.objects.filter(
|
||||
page=page,
|
||||
application_slug=application_slug,
|
||||
application_version=application_version,
|
||||
)
|
||||
.order_by('timestamp')
|
||||
.last()
|
||||
)
|
||||
if not snapshot_for_app:
|
||||
# no snapshot for this bundle
|
||||
no_history_elements.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
}
|
||||
)
|
||||
continue
|
||||
last_snapshot = PageSnapshot.objects.filter(page=page).latest('timestamp')
|
||||
if snapshot_for_app.pk != last_snapshot.pk:
|
||||
differences.append(
|
||||
{
|
||||
'type': element['type'],
|
||||
'slug': element['slug'],
|
||||
'url': '%s%s?version1=%s&version2=%s'
|
||||
% (
|
||||
request.build_absolute_uri('/')[:-1],
|
||||
reverse('combo-manager-page-history-compare', args=[page.pk]),
|
||||
snapshot_for_app.pk,
|
||||
last_snapshot.pk,
|
||||
),
|
||||
}
|
||||
)
|
||||
except tarfile.TarError:
|
||||
raise APIErrorBadRequest(_('Invalid tar file'))
|
||||
|
||||
return Response(
|
||||
{
|
||||
|
@ -304,14 +311,20 @@ bundle_check = BundleCheck.as_view()
|
|||
|
||||
|
||||
class BundleImport(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
action = 'import_bundle'
|
||||
|
||||
def put(self, request, *args, **kwargs):
|
||||
tar_io = io.BytesIO(request.read())
|
||||
with tarfile.open(fileobj=tar_io) as tar:
|
||||
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
|
||||
application_slug = manifest.get('slug')
|
||||
try:
|
||||
with tarfile.open(fileobj=tar_io) as tar:
|
||||
try:
|
||||
manifest = json.loads(tar.extractfile('manifest.json').read().decode())
|
||||
except KeyError:
|
||||
raise APIErrorBadRequest(_('Invalid tar file, missing manifest'))
|
||||
application_slug = manifest.get('slug')
|
||||
except tarfile.TarError:
|
||||
raise APIErrorBadRequest(_('Invalid tar file'))
|
||||
job = ApplicationAsyncJob(
|
||||
action=self.action,
|
||||
)
|
||||
|
@ -325,7 +338,6 @@ bundle_import = BundleImport.as_view()
|
|||
|
||||
|
||||
class BundleDeclare(BundleImport):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
action = 'declare_bundle'
|
||||
|
||||
|
||||
|
@ -333,7 +345,7 @@ bundle_declare = BundleDeclare.as_view()
|
|||
|
||||
|
||||
class BundleUnlink(GenericAPIView):
|
||||
permission_classes = (permissions.IsAuthenticated,)
|
||||
permission_classes = (permissions.IsAdminUser,)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
if request.POST.get('application'):
|
||||
|
|
|
@ -33,6 +33,10 @@ from django.utils.translation import gettext_lazy as _
|
|||
from combo.utils.misc import is_portal_agent
|
||||
|
||||
|
||||
class BundleKeyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Application(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
slug = models.SlugField(max_length=100, unique=True)
|
||||
|
@ -59,10 +63,10 @@ class Application(models.Model):
|
|||
slug=manifest.get('slug'), defaults={'editable': editable}
|
||||
)
|
||||
application.name = manifest.get('application')
|
||||
application.description = manifest.get('description')
|
||||
application.documentation_url = manifest.get('documentation_url')
|
||||
application.description = manifest.get('description') or ''
|
||||
application.documentation_url = manifest.get('documentation_url') or ''
|
||||
application.version_number = manifest.get('version_number') or 'unknown'
|
||||
application.version_notes = manifest.get('version_notes')
|
||||
application.version_notes = manifest.get('version_notes') or ''
|
||||
if not editable:
|
||||
application.editable = editable
|
||||
application.visible = manifest.get('visible', True)
|
||||
|
@ -171,8 +175,6 @@ class ApplicationAsyncJob(models.Model):
|
|||
last_update_timestamp = models.DateTimeField(auto_now=True)
|
||||
completion_timestamp = models.DateTimeField(default=None, null=True)
|
||||
|
||||
raise_exception = True
|
||||
|
||||
def run(self, spool=False):
|
||||
if 'uwsgi' in sys.modules and spool:
|
||||
from combo.utils.spooler import run_async_job
|
||||
|
@ -183,11 +185,12 @@ class ApplicationAsyncJob(models.Model):
|
|||
self.save()
|
||||
try:
|
||||
getattr(self, self.action)()
|
||||
except BundleKeyError as e:
|
||||
self.status = 'failed'
|
||||
self.exception = str(e)
|
||||
except Exception:
|
||||
self.status = 'failed'
|
||||
self.exception = traceback.format_exc()
|
||||
if self.raise_exception:
|
||||
raise
|
||||
finally:
|
||||
if self.status == 'running':
|
||||
self.status = 'completed'
|
||||
|
@ -212,9 +215,17 @@ class ApplicationAsyncJob(models.Model):
|
|||
for element in manifest.get('elements'):
|
||||
if element.get('type') != page_type:
|
||||
continue
|
||||
pages.append(
|
||||
json.loads(tar.extractfile(f'{page_type}/{element["slug"]}').read().decode()).get('data')
|
||||
)
|
||||
try:
|
||||
pages.append(
|
||||
json.loads(tar.extractfile(f'{page_type}/{element["slug"]}').read().decode()).get(
|
||||
'data'
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
raise BundleKeyError(
|
||||
'Invalid tar file, missing component %s/%s.' % (page_type, element['slug'])
|
||||
)
|
||||
|
||||
# init cache of application elements, from manifest
|
||||
self.application_elements = set()
|
||||
# install pages
|
||||
|
|
|
@ -391,6 +391,8 @@ CHART_FILTERS_CELL_ENABLED = True
|
|||
# default country code for phonenumbers' user phone parsing
|
||||
DEFAULT_COUNTRY_CODE = '33'
|
||||
|
||||
REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'combo.utils.api.exception_handler'}
|
||||
|
||||
|
||||
def debug_show_toolbar(request):
|
||||
from debug_toolbar.middleware import show_toolbar as dt_show_toolbar # pylint: disable=import-error
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# combo - content management system
|
||||
# Copyright (C) 2017-2024 Entr'ouvert
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Affero General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.response import Response as DRFResponse
|
||||
from rest_framework.views import exception_handler as DRF_exception_handler
|
||||
|
||||
|
||||
class Response(DRFResponse):
|
||||
def __init__(self, data=None, *args, **kwargs):
|
||||
# add reason for compatibility (https://dev.entrouvert.org/issues/24025)
|
||||
if data is not None and 'err_class' in data:
|
||||
data['reason'] = data['err_class']
|
||||
super().__init__(data=data, *args, **kwargs)
|
||||
|
||||
|
||||
class APIError(Exception):
|
||||
http_status = 200
|
||||
|
||||
def __init__(self, message, *args, err=1, err_class=None, errors=None):
|
||||
self.err_desc = _(message) % args
|
||||
self.err = err
|
||||
self.err_class = err_class or message % args
|
||||
self.errors = errors
|
||||
super().__init__(self.err_desc)
|
||||
|
||||
def to_response(self):
|
||||
data = {
|
||||
'err': self.err,
|
||||
'err_class': self.err_class,
|
||||
'err_desc': self.err_desc,
|
||||
}
|
||||
if self.errors:
|
||||
data['errors'] = self.errors
|
||||
return Response(data, status=self.http_status)
|
||||
|
||||
|
||||
class APIErrorBadRequest(APIError):
|
||||
http_status = 400
|
||||
|
||||
|
||||
def exception_handler(exc, context):
|
||||
if isinstance(exc, APIError):
|
||||
return exc.to_response()
|
||||
|
||||
return DRF_exception_handler(exc, context)
|
|
@ -125,7 +125,8 @@ REST_FRAMEWORK = {
|
|||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
]
|
||||
],
|
||||
'EXCEPTION_HANDLER': 'combo.utils.api.exception_handler',
|
||||
}
|
||||
|
||||
DEFAULT_COUNTRY_CODE = '33'
|
||||
|
|
|
@ -28,8 +28,8 @@ from .wcs.utils import MockedRequestResponse, mocked_requests_send
|
|||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
def test_object_types(settings, app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_object_types(settings, app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
resp = app.get('/api/export-import/')
|
||||
assert resp.json == {
|
||||
'data': [
|
||||
|
@ -71,8 +71,8 @@ def test_object_types(settings, app, john_doe):
|
|||
}
|
||||
|
||||
|
||||
def test_list(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_list(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Test', slug='test', template_name='standard')
|
||||
page.save()
|
||||
|
@ -115,8 +115,8 @@ def test_list(app, john_doe):
|
|||
}
|
||||
|
||||
|
||||
def test_export_page(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_export_page(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
Page.objects.all().delete()
|
||||
page = Page(title='Test', slug='test', template_name='standard')
|
||||
page.save()
|
||||
|
@ -126,8 +126,8 @@ def test_export_page(app, john_doe):
|
|||
app.get('/api/export-import/pages/%s/' % uuid.uuid4(), status=404)
|
||||
|
||||
|
||||
def test_export_page_with_role(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_export_page_with_role(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
group = Group(name='plop')
|
||||
group.save()
|
||||
Page.objects.all().delete()
|
||||
|
@ -138,8 +138,8 @@ def test_export_page_with_role(app, john_doe):
|
|||
assert resp.json['data']['fields']['groups'] == ['plop']
|
||||
|
||||
|
||||
def test_page_dependencies(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_page_dependencies(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
page = Page.objects.create(title='Test', slug='test', template_name='standard')
|
||||
resp = app.get(f'/api/export-import/pages/{page.uuid}/dependencies/')
|
||||
assert resp.json == {'data': [], 'err': 0}
|
||||
|
@ -147,8 +147,8 @@ def test_page_dependencies(app, john_doe):
|
|||
app.get('/api/export-import/pages/%s/dependencies/' % uuid.uuid4(), status=404)
|
||||
|
||||
|
||||
def test_page_dependencies_groups(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_page_dependencies_groups(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
group1 = Group(name='plop1')
|
||||
group1.save()
|
||||
group2 = Group(name='plop2')
|
||||
|
@ -172,8 +172,8 @@ def test_page_dependencies_groups(app, john_doe):
|
|||
}
|
||||
|
||||
|
||||
def test_page_dependencies_children(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_page_dependencies_children(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
page = Page(title='Test', slug='test', template_name='standard')
|
||||
page.save()
|
||||
page2 = Page(title='Child', slug='child', template_name='standard', parent=page)
|
||||
|
@ -198,8 +198,7 @@ def test_page_dependencies_children(app, john_doe):
|
|||
}
|
||||
|
||||
|
||||
def test_page_redirect(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_page_redirect(app):
|
||||
page = Page(title='Test', slug='test', template_name='standard')
|
||||
page.save()
|
||||
redirect_url = f'/api/export-import/pages/{page.uuid}/redirect/'
|
||||
|
@ -257,7 +256,7 @@ def create_bundle(elements, *args, **kwargs):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def bundle(app, john_doe):
|
||||
def bundle(app, admin_user):
|
||||
page, dummy = Page.objects.get_or_create(
|
||||
slug='test', defaults={'title': 'Test Page', 'template_name': 'standard'}
|
||||
)
|
||||
|
@ -270,8 +269,8 @@ def bundle(app, john_doe):
|
|||
)
|
||||
|
||||
|
||||
def test_bundle_import(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_bundle_import(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
|
||||
bundles = []
|
||||
for version_number in ['42.0', '42.1']:
|
||||
|
@ -347,9 +346,48 @@ def test_bundle_import(app, john_doe):
|
|||
assert last_snapshot.application_slug == 'test'
|
||||
assert last_snapshot.application_version == '42.1'
|
||||
|
||||
# bad file format
|
||||
resp = app.put('/api/export-import/bundle-import/', b'garbage', status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file'
|
||||
|
||||
def test_bundle_import_pages_position(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = app.put('/api/export-import/bundle-import/', tar_io.getvalue(), status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing manifest'
|
||||
|
||||
# missing component
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
manifest_json = {
|
||||
'application': 'Test',
|
||||
'slug': 'test',
|
||||
'elements': [{'type': 'pages', 'slug': str(uuid.uuid4()), 'name': 'foo'}],
|
||||
}
|
||||
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)
|
||||
resp = app.put('/api/export-import/bundle-import/', tar_io.getvalue())
|
||||
job_url = resp.json['url']
|
||||
resp = app.get(job_url)
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
job = ApplicationAsyncJob.objects.get(uuid=job_url.split('/')[-3])
|
||||
assert job.status == 'failed'
|
||||
assert (
|
||||
job.exception
|
||||
== 'Invalid tar file, missing component pages/%s.' % manifest_json['elements'][0]['slug']
|
||||
)
|
||||
|
||||
|
||||
def test_bundle_import_pages_position(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
page1 = Page.objects.create(
|
||||
slug='test1',
|
||||
title='Test Page 1',
|
||||
|
@ -891,8 +929,8 @@ def test_bundle_import_pages_position(app, john_doe):
|
|||
assert Page.objects.get(uuid=page2.uuid).parent is None
|
||||
|
||||
|
||||
def test_bundle_declare(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_bundle_declare(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
|
||||
page, dummy = Page.objects.get_or_create(
|
||||
slug='test', defaults={'title': 'Test Page', 'template_name': 'standard'}
|
||||
|
@ -952,9 +990,48 @@ def test_bundle_declare(app, john_doe):
|
|||
assert application.visible is True
|
||||
assert ApplicationElement.objects.count() == 0
|
||||
|
||||
# bad file format
|
||||
resp = app.put('/api/export-import/bundle-declare/', b'garbage', status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file'
|
||||
|
||||
def test_bundle_unlink(app, john_doe, bundle):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = app.put('/api/export-import/bundle-declare/', tar_io.getvalue(), status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing manifest'
|
||||
|
||||
# missing component
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
manifest_json = {
|
||||
'application': 'Test',
|
||||
'slug': 'test',
|
||||
'elements': [{'type': 'pages', 'slug': str(uuid.uuid4()), 'name': 'foo'}],
|
||||
}
|
||||
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)
|
||||
resp = app.put('/api/export-import/bundle-declare/', tar_io.getvalue())
|
||||
job_url = resp.json['url']
|
||||
resp = app.get(job_url)
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
job = ApplicationAsyncJob.objects.get(uuid=job_url.split('/')[-3])
|
||||
assert job.status == 'failed'
|
||||
assert (
|
||||
job.exception
|
||||
== 'Invalid tar file, missing component pages/%s.' % manifest_json['elements'][0]['slug']
|
||||
)
|
||||
|
||||
|
||||
def test_bundle_unlink(app, admin_user, bundle):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
|
||||
application = Application.objects.create(
|
||||
name='Test',
|
||||
|
@ -1008,8 +1085,8 @@ def test_bundle_unlink(app, john_doe, bundle):
|
|||
assert ApplicationElement.objects.count() == 2
|
||||
|
||||
|
||||
def test_bundle_check(app, john_doe):
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
def test_bundle_check(app, admin_user):
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
|
||||
bundles = []
|
||||
for version_number in ['1.0', '2.0']:
|
||||
|
@ -1153,9 +1230,25 @@ def test_bundle_check(app, john_doe):
|
|||
}
|
||||
}
|
||||
|
||||
# bad file format
|
||||
resp = app.put('/api/export-import/bundle-check/', b'garbage', status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file'
|
||||
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = app.put('/api/export-import/bundle-check/', tar_io.getvalue(), status=400)
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing manifest'
|
||||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_page_dependencies_card_models(mock_send, app, john_doe):
|
||||
def test_page_dependencies_card_models(mock_send, app, admin_user):
|
||||
page = Page.objects.create(
|
||||
title='Test',
|
||||
slug='test',
|
||||
|
@ -1174,7 +1267,7 @@ def test_page_dependencies_card_models(mock_send, app, john_doe):
|
|||
}
|
||||
assert card_dep in page.get_dependencies()
|
||||
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
with mock.patch('requests.Session.get') as requests_get:
|
||||
requests_get.side_effect = ConnectionError()
|
||||
resp = app.get(f'/api/export-import/pages/{page.uuid}/dependencies/', status=400)
|
||||
|
@ -1269,7 +1362,7 @@ def test_page_dependencies_linkslist_cell():
|
|||
|
||||
|
||||
@mock.patch('requests.Session.send', side_effect=mocked_requests_send)
|
||||
def test_page_dependencies_form_cell(mock_send, app, john_doe):
|
||||
def test_page_dependencies_form_cell(mock_send, app, admin_user):
|
||||
page = Page.objects.create(title='Test', slug='test', template_name='standard')
|
||||
cell = WcsFormCell(page=page, placeholder='content', order=0, formdef_reference='default:form-title')
|
||||
cell.save()
|
||||
|
@ -1283,7 +1376,7 @@ def test_page_dependencies_form_cell(mock_send, app, john_doe):
|
|||
'redirect': 'http://127.0.0.1:8999/api/export-import/forms/form-title/redirect/',
|
||||
},
|
||||
} in page.get_dependencies()
|
||||
app.authorization = ('Basic', (john_doe.username, john_doe.username))
|
||||
app.authorization = ('Basic', (admin_user.username, admin_user.username))
|
||||
resp = app.get(f'/api/export-import/pages/{page.uuid}/dependencies/')
|
||||
assert resp.json['data'][0]['type'] == 'forms'
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Ok ici l’ensemble de l’API est déjà écrite alors ça ne changera pas, mais je pense qu’on aurait raisonnablement pu envoyer autre chose que des http 200 sur ces erreurs de requête d’applification ratées (sans pour autant que ça prête à confusion entre l’applicatif et le technique).
bien vu, hobo ne gère pas les 200 avec err!=0, j'ai changé ça