From 526ce31a1765b9f81c03b744f793268ecab986a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 3 Nov 2022 09:30:08 +0100
Subject: [PATCH] applications: report scan errors (#68017)

---
 hobo/applications/models.py                   | 19 ++++++-
 .../templates/hobo/applications/job.html      |  1 +
 tests/test_application.py                     | 53 ++++++++++++++++++-
 3 files changed, 70 insertions(+), 3 deletions(-)

diff --git a/hobo/applications/models.py b/hobo/applications/models.py
index 82a5726..72f49a6 100644
--- a/hobo/applications/models.py
+++ b/hobo/applications/models.py
@@ -37,11 +37,19 @@ from .utils import Requests
 requests = Requests()
 
 
-class DeploymentError(Exception):
+class ApplicationError(Exception):
     def __init__(self, msg):
         self.msg = msg
 
 
+class ScanError(ApplicationError):
+    pass
+
+
+class DeploymentError(ApplicationError):
+    pass
+
+
 class Application(models.Model):
     SUPPORTED_MODULES = ('wcs',)
 
@@ -89,6 +97,13 @@ class Application(models.Model):
                 if not dependencies_url:
                     continue
                 response = requests.get(dependencies_url)
+                if not response.ok:
+                    raise ScanError(
+                        _(
+                            'Failed to scan "%s" (type %s, slug %s) dependencies (%s)'
+                            % (el.name, el.type, el.slug, response.status_code)
+                        )
+                    )
                 for dependency in response.json()['data']:
                     if (dependency['type'], dependency['id']) in elements:
                         continue
@@ -289,7 +304,7 @@ class AsyncJob(models.Model):
                 self.version.create_bundle()
             elif self.action == 'deploy':
                 self.version.deploy()
-        except DeploymentError as e:
+        except ApplicationError as e:
             self.status = 'failed'
             self.exception = e.msg
             if self.raise_exception:
diff --git a/hobo/applications/templates/hobo/applications/job.html b/hobo/applications/templates/hobo/applications/job.html
index baafb0e..490902e 100644
--- a/hobo/applications/templates/hobo/applications/job.html
+++ b/hobo/applications/templates/hobo/applications/job.html
@@ -9,6 +9,7 @@
   {% if object.status == 'failed' %}
     <div class="pk-error">
       <p>{% trans "Error running the job." %}</p>
+      {% if object.exception %}<pre>{{ object.exception }}</pre>{% endif %}
       <p><a class="pk-button" href="{{ view.get_redirect_url }}">{% trans "Back" %}</a></p>
     </div>
   {% else %}
diff --git a/tests/test_application.py b/tests/test_application.py
index 3d7564d..9e5be0e 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -10,7 +10,15 @@ from httmock import HTTMock
 from test_manager import login
 from webtest import Upload
 
-from hobo.applications.models import Application, AsyncJob, DeploymentError, Element, Relation, Version
+from hobo.applications.models import (
+    Application,
+    AsyncJob,
+    DeploymentError,
+    Element,
+    Relation,
+    ScanError,
+    Version,
+)
 from hobo.environment.models import Authentic, Wcs
 
 pytestmark = pytest.mark.django_db
@@ -333,6 +341,49 @@ def test_create_application(app, admin_user, settings, analyze):
         app.get('/applications/manifest/test/delete/%s/' % application.relation_set.first().pk, status=404)
 
 
+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/",
+            }
+        },
+    )
+    Relation.objects.create(application=application, element=element)
+
+    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)'
+
+
 @pytest.mark.parametrize('editable', [True, False])
 def test_redirect_application_element(app, admin_user, settings, editable):
     Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
-- 
2.35.1

