From 2190924e4ce4b9cb4dcb06b742c58f2bae11fa46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Thu, 4 Nov 2021 09:26:45 +0100
Subject: [PATCH 3/4] formdefs: report all missing things in one time (#57753)

---
 tests/test_formdef_import.py  | 30 ++++++++++++++++++++++++++++--
 tests/test_workflow_import.py | 16 ++++++++++++----
 wcs/formdef.py                | 15 ++++++++++-----
 3 files changed, 50 insertions(+), 11 deletions(-)

diff --git a/tests/test_formdef_import.py b/tests/test_formdef_import.py
index 7707d394..b74e1f5b 100644
--- a/tests/test_formdef_import.py
+++ b/tests/test_formdef_import.py
@@ -396,8 +396,10 @@ def test_unknown_data_source(pub):
 
     formdef.fields = [fields.StringField(id='1', type='string', data_source={'type': 'foobar'})]
     export = ET.tostring(export_to_indented_xml(formdef))
-    with pytest.raises(FormdefImportError, match='Unknown datasources'):
+    with pytest.raises(FormdefImportError) as excinfo:
         FormDef.import_from_xml(io.BytesIO(export))
+    assert excinfo.value.msg == 'Unknown referenced objects'
+    assert excinfo.value.details == 'Unknown datasources: foobar'
 
     # carddef as datasource
     carddef = CardDef()
@@ -454,8 +456,10 @@ def test_formdef_with_block(pub):
 
     BlockDef.wipe()
     export = ET.tostring(export_to_indented_xml(formdef))
-    with pytest.raises(FormdefImportError, match='Unknown field type'):
+    with pytest.raises(FormdefImportError) as excinfo:
         FormDef.import_from_xml(io.BytesIO(export))
+    assert excinfo.value.msg == 'Unknown referenced objects'
+    assert excinfo.value.details == 'Unknown field types: block:foobar'
 
 
 def test_duplicated_field_ids(pub):
@@ -824,3 +828,25 @@ def test_custom_views(pub):
     assert custom_views[0].order_by == 'receipt_time'
     assert custom_views[0].formdef_id == formdef2.id
     assert custom_views[0].formdef_type == 'formdef'
+
+
+def test_import_formdef_multiple_errors(pub):
+    BlockDef.wipe()
+
+    formdef = FormDef()
+    formdef.name = 'foo'
+    formdef.fields = [
+        fields.BlockField(id='1', type='block:foobar'),
+        fields.BlockField(id='2', type='block:foobaz'),
+        fields.StringField(id='3', type='string', data_source={'type': 'foobar'}),
+        fields.StringField(id='4', type='string', data_source={'type': 'carddef:unknown'}),
+    ]
+
+    export = ET.tostring(export_to_indented_xml(formdef))
+    with pytest.raises(FormdefImportError) as excinfo:
+        FormDef.import_from_xml(io.BytesIO(export))
+    assert excinfo.value.msg == 'Unknown referenced objects'
+    assert (
+        excinfo.value.details
+        == 'Unknown field types: block:foobar, block:foobaz; Unknown datasources: carddef:unknown, foobar'
+    )
diff --git a/tests/test_workflow_import.py b/tests/test_workflow_import.py
index 27da47c3..a2209414 100644
--- a/tests/test_workflow_import.py
+++ b/tests/test_workflow_import.py
@@ -927,8 +927,10 @@ def test_workflow_with_unknown_data_source(pub):
 
     for wf in [wf1, wf2, wf3]:
         export = ET.tostring(export_to_indented_xml(wf))
-        with pytest.raises(WorkflowImportError, match='Unknown datasources'):
+        with pytest.raises(WorkflowImportError) as excinfo:
             Workflow.import_from_xml(io.BytesIO(export))
+        assert excinfo.value.msg == 'Unknown referenced objects'
+        assert excinfo.value.details == 'Unknown datasources: foobar'
 
     # carddef as datasource
     CardDef.wipe()
@@ -951,8 +953,10 @@ def test_workflow_with_unknown_data_source(pub):
 
     for wf in [wf1, wf2, wf3]:
         export = ET.tostring(export_to_indented_xml(wf))
-        with pytest.raises(WorkflowImportError, match='Unknown datasources'):
+        with pytest.raises(WorkflowImportError) as excinfo:
             Workflow.import_from_xml(io.BytesIO(export))
+        assert excinfo.value.msg == 'Unknown referenced objects'
+        assert excinfo.value.details == 'Unknown datasources: carddef:unknown'
 
     # carddef custom view as datasource
     pub.custom_view_class.wipe()
@@ -978,8 +982,10 @@ def test_workflow_with_unknown_data_source(pub):
 
     for wf in [wf1, wf2, wf3]:
         export = ET.tostring(export_to_indented_xml(wf))
-        with pytest.raises(WorkflowImportError, match='Unknown datasources'):
+        with pytest.raises(WorkflowImportError) as excinfo:
             Workflow.import_from_xml(io.BytesIO(export))
+        assert excinfo.value.msg == 'Unknown referenced objects'
+        assert excinfo.value.details == 'Unknown datasources: carddef:foo:unknown'
 
 
 def test_workflow_with_block(pub):
@@ -1012,8 +1018,10 @@ def test_workflow_with_block(pub):
     BlockDef.wipe()
     for wf in [wf1, wf2, wf3]:
         export = ET.tostring(export_to_indented_xml(wf))
-        with pytest.raises(WorkflowImportError, match='Unknown field type'):
+        with pytest.raises(WorkflowImportError) as excinfo:
             Workflow.import_from_xml(io.BytesIO(export))
+        assert excinfo.value.msg == 'Unknown referenced objects'
+        assert excinfo.value.details == 'Unknown field types: block:foobar'
 
 
 def test_workflow_with_category(pub):
diff --git a/wcs/formdef.py b/wcs/formdef.py
index a9cf013a..215b6bb4 100644
--- a/wcs/formdef.py
+++ b/wcs/formdef.py
@@ -1190,11 +1190,13 @@ class FormDef(StorableObject):
             setattr(formdef, boolean_attribute, value.text == 'true')
 
         formdef.fields = []
+        unknown_field_types = set()
         for i, field in enumerate(tree.find('fields')):
             try:
                 field_o = fields.get_field_class_by_type(field.findtext('type'))()
             except KeyError:
-                raise FormdefImportError(_('Unknown field type'), details=field.findtext('type'))
+                unknown_field_types.add(field.findtext('type'))
+                continue
             field_o.init_with_xml(field, charset, include_id=True)
             if fix_on_error or not field_o.id:
                 # this assumes all fields will have id, or none of them
@@ -1319,9 +1321,9 @@ class FormDef(StorableObject):
                 value = xml_node_text(child)
                 formdef.digest_templates[key] = value
 
+        unknown_datasources = set()
         if check_datasources:
             # check if datasources are defined
-            unknown_datasources = set()
             for field in formdef.fields:
                 data_source = getattr(field, 'data_source', None)
                 if data_source:
@@ -1354,10 +1356,13 @@ class FormDef(StorableObject):
                         except IndexError:
                             unknown_datasources.add(data_source_id)
 
+        if unknown_field_types or unknown_datasources:
+            details = []
+            if unknown_field_types:
+                details.append('%s: %s' % (_('Unknown field types'), ', '.join(sorted(unknown_field_types))))
             if unknown_datasources:
-                raise FormdefImportError(
-                    _('Unknown datasources'), details=', '.join(sorted(unknown_datasources))
-                )
+                details.append('%s: %s' % (_('Unknown datasources'), ', '.join(sorted(unknown_datasources))))
+            raise FormdefImportError(_('Unknown referenced objects'), details='; '.join(details))
 
         return formdef
 
-- 
2.33.0

