2022-10-18 17:38:31 +02:00
|
|
|
import base64
|
2022-01-08 16:07:08 +01:00
|
|
|
import io
|
|
|
|
import json
|
2022-11-14 21:07:47 +01:00
|
|
|
import random
|
2022-10-18 17:38:31 +02:00
|
|
|
import re
|
2022-01-08 16:07:08 +01:00
|
|
|
import tarfile
|
|
|
|
|
2022-08-09 15:28:17 +02:00
|
|
|
import httmock
|
2022-01-08 16:07:08 +01:00
|
|
|
import pytest
|
|
|
|
from httmock import HTTMock
|
2022-11-08 18:02:04 +01:00
|
|
|
from pyquery import PyQuery
|
2022-01-08 16:07:08 +01:00
|
|
|
from test_manager import login
|
|
|
|
from webtest import Upload
|
|
|
|
|
2022-11-03 09:30:08 +01:00
|
|
|
from hobo.applications.models import (
|
|
|
|
Application,
|
|
|
|
AsyncJob,
|
|
|
|
DeploymentError,
|
|
|
|
Element,
|
|
|
|
Relation,
|
|
|
|
ScanError,
|
|
|
|
Version,
|
|
|
|
)
|
2022-08-09 15:28:17 +02:00
|
|
|
from hobo.environment.models import Authentic, Wcs
|
2022-01-08 16:07:08 +01:00
|
|
|
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
|
|
WCS_AVAILABLE_OBJECTS = {
|
|
|
|
"data": [
|
|
|
|
{
|
|
|
|
"id": "forms",
|
|
|
|
"text": "Forms",
|
|
|
|
"singular": "Form",
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/forms/"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "cards",
|
|
|
|
"text": "Card Models",
|
|
|
|
"singular": "Card Model",
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/cards/"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "workflows",
|
|
|
|
"text": "Workflows",
|
|
|
|
"singular": "Workflow",
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/workflows/"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "blocks",
|
|
|
|
"text": "Blocks",
|
|
|
|
"singular": "Block of fields",
|
|
|
|
"minor": True,
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/blocks/"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "data-sources",
|
|
|
|
"text": "Data Sources",
|
|
|
|
"singular": "Data Source",
|
|
|
|
"minor": True,
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/data-sources/"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "mail-templates",
|
|
|
|
"text": "Mail Templates",
|
|
|
|
"singular": "Mail Template",
|
|
|
|
"minor": True,
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/mail-templates/"},
|
|
|
|
},
|
2023-02-06 17:50:05 +01:00
|
|
|
{
|
|
|
|
"id": "comment-templates-categories",
|
|
|
|
"text": "Categories (comment templates)",
|
|
|
|
"singular": "Category (comment templates)",
|
|
|
|
"minor": True,
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/comment-templates-categories/"},
|
|
|
|
},
|
2022-01-08 16:07:08 +01:00
|
|
|
{
|
|
|
|
"id": "wscalls",
|
|
|
|
"text": "Webservice Calls",
|
|
|
|
"singular": "Webservice Call",
|
|
|
|
"minor": True,
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/wscalls/"},
|
|
|
|
},
|
2022-10-24 16:56:58 +02:00
|
|
|
{
|
|
|
|
"id": "roles",
|
|
|
|
"text": "Roles",
|
|
|
|
"singular": "Role",
|
|
|
|
"minor": True,
|
|
|
|
"urls": {"list": "https://wcs.example.invalid/api/export-import/roles/"},
|
|
|
|
},
|
2022-01-08 16:07:08 +01:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
WCS_AVAILABLE_FORMS = {
|
|
|
|
"data": [
|
|
|
|
{
|
|
|
|
"id": "test-form",
|
|
|
|
"text": "Test Form",
|
|
|
|
"type": "forms",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/forms/test-form/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/test-form/dependencies/",
|
2022-10-24 16:56:58 +02:00
|
|
|
"redirect": "https://wcs.example.invalid/api/export-import/forms/test-form/redirect/",
|
2022-01-08 16:07:08 +01:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "test2-form",
|
|
|
|
"text": "Second Test Form",
|
|
|
|
"type": "forms",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/forms/test2-form/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/test2-form/dependencies/",
|
|
|
|
},
|
|
|
|
},
|
2022-11-08 18:02:04 +01:00
|
|
|
{
|
|
|
|
"id": "foo2-form",
|
|
|
|
"text": "Foo2 Test Form",
|
|
|
|
"type": "forms",
|
|
|
|
"category": "Foo",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/forms/foo2-form/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/foo2-form/dependencies/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "foo-form",
|
|
|
|
"text": "Foo Test Form",
|
|
|
|
"type": "forms",
|
|
|
|
"category": "Foo",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/forms/foo-form/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/foo-form/dependencies/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"id": "bar-form",
|
|
|
|
"text": "Bar Test Form",
|
|
|
|
"type": "forms",
|
|
|
|
"category": "Bar",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/forms/bar-form/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/bar-form/dependencies/",
|
|
|
|
},
|
|
|
|
},
|
2022-01-08 16:07:08 +01:00
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
WCS_FORM_DEPENDENCIES = {
|
|
|
|
"data": [
|
|
|
|
{
|
|
|
|
"id": "test-card",
|
|
|
|
"text": "Test Card",
|
|
|
|
"type": "cards",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/cards/test-card/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/cards/test-card/dependencies/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def mocked_http(url, request):
|
2022-09-13 09:42:36 +02:00
|
|
|
assert url.query.count('&signature=') == 1
|
2022-08-09 15:28:17 +02:00
|
|
|
|
|
|
|
if url.netloc == 'idp.example.invalid':
|
|
|
|
if url.path == '/api/roles/':
|
|
|
|
return {
|
|
|
|
'content': json.dumps({'name': 'test', 'uuid': '123', 'slug': 'test'}),
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
|
|
|
if url.path == '/api/provision/':
|
|
|
|
return {
|
|
|
|
'content': json.dumps({'name': 'test', 'uuid': '123', 'slug': 'test'}),
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
|
|
|
return {'content': '{}', 'status_code': 500}
|
|
|
|
|
2022-01-08 16:07:08 +01:00
|
|
|
if url.netloc == 'wcs.example.invalid' and url.path == '/api/export-import/':
|
|
|
|
return {'content': json.dumps(WCS_AVAILABLE_OBJECTS), 'status_code': 200}
|
|
|
|
|
|
|
|
if url.path == '/api/export-import/forms/':
|
|
|
|
return {'content': json.dumps(WCS_AVAILABLE_FORMS), 'status_code': 200}
|
|
|
|
|
|
|
|
if url.path == '/api/export-import/forms/test-form/dependencies/':
|
|
|
|
return {'content': json.dumps(WCS_FORM_DEPENDENCIES), 'status_code': 200}
|
|
|
|
|
|
|
|
if url.path.endswith('/dependencies/'):
|
|
|
|
return {'content': json.dumps({'data': []}), 'status_code': 200}
|
|
|
|
|
|
|
|
if url.path == '/api/export-import/forms/test-form/':
|
|
|
|
return {'content': '<formdef/>', 'status_code': 200, 'headers': {'content-length': '10'}}
|
|
|
|
|
|
|
|
if url.path == '/api/export-import/cards/test-card/':
|
|
|
|
return {'content': '<carddef/>', 'status_code': 200, 'headers': {'content-length': '10'}}
|
|
|
|
|
|
|
|
if url.path == '/api/export-import/bundle-import/':
|
2022-11-17 15:15:32 +01:00
|
|
|
return {
|
|
|
|
'content': json.dumps({'err': 0, 'url': 'https://wcs.example.invalid/api/jobs/job-uuid/'}),
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
2022-01-08 16:07:08 +01:00
|
|
|
|
2022-11-14 15:47:45 +01:00
|
|
|
return {'content': json.dumps({'data': []}), 'status_code': 200}
|
|
|
|
|
2022-01-08 16:07:08 +01:00
|
|
|
|
2022-10-18 11:43:33 +02:00
|
|
|
@pytest.mark.parametrize('analyze', [True, False])
|
|
|
|
def test_create_application(app, admin_user, settings, analyze):
|
2022-01-08 16:07:08 +01:00
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
2022-09-06 18:14:21 +02:00
|
|
|
'blah': {
|
|
|
|
# simulate an instance from another collectivity
|
|
|
|
'title': 'Unknown',
|
|
|
|
'url': 'https://unknown.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
},
|
2022-01-08 16:07:08 +01:00
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
2022-09-06 18:14:21 +02:00
|
|
|
},
|
2022-01-08 16:07:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
|
|
|
|
resp = app.get('/applications/')
|
|
|
|
resp = resp.click('Create')
|
|
|
|
resp.form['name'] = 'Test'
|
|
|
|
resp = resp.form.submit()
|
|
|
|
|
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = resp.follow()
|
|
|
|
assert 'You should now assemble the different parts of your application.' in resp.text
|
|
|
|
|
|
|
|
# edit metadata
|
|
|
|
resp = resp.click('Metadata')
|
|
|
|
resp.form['description'] = 'Lorem ipsum'
|
2022-10-21 09:28:04 +02:00
|
|
|
resp.form['documentation_url'] = 'http://foo.bar'
|
2022-01-08 16:07:08 +01:00
|
|
|
resp = resp.form.submit().follow()
|
2022-10-21 09:28:04 +02:00
|
|
|
application = Application.objects.get(slug='test')
|
|
|
|
assert application.icon.name == ''
|
|
|
|
assert application.documentation_url == 'http://foo.bar'
|
2022-01-08 16:07:08 +01:00
|
|
|
|
|
|
|
# add forms
|
|
|
|
assert '/add/forms/' in resp
|
|
|
|
resp = resp.click('Forms')
|
2022-11-08 18:02:04 +01:00
|
|
|
assert len(resp.pyquery('.application-elements div')) == 3
|
|
|
|
assert resp.pyquery('.application-elements div:nth-child(1) h4').text() == 'Bar'
|
|
|
|
assert PyQuery(resp.pyquery('.application-elements div:nth-child(1) input')[0]).val() == 'bar-form'
|
|
|
|
assert resp.pyquery('.application-elements div:nth-child(2) h4').text() == 'Foo'
|
|
|
|
assert PyQuery(resp.pyquery('.application-elements div:nth-child(2) input')[0]).val() == 'foo-form'
|
|
|
|
assert PyQuery(resp.pyquery('.application-elements div:nth-child(2) input')[1]).val() == 'foo2-form'
|
|
|
|
assert resp.pyquery('.application-elements div:nth-child(3) h4').text() == 'Uncategorized'
|
|
|
|
assert PyQuery(resp.pyquery('.application-elements div:nth-child(3) input')[0]).val() == 'test2-form'
|
|
|
|
assert PyQuery(resp.pyquery('.application-elements div:nth-child(3) input')[1]).val() == 'test-form'
|
|
|
|
resp.form.fields['elements'][4].checked = True
|
2022-01-08 16:07:08 +01:00
|
|
|
resp = resp.form.submit().follow()
|
2022-10-21 09:28:04 +02:00
|
|
|
assert application.elements.count() == 1
|
|
|
|
element = application.elements.all()[0]
|
2022-01-08 16:07:08 +01:00
|
|
|
assert element.slug == 'test-form'
|
2022-10-18 11:43:33 +02:00
|
|
|
assert 'Test Card' not in resp.text
|
2022-01-08 16:07:08 +01:00
|
|
|
|
2022-10-18 11:43:33 +02:00
|
|
|
if analyze:
|
|
|
|
resp = resp.click('Scan dependencies').follow()
|
|
|
|
assert Application.objects.get(slug='test').elements.count() == 2
|
2022-01-08 16:07:08 +01:00
|
|
|
|
2022-10-20 14:39:59 +02:00
|
|
|
resp = resp.click('Generate application bundle')
|
|
|
|
resp.form['number'] = '1.0'
|
|
|
|
resp.form['notes'] = 'Foo bar blah.'
|
|
|
|
resp = resp.form.submit().follow()
|
2022-10-18 11:43:33 +02:00
|
|
|
assert 'Test Card' in resp.text
|
2022-10-20 14:39:59 +02:00
|
|
|
version = Version.objects.latest('pk')
|
|
|
|
assert version.number == '1.0'
|
|
|
|
assert version.notes == 'Foo bar blah.'
|
2022-01-08 16:07:08 +01:00
|
|
|
resp = resp.click('Download')
|
|
|
|
assert resp.content_type == 'application/x-tar'
|
|
|
|
# uncompressed tar, primitive check of contents
|
|
|
|
assert b'<formdef/>' in resp.content
|
|
|
|
assert b'<carddef/>' in resp.content
|
2022-10-18 17:38:31 +02:00
|
|
|
assert b'"icon": null' in resp.content
|
2022-10-21 09:28:04 +02:00
|
|
|
assert b'"documentation_url": "http://foo.bar"' in resp.content
|
2022-10-20 14:39:59 +02:00
|
|
|
assert b'"version_number": "1.0"' in resp.content
|
|
|
|
assert b'"version_notes": "Foo bar blah."' in resp.content
|
|
|
|
|
2022-10-20 16:39:21 +02:00
|
|
|
resp = app.get('/applications/manifest/test/versions/')
|
2022-12-01 10:55:29 +01:00
|
|
|
versions = [e.text() for e in resp.pyquery('h3').items()]
|
|
|
|
assert versions.count('1.0') == 1
|
2022-11-02 15:15:26 +01:00
|
|
|
assert resp.text.count('Creating application bundle') == 1
|
2022-10-20 16:39:21 +02:00
|
|
|
resp = resp.click(href='/applications/manifest/test/download/%s/' % version.pk)
|
|
|
|
assert resp.content_type == 'application/x-tar'
|
|
|
|
assert b'"version_number": "1.0"' in resp.content
|
|
|
|
|
2022-10-20 14:39:59 +02:00
|
|
|
# generate again without changing version number
|
|
|
|
resp = app.get('/applications/manifest/test/generate/')
|
|
|
|
assert resp.form['number'].value == '1.0' # last one
|
|
|
|
assert resp.form['notes'].value == 'Foo bar blah.' # last one
|
|
|
|
resp.form['notes'] = 'Foo bar blahha.'
|
|
|
|
resp = resp.form.submit().follow()
|
|
|
|
same_version = Version.objects.latest('pk')
|
|
|
|
assert same_version.number == '1.0'
|
|
|
|
assert same_version.notes == 'Foo bar blahha.'
|
|
|
|
assert same_version.pk == version.pk
|
|
|
|
resp = resp.click('Download')
|
|
|
|
assert resp.content_type == 'application/x-tar'
|
|
|
|
assert b'"version_number": "1.0"' in resp.content
|
|
|
|
assert b'"version_notes": "Foo bar blahha."' in resp.content
|
2022-10-18 17:38:31 +02:00
|
|
|
|
2022-10-20 16:39:21 +02:00
|
|
|
resp = app.get('/applications/manifest/test/versions/')
|
2022-12-01 10:55:29 +01:00
|
|
|
versions = [e.text() for e in resp.pyquery('h3').items()]
|
|
|
|
assert versions.count('1.0') == 1
|
2022-11-02 15:15:26 +01:00
|
|
|
assert resp.text.count('Creating application bundle') == 2
|
2022-10-20 16:39:21 +02:00
|
|
|
resp = resp.click(href='/applications/manifest/test/download/%s/' % same_version.pk)
|
|
|
|
assert resp.content_type == 'application/x-tar'
|
|
|
|
assert b'"version_number": "1.0"' in resp.content
|
|
|
|
|
2022-10-18 17:38:31 +02:00
|
|
|
# add an icon
|
|
|
|
resp = app.get('/applications/manifest/test/metadata/')
|
|
|
|
resp.form['icon'] = Upload(
|
|
|
|
'foo.png',
|
|
|
|
base64.decodebytes(
|
|
|
|
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
|
|
|
|
),
|
|
|
|
'image/png',
|
|
|
|
)
|
2022-10-21 09:28:04 +02:00
|
|
|
resp.form['documentation_url'] = '' # and reset documentation_url
|
2022-10-18 17:38:31 +02:00
|
|
|
resp = resp.form.submit().follow()
|
2022-10-21 09:28:04 +02:00
|
|
|
application.refresh_from_db()
|
|
|
|
assert re.match(r'applications/icons/foo(_\w+)?.png', application.icon.name)
|
|
|
|
assert application.documentation_url == ''
|
2022-10-18 17:38:31 +02:00
|
|
|
|
|
|
|
# try an icon in an invalid format
|
|
|
|
resp = app.get('/applications/manifest/test/metadata/')
|
|
|
|
resp.form['icon'] = Upload('test.txt', b'hello', 'text/plain')
|
|
|
|
resp = resp.form.submit()
|
|
|
|
assert 'The icon must be in JPEG or PNG format' in resp
|
2022-10-21 09:28:04 +02:00
|
|
|
application.refresh_from_db()
|
|
|
|
assert re.match(r'applications/icons/foo(_\w+)?.png', application.icon.name)
|
2022-10-18 17:38:31 +02:00
|
|
|
|
|
|
|
resp = app.get('/applications/manifest/test/')
|
2022-10-20 14:39:59 +02:00
|
|
|
resp = resp.click('Generate application bundle')
|
|
|
|
resp.form['number'] = '2.0'
|
|
|
|
resp.form['notes'] = 'Foo bar blah. But with an icon.'
|
|
|
|
resp = resp.form.submit().follow()
|
2022-10-18 17:38:31 +02:00
|
|
|
resp = resp.click('Download')
|
|
|
|
assert resp.content_type == 'application/x-tar'
|
|
|
|
# uncompressed tar, primitive check of contents
|
|
|
|
assert b'<formdef/>' in resp.content
|
|
|
|
assert b'<carddef/>' in resp.content
|
|
|
|
assert b'"icon": "foo' in resp.content
|
2022-10-21 09:28:04 +02:00
|
|
|
assert b'"documentation_url": ""' in resp.content
|
2022-10-20 14:39:59 +02:00
|
|
|
assert b'"version_number": "2.0"' in resp.content
|
|
|
|
assert b'"version_notes": "Foo bar blah. But with an icon."' in resp.content
|
|
|
|
version = Version.objects.latest('pk')
|
|
|
|
assert version.number == '2.0'
|
|
|
|
assert version.notes == 'Foo bar blah. But with an icon.'
|
|
|
|
assert version.pk != same_version.pk
|
|
|
|
|
2022-10-20 16:39:21 +02:00
|
|
|
resp = app.get('/applications/manifest/test/versions/')
|
2022-11-12 10:51:30 +01:00
|
|
|
versions = [e.text() for e in resp.pyquery('h3').items()]
|
|
|
|
assert versions.count('1.0') == 1
|
|
|
|
assert versions.count('2.0') == 1
|
2022-11-02 15:15:26 +01:00
|
|
|
assert resp.text.count('Creating application bundle') == 3
|
2022-10-20 16:39:21 +02:00
|
|
|
resp = resp.click(href='/applications/manifest/test/download/%s/' % same_version.pk)
|
|
|
|
assert resp.content_type == 'application/x-tar'
|
|
|
|
assert b'"version_number": "1.0"' in resp.content
|
|
|
|
resp = app.get('/applications/manifest/test/versions/')
|
|
|
|
resp = resp.click(href='/applications/manifest/test/download/%s/' % version.pk)
|
|
|
|
assert resp.content_type == 'application/x-tar'
|
|
|
|
assert b'"version_number": "2.0"' in resp.content
|
|
|
|
|
2022-10-20 14:39:59 +02:00
|
|
|
resp = app.get('/applications/manifest/test/generate/')
|
|
|
|
assert resp.form['number'].value == '2.0' # last one
|
|
|
|
assert resp.form['notes'].value == 'Foo bar blah. But with an icon.' # last one
|
2022-11-02 14:50:24 +01:00
|
|
|
resp.form['number'] = '1.0' # old number
|
|
|
|
resp = resp.form.submit().follow()
|
|
|
|
new_version = Version.objects.latest('pk')
|
|
|
|
assert new_version.number == '1.0'
|
|
|
|
assert new_version.notes == 'Foo bar blah. But with an icon.'
|
|
|
|
assert new_version.pk != version.pk # new version created
|
2022-11-02 15:15:26 +01:00
|
|
|
resp = app.get('/applications/manifest/test/versions/')
|
2022-11-12 10:51:30 +01:00
|
|
|
versions = [e.text() for e in resp.pyquery('h3').items()]
|
|
|
|
assert versions.count('1.0') == 2
|
|
|
|
assert versions.count('2.0') == 1
|
2022-11-02 15:15:26 +01:00
|
|
|
assert resp.text.count('Creating application bundle') == 4
|
2022-01-08 16:07:08 +01:00
|
|
|
|
2023-02-20 14:23:23 +01:00
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/bundle-declare/':
|
|
|
|
return {'status_code': 500}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
resp = app.get('/applications/manifest/test/generate/')
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(DeploymentError) as e:
|
|
|
|
resp.form.submit()
|
|
|
|
assert str(e.value) == 'Failed to declare elements for module wcs (500)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to declare elements for module wcs (500)'
|
|
|
|
|
|
|
|
# non editable app
|
|
|
|
application.editable = False
|
|
|
|
application.save()
|
|
|
|
app.get('/applications/manifest/test/metadata/', status=404)
|
|
|
|
app.get('/applications/manifest/test/scandeps/', status=404)
|
|
|
|
app.get('/applications/manifest/test/generate/', status=404)
|
|
|
|
app.get('/applications/manifest/test/add/forms/', status=404)
|
|
|
|
app.get('/applications/manifest/test/delete/%s/' % application.relation_set.first().pk, status=404)
|
2022-10-31 16:14:12 +01:00
|
|
|
|
|
|
|
|
2022-11-14 21:07:47 +01:00
|
|
|
def test_manifest_ordering(app, admin_user, settings):
|
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
application = Application.objects.create(name='Test', slug='test')
|
|
|
|
objects = [
|
|
|
|
# type, slug, auto_dependency
|
|
|
|
('forms', 'bar', True),
|
|
|
|
('forms', 'foo', True),
|
|
|
|
('cards', 'bar', True),
|
|
|
|
('cards', 'foo', True),
|
|
|
|
('workflows', 'bar', True),
|
|
|
|
('workflows', 'foo', True),
|
|
|
|
('forms', 'baaaar', False),
|
|
|
|
('forms', 'foooo', False),
|
|
|
|
('cards', 'baaaar', False),
|
|
|
|
('cards', 'foooo', False),
|
|
|
|
('workflows', 'baaaar', False),
|
|
|
|
('workflows', 'foooo', False),
|
|
|
|
]
|
|
|
|
random.shuffle(objects)
|
|
|
|
for _type, slug, auto_dependency in objects:
|
|
|
|
element = Element.objects.create(
|
|
|
|
type=_type,
|
|
|
|
slug=slug,
|
|
|
|
name=slug.title(),
|
|
|
|
cache={},
|
|
|
|
)
|
|
|
|
Relation.objects.create(application=application, element=element, auto_dependency=auto_dependency)
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = app.get('/applications/manifest/test/')
|
|
|
|
assert resp.pyquery('.application-content li a').text() == (
|
|
|
|
'Baaaar - Form remove '
|
|
|
|
'Foooo - Form remove '
|
|
|
|
'Baaaar - Card Model remove '
|
|
|
|
'Foooo - Card Model remove '
|
|
|
|
'Baaaar - Workflow remove '
|
|
|
|
'Foooo - Workflow remove '
|
|
|
|
'Bar - Form '
|
|
|
|
'Foo - Form '
|
|
|
|
'Bar - Card Model '
|
|
|
|
'Foo - Card Model '
|
|
|
|
'Bar - Workflow '
|
|
|
|
'Foo - Workflow'
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-11-03 09:30:08 +01:00
|
|
|
def test_scandeps_on_unknown_element(app, admin_user, settings):
|
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
application = Application.objects.create(name='Test', slug='test')
|
|
|
|
element = Element.objects.create(
|
|
|
|
type='forms',
|
|
|
|
slug='unknown',
|
|
|
|
name='Unknown',
|
|
|
|
cache={
|
|
|
|
'urls': {
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/unknown/dependencies/",
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
2022-11-18 10:23:45 +01:00
|
|
|
relation = Relation.objects.create(application=application, element=element)
|
2022-11-03 09:30:08 +01:00
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/forms/unknown/dependencies/':
|
|
|
|
return {'status_code': 404}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(ScanError) as e:
|
|
|
|
app.get('/applications/manifest/test/scandeps/').follow()
|
|
|
|
assert str(e.value) == 'Failed to scan "Unknown" (type forms, slug unknown) dependencies (404)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to scan "Unknown" (type forms, slug unknown) dependencies (404)'
|
2022-11-18 10:23:45 +01:00
|
|
|
relation.refresh_from_db()
|
|
|
|
assert relation.error is True
|
|
|
|
assert relation.error_status == 'notfound'
|
2022-11-04 09:54:55 +01:00
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/forms/unknown/dependencies/':
|
|
|
|
return {'status_code': 500}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(ScanError) as e:
|
|
|
|
app.get('/applications/manifest/test/scandeps/').follow()
|
|
|
|
assert str(e.value) == 'Failed to scan "Unknown" (type forms, slug unknown) dependencies (500)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to scan "Unknown" (type forms, slug unknown) dependencies (500)'
|
2022-11-18 10:23:45 +01:00
|
|
|
relation.refresh_from_db()
|
|
|
|
assert relation.error is True
|
|
|
|
assert relation.error_status == 'error'
|
2022-11-04 09:54:55 +01:00
|
|
|
|
|
|
|
with HTTMock(mocked_http):
|
|
|
|
app.get('/applications/manifest/test/scandeps/').follow()
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'completed'
|
2022-11-18 10:23:45 +01:00
|
|
|
relation.refresh_from_db()
|
|
|
|
assert relation.error is False
|
|
|
|
assert relation.error_status is None
|
2022-11-03 09:30:08 +01:00
|
|
|
|
|
|
|
|
2022-11-14 15:47:45 +01:00
|
|
|
def test_scandeps_on_renamed_element(app, admin_user, settings):
|
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
application = Application.objects.create(name='Test', slug='test')
|
|
|
|
element = Element.objects.create(
|
|
|
|
type='forms',
|
|
|
|
slug='test-form',
|
|
|
|
name='Test 1 form',
|
|
|
|
cache={
|
|
|
|
'urls': {'redirect': 'https://wcs.example.invalid/api/export-import/forms/test-form/redirect/'}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
Relation.objects.create(application=application, element=element)
|
|
|
|
element2 = Element.objects.create(
|
|
|
|
type="cards",
|
|
|
|
slug="test-card",
|
|
|
|
name="Test Card",
|
|
|
|
cache={
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/cards/test-card/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/cards/test-card/dependencies/",
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
Relation.objects.create(application=application, element=element2, auto_dependency=True)
|
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/forms/':
|
|
|
|
return {
|
|
|
|
'content': {
|
|
|
|
"data": [
|
|
|
|
{
|
|
|
|
"id": "test-form",
|
|
|
|
"text": "Test Form (renamed)",
|
|
|
|
"type": "forms",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/forms/test-form/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/test-form/dependencies/",
|
|
|
|
"redirect": "https://wcs.example.invalid/api/export-import/forms/test-form/redirect/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
]
|
|
|
|
},
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
|
|
|
if url.path == '/api/export-import/forms/test-form/dependencies/':
|
|
|
|
return {
|
|
|
|
'content': {
|
|
|
|
"data": [
|
|
|
|
{
|
|
|
|
"id": "test-card",
|
|
|
|
"text": "Test Card (renamed)",
|
|
|
|
"type": "cards",
|
|
|
|
"urls": {
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/cards/test-card/dependencies/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
with HTTMock(response_content):
|
|
|
|
app.get('/applications/manifest/test/scandeps/').follow()
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'completed'
|
|
|
|
element.refresh_from_db()
|
|
|
|
assert element.name == 'Test Form (renamed)'
|
|
|
|
assert element.cache == {
|
|
|
|
"id": "test-form",
|
|
|
|
"text": "Test Form (renamed)",
|
|
|
|
"type": "forms",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/forms/test-form/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/test-form/dependencies/",
|
|
|
|
"redirect": "https://wcs.example.invalid/api/export-import/forms/test-form/redirect/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
element2 = Element.objects.get(relation__auto_dependency=True)
|
|
|
|
assert element2.name == 'Test Card (renamed)'
|
|
|
|
assert element2.cache == {
|
|
|
|
"id": "test-card",
|
|
|
|
"text": "Test Card (renamed)",
|
|
|
|
"type": "cards",
|
|
|
|
"urls": {
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/cards/test-card/dependencies/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/forms/':
|
|
|
|
return {'status_code': 404}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(ScanError) as e:
|
|
|
|
app.get('/applications/manifest/test/scandeps/').follow()
|
|
|
|
assert str(e.value) == 'Failed to get elements of type forms (404)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to get elements of type forms (404)'
|
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/forms/':
|
|
|
|
return {'status_code': 500}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(ScanError) as e:
|
|
|
|
app.get('/applications/manifest/test/scandeps/').follow()
|
|
|
|
assert str(e.value) == 'Failed to get elements of type forms (500)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to get elements of type forms (500)'
|
|
|
|
|
|
|
|
|
2022-10-31 16:14:12 +01:00
|
|
|
@pytest.mark.parametrize('editable', [True, False])
|
|
|
|
def test_redirect_application_element(app, admin_user, settings, editable):
|
2022-10-24 16:56:58 +02:00
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-31 16:14:12 +01:00
|
|
|
application = Application.objects.create(name='Test', slug='test', editable=editable)
|
2022-10-24 16:56:58 +02:00
|
|
|
element = Element.objects.create(
|
|
|
|
type='forms',
|
|
|
|
slug='test-form',
|
|
|
|
name='Test 1 form',
|
|
|
|
cache={
|
|
|
|
'urls': {'redirect': 'https://wcs.example.invalid/api/export-import/forms/test-form/redirect/'}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
Relation.objects.create(application=application, element=element)
|
|
|
|
element = Element.objects.create(
|
|
|
|
type='forms',
|
|
|
|
slug='test2-form',
|
|
|
|
name='Test 2 form',
|
|
|
|
cache={'urls': {'export': 'https://wcs.example.invalid/api/export-import/forms/test2-form/'}},
|
|
|
|
)
|
|
|
|
Relation.objects.create(application=application, element=element)
|
|
|
|
element = Element.objects.create(
|
|
|
|
type='forms',
|
|
|
|
slug='test3-form',
|
|
|
|
name='Test 3 form',
|
|
|
|
cache={},
|
|
|
|
)
|
|
|
|
Relation.objects.create(application=application, element=element)
|
|
|
|
element = Element.objects.create(
|
|
|
|
type='roles',
|
|
|
|
slug='test',
|
|
|
|
name='Test',
|
|
|
|
cache={'urls': {'redirect': 'https://wcs.example.invalid/api/export-import/roles/test/redirect/'}},
|
|
|
|
)
|
|
|
|
Relation.objects.create(application=application, element=element)
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
|
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = app.get('/applications/manifest/test/')
|
|
|
|
assert 'https://wcs.example.invalid/api/export-import/forms/test-form/redirect/' in resp
|
|
|
|
assert (
|
|
|
|
'https://wcs.example.invalid/api/export-import/forms/test2-form/redirect/' in resp
|
|
|
|
) # no redirect url, but it's ok from export url
|
|
|
|
assert (
|
|
|
|
'https://wcs.example.invalid/api/export-import/forms/test3-form/redirect/' not in resp
|
|
|
|
) # no urls
|
|
|
|
assert (
|
|
|
|
'https://wcs.example.invalid/api/export-import/roles/test/redirect/' not in resp
|
|
|
|
) # not for roles
|
|
|
|
|
|
|
|
|
2022-03-29 16:55:03 +02:00
|
|
|
def test_delete_application(app, admin_user, settings):
|
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
|
|
|
|
Application.objects.create(name='AppToDelete', slug='app_to_delete')
|
|
|
|
Application.objects.create(name='OtherApp', slug='other_app')
|
|
|
|
|
|
|
|
assert Application.objects.count() == 2
|
|
|
|
|
2022-12-02 13:58:58 +01:00
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = app.get('/applications/manifest/app_to_delete/')
|
2023-02-20 16:34:04 +01:00
|
|
|
resp = resp.click(re.compile('^Delete$'))
|
|
|
|
resp = resp.forms[0].submit()
|
2022-03-29 16:55:03 +02:00
|
|
|
resp = resp.follow()
|
|
|
|
|
|
|
|
assert '/applications/' in resp
|
|
|
|
assert 'AppToDelete' not in resp.text
|
|
|
|
|
|
|
|
assert Application.objects.count() == 1
|
|
|
|
assert Application.objects.first().name == 'OtherApp'
|
|
|
|
|
2023-02-20 16:34:04 +01:00
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/unlink/':
|
|
|
|
return {'status_code': 500}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
with HTTMock(response_content):
|
|
|
|
resp = app.get('/applications/manifest/other_app/')
|
|
|
|
resp = resp.click(re.compile('^Delete$'))
|
|
|
|
resp = resp.forms[0].submit()
|
|
|
|
resp = resp.follow()
|
|
|
|
assert 'Failed to unlink application in module wcs (500)' in resp
|
|
|
|
assert Application.objects.count() == 1
|
|
|
|
assert Application.objects.first().name == 'OtherApp'
|
|
|
|
|
2022-03-29 16:55:03 +02:00
|
|
|
|
2022-12-02 11:00:09 +01:00
|
|
|
def test_404_unknown_app(app, admin_user, settings):
|
|
|
|
login(app)
|
|
|
|
app.get('/applications/manifest/xxx/', status=404)
|
|
|
|
|
|
|
|
|
2022-01-08 16:07:08 +01:00
|
|
|
@pytest.fixture
|
|
|
|
def app_bundle():
|
2022-10-18 17:38:31 +02:00
|
|
|
return get_bundle()
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def app_bundle_with_icon():
|
|
|
|
return get_bundle(with_icon=True)
|
|
|
|
|
|
|
|
|
|
|
|
def get_bundle(with_icon=False):
|
2022-01-08 16:07:08 +01:00
|
|
|
tar_io = io.BytesIO()
|
|
|
|
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
|
|
|
manifest_json = {
|
|
|
|
'application': 'Test',
|
|
|
|
'slug': 'test',
|
2022-10-18 17:38:31 +02:00
|
|
|
'icon': 'foo.png' if with_icon else None,
|
2022-01-08 16:07:08 +01:00
|
|
|
'description': '',
|
2022-10-21 09:28:04 +02:00
|
|
|
'documentation_url': 'http://foo.bar',
|
2022-11-02 14:43:44 +01:00
|
|
|
'version_number': '43.0' if with_icon else '42.0',
|
2022-10-20 14:39:59 +02:00
|
|
|
'version_notes': 'foo bar blah',
|
2022-01-08 16:07:08 +01:00
|
|
|
'elements': [
|
|
|
|
{'type': 'forms', 'slug': 'test', 'name': 'test', 'auto-dependency': False},
|
|
|
|
{'type': 'blocks', 'slug': 'test', 'name': 'test', 'auto-dependency': True},
|
|
|
|
{'type': 'workflows', 'slug': 'test', 'name': 'test', 'auto-dependency': True},
|
2023-02-06 17:50:05 +01:00
|
|
|
{
|
|
|
|
'type': 'comment-templates-categories',
|
|
|
|
'slug': 'test',
|
|
|
|
'name': 'test',
|
|
|
|
'auto-dependency': True,
|
|
|
|
},
|
2022-01-08 16:07:08 +01:00
|
|
|
],
|
|
|
|
}
|
|
|
|
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)
|
2022-10-18 17:38:31 +02:00
|
|
|
if with_icon:
|
|
|
|
icon_fd = io.BytesIO(
|
|
|
|
b'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVQI12NoAAAAggCB3UNq9AAAAABJRU5ErkJggg=='
|
|
|
|
)
|
|
|
|
tarinfo = tarfile.TarInfo('foo.png')
|
|
|
|
tarinfo.size = len(icon_fd.getvalue())
|
|
|
|
tar.addfile(tarinfo, fileobj=icon_fd)
|
2022-01-08 16:07:08 +01:00
|
|
|
|
|
|
|
return tar_io.getvalue()
|
|
|
|
|
|
|
|
|
2022-11-17 17:55:27 +01:00
|
|
|
@pytest.mark.parametrize('action', ['Install', 'Update'])
|
|
|
|
def test_deploy_application(app, admin_user, settings, app_bundle, app_bundle_with_icon, action):
|
2022-01-08 16:07:08 +01:00
|
|
|
Application.objects.all().delete()
|
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
|
2022-11-17 17:55:27 +01:00
|
|
|
if action == 'Update':
|
|
|
|
Application.objects.create(name='Test', slug='test', editable=False)
|
|
|
|
|
2022-11-02 14:43:44 +01:00
|
|
|
def install(resp, bundle):
|
2022-11-17 17:55:27 +01:00
|
|
|
resp = resp.click(action)
|
2022-11-02 14:43:44 +01:00
|
|
|
resp.form['bundle'] = Upload('app.tar', bundle, 'application/x-tar')
|
2022-01-08 16:07:08 +01:00
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = resp.form.submit().follow()
|
|
|
|
|
|
|
|
assert Application.objects.count() == 1
|
2022-11-02 14:43:44 +01:00
|
|
|
application = Application.objects.get(slug='test')
|
|
|
|
assert application.name == 'Test'
|
|
|
|
if bundle == app_bundle_with_icon:
|
|
|
|
assert re.match(r'applications/icons/foo(_\w+)?.png', application.icon.name)
|
|
|
|
else:
|
|
|
|
assert application.icon.name == ''
|
|
|
|
assert application.documentation_url == 'http://foo.bar'
|
|
|
|
version = application.version_set.latest('pk')
|
|
|
|
if bundle == app_bundle_with_icon:
|
|
|
|
assert version.number == '43.0'
|
2022-10-18 17:38:31 +02:00
|
|
|
else:
|
2022-11-02 14:43:44 +01:00
|
|
|
assert version.number == '42.0'
|
2022-10-20 14:39:59 +02:00
|
|
|
assert version.notes == 'foo bar blah'
|
2023-02-06 17:50:05 +01:00
|
|
|
assert application.elements.count() == 4
|
2022-11-17 15:15:32 +01:00
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'completed'
|
|
|
|
assert job.progression_urls == {'wcs': {'Foobar': 'https://wcs.example.invalid/api/jobs/job-uuid/'}}
|
2022-11-02 14:43:44 +01:00
|
|
|
return version
|
|
|
|
|
|
|
|
resp = app.get('/applications/')
|
2022-11-17 17:55:27 +01:00
|
|
|
if action == 'Update':
|
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = resp.click(href='/manifest/test/')
|
2022-11-02 14:43:44 +01:00
|
|
|
version1 = install(resp, app_bundle)
|
|
|
|
assert version1.application.version_set.count() == 1
|
|
|
|
version2 = install(resp, app_bundle)
|
|
|
|
assert version2.application.version_set.count() == 1
|
|
|
|
assert version1.pk == version2.pk
|
|
|
|
assert version1.creation_timestamp == version2.creation_timestamp
|
|
|
|
assert version1.last_update_timestamp < version2.last_update_timestamp
|
|
|
|
version3 = install(resp, app_bundle_with_icon)
|
|
|
|
assert version2.application.version_set.count() == 2
|
|
|
|
assert version2.pk != version3.pk
|
|
|
|
assert version2.creation_timestamp < version3.creation_timestamp
|
|
|
|
assert version2.last_update_timestamp < version3.last_update_timestamp
|
|
|
|
version4 = install(resp, app_bundle)
|
|
|
|
assert version4.application.version_set.count() == 3
|
|
|
|
assert version3.pk != version4.pk
|
|
|
|
assert version3.creation_timestamp < version4.creation_timestamp
|
|
|
|
assert version3.last_update_timestamp < version4.last_update_timestamp
|
2022-11-02 15:15:26 +01:00
|
|
|
resp = app.get('/applications/manifest/test/versions/')
|
2022-12-01 10:55:29 +01:00
|
|
|
versions = [e.text() for e in resp.pyquery('h3').items()]
|
|
|
|
assert versions.count('42.0') == 2
|
|
|
|
assert versions.count('43.0') == 1
|
2022-11-02 15:15:26 +01:00
|
|
|
assert resp.text.count('Deploying application bundle') == 4
|
2022-08-09 15:28:17 +02:00
|
|
|
|
2022-11-02 17:00:17 +01:00
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/bundle-import/':
|
|
|
|
return {'status_code': 500}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
2022-11-17 17:55:27 +01:00
|
|
|
resp = app.get('/applications/')
|
|
|
|
if action == 'Update':
|
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = resp.click(href='manifest/test/')
|
|
|
|
resp = resp.click(action)
|
2022-11-02 17:00:17 +01:00
|
|
|
resp.form['bundle'] = Upload('app.tar', app_bundle, 'application/x-tar')
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(DeploymentError) as e:
|
|
|
|
resp.form.submit()
|
|
|
|
assert str(e.value) == 'Failed to deploy module wcs (500)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to deploy module wcs (500)'
|
|
|
|
|
2022-11-21 15:22:10 +01:00
|
|
|
form_def = {
|
|
|
|
"id": "test",
|
|
|
|
"text": "Test",
|
|
|
|
"type": "forms",
|
|
|
|
"urls": {
|
|
|
|
"export": "https://wcs.example.invalid/api/export-import/forms/test/",
|
|
|
|
"dependencies": "https://wcs.example.invalid/api/export-import/forms/test/dependencies/",
|
|
|
|
"redirect": "https://wcs.example.invalid/api/export-import/forms/test/redirect/",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/forms/':
|
|
|
|
return {
|
|
|
|
'content': {"data": [form_def]},
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
resp = app.get('/applications/')
|
|
|
|
if action == 'Update':
|
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = resp.click(href='manifest/test/')
|
|
|
|
resp = resp.click(action)
|
|
|
|
resp.form['bundle'] = Upload('app.tar', app_bundle, 'application/x-tar')
|
|
|
|
with HTTMock(response_content):
|
|
|
|
resp.form.submit()
|
|
|
|
application = Application.objects.get(slug='test')
|
|
|
|
elements = application.elements.all().order_by('type')
|
2023-02-06 17:50:05 +01:00
|
|
|
assert len(elements) == 4
|
2022-11-21 15:22:10 +01:00
|
|
|
assert elements[0].cache == {}
|
2023-02-06 17:50:05 +01:00
|
|
|
assert elements[1].cache == {}
|
|
|
|
assert elements[2].cache == form_def
|
|
|
|
assert elements[3].cache == {}
|
2022-11-21 15:22:10 +01:00
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/export-import/forms/':
|
|
|
|
return {'status_code': 500}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
resp = app.get('/applications/')
|
|
|
|
if action == 'Update':
|
|
|
|
with HTTMock(mocked_http):
|
|
|
|
resp = resp.click(href='manifest/test/')
|
|
|
|
resp = resp.click(action)
|
|
|
|
resp.form['bundle'] = Upload('app.tar', app_bundle, 'application/x-tar')
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(ScanError) as e:
|
|
|
|
resp.form.submit()
|
|
|
|
assert str(e.value) == 'Failed to get elements of type forms (500)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to get elements of type forms (500)'
|
|
|
|
|
2022-08-09 15:28:17 +02:00
|
|
|
|
2022-11-17 17:55:27 +01:00
|
|
|
def test_update_application(app, admin_user, settings, app_bundle):
|
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
|
|
|
|
application = Application.objects.create(name='Test', slug='test', editable=True)
|
|
|
|
|
|
|
|
app.get('/applications/manifest/test/update/', status=404)
|
|
|
|
|
|
|
|
application.editable = False
|
|
|
|
application.slug = 'wrong'
|
|
|
|
application.save()
|
|
|
|
|
|
|
|
resp = app.get('/applications/manifest/wrong/update/')
|
|
|
|
resp.form['bundle'] = Upload('app.tar', app_bundle, 'application/x-tar')
|
|
|
|
resp = resp.form.submit()
|
|
|
|
assert resp.context['form'].errors == {'bundle': ['Can not update this application, wrong slug (test).']}
|
|
|
|
|
|
|
|
|
2022-08-09 15:28:17 +02:00
|
|
|
@pytest.fixture
|
|
|
|
def app_bundle_roles():
|
|
|
|
tar_io = io.BytesIO()
|
|
|
|
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
|
|
|
manifest_json = {
|
|
|
|
'application': 'Test',
|
|
|
|
'slug': 'test',
|
|
|
|
'description': '',
|
|
|
|
'elements': [
|
|
|
|
{'type': 'forms', 'slug': 'test', 'name': 'test', 'auto-dependency': False},
|
|
|
|
{'type': 'roles', 'slug': 'test-role', 'name': 'test', 'auto-dependency': True},
|
|
|
|
{'type': 'roles', 'slug': 'test-role2', 'name': 'test2', 'auto-dependency': True},
|
|
|
|
],
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
|
|
|
|
role_json = {'name': 'Test', 'slug': 'test-role', 'uuid': '061e5de7023946c79a2f7f1273afc5a2'}
|
|
|
|
role_json_fd = io.BytesIO(json.dumps(role_json, indent=2).encode())
|
|
|
|
tarinfo = tarfile.TarInfo('roles/test-role')
|
|
|
|
tarinfo.size = len(role_json_fd.getvalue())
|
|
|
|
tar.addfile(tarinfo, fileobj=role_json_fd)
|
|
|
|
|
|
|
|
role_json = {'name': 'Test', 'slug': 'test-role2', 'uuid': '061e5de7023946c79a2f7f1273afc5a3'}
|
|
|
|
role_json_fd = io.BytesIO(json.dumps(role_json, indent=2).encode())
|
|
|
|
tarinfo = tarfile.TarInfo('roles/test-role2')
|
|
|
|
tarinfo.size = len(role_json_fd.getvalue())
|
|
|
|
tar.addfile(tarinfo, fileobj=role_json_fd)
|
|
|
|
|
|
|
|
return tar_io.getvalue()
|
|
|
|
|
|
|
|
|
|
|
|
def test_deploy_application_roles(app, admin_user, settings, app_bundle_roles):
|
|
|
|
Application.objects.all().delete()
|
|
|
|
Authentic.objects.create(base_url='https://idp.example.invalid', slug='idp', title='Foobar')
|
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'authentic': {
|
|
|
|
'idp': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://idp.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
|
|
|
|
resp = app.get('/applications/')
|
|
|
|
for i in range(2):
|
|
|
|
resp = resp.click('Install')
|
|
|
|
resp.form['bundle'] = Upload('app.tar', app_bundle_roles, 'application/x-tar')
|
|
|
|
with HTTMock(httmock.remember_called(mocked_http)):
|
|
|
|
resp = resp.form.submit().follow()
|
|
|
|
# roles
|
|
|
|
assert mocked_http.call['requests'][0].url.startswith(
|
|
|
|
'https://idp.example.invalid/api/roles/?update'
|
|
|
|
)
|
|
|
|
assert mocked_http.call['requests'][1].url.startswith(
|
|
|
|
'https://idp.example.invalid/api/provision/'
|
|
|
|
)
|
|
|
|
assert mocked_http.call['requests'][2].url.startswith(
|
|
|
|
'https://idp.example.invalid/api/roles/?update'
|
|
|
|
)
|
|
|
|
assert mocked_http.call['requests'][3].url.startswith(
|
|
|
|
'https://idp.example.invalid/api/provision/'
|
|
|
|
)
|
|
|
|
# then form
|
|
|
|
assert 'wcs.example.invalid' in mocked_http.call['requests'][4].url
|
2022-11-21 15:22:10 +01:00
|
|
|
assert mocked_http.call['requests'][5].url.startswith(
|
|
|
|
'https://wcs.example.invalid/api/export-import/?'
|
|
|
|
)
|
|
|
|
# then element refresh
|
|
|
|
available_objects = [o for o in WCS_AVAILABLE_OBJECTS['data'] if o['id'] in ['forms', 'roles']]
|
|
|
|
assert mocked_http.call['count'] == 5 + 1 + len(available_objects)
|
|
|
|
for i, object_type in enumerate(available_objects):
|
|
|
|
assert mocked_http.call['requests'][6 + i].url.startswith(
|
|
|
|
'https://wcs.example.invalid/api/export-import/%s/?' % object_type['id']
|
|
|
|
)
|
2022-11-02 17:00:17 +01:00
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/roles/':
|
|
|
|
return {'status_code': 500}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
resp = app.get('/applications/install/')
|
|
|
|
resp.form['bundle'] = Upload('app.tar', app_bundle_roles, 'application/x-tar')
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(DeploymentError) as e:
|
|
|
|
resp.form.submit()
|
|
|
|
assert str(e.value) == 'Failed to create role test-role (500)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to create role test-role (500)'
|
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path == '/api/provision/':
|
|
|
|
return {'status_code': 500}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
resp = app.get('/applications/install/')
|
|
|
|
resp.form['bundle'] = Upload('app.tar', app_bundle_roles, 'application/x-tar')
|
|
|
|
with HTTMock(response_content):
|
|
|
|
with pytest.raises(DeploymentError) as e:
|
|
|
|
resp.form.submit()
|
|
|
|
assert str(e.value) == 'Failed to provision role test-role (500)'
|
|
|
|
job = AsyncJob.objects.latest('pk')
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to provision role test-role (500)'
|
2022-11-17 15:15:32 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_job_status_page(app, admin_user, settings):
|
|
|
|
Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
|
|
|
|
application = Application.objects.create(name='Test', slug='test')
|
|
|
|
|
|
|
|
settings.KNOWN_SERVICES = {
|
|
|
|
'wcs': {
|
|
|
|
'foobar': {
|
|
|
|
'title': 'Foobar',
|
|
|
|
'url': 'https://wcs.example.invalid/',
|
|
|
|
'orig': 'example.org',
|
|
|
|
'secret': 'xxx',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
login(app)
|
|
|
|
|
|
|
|
for action in ['scandeps', 'create_bundle']:
|
|
|
|
job = AsyncJob.objects.create(
|
|
|
|
label=action,
|
|
|
|
application=application,
|
|
|
|
action=action,
|
|
|
|
status='running',
|
|
|
|
)
|
|
|
|
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
|
|
|
assert 'Please wait…' in resp
|
|
|
|
assert 'window.location.reload' in resp
|
|
|
|
assert 'window.location = "/applications/manifest/test/"' not in resp
|
|
|
|
|
|
|
|
job.status = 'failed'
|
|
|
|
job.exception = 'foo bar exception'
|
|
|
|
job.save()
|
|
|
|
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
|
|
|
assert 'Error running the job.' in resp
|
|
|
|
assert 'Please wait…' not in resp
|
|
|
|
assert 'window.location.reload' not in resp
|
|
|
|
assert 'window.location = "/applications/manifest/test/"' not in resp
|
|
|
|
|
|
|
|
job = AsyncJob.objects.create(
|
|
|
|
label='deploy',
|
|
|
|
application=application,
|
|
|
|
action='deploy',
|
|
|
|
progression_urls={'wcs': {'Foobar': 'https://wcs.example.invalid/api/jobs/job-uuid/'}},
|
|
|
|
status='completed', # completed, with async job on wcs part
|
|
|
|
)
|
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path.startswith('/api/jobs/'):
|
|
|
|
return {
|
|
|
|
'content': json.dumps({'err': 0, 'data': {'status': 'running', 'completion_status': '42%'}}),
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
with HTTMock(response_content):
|
|
|
|
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
|
|
|
assert 'Please wait…' in resp
|
|
|
|
assert 'Running: 42%' in resp
|
|
|
|
assert 'window.location.reload' in resp
|
|
|
|
assert 'window.location = "/applications/manifest/test/"' not in resp
|
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path.startswith('/api/jobs/'):
|
|
|
|
return {
|
|
|
|
'content': json.dumps(
|
|
|
|
{'err': 0, 'data': {'status': 'completed', 'completion_status': '100%'}}
|
|
|
|
),
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
with HTTMock(response_content):
|
|
|
|
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
|
|
|
assert 'Please wait…' in resp
|
|
|
|
assert 'Completed: 100%' in resp
|
|
|
|
assert 'window.location.reload' not in resp
|
|
|
|
assert 'window.location = "/applications/manifest/test/"' in resp
|
|
|
|
|
|
|
|
job.status = 'failed'
|
|
|
|
job.exception = 'foo bar exception'
|
|
|
|
job.save()
|
|
|
|
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
|
|
|
assert 'Error running the job.' in resp
|
|
|
|
assert 'Please wait…' not in resp
|
|
|
|
assert 'window.location.reload' not in resp
|
|
|
|
assert 'window.location = "/applications/manifest/test/"' not in resp
|
|
|
|
|
|
|
|
job.status = 'running'
|
|
|
|
job.exception = ''
|
|
|
|
job.save()
|
|
|
|
|
|
|
|
def response_content(url, request):
|
|
|
|
if url.path.startswith('/api/jobs/'):
|
|
|
|
return {
|
|
|
|
'content': json.dumps({'err': 0, 'data': {'status': 'failed', 'completion_status': '42%'}}),
|
|
|
|
'status_code': 200,
|
|
|
|
}
|
|
|
|
return mocked_http(url, request)
|
|
|
|
|
|
|
|
with HTTMock(response_content):
|
|
|
|
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
|
|
|
assert 'Error running the job.' in resp
|
|
|
|
assert 'Please wait…' not in resp
|
|
|
|
assert 'window.location.reload' not in resp
|
|
|
|
assert 'window.location = "/applications/manifest/test/"' not in resp
|
|
|
|
job.refresh_from_db()
|
|
|
|
assert job.status == 'failed'
|
|
|
|
assert job.exception == 'Failed to deploy module wcs'
|