From 53b02052ef81e95548c34748e4e7e0c4bc7c1558 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Thu, 27 Jan 2022 11:37:45 +0100
Subject: [PATCH 2/2] toulouse_smart: do not crash on unknown property type
 (#60989)

---
 passerelle/contrib/toulouse_smart/models.py |  6 +++++
 tests/test_toulouse_smart.py                | 30 +++++++++++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/passerelle/contrib/toulouse_smart/models.py b/passerelle/contrib/toulouse_smart/models.py
index e849c523..020518b5 100644
--- a/passerelle/contrib/toulouse_smart/models.py
+++ b/passerelle/contrib/toulouse_smart/models.py
@@ -177,16 +177,22 @@ class ToulouseSmartResource(BaseResource, HTTPResource):
             block = post_data['fields']['%s_raw' % wcs_block_varname][0]
         except (KeyError, TypeError):
             block = {}
         data = {}
         cast = {'string': str, 'int': int, 'boolean': bool, 'item': str}
         for prop in intervention_type.get('properties') or []:
             varname = slugify(prop['name']).replace('-', '_')
             if block.get(varname):
+                if prop['type'] not in cast:
+                    raise APIError(
+                        'unmanaged "%s" type for the %s/%s property'
+                        % (prop['type'], intervention_type['name'], prop['name']),
+                        http_status=400,
+                    )
                 try:
                     data[prop['name']] = cast[prop['type']](block[varname])
                 except ValueError:
                     raise APIError(
                         "cannot cast '%s' field to %s : '%s'" % (varname, cast[prop['type']], block[varname]),
                         http_status=400,
                     )
             elif prop['required']:
diff --git a/tests/test_toulouse_smart.py b/tests/test_toulouse_smart.py
index 41d7be9e..a2f1d8fe 100644
--- a/tests/test_toulouse_smart.py
+++ b/tests/test_toulouse_smart.py
@@ -155,16 +155,21 @@ INTERVENTION_TYPES = '''<List>
               <displayName>Champ 2</displayName>
               <type>int</type>
               <required>true</required>
            </properties>
            <properties>
               <name>IGNORED-FIELD-HAVING-NO-TYPE</name>
               <displayName>Champ 3</displayName>
            </properties>
+          <properties>
+              <name>FIELD3</name>
+              <displayName>Champ 3</displayName>
+              <type>plop</type>
+           </properties>
        </properties>
    </item>
    <item>
        <id>0002</id>
        <name>empty</name>
    </item>
 </List>'''.encode()
 
@@ -181,16 +186,17 @@ def test_model_intervention_types(smart):
                     'name': 'TYPE-OBJET',
                     'displayName': 'Champ 1',
                     'required': False,
                     'type': 'item',
                     'defaultValue': 'Ne sait pas',
                     'restrictedValues': ['Candélabre', 'Mât', 'Ne sait pas'],
                 },
                 {'name': 'FIELD2', 'displayName': 'Champ 2', 'required': True, 'type': 'int'},
+                {'name': 'FIELD3', 'displayName': 'Champ 3', 'required': False, 'type': 'plop'},
             ],
         },
         {
             'id': '0002',
             'name': 'empty',
             'order': 2,
         },
     ]
@@ -221,16 +227,17 @@ def test_endpoint_intervention_types_unavailable(app, smart):
 def test_manage_intervention_types(app, smart, admin_user):
     login(app)
     resp = app.get('/manage' + URL + 'type-intervention/')
     assert [[td.text for td in tr.cssselect('td,th')] for tr in resp.pyquery('tr')] == [
         ["Nom du type d'intervention", 'Nom', 'Type', 'Requis', 'Valeur par défaut'],
         ['1 - coin'],
         [None, 'TYPE-OBJET', 'item («Candélabre», «Mât», «Ne sait pas»)', '✘', 'Ne sait pas'],
         [None, 'FIELD2', 'int', '✔', None],
+        [None, 'FIELD3', 'plop', '✘', None],
         ['2 - empty'],
     ]
     resp = resp.click('Export to blocks')
     with zipfile.ZipFile(io.BytesIO(resp.body)) as zip_file:
         assert zip_file.namelist() == ['block-coin.wcs']
         with zip_file.open('block-coin.wcs') as fd:
             content = ET.tostring(ET.fromstring(fd.read()), pretty_print=True).decode()
             assert (
@@ -264,16 +271,27 @@ def test_manage_intervention_types(app, smart, admin_user):
       <display_locations>
         <display_location>validation</display_location>
         <display_location>summary</display_location>
       </display_locations>
       <validation>
         <type>digits</type>
       </validation>
     </field>
+    <field>
+      <id>66c8a74e-cd26-eb34-2bec-9c0c4ec43841</id>
+      <label>Champ 3</label>
+      <type>plop</type>
+      <required>False</required>
+      <varname>field3</varname>
+      <display_locations>
+        <display_location>validation</display_location>
+        <display_location>summary</display_location>
+      </display_locations>
+    </field>
   </fields>
 </block>
 '''
             )
 
 
 INTERVENTION_ID = json.loads(get_json_file('create_intervention'))['id']
 
@@ -486,16 +504,28 @@ def test_create_intervention_no_block(app, smart):
 def test_create_intervention_cast_error(app, smart):
     payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
     payload['fields']['coin_raw'][0]['field2'] = 'not-an-integer'
     resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
     assert resp.json['err']
     assert "cannot cast 'field2' field to <class 'int'>" in resp.json['err_desc']
 
 
+@mock_response(
+    ['/v1/type-intervention', None, INTERVENTION_TYPES],
+)
+@mock.patch("django.db.models.fields.UUIDField.get_default", return_value=UUID)
+def test_create_intervention_unmanaged_type_to_cast(mocked_uuid4, app, smart):
+    payload = deepcopy(CREATE_INTERVENTION_PAYLOAD)
+    payload['fields']['coin_raw'][0]['field3'] = 'plop value'
+    resp = app.post_json(URL + 'create-intervention/', params=payload, status=400)
+    assert resp.json['err']
+    assert resp.json['err_desc'] == 'unmanaged "plop" type for the coin/FIELD3 property'
+
+
 @mock_response(
     ['/v1/type-intervention', None, INTERVENTION_TYPES],
 )
 def test_create_intervention_missing_value(app, smart):
     field_payload = {
         'coin_raw': [
             {
                 'type_objet': 'Candélabre',
-- 
2.30.2

