applications: mark elements with a not-installed status at first (#81783)
gitea/hobo/pipeline/head This commit looks good
Details
gitea/hobo/pipeline/head This commit looks good
Details
This commit is contained in:
parent
5c2b51f671
commit
3447df418f
|
@ -24,7 +24,9 @@ class Migration(migrations.Migration):
|
|||
model_name='relation',
|
||||
name='error_status',
|
||||
field=models.CharField(
|
||||
choices=[('notfound', 'Not Found'), ('error', 'Error')], max_length=100, null=True
|
||||
choices=[('notfound', 'Not Found'), ('error', 'Error'), ('not-installed', 'Not installed')],
|
||||
max_length=100,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -140,6 +140,7 @@ class Application(models.Model):
|
|||
if not remote_elements[element.type].get(element.slug):
|
||||
continue
|
||||
remote_element = remote_elements[element.type][element.slug]
|
||||
relation.reset_error()
|
||||
if cache_only:
|
||||
if element.cache == remote_element:
|
||||
continue
|
||||
|
@ -166,7 +167,7 @@ class Application(models.Model):
|
|||
continue
|
||||
response = requests.get(dependencies_url)
|
||||
if not response.ok:
|
||||
rel.set_error(response.status_code)
|
||||
rel.set_error_from_http_code(response.status_code)
|
||||
raise ScanError(
|
||||
_('Failed to scan "%s" (type %s, slug %s) dependencies (%s)')
|
||||
% (el.name, el.type, el.slug, response.status_code)
|
||||
|
@ -259,6 +260,7 @@ class Relation(models.Model):
|
|||
choices=[
|
||||
('notfound', _('Not Found')),
|
||||
('error', _('Error')),
|
||||
('not-installed', _('Not installed')),
|
||||
],
|
||||
null=True,
|
||||
)
|
||||
|
@ -266,11 +268,14 @@ class Relation(models.Model):
|
|||
def __repr__(self):
|
||||
return '<Relation %s - %s/%s>' % (self.application.slug, self.element.type, self.element.slug)
|
||||
|
||||
def set_error(self, http_status_code):
|
||||
def set_error(self, status):
|
||||
self.error = True
|
||||
self.error_status = 'notfound' if http_status_code == 404 else 'error'
|
||||
self.error_status = status
|
||||
self.save()
|
||||
|
||||
def set_error_from_http_code(self, http_status_code):
|
||||
self.set_error('notfound' if http_status_code == 404 else 'error')
|
||||
|
||||
def reset_error(self):
|
||||
self.error = False
|
||||
self.error_status = None
|
||||
|
|
|
@ -462,6 +462,7 @@ class ConfirmInstall(TemplateView):
|
|||
element=element,
|
||||
auto_dependency=element_dict['auto-dependency'],
|
||||
)
|
||||
relation.set_error('not-installed')
|
||||
relation.save()
|
||||
self.version.bundle.seek(0)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import base64
|
||||
import copy
|
||||
import io
|
||||
import json
|
||||
import random
|
||||
|
@ -26,6 +27,7 @@ from hobo.environment.models import Authentic, Variable, Wcs
|
|||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
WCS_AVAILABLE_OBJECTS = {
|
||||
'data': [
|
||||
{
|
||||
|
@ -160,6 +162,16 @@ WCS_FORM_DEPENDENCIES = {
|
|||
}
|
||||
|
||||
|
||||
class StatefulHTTMock(HTTMock):
|
||||
def __enter__(self):
|
||||
self.orig_wcs_available_forms = copy.deepcopy(WCS_AVAILABLE_FORMS)
|
||||
return super().__enter__()
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
WCS_AVAILABLE_FORMS['data'] = self.orig_wcs_available_forms['data']
|
||||
return super().__exit__(*args, **kwargs)
|
||||
|
||||
|
||||
def mocked_http(url, request):
|
||||
assert url.query.count('&signature=') == 1
|
||||
|
||||
|
@ -195,6 +207,18 @@ def mocked_http(url, request):
|
|||
return {'content': '<carddef/>', 'status_code': 200, 'headers': {'content-length': '10'}}
|
||||
|
||||
if url.path == '/api/export-import/bundle-import/':
|
||||
# alter WCS_AVAILABLE_FORMS response with newly installed forms
|
||||
with tarfile.open(mode='r', fileobj=io.BytesIO(request.body)) as tar:
|
||||
manifest_json = json.load(tar.extractfile('manifest.json'))
|
||||
for element in manifest_json.get('elements'):
|
||||
if element['type'] not in [x['id'] for x in WCS_AVAILABLE_OBJECTS['data']]:
|
||||
continue
|
||||
if element['type'] == 'forms' and element['slug'] not in [
|
||||
x['id'] for x in WCS_AVAILABLE_FORMS['data']
|
||||
]:
|
||||
WCS_AVAILABLE_FORMS['data'].append(
|
||||
{'id': element['slug'], 'text': element['name'], 'type': 'forms'}
|
||||
)
|
||||
return {
|
||||
'content': json.dumps({'err': 0, 'url': 'https://wcs.example.invalid/api/jobs/job-uuid/'}),
|
||||
'status_code': 200,
|
||||
|
@ -246,7 +270,7 @@ def test_create_application(app, admin_user, settings, analyze):
|
|||
resp.form['name'] = 'Test'
|
||||
resp = resp.form.submit()
|
||||
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = resp.follow()
|
||||
assert 'You should now assemble the different parts of your application.' in resp.text
|
||||
|
||||
|
@ -417,7 +441,7 @@ def test_create_application(app, admin_user, settings, analyze):
|
|||
return mocked_http(url, request)
|
||||
|
||||
resp = app.get('/applications/manifest/test/generate/')
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
with pytest.raises(DeploymentError) as e:
|
||||
resp.form.submit()
|
||||
assert str(e.value) == 'Failed to declare elements for module wcs (500)'
|
||||
|
@ -476,7 +500,7 @@ def test_manifest_ordering(app, admin_user, settings):
|
|||
Relation.objects.create(application=application, element=element, auto_dependency=auto_dependency)
|
||||
|
||||
login(app)
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = app.get('/applications/manifest/test/')
|
||||
assert resp.pyquery('.application-content li a').text() == (
|
||||
'Baaaar - Form remove '
|
||||
|
@ -528,7 +552,7 @@ def test_scandeps_on_unknown_element(app, admin_user, settings):
|
|||
|
||||
login(app)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(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)'
|
||||
|
@ -544,7 +568,7 @@ def test_scandeps_on_unknown_element(app, admin_user, settings):
|
|||
return {'status_code': 500}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(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)'
|
||||
|
@ -555,7 +579,7 @@ def test_scandeps_on_unknown_element(app, admin_user, settings):
|
|||
assert relation.error is True
|
||||
assert relation.error_status == 'error'
|
||||
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
app.get('/applications/manifest/test/scandeps/').follow()
|
||||
job = AsyncJob.objects.latest('pk')
|
||||
assert job.status == 'completed'
|
||||
|
@ -639,7 +663,7 @@ def test_scandeps_on_renamed_element(app, admin_user, settings):
|
|||
return mocked_http(url, request)
|
||||
|
||||
login(app)
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
app.get('/applications/manifest/test/scandeps/').follow()
|
||||
job = AsyncJob.objects.latest('pk')
|
||||
assert job.status == 'completed'
|
||||
|
@ -671,7 +695,7 @@ def test_scandeps_on_renamed_element(app, admin_user, settings):
|
|||
return {'status_code': 404}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(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)'
|
||||
|
@ -684,7 +708,7 @@ def test_scandeps_on_renamed_element(app, admin_user, settings):
|
|||
return {'status_code': 500}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(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)'
|
||||
|
@ -742,7 +766,7 @@ def test_redirect_application_element(app, admin_user, settings, editable):
|
|||
|
||||
login(app)
|
||||
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = app.get('/applications/manifest/test/')
|
||||
assert 'https://wcs.example.invalid/api/export-import/forms/test-form/redirect/' in resp
|
||||
assert (
|
||||
|
@ -777,7 +801,7 @@ def test_delete_application(app, admin_user, settings):
|
|||
|
||||
assert Application.objects.count() == 2
|
||||
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = app.get('/applications/manifest/app_to_delete/')
|
||||
resp = resp.click(re.compile('^Delete$'))
|
||||
resp = resp.forms[0].submit()
|
||||
|
@ -794,7 +818,7 @@ def test_delete_application(app, admin_user, settings):
|
|||
return {'status_code': 500}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
resp = app.get('/applications/manifest/other_app/')
|
||||
resp = resp.click(re.compile('^Delete$'))
|
||||
resp = resp.forms[0].submit()
|
||||
|
@ -885,7 +909,7 @@ def test_deploy_application(app, admin_user, settings, app_bundle, app_bundle_wi
|
|||
def install(resp, bundle):
|
||||
resp = resp.click(action)
|
||||
resp.form['bundle'] = Upload('app.tar', bundle, 'application/x-tar')
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert Application.objects.count() == 1
|
||||
|
@ -908,11 +932,14 @@ def test_deploy_application(app, admin_user, settings, app_bundle, app_bundle_wi
|
|||
assert job.progression_urls == {'wcs': {'Foobar': 'https://wcs.example.invalid/api/jobs/job-uuid/'}}
|
||||
assert Parameter.objects.filter(application=application).count() == 2
|
||||
assert Variable.objects.filter(name__startswith='app_').count() == 2
|
||||
# check forms are not marked as error (the other object types are not altered in the mocked responses
|
||||
# and would be marked as errors)
|
||||
assert [x.error for x in application.relation_set.filter(element__type='forms')] == [False]
|
||||
return version
|
||||
|
||||
resp = app.get('/applications/')
|
||||
if action == 'Update':
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = resp.click(href='/manifest/test/')
|
||||
version1 = install(resp, app_bundle)
|
||||
if action == 'Update':
|
||||
|
@ -950,14 +977,19 @@ def test_deploy_application(app, admin_user, settings, app_bundle, app_bundle_wi
|
|||
|
||||
resp = app.get('/applications/')
|
||||
if action == 'Update':
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(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 StatefulHTTMock(response_content):
|
||||
with pytest.raises(DeploymentError) as e:
|
||||
resp.form.submit().follow()
|
||||
assert str(e.value) == 'Failed to deploy module wcs (500)'
|
||||
application = Application.objects.get(slug='test')
|
||||
# check elements are marked as not installed if there was a failure
|
||||
assert [
|
||||
(x.error, x.error_status) for x in application.relation_set.filter(element__type='forms')
|
||||
] == [(True, 'not-installed')]
|
||||
job = AsyncJob.objects.latest('pk')
|
||||
assert job.status == 'failed'
|
||||
assert job.exception == 'Failed to deploy module wcs (500)'
|
||||
|
@ -983,11 +1015,11 @@ def test_deploy_application(app, admin_user, settings, app_bundle, app_bundle_wi
|
|||
|
||||
resp = app.get('/applications/')
|
||||
if action == 'Update':
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(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 StatefulHTTMock(response_content):
|
||||
resp.form.submit().follow()
|
||||
application = Application.objects.get(slug='test')
|
||||
elements = application.elements.all().order_by('type')
|
||||
|
@ -1004,11 +1036,11 @@ def test_deploy_application(app, admin_user, settings, app_bundle, app_bundle_wi
|
|||
|
||||
resp = app.get('/applications/')
|
||||
if action == 'Update':
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(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 StatefulHTTMock(response_content):
|
||||
with pytest.raises(ScanError) as e:
|
||||
resp.form.submit().follow()
|
||||
assert str(e.value) == 'Failed to get elements of type forms (500)'
|
||||
|
@ -1019,7 +1051,7 @@ def test_deploy_application(app, admin_user, settings, app_bundle, app_bundle_wi
|
|||
# bad file format
|
||||
resp = app.get('/applications/')
|
||||
if action == 'Update':
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = resp.click(href='manifest/test/')
|
||||
resp = resp.click(action)
|
||||
resp.form['bundle'] = Upload('app.tar', b'garbage', 'application/x-tar')
|
||||
|
@ -1061,7 +1093,7 @@ def test_update_application(app, admin_user, settings, app_bundle):
|
|||
application.save()
|
||||
resp = app.get('/applications/manifest/test/update/')
|
||||
resp.form['bundle'] = Upload('app.tar', app_bundle, 'application/x-tar')
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = resp.form.submit()
|
||||
# only legacy elements, do the update and redirect to manifest
|
||||
last_version = Version.objects.latest('pk')
|
||||
|
@ -1092,7 +1124,7 @@ def test_update_application(app, admin_user, settings, app_bundle):
|
|||
Version.objects.filter(pk=last_version.pk).update(number='1.0')
|
||||
resp = app.get('/applications/manifest/test/update/')
|
||||
resp.form['bundle'] = Upload('app.tar', app_bundle, 'application/x-tar')
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
resp = resp.form.submit()
|
||||
last_version = Version.objects.latest('pk')
|
||||
assert resp.location.endswith('/applications/manifest/test/confirm/%s/' % last_version.pk)
|
||||
|
@ -1138,7 +1170,7 @@ def test_update_application(app, admin_user, settings, app_bundle):
|
|||
Version.objects.filter(pk=last_version.pk).update(number='2.0')
|
||||
resp = app.get('/applications/manifest/test/update/')
|
||||
resp.form['bundle'] = Upload('app.tar', app_bundle, 'application/x-tar')
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
resp = resp.form.submit()
|
||||
assert resp.location.endswith(
|
||||
'/applications/manifest/test/confirm/%s/' % Version.objects.latest('pk').pk
|
||||
|
@ -1277,7 +1309,7 @@ def test_refresh_application(app, admin_user, settings):
|
|||
}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
resp = app.get('/applications/manifest/test/refresh/')
|
||||
application = Application.objects.get(slug='test')
|
||||
element.refresh_from_db()
|
||||
|
@ -1291,7 +1323,7 @@ def test_refresh_application(app, admin_user, settings):
|
|||
|
||||
element.cache = {'foo': 'bar'}
|
||||
element.save()
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
resp = app.get('/applications/manifest/test/refresh/')
|
||||
application = Application.objects.get(slug='test')
|
||||
element.refresh_from_db()
|
||||
|
@ -1363,7 +1395,7 @@ def test_deploy_application_roles(app, admin_user, settings, app_bundle_roles):
|
|||
resp = app.get('/applications/')
|
||||
resp = resp.click('Install')
|
||||
resp.form['bundle'] = Upload('app.tar', app_bundle_roles, 'application/x-tar')
|
||||
with HTTMock(httmock.remember_called(mocked_http)):
|
||||
with StatefulHTTMock(httmock.remember_called(mocked_http)):
|
||||
resp = resp.form.submit().follow()
|
||||
# roles
|
||||
assert mocked_http.call['requests'][0].url.startswith(
|
||||
|
@ -1401,7 +1433,7 @@ def test_deploy_application_roles(app, admin_user, settings, app_bundle_roles):
|
|||
|
||||
resp = app.get('/applications/install/')
|
||||
resp.form['bundle'] = Upload('app.tar', app_bundle_roles, 'application/x-tar')
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
with pytest.raises(DeploymentError) as e:
|
||||
resp.form.submit().follow()
|
||||
assert str(e.value) == 'Failed to create role test-role (500)'
|
||||
|
@ -1416,7 +1448,7 @@ def test_deploy_application_roles(app, admin_user, settings, app_bundle_roles):
|
|||
|
||||
resp = app.get('/applications/install/')
|
||||
resp.form['bundle'] = Upload('app.tar', app_bundle_roles, 'application/x-tar')
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
with pytest.raises(DeploymentError) as e:
|
||||
resp.form.submit().follow()
|
||||
assert str(e.value) == 'Failed to provision role test-role (500)'
|
||||
|
@ -1479,7 +1511,7 @@ def test_job_status_page(app, admin_user, settings):
|
|||
}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
||||
assert 'Please wait…' in resp
|
||||
assert 'Running: 42%' in resp
|
||||
|
@ -1496,7 +1528,7 @@ def test_job_status_page(app, admin_user, settings):
|
|||
}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(response_content):
|
||||
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
||||
assert 'Please wait…' in resp
|
||||
assert 'Completed: 100%' in resp
|
||||
|
@ -1524,7 +1556,7 @@ def test_job_status_page(app, admin_user, settings):
|
|||
}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with HTTMock(response_content):
|
||||
with StatefulHTTMock(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
|
||||
|
@ -1556,7 +1588,7 @@ def test_create_application_parameters(app, admin_user, settings):
|
|||
resp.form['name'] = 'Test'
|
||||
resp = resp.form.submit()
|
||||
|
||||
with HTTMock(mocked_http):
|
||||
with StatefulHTTMock(mocked_http):
|
||||
resp = resp.follow()
|
||||
|
||||
# add a form (an element is required to have the generate app button)
|
||||
|
|
Loading…
Reference in New Issue