From 646f542e54f81762c5326f21b68a701cebf5ea81 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Wed, 26 Jan 2022 17:06:14 +0100
Subject: [PATCH 2/2] toulouse_smart: do not crash on unmanaged 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 31c0bf73..178b1d67 100644
--- a/passerelle/contrib/toulouse_smart/models.py
+++ b/passerelle/contrib/toulouse_smart/models.py
@@ -177,16 +177,22 @@ class ToulouseSmartResource(BaseResource, HTTPResource):
         for prop in intervention_type.get('properties') or []:
             varname = slugify(prop['name']).replace('-', '_')
             if block.get(varname):
                 if 'type' not in prop:
                     raise APIError(
                         'unknwon type for the %s/%s property' % (intervention_type['name'], prop['name']),
                         http_status=400,
                     )
+                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 1df2d6ec..5cc5127c 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>FIELD3</name>
               <displayName>Champ 3</displayName>
            </properties>
+          <properties>
+              <name>FIELD4</name>
+              <displayName>Champ 4</displayName>
+              <type>plop</type>
+           </properties>
        </properties>
    </item>
    <item>
        <id>0002</id>
        <name>empty</name>
    </item>
 </List>'''.encode()
 
@@ -182,16 +187,17 @@ def test_model_intervention_types(smart):
                     '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},
+                {'name': 'FIELD4', 'displayName': 'Champ 4', 'required': False, 'type': 'plop'},
             ],
         },
         {
             'id': '0002',
             'name': 'empty',
             'order': 2,
         },
     ]
@@ -223,16 +229,17 @@ 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', None, '✘', None],
+        [None, 'FIELD4', '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 (
@@ -277,16 +284,27 @@ def test_manage_intervention_types(app, smart, admin_user):
       <type>string</type>
       <required>False</required>
       <varname>field3</varname>
       <display_locations>
         <display_location>validation</display_location>
         <display_location>summary</display_location>
       </display_locations>
     </field>
+    <field>
+      <id>5e9c34f3-ffd9-62c7-5006-79dbff961286</id>
+      <label>Champ 4</label>
+      <type>plop</type>
+      <required>False</required>
+      <varname>field4</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']
 
@@ -511,16 +529,28 @@ def test_create_intervention_cast_error(app, smart):
 def test_create_intervention_no_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'] == 'unknwon type for the coin/FIELD3 property'
 
 
+@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]['field4'] = '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/FIELD4 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

